askill
swe-developing-e2e-test-with-playwright

swe-developing-e2e-test-with-playwrightSafety 100Repository

Playwright E2E testing standards from authoritative docs/explanation/software-engineering/automation-testing/tools/playwright/ documentation

7 stars
1.2k downloads
Updated 2/22/2026

Package Files

Loading files...
SKILL.md

Playwright E2E Testing Standards

Purpose

Progressive disclosure of Playwright end-to-end testing standards for agents writing E2E tests.

Authoritative Source: docs/explanation/software-engineering/automation-testing/tools/playwright/README.md

Usage: Auto-loaded for agents when writing Playwright E2E tests. Provides quick reference to test organization, selectors, assertions, page objects, and debugging patterns.

Quick Standards Reference

Test Organization

File Structure: Group tests by feature or page

tests/
├── e2e/
│   ├── auth/
│   │   ├── login.spec.ts
│   │   └── register.spec.ts
│   ├── payments/
│   │   ├── murabaha.spec.ts
│   │   └── zakat.spec.ts
│   └── navigation.spec.ts
├── page-objects/
│   ├── pages/
│   │   ├── LoginPage.ts
│   │   └── DashboardPage.ts
│   └── components/
│       ├── Header.ts
│       └── Sidebar.ts
└── fixtures/
    └── test-data.ts

Naming Conventions:

  • Test files: *.spec.ts (e.g., login.spec.ts)
  • Page objects: PascalCase (e.g., LoginPage.ts)
  • Test descriptions: Behavior-focused (e.g., "successful login redirects to dashboard")

Selectors (Accessibility-First)

Priority Order: Role → Label → Text → TestID → CSS

// ✅ PASS: Accessibility-first selectors
page.getByRole("button", { name: "Submit" }); // Priority 1: Role
page.getByLabel("Email"); // Priority 2: Label
page.getByText("Welcome"); // Priority 3: Text
page.getByTestId("submit-button"); // Priority 4: TestID
page.locator("css=.button"); // Priority 5: CSS (last resort)

Avoid:

  • Overly specific CSS selectors
  • XPath unless necessary
  • Element IDs that change frequently
  • Position-based selectors

Assertions (Web-First)

Auto-Waiting Assertions: Use web-first assertions with automatic retries

// ✅ PASS: Web-first assertions (auto-wait)
await expect(page).toHaveTitle("Dashboard");
await expect(page.getByRole("heading")).toContainText("Welcome");
await expect(page.getByLabel("Email")).toBeVisible();
await expect(page.getByTestId("status")).toHaveText("Success");

// ❌ FAIL: Generic assertions (no auto-wait)
const text = await page.getByRole("heading").textContent();
expect(text).toBe("Welcome"); // No retry, flaky

Assertion Types:

  • Visibility: toBeVisible(), toBeHidden()
  • Text: toHaveText(), toContainText()
  • Values: toHaveValue(), toHaveAttribute()
  • States: toBeEnabled(), toBeDisabled(), toBeChecked()
  • URL: toHaveURL(), URL patterns with regex

Page Object Model

Class-Based Pattern: Encapsulate page locators and actions

// ✅ PASS: Page Object Model
import { Page, Locator } from "@playwright/test";

export class LoginPage {
  readonly page: Page;
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly submitButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.emailInput = page.getByRole("textbox", { name: "Email" });
    this.passwordInput = page.getByRole("textbox", { name: "Password" });
    this.submitButton = page.getByRole("button", { name: "Sign in" });
  }

  async goto() {
    await this.page.goto("/login");
  }

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

Usage in Tests:

test("successful login", async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login("user@example.com", "password123");
  await expect(page).toHaveURL("/dashboard");
});

Configuration Standards

playwright.config.ts: Environment-specific settings

import { defineConfig, devices } from "@playwright/test";

export default defineConfig({
  testDir: "./tests/e2e",
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: process.env.CI ? [["html"], ["junit"]] : "html",
  use: {
    baseURL: process.env.BASE_URL || "http://localhost:3000",
    trace: "on-first-retry",
    screenshot: "only-on-failure",
  },
  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
    {
      name: "firefox",
      use: { ...devices["Desktop Firefox"] },
    },
    {
      name: "webkit",
      use: { ...devices["Desktop Safari"] },
    },
  ],
});

