test(web): connect a Workspace in the e2e boot path and refresh goldens
The startup-selection flow leaves a fresh world (no Workspace) in the locked view state, so every e2e scenario that types into the composer now connects one first via the shared connectFreshWorkspace helper (hero picker create-by-name dialog; the default 'workspace' name keeps the session-header cwd assertions intact). Golden refreshes carry the current composer chrome: the plan/model control seats are empty until their owning plugins register (the seats shipped without occupants on this branch), the sidebar shows the connected workspace group pre-send, and the bash details material renders Input/code/Output as separate nodes. The cancel scenario polls the frozen-partial swap instead of counting synchronously — the abort frame reaches the browser over SSE after the host settles.
This commit is contained in:
@@ -18,7 +18,7 @@ import {
|
|||||||
captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const FIXTURE = fileURLToPath(new URL('./snapshots/code-mode-round/session.jsonl', import.meta.url))
|
const FIXTURE = fileURLToPath(new URL('./snapshots/code-mode-round/session.jsonl', import.meta.url))
|
||||||
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/code-mode-round/ui.expected.md', import.meta.url))
|
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/code-mode-round/ui.expected.md', import.meta.url))
|
||||||
@@ -48,6 +48,8 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => {
|
|||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||||
|
// Fresh world: connect a Workspace so the composer scenarios start live.
|
||||||
|
await connectFreshWorkspace(page)
|
||||||
}, 120_000)
|
}, 120_000)
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import {
|
|||||||
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/lifecycle-chrome', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/lifecycle-chrome', import.meta.url))
|
||||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||||
@@ -47,6 +47,8 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
|
|||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||||
|
// Fresh world: connect a Workspace so the composer scenarios start live.
|
||||||
|
await connectFreshWorkspace(page)
|
||||||
}, 120_000)
|
}, 120_000)
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/live-interactions', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/live-interactions', import.meta.url))
|
||||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||||
@@ -94,6 +94,8 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
|
|||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||||
|
// Fresh world: connect a Workspace so the composer scenarios start live.
|
||||||
|
await connectFreshWorkspace(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -134,9 +136,11 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
|
|||||||
await page.getByRole('button', { name: 'Stop generating' }).click()
|
await page.getByRole('button', { name: 'Stop generating' }).click()
|
||||||
await settled
|
await settled
|
||||||
expect(turnEndReasons(sessionEvents).at(-1)).toBe('aborted')
|
expect(turnEndReasons(sessionEvents).at(-1)).toBe('aborted')
|
||||||
// Composer recovered; no streaming node lingers.
|
// Composer recovered; no streaming node lingers. The host settled first
|
||||||
|
// (awaited above), but the abort frame reaches the browser over SSE — the
|
||||||
|
// frozen-partial swap is eventually consistent, so poll rather than count.
|
||||||
await expect.poll(() => page.locator('textarea').first().isEnabled(), { timeout: 10_000 }).toBe(true)
|
await expect.poll(() => page.locator('textarea').first().isEnabled(), { timeout: 10_000 }).toBe(true)
|
||||||
expect(await page.locator('[data-streaming="true"]').count()).toBe(0)
|
await expect.poll(() => page.locator('[data-streaming="true"]').count(), { timeout: 10_000 }).toBe(0)
|
||||||
// Golden of the aborted end-state: the prompt bubble plus the frozen
|
// Golden of the aborted end-state: the prompt bubble plus the frozen
|
||||||
// partial ('partial' is the hang entry's replayed prefix) and no more.
|
// partial ('partial' is the hang entry's replayed prefix) and no more.
|
||||||
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold!.workspaceCwd)
|
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold!.workspaceCwd)
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/question-composer', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/question-composer', import.meta.url))
|
||||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||||
@@ -45,6 +45,8 @@ describe('web e2e: resident question composer round trip', () => {
|
|||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||||
|
// Fresh world: connect a Workspace so the composer scenarios start live.
|
||||||
|
await connectFreshWorkspace(page)
|
||||||
}, 120_000)
|
}, 120_000)
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/fresh-round-trip', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/fresh-round-trip', import.meta.url))
|
||||||
const FIXTURE = fileURLToPath(new URL('./snapshots/fresh-round-trip/session.jsonl', import.meta.url))
|
const FIXTURE = fileURLToPath(new URL('./snapshots/fresh-round-trip/session.jsonl', import.meta.url))
|
||||||
@@ -47,6 +47,8 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
|||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||||
|
// Fresh world: connect a Workspace so the composer scenarios start live.
|
||||||
|
await connectFreshWorkspace(page)
|
||||||
}, 120_000)
|
}, 120_000)
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import { pathToFileURL } from 'node:url'
|
|||||||
import type { Browser, Page } from 'playwright'
|
import type { Browser, Page } from 'playwright'
|
||||||
import { chromium } from 'playwright'
|
import { chromium } from 'playwright'
|
||||||
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
||||||
import { REPO_ROOT, probeFreePort, requireDist, saveFailureShot } from './support.ts'
|
import { REPO_ROOT, connectFreshWorkspace, probeFreePort, requireDist, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
/** Repo-root .env → process.env (never overrides an already-set variable). */
|
/** Repo-root .env → process.env (never overrides an already-set variable). */
|
||||||
function loadRootEnv(): void {
|
function loadRootEnv(): void {
|
||||||
@@ -404,6 +404,8 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY || notReady.length > 0)('web smoke
|
|||||||
|
|
||||||
it('2+3 empty-state first send completes a real model round', async () => {
|
it('2+3 empty-state first send completes a real model round', async () => {
|
||||||
onTestFailed(() => saveFailureShot(page, 'w5-first-round'))
|
onTestFailed(() => saveFailureShot(page, 'w5-first-round'))
|
||||||
|
// Fresh world: connect a Workspace so the composer starts live.
|
||||||
|
await connectFreshWorkspace(page)
|
||||||
const input = page.locator('textarea').first()
|
const input = page.locator('textarea').first()
|
||||||
await input.waitFor({ timeout: 10_000 })
|
await input.waitFor({ timeout: 10_000 })
|
||||||
await screen(page, '02-empty-state')
|
await screen(page, '02-empty-state')
|
||||||
|
|||||||
@@ -23,13 +23,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -19,13 +19,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
- button "Search sessions":
|
- button "Search sessions":
|
||||||
- img
|
- img
|
||||||
- textbox "Search name, keywords..."
|
- textbox "Search name, keywords..."
|
||||||
- tree "Sessions": No sessions yet
|
- tree "Sessions":
|
||||||
|
- treeitem "workspace 1 session" [expanded]:
|
||||||
|
- img
|
||||||
|
- text: workspace 1 session
|
||||||
|
- treeitem "New Session now" [selected]
|
||||||
- button "设置":
|
- button "设置":
|
||||||
- img
|
- img
|
||||||
- text: 设置
|
- text: 设置
|
||||||
@@ -23,13 +27,10 @@
|
|||||||
- textbox "Describe what you want to build"
|
- textbox "Describe what you want to build"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
- text: 详情
|
||||||
|
- button "关闭详情"
|
||||||
|
- text: 点击消息流中的工具行查看详情
|
||||||
|
|||||||
@@ -15,13 +15,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -12,13 +12,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -10,13 +10,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -15,13 +15,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
- text: bash
|
- text: bash
|
||||||
- button "关闭详情"
|
- button "关闭详情"
|
||||||
- text: "Input { \"command\": \"echo NAVIGATION_OK\", \"description\": \"Print NAVIGATION_OK\" } Output NAVIGATION_OK"
|
- text: Input
|
||||||
|
- code: "{ \"command\": \"echo NAVIGATION_OK\", \"description\": \"Print NAVIGATION_OK\" }"
|
||||||
|
- text: Output NAVIGATION_OK
|
||||||
|
|||||||
@@ -21,13 +21,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -24,13 +24,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -21,13 +21,7 @@
|
|||||||
- textbox "Message the agent"
|
- textbox "Message the agent"
|
||||||
- button "Add attachment":
|
- button "Add attachment":
|
||||||
- img
|
- img
|
||||||
- combobox "Plan mode":
|
|
||||||
- option "Plan" [selected]
|
|
||||||
- option "Agent"
|
|
||||||
- combobox "Access mode":
|
- combobox "Access mode":
|
||||||
- option "Read-only" [selected]
|
- option "Read-only" [selected]
|
||||||
- option "Read-write"
|
- option "Read-write"
|
||||||
- combobox "Model":
|
|
||||||
- option "DeepSeek-V4-Pro High" [selected]
|
|
||||||
- option "DeepSeek-V4-Pro"
|
|
||||||
- button "Send message" [disabled]
|
- button "Send message" [disabled]
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/steering', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/steering', import.meta.url))
|
||||||
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
const FIXTURE = join(SNAPSHOT_DIR, 'session.jsonl')
|
||||||
@@ -71,6 +71,8 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
|
|||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||||
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
await page.waitForSelector('[class*="frame"]', { timeout: 30_000 })
|
||||||
|
// Fresh world: connect a Workspace so the composer scenarios start live.
|
||||||
|
await connectFreshWorkspace(page)
|
||||||
}, 120_000)
|
}, 120_000)
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
|
|||||||
@@ -32,6 +32,31 @@ export function probeFreePort(): Promise<number> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drive the hero's workspace picker through its create-by-name dialog until
|
||||||
|
* the live composer unlocks. A fresh world has no Workspace, so the boot
|
||||||
|
* lands in the locked view state (startup auto-selection has nothing to
|
||||||
|
* select); every scenario that types into the composer must connect one
|
||||||
|
* first. The default name 'workspace' keeps the session header cwd at
|
||||||
|
* <workspaceRoot>/workspace — the materialization proof several scenarios
|
||||||
|
* assert.
|
||||||
|
* @param page - the page under test.
|
||||||
|
* @param name - workspace name typed into the create dialog.
|
||||||
|
*/
|
||||||
|
export async function connectFreshWorkspace(page: Page, name = 'workspace'): Promise<void> {
|
||||||
|
await page.getByRole('button', { name: 'Choose workspace' }).click()
|
||||||
|
await page.getByRole('menuitem', { name: 'Create workspace' }).hover()
|
||||||
|
await page.getByRole('menuitem', { name: 'Create a new workspace' }).click()
|
||||||
|
const dialog = page.getByRole('dialog', { name: 'Create a new workspace' })
|
||||||
|
await dialog.waitFor({ timeout: 10_000 })
|
||||||
|
await dialog.getByLabel('New workspace name').fill(name)
|
||||||
|
await dialog.getByRole('button', { name: 'Create workspace' }).click()
|
||||||
|
// The pick connected the workspace: the blank session's live composer
|
||||||
|
// replaces the locked placeholder and enables.
|
||||||
|
await page.locator('textarea:enabled[placeholder="Describe what you want to build"]')
|
||||||
|
.waitFor({ timeout: 15_000 })
|
||||||
|
}
|
||||||
|
|
||||||
/** Failure evidence goes to the gitignored .artifacts/ (repo convention). */
|
/** Failure evidence goes to the gitignored .artifacts/ (repo convention). */
|
||||||
export async function saveFailureShot(page: Page, name: string): Promise<void> {
|
export async function saveFailureShot(page: Page, name: string): Promise<void> {
|
||||||
const dir = fileURLToPath(new URL('../../../.artifacts', import.meta.url))
|
const dir = fileURLToPath(new URL('../../../.artifacts', import.meta.url))
|
||||||
|
|||||||
Reference in New Issue
Block a user