test(web): pin browser replay locale
This commit is contained in:
@@ -2,5 +2,5 @@
|
|||||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||||
# after editing either side, bring the other along and re-record with:
|
# after editing either side, bring the other along and re-record with:
|
||||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md
|
# pnpm run verify-translation-pairing --write .agents/notes/implemented/testing/2026-07-24-web-gui-browser-e2e-lane.md
|
||||||
2026-07-24-web-gui-browser-e2e-lane.md: b57273c1c7bb957eeb4de2cf786370c18e6e735c
|
2026-07-24-web-gui-browser-e2e-lane.md: 97be6d5d70d12f783e2c80b1bcd546cfa4582ca8
|
||||||
2026-07-24-web-gui-browser-e2e-lane.zh.md: 53e6550b9b1ac7612908e6b464eadd40704ee05f
|
2026-07-24-web-gui-browser-e2e-lane.zh.md: f55b4f8011cc42aa4e0f8c11d9900525a565e1a7
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ The barrier stack for replay-mode browser assertions is, in order: (1) host-side
|
|||||||
|
|
||||||
No single-shot transient-DOM assertions: every hop from replay yield to React commit can coalesce chunks, so sampling `[data-streaming]` is a race by construction. Streaming incrementality is asserted from the persisted `assistant/chunk` events (model-visible ⟺ logged makes the log the authoritative proof). `dsh-llm-replay`'s opt-in `paceMs` (default absent = burst) is a realism knob so the browser observes genuinely incremental SSE; correctness never leans on it, and abort during a pace wait cancels promptly.
|
No single-shot transient-DOM assertions: every hop from replay yield to React commit can coalesce chunks, so sampling `[data-streaming]` is a race by construction. Streaming incrementality is asserted from the persisted `assistant/chunk` events (model-visible ⟺ logged makes the log the authoritative proof). `dsh-llm-replay`'s opt-in `paceMs` (default absent = burst) is a realism knob so the browser observes genuinely incremental SSE; correctness never leans on it, and abort during a pace wait cancels promptly.
|
||||||
|
|
||||||
Every scenario fails on any pageerror and on the client's connection-loss/gap-repair console warnings: the reconnect machine plus history resync would otherwise self-heal a dead SSE path and the suite would certify a broken wire. Scaffold `close()` calls the `ReplayHandle.assertConsumed()` teardown check (every recorded script bound, every cursor drained), converting silent underruns and shifted bindings into crisp diagnostics. No vitest retry on the lane; one chromium per file, fresh context per scenario, one host per scenario; viewport pinned; interaction selectors anchor on roles, `data-*` attributes, and visible text, while the frame and conversation-region captures use the existing CSS-module local-name anchors.
|
Every scenario fails on any pageerror and on the client's connection-loss/gap-repair console warnings: the reconnect machine plus history resync would otherwise self-heal a dead SSE path and the suite would certify a broken wire. Scaffold `close()` calls the `ReplayHandle.assertConsumed()` teardown check (every recorded script bound, every cursor drained), converting silent underruns and shifted bindings into crisp diagnostics. No vitest retry on the lane; one chromium per file, fresh context per scenario, one host per scenario; viewport pinned; interaction selectors anchor on roles, `data-*` attributes, and visible text, while the frame and conversation-region captures use the existing CSS-module local-name anchors. Standard scenarios set `dsh.locale=en` before client boot so localized role locators and goldens use one explicit language; `settings-chrome.e2e.ts` alone leaves storage unset to cover the default Chinese state and both switch directions.
|
||||||
|
|
||||||
### Expected outputs
|
### Expected outputs
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Web GUI 以一条真实组装链交付——chromium 页面 → client 插件 bu
|
|||||||
|
|
||||||
不做单次瞬态 DOM 断言:从回放产出到 React 提交的每一跳都可能合并分片,采样 `[data-streaming]` 天然就是竞态。流式输出的增量性由持久化的 `assistant/chunk` 事件断言(模型可见 ⟺ 已记录,使日志成为权威证据)。`dsh-llm-replay` 的可选 `paceMs`(默认缺省 = 突发)只是让浏览器观察到真正增量 SSE 的真实感旋钮;正确性绝不依赖它,且节奏等待期间中止会即时取消。
|
不做单次瞬态 DOM 断言:从回放产出到 React 提交的每一跳都可能合并分片,采样 `[data-streaming]` 天然就是竞态。流式输出的增量性由持久化的 `assistant/chunk` 事件断言(模型可见 ⟺ 已记录,使日志成为权威证据)。`dsh-llm-replay` 的可选 `paceMs`(默认缺省 = 突发)只是让浏览器观察到真正增量 SSE 的真实感旋钮;正确性绝不依赖它,且节奏等待期间中止会即时取消。
|
||||||
|
|
||||||
每个场景都会因任何 pageerror 或客户端的连接丢失/间隙修复控制台警告而失败:否则重连机制加历史重同步会把一条死掉的 SSE 通路自愈掉,套件反而认证了坏 wire。Scaffold 的 `close()` 调用 `ReplayHandle.assertConsumed()` 收尾检查(每个已录脚本都被绑定、每个游标都耗尽),把静默的少放与错绑变成清晰诊断。车道不设 vitest 重试;每文件一个 chromium、每场景一个新 context、每场景一个 host;视口固定;交互选择器锚定 role、`data-*` 属性和可见文本,而 frame 与会话区采集则使用既有的 CSS 模块局部类名锚点。
|
每个场景都会因任何 pageerror 或客户端的连接丢失/间隙修复控制台警告而失败:否则重连机制加历史重同步会把一条死掉的 SSE 通路自愈掉,套件反而认证了坏 wire。Scaffold 的 `close()` 调用 `ReplayHandle.assertConsumed()` 收尾检查(每个已录脚本都被绑定、每个游标都耗尽),把静默的少放与错绑变成清晰诊断。车道不设 vitest 重试;每文件一个 chromium、每场景一个新 context、每场景一个 host;视口固定;交互选择器锚定 role、`data-*` 属性和可见文本,而 frame 与会话区采集则使用既有的 CSS 模块局部类名锚点。常规场景在客户端启动前设置 `dsh.locale=en`,使本地化的 role 定位器和预期输出统一采用明确指定的语言;只有 `settings-chrome.e2e.ts` 不预设该存储项,以覆盖默认中文状态及双向切换。
|
||||||
|
|
||||||
### 预期输出
|
### 预期输出
|
||||||
|
|
||||||
|
|||||||
@@ -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 { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, 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))
|
||||||
@@ -44,7 +44,7 @@ describe('web e2e: Code Mode round renders nested sub-calls', () => {
|
|||||||
})
|
})
|
||||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
|
|||||||
@@ -12,7 +12,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 { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const FIXTURE = fileURLToPath(new URL('./snapshots/cordis-tool-round/session.jsonl', import.meta.url))
|
const FIXTURE = fileURLToPath(new URL('./snapshots/cordis-tool-round/session.jsonl', import.meta.url))
|
||||||
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/cordis-tool-round/ui.expected.md', import.meta.url))
|
const UI_EXPECTED = fileURLToPath(new URL('./snapshots/cordis-tool-round/ui.expected.md', import.meta.url))
|
||||||
@@ -62,7 +62,7 @@ describe('web e2e: Cordis tools use the generic row variants', () => {
|
|||||||
})
|
})
|
||||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
|
|||||||
import {
|
import {
|
||||||
fixtureUserPrompts, launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
fixtureUserPrompts, launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const FIXTURE = fileURLToPath(new URL('./snapshots/lifecycle-chrome/session.jsonl', import.meta.url))
|
const FIXTURE = fileURLToPath(new URL('./snapshots/lifecycle-chrome/session.jsonl', import.meta.url))
|
||||||
const SEED_FIXTURE = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
|
const SEED_FIXTURE = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
|
||||||
@@ -40,7 +40,7 @@ describe.skipIf(MODE === 'record')('web e2e: details panel follows the current S
|
|||||||
scaffold = await launchWebScaffold({ replayFixture: FIXTURE, paceMs: 5 })
|
scaffold = await launchWebScaffold({ replayFixture: FIXTURE, paceMs: 5 })
|
||||||
await seedSession(scaffold, await readFile(SEED_FIXTURE, 'utf8'), 'details-session-lifecycle-seed')
|
await seedSession(scaffold, await readFile(SEED_FIXTURE, 'utf8'), 'details-session-lifecycle-seed')
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
await page.goto(scaffold.baseUrl, { waitUntil: 'load' })
|
||||||
await appFrame(page).waitFor({ timeout: 30_000 })
|
await appFrame(page).waitFor({ timeout: 30_000 })
|
||||||
|
|||||||
@@ -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 { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, 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')
|
||||||
@@ -43,7 +43,7 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
|
|||||||
scaffold = await launchWebScaffold(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 })
|
scaffold = await launchWebScaffold(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 })
|
||||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
|
|||||||
@@ -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 { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, 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')
|
||||||
@@ -90,7 +90,7 @@ describe('web e2e: live-turn interactions (cancel / error / retry)', () => {
|
|||||||
})
|
})
|
||||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-actions', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/message-actions', import.meta.url))
|
||||||
// Borrowed read-only: this scenario needs any settled user+assistant pair, not
|
// Borrowed read-only: this scenario needs any settled user+assistant pair, not
|
||||||
@@ -40,7 +40,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
|||||||
expect(fixtureUserPrompts(raw), 'borrowed seed must carry the drive prompt').toEqual([PROMPT])
|
expect(fixtureUserPrompts(raw), 'borrowed seed must carry the drive prompt').toEqual([PROMPT])
|
||||||
await seedSession(scaffold, raw, SEED_ID)
|
await seedSession(scaffold, raw, SEED_ID)
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
@@ -75,7 +75,7 @@ describe('web e2e: message IconActions and clocks on settled history', () => {
|
|||||||
it.skipIf(MODE === 'record')('matches the conversation aria golden with IconActions and clocks', async () => {
|
it.skipIf(MODE === 'record')('matches the conversation aria golden with IconActions and clocks', async () => {
|
||||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-message-actions-aria'))
|
onTestFailed(() => saveFailureShot(page, 'web-e2e-message-actions-aria'))
|
||||||
await page.getByRole('button', {
|
await page.getByRole('button', {
|
||||||
name: '选择模型,当前 deepseek-v4-flash',
|
name: 'Select model, current deepseek-v4-flash',
|
||||||
}).waitFor({ timeout: 10_000 })
|
}).waitFor({ timeout: 10_000 })
|
||||||
// Keep a footer focused so opacity-hidden actions stay in the a11y tree
|
// Keep a footer focused so opacity-hidden actions stay in the a11y tree
|
||||||
// as an active/focused control during the capture.
|
// as an active/focused control during the capture.
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import {
|
|||||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/navigation-panes', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/navigation-panes', import.meta.url))
|
||||||
const SEED = join(SNAPSHOT_DIR, 'seed.jsonl')
|
const SEED = join(SNAPSHOT_DIR, 'seed.jsonl')
|
||||||
@@ -56,7 +56,7 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
|||||||
await seedSession(scaffold, raw, SEED_ID)
|
await seedSession(scaffold, raw, SEED_ID)
|
||||||
}
|
}
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
tripwire = watchConsole(page)
|
tripwire = watchConsole(page)
|
||||||
slotErrors = []
|
slotErrors = []
|
||||||
page.on('console', (message) => {
|
page.on('console', (message) => {
|
||||||
|
|||||||
@@ -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 { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, 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')
|
||||||
@@ -44,7 +44,7 @@ describe('web e2e: resident question composer round trip', () => {
|
|||||||
scaffold = await launchWebScaffold(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 })
|
scaffold = await launchWebScaffold(MODE === 'record' ? {} : { replayFixture: FIXTURE, paceMs: 15 })
|
||||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
|
|||||||
@@ -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 { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, 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))
|
||||||
@@ -43,7 +43,7 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
|||||||
})
|
})
|
||||||
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
scaffold.ctx.on('session/event', (_session, event: SessionEvent) => { sessionEvents.push(event) })
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
@@ -106,7 +106,7 @@ describe('web e2e: fresh round trip through the real assembly', () => {
|
|||||||
await expect(page.getByRole('textbox').first().isVisible()).resolves.toBe(true)
|
await expect(page.getByRole('textbox').first().isVisible()).resolves.toBe(true)
|
||||||
expect(await page.getByText('WEB_E2E_OK', { exact: false }).count()).toBeGreaterThanOrEqual(1)
|
expect(await page.getByText('WEB_E2E_OK', { exact: false }).count()).toBeGreaterThanOrEqual(1)
|
||||||
await page.getByRole('button', {
|
await page.getByRole('button', {
|
||||||
name: '选择模型,当前 DeepSeek-V4-Flash',
|
name: 'Select model, current DeepSeek-V4-Flash',
|
||||||
}).waitFor({ timeout: 10_000 })
|
}).waitFor({ timeout: 10_000 })
|
||||||
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
|
const snapshot = await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd)
|
||||||
await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE)
|
await compareOrRefreshGolden(UI_EXPECTED, snapshot, MODE)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
assertFixtureInventory, captureStableAria, compareOrRefreshGolden, fixtureUserPrompts,
|
||||||
launchWebScaffold, recordFixture, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, recordFixture, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/seeded-history', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/seeded-history', import.meta.url))
|
||||||
const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
|
const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
|
||||||
@@ -49,7 +49,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
|||||||
await seedSession(scaffold, raw, SEED_ID)
|
await seedSession(scaffold, raw, SEED_ID)
|
||||||
}
|
}
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
@@ -125,7 +125,7 @@ describe('web e2e: seeded history renders through cold resume', () => {
|
|||||||
// This scenario deliberately leaves the LLM seam open to prove zero
|
// This scenario deliberately leaves the LLM seam open to prove zero
|
||||||
// model calls. History still restores the selected id, but no catalog
|
// model calls. History still restores the selected id, but no catalog
|
||||||
// adapter exists to provide its presentation name.
|
// adapter exists to provide its presentation name.
|
||||||
name: '选择模型,当前 deepseek-v4-flash',
|
name: 'Select model, current deepseek-v4-flash',
|
||||||
}).waitFor({ timeout: 10_000 })
|
}).waitFor({ timeout: 10_000 })
|
||||||
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
|
const snapshot = (await captureStableAria(page, '[class*="centerCol"]', scaffold.workspaceCwd))
|
||||||
.split(SEED_ID).join('{{seededId}}')
|
.split(SEED_ID).join('{{seededId}}')
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ import {
|
|||||||
assertFixtureInventory, compareOrRefreshGolden, launchWebScaffold, seedSession, watchConsole,
|
assertFixtureInventory, compareOrRefreshGolden, launchWebScaffold, seedSession, watchConsole,
|
||||||
webSnapshotMode, type WebScaffold,
|
webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
|
const SEED = fileURLToPath(new URL('./snapshots/seeded-history/seed.jsonl', import.meta.url))
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/sidebar-scrollbar', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/sidebar-scrollbar', import.meta.url))
|
||||||
@@ -275,7 +275,7 @@ describe('web e2e: sidebar session list scrollbar (reserved gutter / themed thum
|
|||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
// Shorter than the other scenarios' 1000px so SEED_COUNT rows overflow
|
// Shorter than the other scenarios' 1000px so SEED_COUNT rows overflow
|
||||||
// the list with room to spare.
|
// the list with room to spare.
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 800 } })
|
page = await newEnglishPage(browser, 800)
|
||||||
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 })
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import {
|
|||||||
webSnapshotMode,
|
webSnapshotMode,
|
||||||
type WebScaffold,
|
type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/skill-invocation-policy', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/skill-invocation-policy', import.meta.url))
|
||||||
const MENU_EXPECTED = join(SNAPSHOT_DIR, 'menu.expected.md')
|
const MENU_EXPECTED = join(SNAPSHOT_DIR, 'menu.expected.md')
|
||||||
@@ -80,7 +80,7 @@ describe('web e2e: skill invocation policy through the real host', () => {
|
|||||||
scaffold = await launchWebScaffold({})
|
scaffold = await launchWebScaffold({})
|
||||||
await seedSkills(scaffold.workspaceCwd)
|
await seedSkills(scaffold.workspaceCwd)
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
|
|||||||
@@ -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, connectFreshWorkspace, probeFreePort, requireDist, saveFailureShot } from './support.ts'
|
import { REPO_ROOT, connectFreshWorkspace, newEnglishPage, probeFreePort, requireDist, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
function waitForReadyLine(child: ChildProcess): Promise<string> {
|
function waitForReadyLine(child: ChildProcess): Promise<string> {
|
||||||
return new Promise((resolveReady, reject) => {
|
return new Promise((resolveReady, reject) => {
|
||||||
@@ -376,7 +376,7 @@ describe.skipIf(!process.env.DEEPSEEK_API_KEY || notReady.length > 0)('web smoke
|
|||||||
)
|
)
|
||||||
baseUrl = (await waitForReadyLine(child)).replace('0.0.0.0', '127.0.0.1')
|
baseUrl = (await waitForReadyLine(child)).replace('0.0.0.0', '127.0.0.1')
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
page.on('pageerror', e => pageErrors.push(String(e)))
|
page.on('pageerror', e => pageErrors.push(String(e)))
|
||||||
await page.goto(baseUrl, { waitUntil: 'load' })
|
await page.goto(baseUrl, { waitUntil: 'load' })
|
||||||
}, 120_000)
|
}, 120_000)
|
||||||
|
|||||||
@@ -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 { connectFreshWorkspace, saveFailureShot } from './support.ts'
|
import { connectFreshWorkspace, newEnglishPage, 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')
|
||||||
@@ -67,7 +67,7 @@ describe('web e2e: mid-turn steering lands durably and visibly', () => {
|
|||||||
sessionEvents.push(event)
|
sessionEvents.push(event)
|
||||||
})
|
})
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
|
|||||||
@@ -2,13 +2,28 @@
|
|||||||
import { existsSync, mkdirSync } from 'node:fs'
|
import { existsSync, mkdirSync } from 'node:fs'
|
||||||
import { createServer } from 'node:net'
|
import { createServer } from 'node:net'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
import type { Page } from 'playwright'
|
import type { Browser, Page } from 'playwright'
|
||||||
|
|
||||||
/** The built page under test; `pnpm run test:web` rebuilds it before running. */
|
/** The built page under test; `pnpm run test:web` rebuilds it before running. */
|
||||||
export const DIST_INDEX = fileURLToPath(new URL('../dist/index.html', import.meta.url))
|
export const DIST_INDEX = fileURLToPath(new URL('../dist/index.html', import.meta.url))
|
||||||
|
|
||||||
export const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
|
export const REPO_ROOT = fileURLToPath(new URL('../../..', import.meta.url))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Open the standard browser-test page with English selected before client
|
||||||
|
* boot. This keeps role locators and goldens deterministic across localized
|
||||||
|
* component migrations; the settings locale scenario deliberately bypasses
|
||||||
|
* this helper to cover the product's default Chinese state.
|
||||||
|
* @param browser - Playwright browser owning the page.
|
||||||
|
* @param height - Viewport height; width is fixed to the lane baseline.
|
||||||
|
* @returns the initialized page.
|
||||||
|
*/
|
||||||
|
export async function newEnglishPage(browser: Browser, height = 1000): Promise<Page> {
|
||||||
|
const page = await browser.newPage({ viewport: { width: 1680, height } })
|
||||||
|
await page.addInitScript(() => { localStorage.setItem('dsh.locale', 'en') })
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
|
||||||
/** Fail loud on a stale checkout instead of testing yesterday's bundle. */
|
/** Fail loud on a stale checkout instead of testing yesterday's bundle. */
|
||||||
export function requireDist(): void {
|
export function requireDist(): void {
|
||||||
if (!existsSync(DIST_INDEX)) {
|
if (!existsSync(DIST_INDEX)) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
|
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureStableAria, compareOrRefreshGolden,
|
||||||
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
launchWebScaffold, seedSession, watchConsole, webSnapshotMode, type WebScaffold,
|
||||||
} from './scaffold.ts'
|
} from './scaffold.ts'
|
||||||
import { saveFailureShot } from './support.ts'
|
import { newEnglishPage, saveFailureShot } from './support.ts'
|
||||||
|
|
||||||
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/workspace-management', import.meta.url))
|
const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/workspace-management', import.meta.url))
|
||||||
// The seed is another scenario's committed fixture, reused read-only: this
|
// The seed is another scenario's committed fixture, reused read-only: this
|
||||||
@@ -42,12 +42,12 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
|
|||||||
const agentsBefore = scaffold.ctx.agents.list().length
|
const agentsBefore = scaffold.ctx.agents.list().length
|
||||||
await page.getByRole('button', { name: 'Create workspace' }).click()
|
await page.getByRole('button', { name: 'Create workspace' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Open local folder…' }).click()
|
await page.getByRole('menuitem', { name: 'Open local folder…' }).click()
|
||||||
const dialog = page.getByRole('dialog', { name: '选择工作区目录' })
|
const dialog = page.getByRole('dialog', { name: 'Select Workspace Directory' })
|
||||||
await dialog.waitFor({ timeout: 10_000 })
|
await dialog.waitFor({ timeout: 10_000 })
|
||||||
await dialog.getByRole('button', { name: '编辑路径' }).click()
|
await dialog.getByRole('button', { name: 'Edit path' }).click()
|
||||||
await dialog.getByLabel('编辑路径').fill(path)
|
await dialog.getByLabel('Edit path').fill(path)
|
||||||
await dialog.getByLabel('编辑路径').press('Enter')
|
await dialog.getByLabel('Edit path').press('Enter')
|
||||||
await dialog.getByRole('button', { name: '打开' }).click()
|
await dialog.getByRole('button', { name: 'Open' }).click()
|
||||||
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
|
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
|
||||||
await expect.poll(
|
await expect.poll(
|
||||||
() => scaffold.ctx.workspace.resolveByPath(path),
|
() => scaffold.ctx.workspace.resolveByPath(path),
|
||||||
@@ -72,7 +72,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
|
|||||||
await writeFile(join(sessionCwd, 'b.txt'), 'beta\n')
|
await writeFile(join(sessionCwd, 'b.txt'), 'beta\n')
|
||||||
await seedSession(scaffold, await readFile(SEED, 'utf8'), SEED_ID)
|
await seedSession(scaffold, await readFile(SEED, 'utf8'), SEED_ID)
|
||||||
browser = await chromium.launch()
|
browser = await chromium.launch()
|
||||||
page = await browser.newPage({ viewport: { width: 1680, height: 1000 } })
|
page = await newEnglishPage(browser)
|
||||||
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 })
|
||||||
@@ -363,15 +363,15 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
|
|||||||
try {
|
try {
|
||||||
await page.getByRole('button', { name: 'Create workspace' }).click()
|
await page.getByRole('button', { name: 'Create workspace' }).click()
|
||||||
await page.getByRole('menuitem', { name: 'Open local folder…' }).click()
|
await page.getByRole('menuitem', { name: 'Open local folder…' }).click()
|
||||||
const dialog = page.getByRole('dialog', { name: '选择工作区目录' })
|
const dialog = page.getByRole('dialog', { name: 'Select Workspace Directory' })
|
||||||
await dialog.waitFor({ timeout: 10_000 })
|
await dialog.waitFor({ timeout: 10_000 })
|
||||||
await dialog.getByRole('button', { name: '编辑路径' }).click()
|
await dialog.getByRole('button', { name: 'Edit path' }).click()
|
||||||
await dialog.getByLabel('编辑路径').fill(staged)
|
await dialog.getByLabel('Edit path').fill(staged)
|
||||||
await dialog.getByLabel('编辑路径').press('Enter')
|
await dialog.getByLabel('Edit path').press('Enter')
|
||||||
await expect.poll(() => dialog.getByText('alpha', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
await expect.poll(() => dialog.getByText('alpha', { exact: true }).count(), { timeout: 10_000 }).toBe(1)
|
||||||
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
|
const snapshot = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
|
||||||
await compareOrRefreshGolden(BROWSER_EXPECTED, snapshot, MODE)
|
await compareOrRefreshGolden(BROWSER_EXPECTED, snapshot, MODE)
|
||||||
await dialog.getByRole('button', { name: '取消' }).click()
|
await dialog.getByRole('button', { name: 'Cancel' }).click()
|
||||||
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
|
await dialog.waitFor({ state: 'hidden', timeout: 10_000 })
|
||||||
} finally {
|
} finally {
|
||||||
if (realHome === undefined) delete process.env.HOME
|
if (realHome === undefined) delete process.env.HOME
|
||||||
@@ -406,7 +406,7 @@ describe('web e2e: workspace management (create / rename / flat view / hover car
|
|||||||
// card; no aria role — text anchors are the stable selector).
|
// card; no aria role — text anchors are the stable selector).
|
||||||
await expect.poll(() => page.getByText('Idle', { exact: true }).count(), { timeout: 5_000 }).toBeGreaterThanOrEqual(1)
|
await expect.poll(() => page.getByText('Idle', { exact: true }).count(), { timeout: 5_000 }).toBeGreaterThanOrEqual(1)
|
||||||
// Leaving the anchor closes it with no delay.
|
// Leaving the anchor closes it with no delay.
|
||||||
await page.getByRole('button', { name: '设置' }).hover()
|
await page.getByRole('button', { name: 'Settings' }).hover()
|
||||||
await expect.poll(() => page.getByText('Idle', { exact: true }).count(), { timeout: 5_000 }).toBe(0)
|
await expect.poll(() => page.getByText('Idle', { exact: true }).count(), { timeout: 5_000 }).toBe(0)
|
||||||
expect(tripwire.pageErrors).toEqual([])
|
expect(tripwire.pageErrors).toEqual([])
|
||||||
}, 60_000)
|
}, 60_000)
|
||||||
|
|||||||
Reference in New Issue
Block a user