Playwright Java 测试运行器接入指南:JUnit 与 TestNG 集成、类级并行执行及 Gradle 构建配置
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
本文以 Playwright 官方 Java 文档 Test Runners 为主体,讲解如何将 Playwright 接入 Java 生态中最常用的两个测试运行器 JUnit 与 TestNG:包括 Playwright/Browser/BrowserContext/Page 四级对象的生命周期管理、JUnit 5 类级并行执行的完整配置、Gradle(Groovy/Kotlin DSL)构建脚本接入方式,以及 Playwright 命令行工具的 Gradle 任务封装。读完本文,你可以直接复制可运行的配置到项目中,并理解"浏览器跨测试共享、上下文按测试隔离、Playwright 实例按线程独占"这套并行化设计的底层原因。
核心设计:对象生命周期与测试隔离
Playwright Java 接入测试运行器的核心原则是分级复用:
Playwright与Browser在整个测试类生命周期内共享一次创建、一次销毁,避免反复启动浏览器进程的开销;BrowserContext与Page为每个测试方法新建、测试结束后关闭,保证各测试之间的浏览器状态(Cookie、LocalStorage、缓存、弹窗等)完全隔离。
官方文档明确建议:"We recommend running each test case in a new BrowserContext, this way browser state will be isolated between the tests." 这与 Writing tests 中 Test Isolation 一节的说法一致:BrowserContext 是一个内存中隔离的浏览器 profile,每个测试创建独立上下文可确保测试互不干扰。
理解这一点后,后文 JUnit 与 TestNG 两个示例的差异就只是"生命周期钩子注解不同":
| 生命周期层次 | JUnit 5 注解 | TestNG 注解 |
|---|---|---|
| 类级启动(Playwright + Browser) | @BeforeAll/@AfterAll | @BeforeClass/@AfterClass |
| 方法级(BrowserContext + Page) | @BeforeEach/@AfterEach | @BeforeMethod/@AfterMethod |
| 测试方法 | @Test | @Test |
JUnit 5 接入
标准写法:静态生命周期钩子
在 JUnit 中,Playwright与Browser初始化为静态成员,在@BeforeAll中创建,@AfterAll中销毁;BrowserContext与Page为非静态成员,逐方法创建/销毁。以下示例完整来自官方文档,三个测试方法共用同一个Browser,每个测试使用自己的BrowserContext和Page:
package org.example; import com.microsoft.playwright.Browser; import com.microsoft.playwright.BrowserContext; import com.microsoft.playwright.Page; import com.microsoft.playwright.Playwright; import org.junit.jupiter.api.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; public class TestExample { // Shared between all tests in this class. static Playwright playwright; static Browser browser; // New instance for each test method. BrowserContext context; Page page; @BeforeAll static void launchBrowser() { playwright = Playwright.create(); browser = playwright.chromium().launch(); } @AfterAll static void closeBrowser() { playwright.close(); } @BeforeEach void createContextAndPage() { context = browser.newContext(); page = context.newPage(); } @AfterEach void closeContext() { context.close(); } @Test void shouldClickButton() { page.navigate("data:text/html,<script>var result;</script><button onclick='result=\"Clicked\"'>Go</button>"); page.locator("button").click(); assertEquals("Clicked", page.evaluate("result")); } @Test void shouldCheckTheBox() { page.setContent("<input id='checkbox' type='checkbox'></input>"); page.locator("input").check(); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } @Test void shouldSearchWiki() { page.navigate("https://www.wikipedia.org/"); page.locator("input[name=\"search\"]").click(); page.locator("input[name=\"search\"]").fill("playwright"); page.locator("input[name=\"search\"]").press("Enter"); assertEquals("https://en.wikipedia.org/wiki/Playwright", page.url()); } }实现要点说明:
Playwright.create()是 Java 驱动的入口,内部启动 Playwright server 进程;playwright.close()会连同其下的 Browser 一起回收,@AfterAll中无需单独调用browser.close();- 三个测试覆盖了三种典型自动化场景:
data:URL +evaluate做点击验证、setContent做本地 DOM 交互验证、真实站点导航验证端到端流程; - 如需有头模式调试,可改为
playwright.chromium().launch(new BrowserType.LaunchOptions().setHeadless(false)),参见 Running and debugging tests。
实验性 JUnit 集成:@UsePlaywright
官方文档还链接了一个实验性(experimental)的 JUnit 集成,仓库中对应 JUnit (experimental)。它通过@UsePlaywright注解 + JUnit fixture 机制自动完成初始化,测试方法直接以Page page作为参数注入,无需手写任何生命周期代码:
package org.example; import com.microsoft.playwright.Page; import com.microsoft.playwright.junit.UsePlaywright; import org.junit.jupiter.api.Test; import static com.microsoft.playwright.assertions.PlaywrightAssertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; @UsePlaywright public class TestExample { @Test void shouldClickButton(Page page) { page.navigate("data:text/html,<script>var result;</script><button onclick='result=\"Clicked\"'>Go</button>"); page.locator("button").click(); assertEquals("Clicked", page.evaluate("result")); } @Test void shouldSearchWiki(Page page) { page.navigate("https://www.wikipedia.org/"); page.locator("input[name=\"search\"]").click(); page.locator("input[name=\"search\"]").fill("playwright"); page.locator("input[name=\"search\"]").press("Enter"); assertThat(page).hasURL("https://en.wikipedia.org/wiki/Playwright"); } }该实验性集成预定义了五组 fixture,官方给出的对照表如下:
| Fixture | 类型 | 说明 |
|---|---|---|
page | Page | 本次测试运行独立的页面 |
browserContext | BrowserContext | 本次测试运行独立的上下文,pagefixture 也隶属于该上下文 |
browser | Browser | 浏览器在测试之间共享以优化资源 |
playwright | Playwright | Playwright 实例在同线程运行的测试之间共享 |
request | APIRequestContext | 本次测试运行独立的 APIRequestContext,用于 API 测试 |
注意 fixture 表中"playwright 实例在同线程之间共享"这一条——这恰好呼应了下一节的并行化约束。
并行运行测试
为什么必须"每线程一个 Playwright"
官方文档给出的并行化建议是:类内测试顺序执行、多个测试类并行执行,且"每个线程创建自己的 Playwright 实例并只在该线程使用"。这条建议的根源在 Multithreading 文档中有明确说明:
Playwright Java is not thread safe, i.e. all its methods as well as methods on all objects created by it (such as BrowserContext, Browser, Page etc.) are expected to be called on the same thread where the Playwright object was created.
即 Playwright Java 及其创建的所有对象都不是线程安全的,同一实例的多线程并发调用需要自行加锁同步;官方推荐的并行模式是"每线程独立 Playwright 实例、各自启动独立浏览器进程"。因此并行方案的关键就是让每个测试类持有独立的 Playwright/Browser 副本。
实现:PER_CLASS 实例生命周期 + 公共基类
JUnit 默认每个测试方法都会新建一个测试类实例,无法承载"类级共享"的静态外字段。解决方式是使用@TestInstance(TestInstance.Lifecycle.PER_CLASS)注解,让 JUnit 对整个测试类只创建一个实例(JUnit 5.4+),再把 Playwright/Browser 存为实例字段。官方示例的结构是:公共基类TestFixtures承担全部生命周期管理,业务测试类只继承并写断言:
// Subclasses will inherit PER_CLASS behavior. @TestInstance(TestInstance.Lifecycle.PER_CLASS) class TestFixtures { // Shared between all tests in the class. Playwright playwright; Browser browser; @BeforeAll void launchBrowser() { playwright = Playwright.create(); browser = playwright.chromium().launch(); } @AfterAll void closeBrowser() { playwright.close(); } // New instance for each test method. BrowserContext context; Page page; @BeforeEach void createContextAndPage() { context = browser.newContext(); page = context.newPage(); } @AfterEach void closeContext() { context.close(); } } class Test1 extends TestFixtures { @Test void shouldClickButton() { page.navigate("data:text/html,<script>var result;</script><button onclick='result=\"Clicked\"'>Go</button>"); page.locator("button").click(); assertEquals("Clicked", page.evaluate("result")); } @Test void shouldCheckTheBox() { page.setContent("<input id='checkbox' type='checkbox'></input>"); page.locator("input").check(); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } @Test void shouldSearchWiki() { page.navigate("https://www.wikipedia.org/"); page.locator("input[name=\"search\"]").click(); page.locator("input[name=\"search\"]").fill("playwright"); page.locator("input[name=\"search\"]").press("Enter"); assertEquals("https://en.wikipedia.org/wiki/Playwright", page.url()); } } class Test2 extends TestFixtures { @Test void shouldReturnInnerHTML() { page.setContent("<div>hello</div>"); assertEquals("hello", page.innerHTML("css=div")); } @Test void shouldClickButton() { Page popup = page.waitForPopup(() -> { page.evaluate("window.open('about:blank');"); }); assertEquals("about:blank", popup.url()); } }从源码结构看,Test1、Test2各自继承TestFixtures后会继承PER_CLASS行为:JUnit 为Test1创建一个实例(对应一个 Playwright + 一个浏览器进程),为Test2再创建另一个实例(另一个 Playwright + 另一个浏览器进程)。两个类被 JUnit 分配到不同线程并发执行时,各自访问的 Playwright 对象从未跨线程共享,天然满足线程安全约束。
JUnit 并行配置项
自 JUnit 5.3 起支持并行执行,官方推荐"类内顺序、类间并行、线程数 = CPU 核数的一半"的配置,写入junit-platform.properties(或对应构建配置):
junit.jupiter.execution.parallel.enabled = true junit.jupiter.execution.parallel.mode.default = same_thread junit.jupiter.execution.parallel.mode.classes.default = concurrent junit.jupiter.execution.parallel.config.strategy=dynamic junit.jupiter.execution.parallel.config.dynamic.factor=0.5各配置项含义:
parallel.enabled = true:总开关,启用并行执行;parallel.mode.default = same_thread:默认(类内测试方法之间)保持在同一线程顺序执行,保证单个类内的测试不产生并发;parallel.mode.classes.default = concurrent:测试类之间并行调度到不同线程;parallel.config.strategy = dynamic:使用动态线程池策略;parallel.config.dynamic.factor = 0.5:动态因子 0.5,即最大并行线程数取 CPU 核数的一半。
官方取 0.5 而非更激进的核数,是为每个浏览器进程留出渲染与 I/O 余量;如果你的测试更偏 CPU 密集(大量截图比对、录制视频),可以适当调大该因子观察机器负载。
使用 Gradle 构建
Playwright Java 依赖发布在 Maven Central,模块坐标为com.microsoft.playwright:playwright。官方文档给出了 Groovy 与 Kotlin 两种 DSL 的完整构建脚本,文档中的%%VERSION%%是文档构建时的版本占位符,实际项目中替换为具体版本号即可。
Groovy(build.gradle)
plugins { application id 'java' } repositories { mavenCentral() } dependencies { implementation 'com.microsoft.playwright:playwright:%%VERSION%%' } application { mainClass = 'org.example.App' } // Usage: ./gradlew playwright --args="help" task playwright(type: JavaExec) { classpath sourceSets.test.runtimeClasspath mainClass = 'com.microsoft.playwright.CLI' } test { useJUnitPlatform() }Kotlin(build.gradle.kts)
plugins { application id("java") } repositories { mavenCentral() } dependencies { implementation("com.microsoft.playwright:playwright:%%VERSION%%") } application { mainClass.set("org.example.App") } // Usage: ./gradlew playwright --args="help" tasks.register<JavaExec>("playwright") { classpath(sourceSets["test"].runtimeClasspath) mainClass.set("com.microsoft.playwright.CLI") } tasks.test { useJUnitPlatform() testLogging { events("passed", "skipped", "failed") } }两个脚本的要点:
application插件让普通 Java 主程序可以直接./gradlew run运行;test { useJUnitPlatform() }声明使用 JUnit 5 平台执行测试;- 关键在自定义的
playwright任务:它是一个JavaExec任务,mainClass指向com.microsoft.playwright.CLI,即 Playwright Java 驱动的命令行入口(提供codegen、install、screenshot等子命令),classpath 复用测试运行时依赖,这样无需全局安装任何东西即可在构建内运行 Playwright CLI。
常用命令:
# 以应用方式运行主程序 ./gradlew run # 运行测试 ./gradlew test # 调用 Playwright 命令行工具(帮助) ./gradlew playwright --args="help"TestNG 接入
TestNG 版本的接入结构与 JUnit 完全同构,只是钩子注解不同:Playwright与Browser为非静态实例字段,在@BeforeClass创建、@AfterClass销毁;BrowserContext与Page在@BeforeMethod创建、@AfterMethod关闭。完整示例如下:
package org.example; import com.microsoft.playwright.Browser; import com.microsoft.playwright.BrowserContext; import com.microsoft.playwright.Page; import com.microsoft.playwright.Playwright; import org.testng.annotations.*; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; public class TestExample { // Shared between all tests in this class. Playwright playwright; Browser browser; // New instance for each test method. BrowserContext context; Page page; @BeforeClass void launchBrowser() { playwright = Playwright.create(); browser = playwright.chromium().launch(); } @AfterClass void closeBrowser() { playwright.close(); } @BeforeMethod void createContextAndPage() { context = browser.newContext(); page = context.newPage(); } @AfterMethod void closeContext() { context.close(); } @Test void shouldClickButton() { page.navigate("data:text/html,<script>var result;</script><button onclick='result=\"Clicked\"'>Go</button>"); page.locator("button").click(); assertEquals("Clicked", page.evaluate("result")); } @Test void shouldCheckTheBox() { page.setContent("<input id='checkbox' type='checkbox'></input>"); page.locator("input").check(); assertTrue((Boolean) page.evaluate("() => window['checkbox'].checked")); } @Test void shouldSearchWiki() { page.navigate("https://www.wikipedia.org/"); page.locator("input[name=\"search\"]").click(); page.locator("input[name=\"search\"]").fill("playwright"); page.locator("input[name=\"search\"]").press("Enter"); assertEquals("https://en.wikipedia.org/wiki/Playwright", page.url()); } }注意与 JUnit 静态版本的差异:TestNG 示例中Playwright/Browser是实例字段(TestNG 默认整个测试类共享一个实例),而 JUnit 版本因默认每方法新建实例,被迫使用static字段。两者的隔离效果完全一致——浏览器类级共享、上下文方法级隔离。
与其他 Java 文档的衔接
Playwright Java 支持任意测试框架,官方在 Supported languages 中说明 Java 版本可自由选择 JUnit 或 TestNG。围绕本文主题的延伸阅读(均在本仓库文档中):
- Running and debugging tests:单测/多测运行、有头模式(
setHeadless(false))、调试; - Writing tests:Web-first 断言(
assertThat)、Locator 与 auto-wait 机制; - JUnit (experimental):
@UsePlaywrightfixture 化集成、OptionsFactory自定义 launch/context 选项; - Multithreading:同步 API 的事件循环机制,以及
Page.waitForTimeout()与Thread.sleep()的行为差异——并行场景下尤其要注意,Thread.sleep()期间浏览器事件不会被分发。
小结
将 Playwright 接入 Java 测试运行器的标准模式可以归纳为三条规则:
- 生命周期分层:
Playwright/Browser类级(或线程级)共享,BrowserContext/Page方法级新建并关闭,用测试框架的 before/after 钩子表达; - 并行安全边界:Playwright Java 非线程安全,并行时让每个测试类(或线程)拥有独立 Playwright 实例,配合 JUnit 5 的"类间 concurrent、类内 same_thread"配置实现安全提速;
- 构建集成:Gradle 中引入 Maven Central 依赖并注册
com.microsoft.playwright.CLI的JavaExec任务,即可在构建内同时运行测试与 Playwright 命令行工具。
【免费下载链接】playwrightPlaywright is a framework for Web Testing and Automation. It allows testing Chromium, Firefox and WebKit with a single API.项目地址: https://gitcode.com/GitHub_Trending/pl/playwright
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考