e2e-test
Generate Playwright E2E tests following project conventions (Tauri fixture, storage isolation, helpers).
SKILL.md.claude/skills/e2e-test/SKILL.md
GitHubGenerate Playwright E2E tests for the specified feature or user flow.
Input: Feature or flow to test. Example: /e2e-test profile selection persistence across reload
Steps
- Determine spec file path:
e2e/<descriptive-name>.spec.ts - Read relevant source to understand the UI flow being tested
- Generate the spec following project conventions (see below)
- Verify syntax:
npx playwright test e2e/<spec-file> --list(lists tests without running)
Project Conventions
- Import test and expect from the Tauri fixture, NOT from
@playwright/testdirectly:import { test, expect } from './fixtures/tauri'; - This fixture auto-injects
__TAURI__mock and__ENDSTATE_MOCK_ENGINE__into the browser context - Use helpers from
e2e/helpers/ui-mode.ts:forceAdvancedMode(page)— enable sidebar navigation (call inaddInitScriptbeforegoto)forceDefaultMode(page)— Overview-centric modegoToApplyPage(page)— navigate to and expand Apply cardgoToCapturePage(page)— navigate to and expand Capture cardgoToVerifyPage(page)— navigate to and expand Verify cardseedProfileSettings(page)— seed profile viaaddInitScriptbeforegotoseedProfilesViaHook(page)— seed profiles after page loads via E2E hook
- Storage is isolated:
VITE_STORAGE_NS=testprefixes all localStorage keys withtest: - Configure Tauri mock overrides via
test.use({ tauriMockOptions: { invoke: { ... } } }) - App always starts on Overview page — tests must navigate explicitly
Mock Pattern for Tauri Commands
test.use({
tauriMockOptions: {
invoke: {
list_manifest_files: () => [
{ name: 'my-profile', path: 'C:\\profiles\\my-profile.jsonc', displayName: 'My Profile' }
],
check_file_exists: () => true,
}
}
});
Spec Template
import { test, expect } from './fixtures/tauri';
import { forceDefaultMode, goToApplyPage } from './helpers/ui-mode';
test.describe('Feature Name', () => {
test.use({
tauriMockOptions: {
invoke: {
list_manifest_files: () => [],
}
}
});
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForLoadState('networkidle');
});
test('describes expected behavior', async ({ page }) => {
// Arrange: set up state
// Act: user interactions
// Assert: verify outcomes
await expect(page.getByRole('heading', { name: /expected/i })).toBeVisible();
});
});
Known Pitfalls
- Radix Select cannot be automated with standard click → option. Use
page.dispatchEvent()workaround or evaluate-based selection. - Always
waitForLoadState('networkidle')aftergoto('/')before interacting - Use
{ timeout: 5000 }on assertions that depend on async rendering - Prefer
getByRoleandgetByTextlocators. Usedata-testidlocators only for structural elements (cards, panels)