📖 前言

在现代Web应用开发中,UI自动化测试是保证产品质量的重要环节。本文将介绍如何基于Playwright构建企业级UI自动化测试框架,并探讨如何将AI技术融入测试流程,实现更智能的测试体验。


🏗️ 一、项目架构概览

auto_ui/
├── config/                 # 配置模块
│   ├── settings.py        # 环境配置
│   └── config.yaml        # 配置文件
├── pages/                  # 页面对象模块
│   ├── base_page.py       # 基础页面类
│   ├── login_page.py      # 登录页面
│   └── personal_workspace_page.py  # 个人工作台页面
├── tests/                  # 测试用例模块
│   ├── conftest.py        # pytest配置
│   └── test_*.py          # 测试用例
├── utils/                  # 工具模块
│   ├── logger.py          # 日志工具
│   └── decorators.py       # 装饰器
└── logs/                   # 日志目录

核心设计理念

  1. Page Object模式 - 页面元素与业务逻辑分离

  2. YAML配置管理 - 环境配置与代码解耦

  3. 统一日志系统 - 测试执行全程可追溯

  4. AI智能辅助 - 智能元素定位与异常分析


🔧 二、核心模块实现

2.1 配置管理

# config/settings.py
from pathlib import Path
import yaml

CONFIG_PATH = Path(__file__).parent / "config.yaml"

def load_config():
    with open(CONFIG_PATH, "r", encoding="utf-8") as f:
        return yaml.safe_load(f)

_config = load_config()

BASE_URL = _config.get("base_url", "https://test.example.com")
TIMEOUT = _config.get("timeout", 30000)
HEADLESS = _config.get("headless", False)
BROWSER = _config.get("browser", "chromium")

WAIT_TIMEOUTS = {
    "short": 2000,
    "medium": 3000,
    "long": 5000,
    "page_load": 30000,
}
# config/config.yaml
base_url: "https://test.example.com"
timeout: 30000
headless: false
browser: "chromium"
use_system_chrome: false

2.2 基础页面对象

# pages/base_page.py
from playwright.sync_api import Page
from config.settings import WAIT_TIMEOUTS

class BasePage:
    """页面对象基类"""

    def __init__(self, page: Page):
        self.page = page

    def wait_for_timeout(self, milliseconds: int):
        """等待指定毫秒"""
        self.page.wait_for_timeout(milliseconds)

    def wait_for_page_load(self, timeout: int = 30000):
        """等待页面加载完成"""
        try:
            self.page.wait_for_load_state("domcontentloaded", timeout=timeout)
            self.page.wait_for_load_state("networkidle", timeout=timeout)
        except Exception:
            pass

    def find_elements(self, selector: str):
        """查找多个元素"""
        return self.page.query_selector_all(selector)

    def find_element_contains_text(self, text: str):
        """查找包含指定文本的元素"""
        return self.page.query_selector_all(f"text={text}")

    def _wait_and_click_element(self, text: str, exact: bool = False, timeout: int = 5000):
        """等待并点击元素"""
        try:
            if exact:
                locator = self.page.get_by_text(text, exact=True)
            else:
                locator = self.page.get_by_text(text)

            locator.click(timeout=timeout)
            return True
        except Exception:
            return False

2.3 智能元素定位(AI辅助)

传统UI自动化测试最头疼的问题就是元素定位不稳定。元素选择器经常因为前端变化而失效,导致测试频繁中断。通过智能文本提取和多重定位策略,我们可以大大提升元素定位的稳定性:

def get_all_dashboards(self) -> list:
    """智能获取下拉列表中的看板选项
    
    这个方法展示了如何处理复杂的DOM结构:
    1. 尝试获取子元素的纯文本
    2. 过滤掉功能按钮(如"新建")
    3. 去重避免重复
    """
    dashboards = []
    seen_names = set()

    dropdown_selectors = [
        ".el-select-dropdown__item:visible",
        ".el-select-dropdown__list li:visible",
    ]

    for selector in dropdown_selectors:
        try:
            locator = self.page.locator(selector)
            if locator.count() > 0:
                for i in range(locator.count()):
                    item = locator.nth(i)
                    if not item.is_visible():
                        continue

                    # 🔍 智能文本提取
                    text = self._extract_clean_text(item)
                    if not text or len(text) > 50:
                        continue

                    # 过滤功能按钮
                    if any(keyword in text for keyword in ["重置", "导出", "新建", "编辑"]):
                        continue

                    if text in seen_names:
                        continue

                    seen_names.add(text)
                    dashboards.append({
                        "name": text,
                        "element": item,
                    })
        except Exception:
            continue

    return dashboards

