
1. 项目概述为什么是Playwright如果你正在寻找一个能真正“一统江湖”的Web自动化测试工具那么Playwright绝对是你绕不开的名字。我接触过Selenium、Puppeteer也折腾过Cypress但最终让我在团队中力推并大规模应用的还是Playwright。它不是一个简单的工具升级而是一次测试理念的革新。简单来说Playwright是一个由微软开源的Node.js库同时提供了Python、Java和.NET的绑定它允许你用一套代码同时驱动Chromium、Firefox和WebKitSafari的引擎三大浏览器进行自动化操作。这意味着你写一次测试脚本就能覆盖市面上几乎所有主流浏览器内核这对于追求测试覆盖率和效率的团队来说简直是降维打击。为什么说它是“终极实战指南”级别的方案因为在实际项目中我们遇到的痛点远不止“点击按钮”和“输入文本”。页面加载慢导致元素定位失败、动态内容异步加载、iframe嵌套、文件上传下载、网络请求拦截与模拟、甚至是移动端设备模拟……这些才是真正的拦路虎。Playwright在设计之初就考虑了这些生产环境中的复杂场景提供了开箱即用的强大API。比如它的自动等待机制能智能等待元素可操作大大减少了编写显式等待time.sleep的繁琐和脆弱性再比如它原生支持录制生成代码对新手极其友好。本指南的目的就是带你从零开始搭建一套基于Playwright Python的、可用于真实项目的、健壮的跨浏览器自动化测试解决方案避开我踩过的所有坑直达最佳实践。2. 环境搭建与核心配置详解工欲善其事必先利其器。一个稳定、可复现的测试环境是自动化成功的基石。这里我会详细拆解每一步并解释其背后的原因。2.1 Python环境与Playwright安装首先确保你有一个干净的Python环境。我强烈建议使用venv或conda创建独立的虚拟环境以避免包依赖冲突。这是血泪教训曾经因为全局环境混乱导致不同项目间的Playwright版本和浏览器版本打架排查了整整一天。# 创建并激活虚拟环境 (以venv为例) python -m venv playwright-env # Windows playwright-env\Scripts\activate # macOS/Linux source playwright-env/bin/activate接下来安装Playwright的Python包。这里有个关键选择是只安装核心库还是连同浏览器一起安装对于团队协作或CI/CD环境我推荐分步安装。# 安装playwright核心库 pip install playwright # 安装浏览器Chromium, Firefox, WebKit。这一步会下载浏览器二进制文件体积较大。 playwright install注意playwright install命令默认会安装所有支持浏览器chromium, firefox, webkit的稳定版本。如果你只需要特定浏览器可以使用playwright install chromium。在国内网络环境下这一步可能会很慢或失败。解决方案是配置镜像源设置环境变量PLAYWRIGHT_DOWNLOAD_HOSThttps://npmmirror.com/mirrors/playwright后再执行安装命令速度会快很多。这是解决“安装浏览器慢”这个热搜问题的关键技巧。2.2 项目结构与配置管理一个混乱的脚本目录是维护的噩梦。从第一天起就要建立清晰的项目结构。这是我的推荐结构your-automation-project/ ├── config/ # 配置文件 │ ├── __init__.py │ ├── settings.py # 全局配置超时时间、基础URL等 │ └── browsers.py # 浏览器启动配置 ├── pages/ # 页面对象模型Page Object Model, POM │ ├── __init__.py │ ├── login_page.py │ └── home_page.py ├── tests/ # 测试用例 │ ├── __init__.py │ ├── conftest.py # Pytest fixtures 配置核心 │ ├── test_login.py │ └── test_search.py ├── fixtures/ # 测试数据 │ └── test_data.json ├── reports/ # 测试报告自动生成 ├── utils/ # 工具函数如截图、日志 │ └── helper.py ├── requirements.txt # 项目依赖 └── pytest.ini # Pytest 配置文件为什么采用POM页面对象模型它将页面的元素定位和操作封装成类测试脚本只调用业务方法。当页面UI变动时你只需要修改对应的Page类而不是散落在各处的测试脚本极大提升了可维护性。conftest.py是Pytest的精华我们可以在这里定义全局的browser和pagefixture让所有测试用例共享一套浏览器上下文配置。2.3 核心配置代码解析让我们深入config/settings.py和conftest.py看看如何配置一个健壮的测试环境。config/settings.pyimport os from pathlib import Path BASE_URL https://your-test-site.com DEFAULT_TIMEOUT 30000 # Playwright默认超时是30秒这里显式定义便于调整 HEADLESS os.getenv(HEADLESS, True).lower() true # 环境变量控制是否无头模式 SLOW_MO int(os.getenv(SLOW_MO, 100)) # 操作延迟毫秒调试时非常有用 SCREENSHOT_ON_FAILURE True REPORT_PATH Path(__file__).parent.parent / reportstests/conftest.py(核心Fixture定义)import pytest import allure from playwright.sync_api import Browser, BrowserContext, Page from config.settings import HEADLESS, SLOW_MO, SCREENSHOT_ON_FAILURE, REPORT_PATH pytest.fixture(scopesession) def browser(browser_type_launch_args): 启动一个浏览器实例整个测试会话只启动一次 # browser_type_launch_args 是Playwright pytest插件提供的fixture可获取浏览器类型 browser browser_type_launch_args.launch(headlessHEADLESS, slow_moSLOW_MO) yield browser browser.close() pytest.fixture def context(browser: Browser, browser_context_args): 为每个测试用例创建一个独立的浏览器上下文类似隐身会话 # browser_context_args 可以传递上下文参数如视口大小、权限等 context browser.new_context(**browser_context_args) yield context context.close() pytest.fixture def page(context: BrowserContext) - Page: 为每个测试用例创建一个独立的页面Tab page context.new_page() yield page if SCREENSHOT_ON_FAILURE: # 如果测试失败自动截图并附加到Allure报告 if hasattr(pytest, “test_failed”) and pytest.test_failed: screenshot_path REPORT_PATH / fscreenshot_{pytest.current_test_name}.png page.screenshot(pathscreenshot_path) allure.attach.file(screenshot_path, name失败截图, attachment_typeallure.attachment_type.PNG) page.close()这里的关键点在于browser,context,page三个fixture的作用域scopebrowser(session): 启动浏览器进程成本高整个测试套件共用。context(function): 每个测试用例一个独立的上下文实现了测试间的完全隔离避免cookie、localStorage相互污染。page(function): 每个测试用例一个标签页是最常用的操作对象。使用browser_context_argsfixture需要安装pytest-playwright插件可以方便地在pytest.ini或命令行中配置上下文参数如模拟移动设备# pytest.ini [pytest] addopts --browser chromium --browser firefox --browser webkit --headed # 覆盖HEADLESS设置有头模式运行 --slowmo 100 --device “iPhone 12”3. 核心API与最佳实践模式掌握了环境配置我们来深入Playwright Python API的核心。很多人学了API但写出的脚本依然脆弱问题往往出在没有遵循正确的模式。3.1 元素定位与等待告别time.sleepPlaywright最令人称道的特性之一是其强大的自动等待。它会在执行操作如click,fill前自动等待元素满足可操作条件如可见、可点击、稳定。这意味着在绝大多数情况下你不需要手动写page.wait_for_selector。最佳定位策略按优先级排序Role-based 定位 (get_by_role)这是首选。通过元素的ARIA角色如button、textbox和可访问名name来定位。这最接近用户感知方式且对UI变化最不敏感。# 优于 page.locator(“button.submit”) page.get_by_role(“button”, name“提交”).click()Text-based 定位 (get_by_text,get_by_label)根据可见文本或标签文本来定位。非常直观。page.get_by_text(“登录”).click() page.get_by_label(“用户名”).fill(“admin”)Test ID 定位 (get_by_test_id)与开发约定为关键测试元素添加专用的># 前端元素button># CSS page.locator(“.primary-btn:has-text(‘确认’)”).click() # XPath (不得已时使用) page.locator(“//button[contains(class, ‘submit’)]”).click()显式等待的使用场景虽然自动等待很强大但在某些特定场景仍需显式等待等待网络请求完成page.wait_for_response(url_pattern)等待导航完成page.wait_for_url(url)等待元素达到特定状态page.locator(“#progress”).wait_for(state“hidden”)等待自定义条件page.wait_for_function(“window.appState ‘ready’”)实操心得永远不要使用time.sleep这是自动化测试的“癌症”。它会让测试变得极慢且不可靠因为网络或机器性能差异。利用Playwright的内置等待让你的测试既快速又健壮。3.2 页面对象模型POM的现代化实现传统的POM可能只是简单的元素定位器集合。我们可以利用Python的property装饰器和Playwright的Locator对象实现更优雅、更强大的POM。pages/login_page.pyfrom playwright.sync_api import Page from config.settings import BASE_URL class LoginPage: def __init__(self, page: Page): self.page page self._endpoint “/login” property def url(self): return BASE_URL self._endpoint # 使用property将定位器定义为属性延迟计算更清晰 property def username_input(self): return self.page.get_by_label(“用户名或邮箱”) property def password_input(self): return self.page.get_by_label(“密码”) property def submit_button(self): return self.page.get_by_role(“button”, name“登录”) property def error_message(self): return self.page.locator(“.alert-error”) # 页面操作方法 def navigate(self): self.page.goto(self.url) return self def login(self, username: str, password: str): 登录核心业务流 self.navigate() self.username_input.fill(username) self.password_input.fill(password) self.submit_button.click() # 可以在这里加入等待登录成功的逻辑比如等待跳转或某个元素出现 self.page.wait_for_url(**contains(“/dashboard”)) return DashboardPage(self.page) # 返回下一个页面对象实现链式调用 def get_error_text(self) - str: return self.error_message.text_content() if self.error_message.is_visible() else “”在测试用例中使用这个Page对象代码会非常清晰def test_successful_login(page): login_page LoginPage(page) dashboard_page login_page.login(“valid_user”, “valid_pass”) # 断言登录成功例如检查dashboard页面是否有用户菜单 assert dashboard_page.user_menu.is_visible()这种模式的优点在于将元素定位、页面操作和业务逻辑完美分离。测试用例读起来就像自然语言描述的验收标准。3.3 处理复杂交互文件、iframe、弹窗与API拦截文件上传与下载Playwright处理文件上传极其简单无需像Selenium那样找input元素并send_keys。# 文件上传 - 直接设置输入文件 page.locator(“input[type‘file’]”).set_input_files(‘my-file.pdf’) # 上传多个文件 page.locator(“input[type‘file’]”).set_input_files([‘file1.pdf’, ‘file2.jpg’]) # 等待文件上传完成监听特定请求 with page.expect_response(lambda response: “/upload” in response.url): page.click(“#upload-button”) # 文件下载 - 监听下载事件 with page.expect_download() as download_info: page.click(“a#download-link”) download download_info.value # 指定下载路径 path download.save_as(“/path/to/save.pdf”)处理iframe定位iframe内的元素需要先获取Frame对象。# 通过名称或URL定位iframe frame page.frame(name“widget-frame”) # 或 page.frame(urlre.compile(r”.*widget.*”)) # 然后在frame对象上操作 frame.fill(“#input-inside-iframe”, “text”) # 更简洁的方式使用frame_locator page.frame_locator(“iframe[name‘widget-frame’]”).locator(“button”).click()处理弹窗对话框Playwright可以监听并接受或拒绝原生的alert,confirm,prompt。# 监听并接受confirm对话框 page.on(“dialog”, lambda dialog: dialog.accept()) page.click(“button-that-triggers-confirm”) # 更精确的控制使用 expect_event page.once(“dialog”, lambda dialog: dialog.accept(“输入的文字”)) page.click(“button-that-triggers-prompt”)网络请求拦截与模拟Mock这是Playwright的王牌功能之一用于测试边缘场景或屏蔽不稳定第三方依赖。# 1. 拦截并修改请求 page.route(“**/api/user”, lambda route: route.fulfill( status200, content_type“application/json”, bodyjson.dumps({“name”: “Mock User”, “id”: 123}) )) # 之后所有匹配的API请求都会返回这个模拟数据 # 2. 拦截并继续请求如修改请求头 page.route(“**/*”, lambda route: route.continue_(headers{**route.request.headers, “x-test”: “true”})) # 3. 记录或断言网络请求 def log_request(request): print(f“{request.method} {request.url}”) page.on(“request”, log_request) # 等待特定请求并获取其响应 response page.wait_for_response(“**/api/data”) data response.json() assert data[“status”] “success”4. 跨浏览器测试与多环境执行策略“跨浏览器”不是简单地在不同浏览器里跑同一套脚本。不同浏览器有细微的渲染、行为和性能差异需要有针对性的策略。4.1 配置与启动多浏览器在conftest.py中我们可以通过参数化pytest.mark.parametrize或Pytest插件来运行多浏览器测试。方法一使用pytest-playwright插件推荐安装插件pip install pytest-playwright。它提供了browser_type_launch_args和browser_context_args等fixture以及命令行参数。# 在Chromium和Firefox上运行所有测试 pytest --browser chromium --browser firefox # 指定有头模式运行便于调试 pytest --browser chromium --headed方法二自定义参数化fixture如果你需要更精细的控制可以在conftest.py中定义import pytest from playwright.sync_api import BrowserType pytest.fixture(params[“chromium”, “firefox”, “webkit”]) def browser_type(request, playwright): yield getattr(playwright, request.param) pytest.fixture def browser(browser_type: BrowserType): browser browser_type.launch(headlessFalse) yield browser browser.close()这样每个测试用例都会自动在三个浏览器上各运行一次。4.2 处理浏览器特异性问题尽管Playwright尽力统一了API但差异依然存在。你需要一个策略来应对。条件执行与跳过对于已知的、仅在特定浏览器上存在的问题或尚未支持的功能使用条件跳过。import pytest from config.settings import BROWSER_NAME # 从环境变量获取当前浏览器 pytest.mark.skipif(BROWSER_NAME “webkit”, reason“WebKit下文件下载API行为不同暂不测试”) def test_file_download(page): # ... 测试代码 # 或者在测试内部进行条件判断 def test_geolocation(page): if page.context.browser.browser_type.name ‘firefox’: pytest.skip(“Firefox在此版本中地理位置权限需要额外配置”) # ... 其他浏览器的测试代码浏览器特定的定位器或操作极少数情况下你可能需要为不同浏览器准备不同的定位器。def get_submit_button(page): if page.context.browser.browser_type.name ‘webkit’: return page.locator(“css.safari-special-btn”) else: return page.get_by_role(“button”, name“提交”)视觉回归测试使用page.screenshot()进行截图并配合像pixelmatch这样的库进行图片对比捕捉不同浏览器间的渲染差异。这通常是UI测试的最后一环。4.3 集成CI/CD与并行执行真正的实战方案必须能在CI/CD流水线中快速、稳定地运行。核心是并行化。使用Pytest-xdist进行并行测试pip install pytest-xdist # 启动4个worker并行执行测试 pytest -n auto # ‘auto’会根据CPU核心数自动设置worker数 pytest -n 4 --browser chromium --browser firefox这会让测试套件执行时间成倍缩短。注意并行测试时要确保测试用例之间是独立的不能有共享状态依赖。我们之前用context和pagefixture为每个测试创建独立环境正是为此做准备。CI配置示例GitHub Actionsname: Playwright Tests on: [push] jobs: test: timeout-minutes: 60 runs-on: ubuntu-latest strategy: fail-fast: false # 一个浏览器失败不影响其他浏览器继续测试 matrix: browser: [chromium, firefox, webkit] steps: - uses: actions/checkoutv3 - uses: actions/setup-pythonv4 with: python-version: ‘3.10’ - name: Cache Playwright browsers uses: actions/cachev3 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles(‘**/package-lock.json’, ‘**/poetry.lock’) }} restore-keys: | ${{ runner.os }}-playwright- - name: Install dependencies run: | pip install -r requirements.txt playwright install --with-deps ${{ matrix.browser }} # 只安装当前测试需要的浏览器 - name: Run your tests run: | pytest tests/ --browser ${{ matrix.browser }} --headless --junitxmlresults-${{ matrix.browser }}.xml - name: Upload test results if: always() uses: actions/upload-artifactv3 with: name: test-results-${{ matrix.browser }} path: results-${{ matrix.browser }}.xml这个配置实现了1) 缓存浏览器二进制文件以加速构建2) 矩阵策略在多浏览器上并行运行测试3) 生成JUnit格式报告用于CI集成。5. 高级技巧、调试与问题排查即使遵循了最佳实践在复杂的Web应用中你依然会遇到各种诡异的问题。这一章分享我积累的“救命”技巧。5.1 调试技巧大全有头模式与慢动作这是最基本的调试手段。在命令行添加--headed和--slowmo 10001000毫秒让你可以清晰看到每一步操作。Playwright Inspector这是官方神器。通过设置环境变量PWDEBUG1运行脚本会自动打开一个调试器你可以单步执行、查看页面快照、检查定位器甚至能直接修改脚本并重新执行。PWDEBUG1 pytest test_login.py -k “test_failed_login” --headed录制代码对于不熟悉的页面先用playwright codegen录制操作生成代码骨架再在其基础上修改和完善。这是快速上手的神器。playwright codegen https://your-test-site.com丰富的截图与录屏# 截图 page.screenshot(path“fullpage.png”, full_pageTrue) # 截取整个可滚动页面 page.locator(“#widget”).screenshot(path“widget.png”) # 截取特定元素 # 录屏需要以context方式启动 context browser.new_context(record_video_dir“videos/”) page context.new_page() # ... 测试操作 ... # 测试结束后视频会自动保存控制台日志与网络监听# 监听控制台日志 page.on(“console”, lambda msg: print(f“CONSOLE: {msg.type}: {msg.text}”)) # 监听页面错误 page.on(“pageerror”, lambda error: print(f“PAGE ERROR: {error}”)) # 监听所有网络请求和响应调试时非常有用但生产环境慎用日志量巨大 # page.on(“request”, lambda req: print(f“ {req.method} {req.url}”)) # page.on(“response”, lambda res: print(f“ {res.status} {res.url}”))5.2 常见问题排查清单当你遇到元素找不到、点击没反应、测试不稳定等问题时按这个清单排查问题现象可能原因排查步骤与解决方案Locator.click()超时1. 元素被遮挡弹窗、遮罩层2. 元素不可见display: none,visibility: hidden3. 元素不可交互disabled属性4. 坐标点错误动态布局1. 使用page.pause()或Inspector检查元素状态。2. 尝试使用forceTrue参数强制点击模拟用户绕过UI检查慎用。3. 改用element_handle.click()并指定坐标。Locator.fill()不输入文本1. 输入框不是真正的input或textarea如div模拟2. 有JS事件拦截输入1. 先click()聚焦再fill()。2. 使用page.type(selector, text, delay100)模拟真人输入。3. 使用page.evaluate()直接设置元素的value属性。页面跳转后操作失效1. 页面未加载完成就执行操作2. 跳转到了新窗口或新Tab1. 在跳转操作后使用page.wait_for_load_state(‘networkidle’)。2. 使用page.wait_for_event(‘popup’)或context.pages来获取新页面对象。测试在CI上失败本地却成功1. 环境差异浏览器版本、屏幕分辨率2. 网络延迟或超时设置过短3. 资源加载失败CDN、第三方库1. 在CI日志中启用详细日志和截图。2. 增加全局超时DEFAULT_TIMEOUT。3. 使用page.route拦截并Mock不稳定的第三方资源。文件上传失败1. 上传组件是自定义的非原生input type“file”2. 文件选择对话框是操作系统原生窗口1. 使用page.locator(‘.upload-area’).set_input_files()直接设置文件路径Playwright可以绕过文件选择框。2. 如果组件通过拖拽上传使用page.dispatch_event(selector, ‘drop’, { dataTransfer: { files: [file] } })。iframe内元素无法定位1. iframe未加载完成2. iframe有跨域限制3. 定位器作用域错误1. 使用page.frame(…).wait_for_load_state()。2. 检查浏览器控制台是否有跨域错误可能需要启动参数--disable-web-security仅测试环境。3. 确保在Frame对象或frame_locator上调用定位器而不是在父page上。5.3 性能优化与稳定性提升复用Browser Context虽然每个测试用例用独立的Context利于隔离但创建Context有开销。对于一组紧密相关、需要共享登录态的测试可以共享一个Context但要在每个测试后清理Cookies和Storage (context.clear_cookies())。合理配置超时全局超时DEFAULT_TIMEOUT设得太短会导致不必要的失败太长会拖慢测试套件。我的经验是根据应用响应速度设置在20-60秒。对于特定的慢操作可以单独设置更长的超时locator.click(timeout60000)。选择性安装浏览器在CI环境中如果只测试Chromium就只安装Chromium可以显著减少流水线时间和存储空间。使用请求拦截减少不必要流量在测试开始时拦截并阻断所有非必要的资源请求如图片、字体、分析脚本可以大幅提升测试执行速度。def block_aggressively(route): if route.request.resource_type in [“image”, “stylesheet”, “font”, “media”]: route.abort() else: route.continue_() page.route(“**/*”, block_aggressively)定期清理与维护浏览器缓存、用户数据目录会不断增长。定期在CI脚本中添加清理步骤并考虑使用Docker容器提供纯净的测试环境。6. 测试报告、日志与持续集成自动化测试如果不产生清晰的结果报告其价值就大打折扣。我们需要让失败的原因一目了然。6.1 生成丰富的测试报告Allure报告这是目前最强大、最美观的测试报告框架之一。安装pip install allure-pytest在测试中添加注解和附件import allure import pytest allure.title(“验证用户登录功能”) allure.feature(“用户认证”) def test_login(page): with allure.step(“导航到登录页面”): page.goto(“/login”) with allure.step(“输入用户名和密码”): page.fill(“#username”, “test”) page.fill(“#password”, “pass”) with allure.step(“点击登录按钮”): page.click(“button[type‘submit’]”) with allure.step(“验证登录成功”): assert page.inner_text(“.welcome”) “Welcome, test!” # 失败时自动附加截图已在conftest.py的page fixture中实现运行测试并生成报告pytest tests/ --alluredir./allure-results allure serve ./allure-results # 本地查看 allure generate ./allure-results -o ./allure-report --clean # 生成静态HTML报告HTML报告使用pytest-html可以快速生成简洁的HTML报告。pip install pytest-html pytest tests/ --htmlreport.html --self-contained-html6.2 结构化日志记录使用Python标准的logging模块为测试框架添加日志。# utils/logger.py import logging import sys def setup_logger(name): logger logging.getLogger(name) logger.setLevel(logging.DEBUG) # 控制台处理器 ch logging.StreamHandler(sys.stdout) ch.setLevel(logging.INFO) # 文件处理器 fh logging.FileHandler(“automation.log”) fh.setLevel(logging.DEBUG) # 格式 formatter logging.Formatter(‘%(asctime)s - %(name)s - %(levelname)s - %(message)s’) ch.setFormatter(formatter) fh.setFormatter(formatter) logger.addHandler(ch) logger.addHandler(fh) return logger # 在page object或测试用例中使用 logger setup_logger(__name__) logger.info(“开始执行登录测试”) try: page.click(“button”) except Exception as e: logger.error(f“点击按钮失败: {e}”, exc_infoTrue)6.3 集成到CI/CD流水线将上述所有部分组合起来形成一个完整的CI流水线。以GitHub Actions为例一个成熟的配置应该检出代码。缓存依赖和浏览器以加速构建。安装Python依赖和Playwright浏览器。并行运行多浏览器测试并生成JUnit XML格式的结果。无论测试成功与否都上传测试结果、日志、截图和录屏作为制品便于事后分析。如果测试失败向Slack、Teams或钉钉发送通知。# .github/workflows/playwright-ci.yml 更完整的示例 name: Playwright Cross-Browser CI on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: browser: [chromium, firefox] # webkit在CI上可能较慢按需添加 python-version: [“3.9”, “3.10”] steps: - name: Checkout uses: actions/checkoutv3 - name: Set up Python uses: actions/setup-pythonv4 with: python-version: ${{ matrix.python-version }} - name: Cache dependencies uses: actions/cachev3 with: path: | ~/.cache/pip ~/.cache/ms-playwright key: ${{ runner.os }}-py-${{ matrix.python-version }}-${{ matrix.browser }}-${{ hashFiles(‘**/requirements.txt’) }} - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt playwright install ${{ matrix.browser }} --with-deps - name: Run tests run: | pytest tests/ \ --browser${{ matrix.browser }} \ --headless \ --junitxmltest-results-${{ matrix.browser }}-py${{ matrix.python-version }}.xml \ --htmlreport-${{ matrix.browser }}-py${{ matrix.python-version }}.html \ --self-contained-html \ -v env: HEADLESS: true - name: Upload test results if: always() uses: actions/upload-artifactv3 with: name: test-results-py${{ matrix.python-version }}-${{ matrix.browser }} path: | test-results-*.xml report-*.html screenshots/ # 假设你的截图存于此目录 logs/ # 假设你的日志存于此目录这套方案从环境搭建、脚本编写、到执行调试、报告集成形成了一个完整的闭环。它不仅仅是API的使用手册更是经过真实项目锤炼后的工程化实践。记住好的自动化测试不是一蹴而就的而是随着项目迭代不断演进和维护的。从一个小而精的测试用例开始逐步扩展持续重构你会发现Playwright Python将成为你保障Web应用质量最得力的伙伴。