import { expect, test } from '@playwright/test' import { existsSync, mkdtempSync, readFileSync, rmSync, statSync } from 'fs' import { tmpdir } from 'os' import { resolve } from 'path' import { launchTestApp } from './support/electron' async function dismissFirstUseWelcome(page: import('@playwright/test').Page): Promise { const welcome = page.getByRole('dialog', { name: '开始探索你的微信' }) await expect(welcome).toBeVisible() await welcome.getByRole('button', { name: '关闭' }).click() await expect(welcome).toHaveCount(0) } test('APP-01 first launch renders a usable connection screen without uncaught errors', async () => { const fixture = await launchTestApp({ mode: 'disconnected' }) const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await expect(fixture.page.getByRole('heading', { name: 'TraceMemo(迹忆)' })).toBeVisible() await expect(fixture.page.getByRole('main')).not.toBeEmpty() const loginLayout = await fixture.page.locator('.database-login-page').evaluate((element) => ({ width: Math.round(element.getBoundingClientRect().width), viewportWidth: window.innerWidth })) expect(loginLayout.width).toBe(loginLayout.viewportWidth) expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('KEY-01 KEY-02 invalid key remains recoverable and valid key enters the app', async () => { const fixture = await launchTestApp({ mode: 'disconnected' }) try { await fixture.page.getByRole('tab', { name: /高级用户/ }).click() const keyInput = fixture.page.getByLabel('数据库密钥') await keyInput.fill('b'.repeat(64)) await fixture.page.getByRole('button', { name: '连接数据库' }).click() await expect(fixture.page.getByText('数据库密钥无效')).toBeVisible() await expect(keyInput).toBeVisible() await keyInput.fill('a'.repeat(64)) await fixture.page.getByRole('button', { name: '连接数据库' }).click() await dismissFirstUseWelcome(fixture.page) await expect(fixture.page.getByRole('navigation', { name: '一级导航' })).toBeVisible() } finally { await fixture.close() } }) test('P0-01 an invalid directory can be corrected and retried without restarting', async () => { test.skip(process.platform !== 'win32', 'Manual database directory editing is Windows-only') const fixture = await launchTestApp({ mode: 'disconnected' }) try { await fixture.page.getByRole('tab', { name: /高级用户/ }).click() await fixture.page.getByLabel('数据库密钥').fill('a'.repeat(64)) await fixture.page.getByLabel('微信数据目录').fill('Z:\\missing-wechat-data') await fixture.page.getByRole('button', { name: '连接数据库' }).click() await expect(fixture.page.getByText('微信数据目录不存在,请重新选择目录')).toBeVisible() await expect(fixture.page.getByLabel('微信数据目录')).toBeEditable() await expect(fixture.page.getByRole('button', { name: '选择目录' })).toBeEnabled() await fixture.page.getByRole('button', { name: '选择目录' }).click() await expect(fixture.page.getByLabel('微信数据目录')).toHaveValue('fixture-account') await fixture.page.getByRole('button', { name: '连接数据库' }).click() await dismissFirstUseWelcome(fixture.page) await expect(fixture.page.getByRole('navigation', { name: '一级导航' })).toBeVisible() } finally { await fixture.close() } }) test('P2-01 P2-02 guided connection exposes safe diagnostics and completes all stages', async () => { const fixture = await launchTestApp({ mode: 'disconnected' }) try { await expect(fixture.page.getByText('4.1.9.57')).toBeVisible() await expect(fixture.page.getByText('微信 4.x(WCDB)')).toBeVisible() await expect(fixture.page.getByRole('button', { name: '复制脱敏诊断摘要' })).toBeEnabled() await fixture.page.getByRole('button', { name: '检查完成,继续' }).click() await fixture.page.getByRole('button', { name: '我已准备好' }).click() // macOS(含 Apple Silicon)走「获取密钥」流程,Windows 仍是「准备连接组件」。 await fixture.page .getByRole('button', { name: process.platform === 'darwin' ? '开始获取密钥' : '开始准备连接组件' }) .click() const verifyConnection = fixture.page.getByRole('button', { name: '验证连接', exact: true }) await expect(verifyConnection).toBeEnabled() await verifyConnection.click() await dismissFirstUseWelcome(fixture.page) await expect(fixture.page.getByRole('navigation', { name: '一级导航' })).toBeVisible() } finally { await fixture.close() } }) test('KEY-03 changing one key does not invalidate archive data or unrelated settings', async () => { const fixture = await launchTestApp() try { await expect(fixture.page.getByRole('navigation', { name: '一级导航' })).toBeVisible() const result = await fixture.page.evaluate(async () => { const before = await window.api.getContacts() const image = await window.api.saveImageKeyConfig({ resourceRoot: 'fixture-account', xorKey: '0x41', aesKey: 'fedcba9876543210' }) const after = await window.api.getContacts() const database = await window.api.getSavedDbKey() return { before, after, image, database } }) expect(result.image.success).toBe(true) expect(result.before).toEqual(result.after) expect(result.database.saved).toBe(true) } finally { await fixture.close() } }) test('NAV-01 NAV-02 every top-level page is unique and switchable', async () => { const fixture = await launchTestApp() const labels = ['档案', '问问微信', '日报', '退群监控', 'Agent', '导出', 'API', '设置'] try { const navigation = fixture.page.getByRole('navigation', { name: '一级导航' }) await expect(navigation).toBeVisible() const appShell = fixture.page.locator('.app-shell') await expect(appShell).toBeVisible() const shellLayout = await appShell.evaluate((element) => ({ width: Math.round(element.getBoundingClientRect().width), viewportWidth: window.innerWidth })) expect(shellLayout.width).toBe(shellLayout.viewportWidth) for (const label of labels) { await expect(navigation.getByRole('button', { name: label })).toHaveCount(1) await navigation.getByRole('button', { name: label }).click() await expect(fixture.page.locator(`main.app-shell-main[aria-label="${label}"]`)).toBeVisible() } } finally { await fixture.close() } }) test('LOGS-01 shows Action Gateway audit details and filters by date', async () => { const fixture = await launchTestApp({ now: Date.parse('2026-09-12T10:00:00+08:00') }) try { await fixture.page.getByRole('navigation', { name: '一级导航' }).getByRole('button', { name: '设置' }).click() await fixture.page.getByRole('button', { name: '发送日志' }).click() await expect(fixture.page.getByRole('heading', { name: '日志', exact: true })).toBeVisible() await expect( fixture.page.locator('article').filter({ hasText: '产品测试群' }) ).toBeVisible() await fixture.page.getByText('查看详情').first().click() await expect(fixture.page.getByText('fixture-execution', { exact: true })).toBeVisible() await expect(fixture.page.getByText(/自动化 · 定时日报/)).toBeVisible() await fixture.page.getByLabel('开始日期').fill('2026-09-13') await expect(fixture.page.getByText('没有匹配的日志')).toBeVisible() } finally { await fixture.close() } }) test('NAV-03 exit monitor page shows a member departure event and stays within the viewport', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByRole('button', { name: '退群监控' }).click() await expect(fixture.page.getByRole('heading', { name: '退群监控', exact: true })).toBeVisible() await expect(fixture.page.getByText('测试成员退出了产品测试群', { exact: true })).toBeVisible() await expect(fixture.page.getByText(/240 人 → 239 人/)).toBeVisible() await expect(fixture.page.getByText('实时监听已启用')).toBeVisible() await expect(fixture.page.getByRole('button', { name: '立即检查' })).toBeEnabled() await expect(fixture.page.getByRole('button', { name: '清空记录' })).toBeEnabled() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('NAV-04 exit monitor management can save an empty scope and show the setup state', async () => { const fixture = await launchTestApp() try { await fixture.page.getByRole('button', { name: '退群监控' }).click() await fixture.page.getByRole('button', { name: '管理群聊' }).click() await expect(fixture.page.getByRole('heading', { name: '管理群聊', exact: true })).toBeVisible() await expect(fixture.page.getByText('发送能力已就绪', { exact: true })).toBeVisible() await expect(fixture.page.getByRole('checkbox', { name: '监控产品测试群' })).toBeChecked() await fixture.page.getByRole('checkbox', { name: '监控产品测试群' }).click() await fixture.page.getByRole('checkbox', { name: '监控折叠群聊样本' }).click() await fixture.page.getByRole('button', { name: '保存监控群聊' }).click() await expect(fixture.page.getByText('还没有设置监控群聊', { exact: true })).toBeVisible() await expect(fixture.page.getByRole('button', { name: '选择群聊' })).toBeVisible() } finally { await fixture.close() } }) /** * 退群通知的**内容模板**已随「退群通知」一起迁进自动化,管理群聊页只负责 * 「监测哪些群」。这条用例锁住新家:模板可编辑、可保存,且真的落库。 */ test('NAV-05 leave notification template is editable in the automation workspace', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByRole('button', { name: '退群监控' }).click() await fixture.page.getByRole('button', { name: '配置退群通知自动化' }).click() await expect(fixture.page.getByRole('heading', { name: '自动化', exact: true })).toBeVisible() await expect(fixture.page.getByRole('heading', { name: '退群通知' })).toBeVisible() const template = fixture.page.getByLabel('退群通知模板内容') await expect(template).toHaveValue(/用户: \{user\}/) await expect(template).toHaveValue(/群备注: \{groupRemark\}/) const customTemplate = '[退群监测]\n用户: {user}\n群备注: {groupRemark}' await template.fill(customTemplate) await fixture.page.getByRole('button', { name: '保存' }).click() /* * `exact: true` 不能省:Radix 额外渲染一个 `role="status"` 的无障碍播报节点, * 文本是 `Notification 已保存「退群通知」`,且由 `useNextFrame` **下一帧才填** —— * 裸 regex 会命中它造成 strict mode violation,且是否命中取决于帧时序。 */ await expect(fixture.page.getByText('已保存「退群通知」', { exact: true })).toBeVisible() await fixture.page.reload() await fixture.page.getByRole('button', { name: '退群监控' }).click() await fixture.page.getByRole('button', { name: '配置退群通知自动化' }).click() await expect(fixture.page.getByLabel('退群通知模板内容')).toHaveValue(customTemplate) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('CHAT-01 archive More menu is keyboard-safe and keeps the page usable', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { const conversationSearch = fixture.page.getByRole('searchbox', { name: '搜索会话' }) await conversationSearch.fill('产品') await expect(fixture.page.getByText('产品测试群', { exact: true })).toBeVisible() await conversationSearch.fill('') await fixture.page.getByRole('button', { name: '刷新会话列表' }).click() await fixture.page.getByText('产品测试群', { exact: true }).click() const moreButton = fixture.page.getByRole('button', { name: '更多' }) await moreButton.click() await expect(fixture.page.getByRole('menuitem', { name: '刷新数据' })).toBeVisible() await fixture.page.keyboard.press('Escape') await expect(fixture.page.getByRole('menuitem', { name: '刷新数据' })).toHaveCount(0) await expect(moreButton).toBeFocused() await moreButton.click() await fixture.page.getByRole('menuitem', { name: '刷新数据' }).click() await expect(fixture.page.getByRole('heading', { name: '产品测试群' })).toBeVisible() await fixture.page.getByRole('button', { name: '搜索当前聊天' }).click() const searchInput = fixture.page.getByRole('textbox', { name: '搜索当前聊天内容' }) await expect(searchInput).toBeFocused() await searchInput.fill('测试') await expect(searchInput).toHaveValue('测试') expect( await fixture.page .locator('.chat-archive-header') .evaluate((element) => element.scrollWidth <= element.clientWidth) ).toBe(true) await fixture.page.getByRole('button', { name: '关闭搜索' }).click() await expect(searchInput).toHaveCount(0) const avatarSwitch = fixture.page.getByRole('switch', { name: '显示头像' }) await expect(avatarSwitch).toBeChecked() await avatarSwitch.click() await expect(avatarSwitch).not.toBeChecked() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('CHAT-REALTIME-01 archive refreshes from a native message-shard event', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByText('产品测试群', { exact: true }).click() await expect(fixture.page.getByText('这是一条脱敏测试消息', { exact: true })).toBeVisible() const result = await fixture.page.evaluate(() => window.electron.ipcRenderer.invoke('test:messageChange', { md5: 'group-regular-md5', event: { db: 'message_0.db', table: 'message', action: 'update' }, message: { id: 'msg-monitor', localId: 999, from: 'user', type: '普通文本', datetime: '2026-08-31 12:00:00', content: '消息', isSender: false, name: '测试成员', senderId: 'wxid_fixture_member', createTime: Math.floor(Date.now() / 1000), contentData: { type: 'text', content: '消息' } } }) ) expect(result).toEqual({ success: true }) await expect(fixture.page.getByText('消息', { exact: true })).toBeVisible({ timeout: 3_000 }) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('CHAT-REALTIME-02 refreshes and reorders conversations without losing selection or filter', async () => { const fixture = await launchTestApp({ realtimeReorder: true }) try { const names = fixture.page.locator('.conversation-item-name') await expect(names).toHaveText(['产品测试群', '自动刷新 B', '自动刷新 A']) await fixture.page.getByRole('button', { name: /自动刷新 A/ }).click() await fixture.page.getByRole('searchbox', { name: '搜索会话' }).fill('自动刷新') await expect(fixture.page.getByRole('searchbox', { name: '搜索会话' })).toHaveValue('自动刷新') await expect(fixture.page.locator('.conversation-item-name')).toHaveText([ '自动刷新 B', '自动刷新 A', '自动刷新 D', '自动刷新 C' ]) const readsBeforeMessage = await fixture.page.evaluate(() => window.electron.ipcRenderer.invoke('test:getContactReadCount') ) await fixture.page.evaluate(() => window.electron.ipcRenderer.invoke('test:messageChange', { reorderContactMd5: 'realtime-group-a-md5', event: { db: 'message_0.db', table: 'message', action: 'insert' } }) ) await expect .poll(async () => fixture.page.locator('.conversation-item-name').allTextContents()) .toEqual(['自动刷新 A', '自动刷新 B', '自动刷新 D', '自动刷新 C']) expect(await fixture.page.getByRole('searchbox', { name: '搜索会话' }).inputValue()).toBe( '自动刷新' ) await expect( fixture.page.locator('.conversation-item.active .conversation-item-name') ).toHaveText('自动刷新 A') const readsAfterSingleMessage = await fixture.page.evaluate(() => window.electron.ipcRenderer.invoke('test:getContactReadCount') ) expect(readsAfterSingleMessage - readsBeforeMessage).toBe(1) await Promise.all( Array.from({ length: 50 }, () => fixture.page.evaluate(() => window.electron.ipcRenderer.invoke('test:messageChange', { reorderContactMd5: 'realtime-direct-c-md5', event: { db: 'message_0.db', table: 'message', action: 'insert' } }) ) ) ) await expect .poll(async () => fixture.page.locator('.conversation-item-name').allTextContents()) .toEqual(['自动刷新 A', '自动刷新 B', '自动刷新 C', '自动刷新 D']) const readsAfterBurst = await fixture.page.evaluate(() => window.electron.ipcRenderer.invoke('test:getContactReadCount') ) expect(readsAfterBurst - readsAfterSingleMessage).toBe(1) await expect(fixture.page.locator('.conversation-item-name')).toHaveCount(4) } finally { await fixture.close() } }) test('CHAT-02 personal WeChat send dialog is keyboard-safe and fits the viewport', async () => { test.skip(process.platform !== 'darwin', 'Personal WeChat sending is currently macOS-only') const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByText('产品测试群', { exact: true }).click() const trigger = fixture.page.getByRole('button', { name: '文字转语音' }) await trigger.click() const dialog = fixture.page.getByRole('dialog', { name: '产品测试群' }) await expect(dialog).toBeVisible() const startSending = dialog.getByRole('button', { name: '开始发送' }) if (await startSending.isVisible()) await startSending.click() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(await dialog.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe( true ) const bounds = await dialog.boundingBox() expect(bounds).not.toBeNull() expect(bounds!.y).toBeGreaterThanOrEqual(0) expect(bounds!.y + bounds!.height).toBeLessThanOrEqual( await fixture.page.evaluate(() => window.innerHeight) ) expect(pageErrors).toEqual([]) const voiceText = dialog.getByRole('textbox', { name: '语音文字' }) await voiceText.focus() await expect(voiceText).toBeFocused() expect(await dialog.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe( true ) await fixture.page.keyboard.press('Escape') await expect(dialog).toHaveCount(0) await expect(trigger).toBeFocused() } finally { await fixture.close() } }) test('GUIDE-01 first-use welcome is keyboard-safe and fits the viewport', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { const guideButton = fixture.page.getByRole('button', { name: '新手引导' }) await guideButton.click() const dialog = fixture.page.getByRole('dialog', { name: '开始探索你的微信' }) await expect(dialog).toBeVisible() await expect(dialog.getByRole('button', { name: /试试 AI 群聊日报/ })).toBeVisible() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) await fixture.page.keyboard.press('Escape') await expect(dialog).toHaveCount(0) await expect(guideButton).toBeFocused() } finally { await fixture.close() } }) test('SETTINGS-01 macOS WeChat setup keeps the binding guidance focused', async () => { test.skip(process.platform !== 'darwin', 'The personal WeChat runtime is currently macOS-only') const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page .getByRole('navigation', { name: '一级导航' }) .getByRole('button', { name: '设置' }) .click() await fixture.page.getByRole('button', { name: '微信发送' }).click() await expect( fixture.page.getByText('请保持微信未登录窗口状态,点击“绑定微信”后,再点击微信窗口登录。') ).toBeVisible() await expect(fixture.page.getByRole('button', { name: '支持版本' })).toHaveCount(0) await expect(fixture.page.getByText('查看支持的微信版本')).toHaveCount(0) await expect(fixture.page.getByText('高级诊断')).toHaveCount(0) expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) await fixture.page.getByRole('button', { name: '文字转语音' }).click() const modelSelect = fixture.page.getByRole('combobox', { name: '合成模型' }) await modelSelect.scrollIntoViewIfNeeded() await modelSelect.click() await expect(fixture.page.getByRole('option', { name: 's2.1-pro', exact: true })).toBeVisible() await fixture.page.keyboard.press('Escape') await expect(modelSelect).toBeFocused() await expect(fixture.page.getByRole('searchbox', { name: '按音色名称搜索' })).toBeDisabled() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('SETTINGS-02 basic settings controls fit the default desktop window and keep their semantics', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page .getByRole('navigation', { name: '一级导航' }) .getByRole('button', { name: '设置' }) .click() await expect(fixture.page.getByRole('heading', { name: '账号与数据库' })).toBeVisible() await expect(fixture.page.getByRole('button', { name: '存储与导出' })).toHaveCount(0) const autoLoginSwitch = fixture.page.getByRole('switch', { name: '启动时自动连接数据库' }) await expect(autoLoginSwitch).toBeVisible() await expect(autoLoginSwitch).toBeChecked() await autoLoginSwitch.click() await expect(autoLoginSwitch).not.toBeChecked() await autoLoginSwitch.click() await expect(autoLoginSwitch).toBeChecked() await fixture.page.getByRole('button', { name: '缓存与清理' }).click() await expect(fixture.page.getByRole('heading', { name: '缓存与清理' })).toBeVisible() await expect(fixture.page.getByRole('button', { name: '清理全部' })).toBeEnabled() await fixture.page.getByRole('button', { name: '高级' }).click() const debugSwitch = fixture.page.getByRole('switch', { name: '显示诊断日志' }) await expect(debugSwitch).toBeVisible() await debugSwitch.click() await expect(debugSwitch).toBeChecked() // 防撤回已下线:设置导航不再暴露该入口(与「存储与导出」同一处理方式)。 await expect(fixture.page.getByRole('button', { name: '防撤回' })).toHaveCount(0) await fixture.page.getByRole('button', { name: '关于' }).click() await expect(fixture.page.getByRole('button', { name: '检查更新' })).toBeVisible() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('UPDATE-01 simulated startup update navigates to live progress and never exits on install', async () => { const fixture = await launchTestApp({ updateSimulation: true }) const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.0.0' }) await expect(dialog).toBeVisible() await dialog.getByRole('button', { name: '立即下载' }).click() await expect(fixture.page.getByRole('heading', { name: '关于' })).toBeVisible() await expect(fixture.page.getByRole('button', { name: '关于' })).toHaveClass(/active/) await expect(fixture.page.getByText('正在下载 v2.0.0')).toBeVisible() await expect .poll(async () => { const value = await fixture.page .getByRole('progressbar', { name: /更新下载进度/ }) .getAttribute('aria-valuenow') return Number(value) }) .toBeGreaterThan(0) await expect(fixture.page.getByText(/MB \/ 60\.0 MB/)).toBeVisible() await expect(fixture.page.getByText(/MB\/s/)).toBeVisible() await expect(fixture.page.getByText('v2.0.0 已准备完成')).toBeVisible({ timeout: 5_000 }) await fixture.page.getByRole('button', { name: '立即重启更新' }).click() await expect( fixture.page .getByRole('region', { name: 'Notifications (F8)' }) .getByText('开发模拟模式:更新安装动作已模拟,未实际退出应用。') ).toBeVisible() await expect(fixture.page.getByRole('heading', { name: '关于' })).toBeVisible() expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('UPDATE-02 unsigned macOS update opens the latest release without downloading', async () => { const fixture = await launchTestApp({ unsignedMacUpdate: true }) const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.2.3' }) await expect(dialog).toBeVisible() await expect(dialog.getByText('有新版本可用,前往 GitHub 下载最新版本。')).toBeVisible() await dialog.getByRole('button', { name: '前往下载' }).click() const openedUrl = await fixture.page.evaluate(() => window.electron.ipcRenderer.invoke('app-update:getOpenedDownloadUrl') ) expect(openedUrl).toBe('https://github.com/Wxw-Gu/TraceMemo/releases/latest') await expect(fixture.page.getByRole('progressbar')).toHaveCount(0) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('SETTINGS-03 database key actions keep destructive confirmation keyboard-safe', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page .getByRole('navigation', { name: '一级导航' }) .getByRole('button', { name: '设置' }) .click() await fixture.page.getByRole('button', { name: '数据库密钥' }).click() await expect(fixture.page.getByRole('heading', { name: '数据库密钥' })).toBeVisible() await expect(fixture.page.getByRole('button', { name: '重新验证' })).toBeVisible() const clearKey = fixture.page.getByRole('button', { name: '清除密钥' }) await clearKey.scrollIntoViewIfNeeded() await clearKey.click() const dialog = fixture.page.getByRole('alertdialog', { name: '确认清除数据库密钥?' }) await expect(dialog).toBeVisible() await fixture.page.keyboard.press('Escape') await expect(dialog).toHaveCount(0) await expect(clearKey).toBeFocused() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('SETTINGS-04 image decryption controls keep filtering and selection keyboard-safe', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page .getByRole('navigation', { name: '一级导航' }) .getByRole('button', { name: '设置' }) .click() await fixture.page.getByRole('button', { name: '图片解密' }).click() await expect(fixture.page.getByRole('heading', { name: '图片解密', exact: true })).toBeVisible() const filterGroup = fixture.page.getByRole('radiogroup', { name: '会话类型筛选' }) await filterGroup.scrollIntoViewIfNeeded() const allFilter = filterGroup.getByRole('radio', { name: '全部 3' }) await expect(allFilter).toBeChecked() await allFilter.focus() await fixture.page.keyboard.press('ArrowRight') const groupFilter = filterGroup.getByRole('radio', { name: '群聊 2' }) await expect(groupFilter).toBeFocused() await fixture.page.keyboard.press('Space') await expect(groupFilter).toBeChecked() await fixture.page.getByRole('searchbox', { name: '搜索会话' }).fill('产品') const conversationSelect = fixture.page.getByRole('combobox', { name: '选择会话' }) await conversationSelect.click() await expect(fixture.page.getByRole('option', { name: '产品测试群' })).toBeVisible() await expect(fixture.page.getByRole('option', { name: '折叠群聊样本' })).toHaveCount(0) await fixture.page.keyboard.press('Escape') await expect(conversationSelect).toBeFocused() await conversationSelect.click() await fixture.page.getByRole('option', { name: '产品测试群' }).click() await expect(conversationSelect).toContainText('产品测试群') const testAction = fixture.page.getByRole('button', { name: '测试图片解析' }) await expect(testAction).toBeEnabled() await testAction.click() await expect(fixture.page.getByText('图片可以读取')).toBeVisible() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect( await fixture.page .locator('.settings-page-scroll') .evaluate((element) => element.scrollWidth <= element.clientWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('SETTINGS-05 AI model editor keeps provider and capability semantics', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page .getByRole('navigation', { name: '一级导航' }) .getByRole('button', { name: '设置' }) .click() await fixture.page.getByRole('button', { name: 'AI 模型' }).click() await expect(fixture.page.getByRole('heading', { name: 'AI 模型' })).toBeVisible() await expect(fixture.page.getByRole('button', { name: '当前默认' })).toBeDisabled() await fixture.page.getByRole('button', { name: '编辑' }).click() await expect(fixture.page.getByRole('heading', { name: '编辑供应商' })).toBeVisible() await expect(fixture.page.getByLabel('供应商名称')).toBeFocused() await expect(fixture.page.locator('#ai-provider-editor')).toBeInViewport() await fixture.page.getByRole('button', { name: '关闭' }).click() await fixture.page.getByRole('button', { name: '添加供应商' }).click() await expect(fixture.page.getByRole('heading', { name: '新增供应商' })).toBeVisible() await expect(fixture.page.getByLabel('供应商名称')).toBeFocused() await fixture.page.getByLabel('供应商 ID').fill('fixture-new-provider') const presetSelect = fixture.page.getByRole('combobox', { name: '快速模板' }) await presetSelect.click() await fixture.page.getByRole('option', { name: 'OpenAI', exact: true }).click() await expect(fixture.page.getByLabel('供应商名称')).toHaveValue('OpenAI') await expect(fixture.page.getByLabel('模型名称')).toHaveValue('gpt-4o-mini') const providerType = fixture.page.getByRole('combobox', { name: '供应商类型' }) await providerType.click() await expect(fixture.page.getByRole('option', { name: 'Anthropic Messages' })).toBeVisible() await fixture.page.keyboard.press('Escape') await expect(providerType).toBeFocused() const vision = fixture.page.getByRole('checkbox', { name: '图片理解 gpt-4o-mini' }) const ocr = fixture.page.getByRole('checkbox', { name: '图片文字识别 gpt-4o-mini' }) await vision.click() await expect(vision).toBeChecked() await expect(ocr).toBeChecked() await expect(fixture.page.getByRole('radio', { name: '默认 gpt-4o-mini' })).toBeChecked() await fixture.page.getByText('高级设置', { exact: true }).click() await expect(fixture.page.getByLabel('请求超时(ms)')).toHaveValue('120000') expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect( await fixture.page .locator('.settings-page-scroll') .evaluate((element) => element.scrollWidth <= element.clientWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('SETTINGS-07 voice recognition controls keep selection semantics at the default desktop size', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page .getByRole('navigation', { name: '一级导航' }) .getByRole('button', { name: '设置' }) .click() await fixture.page.getByRole('button', { name: '语音转文字' }).click() await expect(fixture.page.getByRole('heading', { name: '语音转文字' })).toBeVisible() const categoryTabs = fixture.page.getByRole('tablist', { name: '会话类别' }) await categoryTabs.scrollIntoViewIfNeeded() await expect(fixture.page.locator('.voice-conversation-avatar img')).toHaveCount(1) await expect(categoryTabs.getByRole('tab', { name: /群聊/ })).toHaveAttribute( 'data-state', 'active' ) const conversation = fixture.page.getByRole('checkbox', { name: /产品测试群/ }) await conversation.click() await expect(conversation).toBeChecked() const rangeSelect = fixture.page.getByRole('combobox', { name: '语音转写时间范围' }) await rangeSelect.click() await expect(fixture.page.getByRole('option', { name: '所选会话全部历史' })).toBeVisible() await fixture.page.keyboard.press('Escape') await expect(rangeSelect).toBeFocused() await expect(fixture.page.getByRole('button', { name: '开始转写' })).toBeDisabled() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect( await fixture.page .locator('.settings-page-scroll') .evaluate((element) => element.scrollWidth <= element.clientWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('AGENT-01 Agent Hub controls stay usable in the default offline layout', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByRole('button', { name: 'Agent' }).click() await expect(fixture.page.getByRole('heading', { name: 'Agent Hub' })).toBeVisible() await expect(fixture.page.getByText('Agent Hub 未运行')).toBeVisible() await expect(fixture.page.getByRole('button', { name: '扫码登录微信机器人' })).toBeDisabled() const logSource = fixture.page.getByRole('combobox', { name: '筛选日志来源' }) await logSource.scrollIntoViewIfNeeded() await logSource.click() await expect(fixture.page.getByRole('option', { name: '微信连接器' })).toBeVisible() await fixture.page.keyboard.press('Escape') await expect(logSource).toBeFocused() await expect(fixture.page.getByRole('button', { name: '复制日志' })).toBeDisabled() await fixture.page.getByRole('button', { name: '清空', exact: true }).click() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect( await fixture.page .locator('.agent-hub-workspace') .evaluate((element) => element.scrollWidth <= element.clientWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('API-01 manages the local API token and previews the Reader Skill safely', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByRole('button', { name: 'API' }).click() await expect(fixture.page.getByText('API Token', { exact: true })).toBeVisible() await expect(fixture.page.getByText('••••••••••••••••')).toBeVisible() await expect(fixture.page.getByText('fixture-api-token')).toHaveCount(0) await fixture.page.getByRole('button', { name: '显示 Token' }).click() await expect(fixture.page.getByText('fixture-api-token')).toBeVisible() fixture.page.once('dialog', (dialog) => dialog.accept()) await fixture.page.getByRole('button', { name: '重新生成 Token' }).click() await expect(fixture.page.getByText('Token 已生成')).toBeVisible() const moreTrigger = fixture.page .locator('#api-reader-skill') .getByRole('button', { name: '更多' }) await moreTrigger.click() await expect(fixture.page.getByRole('menuitem', { name: '打开本地文件夹' })).toBeVisible() await fixture.page.keyboard.press('Escape') await expect(fixture.page.getByRole('menu')).toHaveCount(0) await expect(moreTrigger).toBeFocused() const previewTrigger = fixture.page .locator('#api-reader-skill') .getByRole('button', { name: '预览 Skill' }) await expect(previewTrigger).toBeEnabled() await previewTrigger.click() const previewDialog = fixture.page.getByRole('dialog', { name: 'TraceMemo Reader Skill 预览' }) await expect(previewDialog).toBeVisible() await expect(previewDialog.getByRole('heading', { name: '能力' })).toBeVisible() await previewDialog.getByRole('button', { name: '原始文本' }).click() await expect(previewDialog.getByText(/# TraceMemo Reader/)).toBeVisible() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect( await previewDialog.evaluate((element) => element.scrollWidth <= element.clientWidth) ).toBe(true) expect(pageErrors).toEqual([]) await fixture.page.keyboard.press('Escape') await expect(previewDialog).toHaveCount(0) await expect(previewTrigger).toBeFocused() } finally { await fixture.close() } }) test('EXPORT-01 multi-chat selection stays local to export and forces HTML', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { const navigation = fixture.page.getByRole('navigation', { name: '一级导航' }) await fixture.page.getByRole('button', { name: '联系人 (1)' }).click() await fixture.page.getByText('文件传输助手', { exact: true }).click() await expect(fixture.page.getByText('转发多条内容', { exact: true })).toBeVisible() await navigation.getByRole('button', { name: '导出' }).click() const contactList = fixture.page.locator('.export-contact-list') await expect(contactList.getByRole('button', { name: /文件传输助手/ })).toHaveAttribute( 'aria-pressed', 'true' ) await fixture.page.getByRole('button', { name: '+ 添加聊天' }).click() await contactList.getByRole('button', { name: /产品测试群/ }).click() await expect(fixture.page.getByText('已选 2 / 5 个')).toBeVisible() await expect(fixture.page.getByRole('radio', { name: 'CSV' })).toBeDisabled() const htmlFormat = fixture.page .getByRole('radiogroup', { name: '导出格式' }) .getByRole('radio', { name: /HTML/ }) await expect(htmlFormat).toBeChecked() await expect(fixture.page.getByText('文件传输助手、产品测试群 · 共 2 个聊天')).toBeVisible() await expect( fixture.page.locator('.export-preview-bubble').filter({ hasText: '这是一条脱敏测试消息' }) ).toHaveCount(1) expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) await navigation.getByRole('button', { name: '档案' }).click() await expect(fixture.page.getByText('转发多条内容', { exact: true })).toBeVisible() } finally { await fixture.close() } }) test('EXPORT-02 large contact list stays bounded and searchable', async () => { const fixture = await launchTestApp({ largeContacts: 1500 }) const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByRole('button', { name: '导出' }).click() await expect(fixture.page.getByText('共 1,503 个')).toBeVisible() const contactList = fixture.page.locator('.export-contact-list') expect(await contactList.getByRole('button').count()).toBeLessThan(50) const compositeButtons = [ fixture.page.getByRole('button', { name: /^全部导出/ }), fixture.page .getByRole('radiogroup', { name: '导出格式' }) .getByRole('radio', { name: /HTML/ }), fixture.page.locator('.export-workspace > aside:first-child > button:last-child') ] for (const button of compositeButtons) { await expect(button).toBeVisible() expect( await button.evaluate( (element) => element.scrollHeight <= element.clientHeight && element.clientHeight > 32 ) ).toBe(true) } const previewGeometryIsStable = await fixture.page .locator('.export-workspace > aside:last-child') .evaluate((previewPanel) => { const scrollRegion = previewPanel.children.item(1) const statistics = previewPanel.children.item(2) if (!(scrollRegion instanceof HTMLElement) || !(statistics instanceof HTMLElement)) { return false } const panelRect = previewPanel.getBoundingClientRect() const scrollRect = scrollRegion.getBoundingClientRect() const statisticsRect = statistics.getBoundingClientRect() return ( scrollRect.bottom <= statisticsRect.top && statisticsRect.bottom <= panelRect.bottom && statistics.scrollHeight <= statistics.clientHeight ) }) expect(previewGeometryIsStable).toBe(true) await fixture.page.getByRole('textbox', { name: '搜索聊天' }).fill('性能样本 1499') const target = contactList.getByRole('button', { name: /性能样本 1499/ }) await expect(target).toBeVisible() await target.click() await expect(target).toHaveAttribute('aria-pressed', 'true') expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('LAYOUT-01 core workspaces fit the default desktop window without page errors', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { const navigation = fixture.page.getByRole('navigation', { name: '一级导航' }) for (const pageName of ['档案', '问问微信', '日报', '导出', 'API', '设置']) { await navigation.getByRole('button', { name: pageName }).click() await expect(fixture.page.locator('main.app-shell-main')).not.toBeEmpty() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) } expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('ARCH-01 ARCH-02 folded chats and supported message types are represented explicitly', async () => { const fixture = await launchTestApp() try { await expect(fixture.page.getByText('产品测试群', { exact: true })).toBeVisible() await fixture.page.getByText('产品测试群', { exact: true }).click() await expect(fixture.page.getByText('这是一条脱敏测试消息', { exact: true })).toBeVisible() await expect(fixture.page.getByText('暂不支持此消息', { exact: true })).toBeVisible() await expect(fixture.page.getByAltText('图片')).toBeVisible() const imageTrigger = fixture.page.getByRole('button', { name: '查看图片' }) await imageTrigger.click() const imageDialog = fixture.page.getByRole('dialog', { name: '图片查看' }) await expect(imageDialog).toBeVisible() await imageDialog.getByRole('button', { name: '放大' }).click() await expect(imageDialog.getByText('110%')).toBeVisible() await imageDialog.getByRole('button', { name: '右旋转' }).click() expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) await fixture.page.keyboard.press('Escape') await expect(imageDialog).toHaveCount(0) await expect(imageTrigger).toBeFocused() await fixture.page.getByRole('button', { name: '折叠群聊 (1)' }).click() await expect(fixture.page.getByText('折叠群聊样本', { exact: true })).toBeVisible() } finally { await fixture.close() } }) test('MEDIA-01 and merged forwards work on the first interaction', async () => { const fixture = await launchTestApp() try { await fixture.page.getByRole('button', { name: '联系人 (1)' }).click() await fixture.page.getByText('文件传输助手', { exact: true }).click() await expect(fixture.page.getByText('转发多条内容', { exact: true })).toBeVisible() await fixture.page.evaluate(() => { Object.defineProperty(window, '__wxePlayCount', { configurable: true, value: 0, writable: true }) HTMLMediaElement.prototype.play = async function () { ;(window as Window & { __wxePlayCount: number }).__wxePlayCount += 1 } HTMLMediaElement.prototype.pause = function () { return undefined } HTMLMediaElement.prototype.load = function () { return undefined } }) await fixture.page.locator('.voice-message').click() await expect(fixture.page.locator('.voice-icon')).toHaveClass(/playing/) expect( await fixture.page.evaluate( () => (window as Window & { __wxePlayCount: number }).__wxePlayCount ) ).toBe(1) } finally { await fixture.close() } }) test('MEDIA-02 MEDIA-04 return accurate unsupported and HTTP 403 reasons', async () => { const fixture = await launchTestApp() try { const result = await fixture.page.evaluate(async () => ({ image: await window.api.getImage('unsupported'), sticker: await window.api.getSticker( 'https://fixture.invalid/403?token=secret', 'b'.repeat(32) ) })) expect(result.image).toMatchObject({ success: false, error: '不支持的 DAT 版本' }) expect(result.sticker).toMatchObject({ success: false, failureCode: 'access_denied', httpStatus: 403 }) } finally { await fixture.close() } }) test('ASK-01 uses the local fixed AI service and keeps evidence in the UI', async () => { const fixture = await launchTestApp() const pageErrors: Error[] = [] fixture.page.on('pageerror', (error) => pageErrors.push(error)) try { await fixture.page.getByRole('button', { name: '问问微信' }).click() await fixture.page.getByPlaceholder(/例如:技术交流群/).fill('测试群讨论了什么?') await fixture.page.getByRole('button', { name: '开始分析' }).click() await expect(fixture.page.getByText(/固定假回答:测试数据中的核心流程正常/)).toBeVisible({ timeout: 15_000 }) await fixture.page.getByRole('button', { name: /历史提问/ }).click() await fixture.page.getByRole('button', { name: '测试群讨论了什么?', exact: true }).click() const toastClose = fixture.page.getByRole('button', { name: '关闭通知' }) await expect(toastClose).toBeVisible() expect(await toastClose.boundingBox()).toMatchObject({ width: 28, height: 28 }) await toastClose.click() await expect(toastClose).toHaveCount(0) expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) expect(pageErrors).toEqual([]) } finally { await fixture.close() } }) test('ASK-02 AI failures are recoverable and do not break the archive', async () => { const fixture = await launchTestApp({ aiFailure: '429' }) try { await fixture.page.getByRole('button', { name: '问问微信' }).click() await fixture.page.getByPlaceholder(/例如:技术交流群/).fill('测试') await fixture.page.getByRole('button', { name: '开始分析' }).click() await expect(fixture.page.getByText(/本地假服务错误 429/)).toBeVisible() await fixture.page.getByRole('button', { name: '档案' }).click() await expect(fixture.page.getByText('产品测试群', { exact: true })).toBeVisible() } finally { await fixture.close() } }) test('REPORT-01 REPORT-02 generates a fixed report with non-empty local assets', async () => { const fixture = await launchTestApp() try { await fixture.page.getByRole('button', { name: '日报' }).click() await fixture.page.getByRole('button', { name: '开始生成日报' }).click() await expect(fixture.page.getByRole('heading', { name: '生成群聊日报' })).toBeVisible() const textModel = fixture.page.getByRole('combobox', { name: '文字总结模型' }) const visionModel = fixture.page.getByRole('combobox', { name: '图片理解模型' }) await expect(textModel).toContainText('固定响应模型') await expect(visionModel).toContainText('固定图片识别模型') await textModel.click() await expect(fixture.page.getByRole('option')).toHaveCount(2) await fixture.page.keyboard.press('Escape') await expect(textModel).toBeFocused() await visionModel.click() await expect(fixture.page.getByRole('option')).toHaveCount(1) await fixture.page.keyboard.press('Escape') await expect(visionModel).toBeFocused() await fixture.page.locator('.report-source-item').filter({ hasText: '产品测试群' }).click() await fixture.page.getByRole('radio', { name: '近 7 天' }).click() const generate = fixture.page.getByRole('button', { name: '开始生成日报' }) await expect(generate).toBeEnabled() await generate.click() await expect(fixture.page.getByAltText('产品测试群 群聊日报')).toBeVisible({ timeout: 15_000 }) await expect(fixture.page.getByText('文字模型')).toBeVisible() await expect(fixture.page.getByText('固定响应模型')).toBeVisible() await expect(fixture.page.getByText('图片模型')).toBeVisible() await expect(fixture.page.getByText('固定图片识别模型')).toBeVisible() await expect(fixture.page.getByRole('menuitem', { name: '生成微信卡片' })).toHaveCount(0) const moreButton = fixture.page.getByRole('button', { name: '更多' }) await moreButton.click() await fixture.page.getByRole('menuitem', { name: '生成微信卡片' }).click() const shareDialog = fixture.page.getByRole('dialog', { name: '生成微信分享卡片' }) await expect(shareDialog).toBeVisible() await expect(shareDialog.locator('input').first()).toHaveValue(/产品测试群日报/) expect( await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth) ).toBe(true) await fixture.page.keyboard.press('Escape') await expect(shareDialog).toHaveCount(0) await expect(moreButton).toBeFocused() const reportTitle = fixture.page.getByRole('heading', { name: '产品测试群 群聊日报' }) await expect(reportTitle).toBeVisible() expect((await reportTitle.boundingBox())?.width || 0).toBeGreaterThan(170) await expect(fixture.page.getByRole('button', { name: '放大' })).toBeEnabled() await fixture.page.getByRole('button', { name: '放大' }).click() const exported = await fixture.page.evaluate(async () => window.api.exportGroupReport({ report: {} as never, metadata: {} as never, templateId: 'v1' }) ) expect(exported.success).toBe(true) expect(exported.imageDataUrl).toMatch(/^data:image\/png;base64,/) expect(existsSync(exported.htmlPath!)).toBe(true) expect(existsSync(exported.pngPath!)).toBe(true) expect(statSync(exported.pngPath!).size).toBeGreaterThan(20) } finally { await fixture.close() } }) test('REPORT-03 report failure is retryable and leaves other pages usable', async () => { const fixture = await launchTestApp({ aiFailure: '401' }) try { await fixture.page.getByRole('button', { name: '日报' }).click() await fixture.page.getByRole('button', { name: '开始生成日报' }).click() await fixture.page.locator('.report-source-item').filter({ hasText: '产品测试群' }).click() await fixture.page.getByRole('radio', { name: '近 7 天' }).click() await fixture.page.getByRole('button', { name: '开始生成日报' }).click() await expect(fixture.page.getByText(/本地假服务错误 401/).first()).toBeVisible() await expect(fixture.page.getByRole('button', { name: '使用所选模型重新生成' })).toBeEnabled() await expect(fixture.page.getByText(/从第三步继续/)).toBeVisible() await fixture.page.getByRole('button', { name: '档案' }).click() await expect(fixture.page.locator('main.app-shell-main[aria-label="档案"]')).toBeVisible() await expect( fixture.page.locator('.conversation-item-name').filter({ hasText: '产品测试群' }).first() ).toBeVisible() } finally { await fixture.close() } }) test('CACHE-01 corrupt startup cache degrades to native fixture data', async () => { const fixture = await launchTestApp({ corruptCache: true }) try { await expect(fixture.page.getByRole('navigation', { name: '一级导航' })).toBeVisible() await expect(fixture.page.getByText('产品测试群', { exact: true })).toBeVisible() } finally { await fixture.close() } }) test('PERF-01 repeated startup with 1500 sessions remains bounded and responsive', async () => { test.setTimeout(60_000) const userData = mkdtempSync(resolve(tmpdir(), 'wxe-e2e-perf-')) try { for (let run = 0; run < 2; run += 1) { const startedAt = Date.now() const fixture = await launchTestApp({ userData, largeContacts: 1500 }) try { await expect(fixture.page.getByRole('navigation', { name: '一级导航' })).toBeVisible({ timeout: 10_000 }) expect(Date.now() - startedAt).toBeLessThan(10_000) await fixture.page .getByRole('navigation', { name: '一级导航' }) .getByRole('button', { name: '设置' }) .click() await expect(fixture.page.locator('main.app-shell-main[aria-label="设置"]')).toBeVisible() } finally { await fixture.close() } } } finally { rmSync(userData, { recursive: true, force: true }) } }) test('KEY-04 e2e diagnostic log does not contain a supplied key', async () => { const fixture = await launchTestApp() const key = 'c'.repeat(64) try { await fixture.page.evaluate( (databaseKey) => window.api.writeAppLog({ level: 'error', scope: 'key-test', message: `fixture key=${databaseKey}` }), key ) const logPath = resolve(fixture.userData, 'logs/e2e.log') const content = readFileSync(logPath, 'utf8') expect(content).not.toContain(key) } finally { await fixture.close() } })