91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
|
|
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("=== 测试全部通过 ===")
|