def _extract_clean_text(self, element) -> str:
    """从复杂DOM元素中提取纯净文本
    
    解决思路:
    有些下拉选项会隐藏JSON数据在元素内
    我们需要只提取可见的文本部分
    """
    text = element.text_content() or ""
    text = text.strip()

    # 尝试获取子元素的纯文本
    try:
        span_elem = element.locator("span").first
        if span_elem.count() > 0:
            text = span_elem.text_content() or ""
            text = text.strip()
    except Exception:
        pass

    # 如果文本包含JSON,只取之前的部分
    text = text.split("{")[0].strip()

    return text

🤖 三、AI能力集成

3.1 智能异常检测

class BasePage:
    """包含AI异常检测的基础页面类"""

    def __init__(self, page: Page):
        self.page = page
        self.error_dialog_selectors = [
            ".el-message-box__wrapper",      # Element Plus错误弹框
            ".ant-modal-error-desc",        # Ant Design错误弹框
            "[class*='message-box']",       # 自定义错误弹框
            ".el-message--error",           # 错误提示
        ]

    def check_error_dialog(self) -> tuple[bool, list]:
        """AI驱动的异常检测
        
        Returns:
            (has_error, error_messages): 是否存在错误及错误信息列表
        """
        error_messages = []

        for selector in self.error_dialog_selectors:
            try:
                locator = self.page.locator(selector)
                if locator.count() > 0:
                    for i in range(locator.count()):
                        elem = locator.nth(i)
                        if elem.is_visible():
                            text = elem.text_content() or ""
                            error_messages.append(f"[错误弹框] {text[:200]}")
            except Exception:
                continue

        return len(error_messages) > 0, error_messages

3.2 网络请求智能监听

def setup_network_failure_listener(self):
    """设置网络请求监听器
    
    AI能力:
    - 实时监控所有HTTP请求
    - 自动识别失败请求(状态码>=400)
    - 记录完整的请求信息用于分析
    """
    failed_requests = []
    all_requests = []

    def handle_response(response):
        status = response.status
        all_requests.append({
            "url": response.url,
            "status": status,
            "status_text": response.status_text,
        })

        # 🔍 智能识别失败请求
        if status >= 400:
            failed_requests.append({
                "url": response.url,
                "status": status,
                "status_text": response.status_text,
            })

    self.page.on("response", handle_response)
    return failed_requests, all_requests

📝 四、测试用例实现

4.1 个人工作台测试

# tests/test_personal_workspace.py
import pytest
from pages import LoginPage, PersonalWorkspacePage
from config.settings import TEST_USER
from utils import log_info, log_error, log_step

@pytest.mark.ui
class TestPersonalWorkspace:

    @log_step("测试个人工作台页面加载和异常检测")
    def test_personal_workspace_load_and_error_check(self, page: Page):
        """
        测试个人工作台功能:
        1. 登录系统
        2. 遍历检查所有看板
        3. 检测异常弹框
        4. 监控网络请求
        """
        login_page = LoginPage(page)
        workspace_page = PersonalWorkspacePage(page)

        # 步骤1: 登录
        log_info("步骤1: 打开登录页面并登录系统")
        login_page.navigate_to_login()
        login_page.login(TEST_USER["username"], TEST_USER["password"])
        assert login_page.is_login_successful(), "登录失败"

        # 步骤2: 进入个人工作台
        log_info("步骤2: 进入菜单个人事务-个人工作台")
        assert workspace_page.navigate_to_personal_workspace(), "导航失败"

        # 步骤3: 遍历检查所有看板
        log_info("步骤3: 遍历检查所有看板")
        iterate_result = workspace_page.iterate_all_dashboards_with_errors()

        # 汇总结果
        all_failed_requests = []
        dashboards_with_errors = []

        for dashboard in iterate_result["dashboards"]:
            if dashboard["has_error"]:
                dashboards_with_errors.append(dashboard)
            all_failed_requests.extend(dashboard.get("failed_requests", []))

        # 断言检查
        if dashboards_with_errors:
            pytest.fail(f"{len(dashboards_with_errors)} 个看板存在异常")

        if all_failed_requests:
            pytest.fail(f"页面存在 {len(all_failed_requests)} 个失败的请求")

        # 输出最终报告
        dashboard_names = [d['name'] for d in iterate_result['dashboards']]
        log_info(f"[SUCCESS] 共检查 {iterate_result['total']} 个看板: {', '.join(dashboard_names)}")

4.2 看板智能遍历

