agentsclimarketplace

Test ui

Skill lgtm-hq/ai-skills/skills/test-ui

Canonical Agent Skills library for Claude Code, Cursor, Codex, and other agents.

Install
npx -y skills add lgtm-hq/ai-skills --skill test-ui

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 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

Playwright E2E testing best practices. Use when writing browser tests, visual regression, or accessibility tests in any project. Enforces user-facing locators, auto-waiting, web-first assertions, and Page Object Model.

SKILL.md

6.1 KB, ~1.4k tokens by cl100k_base, as published. Nobody here has run it

Playwright E2E Testing Standards

Write and maintain browser E2E tests following Playwright best practices. These rules are project-agnostic; project-specific conventions (fixtures, test IDs, config) live in the project's own skill — for the QSF suite, follow the test-ui-qsf skill alongside this one.

Locators (Priority Order)

Use user-facing locators.

// BEST: Semantic locators
page.getByRole("button", { name: "Submit" });
page.getByRole("tab", { name: "Dashboard" });
page.getByLabel("Email");
page.getByPlaceholder("Search...");
page.getByText("Welcome");
page.getByTitle("Document title");

// GOOD: Test IDs (attribute set by testIdAttribute in playwright.config.ts)
page.getByTestId("delete-row-btn");

// ACCEPTABLE: CSS locators for structural queries
page.locator('input[type="file"][multiple]');

// AVOID: Fragile CSS selectors
page.locator("#submit-btn");
page.locator("div > button.primary");

Auto-Waiting

Never use waitForTimeout(). Playwright auto-waits for elements.

// WRONG: Manual timeouts
await page.waitForTimeout(1000);
await button.click();

// CORRECT: Auto-waiting assertions
await expect(button).toBeVisible();
await button.click();

// CORRECT: Poll for async state
await expect
  .poll(async () => page.evaluate(() => localStorage.getItem("theme")))
  .toBe("dark");

// CORRECT: Wait for specific conditions
await page.waitForLoadState("networkidle");
await page.waitForURL(/\/dashboard/);

// ACCEPTABLE: toPass() for polling complex async operations
await expect(async () => {
  await dashboardPage.open();
  await dashboardPage.assertPresent();
}).toPass({ timeout: 30_000 });

Web-First Assertions

Use Playwright's auto-retrying assertions. Keep assertions in page objects where possible.

// CORRECT: Web-first (auto-retries)
await expect(page.getByRole("heading")).toHaveText("Dashboard");
await expect(page.locator("html")).toHaveAttribute("data-theme", "dark");
await expect(button).toBeEnabled();

// CORRECT: Page object assertion methods
await dashboardPage.assertSelectedCheckboxes(["Odd", "Free"]);

// AVOID: Manual checks (no retry)
const text = await heading.textContent();
expect(text).toBe("Dashboard");

Page Object Model

Use a BasePage → SpecializedPage hierarchy with composed components.

// pageObjects/BasePage.ts — all pages extend this
export default class BasePage {
  public navigation = new Navigation(this.page);

  constructor(readonly page: Page) {}

  async openTab(tabName: string): Promise<void> {
    await this.page.getByRole("tab", { name: tabName }).click();
  }

  async assertUrl(path: string | RegExp): Promise<void> {
    await expect(this.page).toHaveURL(path);
  }
}

// pageObjects/DetailPage.ts — specialized page
export default class DetailPage extends BasePage {
  public fileUploadComponent = new FileUpload(this.page);

  readonly errorLabel: Locator = this.page.getByRole("alert");

  async assertError(message?: string): Promise<void> {
    await expect(this.errorLabel).toBeVisible();
    if (message) await expect(this.errorLabel).toHaveText(message);
  }
}

Rules

  • One class per page/component — keep files focused
  • Locators as readonly properties — defined in constructor scope, not in methods
  • Assertions belong in page objects — prefix with assert
  • Actions return Promise<void> — no chaining
  • Compose via child componentsthis.navigation, this.fileUploadComponent, etc.
  • Split large element libraries — keep shared element classes under ~300 lines, one file per element type

Test Design

  • Tests are independent — no shared mutable state between tests
  • Use forEach loops over arrays/objects for data-driven parameterization
  • Test names describe the scenario and expected outcome
  • Extract beforeEach navigation to shared helpers when duplicated 3+ times
  • No console.log debug output — use test.step() annotations
  • Import UI labels, error messages, and identifiers from constants/enums — never hard-code strings in tests

Known Anti-Patterns

1. Mutable global state across tests

// BAD: shared array mutated across iterations — creates order dependency
const passedTests: string[] = [];
for (const tc of testcases) {
  test(`test ${tc}`, async () => {
    passedTests.push(tc);
  });
}

// GOOD: each test is self-contained
testcases.forEach((tc) => {
  test(`test ${tc}`, async ({ page }) => {
    // no shared mutable state
  });
});

2. Test name doesn't match behavior

Name the test after what it actually asserts, not the component you started with.

3. Visibility-only assertions ("nothing burger" tests)

// BAD: only checks the element exists — passes even if broken
await detailPage.assertBadgeVisible("Theme");

// GOOD: verify content and interaction
await detailPage.assertBadgeText("Theme", "Default");
await detailPage.selectBadge("Theme", "Dark");
await detailPage.assertBadgeText("Theme", "Dark");

Network Interception

// Mock API responses
await page.route("**/api/user", (route) => {
  route.fulfill({ json: { name: "Test User" } });
});

// Simulate failures
await page.route("**/*.css", (route) => route.abort("failed"));

// Cleanup after test — unroute every mock you registered
await page.unroute("**/api/user");
await page.unroute("**/*.css");

Checklist

  • Locators use getByRole, getByTestId, getByLabel, getByTitle (not fragile CSS)
  • No waitForTimeout() calls — auto-waiting assertions only
  • Assertions are web-first (expect() auto-retrying), meaningful beyond visibility checks
  • Page objects extend the base page, compose components, keep locators as readonly properties, and own the assert* methods
  • Tests independent; forEach parameterization; descriptive names
  • No debug logging; constants imported, not hard-coded

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.