test(web): await command and settings readiness before assertions

This commit is contained in:
imccyu committed 2026-09-09 00:26:41 +08:00
1 parent e807447288
commit 58144ccf78
2 files changed
+40 -18

No files matched your search

+13 -5
View File
@@ -15,7 +15,7 @@ import { fileURLToPath } from 'node:url'
import { join } from 'node:path'
import type { Browser, Page, WebSocketRoute } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { afterAll, beforeAll, describe, expect, it, onTestFailed, onTestFinished } from 'vitest'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import {
acknowledgeReloadConnectionLoss, assertFixtureInventory, captureExpandedTurnProcessAria,
@@ -72,10 +72,17 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
it.skipIf(MODE === 'record')('opens the shared slash menu from plus with only Command candidates', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-command-menu-launcher'))
const input = page.locator('[data-composer-input]').first()
onTestFinished(async () => {
await input.press('Escape')
await writeComposerDraft(page, input, '')
await page.getByRole('listbox', { name: 'Trigger suggestions' }).waitFor({ state: 'hidden' })
})
const launcher = page.getByRole('button', { name: 'Commands' })
await launcher.click()
const menu = page.getByRole('listbox', { name: 'Trigger suggestions' })
await menu.waitFor({ timeout: 10_000 })
await menu.getByRole('option').first().waitFor({ timeout: 10_000 })
await menu.getByRole('status').waitFor({ state: 'hidden', timeout: 10_000 })
const snapshot = await captureStableAria(page, '[role="listbox"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(COMMAND_MENU_EXPECTED, snapshot, MODE)
expect(snapshot).toContain('text: Commands')
@@ -84,9 +91,9 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
const launchedBox = await menu.boundingBox()
await page.locator('[data-composer-input]').first().press('Escape')
await expect.poll(() => menu.count()).toBe(0)
const input = page.locator('[data-composer-input]').first()
await writeComposerDraft(page, input, '/')
await menu.waitFor({ timeout: 10_000 })
await menu.getByRole('option').first().waitFor({ timeout: 10_000 })
await menu.getByRole('status').waitFor({ state: 'hidden', timeout: 10_000 })
const typedBox = await menu.boundingBox()
expect(launchedBox).not.toBeNull()
expect(typedBox).not.toBeNull()
@@ -114,7 +121,8 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
const launcher = zhPage.getByRole('button', { name: '指令' })
await launcher.click()
const menu = zhPage.getByRole('listbox', { name: '触发候选建议' })
await menu.waitFor({ timeout: 10_000 })
await menu.getByRole('option').first().waitFor({ timeout: 10_000 })
await menu.getByRole('status').waitFor({ state: 'hidden', timeout: 10_000 })
const snapshot = await captureStableAria(zhPage, '[role="listbox"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(COMMAND_MENU_ZH_EXPECTED, snapshot, MODE)
expect(zhTripwire.pageErrors).toEqual([])
+27 -13
View File
@@ -12,7 +12,7 @@ import { readFile } from 'node:fs/promises'
import { fileURLToPath } from 'node:url'
import type { Browser, Locator, Page } from 'playwright'
import { chromium } from 'playwright'
import { afterAll, beforeAll, describe, expect, it, onTestFailed } from 'vitest'
import { afterAll, beforeAll, describe, expect, it, onTestFailed, onTestFinished } from 'vitest'
import { join } from 'node:path'
import { SessionId } from '@deepseek-ai/dsh-session'
import {
@@ -378,6 +378,10 @@ describe('web e2e: settings modal and General preferences', () => {
it('steps the content font size, applies it to body, and persists across reload', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-settings-font-size'))
onTestFinished(async () => {
await page.keyboard.press('Escape')
await page.getByRole('dialog', { name: '设置', exact: true }).waitFor({ state: 'hidden' })
})
const readFontSize = async (target: Page = page): Promise<string> => await target.evaluate(
() => document.body.style.getPropertyValue('--dsh-content-font-size'),
)
@@ -392,6 +396,24 @@ describe('web e2e: settings modal and General preferences', () => {
probe.remove()
return size
})
// The displayed value is optimistic; wait for the write before the next step.
const stepFontSize = async (button: Locator, px: number): Promise<void> => {
const [response] = await Promise.all([
page.waitForResponse((reply) => {
if (new URL(reply.url()).pathname !== '/api/settings/mutate' || reply.request().method() !== 'POST') return false
const request = reply.request().postDataJSON() as { payload: { args: { ns: string } } }
return request.payload.args.ns === 'ui-theme'
}),
button.click(),
])
expect(await response.finished()).toBeNull()
const envelope = await response.json() as { result: { ok: boolean } }
expect(envelope.result.ok).toBe(true)
await expect.poll(async () => readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8'), { timeout: 5_000 })
.toMatch(new RegExp(`ui-theme:\n(?:\\s+\\w+: .*\n)*?\\s+fontSize: ${px}`))
await page.getByRole('dialog', { name: '设置' }).getByText(String(px), { exact: true }).waitFor({ timeout: 5_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe(`${px}px`)
}
expect(await readFontSize()).toBe('14px')
expect(await readSecondaryFontSize()).toBe('13px')
await page.getByRole('button', { name: '设置', exact: true }).click()
@@ -400,17 +422,12 @@ describe('web e2e: settings modal and General preferences', () => {
// The stepper reveals its arrows on hover; the up arrow steps 14 → 15 → 16.
await dialog.getByText('14', { exact: true }).hover()
const increase = dialog.getByRole('button', { name: '增大字号' })
await increase.click()
await dialog.getByText('15', { exact: true }).waitFor({ timeout: 5_000 })
await stepFontSize(increase, 15)
// 15 is the piecewise boundary: the secondary tier holds at 13px (−2)
// where the ≤14 branch would have given 14px (−1).
await expect.poll(readSecondaryFontSize, { timeout: 5_000 }).toBe('13px')
await increase.click()
await dialog.getByText('16', { exact: true }).waitFor({ timeout: 5_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe('16px')
await stepFontSize(increase, 16)
await expect.poll(readSecondaryFontSize, { timeout: 5_000 }).toBe('14px')
await expect.poll(async () => readFile(join(scaffold.harnessHome, 'settings.yaml'), 'utf8'), { timeout: 5_000 })
.toMatch(/ui-theme:\n(?:\s+\w+: .*\n)*?\s+fontSize: 16/)
await page.keyboard.press('Escape')
// Reload: the boot script embeds the durable size and ThemeRuntime seeds
@@ -429,11 +446,8 @@ describe('web e2e: settings modal and General preferences', () => {
await restored.waitFor({ timeout: 10_000 })
await restored.getByText('16', { exact: true }).hover()
const decrease = restored.getByRole('button', { name: '减小字号' })
await decrease.click()
await restored.getByText('15', { exact: true }).waitFor({ timeout: 5_000 })
await decrease.click()
await restored.getByText('14', { exact: true }).waitFor({ timeout: 5_000 })
await expect.poll(readFontSize, { timeout: 5_000 }).toBe('14px')
await stepFontSize(decrease, 15)
await stepFontSize(decrease, 14)
await page.keyboard.press('Escape')
expect(tripwire.pageErrors).toEqual([])
}, 90_000)