selenium测试
This commit is contained in:
parent
bcbf100356
commit
c6ea1854e5
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ dsl_schema.json
|
||||
sessions/*
|
||||
artifacts/*
|
||||
.vscode/settings.json
|
||||
tests/__pycache__/*.pyc
|
||||
|
||||
@ -3,3 +3,11 @@
|
||||
Can not load UIAutomationCore.dll.
|
||||
1, You may need to install Windows Update KB971513 if your OS is Windows XP, see https://github.com/yinkaisheng/WindowsUpdateKB971513ForIUIAutomation
|
||||
2, You need to use an UIAutomationInitializerInThread object if use uiautomation in a thread, see demos/uiautomation_in_thread.py
|
||||
2025-12-30 22:39:52.738 pydevd_safe_repr.py[343] _repr_obj -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:40:02.757 pydevd_safe_repr.py[343] _repr_obj -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:40:12.791 pydevd_safe_repr.py[343] _repr_obj -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:40:25.854 pydevd_safe_repr.py[343] _repr_obj -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:40:35.867 pydevd_safe_repr.py[343] _repr_obj -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:40:45.888 pydevd_safe_repr.py[343] _repr_obj -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:40:55.909 pydevd_resolver.py[193] _get_py_dictionary -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:41:05.933 pydevd_resolver.py[193] _get_py_dictionary ->
|
||||
@ -1,6 +1,6 @@
|
||||
# MIT License
|
||||
# Copyright (c) 2024
|
||||
"""执行层:基于 DSL 进行 UI 自动化,并支持可选视觉校验与结构化日志"""
|
||||
"""执行层:基于 DSL 进行 UI 自动化,并支持可选视觉校验与结构化日志。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@ -21,7 +21,7 @@ from .schema import DSLSpec
|
||||
|
||||
@dataclass
|
||||
class ExecContext:
|
||||
"""执行上下文"""
|
||||
"""执行上下文。"""
|
||||
|
||||
allow_title: str
|
||||
dry_run: bool = False
|
||||
@ -29,7 +29,7 @@ class ExecContext:
|
||||
|
||||
|
||||
def _match_window(allow_title: str) -> Optional[auto.Control]:
|
||||
"""仅在窗口标题匹配白名单时返回窗口,容忍标题前缀/包含"""
|
||||
"""仅在窗口标题匹配白名单时返回窗口,容忍标题前缀或包含。"""
|
||||
patterns = [allow_title]
|
||||
if " - " in allow_title:
|
||||
patterns.append(allow_title.split(" - ", 1)[0])
|
||||
@ -43,7 +43,7 @@ def _match_window(allow_title: str) -> Optional[auto.Control]:
|
||||
return False
|
||||
|
||||
def _ascend_to_top(node: auto.Control) -> auto.Control:
|
||||
"""向上寻找最可能的顶层窗口(Chrome 主窗口类名/WindowControl 优先)"""
|
||||
"""向上寻找最可能的顶层窗口(优先返回类似 Chrome 的 WindowControl)。"""
|
||||
best = node
|
||||
cur = node
|
||||
while True:
|
||||
@ -88,7 +88,7 @@ def _match_window(allow_title: str) -> Optional[auto.Control]:
|
||||
|
||||
|
||||
def _find_control(root: auto.Control, locator: Dict[str, Any], timeout: float) -> Optional[auto.Control]:
|
||||
"""Find a control under root according to locator."""
|
||||
"""根据 locator 在 root 下查找控件。"""
|
||||
start = time.time()
|
||||
try:
|
||||
print(
|
||||
@ -98,7 +98,7 @@ def _find_control(root: auto.Control, locator: Dict[str, Any], timeout: float) -
|
||||
pass
|
||||
|
||||
def _matches(ctrl: auto.Control) -> bool:
|
||||
"""Simple property match without relying on uiautomation AndCondition."""
|
||||
"""简单属性匹配,避免依赖 uiautomation 的 AndCondition。"""
|
||||
try:
|
||||
name_val = locator.get("Name")
|
||||
name_contains = locator.get("Name__contains")
|
||||
@ -130,11 +130,11 @@ def _find_control(root: auto.Control, locator: Dict[str, Any], timeout: float) -
|
||||
if not locator:
|
||||
print("10001")
|
||||
return root
|
||||
# Check root itself first
|
||||
# 先检查根节点自身
|
||||
if _matches(root):
|
||||
print("10002")
|
||||
return root
|
||||
# Simple BFS when AndCondition is unavailable
|
||||
# AndCondition 不可用时,使用简单 BFS
|
||||
queue: List[Any] = [(root, 0)]
|
||||
while queue:
|
||||
node, depth = queue.pop(0)
|
||||
@ -184,7 +184,7 @@ def _find_control(root: auto.Control, locator: Dict[str, Any], timeout: float) -
|
||||
|
||||
|
||||
def _capture_screenshot(ctrl: Optional[auto.Control], out_path: Path) -> Optional[Path]:
|
||||
"""截取控件区域或全屏"""
|
||||
"""截取控件区域或全屏。"""
|
||||
try:
|
||||
with mss.mss() as sct:
|
||||
if ctrl and getattr(ctrl, "BoundingRectangle", None):
|
||||
@ -203,7 +203,7 @@ def _capture_screenshot(ctrl: Optional[auto.Control], out_path: Path) -> Optiona
|
||||
|
||||
|
||||
def _capture_tree(ctrl: Optional[auto.Control], max_depth: int = 3) -> List[Dict[str, Any]]:
|
||||
"""采集浅层 UIA 树摘要"""
|
||||
"""采集浅层 UIA 树摘要。"""
|
||||
if ctrl is None:
|
||||
return []
|
||||
nodes: List[Dict[str, Any]] = []
|
||||
@ -241,7 +241,7 @@ def _save_tree(ctrl: Optional[auto.Control], out_path: Path) -> Optional[Path]:
|
||||
|
||||
|
||||
def _image_similarity(full_img_path: Path, template_path: Path, threshold: float = 0.8) -> bool:
|
||||
"""简单模板匹配,相似度 >= 阈值视为通过"""
|
||||
"""简单模板匹配,相似度 >= 阈值视为通过。"""
|
||||
if not full_img_path.exists() or not template_path.exists():
|
||||
return False
|
||||
full = cv2.imread(str(full_img_path), cv2.IMREAD_COLOR)
|
||||
@ -254,7 +254,7 @@ def _image_similarity(full_img_path: Path, template_path: Path, threshold: float
|
||||
|
||||
|
||||
def _visual_check(expected: Dict[str, Any], ctrl: Optional[auto.Control], artifacts_dir: Path, step_idx: int, attempt: int) -> bool:
|
||||
"""执行可选视觉校验:模板匹配"""
|
||||
"""执行可选视觉校验:模板匹配。"""
|
||||
template_path = expected.get("template_path")
|
||||
threshold = float(expected.get("threshold", 0.8))
|
||||
if not template_path:
|
||||
@ -274,7 +274,7 @@ def _log_event(log_path: Path, record: Dict[str, Any]) -> None:
|
||||
|
||||
|
||||
def _render_value(val: Any, params: Dict[str, Any]) -> Any:
|
||||
"""简单占位符替换 ${param}"""
|
||||
"""简单占位符替换 ${param}。"""
|
||||
if isinstance(val, str):
|
||||
out = val
|
||||
for k, v in params.items():
|
||||
@ -290,7 +290,7 @@ def _render_value(val: Any, params: Dict[str, Any]) -> Any:
|
||||
|
||||
|
||||
def _do_action(ctrl: auto.Control, step: Dict[str, Any], dry_run: bool) -> None:
|
||||
"""执行单步动作"""
|
||||
"""执行单步动作。"""
|
||||
action = step.get("action")
|
||||
text = step.get("text", "")
|
||||
send_enter = bool(step.get("send_enter"))
|
||||
@ -318,11 +318,13 @@ def _do_action(ctrl: auto.Control, step: Dict[str, Any], dry_run: bool) -> None:
|
||||
|
||||
def execute_spec(spec: DSLSpec, ctx: ExecContext) -> None:
|
||||
"""执行完整 DSL。
|
||||
|
||||
流程概览:
|
||||
1. 先根据 allow_title 找到当前前台窗口作为根控件 root。
|
||||
2. 逐步标准化 DSL:字段兼容、文本替换、等待策略等。
|
||||
3. 对每个步骤依次查找目标控件 -> 视觉校验(可选)-> 执行动作/记录 dry-run。
|
||||
4. 每次尝试都会落盘截图、UI 树和日志,方便回溯。"""
|
||||
3. 对每个步骤依次查找目标控件 -> 视觉校验(可选) -> 执行动作/记录 dry-run。
|
||||
4. 每次尝试都会落盘截图、UI 树和日志,方便回溯。
|
||||
"""
|
||||
# 给前台窗口切换预留时间,避免刚启动命令时窗口还未聚焦
|
||||
time.sleep(1.0)
|
||||
root = _match_window(ctx.allow_title)
|
||||
@ -340,7 +342,7 @@ def execute_spec(spec: DSLSpec, ctx: ExecContext) -> None:
|
||||
log_path = artifacts / "executor_log.jsonl"
|
||||
|
||||
def _normalize_target(tgt: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""规范 target 键名,兼容窗口标题匹配/包含等写法"""
|
||||
"""规范 target 键名,兼容窗口标题匹配/包含等写法。"""
|
||||
norm: Dict[str, Any] = {}
|
||||
for k, v in tgt.items():
|
||||
lk = k.lower()
|
||||
@ -365,7 +367,7 @@ def execute_spec(spec: DSLSpec, ctx: ExecContext) -> None:
|
||||
return norm
|
||||
|
||||
def normalize_step(step: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""归一化字段,兼容不同 DSL 变体"""
|
||||
"""归一化字段,兼容不同 DSL 变量。"""
|
||||
out = _render_value(dict(step), spec.params)
|
||||
if "target" not in out and "selector" in out:
|
||||
out["target"] = out.get("selector")
|
||||
@ -425,7 +427,7 @@ def execute_spec(spec: DSLSpec, ctx: ExecContext) -> None:
|
||||
for item in iterable:
|
||||
run_steps(step.get("steps", []))
|
||||
elif "if_condition" in step:
|
||||
# if_condition:依据参数布尔值选择分支
|
||||
# if_condition:依赖参数布尔值选择分支
|
||||
cond = step["if_condition"]
|
||||
if spec.params.get(cond):
|
||||
run_steps(step.get("steps", []))
|
||||
@ -436,7 +438,7 @@ def execute_spec(spec: DSLSpec, ctx: ExecContext) -> None:
|
||||
target = step.get("target", {})
|
||||
timeout = float(step.get("waits", {}).get("appear", spec.waits.get("appear", 1.0)))
|
||||
if ctx.dry_run:
|
||||
timeout = min(timeout, 1) # 纯 dry-run 场景快速返回,避免长时间等待
|
||||
timeout = min(timeout, 1) # 让 dry-run 场景快速返回,避免长时间等待
|
||||
retry = step.get("retry_policy", spec.retry_policy)
|
||||
attempts = int(retry.get("max_attempts", 1))
|
||||
interval = float(retry.get("interval", 1.0))
|
||||
|
||||
91
dsl.yaml
Normal file
91
dsl.yaml
Normal file
@ -0,0 +1,91 @@
|
||||
params:
|
||||
baidu_url: www.baidu.com
|
||||
search_text: "你好"
|
||||
|
||||
steps:
|
||||
- id: wait_new_tab_chrome
|
||||
action: wait_for
|
||||
timeout_ms: 10000
|
||||
target:
|
||||
window_title: "新标签页 - Google Chrome"
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: WindowControl
|
||||
|
||||
- id: focus_address_bar
|
||||
action: click
|
||||
waits:
|
||||
- type: wait_for
|
||||
selector:
|
||||
name: 地址和搜索栏
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: EditControl
|
||||
timeout_ms: 5000
|
||||
target:
|
||||
name: 地址和搜索栏
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: EditControl
|
||||
|
||||
- id: type_baidu_url
|
||||
action: type
|
||||
text_param: baidu_url
|
||||
send_enter: true
|
||||
waits:
|
||||
- type: wait_for
|
||||
selector:
|
||||
window_title: "百度一下,你就知道 - Google Chrome"
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: WindowControl
|
||||
timeout_ms: 15000
|
||||
target:
|
||||
name: 地址和搜索栏
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: EditControl
|
||||
|
||||
- id: click_baidu_search_box
|
||||
action: click
|
||||
waits:
|
||||
- type: wait_for
|
||||
selector:
|
||||
control_type: EditControl
|
||||
timeout_ms: 5000
|
||||
target:
|
||||
control_type: EditControl
|
||||
|
||||
- id: type_search_text
|
||||
action: type
|
||||
text_param: search_text
|
||||
send_enter: true
|
||||
waits:
|
||||
- type: wait_for
|
||||
selector:
|
||||
window_title_contains_param: search_text
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: WindowControl
|
||||
timeout_ms: 15000
|
||||
target:
|
||||
control_type: EditControl
|
||||
|
||||
assertions:
|
||||
- id: assert_baidu_home_opened
|
||||
action: assert_exists
|
||||
selector:
|
||||
window_title: "百度一下,你就知道 - Google Chrome"
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: WindowControl
|
||||
timeout_ms: 5000
|
||||
|
||||
- id: assert_search_result_page
|
||||
action: assert_exists
|
||||
selector:
|
||||
window_title_contains_param: search_text
|
||||
class_name: Chrome_WidgetWin_1
|
||||
control_type: WindowControl
|
||||
timeout_ms: 10000
|
||||
|
||||
retry_policy:
|
||||
max_attempts: 2
|
||||
interval: 1.0
|
||||
|
||||
waits:
|
||||
appear: 5.0
|
||||
disappear: 5.0
|
||||
@ -10,3 +10,4 @@ psutil>=5.9.6
|
||||
numpy>=1.26.0
|
||||
requests>=2.31.0
|
||||
python-dotenv>=1.0.0
|
||||
selenium
|
||||
2
tests/@AutomationLog.txt
Normal file
2
tests/@AutomationLog.txt
Normal file
@ -0,0 +1,2 @@
|
||||
2025-12-30 22:33:41.606 test-uiautomation.py[71] run -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
2025-12-30 22:34:54.215 test-uiautomation.py[71] run -> Find Control Timeout(10s): {Name: '帮助', ControlType: MenuItemControl}
|
||||
91
tests/engine.py
Normal file
91
tests/engine.py
Normal file
@ -0,0 +1,91 @@
|
||||
import yaml
|
||||
import time
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
|
||||
class DSLEngine:
|
||||
def __init__(self, driver):
|
||||
self.driver = driver
|
||||
self.wait = WebDriverWait(self.driver, 10)
|
||||
|
||||
def _parse_locator(self, locator_str):
|
||||
"""
|
||||
解析定位符字符串,例如 "id:username" -> (By.ID, "username")
|
||||
"""
|
||||
if not locator_str:
|
||||
return None
|
||||
|
||||
by_map = {
|
||||
"id": By.ID,
|
||||
"xpath": By.XPATH,
|
||||
"css": By.CSS_SELECTOR,
|
||||
"name": By.NAME,
|
||||
"class": By.CLASS_NAME,
|
||||
"tag": By.TAG_NAME
|
||||
}
|
||||
|
||||
try:
|
||||
method, value = locator_str.split(":", 1)
|
||||
return by_map.get(method.lower()), value
|
||||
except ValueError:
|
||||
raise Exception(f"定位符格式错误: {locator_str},应为 '类型:值'")
|
||||
|
||||
def execute_step(self, step):
|
||||
"""
|
||||
执行单个 DSL 步骤
|
||||
"""
|
||||
action = step.get('action')
|
||||
locator_str = step.get('locator')
|
||||
value = step.get('value')
|
||||
desc = step.get('desc', '无描述')
|
||||
|
||||
print(f"正在执行: [{action}] - {desc}")
|
||||
|
||||
# 解析定位符 (如果有)
|
||||
locator = self._parse_locator(locator_str)
|
||||
|
||||
# === 动作分发 (Action Dispatch) ===
|
||||
|
||||
if action == "open":
|
||||
self.driver.get(value)
|
||||
|
||||
elif action == "input":
|
||||
el = self.wait.until(EC.visibility_of_element_located(locator))
|
||||
el.clear()
|
||||
el.send_keys(str(value))
|
||||
|
||||
elif action == "click":
|
||||
el = self.wait.until(EC.element_to_be_clickable(locator))
|
||||
el.click()
|
||||
|
||||
elif action == "wait":
|
||||
time.sleep(float(value))
|
||||
|
||||
elif action == "assert_text":
|
||||
el = self.wait.until(EC.visibility_of_element_located(locator))
|
||||
actual_text = el.text
|
||||
assert value in actual_text, f"断言失败: 期望 '{value}' 包含在 '{actual_text}' 中"
|
||||
print(f" -> 断言通过: 发现 '{actual_text}'")
|
||||
|
||||
else:
|
||||
raise Exception(f"未知的动作指令: {action}")
|
||||
|
||||
def run_yaml(self, yaml_path):
|
||||
"""
|
||||
加载并运行 YAML 文件
|
||||
"""
|
||||
with open(yaml_path, 'r', encoding='utf-8') as f:
|
||||
data = yaml.safe_load(f)
|
||||
|
||||
print(f"=== 开始测试: {data.get('name')} ===")
|
||||
|
||||
for step in data.get('steps', []):
|
||||
try:
|
||||
self.execute_step(step)
|
||||
except Exception as e:
|
||||
print(f" [X] 步骤执行失败: {e}")
|
||||
raise e # 抛出异常以终止测试
|
||||
|
||||
print("=== 测试全部通过 ===")
|
||||
31
tests/main.py
Normal file
31
tests/main.py
Normal file
@ -0,0 +1,31 @@
|
||||
from selenium import webdriver
|
||||
from engine import DSLEngine
|
||||
import os
|
||||
|
||||
def main():
|
||||
# 1. 设置 WebDriver (这里以 Chrome 为例)
|
||||
options = webdriver.ChromeOptions()
|
||||
# options.add_argument('--headless') # 如果需要无头模式可开启
|
||||
driver = webdriver.Chrome(options=options)
|
||||
|
||||
try:
|
||||
# 2. 初始化 DSL 引擎
|
||||
engine = DSLEngine(driver)
|
||||
|
||||
# 3. 指定 YAML 文件路径并运行
|
||||
yaml_file = "test_case.yaml"
|
||||
|
||||
if os.path.exists(yaml_file):
|
||||
engine.run_yaml(yaml_file)
|
||||
else:
|
||||
print(f"找不到文件: {yaml_file}")
|
||||
|
||||
except Exception as e:
|
||||
print("测试过程中发生严重错误。")
|
||||
finally:
|
||||
# 4. 清理环境
|
||||
# time.sleep(3) # 调试时可以暂停一下看结果
|
||||
driver.quit()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
93
tests/test-uiautomation.py
Normal file
93
tests/test-uiautomation.py
Normal file
@ -0,0 +1,93 @@
|
||||
import uiautomation as auto
|
||||
import subprocess
|
||||
import time
|
||||
import yaml
|
||||
|
||||
class AutomationEngine:
|
||||
def __init__(self, config_path):
|
||||
with open(config_path, 'r', encoding='utf-8') as f:
|
||||
self.config = yaml.safe_load(f)
|
||||
self.window = None
|
||||
|
||||
def find_control(self, parent, selector_dict):
|
||||
"""根据字典动态查找控件"""
|
||||
if not selector_dict:
|
||||
return parent
|
||||
|
||||
args = selector_dict.copy()
|
||||
|
||||
# 1. 提取 ControlType,例如 "MenuItemControl"
|
||||
# 如果 YAML 里没写,默认用 generic 的 Control
|
||||
control_type = args.pop('ControlType', 'Control')
|
||||
|
||||
# 2. 动态获取查找方法,例如 parent.MenuItemControl(...)
|
||||
# 这样比直接用 parent.Control(ControlType=...) 更符合库的设计
|
||||
if hasattr(parent, control_type):
|
||||
finder_method = getattr(parent, control_type)
|
||||
else:
|
||||
print(f"警告: 未知的 ControlType '{control_type}',回退到通用查找。")
|
||||
finder_method = parent.Control
|
||||
# 如果回退,需要把 ControlType 加回去作为属性过滤
|
||||
if control_type != 'Control':
|
||||
args['ControlType'] = control_type
|
||||
|
||||
# 3. 执行查找
|
||||
return finder_method(**args)
|
||||
|
||||
def run(self):
|
||||
print(f"开始执行任务: {self.config['name']}")
|
||||
|
||||
subprocess.Popen(self.config['app'])
|
||||
time.sleep(1) # 等待启动
|
||||
|
||||
# 查找主窗口
|
||||
self.window = auto.WindowControl(**self.config['target_window'])
|
||||
if not self.window.Exists(5):
|
||||
raise Exception("❌ 主窗口未找到,请检查 ClassName 是否正确 (Win11记事本可能是 'Notepad' 但内部结构不同)")
|
||||
|
||||
self.window.SetTopmost(True)
|
||||
print(f"✅ 锁定主窗口: {self.window.Name}")
|
||||
|
||||
for i, step in enumerate(self.config['steps']):
|
||||
action = step.get('action')
|
||||
desc = step.get('desc', action)
|
||||
print(f"\n--- 步骤 {i+1}: {desc} ---")
|
||||
|
||||
# 确定父级
|
||||
target_control = self.window
|
||||
if 'parent' in step:
|
||||
# 弹窗通常是顶层窗口,从 Root 找,searchDepth=1 表示只查桌面的一级子窗口
|
||||
target_control = self.find_control(auto.GetRootControl(), step['parent'])
|
||||
|
||||
# 确定目标控件
|
||||
if 'selector' in step:
|
||||
target_control = self.find_control(target_control, step['selector'])
|
||||
|
||||
# !!! 关键调试信息 !!!
|
||||
# 检查控件是否存在,如果不存在,打印详细信息并停止
|
||||
if not target_control.Exists(3):
|
||||
print(f"❌ 错误: 无法找到控件!")
|
||||
print(f" 查找参数: {step.get('selector')}")
|
||||
print(f" 父级控件: {target_control.GetParentControl()}")
|
||||
# 可以在这里抛出异常停止脚本,方便调试
|
||||
break
|
||||
|
||||
# 执行动作
|
||||
if action == 'input':
|
||||
target_control.Click()
|
||||
target_control.SendKeys(step['value'])
|
||||
print(f" 输入: {step['value']}")
|
||||
|
||||
elif action == 'click':
|
||||
target_control.Click()
|
||||
print(" 点击成功")
|
||||
|
||||
elif action == 'sleep':
|
||||
time.sleep(step['value'])
|
||||
print(f" 等待 {step['value']} 秒")
|
||||
|
||||
print("\n自动化结束")
|
||||
|
||||
if __name__ == '__main__':
|
||||
engine = AutomationEngine(r'D:\project\audoWin\tests\test_case.yaml')
|
||||
engine.run()
|
||||
30
tests/test_case.yaml
Normal file
30
tests/test_case.yaml
Normal file
@ -0,0 +1,30 @@
|
||||
# test_case.yaml
|
||||
name: 标准用户登录测试
|
||||
description: 测试SauceDemo网站的登录流程
|
||||
steps:
|
||||
- action: open
|
||||
value: "https://www.saucedemo.com/"
|
||||
desc: "打开首页"
|
||||
|
||||
- action: input
|
||||
locator: "id:user-name"
|
||||
value: "standard_user"
|
||||
desc: "输入用户名"
|
||||
|
||||
- action: input
|
||||
locator: "id:password"
|
||||
value: "secret_sauce"
|
||||
desc: "输入密码"
|
||||
|
||||
- action: click
|
||||
locator: "id:login-button"
|
||||
desc: "点击登录按钮"
|
||||
|
||||
- action: wait
|
||||
value: 1
|
||||
desc: "强制等待1秒(可选)"
|
||||
|
||||
- action: assert_text
|
||||
locator: "class:title"
|
||||
value: "Products"
|
||||
desc: "验证页面标题包含Products"
|
||||
Loading…
x
Reference in New Issue
Block a user