| name | weaver-frontend-test |
| description | Run and write frontend tests for Weaver. Use when implementing frontend features, fixing UI bugs, or when asked to test frontend code. Covers Vitest unit tests and Playwright E2E browser tests with screenshots. |
Weaver Frontend Testing
Announce at start: "Running weaver-frontend-test skill."
Decision Tree
What are you doing?
│
├─ Writing NEW UI code → Go to TDD Flow
├─ Fixing a UI bug → Go to Bug Fix Flow
├─ Running existing tests → Go to Run Tests
├─ Need browser screenshot → Go to Playwright Screenshot
└─ Investigating a test failure → Go to Debug Flow
TDD Flow
Iron rule: No implementation without a failing test first.
Step 1: Document
cp docs/testing/test-plan-template.md docs/testing/plans/<feature>-test-plan.md
Fill in the Frontend Test Cases section — both unit (Vitest) and E2E (Playwright).
Step 2: Decide Test Type
Is it pure logic (store, hook, utility, parser)?
→ Vitest unit test
Is it a component's render output or interaction?
→ Vitest + @testing-library/react
Is it a user-facing flow across pages?
→ Playwright E2E test
Does the change affect visual appearance?
→ Playwright E2E with screenshot
Step 3: Write Failing Test
Vitest unit test — for stores, hooks, utils:
Location: app/frontend/src/<path>/__tests__/<module>.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
describe('FeatureName', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should do expected behavior', () => {
const input = ...;
const result = functionUnderTest(input);
expect(result).toBe(expected);
});
it('should handle error case', () => {
expect(() => functionUnderTest(badInput)).toThrow();
});
});
Vitest component test — for React components:
Location: app/frontend/src/components/<path>/__tests__/<Component>.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { ComponentUnderTest } from '../ComponentUnderTest';
vi.mock('@chakra-ui/react', async () => {
const actual = await vi.importActual('@chakra-ui/react');
return { ...actual };
});
describe('ComponentUnderTest', () => {
it('renders with correct content', () => {
render(<ComponentUnderTest prop="value" />);
expect(screen.getByText('expected text')).toBeInTheDocument();
});
it('handles click interaction', async () => {
const onAction = vi.fn();
render(<ComponentUnderTest onAction={onAction} />);
fireEvent.click(screen.getByRole('button', { name: 'Action' }));
expect(onAction).toHaveBeenCalledOnce();
});
});
Playwright E2E test — for full user flows:
Location: app/frontend/e2e/<feature>.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Feature Name', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/dashboard');
await page.waitForLoadState('networkidle');
});
test('user can perform action', async ({ page }) => {
await page.click('[data-testid="action-button"]');
await page.waitForSelector('[data-testid="result"]');
await expect(page.locator('[data-testid="result"]')).toBeVisible();
await page.screenshot({
path: 'e2e/__screenshots__/feature-action-result.png',
fullPage: false,
});
});
test('handles error state', async ({ page }) => {
await page.route('**/api/v1/**', (route) =>
route.fulfill({ status: 500, body: 'Server error' })
);
await page.click('[data-testid="action-button"]');
await expect(page.locator('[data-testid="error-message"]')).toBeVisible();
});
});
Step 4: Confirm RED
cd app/frontend && npx vitest run src/<path>/__tests__/<module>.test.ts
cd app/frontend && npx playwright test e2e/<feature>.spec.ts
Step 5: Implement (minimal)
Write the minimum code to make the test pass.
Step 6: Confirm GREEN
cd app/frontend && npx vitest run src/<path>/__tests__/<module>.test.ts
cd app/frontend && npx playwright test e2e/<feature>.spec.ts
Step 7: Full Suite + Register
make test-frontend-unit
make test-frontend-e2e
Add test IDs to docs/testing/test-registry.md.
Bug Fix Flow
- Write Playwright test or Vitest test reproducing the bug
- Confirm it fails
- Fix the bug
- Confirm test passes
- Run full suite
Run Tests
make test-frontend-unit
cd app/frontend && npx vitest
cd app/frontend && npx vitest run src/stores/__tests__/chatStore.test.ts
cd app/frontend && npx vitest run -t "should handle error"
cd app/frontend && npm run test:coverage
make test-frontend-e2e
cd app/frontend && npx playwright test e2e/user-journey.spec.ts
cd app/frontend && npx playwright test --ui
cd app/frontend && npx playwright test --headed
cd app/frontend && npx playwright test --update-snapshots
cd app/frontend && npx playwright codegen http://localhost:3000
Playwright Screenshot Workflow
When a frontend change affects visual appearance:
cd app/frontend && npx playwright test e2e/<feature>.spec.ts
open app/frontend/e2e/__screenshots__/<name>.png
cd app/frontend && npx playwright test --update-snapshots
In your E2E test, capture screenshots at key points:
await page.screenshot({
path: 'e2e/__screenshots__/feature-state-name.png',
fullPage: false,
});
await expect(page).toHaveScreenshot('expected-state.png', {
maxDiffPixelRatio: 0.01,
});
Debug Flow
cd app/frontend && npx vitest run src/<path>.test.ts --reporter=verbose
cd app/frontend && npx playwright test e2e/<test>.spec.ts --headed --slow-mo=500
cd app/frontend && npx playwright test e2e/<test>.spec.ts --trace on
cd app/frontend && npx playwright show-trace test-results/<test>/trace.zip
cd app/frontend && PWDEBUG=1 npx playwright test e2e/<test>.spec.ts
Project-Specific Patterns
Vitest Config
- Environment:
jsdom (browser APIs simulated)
- Globals:
true (describe/it/expect available without import)
- Setup:
src/test/setup.ts (imports @testing-library/jest-dom)
- Path alias:
@ → src/
Playwright Config
- Browser: Chromium only
- Base URL:
http://localhost:3000
- Timeout: 30s per test
- Screenshots: only on failure
- Traces: on first retry
Mocking API Calls
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'mocked' }),
}));
await page.route('**/api/v1/projects', (route) =>
route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ projects: [] }),
})
);
Testing Zustand Stores
import { useCanvasStore } from '@/stores/canvasStore';
beforeEach(() => {
useCanvasStore.setState(useCanvasStore.getInitialState());
});
it('should update nodes', () => {
const { addNode } = useCanvasStore.getState();
addNode({ id: '1', type: 'card', title: 'Test' });
const { nodes } = useCanvasStore.getState();
expect(nodes).toHaveLength(1);
});
Testing with Konva Canvas
Konva doesn't render in jsdom. For canvas logic:
- Test the data/state layer with Vitest (store operations, layout algorithms)
- Test the visual layer with Playwright (actual rendering in browser)
Auth in E2E Tests
Auth is bypassed in dev mode (AUTH_BYPASS_ENABLED=true). Playwright tests hit localhost directly — no login required.
If testing auth flows specifically:
await page.goto('/login');
await page.click('[data-testid="guest-login"]');
await page.waitForURL('/dashboard');
Checklist Before Done