Java结合Playwright实现高效UI自动化测试:从环境搭建到CI/CD集成实战

📅 2026/7/8 17:50:17
Java结合Playwright实现高效UI自动化测试:从环境搭建到CI/CD集成实战
1. 项目概述为什么选择Java与Playwright的组合如果你是一名Java开发者正在为UI自动化测试的选型而头疼那么今天这篇实战分享或许能给你提供一个全新的、高效的解决方案。我最近在一个大型电商后台管理系统的测试项目中全面引入了Playwright for Java彻底告别了以往基于Selenium时遇到的浏览器驱动版本冲突、执行速度慢、稳定性差等老问题。Playwright这个由微软开源的现代化自动化测试框架以其强大的API、跨浏览器的一致性和卓越的执行性能正在迅速成为自动化测试领域的新宠。而它与Java的结合对于广大Java技术栈的团队来说意味着无需切换语言生态就能享受到最前沿的自动化测试能力。简单来说Java Playwright能帮你做什么它能让你用熟悉的Java代码像真实用户一样操作Chromium、Firefox和WebKitSafari浏览器完成页面导航、元素点击、表单填写、文件上传、截图断言等一系列复杂的UI交互验证。无论是回归测试、冒烟测试还是复杂业务流程的端到端E2E测试它都能胜任。更重要的是Playwright内置了自动等待、网络拦截、移动端模拟等高级特性能极大减少编写“等待”代码和维护测试脚本的心智负担。这篇文章我将从一个实战者的角度手把手带你完成从零环境搭建到编写第一个健壮测试用例的全过程并分享我踩过的坑和总结的最佳实践目标是让你看完就能在自己的项目里用起来。2. 环境准备与项目初始化避开第一个坑万事开头难环境搭建往往是劝退新手的第一个门槛。网上很多教程只告诉你用Maven但实际工作中你可能会遇到内网环境、特定JDK版本约束等复杂情况。因此我会介绍两种主流的搭建方式纯Jar包引入和Maven依赖管理并详细说明各自的适用场景和注意事项。2.1 核心依赖解析你需要哪几个Jar包很多新手会疑惑Playwright官网列出了好几个Jar包到底该引入哪个这里必须彻底搞清楚否则运行时各种ClassNotFoundException会让你怀疑人生。Playwright for Java的核心库实际上进行了模块化拆分主要包含以下四个部分playwright: 主库包含了Playwright、Browser、Page、Locator等核心API的接口和类。这是必须引入的。playwright-driver: 浏览器驱动通信层。Playwright并非直接调用浏览器而是通过一个独立的驱动进程进行通信。这个包封装了与驱动进程交互的协议和逻辑。这也是必须的缺少它会导致无法启动浏览器。playwright-shaded(可选但常被需要): 这是一个“打包”版本将一些额外的依赖如用于处理DevTools协议的库合并进来。在某些网络环境或特定JDK版本下如果你遇到了关于org.java-websocket或com.google.code.gson等类的NoClassDefFoundError引入这个包通常能解决问题。assertj-core(或其他断言库): Playwright本身不包含断言它鼓励你使用专业的断言库如AssertJ、Hamcrest或JUnit 5的Assertions。AssertJ的流式API与Playwright的链式调用风格非常搭配强烈推荐。实操心得我建议在项目初期尤其是学习阶段直接通过Maven引入com.microsoft.playwright:playwright这一个依赖即可。因为Maven会自动处理传递性依赖将playwright-driver等必要包一并下载。只有在无法使用Maven/Gradle的极端环境下才需要手动管理上述多个Jar包。2.2 方案一使用Maven构建推荐这是最省心、最标准的方式。确保你的机器已安装JDK 8或更高版本Playwright要求JDK 8以及Maven。步骤1创建Maven项目你可以使用IDE如IntelliJ IDEA或Eclipse直接创建或通过命令行mvn archetype:generate -DgroupIdcom.yourcompany -DartifactIdplaywright-demo -DarchetypeArtifactIdmaven-archetype-quickstart -DinteractiveModefalse步骤2在pom.xml中添加Playwright依赖打开项目根目录的pom.xml文件在dependencies节点内添加dependency groupIdcom.microsoft.playwright/groupId artifactIdplaywright/artifactId version1.40.0/version !-- 建议使用最新稳定版 -- /dependency同时添加测试和断言依赖这里以JUnit 5和AssertJ为例dependency groupIdorg.junit.jupiter/groupId artifactIdjunit-jupiter/artifactId version5.10.0/version scopetest/scope /dependency dependency groupIdorg.assertj/groupId artifactIdassertj-core/artifactId version3.24.2/version scopetest/scope /dependency步骤3执行Maven命令以下载依赖在项目根目录运行mvn clean compileMaven会自动从中央仓库下载Playwright及其所有依赖项。首次运行时会有一个关键步骤下载浏览器二进制文件。Playwright会下载Chromium、Firefox和WebKit的专用版本到本地缓存通常在用户主目录的.cache/ms-playwright下。这个过程需要联网且下载体积较大约几百MB请耐心等待。如果遇到网络问题可以尝试配置Maven镜像但浏览器二进制文件的下载源可能需要单独处理。2.3 方案二手动引入Jar包适用于受限环境有些公司的开发环境无法连接外网或者项目有严格的架构限制不允许使用Maven。这时就需要手动下载并管理Jar包。步骤1下载所需Jar包你需要从Maven中央仓库https://repo1.maven.org/maven2/com/microsoft/playwright/或可靠的内部镜像站手动下载对应版本的Jar文件。以1.40.0版本为例至少需要下载playwright-1.40.0.jarplaywright-driver-1.40.0.jar建议一并下载playwright-shaded-1.40.0.jar以备不时之需。同时下载断言库如assertj-core-3.24.2.jar。步骤2在IDE中创建项目并添加Jar依赖以IntelliJ IDEA为例创建新的Java项目。在项目根目录下新建一个文件夹如libs将下载的所有Jar包复制进去。右键点击项目 -Open Module Settings-Libraries--Java然后选择你的libs文件夹将所有Jar包添加为库。确保在Modules的Dependencies标签页中这些库已被勾选并导出。步骤3处理浏览器二进制文件这是手动引入模式下的最大挑战。Playwright不会自动下载浏览器。你需要从一台能联网的机器通过一个简单的Maven项目执行一次测试让Playwright完成浏览器下载。找到缓存目录如~/.cache/ms-playwrighton Linux/Mac,%USERPROFILE%\AppData\Local\ms-playwrighton Windows将整个目录打包。在目标机器上解压到相同的路径或者通过设置环境变量PLAYWRIGHT_BROWSERS_PATH来指定一个新的路径。避坑指南手动管理Jar包时最常见的错误是“playwright.driver.impl.PlaywrightDriverImpl类找不到”。这几乎百分之百是因为缺少playwright-driver.jar。请务必检查是否将所有必要的Jar包都添加到了项目的构建路径中。3. 第一个测试脚本从“Hello World”到理解核心API环境搞定后我们来编写第一个可运行的测试脚本。这个脚本将完成启动浏览器、打开网页、获取标题、截图并关闭浏览器。我会逐行解释其背后的原理和最佳实践。3.1 基础脚本编写与解析创建一个Java类例如FirstTest.java。import com.microsoft.playwright.*; import java.nio.file.Paths; public class FirstTest { public static void main(String[] args) { // 1. 创建Playwright实例 Playwright playwright Playwright.create(); // 2. 启动浏览器 Browser browser playwright.chromium().launch( new BrowserType.LaunchOptions().setHeadless(false) // 设置为true则无头运行 ); // 3. 创建浏览器上下文Context和页面Page BrowserContext context browser.newContext(); Page page context.newPage(); try { // 4. 导航到目标网址 page.navigate(https://www.baidu.com); // 5. 获取页面标题并打印 String title page.title(); System.out.println(页面标题是: title); // 6. 对页面进行截图 page.screenshot(new Page.ScreenshotOptions() .setPath(Paths.get(screenshot.png)) // 截图保存路径 .setFullPage(true) // 截取完整页面而非仅可视区域 ); // 7. 定位搜索框输入内容并搜索 Locator searchBox page.locator(input#kw); searchBox.fill(Playwright自动化测试); page.locator(input#su).click(); // 8. 等待导航完成Playwright大部分操作已自动等待 page.waitForURL(url - url.contains(search)); // 9. 对搜索结果页截图 page.screenshot(new Page.ScreenshotOptions() .setPath(Paths.get(search_result.png)) .setFullPage(true) ); } catch (Exception e) { e.printStackTrace(); } finally { // 10. 按顺序关闭资源Page - Context - Browser - Playwright if (page ! null) page.close(); if (context ! null) context.close(); if (browser ! null) browser.close(); if (playwright ! null) playwright.close(); } } }核心API深度解析Playwright.create(): 这是所有操作的起点它创建了一个Playwright驱动程序的连接实例。一个进程内通常只需要一个实例。BrowserType.launch(): 这里选择了Chromium内核浏览器。你可以通过playwright.firefox()或playwright.webkit()启动其他浏览器。setHeadless(false)让你能看到浏览器操作过程调试时非常有用在生产环境或CI/CD中应设为true以提升性能且不依赖图形界面。BrowserContext与Page: 这是Playwright中非常重要的两个概念。BrowserContext相当于一个独立的“隐身会话”。每个Context拥有独立的cookie、缓存、权限设置等。你可以利用它来实现测试间的隔离或者模拟多个用户同时登录。Page代表一个标签页。一个Context下可以有多个Page。我们的大部分交互都在Page对象上进行。page.locator(): 这是Playwright最强大的特性之一。它用于定位页面元素。与Selenium的findElement不同locator是惰性的它只是定义了如何查找元素并不立即执行。真正的查找操作如click()、fill()被触发时Playwright会自动重试直到元素可用或超时这极大地增强了脚本的稳定性。参数支持CSS选择器、XPath、文本内容等多种方式。自动等待: 注意在searchBox.fill()和click()之后我们没有写Thread.sleep()或显式的WebDriverWait。因为Playwright的核心操作如click,fill,navigate内部都内置了智能等待。它会等待元素可操作、导航完成等条件满足后才执行超时时间默认为30秒可配置。3.2 使用Try-With-Resources简化资源管理上面的代码使用了传统的try-catch-finally来确保资源关闭。Java 7提供了更优雅的try-with-resources语法因为Playwright、Browser、BrowserContext和Page都实现了AutoCloseable接口。上述代码可以简化为import com.microsoft.playwright.*; public class FirstTestWithTryResource { public static void main(String[] args) { // 使用try-with-resources无需手动close try (Playwright playwright Playwright.create(); Browser browser playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false)); BrowserContext context browser.newContext(); Page page context.newPage()) { page.navigate(https://www.baidu.com); System.out.println(page.title()); // ... 其他操作 } // 退出try块时会自动按照page-context-browser-playwright的顺序关闭 } }这种方式代码更简洁且能确保在任何情况下包括异常资源都被正确释放是推荐的做法。4. 进阶实战构建健壮的企业级测试用例一个“Hello World”脚本远远不够。在实际项目中我们需要考虑测试结构、断言、失败处理、数据驱动等。下面我将结合JUnit 5展示如何组织一个更接近真实项目的测试。4.1 使用JUnit 5组织测试首先确保你的项目依赖了JUnit 5如前文pom.xml所示。我们创建一个标准的测试类。import com.microsoft.playwright.*; import org.junit.jupiter.api.*; import static org.assertj.core.api.Assertions.assertThat; import java.nio.file.Paths; TestInstance(TestInstance.Lifecycle.PER_CLASS) // 允许在BeforeAll中使用非静态方法 public class BaiduSearchTest { // 声明为实例变量以便在多个测试方法中共享 Playwright playwright; Browser browser; BrowserContext context; Page page; BeforeAll public void setUpAll() { playwright Playwright.create(); // 可以在这里初始化一些全局设置比如启动一次浏览器供所有测试方法使用 // 但注意测试间共享状态可能导致相互干扰需谨慎。 } BeforeEach public void setUp() { // 每个测试方法前执行确保测试隔离 browser playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(true)); // CI环境用无头模式 context browser.newContext(); // 设置默认超时和视口大小 context.setDefaultTimeout(10000); // 10秒 context.setDefaultNavigationTimeout(30000); // 导航超时30秒 page context.newPage(); page.setViewportSize(1920, 1080); // 设置浏览器窗口大小 } Test public void testSearchFunction() { // 1. 导航 page.navigate(https://www.baidu.com); // 2. 断言验证页面标题包含“百度” assertThat(page.title()).contains(百度); // 3. 操作与断言搜索并验证结果 page.locator(input#kw).fill(Playwright Java); page.locator(input#su).click(); // 等待搜索结果区域出现 Locator resultStats page.locator(#content_left); resultStats.waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE)); // 断言结果页标题包含搜索词 assertThat(page.title()).contains(Playwright Java); // 4. 截图通常用于失败调试或报告 page.screenshot(new Page.ScreenshotOptions() .setPath(Paths.get(target/test-search-result.png)) .setFullPage(true)); } Test public void testHomePageLogo() { page.navigate(https://www.baidu.com); // 使用更精确的选择器定位Logo并断言其可见 Locator logo page.locator(a#logo); assertThat(logo.isVisible()).isTrue(); } AfterEach public void tearDown() { // 关闭当前测试方法使用的资源 if (page ! null) page.close(); if (context ! null) context.close(); if (browser ! null) browser.close(); } AfterAll public void tearDownAll() { if (playwright ! null) playwright.close(); } }关键设计解析TestInstance(Lifecycle.PER_CLASS): 这个注解允许在BeforeAll和AfterAll中使用非静态方法。这样我们可以将Playwright实例的创建和销毁放在这两个方法中避免为每个测试方法都创建/销毁一次Playwright连接提升测试套件整体运行速度。但浏览器Browser和上下文Context仍然在每个测试方法BeforeEach中单独创建以保证测试的独立性。资源管理策略:Playwright实例生命周期最长BeforeAll-AfterAllBrowser、Context、Page生命周期与单个测试方法绑定BeforeEach-AfterEach。这是平衡性能和隔离性的常用模式。断言库的使用: 我们使用了AssertJ的assertThat语法它的可读性更强错误信息更友好。例如assertThat(logo.isVisible()).isTrue()如果失败会明确告诉你期望是true但实际是false。配置化: 在setUp方法中我们通过context.setDefaultTimeout()设置了全局等待超时。这意味着所有基于这个Context的locator操作如click(),fill()都会使用这个超时时间无需在每个操作上重复设置。4.2 元素定位最佳实践与高级技巧元素定位是UI自动化的基石。Playwright的LocatorAPI非常强大。1. 优先使用CSS选择器或getBy系列API// 好的做法使用ID、有意义的class或属性 page.locator(#submit-button).click(); page.locator(.primary-btn).click(); page.locator(input[nameusername]).fill(test); // 更好的做法使用Playwright内置的语义化定位器推荐 page.getByRole(AriaRole.BUTTON, new Page.GetByRoleOptions().setName(登录)).click(); page.getByPlaceholder(请输入用户名).fill(test); page.getByText(提交订单).click(); page.getByTestId(search-input).fill(keyword); // 需要前端配合添加>// 等待元素出现并可见 page.locator(.loading-spinner).waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.HIDDEN)); page.locator(.success-message).waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE)); // 等待元素包含特定文本 page.locator(#status).waitFor(new Locator.WaitForOptions().setHasText(操作成功)); // 等待一段时间内的某个条件成立轮询 page.waitForFunction(document.querySelectorAll(.item).length 5);3. 处理iframe和多个页面标签页// 切换到iframe内部操作 Frame frame page.frame(frame-name-or-url); frame.locator(button).click(); // 处理新打开的标签页 Page newPage page.context().waitForPage(() - { // 监听新页面事件 page.locator(a[target_blank]).click(); }); newPage.bringToFront(); // 激活新页面 System.out.println(newPage.title());4.3 模拟复杂用户交互与网络控制Playwright可以模拟几乎所有真实用户操作并拦截和修改网络请求这对于测试复杂场景至关重要。1. 文件上传与下载// 文件上传无需触发系统文件选择框 page.locator(input[typefile]).setInputFiles(Paths.get(/path/to/your/file.pdf)); // 监听并等待文件下载 Page.Download download page.waitForDownload(() - { page.locator(#download-link).click(); }); // 将下载的文件保存到指定位置 download.saveAs(Paths.get(/target/downloaded_file.pdf));2. 鼠标与键盘高级操作// 鼠标悬停 page.locator(.menu-item).hover(); // 拖放操作 page.locator(#draggable).dragTo(page.locator(#droppable)); // 模拟键盘快捷键 page.keyboard().press(ControlA); page.keyboard().press(ControlC); page.locator(#another-input).click(); page.keyboard().press(ControlV);3. 拦截和修改网络请求Mock API这是Playwright的杀手锏之一可以让你在不修改后端的情况下测试前端在各种API响应下的行为。// 在创建页面或导航前设置路由 page.route(**/api/user/profile, route - { // 拦截到匹配的请求并返回一个模拟的JSON响应 MapString, Object mockData new HashMap(); mockData.put(name, Mock User); mockData.put(age, 30); route.fulfill(new Route.FulfillOptions() .setContentType(application/json) .setBody(JSON.toJSONString(mockData)) // 需要JSON库如Jackson ); }); // 或者直接中止某些请求如图片、广告以加速测试 page.route(**/*.{png,jpg,jpeg}, Route::abort); page.navigate(https://your-app.com); // 页面中的相关API请求将被拦截并返回模拟数据5. 工程化与持续集成让测试稳定运行单个测试用例跑通只是第一步如何集成到CI/CD流水线并保证其稳定、可维护、可报告才是真正的挑战。5.1 测试配置与参数化将浏览器类型、是否无头、基础URL等配置外部化。// 使用一个配置类或读取properties文件 public class TestConfig { public static final String BASE_URL System.getProperty(base.url, https://www.baidu.com); public static final boolean HEADLESS Boolean.parseBoolean(System.getProperty(headless, true)); public static final BrowserType BROWSER_TYPE firefox.equals(System.getProperty(browser)) ? BrowserType.FIREFOX : BrowserType.CHROMIUM; } // 在BeforeEach中使用配置 BeforeEach public void setUp() { browser playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(TestConfig.HEADLESS)); // ... page.navigate(TestConfig.BASE_URL); }运行时可以通过JVM参数指定mvn test -Dheadlessfalse -Dbrowserfirefox -Dbase.urlhttps://test.env.com5.2 失败重试与截图测试失败时自动截图并附加到测试报告中是快速定位问题的关键。我们可以利用JUnit 5的TestWatcher扩展。import org.junit.jupiter.api.extension.ExtensionContext; import org.junit.jupiter.api.extension.TestWatcher; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Optional; public class PlaywrightTestWatcher implements TestWatcher { private Page page; // 需要通过某种方式如ThreadLocal将当前测试的Page对象传递进来 public void setPage(Page page) { this.page page; } Override public void testFailed(ExtensionContext context, Throwable cause) { if (page ! null) { String testName context.getDisplayName(); String screenshotPath target/screenshots/failure- testName - System.currentTimeMillis() .png; page.screenshot(new Page.ScreenshotOptions().setPath(Paths.get(screenshotPath)).setFullPage(true)); System.err.println(测试失败截图已保存至: screenshotPath); } } }在测试基类中注册这个Watcher并在BeforeEach中设置当前Page。5.3 集成到CI/CD以GitHub Actions为例在项目的.github/workflows目录下创建test.ymlname: Java Playwright Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up JDK 17 uses: actions/setup-javav3 with: java-version: 17 distribution: temurin - name: Cache Playwright browsers uses: actions/cachev3 with: path: ~/.cache/ms-playwright key: ${{ runner.os }}-playwright-${{ hashFiles(**/pom.xml) }} restore-keys: | ${{ runner.os }}-playwright- - name: Run Maven Test run: mvn test -Dheadlesstrue env: CI: true - name: Upload test reports and screenshots if: always() # 无论测试成功失败都上传 uses: actions/upload-artifactv3 with: name: test-results path: | target/surefire-reports/ target/screenshots/这个工作流会缓存Playwright浏览器二进制文件以加速后续构建并在每次推送或PR时运行测试最后将测试报告和失败截图打包上传便于查看。6. 常见问题排查与性能优化实录在实际使用中你一定会遇到各种问题。下面是我总结的一些典型问题及其解决方案。6.1 浏览器启动失败或超时现象:Browser.newContext()或page.navigate()长时间挂起后报超时错误。排查:检查浏览器二进制文件: 确认Playwright浏览器已正确安装。可以手动运行mvn exec:java -Dexec.mainClasscom.microsoft.playwright.CLI -Dexec.argsinstall来安装或验证。检查代理和网络: 如果公司网络有代理需要配置JVM代理参数或者通过BrowserType.LaunchOptions设置浏览器的代理--proxy-server。增加启动超时时间:browser playwright.chromium().launch(new BrowserType.LaunchOptions().setTimeout(120000));将启动超时设为2分钟。查看详细日志: 在启动选项中加入.setDevtools(true)可以在浏览器中打开开发者工具或者设置环境变量DEBUGpw:api来打印Playwright的详细API调用日志。6.2 元素定位不到或操作失败现象:page.locator(...).click()抛出TimeoutError: Timeout 30000ms exceeded。排查与解决:确认选择器是否正确: 使用浏览器的开发者工具F12的Console输入$$(你的选择器)看是否能选中目标元素。确保没有iframe遮挡。元素可能被覆盖: 使用page.locator(...).click(new Locator.ClickOptions().setForce(true))进行强制点击。等待条件不满足: Playwright的自动等待默认等待元素可操作可见、未被禁用、在视口内。如果业务逻辑要求其他条件如某个属性变化需要使用waitForFunction或更具体的Locator等待方法。页面结构动态加载: 在操作前先等待某个标志性元素出现例如page.locator(#main-content).waitFor()。6.3 测试执行速度慢优化策略:复用Browser Context: 对于一组不依赖严格隔离的测试如只读操作可以在BeforeAll中创建一个Browser和Context所有测试方法共享Page或创建新的Page。这能避免重复启动浏览器的开销。但要注意清理Cookie和LocalStorage(context.clearCookies()).使用无头模式Headless: CI环境中务必设置setHeadless(true)。并行执行: JUnit 5支持并行测试。在src/test/resources下创建junit-platform.properties文件添加junit.jupiter.execution.parallel.enabled true。但需要确保测试之间没有共享状态冲突通常需要为每个测试创建独立的Browser Context。拦截不必要的资源: 使用page.route()拦截并中止对图片、样式表、字体等静态资源的请求可以显著提升页面加载速度。BeforeEach public void setUp() { // ... 创建context和page page.route(**/*.{png,jpg,jpeg,css,woff,woff2}, Route::abort); }调整超时时间: 在稳定的测试环境中可以适当减少默认超时如从30秒减到10秒这样失败时能更快超时而不是长时间等待。6.4 在Docker容器中运行在CI的Docker容器中运行Playwright测试需要安装额外的系统依赖。# 使用微软官方提供的包含Playwright依赖的镜像 FROM mcr.microsoft.com/playwright/java:v1.40.0-jammy # 或者如果你基于普通镜像需要手动安装依赖 # FROM openjdk:17-slim # RUN apt-get update apt-get install -y \ # wget \ # libgbm-dev \ # libnss3 \ # libxss1 \ # libasound2 \ # fonts-liberation \ # libappindicator3-1 \ # xdg-utils \ # rm -rf /var/lib/apt/lists/*确保容器有足够的权限和显示设备即使是无头模式。对于无头模式通常需要设置环境变量PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD1以避免重复下载并确保已通过缓存或镜像层提供了浏览器二进制文件。从环境搭建到编写第一个脚本再到构建复杂、健壮、可集成的测试套件我们走完了Java结合Playwright进行自动化测试的核心路径。这套组合拳的优势在于它让Java开发者能够利用一个现代化、高性能的测试框架而无需离开自己熟悉的技术栈。我个人的体会是Playwright的“自动等待”和“强隔离性”设计让测试脚本的稳定性直接上了一个台阶维护成本显著下降。当然任何工具都有学习曲线初期在元素定位策略和异步操作的理解上可能需要一些适应。但一旦掌握其带来的效率提升是巨大的。最后一个小技巧多利用Playwright Inspector进行脚本录制和调试通过设置环境变量PWDEBUG1或使用playwright codegen命令它能帮你快速生成定位器代码是学习和排查问题的利器。