def iterate_all_dashboards_with_errors(self) -> dict:
    """智能遍历所有看板并检测异常
    
    AI优化:
    1. 自动识别当前看板
    2. 智能打开下拉列表
    3. 过滤功能按钮
    4. 逐个检查看板状态
    """
    result = {"total": 0, "dashboards": []}

    # 获取第一个看板
    first_dashboard_name = self.get_current_dashboard_name()

    # 设置监听器并检查
    failed_requests_1, all_requests_1 = self.setup_network_failure_listener()
    self.wait_for_timeout(WAIT_TIMEOUTS.get("short", 2000))
    has_error_1, errors_1 = self.check_current_dashboard_for_errors()

    result["dashboards"].append({
        "name": first_dashboard_name,
        "has_error": False,
        "errors": errors_1,
        "failed_requests": failed_requests_1,
        "all_requests": all_requests_1,
    })

    # 打开下拉列表
    self.click_dashboard_selector()
    self.wait_for_timeout(WAIT_TIMEOUTS.get("short", 2000))

    # 获取所有其他看板
    dashboard_names = self._get_dashboard_names_from_dropdown(first_dashboard_name)

    # 遍历检查每个看板
    for idx, dashboard_name in enumerate(dashboard_names, start=2):
        print(f"检查第 {idx} 个看板: {dashboard_name}")

        # 设置新的监听器
        failed_requests_n, all_requests_n = self.setup_network_failure_listener()

        # 切换看板
        self.switch_to_dashboard(dashboard_name)
        self.wait_for_timeout(WAIT_TIMEOUTS.get("medium", 3000))

        # 检查异常
        has_error_n, errors_n = self.check_current_dashboard_for_errors()

        result["dashboards"].append({
            "name": dashboard_name,
            "has_error": has_error_n or len(failed_requests_n) > 0,
            "errors": errors_n,
            "failed_requests": failed_requests_n,
            "all_requests": all_requests_n,
        })

    result["total"] = len(result["dashboards"])
    return result

💡 五、AI提示词实战

在实际项目中,我们可以利用AI的图像理解和自然语言处理能力,结合Playwright实现更智能的测试。以下是几个实用的提示词示例:

5.1 智能元素定位

当遇到复杂的下拉菜单时,传统CSS选择器可能不稳定。我们可以让AI帮助分析DOM结构:

提示词:

我需要定位一个下拉菜单中的选项,下拉选项包含JSON数据。
当前获取的文本是:'默认分组{"id":1193,"name":"默认分组"...}'

请帮我分析:
1. 如何只提取显示的文本"默认分组"而不是JSON
2. 是否有更稳定的CSS选择器可以定位到纯文本
3. 这种隐藏JSON在前端框架中常见吗?

技术栈:Element Plus + Vue

AI分析结果:

# 解决方案:使用子元素选择器
text = item.locator("span").first.text_content()
# 或使用文本分割
text = item.text_content().split("{")[0].strip()

5.2 异常根因分析

当测试失败时,可以让AI分析错误日志:

提示词:

测试日志显示:
- 页面加载失败
- 错误弹框内容:"网络请求超时"
- HTTP状态码:504

请分析:
1. 这可能是前端还是后端问题?
2. 需要查看哪些接口日志?
3. 如何在测试代码中添加针对性检测?

前端框架:Vue + Element Plus
后端:Spring Cloud

AI分析结果:

# 针对性检测504错误
if status == 504:
    print("检测到网关超时,可能是后端服务重启")
    # 截图保存现场
    page.screenshot(f"504_error_{datetime.now()}.png")

5.3 智能遍历策略

对于动态数量的列表,我们可以让AI帮我们设计遍历策略:

提示词:

页面有一个看板下拉列表,数量不固定(1-10个不等)。
需要:
1. 遍历所有看板
2. 检查每个看板的数据加载是否正常
3. 过滤掉"新建看板"这个功能按钮

请设计:
1. 如何判断哪些是真正的看板哪些是按钮?
2. 如何处理列表为空的情况?
3. 如何避免重复遍历?

AI分析结果:

def get_all_dashboards(self) -> list:
    """智能获取看板列表"""
    dashboards = []
    seen_names = set()  # 去重

    for item in self.find_all_dropdown_items():
        text = self.extract_clean_text(item)

        # 过滤条件
        if not text or len(text) > 50:
            continue
        if any(kw in text for kw in ["新建", "导出", "重置", "+"]):
            continue  # 跳过功能按钮

        if text not in seen_names:
            seen_names.add(text)
            dashboards.append({"name": text, "element": item})

    return dashboards

5.4 页面变化适应

当页面UI频繁变化时,可以用AI分析最佳适配策略:

