All skills
fugazi avatar

/playwright-e2e-testing

@96feaff

Author and maintain versioned Playwright (@playwright/test) TypeScript UI specs for browser user flows. Use when asked to create, run, debug, or refactor E2E tests, form/navigation/auth flows, responsive checks, UI mocking, fixtures, Page Objects, or visual comparisons. Use api-testing for standalone REST/GraphQL contracts and playwright-cli for live browser sessions. Keywords: E2E spec, Playwright test, POM, fixtures, UI regression.

Use this Skill: https://skilld.dev/gh/fugazi/test-automation-skills-agents/playwright-e2e-testing

This session only. Nothing lands on disk.

referencespage-object-model-components.md

≈1.9k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Page Object Model: Pages & Components

Part of the playwright-e2e-testing skill. See SKILL.md for full context.

Related: this file was split from the original page-object-model.md. See the other page-object-model-*.md files in this folder for related sections.

Page Object Implementation

Login Page

// pages/LoginPage.ts
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "./BasePage";
import { DashboardPage } from "./DashboardPage";

export class LoginPage extends BasePage {
  // Locators (defined in constructor)
  readonly emailInput: Locator;
  readonly passwordInput: Locator;
  readonly loginButton: Locator;
  readonly errorMessage: Locator;
  readonly forgotPasswordLink: Locator;
  readonly signUpLink: Locator;
  readonly rememberMeCheckbox: Locator;

  constructor(page: Page) {
    super(page);
    this.emailInput = page.getByLabel("Email");
    this.passwordInput = page.getByLabel("Password");
    this.loginButton = page.getByRole("button", { name: "Sign in" });
    this.errorMessage = page.getByRole("alert");
    this.forgotPasswordLink = page.getByRole("link", {
      name: "Forgot password?",
    });
    this.signUpLink = page.getByRole("link", { name: "Sign up" });
    this.rememberMeCheckbox = page.getByRole("checkbox", {
      name: "Remember me",
    });
  }

  // Navigation
  async goto(): Promise<this> {
    await this.page.goto("/login");
    return this;
  }

  // Actions - return 'this' for fluent chaining
  async fillEmail(email: string): Promise<this> {
    await this.emailInput.fill(email);
    return this;
  }

  async fillPassword(password: string): Promise<this> {
    await this.passwordInput.fill(password);
    return this;
  }

  async checkRememberMe(): Promise<this> {
    await this.rememberMeCheckbox.check();
    return this;
  }

  async clickLogin(): Promise<void> {
    await this.loginButton.click();
  }

  // Combined action - returns next page
  async login(email: string, password: string): Promise<DashboardPage> {
    await this.fillEmail(email);
    await this.fillPassword(password);
    await this.clickLogin();
    await this.page.waitForURL(/.*dashboard/);
    return new DashboardPage(this.page);
  }

  // Attempt invalid login (stays on same page)
  async attemptInvalidLogin(email: string, password: string): Promise<this> {
    await this.fillEmail(email);
    await this.fillPassword(password);
    await this.clickLogin();
    return this;
  }

  // Validation methods
  async expectErrorMessage(message: string): Promise<void> {
    await expect(this.errorMessage).toContainText(message);
  }

  async expectLoginButtonEnabled(): Promise<void> {
    await expect(this.loginButton).toBeEnabled();
  }

  async expectLoginButtonDisabled(): Promise<void> {
    await expect(this.loginButton).toBeDisabled();
  }

  async expectEmailFieldError(): Promise<void> {
    await expect(this.emailInput).toHaveAttribute("aria-invalid", "true");
  }
}

Dashboard Page

// pages/DashboardPage.ts
import { Page, Locator, expect } from "@playwright/test";
import { BasePage } from "./BasePage";
import { HeaderComponent } from "./components/HeaderComponent";

export class DashboardPage extends BasePage {
  readonly header: HeaderComponent;
  readonly welcomeHeading: Locator;
  readonly statsCards: Locator;
  readonly recentActivityList: Locator;
  readonly quickActionsMenu: Locator;

  constructor(page: Page) {
    super(page);
    this.header = new HeaderComponent(page);
    this.welcomeHeading = page.getByRole("heading", { level: 1 });
    this.statsCards = page.getByTestId("stats-card");
    this.recentActivityList = page.getByRole("list", {
      name: "Recent Activity",
    });
    this.quickActionsMenu = page.getByRole("menu", { name: "Quick Actions" });
  }

