diff --git a/apps/web/tests/deepseek-messages-settings.e2e.ts b/apps/web/tests/deepseek-messages-settings.e2e.ts index 93aa400f57..6543428836 100644 --- a/apps/web/tests/deepseek-messages-settings.e2e.ts +++ b/apps/web/tests/deepseek-messages-settings.e2e.ts @@ -90,8 +90,11 @@ describe.skipIf(webSnapshotMode() === 'record')('web e2e: DeepSeek Messages sett await scaffold.ctx.agentDefaultModel.saveSelection({ provider: 'deepseek-official', model: 'deepseek-v4-flash' }) await page.reload({ waitUntil: 'load' }) const input = page.locator('[data-composer-input]').first() - await expect.poll(() => input.getAttribute('contenteditable')).toBe('false') - await page.getByRole('button', { name: '请选择模型', exact: true }).click() + const trigger = page.getByRole('button', { name: /^选择模型.*deepseek-official\/deepseek-v4-flash/ }) + await trigger.waitFor() + await expect.poll(() => input.getAttribute('contenteditable')).toBe('true') + await trigger.click() + await page.getByRole('menuitem', { name: /模型/ }).click() await page.getByRole('menuitemradio', { name: 'Messages Flash', exact: true }).click() await expect.poll(() => input.getAttribute('contenteditable')).toBe('true') await expect.poll(() => scaffold.ctx.agentDefaultModel.currentSelection().provider).toBe('deepseek-official') diff --git a/apps/web/tests/default-model.e2e.ts b/apps/web/tests/default-model.e2e.ts index 08f8505d7f..04cbd9b80f 100644 --- a/apps/web/tests/default-model.e2e.ts +++ b/apps/web/tests/default-model.e2e.ts @@ -127,7 +127,7 @@ describe('web e2e: the composer model switch is the default for later sessions', expect(tripwire.pageErrors).toEqual([]) }, 60_000) - it('goes inert when the route the default names stops being served', async () => { + it('retains the saved route and editable composer when its provider is removed', async () => { onTestFailed(() => saveFailureShot(page, 'web-e2e-default-model-blocked')) const box = page.locator('[data-composer-input]').first() await expect.poll(async () => box.isEnabled(), { timeout: 10_000 }).toBe(true) @@ -139,28 +139,14 @@ describe('web e2e: the composer model switch is the default for later sessions', baseURL: 'https://gateway.origin.example/v1', models: [{ id: START_MODEL, name: 'Origin Large' }] }, } }) - await expect.poll(async () => box.isEnabled(), { timeout: 15_000 }).toBe(false) - const unset = page.getByRole('button', { name: '请选择模型', exact: true }) - await unset.waitFor() - expect(await unset.evaluate(element => getComputedStyle(element).fontWeight)).toBe('400') - expect(await page.getByRole('button', { name: '发送消息', exact: true }).isEnabled()).toBe(false) + const seat = page.getByRole('button', { name: new RegExp(`^选择模型.*${ROUTE}/${MODEL}`) }) + await seat.waitFor() + expect(await box.isEnabled()).toBe(true) + expect(scaffold.ctx.agentDefaultModel.currentSelection()).toMatchObject({ provider: ROUTE, model: MODEL }) const aria = await captureStableAria(page, '[data-composer-card]', scaffold.workspaceCwd) await compareOrRefreshGolden(fileURLToPath(new URL('./expected/default-model/unselected.expected.md', import.meta.url)), aria, webSnapshotMode()) - - // The block is an affordance; the refusal is the Host's. A client that - // never disabled anything still cannot start a turn on a dead route. - await expect(scaffold.ctx.sessionController.prompt({ - requestId: 'default-model-refused' as never, - sessionId: SessionId(await createSession('default-model-refusal')), - mode: 'queue', - content: [{ type: 'text', text: 'hi' }], - }, new AbortController().signal)).rejects.toMatchObject({ code: 'session/model-unavailable' }) - - // The way out stays open. Locking the model seat with everything else - // would leave the composer asking for the one thing it prevents. - const seat = page.getByRole('button', { name: '请选择模型', exact: true }) - expect(await seat.isEnabled()).toBe(true) await seat.click() + await page.getByRole('menuitem', { name: /模型/ }).click() await page.getByRole('menuitemradio').first().click() await expect.poll(async () => box.isEnabled(), { timeout: 15_000 }).toBe(true) expect(tripwire.pageErrors).toEqual([]) diff --git a/apps/web/tests/expected/default-model/unselected.expected.md b/apps/web/tests/expected/default-model/unselected.expected.md index 6228bea41c..8845f5196b 100644 --- a/apps/web/tests/expected/default-model/unselected.expected.md +++ b/apps/web/tests/expected/default-model/unselected.expected.md @@ -1,5 +1,5 @@ -- textbox "描述你想要构建的内容, / 调用指令, @ 文件或对话" [disabled] -- button "添加文件或调用指令" [disabled] -- button "访问模式,当前:工作区内修改" [disabled]: 工作区内修改 -- button "请选择模型" +- textbox "描述你想要构建的内容, / 调用指令, @ 文件或对话" +- button "添加文件或调用指令" +- button "访问模式,当前:工作区内修改": 工作区内修改 +- button "选择模型,当前 acme-gateway/acme-large": acme-gateway/acme-large - button "发送消息" [disabled] diff --git a/apps/web/tests/onboarding-deepseek-config.e2e.ts b/apps/web/tests/onboarding-deepseek-config.e2e.ts index d0e5ddd582..f3b3b48542 100644 --- a/apps/web/tests/onboarding-deepseek-config.e2e.ts +++ b/apps/web/tests/onboarding-deepseek-config.e2e.ts @@ -275,9 +275,10 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup // trigger — on the page; the scaffold boots without one. await connectFreshWorkspaceZh(page, scaffold.workspaceCwd, 'model-fallback-e2e', false) - const modelTrigger = page.getByRole('button', { name: '请选择模型', exact: true }) + const modelTrigger = page.getByRole('button', { name: /^选择模型.*deepseek-official\// }) await modelTrigger.waitFor({ timeout: 10_000 }) await modelTrigger.click() + await page.getByRole('menuitem', { name: /模型/ }).click() expect(await page.getByText('Configured Flash', { exact: true }).count()).toBe(0) await page.getByRole('menuitemradio', { name: 'Private Preview' }).waitFor({ timeout: 10_000 }) expect(tripwire.warnings).toEqual([]) diff --git a/apps/web/tests/reference-composer.e2e.ts b/apps/web/tests/reference-composer.e2e.ts index ed1bd9fa82..d4518210b4 100644 --- a/apps/web/tests/reference-composer.e2e.ts +++ b/apps/web/tests/reference-composer.e2e.ts @@ -24,6 +24,7 @@ import { compareOrRefreshGolden, launchWebScaffold, seedSession, + readPersistedEvents, watchConsole, webSnapshotMode, type WebScaffold, @@ -403,9 +404,8 @@ describe.skipIf(MODE === 'record')('web e2e: file and session references through await target.waitFor({ timeout: 15_000 }) await target.click() await page.locator('[data-chat-flow-kind="user"]').filter({ hasText: 'Research notes' }).waitFor({ timeout: 15_000 }) - const session = scaffold.ctx.sessions.get(SessionId(TARGET_SESSION_ID)) - if (session === undefined) throw new Error('reference target session is unavailable') - const inputs = session.snapshotEvents().filter(event => event.type === 'user/message') + const events = await readPersistedEvents(scaffold, SessionId(TARGET_SESSION_ID)) + const inputs = events.filter(event => event.type === 'user/message') expect(inputs.map(event => event.data.source.kind)).toEqual(['user', 'session-reference']) expect(inputs[0]?.seq).toBeLessThan(inputs[1]!.seq) expect(JSON.stringify(inputs[1]?.data)).toContain('snapshot')