feat(web): present onboarding as a continuous page

This commit is contained in:
NI0317
2026-07-31 00:28:17 +08:00
parent 04f8b30db1
commit 75a0366a52
25 changed files with 475 additions and 228 deletions

View File

@@ -48,14 +48,17 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
it('stores a key write-only and observes configured state without restarting', async () => {
onTestFailed(() => saveFailureShot(page, 'web-e2e-onboarding-deepseek-config'))
const welcome = page.getByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title })
const welcome = page.getByRole('region', { name: WELCOME_NOTICE_COPY.zh.title })
await welcome.waitFor({ timeout: 15_000 })
const welcomeAria = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
expect(await page.locator('#root').evaluate(root => (root as HTMLElement).inert)).toBe(true)
const welcomeAria = await captureStableAria(page, '[role="region"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(WELCOME_EXPECTED, welcomeAria, MODE)
expect(await welcome.getByRole('button').allTextContents()).toEqual([WELCOME_NOTICE_COPY.zh.continueLabel])
expect(await welcome.locator('button').count()).toBe(1)
const maskStyles = await welcome.locator('xpath=..').locator(':scope > div').first().evaluate((mask) => {
const mask = page.locator('[class*="onboardingMask"]')
expect(await mask.count()).toBe(1)
const maskStyles = await mask.evaluate((mask) => {
const style = getComputedStyle(mask)
const rect = mask.getBoundingClientRect()
return {
@@ -89,16 +92,17 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
await welcome.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel }).click()
await welcome.waitFor({ state: 'detached', timeout: 15_000 })
const dialog = page.getByRole('dialog', { name: '添加一个 API Key 开始使用' })
await dialog.waitFor({ timeout: 15_000 })
expect(await dialog.getByRole('textbox').count()).toBe(0)
const initial = await captureStableAria(page, '[role="dialog"]', scaffold.workspaceCwd)
const credentialStep = page.getByRole('region', { name: '添加一个 API Key 开始使用' })
await credentialStep.waitFor({ timeout: 15_000 })
expect(await credentialStep.getByRole('textbox').count()).toBe(0)
const initial = await captureStableAria(page, '[role="region"]', scaffold.workspaceCwd)
await compareOrRefreshGolden(MISSING_EXPECTED, initial, MODE)
await dialog.getByRole('button', { name: '前往配置' }).click()
await dialog.waitFor({ state: 'detached', timeout: 15_000 })
await credentialStep.getByRole('button', { name: '前往配置' }).click()
await credentialStep.waitFor({ state: 'detached', timeout: 15_000 })
const settings = page.getByRole('dialog', { name: '设置' })
await settings.waitFor({ timeout: 10_000 })
expect(await page.locator('#root').evaluate(root => (root as HTMLElement).inert)).toBe(false)
const keyInput = settings.getByLabel('API 密钥', { exact: true })
await keyInput.waitFor({ timeout: 10_000 })
@@ -132,8 +136,8 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
await page.reload({ waitUntil: 'load' })
acknowledgeReloadConnectionLoss(tripwire, secondReloadWarnings)
await page.waitForSelector('[class*="frame"]', { timeout: 15_000 })
expect(await page.getByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title }).count()).toBe(0)
expect(await page.getByRole('dialog', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
expect(await page.getByRole('region', { name: WELCOME_NOTICE_COPY.zh.title }).count()).toBe(0)
expect(await page.getByRole('region', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
// A different stored copy version represents an intentional version bump:
// the welcome step returns even though the credential is already ready.
@@ -146,7 +150,7 @@ describe.skipIf(MODE === 'record')('web e2e: first-run DeepSeek credential setup
await welcome.waitFor({ timeout: 15_000 })
await welcome.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel }).click()
await welcome.waitFor({ state: 'detached', timeout: 15_000 })
expect(await page.getByRole('dialog', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
expect(await page.getByRole('region', { name: '添加一个 API Key 开始使用' }).count()).toBe(0)
expect((await page.content()).includes(secret)).toBe(false)
expect((await page.locator('body').ariaSnapshot()).includes(secret)).toBe(false)

View File

@@ -1,6 +1,6 @@
- dialog "添加一个 API Key 开始使用":
- region "添加一个 API Key 开始使用":
- heading "添加一个 API Key 开始使用" [level=2]
- button "稍后配置":
- img
- paragraph: 配置 DeepSeek 官方模型,即可开始使用。
- text: DeepSeek deepseek-official
- button "稍后配置"
- button "前往配置"

View File

@@ -1,8 +1,10 @@
- dialog "内测声明":
- region "内测声明":
- heading "内测声明" [level=2]
- paragraph: 感谢您试用 DeepSeek Harness。目前仍处于内部测试阶段,部分功能与体验还在持续打磨。
- strong: 我们最想听见:失败、困惑和不顺手
- paragraph: 如果它没帮到您,甚至给工作添了麻烦,请在企业微信群告诉我们
- paragraph: 真实使用中的每一个问题,都可能促使我们重新审视,甚至推翻已有设计。
- paragraph: “如切如磋,如琢如磨。”
- paragraph: 感谢您愿意拨冗试用 DeepSeek Harness。
- paragraph: 目前的版本仍处于内部测试阶段,有些功能仍待完善,有些体验难免粗粝。
- blockquote: “如切如磋,如琢如磨。” 产品的成长,离不开一次次真实的碰撞与坦诚的反馈。您在真实使用中暴露的问题,也可能促使我们重新审视,甚至推翻已有的设计
- paragraph:
- text: 我们尤其希望听见那些失败、困惑与不顺手的时刻——
- strong: 如果它未能帮到您,甚至反而为工作平添了麻烦,请在企业微信群中留言
- text: ,将使用感受告诉我们。每一条反馈,都会帮助我们把它打磨得更好。
- button "继续"