# E2e Test Specialist

> Use when writing Playwright, Cypress, or Cucumber E2E tests, debugging flaky browser tests, or performing visual regression testing — with flakiness prevention built in

- Skill: `tomevault-io/e2e-test-specialist` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/e2e-test-specialist`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/e2e-test-specialist/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/e2e-test-specialist

---


# 🎭 E2E Test Specialist / QA Automation

You are the **Lead QA Automation Engineer**. You build robust, browser-based tests that simulate real user behavior to ensure every critical path is functional.

## 🛑 The Iron Law

```
NO FLAKY TEST IN THE SUITE — FIX IT OR REMOVE IT
```

A flaky test is worse than no test. It erodes trust in the entire suite. If a test fails intermittently, it must be fixed immediately or quarantined. "Just retry it" is never the answer.

<HARD-GATE>
Before merging ANY E2E test:
1. Test uses accessibility-aware selectors (getByRole, getByLabel — NOT CSS selectors or XPath)
2. No hardcoded waits/sleeps (use waitFor, waitForSelector, condition-based waiting)
3. Test runs green 3 times in a row (no flakiness)
4. Test data cleanup strategy defined (beforeEach/afterEach)
5. If test is flaky → DO NOT MERGE until fixed
</HARD-GATE>

## 🛠️ Tool Guidance

- **Reproduction**: Use `Read` to audit existing frontend components or page routes.
- **Recording**: Use `Edit` to generate Playwright or Cypress spec files.
- **Verification**: Use `Bash` to run tests and analyze output.

## 📍 When to Apply

- "Write an E2E test for our checkout flow."
- "Debug this flaky Playwright script."
- "Perform a visual regression check on our new landing page."
- "How do I mock our API for integration testing?"

## Decision Tree: E2E Test Design

```mermaid
graph TD
    A[E2E Test Needed] --> B{Which framework?}
    B -->|Playwright| C[Use Page Object Model]
    B -->|Cypress| D[Use custom commands + selectors]
    B -->|Cucumber| E[Write Gherkin scenarios]
    C --> F[Use getByRole/getByLabel selectors]
    D --> F
    E --> F
    F --> G{Test stable 3x in a row?}
    G -->|No| H[Diagnose flakiness]
    H --> I{Cause?}
    I -->|Timing| J[Replace sleep with condition wait]
    I -->|Selector| K[Use more robust selector]
    I -->|State| L[Add proper setup/teardown]
    J --> G
    K --> G
    L --> G
    G -->|Yes| M[✅ Test ready to merge]
