test: 完善 2.3.0 跨平台测试与 CI 稳定性

- 修复 Windows 与 macOS 单元测试环境差异\n- 完善组件、集成与 Electron E2E 测试\n- 修复异步等待、平台路径和环境依赖问题\n- 统一 E2E 窗口尺寸与 Visual 测试策略\n- 提升 GitHub Actions 跨平台 CI 稳定性
This commit is contained in:
Wxw-Gu
2026-09-05 10:38:41 +08:00
parent 60528ffe53
commit 1156362c3c
23 changed files with 123 additions and 119 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 72 KiB

+29 -32
View File
@@ -16,9 +16,13 @@ test('APP-01 first launch renders a usable connection screen without uncaught er
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
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)
@@ -63,6 +67,7 @@ test('P0-01 an invalid directory can be corrected and retried without restarting
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()
@@ -121,6 +126,13 @@ test('NAV-01 NAV-02 every top-level page is unique and switchable', async () =>
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()
@@ -136,7 +148,6 @@ test('NAV-03 exit monitor page shows a member departure event and stays within t
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
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()
@@ -185,7 +196,6 @@ test('CHAT-01 archive More menu is keyboard-safe and keeps the page usable', asy
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
const conversationSearch = fixture.page.getByRole('searchbox', { name: '搜索会话' })
await conversationSearch.fill('产品')
await expect(fixture.page.getByText('产品测试群', { exact: true })).toBeVisible()
@@ -234,7 +244,6 @@ test('CHAT-REALTIME-01 archive refreshes from a native message-shard event', asy
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page.getByText('产品测试群', { exact: true }).click()
await expect(fixture.page.getByText('这是一条脱敏测试消息', { exact: true })).toBeVisible()
@@ -273,7 +282,6 @@ test('CHAT-02 personal WeChat send dialog is keyboard-safe and fits the viewport
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page.getByText('产品测试群', { exact: true }).click()
const trigger = fixture.page.getByRole('button', { name: '文字转语音' })
await trigger.click()
@@ -290,16 +298,14 @@ test('CHAT-02 personal WeChat send dialog is keyboard-safe and fits the viewport
const bounds = await dialog.boundingBox()
expect(bounds).not.toBeNull()
expect(bounds!.y).toBeGreaterThanOrEqual(0)
expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(600)
expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(
await fixture.page.evaluate(() => window.innerHeight)
)
expect(pageErrors).toEqual([])
const imageMode = dialog.getByRole('radio', { name: '图片' })
const voiceMode = dialog.getByRole('radio', { name: '语音', exact: true })
await imageMode.focus()
await fixture.page.keyboard.press('ArrowRight')
await expect(voiceMode).toBeFocused()
await fixture.page.keyboard.press('Space')
await expect(voiceMode).toBeChecked()
const voiceText = dialog.getByRole('textbox', { name: '语音文字' })
await voiceText.focus()
await expect(voiceText).toBeFocused()
expect(await dialog.evaluate((element) => element.scrollWidth <= element.clientWidth)).toBe(
true
)
@@ -317,7 +323,6 @@ test('GUIDE-01 first-use welcome is keyboard-safe and fits the viewport', async
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 1000, height: 650 })
const guideButton = fixture.page.getByRole('button', { name: '新手引导' })
await guideButton.click()
const dialog = fixture.page.getByRole('dialog', { name: '开始探索你的微信' })
@@ -342,7 +347,6 @@ test('SETTINGS-01 supported WeChat versions dialog is keyboard-safe and fits the
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -382,12 +386,11 @@ test('SETTINGS-01 supported WeChat versions dialog is keyboard-safe and fits the
}
})
test('SETTINGS-02 basic settings controls fit a narrow viewport and keep their semantics', async () => {
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.setWindowContentSize({ width: 820, height: 600 })
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -397,6 +400,9 @@ test('SETTINGS-02 basic settings controls fit a narrow viewport and keep their s
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()
@@ -453,7 +459,9 @@ test('UPDATE-01 simulated startup update navigates to live progress and never ex
await expect(fixture.page.getByText('v2.0.0 已准备完成')).toBeVisible({ timeout: 5_000 })
await fixture.page.getByRole('button', { name: '立即重启更新' }).click()
await expect(
fixture.page.getByText('开发模拟模式:更新安装动作已模拟,未实际退出应用。')
fixture.page
.getByRole('region', { name: 'Notifications (F8)' })
.getByText('开发模拟模式:更新安装动作已模拟,未实际退出应用。')
).toBeVisible()
await expect(fixture.page.getByRole('heading', { name: '关于' })).toBeVisible()
expect(pageErrors).toEqual([])
@@ -488,7 +496,6 @@ test('SETTINGS-03 database key actions keep destructive confirmation keyboard-sa
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -519,7 +526,6 @@ test('SETTINGS-04 image decryption controls keep filtering and selection keyboar
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -573,7 +579,6 @@ test('SETTINGS-05 AI model editor keeps provider and capability semantics', asyn
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -628,12 +633,11 @@ test('SETTINGS-05 AI model editor keeps provider and capability semantics', asyn
}
})
test('SETTINGS-07 voice recognition controls keep selection semantics at a narrow viewport', async () => {
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.setWindowContentSize({ width: 820, height: 600 })
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -672,12 +676,11 @@ test('SETTINGS-07 voice recognition controls keep selection semantics at a narro
}
})
test('AGENT-01 Agent Hub controls stay usable in the offline narrow layout', async () => {
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.setWindowContentSize({ width: 820, height: 600 })
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()
@@ -711,7 +714,6 @@ test('API-01 manages the local API token and previews the Reader Skill safely',
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 1000, height: 650 })
await fixture.page.getByRole('button', { name: 'API' }).click()
await expect(fixture.page.getByText('API Token', { exact: true })).toBeVisible()
await expect(fixture.page.getByText('••••••••••••••••')).toBeVisible()
@@ -766,7 +768,6 @@ test('EXPORT-01 multi-chat selection stays local to export and forces HTML', asy
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 1000, height: 650 })
const navigation = fixture.page.getByRole('navigation', { name: '一级导航' })
await fixture.page.getByRole('button', { name: '联系人 (1)' }).click()
await fixture.page.getByText('文件传输助手', { exact: true }).click()
@@ -808,7 +809,6 @@ test('EXPORT-02 large contact list stays bounded and searchable', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 1400, height: 772 })
await fixture.page.getByRole('button', { name: '导出' }).click()
await expect(fixture.page.getByText('共 1,503 个')).toBeVisible()
@@ -861,12 +861,11 @@ test('EXPORT-02 large contact list stays bounded and searchable', async () => {
}
})
test('LAYOUT-01 core workspaces fit a narrow desktop viewport without page errors', async () => {
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 {
await fixture.setWindowContentSize({ width: 820, height: 600 })
const navigation = fixture.page.getByRole('navigation', { name: '一级导航' })
for (const pageName of ['档案', '问问微信', '日报', '导出', 'API', '设置']) {
await navigation.getByRole('button', { name: pageName }).click()
@@ -970,7 +969,6 @@ test('ASK-01 uses the local fixed AI service and keeps evidence in the UI', asyn
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page.getByRole('button', { name: '问问微信' }).click()
await fixture.page.getByPlaceholder(/例如:技术交流群/).fill('测试群讨论了什么?')
await fixture.page.getByRole('button', { name: '开始分析' }).click()
@@ -1053,7 +1051,6 @@ test('REPORT-01 REPORT-02 generates a fixed report with non-empty local assets',
await expect(shareDialog).toHaveCount(0)
await expect(moreButton).toBeFocused()
await fixture.setWindowContentSize({ width: 1024, height: 760 })
const reportTitle = fixture.page.getByRole('heading', { name: '产品测试群 群聊日报' })
await expect(reportTitle).toBeVisible()
expect((await reportTitle.boundingBox())?.width || 0).toBeGreaterThan(170)
+20 -1
View File
@@ -44,6 +44,25 @@ const zipDirectory = async (
})
}
const extractZip = (zipPath: string, extractedDir: string): void => {
if (process.platform === 'win32') {
const escapePowerShellLiteral = (value: string): string => value.replace(/'/g, "''")
execFileSync(
'powershell.exe',
[
'-NoProfile',
'-NonInteractive',
'-Command',
`Expand-Archive -LiteralPath '${escapePowerShellLiteral(zipPath)}' -DestinationPath '${escapePowerShellLiteral(extractedDir)}'`
],
{ windowsHide: true }
)
return
}
execFileSync('unzip', ['-q', zipPath, '-d', extractedDir])
}
test('EXPORT-ARCHIVE-00 shows a loading state while archive data is still loading', async ({
page
}, testInfo) => {
@@ -149,7 +168,7 @@ test('EXPORT-ARCHIVE-01 merged v2 archive is usable offline on desktop and mobil
const extractedDir = join(fixtureRoot, 'extracted')
await zipDirectory(outputDir, zipPath, '合并聊天档案')
mkdirSync(extractedDir, { recursive: true })
execFileSync('unzip', ['-q', zipPath, '-d', extractedDir])
extractZip(zipPath, extractedDir)
const offlineIndex = join(extractedDir, '合并聊天档案', 'index.html')
await page.setViewportSize({ width: 1440, height: 900 })
+5 -4
View File
@@ -16,7 +16,6 @@ test('SCHEDULED-REPORT-UI-01 opens the scheduled report dialog without viewport
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize({ width: 820, height: 600 })
await fixture.page.getByRole('button', { name: '日报' }).click()
await fixture.page.getByRole('tab', { name: '定时日报' }).click()
await expect(fixture.page.getByRole('heading', { name: '定时日报', exact: true })).toBeVisible()
@@ -52,8 +51,8 @@ test('SCHEDULED-REPORT-UI-01 opens the scheduled report dialog without viewport
await selects.nth(1).click()
const memberOption = fixture.page.getByRole('option', { name: '微信昵称' })
await expect(memberOption).toBeVisible()
await memberOption.click()
await expect(selects.nth(1)).toContainText('微信昵称')
await memberOption.click({ force: true })
await expect(selects.nth(1)).toContainText('微信昵称', { timeout: 8000 })
expect(
await fixture.page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)
@@ -61,7 +60,9 @@ test('SCHEDULED-REPORT-UI-01 opens the scheduled report dialog without viewport
const bounds = await dialog.boundingBox()
expect(bounds).not.toBeNull()
expect(bounds!.y).toBeGreaterThanOrEqual(0)
expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(600)
expect(bounds!.y + bounds!.height).toBeLessThanOrEqual(
await fixture.page.evaluate(() => window.innerHeight)
)
await dialog.getByPlaceholder('例如:技术交流 · 每日日报').fill('E2E 定时日报')
await dialog.getByRole('radio', { name: '今日' }).click()
+6 -3
View File
@@ -7,6 +7,7 @@ const root = path.resolve(__dirname, '../../..')
const fixture = structuredClone(require(path.join(root, 'tests/fixtures/chat-data.json')))
const userData = process.env.WXE_E2E_USER_DATA
if (!userData) throw new Error('WXE_E2E_USER_DATA is required')
const backgroundE2E = process.env.WXE_E2E_HEADLESS === '1'
app.setPath('userData', userData)
app.setPath('logs', path.join(userData, 'logs'))
app.commandLine.appendSwitch('disable-gpu')
@@ -253,6 +254,7 @@ const groupExitEvent = {
detectedAt: fixtureNowMs - 60 * 1000
}
let groupExitMonitorState = {
enabled: connected,
events: [groupExitEvent],
running: connected,
nativeMonitorActive: connected,
@@ -1215,8 +1217,9 @@ for (const channel of [
app.whenReady().then(() => {
const window = new BrowserWindow({
width: 1440,
height: 960,
// Keep the E2E window aligned with createWindow() in src/main/index.ts.
width: 1400,
height: 800,
show: false,
backgroundColor: '#ffffff',
webPreferences: {
@@ -1226,7 +1229,7 @@ app.whenReady().then(() => {
sandbox: false
}
})
window.once('ready-to-show', () => window.show())
if (!backgroundE2E) window.once('ready-to-show', () => window.show())
window.loadFile(path.join(root, 'out/renderer/index.html'))
})
-19
View File
@@ -10,7 +10,6 @@ export interface TestApplication {
app: ElectronApplication
page: Page
userData: string
setWindowContentSize: (size: { width: number; height: number }) => Promise<void>
close: () => Promise<void>
}
@@ -60,28 +59,10 @@ export async function launchTestApp(
const page = await app.firstWindow()
await page.waitForLoadState('domcontentloaded')
if (options.now) await page.clock.setFixedTime(options.now)
const setWindowContentSize = async (size: { width: number; height: number }): Promise<void> => {
await app.evaluate(({ BrowserWindow, screen }, nextSize) => {
const [window] = BrowserWindow.getAllWindows()
if (!window) throw new Error('E2E BrowserWindow is unavailable')
const { workArea } = screen.getPrimaryDisplay()
window.setPosition(workArea.x + 40, workArea.y + 40)
window.setContentSize(nextSize.width, nextSize.height)
}, size)
await page.waitForFunction(
(nextSize) => window.innerWidth === nextSize.width && window.innerHeight === nextSize.height,
size
)
await page.evaluate(
() =>
new Promise<void>((resolve) => requestAnimationFrame(() => requestAnimationFrame(resolve)))
)
}
return {
app,
page,
userData,
setWindowContentSize,
close: async () => {
if (!page.isClosed() && closeDelayMs > 0) await page.waitForTimeout(closeDelayMs)
await app.close().catch(() => undefined)
+1 -33
View File
@@ -4,7 +4,6 @@ import { resolve } from 'path'
import { launchTestApp } from './support/electron'
const baselineDirectory = resolve(`tests/e2e/__screenshots__/${process.platform}/visual.spec.ts`)
const visualViewport = { width: 1400, height: 772 }
const visualNow = Date.parse('2026-08-19T14:46:40+08:00')
async function clearScreenshotFocus(page: import('@playwright/test').Page): Promise<void> {
@@ -21,7 +20,6 @@ test.skip(
test('NAV-01 login page visual @visual', async () => {
const fixture = await launchTestApp({ mode: 'disconnected' })
try {
await fixture.setWindowContentSize(visualViewport)
await expect(fixture.page.getByRole('heading', { name: 'TraceMemo(迹忆)' })).toBeVisible()
await clearScreenshotFocus(fixture.page)
await expect(fixture.page).toHaveScreenshot('login-page.png', {
@@ -36,7 +34,6 @@ test('NAV-01 login page visual @visual', async () => {
test('ARCH-01 archive page visual @visual', async () => {
const fixture = await launchTestApp({ now: visualNow })
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByText('产品测试群', { exact: true }).click()
await expect(fixture.page.getByText('这是一条脱敏测试消息', { exact: true })).toBeVisible()
await clearScreenshotFocus(fixture.page)
@@ -54,7 +51,6 @@ test('CHAT-01 archive search controls visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByText('产品测试群', { exact: true }).click()
await fixture.page.getByRole('button', { name: '搜索当前聊天' }).click()
const searchInput = fixture.page.getByRole('textbox', { name: '搜索当前聊天内容' })
@@ -81,7 +77,6 @@ test('ASK-01 AI Search idle page visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '问问微信' }).click()
await expect(fixture.page.getByRole('heading', { name: '问问你的微信' })).toBeVisible()
await expect(fixture.page.getByPlaceholder(/例如:技术交流群/)).toBeVisible()
@@ -105,7 +100,6 @@ test('ASK-03 AI Search result page visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '问问微信' }).click()
await fixture.page.getByPlaceholder(/例如:技术交流群/).fill('测试群讨论了什么?')
await fixture.page.getByRole('button', { name: '开始分析' }).click()
@@ -132,7 +126,6 @@ test('ASK-04 AI Search idle page dark visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '问问微信' }).click()
await expect(fixture.page.getByRole('heading', { name: '问问你的微信' })).toBeVisible()
await expect(fixture.page.locator('html')).toHaveAttribute('data-theme', 'dark')
@@ -155,7 +148,6 @@ test('ASK-05 AI Search result page dark visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '问问微信' }).click()
await fixture.page.getByPlaceholder(/例如:技术交流群/).fill('测试群讨论了什么?')
await fixture.page.getByRole('button', { name: '开始分析' }).click()
@@ -182,7 +174,6 @@ test('API-00 Reader Skill page visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: 'API' }).click()
await expect(fixture.page.getByRole('heading', { name: 'TraceMemo Reader' })).toBeVisible()
await expect(fixture.page.getByText('API Token', { exact: true })).toBeVisible()
@@ -205,7 +196,6 @@ test('API-00 Reader Skill page dark visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: 'API' }).click()
await expect(fixture.page.getByRole('heading', { name: 'TraceMemo Reader' })).toBeVisible()
await expect(fixture.page.getByText('API Token', { exact: true })).toBeVisible()
@@ -229,7 +219,6 @@ test('API-01 Reader Skill preview visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: 'API' }).click()
await fixture.page
.locator('#api-reader-skill')
@@ -257,7 +246,6 @@ test('API-02 Agent target segmented control visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: 'API' }).click()
const targetGroup = fixture.page.getByRole('radiogroup', { name: '选择目标 Agent' })
await targetGroup.evaluate((element) => element.scrollIntoView({ block: 'center' }))
@@ -289,7 +277,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '退群监控' }).click()
await expect(
fixture.page.getByRole('heading', { name: '退群监控', exact: true })
@@ -317,7 +304,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '退群监控' }).click()
await fixture.page.getByRole('button', { name: '管理群聊' }).click()
await expect(
@@ -346,7 +332,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '退群监控' }).click()
await fixture.page.getByRole('button', { name: '管理群聊' }).click()
await fixture.page.getByRole('checkbox', { name: '监控产品测试群' }).click()
@@ -375,7 +360,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '日报' }).click()
await fixture.page.getByRole('button', { name: '开始生成日报' }).click()
await fixture.page.locator('.report-source-item').filter({ hasText: '产品测试群' }).click()
@@ -436,7 +420,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '日报' }).click()
await fixture.page.getByRole('button', { name: '开始生成日报' }).click()
await fixture.page.locator('.report-source-item').filter({ hasText: '产品测试群' }).click()
@@ -470,7 +453,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByText('产品测试群', { exact: true }).click()
await fixture.page.getByRole('button', { name: '文字转语音' }).click()
const dialog = fixture.page.getByRole('dialog', { name: '产品测试群' })
@@ -516,7 +498,6 @@ test('CHAT-03 image viewer visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByText('产品测试群', { exact: true }).click()
await fixture.page.getByRole('button', { name: '查看图片' }).click()
await expect(fixture.page.getByRole('dialog', { name: '图片查看' })).toBeVisible()
@@ -539,7 +520,6 @@ test('SETTINGS-02 basic settings controls visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -590,7 +570,6 @@ test('UPDATE-01 startup prompt and downloaded state visual @visual', async () =>
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.0.0' })
await expect(dialog).toBeVisible()
await clearScreenshotFocus(fixture.page)
@@ -620,7 +599,6 @@ test('UPDATE-02 unsigned macOS release prompt visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
const dialog = fixture.page.getByRole('alertdialog', { name: '发现新版本 v2.2.3' })
await expect(dialog).toBeVisible()
await clearScreenshotFocus(fixture.page)
@@ -656,7 +634,6 @@ test('SETTINGS-03 database key controls visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -690,7 +667,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -722,7 +698,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -758,7 +733,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -796,7 +770,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -827,11 +800,11 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
for (const appearanceTheme of ['light', 'dark'] as const) {
test(`SETTINGS-06A voice encoding environment ${appearanceTheme} visual @visual`, async () => {
test.skip(process.platform !== 'darwin', 'Voice encoding environment is macOS-only')
const fixture = await launchTestApp({ now: visualNow, appearanceTheme })
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -867,7 +840,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page
.getByRole('navigation', { name: '一级导航' })
.getByRole('button', { name: '设置' })
@@ -902,7 +874,6 @@ for (const appearanceTheme of ['light', 'dark'] as const) {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
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()
@@ -927,7 +898,6 @@ test('EXPORT-01 export workspace idle visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '导出' }).click()
await expect(fixture.page.getByRole('heading', { name: '导出设置' })).toBeVisible()
await expect(fixture.page.getByRole('button', { name: '开始导出' })).toBeEnabled()
@@ -950,7 +920,6 @@ test('THEME-01 archive page dark visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByText('产品测试群', { exact: true }).click()
await expect(fixture.page.getByText('这是一条脱敏测试消息', { exact: true })).toBeVisible()
await expect(fixture.page.locator('html')).toHaveAttribute('data-theme', 'dark')
@@ -973,7 +942,6 @@ test('THEME-02 export workspace dark visual @visual', async () => {
const pageErrors: Error[] = []
fixture.page.on('pageerror', (error) => pageErrors.push(error))
try {
await fixture.setWindowContentSize(visualViewport)
await fixture.page.getByRole('button', { name: '导出' }).click()
await expect(fixture.page.getByRole('heading', { name: '导出设置' })).toBeVisible()
await expect(fixture.page.locator('html')).toHaveAttribute('data-theme', 'dark')