mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 03:27:00 +08:00
test: 建立桌面端自动化回归测试体系并完善跨平台 CI
(cherry picked from commit 74267ae2f63f8256c3da84b04f5b330e6e9d4c67)
This commit is contained in:
@@ -0,0 +1,289 @@
|
||||
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'
|
||||
|
||||
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: 'WechatExplorer' })).toBeVisible()
|
||||
await expect(fixture.page.getByRole('main')).not.toBeEmpty()
|
||||
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))
|
||||
const errorDialog = fixture.page.waitForEvent('dialog')
|
||||
await fixture.page
|
||||
.getByRole('button', { name: '连接数据库' })
|
||||
.evaluate((element: HTMLButtonElement) => element.click())
|
||||
const dialog = await errorDialog
|
||||
expect(dialog.message()).toContain('数据库密钥无效')
|
||||
await dialog.dismiss()
|
||||
|
||||
await expect(keyInput).toBeVisible()
|
||||
await keyInput.fill('a'.repeat(64))
|
||||
await fixture.page.getByRole('button', { name: '连接数据库' }).click()
|
||||
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()
|
||||
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('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()
|
||||
await fixture.page.locator('.image-bubble.image-loaded').click()
|
||||
await expect(fixture.page.getByText('图片查看', { exact: true })).toBeVisible()
|
||||
await fixture.page.locator('.image-viewer-overlay').click({ position: { x: 5, y: 5 } })
|
||||
|
||||
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()
|
||||
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
|
||||
})
|
||||
} 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()
|
||||
await fixture.page.locator('.report-source-item').filter({ hasText: '产品测试群' }).click()
|
||||
const generate = fixture.page.getByRole('button', { name: '开始生成日报' })
|
||||
await expect(generate).toBeEnabled()
|
||||
await generate.click()
|
||||
await expect(fixture.page.getByAltText('产品测试群 群聊日报')).toBeVisible({
|
||||
timeout: 15_000
|
||||
})
|
||||
|
||||
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('button', { name: '开始生成日报' }).click()
|
||||
await expect(fixture.page.getByText(/本地假服务错误 401/).first()).toBeVisible()
|
||||
await expect(fixture.page.getByRole('button', { name: '重试' })).toBeEnabled()
|
||||
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()
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user