Files
commitea/apps/desktop/e2e/smoke.spec.ts
Croissant Le Doux 0dd26b8b05 feat: config & onboarding — each teammate connects their own Gitea (Phase A)
The foundation for a shareable team build. Replaces the .env.local-only dev config
with a real, per-teammate connection flow.

main:
- config-store.ts: token encrypted at rest via Electron safeStorage (OS keychain),
  config JSON in userData. Token lives only in main; renderer gets everything but.
- resolveConfig: saved config > .env.local (dev) > null; ignored under COMMITEA_E2E.
  pm-state repo defaults to `${repo}-pm-state`. resetClients() re-reads on change so
  saving config takes effect without a restart. gitea:status gains `demo` (e2e).
- IPC: config:get (no token), config:test (authed read validates token+repo),
  config:set (encrypt+save+reset), config:clear. Model bridge reads config.modelUrl
  and probes reachability — chat is "configured" only if a model actually answers;
  localhost default is dev-only (app.isPackaged gate).

renderer:
- ConnectScreen: real onboarding form (URL/owner/repo/PAT/optional model) → test →
  save. AppShell gates on it: demo → shell (fixtures/e2e); configured → shell (real);
  else → connect. Settings Connection card is real (repo/url/model/sidecar) with
  Reconfigure + Disconnect. Chat cleanly disables with a "no model" state instead of
  the scripted canned reply.

Verified: main + desktop typecheck clean, 14 fixture e2e green (demo mode unchanged),
live onboarding e2e: fresh app → connect form → validated PAT → real board (24 done /
10 open). COMMITEA_NO_ENV_LOCAL + COMMITEA_USERDATA are test hooks for the onboarding path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 02:09:53 -04:00

