本文へ移動
cccskills
無料GitHub で公開

web-testing-playwright-e2e

Playwright E2E testing patterns - test structure, Page Object Model, locator strategies, assertions, network mocking, visual regression, parallel execution, fixtures, and configuration

インストール方法を見る

含まれるファイル(9)

  • SKILL.md11.2 KB
  • examples/advanced-features.md5.0 KB
  • examples/api-mocking.md1.4 KB
  • examples/core.md9.7 KB
  • examples/fixtures.md3.2 KB
  • examples/page-objects.md2.2 KB
  • examples/visual-testing.md2.3 KB
  • metadata.yaml338 B
  • reference.md12.3 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

Playwright E2E Testing Patterns

Quick Guide: Playwright drives a real browser through complete user journeys. Locators are lazy queries re-resolved on every use and expect(locator) matchers retry until they pass, so a correct test needs no sleeps and no stale-handle handling. Each test gets its own browser context; shared setup arrives through fixtures rather than module-level variables, because tests run in parallel workers.

Detailed Resources:


<critical_requirements>

Before writing Playwright code

Reach for getByRole() first. It selects the element the way a user and a screen reader find it, so it survives markup changes and validates the accessible name as a side effect.

Assert with expect(locator) matchers. They retry until the condition holds or the timeout expires, which is what removes the reason to sleep at all — a fixed wait is either too short or too slow.

Register a route before the navigation that triggers the request. Routes are attached to the context and only affect requests made after they are set, so a page.route() after page.goto() never sees the call it was meant to intercept.

Give each test its own state through a fixture. Workers run in parallel and a module-level variable is shared inside one worker and absent from the next, so setup that lives in a fixture is the only setup that behaves the same in both.

</critical_requirements>


Auto-detection: Playwright, @playwright/test, playwright.config, page.goto, test.describe, test.extend, expect(page), getByRole, getByLabel, getByTestId, toBeVisible, toHaveURL, toHaveScreenshot, toMatchAriaSnapshot, page.route, route.fulfill, page.clock, toPass, devices

Applies to:

  • User journeys through a real browser, across pages and origins
  • Locator strategy, web-first assertions and soft assertions
  • Intercepting, stubbing and modifying network traffic for a test
  • Screenshot comparison and ARIA-tree assertions
  • Fixtures, worker scoping, parallelism and the test config

Handled elsewhere:

  • Unit-level checks on pure functions — a browser adds nothing to an input/output assertion
  • Mounting a single component and poking at its props — a different granularity, settled by whatever owns component testing
  • The application's own accessibility remediation — this skill asserts the accessible tree, it does not decide what that tree should be
  • Baseline custody and review workflow for screenshots across a whole suite

<philosophy>

A locator is a query, not a handle. page.getByRole("button") resolves nothing until it is used, and resolves again on every use — so there is no stale element to guard against, and no reason to capture one early. Assertions inherit the same model: expect(locator).toBeVisible() polls until the timeout.

Everything else follows: the sleep, the CSS selector and the shared variable each throw away something the model already gives for free.

</philosophy>

<decision_framework>

Hooks or fixtures for setup:

  • Used by one describe block → beforeEach, which keeps the setup where it is read.
  • Used across files, or needing teardown → a fixture, which carries both halves and composes.
  • Needed once for the whole run → globalSetup in the config. beforeAll runs once per worker, not once per run.
  • Expensive and safe to share within a worker → a worker-scoped fixture.

Stub the network or hit it:

  • Third-party API → stub it. Rate limits, cost and their downtime are not your test's subject.
  • Your own API, testing an error path → stub it; some statuses cannot be provoked on demand.
  • Your own API, testing that the integration is correct → let it through, against a test database, and keep one such test per feature.
  • Your own API, testing UI behaviour only → stub it, for speed and determinism.

</decision_framework>


<patterns>

Core patterns

Pattern 1: Test structure

test.describe groups, beforeEach handles the navigation every test in the group repeats.

test.describe("Login", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/login");
  });

  test("successful login lands on the dashboard", async ({ page }) => {
    await page.getByLabel(/email/i).fill(VALID_EMAIL);
    await page.getByRole("button", { name: /sign in/i }).click();
    await expect(page).toHaveURL("/dashboard");
  });
});

Full code: examples/core.md

Pattern 2: Page Object Model

Locators in the constructor, domain methods on the class. Worth it once several tests touch the same page; inline locators stay clearer for a one-off.

export class LoginPage {
  readonly emailInput: Locator;
  readonly signInButton: Locator;

  constructor(page: Page) {
    this.emailInput = page.getByLabel(/email/i);
    this.signInButton = page.getByRole("button", { name: /sign in/i });
  }

  async login(email: string, password: string) {
    await this.emailInput.fill(email);
    // ...
  }
}

Full code: examples/core.md — inheritance in examples/page-objects.md

Pattern 3: Locator strategy

Prefer the query that names what the element is; fall back to a test id only where no role or label exists.

await page.getByRole("button", { name: /submit/i });
await page.getByLabel(/email address/i);
await page.getByTestId("user-avatar"); // no semantic role available

// Narrow within a list instead of writing a fragile selector
await page
  .getByRole("listitem")
  .filter({ hasText: "Product A" })
  .getByRole("button", { name: /add to cart/i })
  .click();

