1. 项目概述“Web自动化笔记”这个标题听起来像是一个技术人的私房菜谱。它不是那种宏大叙事的架构设计也不是高深莫测的算法解析而更像是一位常年泡在代码和浏览器里的工程师在无数个调试、测试、部署的日夜后沉淀下来的那份最接地气的实战手册。我干了十多年全栈和测试开发深知“自动化”这三个字背后远不止是写几行脚本那么简单。它关乎效率、关乎质量、关乎如何把我们从那些重复、枯燥且容易出错的“体力活”中解放出来去处理更有价值的问题。当你听到“Web自动化”脑海里可能立刻蹦出Selenium、Playwright这些框架的名字。没错它们是核心工具但真正的“笔记”价值在于如何把这些工具用对、用好、用出效率。这背后是一整套的工程化思维从环境搭建、脚本编写、用例设计到持续集成、结果分析和问题排查。它解决的核心问题是如何让Web应用的测试、部署、监控乃至日常操作变得可靠、可重复且无需人工干预。无论你是前端开发者想验证页面交互还是测试工程师需要保障回归测试或是运维同学追求一键部署和监控这份“笔记”都能给你提供一套清晰的行动路线图和避坑指南。2. 自动化测试框架深度选型与对比2.1 核心框架三剑客Selenium, Playwright, CypressWeb自动化的世界工具框架层出不穷但经过多年沉淀目前主流且经得起考验的“三剑客”是Selenium、Playwright和Cypress。选择哪一个不是非此即彼而是要看你的具体场景。Selenium老牌劲旅生态最庞大。它的核心是WebDriver协议这是一个W3C标准意味着几乎所有浏览器都原生或通过驱动支持它。Selenium WebDriver允许你用多种编程语言Java, Python, C#, JavaScript等来控制浏览器。它的优势在于稳定、兼容性极广从IE旧版到最新Chrome社区资源丰富任何稀奇古怪的问题几乎都能找到答案。但它的劣势也明显速度相对较慢需要额外管理浏览器驱动对于现代Web应用的一些复杂交互如下拉加载、文件上传、网络拦截需要更多代码来处理。Playwright微软出品后来居上。它可以说是为现代Web应用和开发流程量身定做的。Playwright最大的特点是支持Chromium、Firefox和WebKit三大浏览器引擎并且提供了非常强大的API例如自动等待、网络拦截、移动设备模拟、生成追踪视频等。它的执行速度通常比Selenium快脚本编写也更简洁。但它的“新”也意味着在某些非常老的企业内网环境或特定浏览器版本上可能不如Selenium兼容性好。Cypress前端开发者的宠儿。它的设计哲学完全不同运行在浏览器内部与你的应用共享同一个生命周期。这带来了颠覆性的体验超快的执行速度、实时重载、时间旅行调试可以回溯到测试的任何一个步骤。Cypress对现代JavaScript框架React, Vue, Angular的支持得天独厚。然而它的“局限”也源于此它主要专注于同源测试对于需要跨域或多标签页的复杂场景处理起来比较麻烦并且只支持JavaScript/TypeScript。实操心得对于大多数从零开始的团队我现在的首选是Playwright。它平衡了能力、速度和开发体验。如果团队技术栈以Node.js/JavaScript为主且应用是现代单页面应用SPACypress的开发者体验无与伦比。而如果你的项目需要覆盖极其古老的浏览器如IE 11或者团队语言栈是Java/.NET那么Selenium依然是可靠的选择。2.2 框架之外的生态拼图选定了核心框架这只是万里长征第一步。一个完整的自动化体系还需要其他拼图测试运行器Test Runner用来组织、运行和报告你的测试用例。常见的有JUnitJava、pytestPython、Jest/MochaJavaScript。它们提供了beforeEach、afterEach等钩子函数以及断言库。断言库Assertion Library用于验证测试结果是否符合预期。比如assertPython内置、ChaiJS、HamcrestJava。好的断言库能让错误信息更清晰。报告生成器Reporter没人想看控制台里密密麻麻的日志。我们需要美观的HTML报告比如Allure、ExtentReports它们能展示测试通过率、耗时、错误截图甚至操作步骤。页面对象模型Page Object Model, POM这不是一个工具而是一种设计模式。它将页面的元素定位和操作封装成类使测试脚本更清晰、更易维护减少重复代码。这是中大型自动化项目必须采用的模式。持续集成/持续部署CI/CD集成自动化脚本最终要融入开发流程。你需要将其接入Jenkins、GitLab CI、GitHub Actions等实现代码提交后自动触发测试。3. 环境搭建与核心脚本编写实战3.1 基于Playwright的快速入门让我们以目前势头最猛的Playwright为例快速搭建一个可运行的自动化环境。假设我们使用Python语言Playwright对Python的支持非常友好。首先安装Playwright。这里我强烈建议使用虚拟环境如venv来隔离项目依赖。# 创建并激活虚拟环境Linux/macOS python -m venv venv source venv/bin/activate # Windows python -m venv venv venv\Scripts\activate # 安装Playwright的Python库 pip install playwright # 安装Playwright所需的浏览器Chromium, Firefox, WebKit playwright install安装完成后创建一个简单的测试脚本比如叫test_baidu_search.pyimport asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动浏览器headlessFalse表示显示浏览器界面方便调试 browser await p.chromium.launch(headlessFalse) # 创建新页面 page await browser.new_page() # 导航到百度 await page.goto(https://www.baidu.com) # 定位搜索框并输入关键词 await page.fill(input#kw, Playwright自动化) # 点击“百度一下”按钮 await page.click(input#su) # 等待页面加载Playwright会自动等待大部分导航和元素 await page.wait_for_load_state(networkidle) # 截图保存作为测试证据 await page.screenshot(pathsearch_result.png) # 获取页面标题并断言 title await page.title() assert Playwright自动化 in title print(f页面标题: {title}) # 关闭浏览器 await browser.close() # 运行异步函数 asyncio.run(main())运行这个脚本python test_baidu_search.py。你会看到浏览器自动打开完成搜索并截图。这就是一个最基础的Web自动化操作。3.2 元素定位自动化脚本的基石自动化脚本的核心是“找到元素操作元素”。元素定位的准确性和稳定性直接决定了脚本的可靠性。Playwright提供了多种强大的定位器Locators。最佳实践使用get_by_*系列定位器。这是Playwright推荐的方式可读性更好且能自动等待元素出现。# 通过文本内容定位 await page.get_by_text(登录).click() # 通过角色ARIA定位可访问性友好 await page.get_by_role(button, name提交).click() # 通过占位符文本定位 await page.get_by_placeholder(请输入用户名).fill(admin) # 通过Label文本定位关联的表单元素 await page.get_by_label(密码).fill(123456) # 通过Title属性定位 await page.get_by_title(提示信息).hover()传统但依然有效的CSS和XPath选择器# CSS选择器 await page.locator(#submit-btn).click() await page.locator(.list-item:has-text(特定项)).click() # XPath选择器在CSS无法满足复杂逻辑时使用 await page.locator(//button[contains(class, primary) and text()保存]).click()注意事项尽量避免使用绝对XPath如/html/body/div[3]/div[2]/button因为它极度脆弱页面结构稍有变动就会失效。优先使用ID、相对XPath或CSS结合文本的定位方式。3.3 等待策略解决“元素未找到”的银弹90%的自动化脚本失败源于“等待”。页面加载、元素渲染、AJAX请求都需要时间。Playwright的自动等待这是它的一大亮点。像click(),fill()这样的操作Playwright在执行前会自动等待元素满足可操作状态可见、可交互、稳定。这省去了大量显式等待的代码。显式等待Explicit Waits当需要等待特定条件时使用。# 等待元素出现 await page.locator(.success-message).wait_for() # 等待元素消失 await page.locator(.loading-spinner).wait_for(statehidden) # 等待特定文本出现 await page.get_by_text(操作成功).wait_for() # 等待网络请求完成 await page.wait_for_response(**/api/user/profile) # 等待导航完成 await page.wait_for_url(**/dashboard)超时设置全局设置或单独设置。# 全局设置超时毫秒 page.set_default_timeout(60000) # 单个操作设置超时 await page.click(button, timeout10000)4. 构建健壮且可维护的测试套件4.1 页面对象模型POM实战直接在上面的脚本里写定位和操作对于小脚本没问题但项目一旦变大维护将是灾难。POM模式将每个页面封装成一个类。创建一个pages/login_page.pyfrom playwright.async_api import Page class LoginPage: def __init__(self, page: Page): self.page page self.username_input page.get_by_placeholder(用户名/邮箱) self.password_input page.get_by_placeholder(密码) self.login_button page.get_by_role(button, name登录) self.error_message page.locator(.alert-error) async def navigate(self): await self.page.goto(https://example.com/login) async def login(self, username: str, password: str): await self.username_input.fill(username) await self.password_input.fill(password) await self.login_button.click() async def get_error_text(self) - str: await self.error_message.wait_for() return await self.error_message.text_content()在测试脚本中使用import pytest from pages.login_page import LoginPage pytest.mark.asyncio async def test_login_success(page): login_page LoginPage(page) await login_page.navigate() await login_page.login(valid_user, valid_pass) # 断言登录后跳转到了首页 await page.wait_for_url(**/home) assert await page.title() 首页 pytest.mark.asyncio async def test_login_failure(page): login_page LoginPage(page) await login_page.navigate() await login_page.login(wrong_user, wrong_pass) error_text await login_page.get_error_text() assert 用户名或密码错误 in error_text这样当登录页面的HTML结构发生变化时你只需要修改LoginPage这一个类所有用到登录的测试用例都不需要改动。4.2 测试数据管理与参数化测试数据不应该硬编码在脚本里。常用的方法有外部文件JSON, YAML, CSV。pytest.mark.parametrize装饰器如果使用pytest。创建一个test_data/login_data.json{ valid_credentials: { username: test_user, password: Test123, expected_url: **/dashboard }, invalid_username: { username: wrong, password: Test123, expected_error: 用户不存在 }, invalid_password: { username: test_user, password: wrong, expected_error: 密码错误 } }在测试中读取并使用import json import pytest with open(test_data/login_data.json, r, encodingutf-8) as f: LOGIN_TEST_DATA json.load(f) pytest.mark.asyncio pytest.mark.parametrize(case_name, test_data, LOGIN_TEST_DATA.items()) async def test_login_parametrized(page, case_name, test_data): login_page LoginPage(page) await login_page.navigate() await login_page.login(test_data[username], test_data[password]) if expected_url in test_data: await page.wait_for_url(test_data[expected_url]) assert True elif expected_error in test_data: error_text await login_page.get_error_text() assert test_data[expected_error] in error_text4.3 钩子Hooks与夹具Fixtures的使用利用测试框架的钩子和夹具来处理测试的公共前置和后置操作如初始化浏览器、登录状态、清理数据。使用pytest和playwright的官方插件pytest-playwright# conftest.py import pytest from playwright.async_api import async_playwright pytest.fixture(scopesession) async def browser(): async with async_playwright() as p: # 启动一个浏览器实例供所有测试用例共享session级别 browser await p.chromium.launch(headlessTrue) # CI环境通常用无头模式 yield browser await browser.close() pytest.fixture async def page(browser): # 每个测试用例一个独立的页面上下文隔离测试 context await browser.new_context() page await context.new_page() yield page await context.close() pytest.fixture async def logged_in_page(page): 提供一个已登录状态的页面夹具 login_page LoginPage(page) await login_page.navigate() await login_page.login(predefined_user, predefined_pass) # 确保登录成功 await page.wait_for_url(**/dashboard) return page在测试用例中直接使用这些夹具pytest.mark.asyncio async def test_access_profile(logged_in_page): # logged_in_page 已经是一个登录后的页面对象 await logged_in_page.click(text个人中心) # ... 后续断言5. 集成CI/CD与生成测试报告5.1 接入GitHub Actions自动化测试只有融入开发流程才能发挥最大价值。以下是一个简单的GitHub Actions工作流配置在每次推送代码时运行Playwright测试。在项目根目录创建.github/workflows/playwright.ymlname: Playwright Tests on: [push, pull_request] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv4 with: python-version: 3.10 - name: Install dependencies run: | pip install -r requirements.txt pip install pytest pytest-asyncio pytest-playwright - name: Install Playwright Browsers run: playwright install --with-deps chromium - name: Run your tests run: pytest tests/ --htmlreport.html --self-contained-html env: # 如果有需要可以在这里设置环境变量如测试环境URL BASE_URL: ${{ secrets.TEST_ENV_URL }} - name: Upload test report uses: actions/upload-artifactv3 if: always() # 即使测试失败也上传报告 with: name: playwright-report path: report.html retention-days: 30这个工作流做了几件事1) 检出代码2) 安装Python和依赖3) 安装Playwright和浏览器4) 运行pytest并生成一个独立的HTML报告5) 将报告上传为工件供后续下载查看。5.2 生成丰富的测试报告使用pytest-html插件可以生成基础报告。但对于更专业的需求Allure是行业标准。安装Allurepip install allure-pytest # 还需要安装Allure命令行工具可以从官网下载或通过包管理器运行测试并生成Allure结果数据pytest tests/ --alluredir./allure-results在本地生成并查看报告allure serve ./allure-results在CI中可以将allure-results目录上传为工件然后使用Allure的CI工具生成在线报告。Allure报告提供了清晰的用例分类、步骤详情、截图、错误日志甚至支持添加测试描述和链接是团队协作和问题分析的利器。6. 常见问题排查与高级技巧实录6.1 典型问题与解决方案速查表问题现象可能原因解决方案Element not found/TimeoutError1. 元素定位器写错或已失效。2. 页面加载/元素渲染太慢。3. 元素在iframe或shadow DOM内。4. 页面发生了非预期跳转。1. 使用浏览器开发者工具重新检查元素优先使用get_by_*定位器。2. 增加显式等待wait_for_selector,wait_for_function。3. 使用page.frame()切换到iframe或用element_handle.query_selector()穿透shadow DOM。4. 在操作前用page.wait_for_url()确认页面状态。脚本在本地运行成功在CI上失败1. CI环境是无头headless模式可能与有头模式行为有细微差别。2. CI环境资源CPU/内存不足导致响应慢。3. 网络或测试环境在CI上不可达。4. 浏览器版本不一致。1. 本地也使用headlessTrue模式运行一遍复现问题。2. 在CI配置中增加超时时间或优化脚本减少资源占用。3. 检查CI的网络安全组、环境变量配置是否正确。4. 在CI中明确指定安装的浏览器版本与本地保持一致。文件上传失败文件选择框是input typefile但Playwright无法直接点击。使用set_input_files()方法绝对不要尝试去模拟点击文件选择对话框。await page.locator(input[typefile]).set_input_files(/path/to/file.png)下拉选择Select操作不生效使用了自定义样式化的下拉组件不是原生select。1. 先点击触发下拉框展开。2. 再点击下拉列表中的选项。例如await page.click(.custom-select-trigger); await page.click(.dropdown-item:has-text(选项A))新窗口/标签页处理点击链接后打开了新窗口脚本还在原页面操作。使用上下文context来监听新页面async with page.context.expect_page() as new_page_info: await page.click(a[target_blank]) new_page await new_page_info.value6.2 网络请求拦截与模拟Mocking这是Playwright和类似框架的高级功能能极大提升测试速度和稳定性。你可以拦截并修改请求或响应。# 拦截请求修改请求头或阻止请求 await page.route(**/api/ads/*, lambda route: route.abort()) # 拦截并阻止广告请求 # 拦截请求并返回模拟数据 async def handle_route(route): # 伪造一个成功的用户信息响应 await route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({username: mocked_user, age: 30}) ) await page.route(**/api/user/profile, handle_route) # 拦截响应并修改 await page.route(**/api/data, lambda route: route.continue_()) # 先继续请求 # 或者直接修改响应 async def modify_response(route, response): original_body await response.text() modified_body original_body.replace(status: pending, status: approved) await route.fulfill(responseresponse, bodymodified_body) await page.route(**/api/data, modify_response)6.3 执行追踪与视频录制对于调试那些“时好时坏”的诡异问题视频录制和追踪文件是无价之宝。# 在启动浏览器时启用追踪和视频录制 context await browser.new_context( record_video_dirvideos/, # 视频保存目录 record_video_size{width: 1920, height: 1080} ) # 或者全局启用追踪 await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) page await context.new_page() # ... 执行你的测试步骤 ... # 测试结束后保存追踪文件 await context.tracing.stop(pathtrace.zip) # 关闭上下文会自动保存视频 await context.close()当测试失败时你可以打开trace.zip文件使用Playwright的命令行工具playwright show-trace trace.zip它会以一个可视化时间轴的形式重现测试的每一步操作、网络请求、控制台日志就像时光机一样。6.4 处理动态内容与验证码这是自动化测试的“圣杯”难题。完全通用的解决方案不存在但有一些策略环境隔离在测试环境中完全禁用验证码或使用一个万能验证码如“0000”。这是最推荐、最可靠的方式。第三方服务使用付费的验证码识别服务精度有限有成本且违反某些网站条款。Cookie/Token复用首次手动登录获取有效的会话Cookie或Token然后在自动化脚本中注入跳过登录环节。Playwright可以很方便地导入导出Cookie。# 保存Cookie cookies await context.cookies() with open(state.json, w) as f: json.dump(cookies, f) # 加载Cookie with open(state.json, r) as f: cookies json.load(f) await context.add_cookies(cookies)OCR谨慎使用对于简单的图形验证码可以截图后使用Tesseract等OCR库识别但成功率低维护成本高不推荐用于核心流程。我个人在实际项目中的体会是与其在破解验证码上花费巨大精力不如推动项目团队为测试环境提供“后门”或专用测试账号。这需要测试人员具备良好的沟通和推动能力将自动化测试的需求作为一项重要的非功能性需求提出来。自动化不仅仅是技术实现更是团队协作和流程优化的体现。把环境问题解决在脚本之外是最高效的“自动化”。
Web自动化测试实战:从Selenium到Playwright的框架选型与工程化实践
1. 项目概述“Web自动化笔记”这个标题听起来像是一个技术人的私房菜谱。它不是那种宏大叙事的架构设计也不是高深莫测的算法解析而更像是一位常年泡在代码和浏览器里的工程师在无数个调试、测试、部署的日夜后沉淀下来的那份最接地气的实战手册。我干了十多年全栈和测试开发深知“自动化”这三个字背后远不止是写几行脚本那么简单。它关乎效率、关乎质量、关乎如何把我们从那些重复、枯燥且容易出错的“体力活”中解放出来去处理更有价值的问题。当你听到“Web自动化”脑海里可能立刻蹦出Selenium、Playwright这些框架的名字。没错它们是核心工具但真正的“笔记”价值在于如何把这些工具用对、用好、用出效率。这背后是一整套的工程化思维从环境搭建、脚本编写、用例设计到持续集成、结果分析和问题排查。它解决的核心问题是如何让Web应用的测试、部署、监控乃至日常操作变得可靠、可重复且无需人工干预。无论你是前端开发者想验证页面交互还是测试工程师需要保障回归测试或是运维同学追求一键部署和监控这份“笔记”都能给你提供一套清晰的行动路线图和避坑指南。2. 自动化测试框架深度选型与对比2.1 核心框架三剑客Selenium, Playwright, CypressWeb自动化的世界工具框架层出不穷但经过多年沉淀目前主流且经得起考验的“三剑客”是Selenium、Playwright和Cypress。选择哪一个不是非此即彼而是要看你的具体场景。Selenium老牌劲旅生态最庞大。它的核心是WebDriver协议这是一个W3C标准意味着几乎所有浏览器都原生或通过驱动支持它。Selenium WebDriver允许你用多种编程语言Java, Python, C#, JavaScript等来控制浏览器。它的优势在于稳定、兼容性极广从IE旧版到最新Chrome社区资源丰富任何稀奇古怪的问题几乎都能找到答案。但它的劣势也明显速度相对较慢需要额外管理浏览器驱动对于现代Web应用的一些复杂交互如下拉加载、文件上传、网络拦截需要更多代码来处理。Playwright微软出品后来居上。它可以说是为现代Web应用和开发流程量身定做的。Playwright最大的特点是支持Chromium、Firefox和WebKit三大浏览器引擎并且提供了非常强大的API例如自动等待、网络拦截、移动设备模拟、生成追踪视频等。它的执行速度通常比Selenium快脚本编写也更简洁。但它的“新”也意味着在某些非常老的企业内网环境或特定浏览器版本上可能不如Selenium兼容性好。Cypress前端开发者的宠儿。它的设计哲学完全不同运行在浏览器内部与你的应用共享同一个生命周期。这带来了颠覆性的体验超快的执行速度、实时重载、时间旅行调试可以回溯到测试的任何一个步骤。Cypress对现代JavaScript框架React, Vue, Angular的支持得天独厚。然而它的“局限”也源于此它主要专注于同源测试对于需要跨域或多标签页的复杂场景处理起来比较麻烦并且只支持JavaScript/TypeScript。实操心得对于大多数从零开始的团队我现在的首选是Playwright。它平衡了能力、速度和开发体验。如果团队技术栈以Node.js/JavaScript为主且应用是现代单页面应用SPACypress的开发者体验无与伦比。而如果你的项目需要覆盖极其古老的浏览器如IE 11或者团队语言栈是Java/.NET那么Selenium依然是可靠的选择。2.2 框架之外的生态拼图选定了核心框架这只是万里长征第一步。一个完整的自动化体系还需要其他拼图测试运行器Test Runner用来组织、运行和报告你的测试用例。常见的有JUnitJava、pytestPython、Jest/MochaJavaScript。它们提供了beforeEach、afterEach等钩子函数以及断言库。断言库Assertion Library用于验证测试结果是否符合预期。比如assertPython内置、ChaiJS、HamcrestJava。好的断言库能让错误信息更清晰。报告生成器Reporter没人想看控制台里密密麻麻的日志。我们需要美观的HTML报告比如Allure、ExtentReports它们能展示测试通过率、耗时、错误截图甚至操作步骤。页面对象模型Page Object Model, POM这不是一个工具而是一种设计模式。它将页面的元素定位和操作封装成类使测试脚本更清晰、更易维护减少重复代码。这是中大型自动化项目必须采用的模式。持续集成/持续部署CI/CD集成自动化脚本最终要融入开发流程。你需要将其接入Jenkins、GitLab CI、GitHub Actions等实现代码提交后自动触发测试。3. 环境搭建与核心脚本编写实战3.1 基于Playwright的快速入门让我们以目前势头最猛的Playwright为例快速搭建一个可运行的自动化环境。假设我们使用Python语言Playwright对Python的支持非常友好。首先安装Playwright。这里我强烈建议使用虚拟环境如venv来隔离项目依赖。# 创建并激活虚拟环境Linux/macOS python -m venv venv source venv/bin/activate # Windows python -m venv venv venv\Scripts\activate # 安装Playwright的Python库 pip install playwright # 安装Playwright所需的浏览器Chromium, Firefox, WebKit playwright install安装完成后创建一个简单的测试脚本比如叫test_baidu_search.pyimport asyncio from playwright.async_api import async_playwright async def main(): async with async_playwright() as p: # 启动浏览器headlessFalse表示显示浏览器界面方便调试 browser await p.chromium.launch(headlessFalse) # 创建新页面 page await browser.new_page() # 导航到百度 await page.goto(https://www.baidu.com) # 定位搜索框并输入关键词 await page.fill(input#kw, Playwright自动化) # 点击“百度一下”按钮 await page.click(input#su) # 等待页面加载Playwright会自动等待大部分导航和元素 await page.wait_for_load_state(networkidle) # 截图保存作为测试证据 await page.screenshot(pathsearch_result.png) # 获取页面标题并断言 title await page.title() assert Playwright自动化 in title print(f页面标题: {title}) # 关闭浏览器 await browser.close() # 运行异步函数 asyncio.run(main())运行这个脚本python test_baidu_search.py。你会看到浏览器自动打开完成搜索并截图。这就是一个最基础的Web自动化操作。3.2 元素定位自动化脚本的基石自动化脚本的核心是“找到元素操作元素”。元素定位的准确性和稳定性直接决定了脚本的可靠性。Playwright提供了多种强大的定位器Locators。最佳实践使用get_by_*系列定位器。这是Playwright推荐的方式可读性更好且能自动等待元素出现。# 通过文本内容定位 await page.get_by_text(登录).click() # 通过角色ARIA定位可访问性友好 await page.get_by_role(button, name提交).click() # 通过占位符文本定位 await page.get_by_placeholder(请输入用户名).fill(admin) # 通过Label文本定位关联的表单元素 await page.get_by_label(密码).fill(123456) # 通过Title属性定位 await page.get_by_title(提示信息).hover()传统但依然有效的CSS和XPath选择器# CSS选择器 await page.locator(#submit-btn).click() await page.locator(.list-item:has-text(特定项)).click() # XPath选择器在CSS无法满足复杂逻辑时使用 await page.locator(//button[contains(class, primary) and text()保存]).click()注意事项尽量避免使用绝对XPath如/html/body/div[3]/div[2]/button因为它极度脆弱页面结构稍有变动就会失效。优先使用ID、相对XPath或CSS结合文本的定位方式。3.3 等待策略解决“元素未找到”的银弹90%的自动化脚本失败源于“等待”。页面加载、元素渲染、AJAX请求都需要时间。Playwright的自动等待这是它的一大亮点。像click(),fill()这样的操作Playwright在执行前会自动等待元素满足可操作状态可见、可交互、稳定。这省去了大量显式等待的代码。显式等待Explicit Waits当需要等待特定条件时使用。# 等待元素出现 await page.locator(.success-message).wait_for() # 等待元素消失 await page.locator(.loading-spinner).wait_for(statehidden) # 等待特定文本出现 await page.get_by_text(操作成功).wait_for() # 等待网络请求完成 await page.wait_for_response(**/api/user/profile) # 等待导航完成 await page.wait_for_url(**/dashboard)超时设置全局设置或单独设置。# 全局设置超时毫秒 page.set_default_timeout(60000) # 单个操作设置超时 await page.click(button, timeout10000)4. 构建健壮且可维护的测试套件4.1 页面对象模型POM实战直接在上面的脚本里写定位和操作对于小脚本没问题但项目一旦变大维护将是灾难。POM模式将每个页面封装成一个类。创建一个pages/login_page.pyfrom playwright.async_api import Page class LoginPage: def __init__(self, page: Page): self.page page self.username_input page.get_by_placeholder(用户名/邮箱) self.password_input page.get_by_placeholder(密码) self.login_button page.get_by_role(button, name登录) self.error_message page.locator(.alert-error) async def navigate(self): await self.page.goto(https://example.com/login) async def login(self, username: str, password: str): await self.username_input.fill(username) await self.password_input.fill(password) await self.login_button.click() async def get_error_text(self) - str: await self.error_message.wait_for() return await self.error_message.text_content()在测试脚本中使用import pytest from pages.login_page import LoginPage pytest.mark.asyncio async def test_login_success(page): login_page LoginPage(page) await login_page.navigate() await login_page.login(valid_user, valid_pass) # 断言登录后跳转到了首页 await page.wait_for_url(**/home) assert await page.title() 首页 pytest.mark.asyncio async def test_login_failure(page): login_page LoginPage(page) await login_page.navigate() await login_page.login(wrong_user, wrong_pass) error_text await login_page.get_error_text() assert 用户名或密码错误 in error_text这样当登录页面的HTML结构发生变化时你只需要修改LoginPage这一个类所有用到登录的测试用例都不需要改动。4.2 测试数据管理与参数化测试数据不应该硬编码在脚本里。常用的方法有外部文件JSON, YAML, CSV。pytest.mark.parametrize装饰器如果使用pytest。创建一个test_data/login_data.json{ valid_credentials: { username: test_user, password: Test123, expected_url: **/dashboard }, invalid_username: { username: wrong, password: Test123, expected_error: 用户不存在 }, invalid_password: { username: test_user, password: wrong, expected_error: 密码错误 } }在测试中读取并使用import json import pytest with open(test_data/login_data.json, r, encodingutf-8) as f: LOGIN_TEST_DATA json.load(f) pytest.mark.asyncio pytest.mark.parametrize(case_name, test_data, LOGIN_TEST_DATA.items()) async def test_login_parametrized(page, case_name, test_data): login_page LoginPage(page) await login_page.navigate() await login_page.login(test_data[username], test_data[password]) if expected_url in test_data: await page.wait_for_url(test_data[expected_url]) assert True elif expected_error in test_data: error_text await login_page.get_error_text() assert test_data[expected_error] in error_text4.3 钩子Hooks与夹具Fixtures的使用利用测试框架的钩子和夹具来处理测试的公共前置和后置操作如初始化浏览器、登录状态、清理数据。使用pytest和playwright的官方插件pytest-playwright# conftest.py import pytest from playwright.async_api import async_playwright pytest.fixture(scopesession) async def browser(): async with async_playwright() as p: # 启动一个浏览器实例供所有测试用例共享session级别 browser await p.chromium.launch(headlessTrue) # CI环境通常用无头模式 yield browser await browser.close() pytest.fixture async def page(browser): # 每个测试用例一个独立的页面上下文隔离测试 context await browser.new_context() page await context.new_page() yield page await context.close() pytest.fixture async def logged_in_page(page): 提供一个已登录状态的页面夹具 login_page LoginPage(page) await login_page.navigate() await login_page.login(predefined_user, predefined_pass) # 确保登录成功 await page.wait_for_url(**/dashboard) return page在测试用例中直接使用这些夹具pytest.mark.asyncio async def test_access_profile(logged_in_page): # logged_in_page 已经是一个登录后的页面对象 await logged_in_page.click(text个人中心) # ... 后续断言5. 集成CI/CD与生成测试报告5.1 接入GitHub Actions自动化测试只有融入开发流程才能发挥最大价值。以下是一个简单的GitHub Actions工作流配置在每次推送代码时运行Playwright测试。在项目根目录创建.github/workflows/playwright.ymlname: Playwright Tests on: [push, pull_request] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 - uses: actions/setup-pythonv4 with: python-version: 3.10 - name: Install dependencies run: | pip install -r requirements.txt pip install pytest pytest-asyncio pytest-playwright - name: Install Playwright Browsers run: playwright install --with-deps chromium - name: Run your tests run: pytest tests/ --htmlreport.html --self-contained-html env: # 如果有需要可以在这里设置环境变量如测试环境URL BASE_URL: ${{ secrets.TEST_ENV_URL }} - name: Upload test report uses: actions/upload-artifactv3 if: always() # 即使测试失败也上传报告 with: name: playwright-report path: report.html retention-days: 30这个工作流做了几件事1) 检出代码2) 安装Python和依赖3) 安装Playwright和浏览器4) 运行pytest并生成一个独立的HTML报告5) 将报告上传为工件供后续下载查看。5.2 生成丰富的测试报告使用pytest-html插件可以生成基础报告。但对于更专业的需求Allure是行业标准。安装Allurepip install allure-pytest # 还需要安装Allure命令行工具可以从官网下载或通过包管理器运行测试并生成Allure结果数据pytest tests/ --alluredir./allure-results在本地生成并查看报告allure serve ./allure-results在CI中可以将allure-results目录上传为工件然后使用Allure的CI工具生成在线报告。Allure报告提供了清晰的用例分类、步骤详情、截图、错误日志甚至支持添加测试描述和链接是团队协作和问题分析的利器。6. 常见问题排查与高级技巧实录6.1 典型问题与解决方案速查表问题现象可能原因解决方案Element not found/TimeoutError1. 元素定位器写错或已失效。2. 页面加载/元素渲染太慢。3. 元素在iframe或shadow DOM内。4. 页面发生了非预期跳转。1. 使用浏览器开发者工具重新检查元素优先使用get_by_*定位器。2. 增加显式等待wait_for_selector,wait_for_function。3. 使用page.frame()切换到iframe或用element_handle.query_selector()穿透shadow DOM。4. 在操作前用page.wait_for_url()确认页面状态。脚本在本地运行成功在CI上失败1. CI环境是无头headless模式可能与有头模式行为有细微差别。2. CI环境资源CPU/内存不足导致响应慢。3. 网络或测试环境在CI上不可达。4. 浏览器版本不一致。1. 本地也使用headlessTrue模式运行一遍复现问题。2. 在CI配置中增加超时时间或优化脚本减少资源占用。3. 检查CI的网络安全组、环境变量配置是否正确。4. 在CI中明确指定安装的浏览器版本与本地保持一致。文件上传失败文件选择框是input typefile但Playwright无法直接点击。使用set_input_files()方法绝对不要尝试去模拟点击文件选择对话框。await page.locator(input[typefile]).set_input_files(/path/to/file.png)下拉选择Select操作不生效使用了自定义样式化的下拉组件不是原生select。1. 先点击触发下拉框展开。2. 再点击下拉列表中的选项。例如await page.click(.custom-select-trigger); await page.click(.dropdown-item:has-text(选项A))新窗口/标签页处理点击链接后打开了新窗口脚本还在原页面操作。使用上下文context来监听新页面async with page.context.expect_page() as new_page_info: await page.click(a[target_blank]) new_page await new_page_info.value6.2 网络请求拦截与模拟Mocking这是Playwright和类似框架的高级功能能极大提升测试速度和稳定性。你可以拦截并修改请求或响应。# 拦截请求修改请求头或阻止请求 await page.route(**/api/ads/*, lambda route: route.abort()) # 拦截并阻止广告请求 # 拦截请求并返回模拟数据 async def handle_route(route): # 伪造一个成功的用户信息响应 await route.fulfill( status200, content_typeapplication/json, bodyjson.dumps({username: mocked_user, age: 30}) ) await page.route(**/api/user/profile, handle_route) # 拦截响应并修改 await page.route(**/api/data, lambda route: route.continue_()) # 先继续请求 # 或者直接修改响应 async def modify_response(route, response): original_body await response.text() modified_body original_body.replace(status: pending, status: approved) await route.fulfill(responseresponse, bodymodified_body) await page.route(**/api/data, modify_response)6.3 执行追踪与视频录制对于调试那些“时好时坏”的诡异问题视频录制和追踪文件是无价之宝。# 在启动浏览器时启用追踪和视频录制 context await browser.new_context( record_video_dirvideos/, # 视频保存目录 record_video_size{width: 1920, height: 1080} ) # 或者全局启用追踪 await context.tracing.start(screenshotsTrue, snapshotsTrue, sourcesTrue) page await context.new_page() # ... 执行你的测试步骤 ... # 测试结束后保存追踪文件 await context.tracing.stop(pathtrace.zip) # 关闭上下文会自动保存视频 await context.close()当测试失败时你可以打开trace.zip文件使用Playwright的命令行工具playwright show-trace trace.zip它会以一个可视化时间轴的形式重现测试的每一步操作、网络请求、控制台日志就像时光机一样。6.4 处理动态内容与验证码这是自动化测试的“圣杯”难题。完全通用的解决方案不存在但有一些策略环境隔离在测试环境中完全禁用验证码或使用一个万能验证码如“0000”。这是最推荐、最可靠的方式。第三方服务使用付费的验证码识别服务精度有限有成本且违反某些网站条款。Cookie/Token复用首次手动登录获取有效的会话Cookie或Token然后在自动化脚本中注入跳过登录环节。Playwright可以很方便地导入导出Cookie。# 保存Cookie cookies await context.cookies() with open(state.json, w) as f: json.dump(cookies, f) # 加载Cookie with open(state.json, r) as f: cookies json.load(f) await context.add_cookies(cookies)OCR谨慎使用对于简单的图形验证码可以截图后使用Tesseract等OCR库识别但成功率低维护成本高不推荐用于核心流程。我个人在实际项目中的体会是与其在破解验证码上花费巨大精力不如推动项目团队为测试环境提供“后门”或专用测试账号。这需要测试人员具备良好的沟通和推动能力将自动化测试的需求作为一项重要的非功能性需求提出来。自动化不仅仅是技术实现更是团队协作和流程优化的体现。把环境问题解决在脚本之外是最高效的“自动化”。