Files
tf_code/packages/app/e2e/AGENTS.md

4.8 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 page
  • sdk - OpenCode SDK client for API calls
  • gotoSession(sessionID?) - Navigate to session

Helper Functions

Actions (actions.ts):

  • openPalette(page) - Open command palette
  • openSettings(page) - Open settings dialog
  • closeDialog(page, dialog) - Close any dialog
  • openSidebar(page) / closeSidebar(page) - Toggle sidebar
  • withSession(sdk, title, callback) - Create temp session
  • withProject(...) - Create temp project/workspace
  • sessionIDFromUrl(url) - Read session ID from URL
  • slugFromUrl(url) - Read workspace slug from URL
  • waitSlug(page, skip?) - Wait for resolved workspace slug
  • trackSession(sessionID, directory?) - Register session for fixture cleanup
  • trackDirectory(directory) - Register directory for fixture cleanup
  • clickListItem(container, filter) - Click list item by key/text

Selectors (selectors.ts):

  • promptSelector - Prompt input
  • terminalSelector - Terminal panel
  • sessionItemSelector(id) - Session in sidebar
  • listItemSelector - Generic list items

Utils (utils.ts):

  • modKey - Meta (Mac) or Control (Linux/Win)
  • serverUrl - Backend server URL
  • sessionPath(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. Prefer fixture-managed cleanup:

test("test with cleanup", async ({ page, sdk, gotoSession }) => {
  await withSession(sdk, "test session", async (session) => {
    await gotoSession(session.id)
    // Test code...
  }) // Auto-deletes session
})
  • Prefer withSession(...) for temp sessions
  • In withProject(...) tests that create sessions or extra workspaces, call trackSession(sessionID, directory?) and trackDirectory(directory)
  • This lets fixture teardown abort, wait for idle, and clean up safely under CI concurrency
  • Avoid calling sdk.session.delete(...) directly

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

  1. Choose appropriate folder or create new one
  2. Import from ../fixtures
  3. Use helper functions from ../actions and ../selectors
  4. When validating routing, use shared helpers from ../actions. Workspace URL slugs can be canonicalized on Windows, so assert against canonical or resolved workspace slugs.
  5. Clean up any created resources
  6. Use specific selectors (avoid CSS classes)
  7. 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.