Java+Playwright+TestNG:构建现代Web自动化测试框架的完整指南

📅 2026/8/11 21:41:41
Java+Playwright+TestNG:构建现代Web自动化测试框架的完整指南
1. 项目概述为什么选择JavaPlaywrightTestNG这套组合拳如果你是一名Java技术栈的测试工程师或者正在为Web应用寻找一个稳定、高效且现代化的UI自动化测试方案那么“Java Playwright TestNG”这个组合绝对值得你投入时间深入研究。我过去几年在多个大型项目中实践过Selenium、Cypress等框架最终将核心的Web自动化测试迁移到了这套技术栈上因为它实实在在地解决了传统方案的诸多痛点。简单来说这是一个基于Java语言利用微软开源的Playwright库来驱动浏览器并通过TestNG测试框架来组织、管理和运行测试用例的完整解决方案。它不是为了替代已有的API或单元测试而是专门针对Web前端交互的验证确保用户点击的每个按钮、输入的每个表单都能如预期般工作。这套方案特别适合那些后端是Java、前端技术栈多样React, Vue, Angular等且需要做跨浏览器兼容性测试的团队。相比于老牌的SeleniumPlaywright在速度、稳定性和功能丰富性上带来了代际提升而TestNG则提供了Java生态中久经考验的测试生命周期管理和数据驱动支持。2. 技术选型深度解析Playwright与TestNG的强强联合2.1 为什么是Playwright而不是Selenium这是所有技术决策的起点。Selenium WebDriver统治了Web自动化领域十多年但其架构决定了它的一些固有缺陷依赖于浏览器的原生驱动通信基于HTTP协议等待机制不够智能对现代Web特性如Shadow DOM、网络拦截支持较弱。Playwright则采用了截然不同的设计进程外架构Playwright启动的是一个真实的浏览器进程如Chromium并通过WebSocket协议与之通信。这意味着你的测试脚本和浏览器运行在不同的进程避免了因页面JavaScript阻塞而导致整个脚本挂起的问题稳定性极大提升。自动等待这是Playwright最令人称道的特性之一。几乎所有操作如click,fill,waitForSelector都内置了智能等待。它会等待元素可操作可见、启用、稳定后再执行无需在脚本中到处编写Thread.sleep或复杂的显式等待代码简洁且健壮。多浏览器支持一套API支持ChromiumChrome, Edge、Firefox和WebKitSafari。浏览器二进制文件由Playwright统一管理版本兼容性问题大大减少。强大的网络与上下文控制可以轻松模拟移动设备、地理位置、语言、权限如摄像头、拦截和修改网络请求、管理Cookie和本地存储等。这对于测试复杂的单页应用SPA和需要特定环境的场景至关重要。2.2 为什么是TestNG而不是JUnit虽然JUnit 5功能已经非常强大但TestNG在设计之初就为更复杂的集成测试和UI自动化测试场景做了优化灵活的测试配置BeforeSuite/AfterSuite等TestNG提供了粒度更细的配置方法BeforeSuite,BeforeTest,BeforeClass,BeforeMethod及其对应的After...。在UI自动化中我们通常需要在套件开始时启动一次浏览器在所有测试类间共享浏览器上下文或在每个测试方法前后进行页面重置。TestNG的这种层级结构让资源管理更清晰。强大的参数化与数据驱动通过DataProvider注解可以非常优雅地从Excel、JSON、数据库或硬编码的数组中为测试方法提供多组测试数据。这对于需要测试多种输入组合的UI表单场景极其方便。依赖测试与分组执行你可以通过dependsOnMethods或dependsOnGroups来定义测试方法的执行顺序依赖。还可以用Test(groups {smoke, regression})给测试打标签然后选择性地只运行冒烟测试或回归测试套件。并行执行TestNG原生支持在方法、类或测试级别进行并行执行。结合Playwright的BrowserContext浏览器上下文我们可以轻松实现测试用例的隔离与并行大幅缩短测试套件的总执行时间。3. 环境搭建与项目初始化实操3.1 基础环境准备首先确保你的开发机器上已经安装了以下软件JDK 8或更高版本推荐JDK 11或17这是目前企业级应用的主流选择。Maven 或 Gradle作为项目构建和依赖管理工具。本文以Maven为例。IDEIntelliJ IDEA或Eclipse具备良好的Java和Maven支持。3.2 创建Maven项目并配置依赖在你的IDE中创建一个新的Maven项目或者在现有项目的pom.xml中添加以下关键依赖dependencies !-- Playwright for Java -- dependency groupIdcom.microsoft.playwright/groupId artifactIdplaywright/artifactId version1.40.0/version !-- 请使用最新稳定版 -- /dependency !-- TestNG -- dependency groupIdorg.testng/groupId artifactIdtestng/artifactId version7.8.0/version scopetest/scope /dependency !-- 日志框架便于调试 -- dependency groupIdorg.slf4j/groupId artifactIdslf4j-simple/artifactId version2.0.9/version scopetest/scope /dependency /dependencies注意Playwright的Java库会自动管理浏览器二进制文件。当你第一次运行测试时如果系统没有所需的浏览器它会自动下载。你也可以通过命令行mvn exec:java -e -Dexec.mainClasscom.microsoft.playwright.CLI -Dexec.argsinstall chromium firefox webkit来预先安装。3.3 编写第一个测试类验证百度搜索让我们从一个最简单的例子开始直观感受Playwright API的流畅性。import com.microsoft.playwright.*; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import static org.testng.Assert.assertTrue; public class FirstPlaywrightTest { Playwright playwright; Browser browser; BrowserContext context; Page page; BeforeMethod public void setUp() { // 1. 创建Playwright实例 playwright Playwright.create(); // 2. 启动一个Chromium浏览器实例headlessfalse表示有界面便于调试 browser playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false)); // 3. 创建一个新的浏览器上下文类似于隐身会话隔离性好 context browser.newContext(); // 4. 在新上下文中创建一个页面 page context.newPage(); } Test public void testBaiduSearch() { // 导航到百度 page.navigate(https://www.baidu.com); // 定位搜索框并输入关键词 // Playwright的定位器LocatorAPI非常强大且易读 page.locator(input#kw).fill(Playwright自动化测试); // 点击“百度一下”按钮 page.locator(input#su).click(); // 等待搜索结果出现。这里使用waitForSelector它会自动等待直到元素出现。 page.waitForSelector(div#content_left); // 断言检查页面标题或结果中是否包含预期文本 String title page.title(); assertTrue(title.contains(Playwright自动化测试), 页面标题未包含搜索关键词); // 也可以检查页面内容 String content page.textContent(div#content_left); assertTrue(content.contains(Playwright), 搜索结果中未找到Playwright); } AfterMethod public void tearDown() { // 关闭资源顺序与创建相反 if (page ! null) { page.close(); } if (context ! null) { context.close(); } if (browser ! null) { browser.close(); } if (playwright ! null) { playwright.close(); } } }实操心得BeforeMethod和AfterMethod确保了每个测试方法都有自己干净的浏览器上下文和页面测试之间完全隔离避免了状态污染。page.locator()是核心API它返回一个Locator对象。这个对象是“惰性”的只有在执行操作如click()时才会去实际查找元素并且内置了重试和等待逻辑。将setHeadless(false)设置为true可以在无头模式下运行适合CI/CD环境执行更快且不占用GUI资源。4. 核心模式与最佳实践构建可维护的测试框架直接在每个测试类中编写setUp和tearDown会很快导致代码重复。我们需要一个更优雅的架构。4.1 设计基础测试基类BaseTest创建一个抽象基类来封装所有测试的通用设置和清理逻辑。import com.microsoft.playwright.*; import org.testng.annotations.*; public abstract class BaseTest { // 使用ThreadLocal保证在并行测试时每个线程有自己的实例避免冲突 private static final ThreadLocalPlaywright playwrightThreadLocal new ThreadLocal(); private static final ThreadLocalBrowser browserThreadLocal new ThreadLocal(); private static final ThreadLocalBrowserContext contextThreadLocal new ThreadLocal(); private static final ThreadLocalPage pageThreadLocal new ThreadLocal(); BeforeSuite public void initPlaywright() { // 整个测试套件开始时每个线程初始化自己的Playwright实例 // 通常一个线程对应一个浏览器实例 } BeforeMethod public void createBrowserAndPage() { // 每个测试方法前创建新的上下文和页面 Playwright playwright Playwright.create(); playwrightThreadLocal.set(playwright); // 这里可以从配置文件中读取浏览器类型、是否无头等参数 BrowserType.LaunchOptions launchOptions new BrowserType.LaunchOptions() .setHeadless(Boolean.parseBoolean(System.getProperty(headless, true))) // 默认无头 .setSlowMo(500); // 操作间慢速播放便于观察生产环境应设为0或通过配置控制 Browser browser playwright.chromium().launch(launchOptions); browserThreadLocal.set(browser); // 创建上下文时可以设置视口大小、用户代理、语言等 Browser.NewContextOptions contextOptions new Browser.NewContextOptions() .setViewportSize(1920, 1080) .setLocale(zh-CN); BrowserContext context browser.newContext(contextOptions); contextThreadLocal.set(context); Page page context.newPage(); pageThreadLocal.set(page); } protected Page getPage() { return pageThreadLocal.get(); } protected BrowserContext getContext() { return contextThreadLocal.get(); } protected Browser getBrowser() { return browserThreadLocal.get(); } AfterMethod public void closeContext() { // 关闭上下文会自动关闭其中的所有页面 if (contextThreadLocal.get() ! null) { contextThreadLocal.get().close(); contextThreadLocal.remove(); } } AfterSuite public void closeBrowserAndPlaywright() { // 关闭浏览器和Playwright实例 if (browserThreadLocal.get() ! null) { browserThreadLocal.get().close(); browserThreadLocal.remove(); } if (playwrightThreadLocal.get() ! null) { playwrightThreadLocal.get().close(); playwrightThreadLocal.remove(); } } }4.2 使用Page Object Model (POM) 模式POM是UI自动化的黄金法则它将页面元素定位和操作封装成单独的类使测试脚本更清晰元素变更只需修改一处。1. 定义百度首页的Page Object// BaiduHomePage.java import com.microsoft.playwright.Locator; import com.microsoft.playwright.Page; public class BaiduHomePage { private final Page page; private final Locator searchInput; private final Locator searchButton; // 构造函数接收Playwright的Page对象 public BaiduHomePage(Page page) { this.page page; // 初始化定位器 this.searchInput page.locator(input#kw); this.searchButton page.locator(input#su); } // 页面导航动作 public void navigate() { page.navigate(https://www.baidu.com); } // 业务操作输入关键词并搜索 public void searchFor(String keyword) { searchInput.fill(keyword); searchButton.click(); } // 获取页面标题用于断言 public String getTitle() { return page.title(); } }2. 定义搜索结果页的Page Object// BaiduResultsPage.java import com.microsoft.playwright.Locator; import com.microsoft.playwright.Page; public class BaiduResultsPage { private final Page page; private final Locator firstResultLink; private final Locator resultStats; public BaiduResultsPage(Page page) { this.page page; // 更健壮的定位器使用CSS选择器结合文本内容 this.firstResultLink page.locator(div#content_left h3 a).first(); this.resultStats page.locator(span.nums_text); } // 获取第一个结果的标题文本 public String getFirstResultTitle() { return firstResultLink.textContent(); } // 获取结果统计信息 public String getResultStats() { return resultStats.textContent(); } // 点击第一个结果 public void clickFirstResult() { firstResultLink.click(); } }3. 使用POM的测试类// SearchTest.java import org.testng.annotations.Test; import static org.testng.Assert.assertTrue; public class SearchTest extends BaseTest { // 继承我们之前写的BaseTest Test public void testSearchAndVerifyResults() { // 1. 获取基类中初始化的Page对象 Page page getPage(); // 2. 初始化Page Object BaiduHomePage homePage new BaiduHomePage(page); homePage.navigate(); // 3. 执行搜索操作 String keyword Playwright Java; homePage.searchFor(keyword); // 4. 页面跳转后初始化结果页Page Object // Playwright会自动等待导航完成但为了更稳妥可以显式等待某个结果元素出现 page.waitForSelector(div#content_left); BaiduResultsPage resultsPage new BaiduResultsPage(page); // 5. 进行断言 String firstResultTitle resultsPage.getFirstResultTitle(); assertTrue(firstResultTitle.contains(keyword) || firstResultTitle.contains(Playwright), 第一个结果标题不包含关键词。实际标题 firstResultTitle); String stats resultsPage.getResultStats(); assertTrue(stats.contains(约), 未找到结果统计信息。); // 6. (可选) 点击进入第一个结果页并验证新页面 resultsPage.clickFirstResult(); // 注意点击后打开了新标签页需要切换到新页面 Page newPage page.context().waitForPage(() - { // 这个lambda内的点击操作会触发新页面打开 // 但点击动作已在上面执行这里主要作用是等待新页面出现 }); // 或者更简单等待页面加载假设点击后是原地跳转 // page.waitForLoadState(LoadState.NETWORKIDLE); // 然后对新页面进行断言... } }POM模式的优势高可维护性UI元素定位器集中管理前端页面结构变更时只需修改对应的Page Object类。高可读性测试用例读起来像自然语言业务逻辑清晰。低冗余避免了在多个测试脚本中重复编写相同的元素定位和基础操作。5. 高级特性与应用场景实战5.1 处理弹窗、新窗口和iframe现代Web应用充满了弹窗、新标签页和嵌套的iframePlaywright处理这些游刃有余。Test public void testHandlePopupsAndFrames() { Page page getPage(); page.navigate(https://example.com); // 1. 监听并处理弹窗alert, confirm, prompt page.onDialog(dialog - { System.out.println(弹窗消息: dialog.message()); dialog.accept(); // 点击“确定” // dialog.dismiss(); // 点击“取消” }); // 2. 处理新窗口/标签页 // 方式一监听popup事件由target_blank的链接触发 page.context().onPage(newPage - { System.out.println(新页面打开: newPage.url()); // 可以在这里对新页面进行操作 newPage.bringToFront(); // 切换到新页面 }); // 方式二点击一个会打开新窗口的链接然后获取它 Page newPage page.context().waitForPage(() - { page.locator(a[target_blank]).click(); }); // 现在可以操作newPage对象了 newPage.locator(h1).waitFor(); // 3. 处理iframe // 定位到iframe元素 Frame frame page.frame(iframe-name-or-id); // 或者通过选择器 Frame frameBySelector page.frameLocator(iframe[titleMy Iframe]).contentFrame(); // 在iframe内部进行操作 if (frame ! null) { frame.locator(button).click(); } }5.2 模拟设备、网络和地理位置Playwright可以轻松模拟移动端测试、弱网环境等。Test public void testMobileEmulationAndNetwork() { // 从Playwright.deviceDescriptors获取预定义的设备配置 Browser.NewContextOptions options Playwright.devices().get(iPhone 12 Pro); // 可以覆盖或添加更多选项 options.setLocale(en-US) .setTimezoneId(America/Los_Angeles) .setPermissions(Arrays.asList(geolocation)); // 授予地理位置权限 // 使用设备配置创建上下文 BrowserContext mobileContext getBrowser().newContext(options); Page mobilePage mobileContext.newPage(); mobilePage.navigate(https://maps.example.com); // 现在页面会以iPhone 12 Pro的视口和用户代理打开 // 模拟地理位置需要之前授予了权限 mobileContext.setGeolocation(new Geolocation(37.7749, -122.4194)); // 旧金山坐标 // 模拟网络条件慢3G mobilePage.route(**/*, route - { // 这里可以拦截和修改请求 // 例如给所有图片请求添加延迟 if (route.request().resourceType().equals(image)) { try { Thread.sleep(2000); } catch (InterruptedException e) {} } route.resume(); }); // 或者使用更简单的网络限速需要在创建上下文前设置 // BrowserContext context browser.newContext(new Browser.NewContextOptions().set...); // context.setDefaultNavigationTimeout(60000); // 设置导航超时 // 网络模拟更推荐使用page.route进行细粒度控制。 }5.3 使用TestNG进行数据驱动测试这是TestNG的杀手级功能非常适合测试不同输入下的UI行为。public class DataDrivenSearchTest extends BaseTest { // 数据提供者方法返回一个二维Object数组 DataProvider(name searchKeywords) public Object[][] provideSearchData() { return new Object[][] { {Playwright}, {TestNG}, {Java自动化测试}, {, 搜索关键词不应为空}, // 空字符串测试第二个参数是预期错误信息如果需要 {Selenium与Playwright对比} }; } Test(dataProvider searchKeywords) public void testSearchWithMultipleKeywords(String keyword) { Page page getPage(); BaiduHomePage homePage new BaiduHomePage(page); homePage.navigate(); homePage.searchFor(keyword); // 等待结果加载 page.waitForSelector(div#content_left, new Page.WaitForSelectorOptions().setTimeout(10000)); // 简单的存在性断言确保结果区域存在 assertTrue(page.locator(div#content_left).isVisible(), 搜索关键词 keyword 后未显示结果区域。); // 对于空搜索可以有不同的断言逻辑 if (keyword.isEmpty()) { // 可能页面会停留在首页或有提示这里根据实际业务逻辑断言 // assertTrue(page.url().equals(https://www.baidu.com/)); } } // 更复杂的数据提供者可以从外部文件读取 DataProvider(name searchDataFromCSV) public IteratorObject[] loadSearchDataFromCSV() throws IOException { ListObject[] data new ArrayList(); // 使用OpenCSV等库读取CSV文件 // 每一行作为一个Object[]添加到data列表中 // data.add(new Object[]{col1, col2, expectedResult}); return data.iterator(); } }5.4 测试报告与截图在测试失败时自动截图是UI自动化调试的必备功能。import org.testng.ITestResult; import org.testng.annotations.AfterMethod; import java.nio.file.Paths; public class ScreenshotOnFailureTest extends BaseTest { AfterMethod public void takeScreenshotOnFailure(ITestResult result) { // 如果测试方法执行失败 if (result.getStatus() ITestResult.FAILURE) { Page page getPage(); if (page ! null) { // 生成唯一的文件名类名_方法名_时间戳 String fileName String.format(%s_%s_%d.png, result.getTestClass().getName(), result.getMethod().getMethodName(), System.currentTimeMillis()); // 截图并保存到target/screenshots目录 page.screenshot(new Page.ScreenshotOptions() .setPath(Paths.get(target/screenshots, fileName)) .setFullPage(true)); // 截取完整页面 System.out.println(测试失败截图已保存至: target/screenshots/ fileName); // 也可以录制视频需要在创建上下文时启用 // 参见context browser.newContext(new Browser.NewContextOptions().setRecordVideoDir(Paths.get(videos/))); } } } Test public void testThatMightFail() { getPage().navigate(https://example.com); // 一个故意会失败的断言 assertTrue(getPage().title().contains(不存在的标题), 这个测试应该失败并触发截图); } }6. 集成与持续执行让自动化测试融入CI/CD6.1 使用Maven Surefire Plugin运行测试在pom.xml中配置Maven Surefire插件可以方便地通过命令行运行测试并生成TestNG报告。build plugins plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-surefire-plugin/artifactId version3.0.0-M9/version configuration !-- 指定TestNG的XML套件文件 -- suiteXmlFiles suiteXmlFilesrc/test/resources/testng.xml/suiteXmlFile /suiteXmlFiles !-- 并行执行测试方法 -- parallelmethods/parallel threadCount4/threadCount !-- 设置系统属性如是否无头运行 -- systemPropertyVariables headlesstrue/headless /systemPropertyVariables /configuration /plugin /plugins /build对应的testng.xml文件!DOCTYPE suite SYSTEM https://testng.org/testng-1.0.dtd suite namePlaywright UI Automation Suite paralleltests thread-count3 test nameChrome Tests parameter namebrowser valuechromium/ classes class namecom.yourcompany.tests.SearchTest/ class namecom.yourcompany.tests.LoginTest/ /classes /test test nameFirefox Tests parameter namebrowser valuefirefox/ classes class namecom.yourcompany.tests.SearchTest/ /classes /test /suite然后通过命令运行mvn clean test。6.2 与Jenkins/GitHub Actions集成在CI/CD流水线中你需要确保环境有Java、Maven和必要的浏览器或让Playwright自动下载。一个简单的Jenkins Pipeline步骤或GitHub Actions工作流配置如下GitHub Actions示例 (.github/workflows/playwright-tests.yml):name: Playwright UI 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 Maven dependencies uses: actions/cachev3 with: path: ~/.m2 key: maven-${{ hashFiles(**/pom.xml) }} restore-keys: | maven- - name: Install Playwright Browsers run: mvn exec:java -e -Dexec.mainClasscom.microsoft.playwright.CLI -Dexec.argsinstall chromium -q - name: Run Tests with Maven run: mvn clean test -Dheadlesstrue - name: Upload Test Reports if: always() # 即使测试失败也上传 uses: actions/upload-artifactv3 with: name: test-reports path: | target/surefire-reports/ target/screenshots/7. 常见问题排查与性能优化技巧在实际项目中踩过不少坑这里总结几个高频问题和优化点。7.1 元素定位失败超时与稳定性问题page.locator(...).click()抛出超时错误提示元素不可见或无法点击。排查与解决检查选择器使用Playwright Inspector (playwright codegen) 重新录制操作确认选择器是否准确。优先使用id、>page.locator(slow-loading-element).click(new Locator.ClickOptions().setTimeout(30000));等待特定状态不要只等元素出现要等它处于可交互状态。page.locator(button.submit).waitFor(new Locator.WaitForOptions().setState(WaitForSelectorState.VISIBLE)); // 或者使用更通用的等待 page.waitForFunction(document.readyState complete);处理动态内容/iframe确保操作前包含该元素的iframe或Shadow DOM已加载完成。禁用动画有时CSS动画会影响点击。可以在创建上下文时添加一个样式来禁用所有动画。context.addInitScript(() {\n const style document.createElement(style);\n style.innerHTML * { animation-duration: 0s !important; transition-duration: 0s !important; };\n document.head.appendChild(style);\n });7.2 测试执行速度慢优化策略使用无头模式在CI环境务必设置setHeadless(true)。复用浏览器实例不要为每个测试类都启动/关闭浏览器。像我们之前设计的BaseTest可以在BeforeSuite启动浏览器在所有测试类间共享通过ThreadLocal隔离上下文。并行执行充分利用TestNG的parallel特性和Playwright的BrowserContext隔离性并行运行测试。确保测试之间没有共享状态依赖。避免不必要的导航如果多个测试需要同一个初始页面考虑在BeforeClass中导航一次而不是每个BeforeMethod都导航。选择性安装浏览器如果只测试Chrome就只安装chromium不要安装firefox和webkit。7.3 在Docker或CI环境中运行问题在无GUI的服务器上运行失败提示无法启动浏览器。解决Playwright的Docker镜像 (mcr.microsoft.com/playwright/java) 已经包含了所有依赖。直接使用它是首选。如果要在自己的Linux镜像中运行需要安装系统依赖FROM openjdk:17-slim RUN apt-get update apt-get install -y \ wget \ gnupg \ libwoff1 \ libopus0 \ libwebp6 \ libwebpdemux2 \ libenchant1c2a \ libgudev-1.0-0 \ libsecret-1-0 \ libhyphen0 \ libgdk-pixbuf2.0-0 \ libegl1 \ libgles2 \ libevent-2.1-7 \ libnotify4 \ libvpx6 \ libxslt1.1 \ libharfbuzz0b \ libicu67 \ libpng16-16 \ libfreetype6 \ libfontconfig1 \ libglib2.0-0 \ libx11-6 \ libxcomposite1 \ libxdamage1 \ libxext6 \ libxfixes3 \ libxrandr2 \ libnss3 \ libatk1.0-0 \ libatk-bridge2.0-0 \ libcups2 \ libdrm2 \ libdbus-1-3 \ libxkbcommon0 \ libatspi2.0-0 \ libwayland-client0 \ libwayland-server0 \ libgbm1 \ libasound2 \ rm -rf /var/lib/apt/lists/*在CI脚本中确保在运行测试前执行mvn exec:java -Dexec.mainClasscom.microsoft.playwright.CLI -Dexec.argsinstall chromium来安装浏览器。7.4 处理验证码与复杂UI交互UI自动化无法绕过验证码这是设计上的限制。对于测试环境常见的做法是环境隔离在测试环境中禁用验证码或设置一个万能验证码。Cookie/Storage复用使用context.storageState()保存登录后的Cookie和本地存储在后续测试中通过browser.newContext(new Browser.NewContextOptions().setStorageStatePath(Paths.get(state.json)))来复用登录状态绕过登录含验证码环节。第三方服务对于必须测试验证码的场景可以考虑接入付费的验证码识别服务但成本高识别率非100%。对于复杂的拖拽、画图等操作Playwright提供了page.mouse()API进行低级别控制或者使用page.locator().dragTo()等高级封装方法。我个人在实际项目中将这套技术栈应用于一个中后台管理系统涵盖了从简单的CRUD到复杂的图表交互。最大的体会是Playwright的“自动等待”和“多上下文隔离”特性让测试脚本的稳定性从过去的70%提升到了95%以上。而TestNG的数据驱动和分组功能则让我们能灵活地组织冒烟测试、核心路径测试和全量回归测试。初期在元素定位策略和测试数据准备上投入了时间但后期维护成本显著降低新同学上手写用例也非常快。如果你还在为Selenium的飘忽不定而烦恼强烈建议你尝试一下JavaPlaywrightTestNG这个组合它很可能会成为你Web自动化测试的终极方案。