Compare commits
1 Commits
develop
...
feature-WA
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a50eb8289 |
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
# Python-generated files
|
||||||
|
__pycache__/
|
||||||
|
*.py[oc]
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
wheels/
|
||||||
|
*.egg-info
|
||||||
|
.idea
|
||||||
|
# Virtual environments
|
||||||
|
.venv/
|
||||||
|
data/
|
||||||
|
logs/
|
||||||
|
report
|
||||||
|
screenshot/
|
||||||
|
temp/
|
||||||
|
xlsx/
|
||||||
|
uv.lock
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
"""
|
"""
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from selenium.webdriver import Chrome
|
from selenium.webdriver import Chrome,Edge
|
||||||
from selenium.webdriver.common.by import By
|
from selenium.webdriver.common.by import By
|
||||||
from selenium.webdriver.remote.webdriver import WebDriver
|
from selenium.webdriver.remote.webdriver import WebDriver
|
||||||
from commons.modules import Browser
|
from commons.modules import Browser
|
||||||
@@ -28,28 +28,28 @@ class LoginPage(KeyWordDriver):
|
|||||||
password = '//*[@id="pass"]'
|
password = '//*[@id="pass"]'
|
||||||
login_submit = '//*[@id="root"]/div[1]/div/div/form/div[3]/button'
|
login_submit = '//*[@id="root"]/div[1]/div/div/form/div[3]/button'
|
||||||
|
|
||||||
# def __init__(self, browser: Browser):
|
def __init__(self,driver: WebDriver | None = None):
|
||||||
# super().__init__(browser)
|
super().__init__(driver)
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
def login(self, email, password):
|
def login(self, email, password):
|
||||||
self.browser(Browser.EDGE)
|
|
||||||
|
# self.base_url("http://119.91.19.171:40065")
|
||||||
self.get(self.url)
|
self.get(self.url)
|
||||||
self.input(1, self.email, email)
|
self.input(1, self.email, email)
|
||||||
self.input(By.XPATH, self.password, password)
|
self.input(By.XPATH, self.password, password)
|
||||||
text = self.get_text(By.XPATH, self.email_title)
|
text = self.get_text(By.XPATH, self.email_title)
|
||||||
print(text)
|
print(text)
|
||||||
self.click(By.XPATH, self.login_submit)
|
self.click(By.XPATH, self.login_submit)
|
||||||
sleep(10)
|
# sleep(10)
|
||||||
|
# input()
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from commons.settings import configs
|
from commons.settings import configs
|
||||||
|
# _driver =Edge()
|
||||||
_email = configs.username
|
_email = configs.username
|
||||||
_password = configs.password
|
_password = configs.password
|
||||||
login = LoginPage()
|
login = LoginPage()
|
||||||
|
# login.browser(Browser.EDGE)
|
||||||
login.base_url(configs.base_url)
|
login.base_url(configs.base_url)
|
||||||
|
|
||||||
login.login(_email, _password)
|
login.login(_email, _password)
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
"""
|
|
||||||
@author: CNWei
|
|
||||||
@Software: PyCharm
|
|
||||||
@contact: t6i888@163.com
|
|
||||||
@file: main
|
|
||||||
@date: 2025/4/4 17:52
|
|
||||||
@desc:
|
|
||||||
"""
|
|
||||||
from time import sleep
|
|
||||||
import pytest
|
|
||||||
from selenium.webdriver import Chrome
|
|
||||||
from login_page import LoginPage
|
|
||||||
from commons.modules import Browser
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("email, password", [("username", "password"), ("<EMAIL>", "<PASSWORD>")])
|
|
||||||
def test_login(driver, email, password):
|
|
||||||
login = LoginPage()
|
|
||||||
|
|
||||||
login.login(email, password)
|
|
||||||
# sleep(10)
|
|
||||||
|
|
||||||
|
|
||||||
def test_logout_1(login_ok):
|
|
||||||
login = LoginPage()
|
|
||||||
login.browser(Browser.CHROME)
|
|
||||||
login.get("https://www.baidu.com/")
|
|
||||||
print("logout")
|
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.usefixtures("login_ok")
|
|
||||||
def test_logout_2():
|
|
||||||
login = LoginPage()
|
|
||||||
login.browser(Browser.CHROME)
|
|
||||||
login.get("https://www.baidu.com/")
|
|
||||||
print("logout")
|
|
||||||
@@ -10,12 +10,12 @@
|
|||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
import pytest
|
import pytest
|
||||||
from selenium.webdriver import Chrome
|
from selenium.webdriver import Chrome,Edge
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def driver():
|
def driver():
|
||||||
_driver = Chrome()
|
_driver = Edge()
|
||||||
yield _driver
|
yield _driver
|
||||||
_driver.quit()
|
_driver.quit()
|
||||||
|
|
||||||
40
POM/tests/test_login.py
Normal file
40
POM/tests/test_login.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
@author: CNWei
|
||||||
|
@Software: PyCharm
|
||||||
|
@contact: t6i888@163.com
|
||||||
|
@file: main
|
||||||
|
@date: 2025/4/4 17:52
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from time import sleep
|
||||||
|
import pytest
|
||||||
|
from selenium.webdriver import Chrome
|
||||||
|
from POM.page.login_page import LoginPage
|
||||||
|
from commons.modules import Browser
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("email, password", [("ltcs@ltcs.com", "ltcs2024")])
|
||||||
|
def test_login(driver, email, password):
|
||||||
|
login = LoginPage(driver)
|
||||||
|
|
||||||
|
login.login(email, password)
|
||||||
|
# sleep(10)
|
||||||
|
|
||||||
|
|
||||||
|
def test_logout_1(driver):
|
||||||
|
login = LoginPage(driver)
|
||||||
|
# login.browser(Browser.CHROME)
|
||||||
|
login.get("/questions/10010000000000002")
|
||||||
|
print("logout")
|
||||||
|
sleep(10)
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# # @pytest.mark.usefixtures("login_ok")
|
||||||
|
# def test_logout_2():
|
||||||
|
# login = LoginPage()
|
||||||
|
# login.browser(Browser.CHROME)
|
||||||
|
# login.get("https://www.baidu.com/")
|
||||||
|
# print("logout")
|
||||||
@@ -20,7 +20,7 @@ from selenium.webdriver.remote.webdriver import WebDriver
|
|||||||
from selenium.webdriver.remote.webdriver import WebElement
|
from selenium.webdriver.remote.webdriver import WebElement
|
||||||
|
|
||||||
from selenium.webdriver.support import expected_conditions as EC
|
from selenium.webdriver.support import expected_conditions as EC
|
||||||
|
from selenium.webdriver.support.expected_conditions import visibility_of_element_located
|
||||||
__all__ = ["EC","custom_ec"]
|
__all__ = ["EC","custom_ec"]
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -97,3 +97,16 @@ def func_4(mark):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
print(custom_ec)
|
print(custom_ec)
|
||||||
|
from selenium.common.exceptions import StaleElementReferenceException
|
||||||
|
|
||||||
|
def luo_ji(locator: Tuple[str, str]):
|
||||||
|
_ = locator
|
||||||
|
|
||||||
|
def _predicate(driver):
|
||||||
|
try:
|
||||||
|
_ = driver
|
||||||
|
raise StaleElementReferenceException()
|
||||||
|
except StaleElementReferenceException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return _predicate
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import secrets # 原生库,用于生成安全的随机数
|
import secrets # 原生库,用于生成安全的随机数
|
||||||
from typing import Optional, Callable, Union, Literal, Any, TypeVar
|
from typing import Optional, Callable, Union, Literal, Any, TypeVar
|
||||||
@@ -28,6 +29,8 @@ from commons.custom_expected_condition import EC, custom_ec
|
|||||||
from commons.modules import Browser, Locator
|
from commons.modules import Browser, Locator
|
||||||
from commons.settings import configs, EXPLICIT_WAIT_TIMEOUT, SCREENSHOT_DIR
|
from commons.settings import configs, EXPLICIT_WAIT_TIMEOUT, SCREENSHOT_DIR
|
||||||
|
|
||||||
|
from commons.webdriver_finder import BrowserFinder
|
||||||
|
from utils.finder import by_converter
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
WebDriverOrWebElement = Union[WebDriver, WebElement]
|
WebDriverOrWebElement = Union[WebDriver, WebElement]
|
||||||
@@ -41,51 +44,35 @@ T = TypeVar("T")
|
|||||||
# 筛选器 Filters
|
# 筛选器 Filters
|
||||||
# 查找器 finder
|
# 查找器 finder
|
||||||
# 转换器 converter
|
# 转换器 converter
|
||||||
def webdriver_finder(browser: str | Browser = Browser.CHROME) -> WebDriver:
|
|
||||||
match browser:
|
|
||||||
case Browser.CHROME:
|
|
||||||
return Chrome()
|
|
||||||
case Browser.FIREFOX:
|
|
||||||
return Firefox()
|
|
||||||
case Browser.IE:
|
|
||||||
return Ie()
|
|
||||||
case Browser.SAFARI:
|
|
||||||
return Safari()
|
|
||||||
case Browser.EDGE:
|
|
||||||
return Edge()
|
|
||||||
case _:
|
|
||||||
return Chrome() # 默认情况
|
|
||||||
|
|
||||||
|
|
||||||
def by_converter(by_value: str | Locator):
|
# def by_converter(by_value: str | Locator):
|
||||||
try:
|
# try:
|
||||||
# 统一处理输入
|
# # 统一处理输入
|
||||||
if isinstance(by_value, str):
|
# if isinstance(by_value, str):
|
||||||
by = Locator(by_value.lower().replace(' ', ''))
|
# by = Locator(by_value.lower().replace(' ', ''))
|
||||||
|
#
|
||||||
# 创建对应的浏览器实例
|
# # 创建对应的浏览器实例
|
||||||
by = {
|
# by = {
|
||||||
Locator.ID: By.ID,
|
# Locator.ID: By.ID,
|
||||||
Locator.NAME: By.NAME,
|
# Locator.NAME: By.NAME,
|
||||||
Locator.CLASS: By.CLASS_NAME,
|
# Locator.CLASS: By.CLASS_NAME,
|
||||||
Locator.TAG: By.TAG_NAME,
|
# Locator.TAG: By.TAG_NAME,
|
||||||
Locator.LINK_TEXT: By.LINK_TEXT,
|
# Locator.LINK_TEXT: By.LINK_TEXT,
|
||||||
Locator.PARTIAL_LINK_TEXT: By.PARTIAL_LINK_TEXT,
|
# Locator.PARTIAL_LINK_TEXT: By.PARTIAL_LINK_TEXT,
|
||||||
Locator.CSS: By.CSS_SELECTOR,
|
# Locator.CSS: By.CSS_SELECTOR,
|
||||||
Locator.XPATH: By.XPATH,
|
# Locator.XPATH: By.XPATH,
|
||||||
}.get(by_value, By.XPATH)
|
# }.get(by_value, By.XPATH)
|
||||||
return by
|
# return by
|
||||||
except ValueError:
|
# except ValueError:
|
||||||
return By.XPATH
|
# return By.XPATH
|
||||||
|
|
||||||
|
|
||||||
class KeyWordDriver:
|
class CoreDriver:
|
||||||
|
|
||||||
# def __init__(self, browser: str | Browser):
|
def __init__(self, driver: WebDriver | None = None):
|
||||||
# self.driver = self.webdriver_finder(browser)
|
self.driver: WebDriver | None = driver
|
||||||
def __init__(self):
|
self._url: str | None = configs.base_url
|
||||||
self.driver: WebDriver | None = None
|
|
||||||
self._url: str | None = None
|
|
||||||
# self.temp_value = None
|
# self.temp_value = None
|
||||||
|
|
||||||
def base_url(self, url: str, *args, **kwargs):
|
def base_url(self, url: str, *args, **kwargs):
|
||||||
@@ -93,28 +80,33 @@ class KeyWordDriver:
|
|||||||
self._url = url
|
self._url = url
|
||||||
# return url
|
# return url
|
||||||
|
|
||||||
def browser(self, browser_name: str | Browser = Browser.CHROME, *args, **kwargs):
|
def browser(self, browser_name: str | Browser = Browser.CHROME, browser_dir: Path | str | None = None,
|
||||||
|
browser_driver: Path | str | None = None, *args, **kwargs):
|
||||||
browser_name = Browser(browser_name.lower().replace(' ', '')) if isinstance(browser_name, str) else browser_name
|
browser_name = Browser(browser_name.lower().replace(' ', '')) if isinstance(browser_name, str) else browser_name
|
||||||
match browser_name:
|
|
||||||
case Browser.CHROME:
|
logger.info(f"启动 {browser_name.value} 浏览器")
|
||||||
logger.info(f"启动{Browser.CHROME}浏览器")
|
|
||||||
self.driver = Chrome()
|
self.driver = BrowserFinder(browser_name=browser_name).get_browser_driver(browser_dir, browser_driver)
|
||||||
# return Chrome()
|
# match browser_name:
|
||||||
case Browser.FIREFOX:
|
# case Browser.CHROME:
|
||||||
logger.info(f"启动{Browser.FIREFOX}浏览器")
|
# logger.info(f"启动{Browser.CHROME}浏览器")
|
||||||
self.driver = Firefox()
|
# self.driver = Chrome()
|
||||||
case Browser.IE:
|
# # return Chrome()
|
||||||
logger.info(f"启动{Browser.IE}浏览器")
|
# case Browser.FIREFOX:
|
||||||
self.driver = Ie()
|
# logger.info(f"启动{Browser.FIREFOX}浏览器")
|
||||||
case Browser.SAFARI:
|
# self.driver = Firefox()
|
||||||
logger.info(f"启动{Browser.SAFARI}浏览器")
|
# case Browser.IE:
|
||||||
self.driver = Safari()
|
# logger.info(f"启动{Browser.IE}浏览器")
|
||||||
case Browser.EDGE:
|
# self.driver = Ie()
|
||||||
logger.info(f"启动{Browser.EDGE}浏览器")
|
# case Browser.SAFARI:
|
||||||
self.driver = Edge()
|
# logger.info(f"启动{Browser.SAFARI}浏览器")
|
||||||
case _:
|
# self.driver = Safari()
|
||||||
logger.info(f"启动默认浏览器: {Browser.CHROME}")
|
# case Browser.EDGE:
|
||||||
self.driver = Chrome() # 默认情况
|
# logger.info(f"启动{Browser.EDGE}浏览器")
|
||||||
|
# self.driver = Edge()
|
||||||
|
# case _:
|
||||||
|
# logger.info(f"启动默认浏览器: {Browser.CHROME}")
|
||||||
|
# self.driver = Chrome() # 默认情况
|
||||||
|
|
||||||
def find_element(self, by: str = By.XPATH, value: Optional[str] = None, *args, **kwargs) -> WebElement:
|
def find_element(self, by: str = By.XPATH, value: Optional[str] = None, *args, **kwargs) -> WebElement:
|
||||||
by = by_converter(by)
|
by = by_converter(by)
|
||||||
@@ -137,7 +129,7 @@ class KeyWordDriver:
|
|||||||
|
|
||||||
def explicit_wait(self, method: T, timeout: float = EXPLICIT_WAIT_TIMEOUT, *args, **kwargs):
|
def explicit_wait(self, method: T, timeout: float = EXPLICIT_WAIT_TIMEOUT, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
显示等待
|
显示等待AttributeError: 'WebDriver' object has no attribute 'send_keys'
|
||||||
:param method: 可调用对象名
|
:param method: 可调用对象名
|
||||||
:param timeout: 超时时间
|
:param timeout: 超时时间
|
||||||
:param args:
|
:param args:
|
||||||
@@ -159,7 +151,7 @@ class KeyWordDriver:
|
|||||||
def page_load_timeout(self, timeout: float, *args, **kwargs) -> None:
|
def page_load_timeout(self, timeout: float, *args, **kwargs) -> None:
|
||||||
self.driver.set_page_load_timeout(timeout)
|
self.driver.set_page_load_timeout(timeout)
|
||||||
|
|
||||||
def get(self, url, *args, **kwargs):
|
def get(self, url:str, *args, **kwargs):
|
||||||
if self.driver is None:
|
if self.driver is None:
|
||||||
self.browser(*args, **kwargs)
|
self.browser(*args, **kwargs)
|
||||||
if not url.startswith("http"):
|
if not url.startswith("http"):
|
||||||
@@ -326,7 +318,8 @@ class KeyWordDriver:
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
from commons.settings import configs
|
from commons.settings import configs
|
||||||
el = KeyWordDriver()
|
|
||||||
|
el = CoreDriver()
|
||||||
el.base_url(configs.base_url)
|
el.base_url(configs.base_url)
|
||||||
# el.browser("chrome")
|
# el.browser("chrome")
|
||||||
el.browser(Browser.EDGE)
|
el.browser(Browser.EDGE)
|
||||||
@@ -338,4 +331,3 @@ if __name__ == '__main__':
|
|||||||
type_value = el.get_attribute("", '//*[@id="root"]/div[1]/div/div/form/div[3]/button', "type")
|
type_value = el.get_attribute("", '//*[@id="root"]/div[1]/div/div/form/div[3]/button', "type")
|
||||||
print(type_value)
|
print(type_value)
|
||||||
el.assert_text_equals("", '//*[@id="root"]/div[1]/div/div/form/div[3]/button', "登录")
|
el.assert_text_equals("", '//*[@id="root"]/div[1]/div/div/form/div[3]/button', "登录")
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class Browser(str, Enum):
|
|||||||
IE = "ie"
|
IE = "ie"
|
||||||
SAFARI = "safari"
|
SAFARI = "safari"
|
||||||
EDGE = "edge"
|
EDGE = "edge"
|
||||||
|
DEFAULT = "default"
|
||||||
|
|
||||||
|
|
||||||
class Locator(str, Enum):
|
class Locator(str, Enum):
|
||||||
|
|||||||
@@ -43,6 +43,11 @@ EXCHANGER = Path(ROOT_PATH, "extract.toml")
|
|||||||
# 自增ID
|
# 自增ID
|
||||||
ID_PATH = Path(ROOT_PATH, "id.toml")
|
ID_PATH = Path(ROOT_PATH, "id.toml")
|
||||||
|
|
||||||
|
browser_dir: Path | str | None = None
|
||||||
|
|
||||||
|
chrome_driver: Path | str | None = None
|
||||||
|
temp_user_data_dir: Path | str
|
||||||
|
|
||||||
# 默认配置
|
# 默认配置
|
||||||
DEFAULT_CONF = {
|
DEFAULT_CONF = {
|
||||||
"SCREENSHOT_DIR": SCREENSHOT_DIR,
|
"SCREENSHOT_DIR": SCREENSHOT_DIR,
|
||||||
@@ -103,7 +108,6 @@ class Settings:
|
|||||||
|
|
||||||
new_conf = DEFAULT_CONF | result
|
new_conf = DEFAULT_CONF | result
|
||||||
for key, value in new_conf.items():
|
for key, value in new_conf.items():
|
||||||
|
|
||||||
self.__setattr__(key, value)
|
self.__setattr__(key, value)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -134,4 +138,3 @@ configs = Settings()
|
|||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
...
|
...
|
||||||
print(configs.items())
|
print(configs.items())
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,12 @@
|
|||||||
@date: 2025/4/2 21:57
|
@date: 2025/4/2 21:57
|
||||||
@desc:
|
@desc:
|
||||||
"""
|
"""
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
from itertools import chain
|
|
||||||
import inspect
|
|
||||||
|
|
||||||
import allure
|
import allure
|
||||||
from pytest_xlsx.file import XlsxItem
|
from pytest_xlsx.file import XlsxItem
|
||||||
import pytest
|
|
||||||
from selenium.webdriver import Chrome
|
|
||||||
|
|
||||||
from POM.login_page import LoginPage
|
from POM.page.login_page import LoginPage
|
||||||
from commons.driver import KeyWordDriver
|
from commons.driver import KeyWordDriver
|
||||||
from utils.file_processors.file_handle import FileHandle
|
from utils.file_processors.file_handle import FileHandle
|
||||||
from commons.templates import Template
|
from commons.templates import Template
|
||||||
|
|||||||
26
excel_handle.py
Normal file
26
excel_handle.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
@author: CNWei
|
||||||
|
@Software: PyCharm
|
||||||
|
@contact: t6i888@163.com
|
||||||
|
@file: excel_handle
|
||||||
|
@date: 2025/3/30 15:09
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
class ExcelHandler:
|
||||||
|
def __init__(self, path: Path) -> None:
|
||||||
|
self.path = path
|
||||||
|
|
||||||
|
|
||||||
|
def load(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
pass
|
||||||
|
|
||||||
@@ -4,8 +4,7 @@ version = "0.1.0"
|
|||||||
description = "Web自动化测试框架"
|
description = "Web自动化测试框架"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = []
|
||||||
]
|
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
|
|||||||
30
settings.toml
Normal file
30
settings.toml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# settings.toml - 示例配置文件
|
||||||
|
# 如果此文件存在,这里的配置将覆盖代码中的默认值
|
||||||
|
|
||||||
|
base_url = "http://production.example.com:8080" # 覆盖默认的 base_url
|
||||||
|
screenshot = "data/screenshots" # 相对于项目根目录的路径
|
||||||
|
|
||||||
|
[database]
|
||||||
|
host = "192.168.1.100" # 覆盖数据库主机
|
||||||
|
port = 3307 # 覆盖数据库端口
|
||||||
|
user = "prod_user"
|
||||||
|
password = "prod_password_secret"
|
||||||
|
database = "production_db"
|
||||||
|
|
||||||
|
[allure]
|
||||||
|
epic = "项目 V2:核心功能"
|
||||||
|
feature = "用户管理模块"
|
||||||
|
|
||||||
|
[rsa]
|
||||||
|
public = """-----BEGIN PUBLIC KEY-----
|
||||||
|
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyourPublicKeyHere...
|
||||||
|
-----END PUBLIC KEY-----"""
|
||||||
|
private = """-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
Proc-Type: 4,ENCRYPTED
|
||||||
|
DEK-Info: AES-128-CBC,yourEncryptedPrivateKeyInfoHere...
|
||||||
|
|
||||||
|
-----END RSA PRIVATE KEY-----"""
|
||||||
|
|
||||||
|
# 注意:cases_dir, exchanger, id_path, test_dir 也可以在这里覆盖
|
||||||
|
# cases_dir = "MyTestCases/Answer" # 如果是相对路径,是相对于 root_path
|
||||||
|
# test_dir = "/absolute/path/to/tests" # 也可以是绝对路径
|
||||||
Binary file not shown.
139
utils/finder.py
Normal file
139
utils/finder.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
"""
|
||||||
|
@author: CNWei
|
||||||
|
@Software: PyCharm
|
||||||
|
@contact: t6i888@163.com
|
||||||
|
@file: finder
|
||||||
|
@date: 2026/1/25 16:56
|
||||||
|
@desc:
|
||||||
|
"""
|
||||||
|
from typing import Literal, Final
|
||||||
|
from selenium.webdriver.common.by import By
|
||||||
|
|
||||||
|
ByType = Literal[
|
||||||
|
# By(selenium)
|
||||||
|
"id", "xpath", "link text", "partial link text", "name", "tag name", "class name", "css selector",
|
||||||
|
# 自定义常用简写 (Shortcuts)
|
||||||
|
"lt","plt", "class", "css",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class FinderConverter:
|
||||||
|
"""
|
||||||
|
定位查找转换工具类
|
||||||
|
提供策略的归一化处理、简写映射及动态自定义注册
|
||||||
|
"""
|
||||||
|
|
||||||
|
# 预设的常用简写
|
||||||
|
_BUILTIN_SHORTCUTS: Final = {
|
||||||
|
"lt": By.LINK_TEXT,
|
||||||
|
"plt": By.PARTIAL_LINK_TEXT,
|
||||||
|
"class": By.CLASS_NAME,
|
||||||
|
"css": By.CSS_SELECTOR,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._finder_map: dict[str, str] = {}
|
||||||
|
self._map_cache: dict[str, str] = {}
|
||||||
|
self._initialize()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize(text: str) -> str:
|
||||||
|
"""
|
||||||
|
统一清洗逻辑:转小写、去除空格、下划线、横杠
|
||||||
|
"""
|
||||||
|
if not isinstance(text, str):
|
||||||
|
raise TypeError(f"Locator strategy must be a string, got {type(text).__name__} instead.")
|
||||||
|
return text.lower().strip().replace('_', '').replace(' ', '').replace('-', '')
|
||||||
|
|
||||||
|
def _initialize(self) -> None:
|
||||||
|
"""初始化基础映射表"""
|
||||||
|
# 1. 动态加载 AppiumBy 常量值
|
||||||
|
for attr_name in dir(By):
|
||||||
|
if attr_name.startswith("_"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
attr_value = getattr(By, attr_name)
|
||||||
|
if isinstance(attr_value, str):
|
||||||
|
# "class name" -> classname,"class_name" -> classname
|
||||||
|
self._finder_map[self._normalize(attr_value)] = attr_value
|
||||||
|
|
||||||
|
# 2. 加载内置简写(会覆盖同名的策略)
|
||||||
|
self._finder_map.update(self._BUILTIN_SHORTCUTS)
|
||||||
|
|
||||||
|
# 3. 备份初始状态
|
||||||
|
self._map_cache = self._finder_map.copy()
|
||||||
|
|
||||||
|
def convert(self, by_value: ByType | str) -> str:
|
||||||
|
"""
|
||||||
|
将模糊或简写的定位方式转换为 Appium 标准定位字符串
|
||||||
|
:raises ValueError: 当定位方式不支持时抛出
|
||||||
|
"""
|
||||||
|
if not by_value or not isinstance(by_value, str):
|
||||||
|
raise ValueError(f"Invalid selector type: {type(by_value)}. Expected a string.")
|
||||||
|
|
||||||
|
clean_key = self._normalize(by_value)
|
||||||
|
target = self._finder_map.get(clean_key)
|
||||||
|
|
||||||
|
if target is None:
|
||||||
|
raise ValueError(f"Unsupported locator strategy: '{by_value}'.")
|
||||||
|
return target
|
||||||
|
|
||||||
|
def register_custom_finder(self, alias: str, target: str) -> None:
|
||||||
|
"""注册自定义定位策略"""
|
||||||
|
self._finder_map[self._normalize(alias)] = target
|
||||||
|
|
||||||
|
def clear_custom_finders(self) -> None:
|
||||||
|
"""重置回初始官方/内置状态"""
|
||||||
|
self._finder_map = self._map_cache.copy()
|
||||||
|
|
||||||
|
def get_all_finders(self) -> list[str]:
|
||||||
|
"""返回当前所有支持的策略 key(用于调试)"""
|
||||||
|
return list(self._finder_map.keys())
|
||||||
|
|
||||||
|
|
||||||
|
# 导出单例,方便直接使用
|
||||||
|
converter = FinderConverter()
|
||||||
|
by_converter = converter.convert
|
||||||
|
register_custom_finder = converter.register_custom_finder
|
||||||
|
|
||||||
|
__all__ = ["by_converter", "register_custom_finder"]
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# 1. 测试标准转换与内置简写
|
||||||
|
print(f"ID 转换: {by_converter('id')}") # 输出: id
|
||||||
|
print(f"Class 简写转换: {by_converter('class')}") # 输出: accessibility id
|
||||||
|
print(f"CSS 简写转换: {by_converter('css')}") # 输出: css selector
|
||||||
|
|
||||||
|
# 2. 测试强大的归一化容错 (空格、下划线、横杠、大小写)
|
||||||
|
print(f"类链容错: {by_converter(' link_text ')}") # 输出: link text
|
||||||
|
print(f"PARTIAL_LINK_TEXT 容错: {by_converter('PARTIAL_LINK_TEXT')}") # 输出: partial link text
|
||||||
|
|
||||||
|
# 3. 测试自定义注册
|
||||||
|
register_custom_finder("my_text", "-my uiautomator")
|
||||||
|
print(f"自定义注册测试: {by_converter('my_text')}") # 输出: -my uiautomator
|
||||||
|
|
||||||
|
# 4. 测试重置功能
|
||||||
|
converter.clear_custom_finders()
|
||||||
|
print("已重置自定义查找器")
|
||||||
|
try:
|
||||||
|
by_converter("my_text")
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"验证重置成功 (捕获异常): {e}")
|
||||||
|
|
||||||
|
# 5. 查看当前全量支持的归一化后的 Key
|
||||||
|
print(f"当前支持的策略总数: {len(converter.get_all_finders())}")
|
||||||
|
print(f"前 5 个策略示例: {converter.get_all_finders()[:5]}")
|
||||||
|
# 6. 增加类型非法测试
|
||||||
|
print("\n--- 异常类型测试 ---")
|
||||||
|
try:
|
||||||
|
by_converter(123) # 传入数字
|
||||||
|
except TypeError as e:
|
||||||
|
print(f"验证类型拦截成功: {e}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
by_converter(None) # 传入 None
|
||||||
|
except TypeError as e:
|
||||||
|
print(f"验证空值拦截成功: {e}")
|
||||||
Reference in New Issue
Block a user