Best Practices

Test Isolation: Each test independent

// ✅ PASS: Test isolation with beforeEach
test.describe("User Management", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/users");
    await page.getByRole("button", { name: "Add User" }).click();
  });

  test("creates new user", async ({ page }) => {
    // Fresh state from beforeEach
    await page.getByLabel("Name").fill("John Doe");
    await page.getByRole("button", { name: "Save" }).click();
    await expect(page.getByText("User created")).toBeVisible();
  });
});

API Testing Integration: Combine UI and API

test("user sees their data after login", async ({ page, request }) => {
  // API setup
  const response = await request.post("/api/users", {
    data: { name: "Test User", email: "test@example.com" },
  });
  const userId = (await response.json()).id;

  // UI verification
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login("test@example.com", "password");
  await expect(page.getByText("Test User")).toBeVisible();

  // API cleanup
  await request.delete(`/api/users/${userId}`);
});

Anti-Patterns to Avoid

❌ Manual Waits:

// ❌ FAIL: Manual waits
await page.click("button");
await page.waitForTimeout(2000); // Arbitrary wait, flaky

// ✅ PASS: Auto-waiting
await page.click("button");
await expect(page.getByText("Success")).toBeVisible(); // Auto-retries

❌ Overly Specific Selectors:

// ❌ FAIL: Fragile CSS selector
await page.locator("div.container > div:nth-child(2) > button.primary").click();

// ✅ PASS: Semantic selector
await page.getByRole("button", { name: "Submit" }).click();

❌ Test Interdependence:

// ❌ FAIL: Tests depend on execution order
test("1. create user", async ({ page }) => {
  // Creates user, stores in global state
});

test("2. edit user", async ({ page }) => {
  // Depends on user from test 1
});

// ✅ PASS: Independent tests
test.describe("User Management", () => {
  test.beforeEach(async ({ request }) => {
    // Each test creates its own user
    await request.post("/api/users", { data: testUser });
  });

  test("creates user", async ({ page }) => {
    // Independent
  });

  test("edits user", async ({ page }) => {
    // Independent
  });
});

Debugging Tools

Trace Viewer: Post-failure debugging

# Show trace for failed tests
npx playwright show-trace trace.zip

Inspector: Step-through debugging

# Debug specific test
npx playwright test login.spec.ts --debug

Headed Mode: Visual debugging

// playwright.config.ts
use: {
  headless: false, // Show browser
  slowMo: 500, // Slow down actions
},

OSE Platform Context

Islamic Finance Testing

Zakat Calculator Tests:

test("calculates zakat correctly", async ({ page }) => {
  await page.goto("/zakat-calculator");
  await page.getByLabel("Wealth Amount").fill("100000");
  await page.getByRole("button", { name: "Calculate" }).click();

  // Verify 2.5% calculation
  await expect(page.getByTestId("zakat-amount")).toHaveText("RM 2,500.00");
});

Murabaha Contract Tests:

test("murabaha contract workflow", async ({ page }) => {
  const murabaha = new MurabahaPage(page);
  await murabaha.goto();
  await murabaha.createContract({
    asset: "Vehicle",
    cost: 50000,
    profitRate: 5,
  });

  await expect(page.getByText("Contract Created")).toBeVisible();
  await expect(page.getByTestId("total-payment")).toContainText("52,500");
});

Related Standards

See Authoritative Documentation:

Install

Download ZIP
Requires askill CLI v1.0+

AI Quality Score

72/100Analyzed 2/23/2026

High-quality technical reference for Playwright E2E testing with comprehensive coverage of selectors, assertions, page objects, and best practices. Well-structured with clear examples and annotations. However, the OSE Platform Context section with Islamic finance examples (Zakat, Murabaha) ties it to the specific project, and internal path references limit reusability. Core testing patterns are solid and reusable, but domain-specific content reduces broader applicability."

100
85
40
90
85

Metadata

Licenseunknown
Version-
Updated2/22/2026
Publisherwahidyankf

Tags

apici-cdgithub-actionssecuritytesting