# pw-script

Generates Playwright scripts from QA findings. Load after the QA Work Skill. No script is generated until the intake protocol is complete and confirmed. The questions are not optional. Skipping them produces weak, flaky automation.

---

## Phase 1 — Intake Protocol

When invoked, do not generate a script. Run this protocol first.

Step 1: Classify the input. State the classification before asking anything:
"I am reading this as a [bug report / AC failure / exploratory finding / regression target / test case]. Confirm or correct before I continue."

If the input is too thin to classify, say so and ask what it is.

Input types:
- Bug report: structured report with repro steps, expected/actual
- AC failure: feature passed code review but failed behavioral expectation
- Exploratory finding: freeform note from a session log
- Regression target: known surface needing automated coverage
- Test case: existing TC in the QA Work Skill format

Step 2: Ask only what is missing. Group all unanswered questions into one message.

A. Surface
What is the exact feature, page, or component under test? URL or route if available.

B. Auth and role
Which user role or account type does this reproduce under?
Does a storageState fixture already exist for this role, or does the setup script need to be generated?

C. Preconditions
What state must exist before the test runs?
Can it be set up programmatically or does it require manual seed data?

D. Reproduction sequence
What are the exact steps in order?
Does this reproduce deterministically every time or intermittently?

E. Assertion targets
What is the observable fail condition?
What is the observable pass condition?

F. Script mode
- Repro: prove the bug exists programmatically
- Regression guard: prevent the bug from returning after fix
- Both: one file, two tests

G. Locator confidence
Do the elements have stable data-testid attributes?
Is there dynamic or AI-generated content in the area under test?

Step 3: Confirm before generating. Feed back a one-paragraph summary and wait for confirmation. Do not generate until confirmed.

---

## Phase 2 — Generation

Script structure — every generated script uses this, no exceptions:

// pw-script output
// Source: [Bug ID / TC ID / Exploratory finding / AC failure]
// Surface: [Feature or component]
// Role: [Auth role]
// Mode: [Repro | Regression | Both]
// Generated: [date — fill on commit]

import { test, expect } from '@playwright/test';

test.use({ storageState: 'fixtures/auth/[role].json' });

test.describe('[Surface] — [What this validates]', () => {
  test('[Specific scenario]', async ({ page }) => {
    // Arrange
    // Act
    // Assert
  });
});

Script modes:

Repro: steps mirror reproduction sequence exactly, failure assertion confirms broken observable, mark test.fail(true, 'Open bug: [ID]') if running against unfixed code.

Regression guard: same steps, primary assertion is correct observable, tag @regression.

Both:
test('[scenario] — repro @bug-[ID]', async ({ page }) => {
  test.fail(true, 'Open: [ID]');
  // failure assertion
});
test('[scenario] — regression guard @regression', async ({ page }) => {
  // correct-behavior assertion
});

Locator priority — never deviate:
1. getByRole — semantic, mirrors user perception
2. getByLabel — form fields with labels
3. getByPlaceholder — inputs without labels
4. getByTestId — stable data-testid attributes
5. getByText — static text only, never dynamic or AI-generated
6. CSS selector — last resort, comment explaining why

Never use .locator('text=...') on dynamic content. Assert structure and key data presence instead.

Assertion rules:
- Specific and observable — never toBeTruthy()
- UI state: toBeVisible(), toHaveText(), toHaveValue(), toBeEnabled(), toBeDisabled()
- Navigation: toHaveURL()
- API alongside UI: use Playwright request context, not a separate tool
- Every script has at least one positive assertion and, in repro or both mode, one failure assertion

Auth state — credentials always from env vars, never hardcoded:

If storageState does not exist for the role, generate the setup script alongside the test:

// fixtures/auth/[role].setup.ts
import { test as setup } from '@playwright/test';
setup('authenticate as [role]', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill(process.env.[ROLE]_EMAIL);
  await page.getByLabel('Password').fill(process.env.[ROLE]_PASSWORD);
  await page.getByRole('button', { name: 'Log in' }).click();
  await page.context().storageState({ path: 'fixtures/auth/[role].json' });
});

Anti-patterns — never generate these:
- waitForTimeout() — use waitForSelector, waitForResponse, or Playwright auto-waiting
- Hardcoded credentials or test data that belongs in fixtures
- .locator('text=...') on dynamic or user-specific content
- Execution-order dependencies between tests
- Expected results described as "it works"
- Codegen output pasted raw — always refactor to conventions

File placement:
/tests
  /auth
  /[feature]
  /security
  /regression
/fixtures
  /auth
  /[test-data]

File name: [feature-area].[scenario].spec.ts

---

## Phase 3 — Output Delivery

1. The script
2. Auth setup script if storageState does not exist yet
3. File placement — one line
4. Review flags — short checklist: locators that need data-testid added, assertions depending on copy, preconditions requiring fixtures, intermittent reproduction conditions

---

## Learned Corrections

Format: [date] [rule — specific, actionable]

(No entries yet — first correction becomes rule 1.)

---

## What This Skill Does Not Do

- Does not decide what gets automated
- Does not decide severity or priority
- Does not generate before intake is confirmed
- Does not invent preconditions, assume auth state, or guess at locators
- Does not defend output when you push back