// Exclude and combine (v1.33+)
page.getByRole("listitem").filter({ hasNot: page.getByText("Out of stock") });
page.getByRole("button").and(page.getByTitle("Subscribe"));

Priority table and role mappings: reference.md

Pattern 4: Web-first assertions

Every expect(locator) matcher retries, including negated ones.

await expect(page.getByText("Welcome")).toBeVisible();
await expect(page).toHaveURL(/\/dashboard/);
await expect(page.getByRole("progressbar")).not.toBeVisible();

// Soft assertions collect every failure in one run instead of stopping at the first
await expect.soft(page.getByTestId("avatar")).toBeVisible();
await expect.soft(page.getByText("Premium")).toBeVisible();

Full assertion table: reference.md

Pattern 5: Network mocking

page.route() intercepts by URL pattern; route.fulfill() answers, route.abort() fails the request.

await page.route("**/api/users", (route) =>
  route.fulfill({
    status: 200,
    contentType: "application/json",
    body: JSON.stringify({ id: "user-123", name: "Jane Doe" }),
  }),
);

await page.route("**/api/users", (route) => route.abort("failed")); // network failure

Full code: examples/core.md — modifying a real response in examples/api-mocking.md

Pattern 6: Visual regression

toHaveScreenshot() writes a baseline on first run and compares afterwards. Mask what changes on its own.

await expect(page).toHaveScreenshot("dashboard.png", {
  mask: [page.getByTestId("current-time")],
  animations: "disabled",
});

Full code: examples/visual-testing.md

Pattern 7: Custom fixtures

base.extend adds fixtures for page objects and setup. An auto fixture runs without being named by the test.

export const test = base.extend<{ loginPage: LoginPage; authenticated: void }>({
  loginPage: async ({ page }, use) => {
    await use(new LoginPage(page));
  },
  authenticated: [
    async ({ context }, use) => {
      await context.addCookies([SESSION_COOKIE]);
      await use();
      await context.clearCookies();
    },
    { auto: true },
  ],
});

Full code: examples/core.md — composition and seeding in examples/fixtures.md

Pattern 8: Clock control (v1.45+)

Move time instead of waiting for it. clock.install() comes before every other clock call.

await page.clock.install({ time: new Date("2024-02-02T08:00:00") });
await page.goto("/dashboard");
await page.clock.fastForward("25:00");
await expect(page.getByText(/session expires/i)).toBeVisible();

Full code: examples/advanced-features.md

Pattern 9: ARIA snapshots (v1.49+)

Assert the accessible tree as a whole, in one readable block.

await expect(page.getByRole("navigation")).toMatchAriaSnapshot(`
  - navigation:
    - link "Home"
    - link "Products"
`);

Full code: examples/advanced-features.md

</patterns>

<red_flags>

Red flags

Breaks at runtime:

  • page.waitForTimeout() before an assertion — either too short and flaky or too long and slow; the retrying matcher already covers it.
  • A page.route() registered after the navigation that fires the request — the route never matches; set it up before goto, in beforeEach or in a fixture.
  • CSS or id selectors (.btn-primary, #submit-btn) — they change with styling and markup; a role or label query does not.
  • Module-level variables shared between tests — absent in the next worker, and racy inside one; use a fixture.
  • Glob patterns using ? or [] in page.route() — unsupported since v1.52; use a regex.
  • route.continue() overriding the Cookie header — refused since v1.52; use context.addCookies().
  • _react / _vue selector engines — removed in v1.58; use a role query or a test id.

Surprising behaviour:

  • beforeAll runs once per worker, not once per run — one-time setup belongs in globalSetup.
  • toBeVisible() waits for visibility while toBeAttached() only checks the DOM; a hidden element satisfies the second.
  • toBeEditable() throws on a non-editable element since v1.50 rather than returning false.
  • Screenshots differ by OS, browser build and font rendering — compare only against baselines produced in the same environment as CI.
  • A screenshot of unmasked dynamic content (timestamps, ads, avatars) fails on content rather than on layout.
  • Assertions inherit expect.timeout from the config, not the test timeout — a slow page fails the assertion first.

</red_flags>

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Hugging Face Inference SDK patterns for TypeScript/Node.js — InferenceClient setup, chat completion, text generation, streaming, embeddings, image generation, audio transcription, translation, summarization, and Inference Endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

LiteLLM proxy server setup, TypeScript client patterns via OpenAI SDK, model routing, fallbacks, load balancing, spend tracking, virtual keys, and production deployment

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Serverless GPU compute platform for AI model deployment — web endpoints, GPU functions, model serving, and TypeScript client patterns

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Local LLM inference with the Ollama JavaScript client -- chat, streaming, tool calling, vision, embeddings, structured output, model management, and OpenAI-compatible endpoint

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Replicate SDK patterns for TypeScript/Node.js -- client setup, predictions, streaming, webhooks, file handling, model versioning, deployments, and training

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

Together AI SDK patterns for TypeScript — client setup, chat completions, streaming, structured output, function calling, embeddings, image generation, fine-tuning, and OpenAI-compatible endpoints

日本語の概要は準備中です。原文の説明を表示しています。

agents-inc/skills242026年9月8日 更新

agents-inc のスキルをすべて見る

このスキルの問題を報告する