
Test Master
Structure browser and API test suites with Screenplay, keyword-driven steps, and model-based state machines instead of brittle page-object soup.
Install
npx skills add https://github.com/jeffallan/claude-skills --skill test-masterWhat is this skill?
- Screenplay pattern with Actor and Task separation for clearer than classic POM tests
- Keyword-driven step tables so non-engineers can author NAVIGATE, CLICK, TYPE, VERIFY flows
- Model-based testing with explicit state machines (e.g. cart empty → hasItems transitions)
- TypeScript-first examples aligned with modern Playwright-style locators (getByRole, getByLabel)
- Composable async task execution via Actor.attemptsTo for parallel step batches
Adoption & trust: 3k installs on skills.sh; 9.7k GitHub stars; 3/3 security scanners passed (skills.sh audits).
Recommended Skills
Agent Browservercel-labs/open-agents
Tddmattpocock/skills
Use My Browserxixu-me/skills
Test Driven Developmentobra/superpowers
Verification Before Completionobra/superpowers
Webapp Testinganthropics/skills
Journey fit
Common Questions / FAQ
Is Test Master safe to install?
skills.sh reports 3 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Test Master
# Automation Frameworks ## Advanced Framework Patterns ### Screenplay Pattern ```typescript // Better separation of concerns than POM export class Actor { constructor(private page: Page) {} attemptsTo(...tasks: Task[]) { return Promise.all(tasks.map(t => t.performAs(this))); } } class Login implements Task { constructor(private email: string, private password: string) {} async performAs(actor: Actor) { await actor.page.getByLabel('Email').fill(this.email); await actor.page.getByLabel('Password').fill(this.password); await actor.page.getByRole('button', { name: 'Login' }).click(); } } // Clear, maintainable test code await new Actor(page).attemptsTo(new Login('user@test.com', 'pass')); ``` ### Keyword-Driven Testing ```typescript const keywords = { NAVIGATE: (page, url) => page.goto(url), CLICK: (page, selector) => page.click(selector), TYPE: (page, selector, text) => page.fill(selector, text), VERIFY: (page, selector) => expect(page.locator(selector)).toBeVisible(), }; // Data drives execution - ideal for non-technical authors const steps = [ { keyword: 'NAVIGATE', args: ['/login'] }, { keyword: 'TYPE', args: ['#email', 'user@test.com'] }, { keyword: 'CLICK', args: ['#submit'] }, ]; for (const step of steps) await keywords[step.keyword](page, ...step.args); ``` ### Model-Based Testing ```typescript // State machine defines valid transitions const cartModel = { empty: { addItem: 'hasItems' }, hasItems: { addItem: 'hasItems', removeItem: 'hasItems|empty', checkout: 'checkingOut' }, checkingOut: { confirm: 'complete', cancel: 'hasItems' }, }; // Generate comprehensive test paths automatically const testPaths = generatePathsFromModel(cartModel); ``` ## Maintenance Strategies ### Self-Healing Locators ```typescript // Multi-strategy finder with automatic fallback async function findElement(page: Page, strategies: string[]): Promise<Locator> { for (const selector of strategies) { const el = page.locator(selector); if (await el.count() > 0) return el; } throw new Error(`Not found: ${strategies.join(', ')}`); } // Usage: tries best -> good -> fallback const submit = await findElement(page, [ '[data-testid="submit"]', // Best: stable test ID 'button:has-text("Submit")', // Good: semantic 'button.primary', // Fallback: CSS ]); ``` ### Error Recovery & Smart Retry ```typescript // Auto-retry with recovery actions async function clickWithRecovery(page: Page, selector: string, retries = 3) { for (let i = 0; i < retries; i++) { try { await page.click(selector, { timeout: 5000 }); return; } catch (e) { if (i === retries - 1) throw e; await page.reload(); await page.waitForLoadState('networkidle'); } } } // Exponential backoff for flaky operations async function retryWithBackoff<T>(fn: () => Promise<T>, retries = 3): Promise<T> { for (let i = 0; i < retries; i++) { try { return await fn(); } catch (e) { if (i === retries - 1) throw e; await new Promise(r => setTimeout(r, 1000 * Math.pow(2, i))); } } } ``` ## Scaling Strategies ### Parallel & Distributed Execution ```typescript // playwright.config.ts export default defineConfig({ workers: process.env.CI ? 8 : 4, fullyParallel: true, retries: process.env.CI ? 2 : 0, // Shard tests across multiple machines shard: process.env.SHARD ? { current: parseInt(process.env.SHARD_INDEX), total: parseInt(process.env.SHARD_TOTAL), } : undefined, }); ``` ```yaml # GitHub Actions: distribute across 5 workers strategy: matrix: shard: [1, 2, 3, 4, 5] steps: - run: npx playwright test --shard=${{ matrix.shard }}/5 ``` ### Resource Optimization ```typescript // Reuse browser contexts for faster execution let browser: Browser; let context: BrowserContext; test.beforeAll(async () => { browser = await chromium.launch(); context = await browser.newContext(); }); test('test 1', async () => { const p