---
model: sonnet
---

# /tas-functest-web $ARGUMENTS

Role: SE / QA
Generate all Playwright automation test scripts for a web Feature.

## IMPORTANT — Layer 2: Web Functional Test Scripts
- Create Playwright test scripts from the FuncTest spec (markdown)
- Scripts located in `apps/web/e2e/features/`
- Reusable helpers exported for Layer 3 (E2E) reuse
- Hardcoded data in tests, credentials from `.env`
- Cross-viewport testing (mobile, tablet, desktop)

## Actions

### Step 1: Identify Feature
1. `$ARGUMENTS` is Feature ID or file path
2. If not provided: scan `docs/features/{CODE}-Feature-*/{CODE}-Feature-*.md` for Features with status `In Development`
3. Read Feature file to get Feature ID + slug

### Step 2: Check Prerequisites
1. Check `apps/web/` exists. If not → report:
   > "Web app doesn't exist. If project only has mobile, use `/tas-functest-mobile`."
   > STOP, DO NOT create file.
2. Find the FuncTest spec in Feature directory:
   ```
   docs/features/{CODE}-Feature-{NNN}-{slug}/{CODE}-FuncTest.md
   ```
3. If missing → report:
   > "No FuncTest spec for this Feature. Run `/tas-functest {Feature-ID}` first."
4. Read `apps/web/playwright.config.ts` (if exists) for current config
5. Read `apps/web/e2e/helpers/` for patterns
6. Read the **`### Locator Map`** (Web stack) from the Feature-Technical file — this is the authoritative testid → element table. Use it for every `getByTestId`. If a TC needs an element absent from the Map → **warn** (signals source/plan gap; `/tas-dev` should have back-filled it) and do NOT invent a testid.

### Step 2.5: TC ID rule (CRITICAL — chain contract)

- The FuncTest §4 **TC ID is the SSoT**: `F{NNN}_AC{N}_{CAT}_{SEQ}` (CAT ∈ `GUI|VAL|BL|EXC|SEC|INT|PERF`), e.g. `F001_AC1_BL_001`.
- Each `test.describe(...)` MUST reuse the §4 TC ID **verbatim** as its title. Do NOT synthesize a new ID (no `_FT_`, no `{PROJECT}` prefix, no `_H/_E/_N` modifier) — a synthesized title never maps back in `/tas-functest-report` and the result is dropped as "unmatched".
- TC ID must appear at the **start** of the title so the report can parse it: `^(F\d+_AC\d+_[A-Z]+_\d+[a-z]?)`. BVA/EP variants keep their trailing letter (`F001_AC1_VAL_001a`) — each is a separate §4 TC, one `test.describe` each.
- Viewport runs are **variants of the same TC ID**, not new tests — suffix in brackets: `F001_AC1_BL_001 [desktop]`.

### Step 3: Aggregate FT Test Cases
- From the FuncTest spec, list all FT test cases grouped by AC
- Pull test data requirements from spec

### Step 4: Generate Test Scripts

**File output**: `apps/web/e2e/features/{feature-slug}/{feature-slug}.func.spec.ts`

**Structure**:
```typescript
/**
 * Functional Tests: {Feature Name}
 * Feature: {Feature_ID}
 * Stack: web
 *
 * Generated by /tas-functest-web
 * Spec: docs/features/{feature-dir}/{CODE}-FuncTest.md
 */

import { test, expect } from '@playwright/test';
import { loadTestData, getCredentials } from '../../helpers/data-loader';

const testData = loadTestData();

test.describe('{Feature Name}', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/target-page');
  });

  // AC Reference: AC-1 — title = FuncTest §4 TC ID verbatim
  test.describe('F001_AC1_BL_001', () => {
    test('should {description from spec}', async ({ page }) => {
      // Given: {precondition}
      // When: {action} — testid from Feature-Technical ### Locator Map
      await page.getByTestId('btn_auth_submit').click();
      // Then: {expected}
      await expect(page.getByTestId('lbl_dashboard_title')).toBeVisible();
    });
  });

  // Viewport testing — SAME TC ID, viewport as a bracketed variant (not a new ID)
  test.describe('F001_AC1_BL_001', () => {
    const viewports = [
      { width: 375, height: 812, name: 'mobile' },
      { width: 768, height: 1024, name: 'tablet' },
      { width: 1280, height: 720, name: 'desktop' },
    ];

    for (const vp of viewports) {
      // Title stays parseable: "F001_AC1_BL_001 [desktop]"
      test(`F001_AC1_BL_001 [${vp.name}]`, async ({ page }) => {
        await page.setViewportSize({ width: vp.width, height: vp.height });
      });
    }
  });
});
```

