Assembles a multi-step end-to-end user-journey test from a list of high-level user intents - translates each intent ("user signs up", "user adds product to cart", "user completes checkout with promo code") into the corresponding test-runner step (Playwright / Cypress / Selenium / Karate), wires shared state across steps via test fixtures, and emits the resulting test as a single Scenario in the project's E2E framework. Use when scaffolding an E2E test that exercises a complete user flow rather than a single page.
80
100%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
High
Do not use without reviewing
This skill takes a high-level intent list and assembles a single narrative test - named user-language intents map to framework-native steps, so one UI refactor updates one mapping, not every test:
1. user_signs_up_with_email
2. user_confirms_email
3. user_creates_workspace
4. user_invites_teammate
5. user_completes_first_task→ One .spec.ts (or .feature) with the canonical sequence.
A typical user-journey test has 5-15 intents:
test:
name: New user onboards and completes first task
fixtures: [seed_workspace, seed_admin_user] # from seed-data-curator
steps:
- intent: user_visits_landing_page
page: /
- intent: user_clicks_signup
- intent: user_fills_signup_form
data:
email: '{{ faker.email }}'
password: '{{ faker.password }}'
- intent: user_submits_signup
- intent: user_confirms_email_via_inbox
- intent: user_creates_workspace
data:
name: 'Test Workspace'
- intent: user_invites_teammate
data:
teammate_email: 'teammate@example.com'
- intent: user_creates_first_task
data:
title: 'Hello world'
- intent: user_marks_task_complete
assertions:
- on_page: /workspace/{workspace_id}
- selector_visible: '[data-testid="task-complete-celebration"]'
- api_called: POST /api/tasks/completeIntents are named verbs in user-language. They abstract over "click the button with text 'Sign up'" - the matching helper function (in step 2) knows how.
Once per project, a mapping file translates intents to framework-
native code. Each intent is one async function that drives the UI and
optionally shares state via ctx:
// e2e/intents/index.ts (Playwright)
export const intents = {
user_clicks_signup: async (page: Page) =>
page.getByRole('link', { name: 'Sign up' }).click(),
user_fills_signup_form: async (page: Page, ctx: any, data: any) => {
await page.getByLabel('Email').fill(data.email);
ctx.email = data.email; // share state with later intents
},
};The mapping is the project's - it knows how the UI is laid out; adding or refactoring an intent is one edit. Full mapping plus the Cypress and Karate variants: references/intent-mapping.md.
The skill emits the matching .spec / .feature file.
Default: Playwright - first-party TypeScript types, built-in fixtures + parallelism, role/label-based selectors that survive UI refactors. Use Cypress when the project already standardizes on it; use Karate for pure-API journeys; use Selenium only for legacy suites already invested in it. Cypress and Karate output shapes: references/intent-mapping.md.
// e2e/onboarding.spec.ts
import { test, expect } from '@playwright/test';
import { intents } from './intents';
test('new user onboards and completes first task', async ({ page }) => {
const ctx = { baseUrl: process.env.BASE_URL ?? 'http://localhost:3000' };
await intents.user_visits_landing_page(page, ctx);
await intents.user_clicks_signup(page, ctx);
await intents.user_fills_signup_form(page, ctx, {
email: 'newuser@example.com',
password: 'TestPass123!',
});
await intents.user_submits_signup(page, ctx);
await intents.user_confirms_email_via_inbox(page, ctx);
await intents.user_creates_workspace(page, ctx, { name: 'Test Workspace' });
await intents.user_invites_teammate(page, ctx, { teammate_email: 'teammate@example.com' });
await intents.user_creates_first_task(page, ctx, { title: 'Hello world' });
await intents.user_marks_task_complete(page, ctx);
// Assertions
await expect(page).toHaveURL(/\/workspace\/[a-z0-9-]+/);
await expect(page.locator('[data-testid="task-complete-celebration"]')).toBeVisible();
});Each intent may depend on fixtures generated by:
| Fixture source | Skill |
|---|---|
| Seed data (workspaces, admin users) | seed-data-curator. |
| Synthetic field values (emails, passwords) | faker-data etc. |
| Mock external services (email, payment) | wiremock-stubs, msw-handlers, mountebank-imposters. |
The intent file declares fixtures: [...] and the generated test
imports them at the top.
## E2E Narrative Generated - `<test-name>`
**Framework:** Playwright | Cypress | Selenium | Karate
**Intents used:** N
**Test file:** `e2e/<slug>.spec.ts`
**Fixtures referenced:**
- seed_workspace (from seed-data-curator)
- mock_email_inbox (from wiremock-stubs)
**Output preview:**
```typescript
// (the generated test file)
```
### Validation
- All intents have matching entries in `e2e/intents/index.ts`.
- All referenced fixtures exist in the project.
- Test compiles (TypeScript / JS lint pass).
- (Optional) test runs once locally and passes against a seeded env.
### Open intents (require new helpers)
- `user_completes_payment_with_apple_pay` - not yet in the intent
mapping. Add to `e2e/intents/index.ts` before this test will run.| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Inline Playwright calls instead of named intents | Tests break on UI refactors; can't migrate frameworks easily. | Always go through the intent mapping. |
| One mega-intent that does 5 things | Hard to compose with other tests; failure attribution unclear. | One intent per user-observable action. |
Intent names that include implementation details (user_clicks_xpath) | Couples the intent to a selector strategy. | Intent names are user-language: user_signs_up, not user_clicks_signup_button. |
| Sharing context via globals | Cross-test pollution; flaky. | Pass ctx through every intent; per-test scope. |
| Authoring 50-step narrative tests | Single-test failure cascades; hard to debug. | Split into smaller stories; reuse intents across multiple smaller tests. |
seed-data-curator,
faker-data,
wiremock-stubs,
msw-handlers,
mountebank-imposters -
fixture sources.karate-testing (in the qa-api-testing plugin) - Karate output format.