Playwright java e2e
Skill findscripter/everything-skills/02-engineering/playwright-java-e2e
类书式 AI Agent 技能大典 · 精选/中文化/互见成网的 500+ 开源技能,可作为 Claude Code 插件市场一键安装。A curated, cross-referenced encyclopedia of 500+ open-source agent skills.
npx -y skills add findscripter/everything-skills --skill playwright-java-e2eAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
当用 Java 写浏览器端到端(E2E)测试、需要并行/跨浏览器/可追溯报告时使用;做基于页面对象模型(POM)+JUnit 5+Allure 的可落地测试脚手架与页面类/用例代码;不适用于纯 API 测试(用 REST Assured)或单元测试。触发词:Playwright Java、POM、JUnit5、Allure、跨浏览器、ThreadLocal、trace、并行测试
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
11.1 KB, as published. Nobody here has run it
何时使用
适用:
- 从零搭建 Playwright Java 项目脚手架(Java 17+ / Playwright 1.44+)。
- 编写页面对象(Page Object)类或 JUnit 5 用例类。
- 跨浏览器测试、并行执行、Allure 报告、trace/视频取证。
- 修复 flaky 测试:把
Thread.sleep()换成显式等待。 - 在 CI/CD(GitHub Actions、Jenkins、Docker)中接入 Playwright。
- 单个用例内混合 API 调用与 UI 断言(hybrid 测试)。
- 用户提到「POM」「BrowserContext」「Playwright fixtures」「traces」。
不该用(负边界):
- 纯 API 测试套件、无任何 UI 交互 —— 用 REST Assured。
- 普通单元测试 / 组件测试 —— 用 JUnit + Mockito,无需启动浏览器。
- 需求、权限、成功标准不清时,先停下来澄清,别硬写。
步骤 / 指令
- 先选方案,再写代码。按下表对号入座:
| 用户诉求 | 方案 |
|---|---|
| 从零新项目 | 完整脚手架(pom.xml + 目录结构) |
| 单个功能测试 | 一个 POM 页面类 + 一个 JUnit5 用例类 |
| API+UI 混合 | APIRequestContext 与 Page 并用 |
| 跨浏览器 | @MethodSource 参数化浏览器名 |
| 修 flaky | sleep 换成 waitFor / waitForResponse |
| CI 接入 | 流水线里跑 install --with-deps |
| 并行执行 | junit-platform.properties + ThreadLocal |
| 富报告 | Allure + trace + 录屏 |
- 固定目录结构(新项目一律照此布局):
src/test/java/com/company/tests/
├── base/ BaseTest.java, BasePage.java
├── pages/ LoginPage.java
├── tests/ LoginTest.java
├── utils/ TestDataFactory.java, WaitUtils.java
└── config/ ConfigReader.java
src/test/resources/
├── test.properties
├── junit-platform.properties
└── testdata/users.json
pom.xml
- 搭线程安全的
BaseTest:用ThreadLocal持有Playwright → Browser → BrowserContext → Page整条链;@BeforeEach建链 + 开 tracing,@AfterEach停 tracing 落盘后逐层关闭。浏览器由-Dbrowser系统属性切换:
public class BaseTest {
protected static ThreadLocal<Playwright> playwrightTL = new ThreadLocal<>();
protected static ThreadLocal<Browser> browserTL = new ThreadLocal<>();
protected static ThreadLocal<BrowserContext> contextTL = new ThreadLocal<>();
protected static ThreadLocal<Page> pageTL = new ThreadLocal<>();
protected Page page() { return pageTL.get(); }
@BeforeEach
void setUp() {
Playwright pw = Playwright.create();
playwrightTL.set(pw);
Browser browser = resolveBrowser(pw).launch(
new BrowserType.LaunchOptions().setHeadless(ConfigReader.isHeadless()));
browserTL.set(browser);
BrowserContext ctx = browser.newContext(new Browser.NewContextOptions()
.setViewportSize(1920, 1080)
.setRecordVideoDir(Paths.get("target/videos/"))
.setLocale("en-US"));
ctx.tracing().start(new Tracing.StartOptions().setScreenshots(true).setSnapshots(true));
contextTL.set(ctx);
pageTL.set(ctx.newPage());
}
@AfterEach
void tearDown(TestInfo info) {
String name = info.getDisplayName().replaceAll("[^a-zA-Z0-9]", "_");
contextTL.get().tracing().stop(new Tracing.StopOptions()
.setPath(Paths.get("target/traces/" + name + ".zip")));
pageTL.get().close(); contextTL.get().close();
browserTL.get().close(); playwrightTL.get().close();
}
private BrowserType resolveBrowser(Playwright pw) {
return switch (System.getProperty("browser", "chromium").toLowerCase()) {
case "firefox" -> pw.firefox();
case "webkit" -> pw.webkit();
default -> pw.chromium();
};
}
}
- 写页面对象类:所有
Locator声明为字段(不在动作方法里内联),优先getByRole / getByLabel / getByTestId;导航方法返回下一个页面对象以支持链式调用:
public class LoginPage extends BasePage {
private final Locator emailInput, passwordInput, loginButton, errorMessage;
public LoginPage(Page page) {
super(page);
emailInput = page.getByLabel("Email address");
passwordInput = page.getByLabel("Password");
loginButton = page.getByRole(AriaRole.BUTTON,
new Page.GetByRoleOptions().setName("Sign in"));
errorMessage = page.getByTestId("login-error");
}
@Override protected String getUrl() { return "/login"; }
public DashboardPage loginAs(String email, String password) {
fill(emailInput, email); fill(passwordInput, password);
clickAndWaitForNav(loginButton);
return new DashboardPage(page);
}
public LoginPage loginExpectingError(String email, String password) {
fill(emailInput, email); fill(passwordInput, password);
loginButton.click(); errorMessage.waitFor();
return this;
}
public String getErrorMessage() { return errorMessage.textContent(); }
}
- 写带 Allure 注解的用例:
@ExtendWith(AllureJunit5.class),用@Severity / @DisplayName标注,多字段校验用SoftAssertions,多场景用@ParameterizedTest + @MethodSource:
@ExtendWith(AllureJunit5.class)
class LoginTest extends BaseTest {
private LoginPage loginPage;
@BeforeEach void open() { loginPage = new LoginPage(page()); loginPage.navigate(); }
@Test @Severity(SeverityLevel.BLOCKER)
@DisplayName("有效凭据应跳转到 dashboard")
void shouldLoginWithValidCredentials() {
User u = TestDataFactory.getDefaultUser();
DashboardPage dash = loginPage.loginAs(u.email(), u.password());
assertThat(page()).hasURL(Pattern.compile(".*/dashboard"));
assertThat(dash.getWelcomeBanner()).containsText("Welcome, " + u.firstName());
}
}
- 并行执行配置
src/test/resources/junit-platform.properties:
junit.jupiter.execution.parallel.enabled=true
junit.jupiter.execution.parallel.mode.default=concurrent
junit.jupiter.execution.parallel.config.strategy=fixed
junit.jupiter.execution.parallel.config.fixed.parallelism=4
示例
示例一 · API+UI 混合(用 API 准备数据,比走 UI 快):
APIRequestContext api = page().context().request();
APIResponse resp = api.post("/api/orders", RequestOptions.create()
.setHeader("Authorization", "Bearer " + authToken)
.setData(Map.of("productId", "SKU-001", "quantity", 2)));
assertThat(resp).isOK();
String orderId = new JsonParser().parse(resp.text())
.getAsJsonObject().get("id").getAsString();
OrdersPage orders = new OrdersPage(page()); orders.navigate();
assertThat(orders.getOrderRowById(orderId)).isVisible();
示例二 · 网络打桩(mock 后端故障):
page().route("**/api/products", route -> route.fulfill(new Route.FulfillOptions()
.setStatus(503).setBody("{\"error\":\"Service Unavailable\"}")
.setContentType("application/json")));
new ProductsPage(page()).navigate();
assertThat(products.getErrorBanner())
.hasText("We're having trouble loading products. Please try again.");
示例三 · 跨浏览器并行:
@ParameterizedTest @MethodSource("browsers")
void shouldRenderCheckoutOnAllBrowsers(String browserName) {
System.setProperty("browser", browserName);
new CheckoutPage(page()).navigate();
assertThat(page().locator(".checkout-form")).isVisible();
}
static Stream<String> browsers() { return Stream.of("chromium", "firefox", "webkit"); }
示例四 · GitHub Actions 流水线关键步骤:
- name: Install Playwright browsers
run: mvn exec:java -e -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args="install --with-deps"
- name: Run tests
run: mvn test -Dbrowser=${{ matrix.browser }} -Dheadless=true
- name: Upload traces on failure
uses: actions/upload-artifact@v4
if: failure()
with: { name: playwright-traces, path: target/traces/ }
- name: Upload Allure results
uses: actions/upload-artifact@v4
if: always()
with: { name: allure-results, path: target/allure-results/ }
注意事项
要做(✅):
- 并行套件每个用例都用
ThreadLocal<Page>,绝不跨线程共享Page。 Locator字段全部声明在页面类顶部。- 导航方法返回下一个页面对象(链式调用)。
- 用
assertThat(locator)—— 它会自动重试直到超时。 - 优先
getByRole / getByLabel / getByTestId定位。 - tracing 在
@BeforeEach开、@AfterEach停并落盘(不是@AfterAll)。 - 同页多字段校验用
SoftAssertions。 - 跨类复用登录态:保存
storageState跳过重复登录。
不要做(❌):
- 不用
Thread.sleep(),换waitFor()/waitForResponse()。 - 不硬编码 baseURL,统一
ConfigReader.getBaseUrl()。 - 不在页面对象内部
Playwright.create()。 - 动态/频繁变更的元素不用 XPath。
常见坑:
- 并行随机失败 → 检查是否每个用例独立建
Playwright→Browser→Context→Page链。 isVisible()超时 →.setTimeout(10_000)或在BaseTest抬context.setDefaultTimeout()。- 加了
sleep仍 flaky → 换waitForResponse("**/api/endpoint", () -> action())或断言自动轮询。 - trace zip 为空 → 确认
start()在动作前、stop()在@AfterEach。 - Allure 报告空白/缺步骤 → 在
maven-surefire-plugin的<argLine>里加 AspectJ agent。 storageState过期跳登录页 → 重跑AuthSetup重生成target/auth/user-state.json。
互见
- REST Assured(Java)—— 纯 API 测试套件,无 UI 交互时用。
- Selenium(Java)—— 旧方案;新项目一律优先 Playwright。
- Allure 报告 —— 注解、分类、历史趋势深入。
- Testcontainers(Java)—— 测试需要真实数据库/服务时配合本技能。
- GitHub Actions CI —— 构建多浏览器矩阵流水线。
采编自 sickn33/antigravity-awesome-skills(MIT 许可),原作者 amalsam18,已按本仓库 SCHEMA 适配重写。