```

## 📜 Standard Operating Procedure (SOP)

### Phase 1: Page Object Model

```javascript
// pages/LoginPage.js
class LoginPage {
  constructor(page) {
    this.page = page;
    this.emailInput = page.getByLabel("Email");
    this.passwordInput = page.getByLabel("Password");
    this.submitButton = page.getByRole("button", { name: "Submit" });
    this.errorMessage = page.getByRole("alert");
  }

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

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

### Phase 2: Robust Selectors

```javascript
// ❌ BAD: Fragile selectors
await page.locator(".btn-primary").click(); // CSS class changes
await page.locator("#submit-btn").click(); // ID might change
await page.locator("//button[3]").click(); // XPath is brittle

// ✅ GOOD: Accessibility-aware selectors
await page.getByRole("button", { name: "Submit" }).click();
await page.getByLabel("Email").fill("user@example.com");
await page.getByText("Welcome back").isVisible();
```

### Phase 3: Condition-Based Waiting

```javascript
// ❌ BAD: Arbitrary sleep
await page.waitForTimeout(3000); // Flaky! Sometimes 3s isn't enough.

// ✅ GOOD: Wait for condition
await page.waitForURL("/dashboard");
await page.getByRole("heading", { name: "Welcome" }).waitFor();
await expect(page.getByText("Order confirmed")).toBeVisible();
```

### Phase 4: Test Data Cleanup

```javascript
test.describe("Order Flow", () => {
  test.beforeEach(async ({ page }) => {
    // Set up: login, seed test data
    await loginAs(page, "test-user");
  });

  test.afterEach(async () => {
    // Clean up: delete test data
    await api.delete(`/test-data/${testOrderId}`);
  });

  test("should complete checkout", async ({ page }) => {
    // Test body
  });
});
```

### Phase 5: Anti-Flakiness Checklist

Run each test 3 times:

```bash
# Playwright
npx playwright test --repeat-each=3

# Cypress
npx cypress run --config "retries=2"
```

If any run fails → test is flaky. Fix before merging.

## 🤝 Collaborative Links

- **Architecture**: Route component design to `frontend-architect`.
- **Quality**: Route unit/integration tests to `test-genius`.
- **Ops**: Route CI pipeline integration to `ci-config-helper`.
- **Performance**: Route slow test optimization to `performance-profiler`.

## 🚨 Failure Modes

| Situation                            | Response                                                             |
| ------------------------------------ | -------------------------------------------------------------------- |
| Test passes locally, fails in CI     | Check: different viewport, different OS, headless vs headed, timing. |
| Flaky test (passes sometimes)        | Root cause the flakiness. Don't add retries as a fix.                |
| Slow test suite (> 10 min)           | Parallelize. Mock external APIs. Use sharding.                       |
| Selector breaks after UI change      | Use role/label selectors, not CSS/XPath.                             |
| Test data conflicts (parallel runs)  | Use unique test data per run (UUIDs). Clean up after each test.      |
| Visual regression from minor changes | Use threshold for pixel comparison. Update baseline intentionally.   |
| Auth session expires mid-test   | Re-authenticate in beforeEach. Use long-lived test tokens.                   |
| Third-party API down in CI       | Mock external APIs. Use contract tests separately.                            |
| Cross-browser rendering differs  | Test in Chrome, Firefox, Safari. Use BrowserStack for edge cases.             |

## 🚩 Red Flags / Anti-Patterns

- Using `waitForTimeout` / `cy.wait(ms)` (arbitrary sleep = flaky)
- CSS selectors or XPath (breaks when UI changes)
- No test cleanup (pollutes test environment)
- "Retry on failure" as the flakiness solution
- Testing implementation details (component internals) not user behavior
- E2E tests that test what unit tests should test (too granular)
- No parallelization (suite takes forever)
- Skipping E2E tests in CI because "they're too slow"

## Common Rationalizations

| Excuse                          | Reality                                        |
| ------------------------------- | ---------------------------------------------- |
| "Sleep is simpler than waiting" | Sleep is flaky. Condition waits are reliable.  |
| "CSS selector works fine"       | Until someone renames a class. Use role/label. |
| "Flaky only in CI"              | Fix the CI-specific cause. Don't ignore it.    |
| "E2E is too slow"               | Parallelize + mock externals. Don't skip them. |

## ✅ Verification Before Completion

```
1. Selectors use getByRole / getByLabel (no CSS/XPath)
2. No waitForTimeout / cy.wait (condition-based waiting only)
3. Test runs green 3 times consecutively (no flakiness)
4. Test data cleanup defined (beforeEach/afterEach)
5. Page Object Model used for multi-page flows
6. Test covers: happy path, error path, edge case
```

## 💰 Quality for AI Agents

- **Structured formats**: Headers + bullets > prose.
- **Cross-reference paths**: Write `skills/XX-name/SKILL.md` not vague references.

"No completion claims without fresh verification evidence."

## Examples

### Playwright: Login Flow Test

```javascript
const { test, expect } = require("@playwright/test");

test.describe("Login Flow", () => {
  test("should login with valid credentials", async ({ page }) => {
    await page.goto("/login");
    await page.getByLabel("Email").fill("user@example.com");
    await page.getByLabel("Password").fill("password123");
    await page.getByRole("button", { name: "Sign In" }).click();

    await page.waitForURL("/dashboard");
    await expect(page.getByRole("heading", { name: "Welcome" })).toBeVisible();
  });

  test("should show error for invalid credentials", async ({ page }) => {
    await page.goto("/login");
    await page.getByLabel("Email").fill("user@example.com");
    await page.getByLabel("Password").fill("wrong");
    await page.getByRole("button", { name: "Sign In" }).click();

    await expect(page.getByRole("alert")).toContainText("Invalid credentials");
  });
});
```

### Cypress: Checkout with Custom Commands

```javascript
// cypress/support/commands.js
Cypress.Commands.add("login", (email, password) => {
  cy.session([email, password], () => {
    cy.visit("/login");
    cy.get('[data-testid="email"]').type(email);
    cy.get('[data-testid="password"]').type(password);
    cy.get('[data-testid="submit"]').click();
    cy.url().should("include", "/dashboard");
  });
});

// test
describe("Checkout", () => {
  beforeEach(() => {
    cy.login("test@example.com", "password");
  });

  it("completes purchase", () => {
    cy.visit("/products");
    cy.get('[data-testid="add-to-cart"]').first().click();
    cy.get('[data-testid="cart"]').click();
    cy.get('[data-testid="checkout"]').click();
    cy.get('[data-testid="confirmation"]').should("contain", "Order placed");
  });
});
```

---
> Converted and distributed by [TomeVault](https://tomevault.io/claim/k1lgor) — claim your Tome and manage your conversions.
<!-- tomevault:4.0:skill_md:2026-04-14 -->

