3.9 KiB
3.9 KiB
E2E Testing Guide
Build/Lint/Test Commands
# Run all e2e tests
bun test:e2e
# Run specific test file
bun test:e2e -- app/home.spec.ts
# Run single test by title
bun test:e2e -- -g "home renders and shows core entrypoints"
# Run tests with UI mode (for debugging)
bun test:e2e:ui
# Run tests locally with full server setup
bun test:e2e:local
# View test report
bun test:e2e:report
# Typecheck
bun typecheck
Test Structure
All tests live in packages/app/e2e/:
e2e/
├── fixtures.ts # Test fixtures (test, expect, gotoSession, sdk)
├── actions.ts # Reusable action helpers
├── selectors.ts # DOM selectors
├── utils.ts # Utilities (serverUrl, modKey, path helpers)
└── [feature]/
└── *.spec.ts # Test files
Test Patterns
Basic Test Structure
import { test, expect } from "../fixtures"
import { promptSelector } from "../selectors"
import { withSession } from "../actions"
test("test description", async ({ page, sdk, gotoSession }) => {
await gotoSession() // or gotoSession(sessionID)
// Your test code
await expect(page.locator(promptSelector)).toBeVisible()
})
Using Fixtures
page- Playwright pagesdk- OpenCode SDK client for API callsgotoSession(sessionID?)- Navigate to session
Helper Functions
Actions (actions.ts):
openPalette(page)- Open command paletteopenSettings(page)- Open settings dialogcloseDialog(page, dialog)- Close any dialogopenSidebar(page)/closeSidebar(page)- Toggle sidebarwithSession(sdk, title, callback)- Create temp sessionclickListItem(container, filter)- Click list item by key/text
Selectors (selectors.ts):
promptSelector- Prompt inputterminalSelector- Terminal panelsessionItemSelector(id)- Session in sidebarlistItemSelector- Generic list items
Utils (utils.ts):
modKey- Meta (Mac) or Control (Linux/Win)serverUrl- Backend server URLsessionPath(dir, id?)- Build session URL
Code Style Guidelines
Imports
Always import from ../fixtures, not @playwright/test:
// ✅ Good
import { test, expect } from "../fixtures"
// ❌ Bad
import { test, expect } from "@playwright/test"
Naming Conventions
- Test files:
feature-name.spec.ts - Test names: lowercase, descriptive:
"sidebar can be toggled" - Variables: camelCase
- Constants: SCREAMING_SNAKE_CASE
Error Handling
Tests should clean up after themselves:
test("test with cleanup", async ({ page, sdk, gotoSession }) => {
await withSession(sdk, "test session", async (session) => {
await gotoSession(session.id)
// Test code...
}) // Auto-deletes session
})
Timeouts
Default: 60s per test, 10s per assertion. Override when needed:
test.setTimeout(120_000) // For long LLM operations
test("slow test", async () => {
await expect.poll(() => check(), { timeout: 90_000 }).toBe(true)
})
Selectors
Use data-component, data-action, or semantic roles:
// ✅ Good
await page.locator('[data-component="prompt-input"]').click()
await page.getByRole("button", { name: "Open settings" }).click()
// ❌ Bad
await page.locator(".css-class-name").click()
await page.locator("#id-name").click()
Keyboard Shortcuts
Use modKey for cross-platform compatibility:
import { modKey } from "../utils"
await page.keyboard.press(`${modKey}+B`) // Toggle sidebar
await page.keyboard.press(`${modKey}+Comma`) // Open settings
Writing New Tests
- Choose appropriate folder or create new one
- Import from
../fixtures - Use helper functions from
../actionsand../selectors - Clean up any created resources
- Use specific selectors (avoid CSS classes)
- Test one feature per test file
Local Development
For UI debugging, use:
bun test:e2e:ui
This opens Playwright's interactive UI for step-through debugging.