### Step 4.5: Write-back Execution Locators into the FuncTest spec

Overwrite the §4 **"Execution Locators"** column (placeholder `_Phase 3 — SE_`) in `{CODE}-FuncTest.md`. For each TC, translate its Logical Steps into the **Action DSL** from `.tas/rules/common/locator-naming.md`, using testids from the Feature-Technical `### Locator Map`. Example:

```
type:ipt_auth_email "{value}"
type:ipt_auth_password "{value}"
click:btn_auth_submit
wait_invisible:ldn_auth_processing
assert:lbl_dashboard_title == visible
```

Only edit the Execution Locators cells — do not touch other columns. If a step's element is missing from the Locator Map, leave a `# TODO: testid missing in Locator Map` marker in that cell and warn (do not invent a testid).

### Step 5: Generate Feature Helpers
`apps/web/e2e/features/{feature-slug}/helpers.ts`:
```typescript
import { Page, expect } from '@playwright/test';

export async function {featureSpecificHelper}(page: Page) {
  // Helper logic extracted from functional tests
}
```

### Step 6: Generate/Update Data Loader (if missing)
```typescript
type TestEnv = 'dev' | 'staging' | 'prod';

export function loadTestData(env?: TestEnv) {
  const targetEnv = env || (process.env.TEST_ENV as TestEnv) || 'dev';
  return require(`../data/test-data.${targetEnv}.json`);
}

export function getCredentials(userType = 'default') {
  const data = loadTestData();
  const user = data.users[userType] || data.users.default;
  return {
    email: user.email,
    password: process.env.TEST_USER_PASSWORD || '',
  };
}
```

### Step 7: Update package.json Script
```json
"functest:web:{feature-slug}": "npx playwright test e2e/features/{feature-slug}"
```

### Step 8: Generate Index
`apps/web/e2e/features/{feature-slug}/index.ts`:
```typescript
export * from './helpers';
```

## File Structure Output
```
apps/web/e2e/features/
└── {feature-slug}/
    ├── {feature-slug}.func.spec.ts
    ├── helpers.ts
    └── index.ts
```

## Selector Strategy (Playwright)
- **Source of testids**: the Feature-Technical `### Locator Map` (Web stack). Never invent a testid — if one is missing there, warn (it means `/tas-dev` didn't inject/back-fill it).
- **Priority**: `data-testid` → `page.getByTestId('btn_auth_submit')`
- **Text**: `page.getByText('Welcome')`
- **Role**: `page.getByRole('button', { name: 'Submit' })`
- **Label**: `page.getByLabel('Email')`
- DO NOT use traditional CSS selectors unless required

## Cross-Browser
- Chromium (required)
- Firefox (required)
- WebKit (required)
- Config in `playwright.config.ts` projects section

## Test Data Convention
- **Hardcoded values**: Directly in test
- **Credentials**: `getCredentials()` → password from process.env
- **Environment data**: `loadTestData()` → from test-data.{env}.json
- **NEVER** hardcode passwords/tokens

## Run Tests
```bash
yarn functest:web:{feature-slug}
npx playwright test e2e/features/{feature-slug}
npx playwright test --project=firefox e2e/features/{feature-slug}
npx playwright test --ui e2e/features/{feature-slug}
```

## Principles
- Script MUST be runnable directly from CLI
- Each describe block reuses the FuncTest §4 TC ID **verbatim** (`F{NNN}_AC{N}_{CAT}_{SEQ}`) as its title — never synthesize a new ID
- Helpers MUST export for Layer 3 reuse
- DO NOT import from Layer 3 (`flows/`)
- If `apps/web/` doesn't exist → graceful error
- Tests must work on all viewports

## References

- `.tas/rules/common/locator-naming.md` — element prefix system, action DSL, state assertions (Phase 3 mapping)
- Feature-Technical `### Locator Map` (Web stack) — authoritative testid → element table (SSoT)
- `.tas/templates/Func-Test.md` — §4 table structure (input: Logical Steps + Execution Locators columns)
