원클릭으로
e2e-test
Run E2E browser tests via Tidewave browser_eval against the running Phoenix dev server
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Run E2E browser tests via Tidewave browser_eval against the running Phoenix dev server
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Create or manage Architecture Decision Records (ADRs) in docs/decisions/
Use this skill working with Ash Framework or any of its extensions. Always consult this when making any domain changes, features or fixes.
Record a development session summary in the project devlog
Inspect LiveView state (assigns, components) via Phoenix.LiveView.Debug in project_eval
Morning briefing — where we left off, what to work on today. Fast, compact report from canonical sources.
Manage plan lifecycle — list plans, mark completed/abandoned, move between directories
| name | e2e-test |
| description | Run E2E browser tests via Tidewave browser_eval against the running Phoenix dev server |
| argument-hint | [path] e.g. 'pos/terminal-basic', 'pos/terminal-basic#3', 'all', 'list' |
Run markdown-based E2E browser tests using Tidewave browser_eval against the running Phoenix dev server.
All E2E tests live in docs/e2e/. Each .md file contains numbered scenarios.
/e2e [path]Run all scenarios in a test file. Path is relative to docs/e2e/.
/e2e pos/terminal-basic # Run all 10 scenarios
/e2e staff/members-list # Run all scenarios
/e2e public/checkout-link # Run all scenarios
/e2e [path]#[number]Run only one scenario from a test file.
/e2e pos/terminal-basic#3 # Run only Scenario 3: Product Search
/e2e pos/terminal-basic#8-10 # Run Scenarios 8 through 10
/e2e listList all test files with scenario counts and last-verified dates.
/e2e statusShow overall test suite status — which tests have been verified, which are pending.
When running tests, follow these steps precisely:
Read the .md file from docs/e2e/{path}.md. Parse the frontmatter for:
Test files specify an Auth type and a Persona. Use the appropriate method:
| Auth Type | Action |
|---|---|
clerk | Use Clerk login (see below) |
public (token) | Generate/use token, navigate directly |
pos_device + pos_session | Check if already on /pos/ — if redirected to activate/login, handle it |
none | Navigate directly |
Persona emails use +clerk_test subaddress — Clerk verification codes are always 424242.
// 1. Sign out from Clerk JS
await browser.eval(async () => {
if (window.Clerk) await window.Clerk.signOut();
});
await browser.wait(1000);
// 2. Navigate to sign-in page
await browser.reload('/clerk/sign-in');
await browser.wait(4000); // Wait for Clerk widget to load
// 3. Fill email (use snapshot refs — don't use broad selectors)
// Snapshot → find email textbox ref → fill → click Continue
const snapshot = await browser.snapshot(browser.locator('#clerk-sign-in'));
// Find textbox "Email address" ref from snapshot
await browser.fill(browser.getBySnapshotRef('eXX'), 'e2e-admin+clerk_test@example.com');
// Find "Continue" button ref from snapshot
await browser.click(browser.getBySnapshotRef('eYY'));
await browser.wait(3000);
// 4. Password step — snapshot again, find password textbox + Continue
await browser.fill(browser.getBySnapshotRef('eZZ'), 'E2eTest!2026');
await browser.click(browser.getBySnapshotRef('eWW'));
await browser.wait(4000);
// 5. Client Trust verification — snapshot, find code input + Continue
await browser.fill(browser.getBySnapshotRef('eVV'), '424242');
await browser.click(browser.getBySnapshotRef('eUU'));
await browser.wait(5000);
// 6. Verify redirected away from /clerk/sign-in
IMPORTANT: The Clerk widget renders dynamic refs — always take a fresh snapshot before each step. Don't reuse refs from earlier snapshots.
Available personas (from GsNet.E2E.Personas):
| Key | Password | Tenant Role | Org Role | Interfaces | |
|---|---|---|---|---|---|
admin | e2e-admin+clerk_test@example.com | E2eTest!2026 | owner | executive @ company | admin, hq, staff, customer |
staff | e2e-staff+clerk_test@example.com | E2eTest!2026 | member | staff @ center | staff, customer |
manager | e2e-manager+clerk_test@example.com | E2eTest!2026 | member | center_manager @ center | staff, customer |
customer | e2e-customer+clerk_test@example.com | E2eTest!2026 | member | — (no org role) | customer |
hq | e2e-hq+clerk_test@example.com | E2eTest!2026 | member | director @ division | hq, staff, customer |
affiliate | e2e-affiliate+clerk_test@example.com | E2eTest!2026 | member | — (no org role) | affiliate, customer |
hr_manager | e2e-hr+clerk_test@example.com | E2eTest!2026 | member | director @ HR Team | hq, staff, customer |
Provisioned by mix gs_net.e2e_setup (creates Clerk accounts + GsNet records).
To fully sign out both Clerk JS and Phoenix session:
// 1. Sign out from Clerk JS
await browser.eval(async () => {
if (window.Clerk) await window.Clerk.signOut();
});
await browser.wait(1000);
// 2. Clear Phoenix session
await browser.reload('/clerk/session-destroy');
await browser.wait(500);
For each scenario (or the selected subset):
## Scenario N: [Name] headerAfter all scenarios complete, print a summary table:
## Results: pos/terminal-basic
| # | Scenario | Result | Notes |
|---|----------|--------|-------|
| 1 | Product Grid Loads | PASS | |
| 2 | Category Filtering | PASS | |
| 3 | Product Search | FAIL | Search input not found |
...
Total: 8/10 passed
If ALL scenarios pass, update the test file's Last Verified date to today.
Use these exact patterns to translate markdown steps into browser_eval calls:
| Step | Tidewave |
|---|---|
Navigate to /path | browser.reload("/path") |
| Snapshot [element] | console.log(await browser.snapshot(browser.locator('selector'))) |
| Snapshot page | console.log(await browser.snapshot(browser.locator('body'))) |
| Verify [element] visible with text "[text]" | Snapshot → check output contains text |
| Verify [element] exists | Snapshot → check element appears in tree |
| Click [button with text] | await browser.click(browser.locator('button', { hasText: 'text' })) |
| Click [link with text] | await browser.click(browser.locator('a', { hasText: 'text' })) |
| Click [element by ref] | await browser.click(browser.getBySnapshotRef('eXX')) |
| Fill "[field name]" with "[value]" | await browser.fill(browser.locator('input[name="field"]'), "value") |
| Fill [placeholder text] with "[value]" | await browser.fill(browser.locator('input[placeholder="text"]'), "value") |
| Wait [N] ms | await browser.wait(N) |
| Select "[option]" from "[select]" | Use browser.eval with dispatchEvent |
| Sign out from Clerk | await browser.eval(async () => { if (window.Clerk) await window.Clerk.signOut(); }) |
| Fill verification code | await browser.fill(codeInputRef, '424242') |
Always prefer snapshots over DOM scripting:
eXX)browser.getBySnapshotRef('eXX') to interact with found elementsbrowser.click() and browser.fill() over browser.eval() where possibleThis avoids brittle CSS selectors and matches how the test files describe elements (by text/role, not by class).
Clerk widget note: The Clerk sign-in widget re-renders between steps (email → password → verification). Always take a fresh snapshot after each transition — refs from previous steps will be stale.
/clerk/sign-in for the widget to mount.Test files mirror the app's route structure:
docs/e2e/
├── README.md # This guide
│
├── auth/ # Authentication flows
│ ├── clerk-login.md # 6 scenarios — Clerk SSO login/logout
│ └── pos-device-auth.md # 6 scenarios — POS device activation + PIN
│
├── pos/ # POS terminal (/pos/*)
│ ├── terminal-basic.md # 10 scenarios — product grid, cart, payment
│ └── terminal-membership.md # 5 scenarios — membership + agreement signing
│
├── staff/ # Staff portal (/staff/*)
│ ├── members-list.md # 10 scenarios — Cinder table, search, filter
│ ├── dashboard.md # 4 scenarios — staff landing, navigation
│ └── member-detail.md # 6 scenarios — member view, transfers, status
│
├── public/ # Public pages (no auth)
│ └── checkout-link.md # 10 scenarios — review → payment → sign → confirm
│
├── admin/ # Admin console (/admin/*)
│ ├── catalog-management.md # 8 scenarios — product CRUD, pricing, publishing
│ ├── organization.md # 5 scenarios — org tree view, edit units
│ ├── users-invites.md # 6 scenarios — user list, invite code CRUD
│ └── events-roster.md # 6 scenarios — roster, attendance, enrollment
│
├── customer/ # Customer portal (/my/*)
│ ├── workshop-catalog.md # 5 scenarios — browse, filter, enroll
│ └── events.md # 5 scenarios — my events, deferred registration
│
└── hq/ # HQ dashboard (/hq/*) — future
kebab-case.md, match the feature/page namepos/terminal-basic.md, NOT pos/terminal/basic.mdsmoke/, regression/ folders)Every test file MUST have this frontmatter:
# E2E: [Page/Feature Name]
> **Route**: /path/to/page
> **Auth**: clerk | pos_device | pos_session | public (token) | none
> **Persona**: admin | staff | manager
> **Prerequisites**: [what must be true before tests run]
> **Last Verified**: YYYY-MM-DD | —
When creating a new E2E test file:
### Scenario 1:, ### Scenario 2:, etc.**Expected**: after each scenario's steps--- horizontal rules/e2e list # Show all test files
/e2e status # Overall pass/fail status
/e2e pos/terminal-basic # Run all scenarios in file
/e2e pos/terminal-basic#3 # Run one scenario
/e2e pos/terminal-basic#8-10 # Run scenario range
| Concept | Value |
|---|---|
| Test email suffix | +clerk_test (e.g., user+clerk_test@example.com) |
| Verification code | 424242 (always, no email sent) |
| Password (all personas) | E2eTest!2026 |
| Provisioning | mix gs_net.e2e_setup |
| Clerk docs | https://clerk.com/docs/guides/development/testing/test-emails-and-phones |