从零构建基于Playwright的UI自动化测试框架与CI/CD集成实战
1. 项目概述从“点点点”到“自动跑”的质变做测试的朋友尤其是做UI测试的谁没经历过“点点点”的噩梦一个回归测试包几十上百个用例每个用例要手动操作浏览器点按钮、输数据、等响应、看结果一轮下来少说也得大半天枯燥不说还容易因为疲劳而出错。更别提敏捷开发模式下一天可能要发布好几个版本手动测试根本跟不上节奏。这就是UI自动化测试要解决的核心痛点将重复、机械、易出错的UI操作交给机器去执行把人解放出来去做更有价值的探索性测试、业务逻辑分析和质量保障策略设计。“使用自动化测试工具进行UI自动化”这个标题听起来像是一个工具使用教程但它的内核远不止于此。它本质上是一场测试工作流的效率革命和质量保障体系的升级。我干了十多年测试从最早的QTP、Selenium IDE录屏回放到后来的Selenium WebDriver配合各种编程语言再到如今如日中天的Playwright和Cypress可以说亲身经历了UI自动化工具的迭代史。今天我就结合最新的技术趋势和实战经验跟你聊聊怎么把UI自动化这件事做稳、做深、做进CI/CD流水线让它不再是团队里的“玩具”或“负担”而是真正能扛起回归测试大旗的“主力军”。2. 核心思路与工具选型为什么是Playwright工欲善其事必先利其器。UI自动化工具五花八门选型是第一步也是最关键的一步。选错了后面全是坑选对了事半功倍。2.1 主流工具横向对比目前市面上主流的开源UI自动化框架绕不开Selenium、Cypress和Playwright这三巨头。Selenium WebDriver老牌王者生态最成熟支持语言最多Java, Python, C#, JavaScript等浏览器支持最全。它的架构是基于WebDriver协议通过浏览器驱动与真实浏览器通信。优点是灵活、强大、社区资源海量。但缺点也很明显环境配置相对复杂需要单独下载浏览器驱动异步操作和等待机制需要开发者精心处理否则不稳定执行速度在三者中相对较慢。Cypress后起之秀主打前端开发者友好和开箱即用。它运行在Node.js环境中采用独特的架构测试代码和应用程序运行在同一个循环中因此能提供超快的执行速度和实时重载、时间旅行调试等炫酷功能。它的断言库基于Mocha和Chai对前端开发者非常友好。但它的“缺点”也很突出只支持Chromium系浏览器和Firefox通过插件不支持Safari和IE/Edge旧版由于其架构限制不能直接用于测试跨域或多标签页的复杂场景虽然有workaround更像是一个专注于单页应用(SPA)的端到端测试工具。Playwright微软出品生于2020年可以看作是Selenium和Cypress优点的集大成者。它为Chromium、Firefox和WebKitSafari的内核都提供了高质量的自动化API真正实现了跨浏览器测试。它的设计理念非常现代自动等待几乎所有操作如点击、填充都内置了智能等待直到元素可操作极大减少了编写显式等待time.sleep的代码让脚本更健壮。网络拦截可以轻松地模拟网络条件如离线、慢速3G、拦截和修改网络请求这对于测试错误处理、性能和安全场景非常有用。多上下文与多页面天然支持浏览器上下文可以轻松模拟多个独立会话如不同用户登录以及在同一浏览器实例中操作多个标签页。强大的录制器内置的Codegen工具可以录制操作并生成多种语言的脚本是快速创建用例原型的利器。统一的API无论对哪种浏览器API都是一致的减少了学习成本和维护成本。2.2 为什么我推荐Playwright作为新项目的起点结合当前的技术趋势和实战需求尤其是考虑到你提到的“ui自动化代码如何cicd”和“如何使用 playwright 进行 ui 自动化测试”这些热词我的推荐是新项目优先选择Playwright老项目或特定场景考虑Selenium深度前端团队且场景匹配可考虑Cypress。理由如下稳定性与健壮性Playwright的自动等待机制让脚本的稳定性上了一个大台阶。新手最容易犯的“元素未找到”错误在Playwright里被大幅降低。这对于CI/CD环境下的无人值守执行至关重要。跨浏览器覆盖能力原生支持三大浏览器引擎对于需要保证跨浏览器兼容性的产品来说这是刚需。无需为不同浏览器配置不同的驱动或处理兼容性问题。对CI/CD友好Playwright Test其内置的测试运行器天生为现代CI/CD流程设计。它支持并行执行、重试失败用例、生成多种格式的报告HTML, JUnit, JSON Line、录制视频和追踪Trace等。这些功能开箱即用集成到Jenkins、GitLab CI、GitHub Actions中非常顺畅。开发体验优秀调试工具强大Trace Viewer可以回溯每一步操作录制功能实用TypeScript支持一流即使你用Python也能享受到更好的IDE提示。生态活跃背靠微软社区活跃更新迭代快遇到问题容易找到解决方案。注意工具选型没有绝对的对错只有适合与否。如果你的团队已经深耕Selenium多年积累了丰富的框架和库那么继续优化Selenium可能是更经济的选择。Cypress在纯前端团队和SPA测试场景下体验无与伦比。但对于大多数从零开始构建现代化UI自动化体系并计划深度集成CI/CD的团队Playwright是目前的最优解。3. 环境搭建与基础脚本编写确定了Playwright我们就开始动手。这里我以Python语言为例因为Python语法简洁在测试领域应用广泛也符合你提到的“python进行ui自动化”的热词。3.1 环境准备与安装首先确保你的系统已经安装了Python建议3.7及以上版本。然后通过pip安装Playwright。# 安装playwright的python库 pip install playwright # 安装Playwright所需的浏览器二进制文件Chromium, Firefox, WebKit playwright installplaywright install这一步很重要它会下载对应的浏览器到本地缓存后续脚本运行时直接调用无需单独安装或配置系统浏览器。3.2 第一个自动化脚本从录制开始对于新手最快上手的方式就是使用Playwright的录制功能。打开命令行运行playwright codegen https://www.example.com这会打开两个窗口一个浏览器窗口和一个录制器窗口。你在浏览器里的所有操作点击、输入、导航等都会被实时转换成代码显示在录制器窗口中。你可以选择生成Python、Java、C#、JavaScript等语言的代码。例如你录制了一个在百度搜索“Playwright”的操作可能会生成如下代码from playwright.sync_api import sync_playwright def run(playwright): browser playwright.chromium.launch(headlessFalse) # headlessFalse 表示打开可视化浏览器 context browser.new_context() page context.new_page() page.goto(https://www.baidu.com/) page.locator(input[name\wd\]).click() page.locator(input[name\wd\]).fill(playwright) page.locator(text百度一下).click() # ... 后续操作 # --------------------- context.close() browser.close() with sync_playwright() as playwright: run(playwright)录制是快速创建脚本原型的绝佳方式但生成的代码往往比较“脆”直接用于生产环境是不够的。我们需要对其进行改造和优化。3.3 编写健壮的生产级脚本一个健壮的自动化脚本需要考虑元素定位策略、等待机制、断言和错误处理。1. 元素定位策略Playwright推荐使用page.locator(selector)来定位元素。选择器selector的优先级建议是首选按角色Role和文本page.locator(button, has_textSubmit)或page.get_by_role(button, nameSubmit)。这是最接近用户视角的定位方式可读性高受UI样式变化影响小。次选按测试IDpage.locator(data-testidsubmit-button)。这需要开发同学在编写前端代码时添加># 等待导航完成 page.goto(https://example.com) page.wait_for_url(**/dashboard) # 等待URL包含dashboard # 等待元素出现使用expect断言更推荐 from playwright.sync_api import expect expect(page.locator(textWelcome, User!)).to_be_visible() # 等待网络请求 with page.expect_response(**/api/user) as response_info: page.click(button#fetch-user) response response_info.value print(response.json())3. 断言Assertions断言是验证测试结果的关键。Playwright Test框架内置了基于expect的断言库功能强大。# 检查元素可见、包含文本、属性值等 expect(page.locator(.status)).to_have_text(Success) expect(page.locator(input#email)).to_have_value(testexample.com) expect(page).to_have_title(Dashboard) expect(page).to_have_url(https://example.com/home)4. 一个完整的例子登录测试让我们写一个稍微完整一点的脚本测试一个假设的登录页面。import re from playwright.sync_api import Playwright, sync_playwright, expect def run(playwright: Playwright) - None: # 启动浏览器headlessTrue适合CI环境False适合调试 browser playwright.chromium.launch(headlessFalse, slow_mo500) # slow_mo 放慢操作便于观察 context browser.new_context() page context.new_page() try: # 1. 导航到登录页 page.goto(https://your-app.com/login) expect(page).to_have_title(re.compile(Login)) # 2. 填写表单 # 使用get_by_role定位这是最推荐的方式 page.get_by_role(textbox, nameusername).fill(testuser) # 或者使用locator配合CSS选择器 page.locator(input[typepassword]).fill(secret123) # 3. 点击登录按钮 page.get_by_role(button, nameSign In).click() # 4. 等待导航并验证登录成功 # 等待跳转到dashboard页面 page.wait_for_url(**/dashboard) # 验证欢迎信息出现 welcome_msg page.locator(h1) expect(welcome_msg).to_contain_text(Welcome, testuser!) # 验证登录后某些特定元素存在 expect(page.get_by_role(link, nameProfile)).to_be_visible() print(登录测试通过) except Exception as e: # 出错时截图这对于CI/CD调试非常重要 page.screenshot(pathlogin_failure.png) print(f测试失败: {e}) raise e # 重新抛出异常让测试运行器感知失败 finally: # 5. 清理资源 context.close() browser.close() with sync_playwright() as playwright: run(playwright)这个脚本包含了导航、定位、交互、等待、断言和基本的错误处理截图。slow_mo参数在调试时非常有用可以让你看清每一步操作。4. 构建可维护的UI自动化框架单个脚本容易写但当成百上千个测试用例需要管理时没有良好的框架设计代码会迅速变成难以维护的“蜘蛛网”。一个基本的自动化测试框架通常包含以下层级4.1 框架核心分层1. 基础层Base Layer浏览器管理封装浏览器的启动、关闭、上下文创建。可以在这里统一配置浏览器类型、是否无头模式、视口大小、忽略HTTPS错误等。页面对象模型Page Object Model, POM核心类提供一个所有页面对象类的基类封装常用的操作如click,fill,wait_for_element等并集成日志和截图功能。# base_page.py import logging from datetime import datetime from pathlib import Path from playwright.sync_api import Page, expect class BasePage: def __init__(self, page: Page): self.page page self.logger logging.getLogger(__name__) def take_screenshot(self, name: str): 通用截图方法 timestamp datetime.now().strftime(%Y%m%d_%H%M%S) screenshot_dir Path(test_results/screenshots) screenshot_dir.mkdir(parentsTrue, exist_okTrue) filepath screenshot_dir / f{name}_{timestamp}.png self.page.screenshot(pathfilepath) self.logger.info(fScreenshot saved to: {filepath}) return filepath def wait_and_click(self, selector: str): 等待元素可点击后点击 element self.page.locator(selector) expect(element).to_be_enabled() element.click() # ... 其他通用方法2. 页面对象层Page Object Layer这是框架的核心。每个页面或页面中的重要组件对应一个类。这个类包含该页面的元素定位器和业务操作方法。绝对不要在测试用例中直接写page.locator(‘#id’)这样的定位器全部抽离到页面对象中。# login_page.py from .base_page import BasePage from playwright.sync_api import Page class LoginPage(BasePage): # 元素定位器Locators property def username_input(self): return self.page.get_by_role(textbox, nameusername) property def password_input(self): return self.page.locator(input[typepassword]) property def sign_in_button(self): return self.page.get_by_role(button, nameSign In) property def error_message(self): return self.page.locator(.alert-error) # 页面操作方法 def navigate_to(self): self.page.goto(/login) return self def login(self, username: str, password: str): self.username_input.fill(username) self.password_input.fill(password) self.sign_in_button.click() def get_error_text(self) - str: return self.error_message.text_content()3. 测试数据层Test Data Layer将测试数据用户名、密码、商品信息等与测试逻辑分离。可以使用JSON、YAML、CSV文件或者从数据库、API获取。对于简单的数据Python字典或pytest.mark.parametrize装饰器就很好用。# conftest.py 或单独的数据文件 import pytest LOGIN_TEST_DATA [ (valid_user, correct_password, True, 登录成功), (invalid_user, wrong_password, False, 用户名或密码错误), (, some_password, False, 用户名不能为空), ]4. 测试用例层Test Case Layer使用测试框架如pytest组织测试用例。测试用例应该只包含测试步骤和断言读起来像自然语言。# test_login.py import pytest from pages.login_page import LoginPage from pages.dashboard_page import DashboardPage pytest.mark.parametrize(username, password, expected_success, expected_msg, LOGIN_TEST_DATA) def test_user_login(page, username, password, expected_success, expected_msg): 测试用户登录功能 login_page LoginPage(page) dashboard_page DashboardPage(page) # 步骤1导航到登录页 login_page.navigate_to() # 步骤2执行登录操作 login_page.login(username, password) # 步骤3验证结果 if expected_success: # 预期成功应跳转到仪表盘 assert dashboard_page.is_at(), f登录成功后未跳转到仪表盘。当前URL: {page.url} assert dashboard_page.welcome_message_contains(username) else: # 预期失败应显示错误信息 actual_error login_page.get_error_text() assert expected_msg in actual_error, f错误信息不符。期望包含‘{expected_msg}’实际是‘{actual_error}’5. 工具与辅助层Utilities Layer存放通用工具如读取配置文件、处理日期、生成随机数据、发送测试报告邮件、操作数据库等。# utils/config_reader.py import yaml import os from typing import Any, Dict def read_config(config_file: str config.yaml) - Dict[str, Any]: with open(config_file, r, encodingutf-8) as f: config yaml.safe_load(f) # 可以在这里处理环境变量覆盖等逻辑 config[base_url] os.getenv(BASE_URL, config.get(base_url, http://localhost:8080)) return config4.2 使用Playwright Test作为测试运行器虽然你可以用pytest-playwright插件来集成Playwright和pytest这也是一个非常流行的选择但Playwright官方推荐的、功能更强大的方式是使用其自带的Playwright Test运行器。它基于playwright/testNode.js或pytest-playwrightPython提供了更深度的集成。使用Playwright TestPython版的优势内置Fixture自动管理page,context,browser的生命周期你无需在用例中手动打开和关闭。并行执行轻松配置多worker并行运行测试大幅缩短测试套件执行时间。强大的报告原生支持HTML、JUnit、JSON Line等多种报告格式与CI/CD工具无缝对接。追踪Trace和视频测试失败时自动保存追踪文件包含操作、网络、快照和视频重现问题神器。全局配置通过playwright.config.py一个文件集中管理浏览器、上下文、截图、视频、基础URL等所有配置。一个简单的playwright.config.py配置示例# playwright.config.py import os from playwright.sync_api import Playwright, BrowserType def run(playwright: Playwright, config): # 这里可以动态获取配置例如从环境变量 pass # 配置对象 config { # 测试文件位置 test_dir: tests, test_match: **/*.spec.py, # 测试文件匹配模式 # 全局超时 timeout: 30000, # 每个测试30秒超时 # 期望断言超时 expect_timeout: 5000, # 全局是否无头模式通常CI环境设为True headless: os.getenv(CI, false).lower() true, # 失败时重试次数 retries: 1 if os.getenv(CI) else 0, # CI环境重试1次 # 工作进程数用于并行执行 workers: os.getenv(PLAYWRIGHT_WORKERS, 2), # 报告 reporter: [ [list], # 控制台输出 [html, {output_folder: playwright-report, open: never}], # HTML报告 [junit, {output_file: test-results/results.xml}] # JUnit报告供CI集成 ], # 项目配置可以为不同浏览器或设备定义不同项目 projects: [ { name: chromium, use: { browser_name: chromium, viewport: {width: 1920, height: 1080}, ignore_https_errors: True, screenshot: only-on-failure, video: retain-on-failure, trace: retain-on-failure, # 失败时保留追踪 }, }, # 可以添加firefox, webkit项目用于跨浏览器测试 ], # 全局基础URLpage.goto(‘/login’) 会拼接成 base_url ‘/login’ base_url: os.getenv(BASE_URL, http://localhost:8080), } # 注意以上是概念性配置。实际Playwright for Python的配置方式略有不同 # 通常通过命令行参数或pytest.ini配置。但理念相通集中化管理。使用Playwright Test运行器执行测试# 运行所有测试 pytest --browser chromium --headed # 用Chromium有头模式运行 pytest --browser all # 运行所有配置的浏览器项目 # 运行特定标签的测试 pytest -m smoke # 生成HTML报告 pytest --htmlreport.html --self-contained-html5. 集成CI/CD让自动化测试成为质量门禁这是将UI自动化价值最大化的关键一步。我们的目标是将自动化测试套件集成到持续集成/持续部署CI/CD流水线中使其在每次代码提交、合并或定时任务时自动执行成为代码合并和发布的质量门禁。5.1 CI/CD流程设计一个典型的集成流程如下开发者推送代码到Git仓库如GitLab、GitHub。CI服务器如Jenkins、GitLab Runner、GitHub Actions被触发。构建阶段拉取代码安装项目依赖包括Playwright。测试阶段 a.安装浏览器运行playwright install或playwright install --with-deps在CI环境中通常需要。 b.执行UI自动化测试运行测试命令如pytest tests/ --browserchromium --headless。 c.收集结果生成JUnit XML格式的测试报告和HTML报告。报告与门禁 a. CI服务器解析JUnit报告判断测试是否通过。 b. 如果测试失败流水线标记为失败阻止向更高级别环境如预发布、生产的部署并通过邮件、Slack等通知相关人员。 c. 将HTML报告和失败截图/视频/Trace文件归档供开发者查看。5.2 以GitHub Actions为例的配置GitHub Actions是目前非常流行的CI/CD平台配置简单。下面是一个完整的.github/workflows/playwright.yml配置文件示例name: Playwright UI Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] schedule: # 每天凌晨2点运行一次可选用于定时回归 - cron: 0 2 * * * jobs: test: timeout-minutes: 60 # 整体任务超时时间 runs-on: ubuntu-latest # 使用最新的Ubuntu运行器 steps: - uses: actions/checkoutv4 # 步骤1检出代码 - uses: actions/setup-pythonv5 # 步骤2设置Python环境 with: python-version: 3.11 - name: Install dependencies # 步骤3安装Python依赖 run: | python -m pip install --upgrade pip pip install -r requirements.txt # 如果使用pytest-playwright也需要安装 pip install pytest-playwright - name: Install Playwright Browsers # 步骤4安装Playwright浏览器 run: playwright install --with-deps chromium # --with-deps 会同时安装系统依赖在CI环境中很重要 - name: Run UI Tests # 步骤5执行UI自动化测试 run: | # 设置环境变量如基础URL export BASE_URLhttps://staging.your-app.com # 运行测试无头模式生成JUnit报告和HTML报告 pytest tests/ \ --browserchromium \ --headless \ -v \ --junitxmltest-results/results.xml \ --htmlplaywright-report/index.html \ --self-contained-html env: # 可以在这里传递测试所需的密码等敏感信息使用GitHub Secrets TEST_PASSWORD: ${{ secrets.TEST_PASSWORD }} - name: Upload Test Results # 步骤6上传测试结果用于归档和展示 if: always() # 无论测试成功失败都上传 uses: actions/upload-artifactv4 with: name: playwright-report path: | playwright-report/ test-results/ retention-days: 7 # 保留7天 - name: Upload Screenshots on Failure # 步骤7失败时上传截图和追踪更详细的诊断 if: failure() uses: actions/upload-artifactv4 with: name: playwright-failure-details path: | test-results/screenshots/ test-results/videos/ test-results/traces/ retention-days: 30这个工作流实现了在代码推送到main或develop分支或创建Pull Request时触发。安装环境、依赖和浏览器。执行测试并生成报告。将HTML报告和详细的失败信息作为制品Artifact保存方便下载查看。5.3 关键实践与注意事项1. 测试环境与数据隔离CI环境下的测试必须是可重复和独立的。使用测试专用环境UI自动化测试应在独立的测试环境Staging中进行避免影响生产数据和用户。数据准备与清理每个测试用例在执行前应准备其所需的数据通过API或数据库脚本并在执行后清理防止用例间相互干扰。可以使用setUp和tearDown方法或pytest的fixture来实现。使用独立账号为自动化测试创建专用的测试账号避免与手动测试或其他自动化任务冲突。2. 稳定性与重试机制UI测试天生比API测试更“脆弱”受网络、前端渲染、第三方依赖影响更大。启用重试在CI配置或测试运行器中配置失败重试如--retries1。有时一次偶然的网络延迟或元素加载慢会导致失败重试一次可能就通过了。优先使用可靠的定位器如前面所述优先使用># 只运行冒烟测试 pytest -m smoke # 运行除了慢速测试外的所有测试 pytest -m not slow6. 高级技巧与疑难排查掌握了基础和框架再来看看一些能让你事半功倍的高级技巧和常见坑的解决方案。6.1 处理动态内容与复杂等待场景一个列表加载数据条目是动态渲染的你需要等待某个特定条目出现。不佳做法time.sleep(10)—— 这是万恶之源要么等不够要么浪费大量时间。推荐做法# 等待包含特定文本的列表项出现 expect(page.locator(ul.items-list li, has_text目标条目)).to_be_visible() # 或者等待列表项数量达到预期 expect(page.locator(ul.items-list li)).to_have_count(10) # 等待API请求完成 with page.expect_response(**/api/items) as response_info: page.click(#load-items) response response_info.value # 可以进一步断言响应内容 assert response.ok items response.json() assert len(items) 06.2 处理iframe、新窗口和文件下载iframe需要先定位到iframe框架然后在其中操作。# 通过名称或URL定位iframe frame page.frame(namelogin-frame) # 或 page.frame(urlre.compile(r.*login.*)) # 在iframe内操作 frame.fill(#username, user)新窗口/标签页使用page.context.expect_page()来等待新页面。# 点击一个会打开新标签页的链接 with page.context.expect_page() as new_page_info: page.click(a[target_blank]) new_page new_page_info.value # 在新页面上操作 new_page.wait_for_load_state() expect(new_page).to_have_title(新页面标题)文件下载使用page.wait_for_event(‘download’)。# 点击下载按钮 with page.expect_download() as download_info: page.click(button#download-report) download download_info.value # 等待下载完成并保存到指定路径 path download.save_as(/tmp/report.pdf) print(f文件已下载到: {path})6.3 模拟复杂用户交互鼠标悬停Hoverpage.locator(.menu-item).hover() expect(page.locator(.sub-menu)).to_be_visible()拖放Drag and Drop# Playwright 提供了便捷的 drag_to 方法 source page.locator(#item-to-drag) target page.locator(#drop-area) source.drag_to(target) # 或者使用更底层的 mouse 操作 source.hover() page.mouse.down() target.hover() page.mouse.up()键盘操作page.locator(input).press(ControlA) # 全选 page.locator(input).press(Backspace) # 删除 page.keyboard.type(Hello World!) # 输入 page.keyboard.press(Enter) # 回车6.4 常见问题排查实录问题1Timeout 30000ms exceeded.元素找不到或操作超时可能原因1定位器写错了或者元素在iframe里。排查使用Playwright Inspector (playwright open或playwright codegen) 重新录制或验证定位器。检查页面是否有iframe。可能原因2页面加载太慢或者元素是动态加载的在操作时还未出现。排查在操作前增加等待。优先使用expect(locator).to_be_visible()或page.wait_for_selector()而不是sleep。检查网络请求是否完成。可能原因3页面有多个匹配的元素操作了错误的那个。排查使用更精确的定位器例如page.locator(‘button’).first或page.locator(‘button’).nth(1)或者使用page.locator(‘button:has-text(“特定文本”)’)。问题2测试在本地通过但在CI上失败。可能原因1CI环境是无头模式headless而本地是有头模式。某些前端行为在两种模式下有差异。解决在本地也用无头模式跑一遍测试 (--headless)。检查前端代码是否对无头模式有特殊判断有些反爬机制会检测无头浏览器。可能原因2CI环境网络、CPU、内存资源不足导致响应慢。解决增加全局超时时间。在定位或操作前使用page.wait_for_timeout(1000)作为临时诊断但最终还是要优化为智能等待。优化CI运行器的配置。可能原因3测试数据或环境状态不同。解决确保CI环境的数据是干净的、可预测的。使用固定的测试数据集并在每个测试套件或用例执行前后进行数据重置。问题3如何处理需要登录的测试最佳实践使用浏览器上下文Browser Context和存储状态Storage State。在一个单独的“认证脚本”中完成登录。保存登录后的上下文状态cookies, local storage到文件。在正式的测试用例中直接加载这个状态文件绕过登录步骤。# auth_setup.py - 运行一次生成认证状态文件 from playwright.sync_api import sync_playwright with sync_playwright() as p: browser p.chromium.launch(headlessFalse) context browser.new_context() page context.new_page() page.goto(https://your-app.com/login) page.fill(#username, testuser) page.fill(#password, password) page.click(#login-btn) # 等待登录成功跳转到首页 page.wait_for_url(**/dashboard) # 将认证状态保存到文件 context.storage_state(path.auth/state.json) browser.close() # test_with_auth.py - 正式测试用例 import pytest from playwright.sync_api import Browser pytest.fixture(scopesession) def browser_context_args(browser_context_args, playwright): # 为所有测试加载共享的认证状态 return { **browser_context_args, storage_state: .auth/state.json } def test_access_dashboard(page): # 此时page已经处于登录状态直接访问受保护页面 page.goto(/dashboard) expect(page).to_have_title(Dashboard)这种方法避免了每个测试用例都执行登录操作极大提升了测试速度也减少了因登录失败导致整个测试套件失败的风险。UI自动化测试不是一蹴而就的它是一个需要持续投入和维护的工程。从选择一个趁手的工具开始搭建一个清晰可维护的框架设计稳定可靠的测试用例最后将其无缝嵌入到团队的开发流水线中让它成为质量反馈环中不可或缺的一环。这个过程肯定会遇到各种挑战但当你看到每次代码提交后自动化测试套件自动运行并给出绿色通行证时当你因为自动化测试提前发现了潜在的回归缺陷时你会觉得所有的投入都是值得的。记住好的UI自动化测试应该是“写起来不费劲跑起来很稳定出了问题能说清”。