  async goto(): Promise<this> {
    await this.page.goto("/dashboard");
    return this;
  }

  async getWelcomeMessage(): Promise<string> {
    return (await this.welcomeHeading.textContent()) ?? "";
  }

  async getStatsCount(): Promise<number> {
    return this.statsCards.count();
  }

  async clickQuickAction(actionName: string): Promise<void> {
    await this.quickActionsMenu
      .getByRole("menuitem", { name: actionName })
      .click();
  }

  async expectWelcomeMessage(name: string): Promise<void> {
    await expect(this.welcomeHeading).toContainText(`Welcome, ${name}`);
  }

  async expectMinimumStats(count: number): Promise<void> {
    await expect(this.statsCards).toHaveCount(count);
  }
}

Component Objects

For reusable UI components (header, footer, modals):

Header Component

// pages/components/HeaderComponent.ts
import { Page, Locator, expect } from "@playwright/test";

export class HeaderComponent {
  readonly page: Page;
  readonly container: Locator;
  readonly logo: Locator;
  readonly searchBox: Locator;
  readonly cartIcon: Locator;
  readonly cartCount: Locator;
  readonly userMenu: Locator;
  readonly logoutButton: Locator;

  constructor(page: Page) {
    this.page = page;
    this.container = page.getByRole("banner");
    this.logo = this.container.getByRole("link", { name: "Home" });
    this.searchBox = page.getByRole("searchbox");
    this.cartIcon = this.container.getByRole("link", { name: /cart/i });
    this.cartCount = page.getByTestId("cart-count");
    this.userMenu = this.container.getByRole("button", { name: /account/i });
    this.logoutButton = page.getByRole("menuitem", { name: "Logout" });
  }

  async search(query: string): Promise<void> {
    await this.searchBox.fill(query);
    await this.searchBox.press("Enter");
  }

  async getCartItemCount(): Promise<number> {
    const text = await this.cartCount.textContent();
    return parseInt(text ?? "0", 10);
  }

  async goToCart(): Promise<void> {
    await this.cartIcon.click();
  }

  async openUserMenu(): Promise<void> {
    await this.userMenu.click();
  }

  async logout(): Promise<void> {
    await this.openUserMenu();
    await this.logoutButton.click();
  }

  async expectLoggedIn(username?: string): Promise<void> {
    if (username) {
      await expect(this.userMenu).toContainText(username);
    }
    await expect(this.userMenu).toBeVisible();
  }
}

Modal Component

// pages/components/ModalComponent.ts
import { Page, Locator, expect } from "@playwright/test";

export class ModalComponent {
  readonly page: Page;
  readonly container: Locator;
  readonly title: Locator;
  readonly closeButton: Locator;
  readonly confirmButton: Locator;
  readonly cancelButton: Locator;

  constructor(page: Page, name?: string) {
    this.page = page;
    this.container = name
      ? page.getByRole("dialog", { name })
      : page.getByRole("dialog");
    this.title = this.container.getByRole("heading");
    this.closeButton = this.container.getByRole("button", { name: "Close" });
    this.confirmButton = this.container.getByRole("button", {
      name: /confirm|save|submit/i,
    });
    this.cancelButton = this.container.getByRole("button", { name: /cancel/i });
  }

  async waitForOpen(): Promise<void> {
    await expect(this.container).toBeVisible();
  }

  async waitForClose(): Promise<void> {
    await expect(this.container).toBeHidden();
  }

  async close(): Promise<void> {
    await this.closeButton.click();
    await this.waitForClose();
  }

  async confirm(): Promise<void> {
    await this.confirmButton.click();
  }

  async cancel(): Promise<void> {
    await this.cancelButton.click();
    await this.waitForClose();
  }

  async expectTitle(title: string): Promise<void> {
    await expect(this.title).toHaveText(title);
  }
}

Source: SKILL.md on GitHub

1 warning16d4 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    No security issues detected. The skill provides standard and safe instructions for authoring Playwright E2E tests, following best practices for locator usage and Page Object Model implementation.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    3/5 files flagged

Signed by skilld at 96feaff. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 4 days ago.

Activeupdated 2 months ago

README badge

README badge for fugazi/test-automation-skills-agents/playwright-e2e-testing