147 lines
7.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { expect, test } from './fixtures.js'
test('boots into the shell with the Focus screen', async ({ app, window }) => {
await app.expectLoaded()
await expect(window.getByText('Reginald', { exact: true })).toBeVisible()
await expect(window.getByRole('heading', { name: 'Morning service' })).toBeVisible()
await app.screenshot('shell-light')
})
test('Focus shows Now/Next/Later cards and the burn-up cone', async ({ window }) => {
for (const slot of ['Now', 'Next', 'Later']) {
await expect(window.getByText(slot, { exact: true })).toBeVisible()
}
await expect(window.getByText('Fix lifecycle inference on merge events')).toBeVisible()
await expect(window.getByText(/scope · 42 issues/)).toBeVisible()
})
test('Standup renders the drift / plan / nag letter', async ({ app, window }) => {
await app.nav('Standup').click()
await expect(window.getByRole('heading', { name: 'Morning standup' })).toBeVisible()
await expect(window.getByText('Overnight drift')).toBeVisible()
await expect(window.getByText('Stephen', { exact: true })).toBeVisible()
await app.screenshot('standup')
})
test('Board: columns, search filter, and Gantt/Deps tabs', async ({ app, window }) => {
await app.nav('The pot').click()
await expect(window.getByRole('heading', { name: 'The pot' })).toBeVisible()
await expect(window.getByText('Diagnosis', { exact: true })).toBeVisible()
await expect(window.getByText('Steeping', { exact: true })).toBeVisible()
await app.screenshot('board')
await window.getByPlaceholder(/Search the pot/).fill('lifecycle')
await expect(window.getByText('Fix lifecycle inference on merge events')).toBeVisible()
await expect(window.getByText('Dark theme: cone fill too faint')).toBeHidden()
await window.getByPlaceholder(/Search the pot/).fill('')
await window.getByRole('tab', { name: 'Gantt' }).click()
await expect(window.getByText(/80% · Mar 312/)).toBeVisible()
await app.screenshot('gantt')
await window.getByRole('tab', { name: 'Dependencies' }).click()
await expect(window.getByText('critical path', { exact: true })).toBeVisible()
await app.screenshot('deps')
})
test('Runway: milestones, capacity, and drill-ins', async ({ app, window }) => {
await app.nav('Runway').click()
await expect(window.getByRole('heading', { name: 'Runway' })).toBeVisible()
await expect(window.getByText('Pilot-ready')).toBeVisible()
await expect(window.getByText('deadline/hard')).toBeVisible()
await app.screenshot('runway')
// milestone drill-in
await window.getByText('Beta', { exact: true }).click()
await expect(window.getByRole('heading', { name: 'Beta' })).toBeVisible()
await expect(window.getByText('42 issues · est 61d')).toBeVisible()
await app.screenshot('milestone')
await window.getByRole('main').getByRole('button', { name: 'Runway' }).click()
// calibration drill-in
await window.getByRole('button', { name: 'Full report' }).click()
await expect(window.getByRole('heading', { name: 'Calibration' })).toBeVisible()
await expect(window.getByText('The shape of hope')).toBeVisible()
await app.screenshot('calibration')
})
test('Inbox: notifications and mark-all-read', async ({ app, window }) => {
await app.nav('Inbox').click()
await expect(window.getByRole('heading', { name: 'Inbox' })).toBeVisible()
await expect(window.getByText(/window moved/)).toBeVisible()
await app.screenshot('inbox')
await window.getByRole('button', { name: 'Mark all read' }).click()
await expect(window.getByText('all read · nothing here rings twice')).toBeVisible()
})
test('Capture: braindump advances to the interview', async ({ app, window }) => {
await app.nav('Capture').click()
await expect(window.getByText("Tell me what you're planning")).toBeVisible()
await app.screenshot('capture')
await window.getByRole('button', { name: 'Brew tickets' }).click()
await expect(window.getByText(/Interview · 1 of 3/)).toBeVisible()
})
test('Directives: consequence diff + append-only ledger', async ({ app, window }) => {
await app.nav('Directives').click()
await expect(window.getByRole('heading', { name: 'Directives' })).toBeVisible()
await expect(window.getByText('Consequence diff')).toBeVisible()
await expect(window.getByText(/append-only · JSONL in pm-state/)).toBeVisible()
await app.screenshot('directives')
// resolving the pending directive moves it into the ledger
await window.getByRole('button', { name: 'Make it so' }).click()
await expect(window.getByText('Consequence diff')).toBeHidden()
await expect(window.getByText(/Nothing awaits your word/)).toBeVisible()
})
test('Settings: connection, schema, and appearance sync with theme', async ({ app, window }) => {
await app.nav('Settings').click()
await expect(window.getByRole('heading', { name: 'Settings' })).toBeVisible()
await expect(window.getByRole('heading', { name: 'Connection' })).toBeVisible()
await app.screenshot('settings')
// the Evening (dark) radio drives the shared theme
await window.getByText('Evening (dark)').click()
await expect(window.locator('html')).toHaveAttribute('data-theme', 'dark')
})
test('Onboarding: full-window first-run flow with test gate', async ({ app, window }) => {
await app.nav('First run').click()
await expect(window.getByRole('heading', { name: 'Good morning.' })).toBeVisible()
// full-window: no rail wordmark link, brand only
await app.screenshot('onboarding')
await window.getByRole('button', { name: 'Begin' }).click()
await expect(window.getByRole('heading', { name: 'Your Gitea' })).toBeVisible()
// Continue is gated until the connection test passes
await expect(window.getByRole('button', { name: 'Continue' })).toBeDisabled()
await window.getByRole('button', { name: 'Test connection' }).click()
await expect(window.getByText(/connected · 3 repos visible/)).toBeVisible()
await expect(window.getByRole('button', { name: 'Continue' })).toBeEnabled()
})
test('issue drill-in from a Focus card and back-stack of one', async ({ app, window }) => {
await window.getByRole('link', { name: 'Fix lifecycle inference on merge events' }).click()
await expect(window.getByText('#87 · stephen/commitea')).toBeVisible()
await expect(window.getByText('Machine-derived')).toBeVisible()
await app.screenshot('issue')
await window.getByRole('button', { name: 'Back' }).click()
await expect(window.getByRole('heading', { name: 'Morning service' })).toBeVisible()
})
test('Evening service toggle flips the document theme', async ({ app, window }) => {
await window.getByText('Evening service', { exact: true }).click()
await expect(window.locator('html')).toHaveAttribute('data-theme', 'dark')
await app.screenshot('shell-dark')
})
test('offline sim shows the banner and disables the composer', async ({ window }) => {
await window.getByRole('button', { name: /Connection/ }).click()
await expect(window.getByText(/Gitea isn.t answering/)).toBeVisible()
await expect(window.getByRole('textbox')).toBeDisabled()
})
test('exposes the commitea preload API', async ({ app }) => {
const api = await app.preloadApi()
expect(api).toBeDefined()
expect(api).toHaveProperty('platform')
})