[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill-85047fa4-8385-4545-89a7-5d0f47da83af":3,"$fpl8LRt8QS9fhD2fS07GcEmQsBME74cK4-CJ_VPAKMqo":43},{"id":4,"title":5,"description":6,"categoryId":7,"moduleId":8,"tags":9,"prompt":10,"icon":11,"source":12,"sourceUrl":13,"authorId":14,"authorName":15,"isPublic":16,"stars":17,"runs":18,"createdAt":19,"updatedAt":19,"module":20,"category":27,"packages":34},"85047fa4-8385-4545-89a7-5d0f47da83af","playwright-java","搭建、编写、调试和增强使用页面对象模型、JUnit 5、Allure 报告和并行执行的企业级 Playwright E2E 测试的 Java 代码。","cat_coding_review","mod_coding","sickn33,coding","---\nname: playwright-java\ndescription: \"Scaffold, write, debug, and enhance enterprise-grade Playwright E2E tests in Java using Page Object Model, JUnit 5, Allure reporting, and parallel execution.\"\ncategory: test-automation\nrisk: safe\nsource: community\ndate_added: \"2025-03-08\"\nauthor: amalsam18\ntags: [playwright, java, e2e-testing, junit5, page-object-model, allure, selenium-alternative]\ntools: [claude, cursor,antigravity]\n---\n\n# Playwright Java – Advanced Test Automation\n\n## Overview\n\nThis skill produces production-quality, enterprise-grade Playwright Java test code.\nIt enforces the Page Object Model (POM), strict locator strategies, thread-safe parallel\nexecution, and full Allure reporting integration. Targets Java 17+ and Playwright 1.44+.\n\nSupporting reference files are available for deeper topics:\n\n| Topic | File |\n|-------|------|\n| Maven POM, ConfigReader, Docker\u002FCI setup | `references\u002Fconfig.md` |\n| Component pattern, dropdowns, uploads, waits | `references\u002Fpage-objects.md` |\n| Full assertion API, soft assertions, visual testing | `references\u002Fassertions.md` |\n| Fixtures, test data factory, auth state, retry | `references\u002Ffixtures.md` |\n| Drop-in base class templates | `templates\u002FBaseTest.java`, `templates\u002FBasePage.java` |\n\n---\n\n## When to Use This Skill\n\n- Use when scaffolding a new Playwright Java project from scratch\n- Use when writing Page Object classes or JUnit 5 test classes\n- Use when the user asks about cross-browser testing, parallel execution, or Allure reports\n- Use when fixing flaky tests or replacing `Thread.sleep()` with proper waits\n- Use when setting up Playwright in CI\u002FCD pipelines (GitHub Actions, Jenkins, Docker)\n- Use when combining API calls and UI assertions in a single test (hybrid testing)\n- Use when the user mentions \"POM pattern\", \"BrowserContext\", \"Playwright fixtures\", or \"traces\"\n\n---\n\n## How It Works\n\n### Step 1: Decide the Approach\n\nUse this matrix to pick the right pattern before writing any code:\n\n| User Request | Approach |\n|---|---|\n| New project from scratch | Full scaffold — see `references\u002Fconfig.md` |\n| Single feature test | POM page class + JUnit5 test class |\n| API + UI hybrid | `APIRequestContext` alongside `Page` |\n| Cross-browser | `@MethodSource` parameterized over browser names |\n| Flaky test fix | Replace `sleep` with `waitFor` \u002F `waitForResponse` |\n| CI integration | `playwright install --with-deps` in pipeline |\n| Parallel execution | `junit-platform.properties` + `ThreadLocal` |\n| Rich reporting | Allure + Playwright trace + video recording |\n\n---\n\n### Step 2: Scaffold the Project Structure\n\nAlways use this layout when creating a new project:\n\n```\nsrc\u002F\n├── test\u002F\n│   ├── java\u002Fcom\u002Fcompany\u002Ftests\u002F\n│   │   ├── base\u002F\n│   │   │   ├── BaseTest.java        ← templates\u002FBaseTest.java\n│   │   │   └── BasePage.java        ← templates\u002FBasePage.java\n│   │   ├── pages\u002F\n│   │   │   └── LoginPage.java\n│   │   ├── tests\u002F\n│   │   │   └── LoginTest.java\n│   │   ├── utils\u002F\n│   │   │   ├── TestDataFactory.java\n│   │   │   └── WaitUtils.java\n│   │   └── config\u002F\n│   │       └── ConfigReader.java\n│   └── resources\u002F\n│       ├── test.properties\n│       ├── junit-platform.properties\n│       └── testdata\u002Fusers.json\npom.xml\n```\n\n---\n\n### Step 3: Set Up Thread-Safe BaseTest\n\n```java\npublic class BaseTest {\n    protected static ThreadLocal\u003CPlaywright>     playwrightTL = new ThreadLocal\u003C>();\n    protected static ThreadLocal\u003CBrowser>        browserTL    = new ThreadLocal\u003C>();\n    protected static ThreadLocal\u003CBrowserContext> contextTL    = new ThreadLocal\u003C>();\n    protected static ThreadLocal\u003CPage>           pageTL       = new ThreadLocal\u003C>();\n\n    protected Page page() { return pageTL.get(); }\n\n    @BeforeEach\n    void setUp() {\n        Playwright playwright = Playwright.create();\n        playwrightTL.set(playwright);\n\n        Browser browser = resolveBrowser(playwright).launch(\n            new BrowserType.LaunchOptions()\n                .setHeadless(ConfigReader.isHeadless()));\n        browserTL.set(browser);\n\n        BrowserContext context = browser.newContext(new Browser.NewContextOptions()\n            .setViewportSize(1920, 1080)\n            .setRecordVideoDir(Paths.get(\"target\u002Fvideos\u002F\"))\n            .setLocale(\"en-US\"));\n        context.tracing().start(new Tracing.StartOptions()\n            .setScreenshots(true).setSnapshots(true));\n        contextTL.set(context);\n        pageTL.set(context.newPage());\n    }\n\n    @AfterEach\n    void tearDown(TestInfo testInfo) {\n        String name = testInfo.getDisplayName().replaceAll(\"[^a-zA-Z0-9]\", \"_\");\n        contextTL.get().tracing().stop(new Tracing.StopOptions()\n            .setPath(Paths.get(\"target\u002Ftraces\u002F\" + name + \".zip\")));\n        pageTL.get().close();\n        contextTL.get().close();\n        browserTL.get().close();\n        playwrightTL.get().close();\n    }\n\n    private BrowserType resolveBrowser(Playwright pw) {\n        return switch (System.getProperty(\"browser\", \"chromium\").toLowerCase()) {\n            case \"firefox\" -> pw.firefox();\n            case \"webkit\"  -> pw.webkit();\n            default        -> pw.chromium();\n        };\n    }\n}\n```\n\n---\n\n### Step 4: Build Page Object Classes\n\n```java\npublic class LoginPage extends BasePage {\n\n    \u002F\u002F Declare ALL locators as fields — never inline in action methods\n    private final Locator emailInput;\n    private final Locator passwordInput;\n    private final Locator loginButton;\n    private final Locator errorMessage;\n\n    public LoginPage(Page page) {\n        super(page);\n        emailInput    = page.getByLabel(\"Email address\");\n        passwordInput = page.getByLabel(\"Password\");\n        loginButton   = page.getByRole(AriaRole.BUTTON,\n                            new Page.GetByRoleOptions().setName(\"Sign in\"));\n        errorMessage  = page.getByTestId(\"login-error\");\n    }\n\n    @Override protected String getUrl() { return \"\u002Flogin\"; }\n\n    \u002F\u002F Navigation methods return the next Page Object — enables fluent chaining\n    public DashboardPage loginAs(String email, String password) {\n        fill(emailInput, email);\n        fill(passwordInput, password);\n        clickAndWaitForNav(loginButton);\n        return new DashboardPage(page);\n    }\n\n    public LoginPage loginExpectingError(String email, String password) {\n        fill(emailInput, email);\n        fill(passwordInput, password);\n        loginButton.click();\n        errorMessage.waitFor();\n        return this;\n    }\n\n    public String getErrorMessage() { return errorMessage.textContent(); }\n}\n```\n\n---\n\n### Step 5: Write Tests with Allure Annotations\n\n```java\n@ExtendWith(AllureJunit5.class)\nclass LoginTest extends BaseTest {\n\n    private LoginPage loginPage;\n\n    @BeforeEach\n    void openLoginPage() {\n        loginPage = new LoginPage(page());\n        loginPage.navigate();\n    }\n\n    @Test\n    @Severity(SeverityLevel.BLOCKER)\n    @DisplayName(\"Valid credentials redirect to dashboard\")\n    void shouldLoginWithValidCredentials() {\n        User user = TestDataFactory.getDefaultUser();\n        DashboardPage dash = loginPage.loginAs(user.email(), user.password());\n\n        assertThat(page()).hasURL(Pattern.compile(\".*\u002Fdashboard\"));\n        assertThat(dash.getWelcomeBanner()).containsText(\"Welcome, \" + user.firstName());\n    }\n\n    @Test\n    void shouldShowErrorOnInvalidCredentials() {\n        loginPage.loginExpectingError(\"bad@test.com\", \"wrongpass\");\n\n        SoftAssertions softly = new SoftAssertions();\n        softly.assertThat(loginPage.getErrorMessage()).contains(\"Invalid email or password\");\n        softly.assertThat(page()).hasURL(Pattern.compile(\".*\u002Flogin\"));\n        softly.assertAll();\n    }\n\n    @ParameterizedTest\n    @MethodSource(\"provideInvalidCredentials\")\n    void shouldRejectInvalidCredentials(String email, String password, String expectedError) {\n        loginPage.loginExpectingError(email, password);\n        assertThat(loginPage.getErrorMessage()).containsText(expectedError);\n    }\n\n    static Stream\u003CArguments> provideInvalidCredentials() {\n        return Stream.of(\n            Arguments.of(\"\", \"password123\", \"Email is required\"),\n            Arguments.of(\"user@test.com\", \"\", \"Password is required\"),\n            Arguments.of(\"notanemail\", \"pass\", \"Invalid email format\")\n        );\n    }\n}\n```\n\n---\n\n## Examples\n\n### Example 1: API + UI Hybrid Test\n\n```java\n@Test\nvoid shouldDisplayNewlyCreatedOrder() {\n    \u002F\u002F Arrange via API — faster than navigating through UI\n    APIRequestContext api = page().context().request();\n    APIResponse response = api.post(\"\u002Fapi\u002Forders\",\n        RequestOptions.create()\n            .setHeader(\"Authorization\", \"Bearer \" + authToken)\n            .setData(Map.of(\"productId\", \"SKU-001\", \"quantity\", 2)));\n    assertThat(response).isOK();\n\n    String orderId = new JsonParser().parse(response.text())\n        .getAsJsonObject().get(\"id\").getAsString();\n\n    OrdersPage orders = new OrdersPage(page());\n    orders.navigate();\n    assertThat(orders.getOrderRowById(orderId)).isVisible();\n}\n```\n\n### Example 2: Network Mocking\n\n```java\n@Test\nvoid shouldHandleApiFailureGracefully() {\n    page().route(\"**\u002Fapi\u002Fproducts\", route -> route.fulfill(\n        new Route.FulfillOptions()\n            .setStatus(503)\n            .setBody(\"{\\\"error\\\":\\\"Service Unavailable\\\"}\")\n            .setContentType(\"application\u002Fjson\")));\n\n    ProductsPage products = new ProductsPage(page());\n    products.navigate();\n\n    assertThat(products.getErrorBanner())\n        .hasText(\"We're having trouble loading products. Please try again.\");\n}\n```\n\n### Example 3: Parallel Cross-Browser Test\n\n```java\n@ParameterizedTest\n@MethodSource(\"browsers\")\nvoid shouldRenderCheckoutOnAllBrowsers(String browserName) {\n    System.setProperty(\"browser\", browserName);\n    new CheckoutPage(page()).navigate();\n    assertThat(page().locator(\".checkout-form\")).isVisible();\n}\n\nstatic Stream\u003CString> browsers() {\n    return Stream.of(\"chromium\", \"firefox\", \"webkit\");\n}\n```\n\n### Example 4: Parallel Execution Config\n\n```properties\n# src\u002Ftest\u002Fresources\u002Fjunit-platform.properties\njunit.jupiter.execution.parallel.enabled=true\njunit.jupiter.execution.parallel.mode.default=concurrent\njunit.jupiter.execution.parallel.config.strategy=fixed\njunit.jupiter.execution.parallel.config.fixed.parallelism=4\n```\n\n### Example 5: GitHub Actions CI Pipeline\n\n```yaml\n- name: Install Playwright browsers\n  run: mvn exec:java -e -Dexec.mainClass=com.microsoft.playwright.CLI -Dexec.args=\"install --with-deps\"\n\n- name: Run tests\n  run: mvn test -Dbrowser=${{ matrix.browser }} -Dheadless=true\n\n- name: Upload traces on failure\n  uses: actions\u002Fupload-artifact@v4\n  if: failure()\n  with:\n    name: playwright-traces\n    path: target\u002Ftraces\u002F\n\n- name: Upload Allure results\n  uses: actions\u002Fupload-artifact@v4\n  if: always()\n  with:\n    name: allure-results\n    path: target\u002Fallure-results\u002F\n```\n\n---\n\n## Best Practices\n\n- ✅ Use `ThreadLocal\u003CPage>` for every parallel-safe test suite\n- ✅ Declare all `Locator` fields at the top of the Page Object class\n- ✅ Return the next Page Object from navigation methods (fluent chaining)\n- ✅ Use `assertThat(locator)` — it auto-retries until timeout\n- ✅ Use `getByRole`, `getByLabel`, `getByTestId` as first-choice locators\n- ✅ Start tracing in `@BeforeEach` and stop with a file path in `@AfterEach`\n- ✅ Use `SoftAssertions` when validating multiple fields on a single page\n- ✅ Set up saved auth state (`storageState`) to skip login across test classes\n- ❌ Never use `Thread.sleep()` — replace with `waitFor()` or `waitForResponse()`\n- ❌ Never hardcode base URLs — always use `ConfigReader.getBaseUrl()`\n- ❌ Never create a `Playwright` instance inside a Page Object\n- ❌ Never use XPath for dynamic or frequently changing elements\n\n---\n\n## Common Pitfalls\n\n- **Problem:** Tests fail randomly in parallel mode\n  **Solution:** Ensure every test creates its own `Playwright → Browser → BrowserContext → Page` chain via `ThreadLocal`. Never share a `Page` across threads.\n\n- **Problem:** `assertThat(locator).isVisible()` times out even when the element appears\n  **Solution:** Increase timeout with `.setTimeout(10_000)` or raise `context.setDefaultTimeout()` in `BaseTest`.\n\n- **Problem:** `Thread.sleep(2000)` was added but tests are still flaky\n  **Solution:** Replace with `page.waitForResponse(\"**\u002Fapi\u002Fendpoint\", () -> action())` or `assertThat(locator).hasText(\"Done\")` which polls automatically.\n\n- **Problem:** Playwright trace zip is empty or missing\n  **Solution:** Ensure `tracing().start()` is called before test actions and `tracing().stop()` is in `@AfterEach` — not `@AfterAll`.\n\n- **Problem:** Allure report is blank or missing steps\n  **Solution:** Add the AspectJ agent to `maven-surefire-plugin` `\u003CargLine>` in `pom.xml` — see `references\u002Fconfig.md` for the exact snippet.\n\n- **Problem:** `storageState` auth file is stale and tests redirect to login\n  **Solution:** Re-run `AuthSetup` to regenerate `target\u002Fauth\u002Fuser-state.json` before the suite, or add a `@BeforeAll` that conditionally refreshes it.\n\n---\n\n## Related Skills\n\n- `@rest-assured-java` — Use for pure API test suites without any UI interaction\n- `@selenium-java` — Legacy alternative; prefer Playwright for all new projects\n- `@allure-reporting` — Deep-dive into Allure annotations, categories, and history trends\n- `@testcontainers-java` — Use alongside this skill when tests need a live database or service\n- `@github-actions-ci` — For building complete multi-browser matrix CI pipelines\n\n## Limitations\n- Use this skill only when the task clearly matches the scope described above.\n- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.\n- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.\n","","imported","https:\u002F\u002Fgithub.com\u002Fsickn33\u002Fantigravity-awesome-skills","user_system_seed","SkillOPIC",true,197,2040,"2026-05-16 13:34:06",{"id":8,"name":21,"slug":22,"icon":23,"description":24,"sort":25,"createdAt":26},"编程开发","coding","mdi-code-braces","代码生成、调试、审查，提升开发效率",2,"2026-05-16 12:53:40",{"id":7,"name":28,"slug":29,"icon":30,"description":31,"moduleId":8,"sort":32,"skillCount":33,"createdAt":26},"代码审查","review","mdi-magnify-scan","代码质量分析、安全审查",4,145,[35],{"id":36,"skillId":4,"version":37,"fileName":38,"fileSize":39,"filePath":40,"fileHash":41,"manifest":42,"createdAt":19},"a9d79236-98b0-4d49-859b-b705336d9748","1.0.0","playwright-java.zip",17010,"uploads\u002Fskills\u002F85047fa4-8385-4545-89a7-5d0f47da83af\u002Fplaywright-java.zip","5889e14bc9600e82e961fabfa58016ebf05ff9d4981bc980b9b07f97a8bbe15b","[{\"path\":\"BasePage.java\",\"isDirectory\":false,\"size\":4830},{\"path\":\"BaseTest.java\",\"isDirectory\":false,\"size\":4526},{\"path\":\"SKILL.md\",\"isDirectory\":false,\"size\":13974},{\"path\":\"assertions.md\",\"isDirectory\":false,\"size\":4226},{\"path\":\"config.md\",\"isDirectory\":false,\"size\":6651},{\"path\":\"fixtures.md\",\"isDirectory\":false,\"size\":7755},{\"path\":\"page-objects.md\",\"isDirectory\":false,\"size\":6104}]",{"code":44,"message":45,"data":46},200,"success",{"items":47,"stats":48,"page":51},[],{"averageRating":49,"totalRatings":49,"ratingCounts":50},0,[49,49,49,49,49],{"limit":52,"offset":49,"hasMore":53,"nextOffset":52,"ratedOnly":16},15,false]