提示词:

页面A从v1.0升级到v2.0后:
- 导航菜单从左侧移到了顶部
- 原来的".menu-item"选择器失效
- 文字从中文改成了英文

请分析:
1. 如何设计选择器让它更稳定(不易受UI变化影响)
2. 如何实现"Fallback"策略(主选择器失效时自动尝试备选)
3. 如何检测页面结构是否发生了重大变化?

AI分析结果:

def click_with_fallback(self, selectors: list) -> bool:
    """多重选择器Fallback"""
    for selector in selectors:
        try:
            elem = self.page.locator(selector).first
            if elem.is_visible():
                elem.click()
                return True
        except Exception:
            continue

    # 最后的兜底方案:基于相对位置
    return self.click_by_position()

5.5 测试用例生成

让AI帮助分析页面并生成测试用例骨架:

提示词:

请分析以下页面截图中的功能模块:
[图片]

页面说明:这是一个"个人工作台"页面,包含:
- 顶部导航栏
- 左侧菜单
- 中间是多个可切换的看板
- 每个看板有刷新、导出等功能按钮

请生成:
1. Page Object类的基本结构
2. 主要功能的方法名和参数
3. 可能的异常场景
4. 需要监控的API接口

预期用户角色:普通员工

AI生成的Page Object骨架:

class PersonalWorkspacePage(BasePage):
    """个人工作台页面"""

    def __init__(self, page):
        super().__init__(page)
        self.menu_personal_affairs = "个人事务"
        self.menu_personal_workspace = "个人工作台"

    def navigate_to_personal_workspace(self) -> bool:
        """导航到个人工作台"""
        pass

    def switch_dashboard(self, dashboard_name: str) -> bool:
        """切换看板"""
        pass

    def refresh_current_dashboard(self) -> bool:
        """刷新当前看板"""
        pass

    def export_dashboard(self, format: str = "excel") -> bool:
        """导出看板数据
        Args:
            format: 导出格式 (excel/csv/pdf)
        """
        pass

📊 六、测试执行与报告

运行命令

# 运行所有UI测试
pytest tests/ -v

# 运行特定测试类
pytest tests/test_personal_workspace.py -v

# 使用标记过滤
pytest -m ui -v

日志输出示例

2026-04-07 15:30:00 [INFO] 步骤1: 打开登录页面并登录系统
2026-04-07 15:30:05 [INFO] 步骤2: 进入菜单个人事务-个人工作台
2026-04-07 15:30:10 [INFO] 步骤3: 遍历检查所有看板
2026-04-07 15:30:12 [INFO] 第 1 个看板(默认): 个人工作台 - ✅ 正常
2026-04-07 15:30:15 [INFO] 检查第 2 个看板: 我的看板 - ✅ 正常
2026-04-07 15:30:18 [INFO] 检查第 3 个看板: 销售看板 - ✅ 正常

2026-04-07 15:30:20 [INFO] 步骤4: 检查页面请求接口
2026-04-07 15:30:20 [INFO]   请求总数: 150 个
2026-04-07 15:30:20 [INFO]   成功数量: 148 个
2026-04-07 15:30:20 [INFO]   失败数量: 2 个

2026-04-07 15:30:20 [ERROR] ❌ [404] https://api.example.com/xxx
2026-04-07 15:30:20 [ERROR] ❌ [500] https://api.example.com/yyy

2026-04-07 15:30:20 [SUCCESS] 共检查 3 个看板: 个人工作台, 我的看板, 销售看板

🎯 七、AI能力总结

能力

实现方式

价值

智能元素定位

多重选择器 + 文本提取

适应前端变化,减少维护成本

异常自动检测

弹框选择器监控

及时发现UI问题

请求智能监控

HTTP响应监听

发现API级别问题

智能遍历

自动识别看板列表

支持动态内容测试

日志智能分析

分类统计 + 失败聚合

快速定位问题根因


🚀 八、未来展望

  1. 视觉AI识别 - 利用CV技术识别页面元素变化

  2. 智能用例生成 - 基于页面结构自动生成测试用例

  3. 异常根因分析 - 结合日志和截图进行AI诊断

  4. 自愈能力 - 元素失效时自动寻找替代方案


📚 结语

通过将AI技术与传统UI自动化测试相结合,我们构建了一个更加智能、稳定的测试框架。核心思想是:让测试框架具备自我感知和适应能力,从而降低维护成本,提高测试效率。

希望本文对您有所帮助,欢迎交流讨论!


如果觉得有用,欢迎点赞、收藏、转发!

关注作者,获取更多测试开发干货~

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