From traxxall-delivery-qa
Authors a Playwright TypeScript test suite from the structured claim list produced by qa-spec-reader. Drives the running TEST application via Playwright MCP to discover locators and UI flows. Follows strict locator, wait, fixture, and page-object conventions. Never reads product source code or diffs.
How this skill is triggered — by the user, by Claude, or both
Slash command
/traxxall-delivery-qa:qa-test-authorThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill is permitted to use only:
This skill is permitted to use only:
qa-spec-reader.mcp__plugin_playwright_playwright__* tools.Forbidden inputs — refuse immediately if offered:
main/ source tree.ISOLATION GATE: If a forbidden input is detected, state: "I have detected a forbidden input: [name it]. Authoring proceeds from the claims list and TEST-app observation only."
Produce a Playwright TypeScript test file. Use this file header for every suite:
import { test, expect } from '@playwright/test';
// Admin surface: import TxAdmin page objects for locator reuse
// import { HomePageAdm } from '../pages/TxAdmin/homePageAdm';
// import { AircraftsPageAdm } from '../pages/TxAdmin/aircraftsPageAdm';
// 360 surface: scaffold fresh page objects in src/pages/Tx360/
// Auth: storageState loaded from playwright.config.ts project dependency
// 'Setup User Login' project must run first and write .auth/storageState.TEST.json
Global use block to include in playwright.config.ts (or test-level test.use):
use: {
baseURL: process.env.TEST_BASE_URL,
storageState: '.auth/storageState.TEST.json',
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
}
Before writing any selector, navigate the TEST application using Playwright MCP tools to observe the actual UI:
mcp__plugin_playwright_playwright__browser_navigate to open the relevant screen.mcp__plugin_playwright_playwright__browser_snapshot to capture the accessibility tree.Never guess a locator. If a TEST-app observation is not possible (environment offline), note the assumption in a comment and use a getByRole/getByLabel placeholder with a // TODO: verify in TEST marker.
| Priority | Method | Use when |
|---|---|---|
| 1 | getByRole('button', { name: '...' }) | Buttons, links, checkboxes, radio buttons |
| 2 | getByLabel('...') | Form inputs with associated labels |
| 3 | getByText('...') | Static text elements, headings, status messages |
| 4 | locator('[data-testid="..."]') | When a data-testid attribute is present |
| 5 | TxAdmin page object method | Admin surface only — import from src/pages/TxAdmin/ |
Never use: CSS class selectors, nth-child, dynamic IDs (containing GUIDs or numeric DB keys), layout-position selectors (first() without a semantic anchor), or any selector derived from reading source code.
Use these wait patterns. Never use page.waitForTimeout.
| Scenario | Correct wait |
|---|---|
| Navigation / page load | await page.waitForURL('**/expected-path**') or await expect(page).toHaveURL(...) |
| Element becomes visible | await expect(locator).toBeVisible() |
| Form save / server postback | await page.waitForResponse(resp => resp.url().includes('/save') && resp.status() === 200) |
| Async SignalR notification | await page.waitForEvent('console', msg => msg.text().includes('notification-key')) or await page.waitForResponse(resp => resp.url().includes('/hub')) |
| Element disappears (spinner) | await expect(spinner).not.toBeVisible() |
| Hard timeout guard (ERROR case) | Wrap in Promise.race([action, new Promise((_, reject) => setTimeout(() => reject(new Error('TIMEOUT')), 30000))]) and catch to record ERROR |
For Web Forms partial-page postbacks (Admin surface): after triggering a postback, wait for the UpdatePanel completion via page.waitForResponse matching the __EVENTTARGET request, or assert that the expected DOM change is visible — never sleep.
For each CLAIM-n from the spec-reader output, write at minimum:
happy test covering the primary success path.edge, negative, abuse, and cross-module cases as test stubs with test.skip and a comment like // adversarial-critic will expand — the critic phase fills these in.Test naming convention: [CLAIM-n] [type] — [brief description]
test('CLAIM-1 happy — technician marks task complete, compliance status updates', async ({ page }) => {
// STEP: Navigate to maintenance task screen
await page.goto('/admin/tasks');
// STEP: Select the task
await page.getByRole('link', { name: /task-name/i }).click();
// STEP: Mark as complete
await page.getByRole('button', { name: /mark complete/i }).click();
// WAIT: Server postback completes
await page.waitForResponse(resp => resp.url().includes('/tasks/complete') && resp.status() === 200);
// ASSERT: Compliance status refreshes
await expect(page.getByRole('status', { name: /compliance/i })).toContainText('Up to Date');
});
When the surface is 360, scaffold a new page object class in src/pages/Tx360/. Minimum structure:
// src/pages/Tx360/PartOrderPage360.ts
import { Page, Locator } from '@playwright/test';
export class PartOrderPage360 {
readonly page: Page;
readonly vendorSelect: Locator;
readonly quantityInput: Locator;
readonly partNumberInput: Locator;
readonly submitButton: Locator;
readonly validationMessage: Locator;
constructor(page: Page) {
this.page = page;
this.vendorSelect = page.getByLabel('Vendor');
this.quantityInput = page.getByLabel('Quantity');
this.partNumberInput = page.getByLabel('Part Number');
this.submitButton = page.getByRole('button', { name: /submit order/i });
this.validationMessage = page.getByRole('alert');
}
async navigate() {
await this.page.goto('/360/inventory/part-orders/new');
await expect(this.submitButton).toBeVisible();
}
}
All locators in the page object must come from TEST-app observation, not source reading.
Include in the test file:
// Auth is pre-loaded from .auth/storageState.TEST.json via playwright.config.ts
// The 'Setup User Login' project dependency writes this file.
// Do NOT call login() inside test bodies.
If a permission-boundary test requires a different role:
test.use({ storageState: '.auth/storageState.TEST.readonly.json' });
Return the Playwright TypeScript test file contents and any new page object files as code blocks, followed by the updated JSON envelope with all test cases populated at result: "NOT_RUN".
Read, Grep, or Glob against main/.page.waitForTimeout in any test.UnitTests.Core, TransactionScope).eval/rubric.private.md or any path under eval/.npx claudepluginhub skobyn/upskill-me --plugin traxxall-delivery-qaGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.