mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-10-03 18:33:14 +08:00
284 lines
11 KiB
TypeScript
284 lines
11 KiB
TypeScript
import { _electron as electron, expect, test } from '@playwright/test'
|
|
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs'
|
|
import { tmpdir } from 'node:os'
|
|
import { join, resolve } from 'node:path'
|
|
|
|
test.describe.configure({ retries: 2 })
|
|
|
|
test('REPORT-TEMPLATE-MARKET-E2E-01 reads, installs, restores, renders, and switches published templates', async () => {
|
|
test.setTimeout(120_000)
|
|
const userData = mkdtempSync(join(tmpdir(), 'tracememo-template-market-user-'))
|
|
const outputDir = mkdtempSync(join(tmpdir(), 'tracememo-template-market-output-'))
|
|
const launch = (): ReturnType<typeof electron.launch> =>
|
|
electron.launch({
|
|
args: [resolve('out/main/reportTemplateTest.js')],
|
|
env: {
|
|
...process.env,
|
|
TRACEMEMO_TEMPLATE_TEST_USER_DATA: userData,
|
|
TRACEMEMO_REPORT_OUTPUT_DIR: outputDir
|
|
}
|
|
})
|
|
|
|
const report = {
|
|
overview: '虚构远端模板日报概览',
|
|
hero: {
|
|
headline: '今日群聊重点',
|
|
summary: '虚构的结构化日报内容',
|
|
keyTakeaway: '保留结构化快照并切换版式',
|
|
pendingNote: '',
|
|
statusLine: ''
|
|
},
|
|
topics: [
|
|
{
|
|
title: '模板市场联调',
|
|
timeRange: '09:00-10:00',
|
|
heat: '高' as const,
|
|
participants: ['小明'],
|
|
summary: '验证安装、重启恢复和导出',
|
|
keywords: ['模板', '日报'],
|
|
messages: []
|
|
}
|
|
],
|
|
resources: [],
|
|
importantMessages: [],
|
|
quotes: [],
|
|
qa: [],
|
|
todos: [],
|
|
unresolved: [],
|
|
storylines: [],
|
|
reversals: [],
|
|
participantChains: [],
|
|
analytics: {
|
|
topicHeat: [{ topic: '模板市场联调', score: 1 }],
|
|
activeTimeline: '09:00-10:00',
|
|
topSpeakers: [{ name: '小明', count: 1 }],
|
|
voiceLeaderboard: []
|
|
},
|
|
keywords: ['模板', '日报'],
|
|
media: { gallery: [], voiceHighlights: [], funBadges: [] }
|
|
}
|
|
const metadata = {
|
|
groupName: '虚构市场验收群',
|
|
reportDate: '2026-09-07',
|
|
dateRange: '今日',
|
|
messageCount: 1,
|
|
activeUsers: 1,
|
|
timeSpan: '09:00-10:00',
|
|
generatedAt: '2026-09-07 10:00',
|
|
recordNote: 'fixture',
|
|
footerNote: 'market fixture',
|
|
heroParticipants: ['小明'],
|
|
avatars: {}
|
|
}
|
|
|
|
let app: Awaited<ReturnType<typeof electron.launch>> | null = null
|
|
try {
|
|
app = await launch()
|
|
let page = await app.firstWindow()
|
|
await page.waitForLoadState('domcontentloaded')
|
|
|
|
const catalogResult = await page.evaluate(() => window.api.listReportTemplateCatalog())
|
|
expect(catalogResult.success, catalogResult.error).toBe(true)
|
|
const catalog = catalogResult.catalog
|
|
expect(catalog?.status).toBe('published')
|
|
expect(catalog?.templates.length).toBeGreaterThanOrEqual(3)
|
|
const sourceCommit = catalog?.source?.commit
|
|
expect(sourceCommit).toMatch(/^[a-f0-9]{40}$/)
|
|
const entries = catalog!.templates
|
|
for (const requiredId of [
|
|
'community.github.tracememo.quickread',
|
|
'community.github.tracememo.paperdaily',
|
|
'community.github.tracememo.teamboard'
|
|
]) expect(entries.some((entry) => entry.id === requiredId)).toBe(true)
|
|
for (const entry of entries) {
|
|
expect(entry.interfaceVersion).toBe('1')
|
|
expect(entry.status).toBe('published')
|
|
expect(entry.download).toContain(`/Wxw-Gu/TraceMemo-Templates/${sourceCommit}/`)
|
|
expect(entry.preview).toContain(`/Wxw-Gu/TraceMemo-Templates/${sourceCommit}/`)
|
|
}
|
|
|
|
const exportsById = new Map<
|
|
string,
|
|
{ htmlPath: string; pngPath: string; imageDataUrl: string; version: string }
|
|
>()
|
|
for (const entry of entries) {
|
|
const installed = await page.evaluate(
|
|
async ({ id, version }) => window.api.installReportTemplateFromCatalog(id, version),
|
|
{ id: entry.id, version: entry.version }
|
|
)
|
|
expect(installed.success, installed.error).toBe(true)
|
|
expect(installed.catalogEntry?.sha256).toBe(entry.sha256)
|
|
expect(installed.template?.id).toBe(entry.id)
|
|
expect(installed.template?.version).toBe(entry.version)
|
|
|
|
const exported = await page.evaluate(
|
|
async ({ id, version, reportValue, metadataValue }) =>
|
|
window.api.exportGroupReport({
|
|
templateRef: { id, version },
|
|
report: reportValue,
|
|
metadata: metadataValue
|
|
}),
|
|
{ id: entry.id, version: entry.version, reportValue: report, metadataValue: metadata }
|
|
)
|
|
expect(exported.success, exported.error).toBe(true)
|
|
expect(exported.htmlPath).toBeTruthy()
|
|
expect(exported.pngPath).toBeTruthy()
|
|
expect(readFileSync(exported.htmlPath!, 'utf8')).toContain('虚构的结构化日报内容')
|
|
const png = readFileSync(exported.pngPath!)
|
|
expect(png.length).toBeGreaterThan(1000)
|
|
const renderedWidth = png.readUInt32BE(16)
|
|
expect(renderedWidth).toBeGreaterThanOrEqual(entry.platform === 'desktop' ? 1000 : 400)
|
|
expect(png.readUInt32BE(20)).toBeGreaterThan(100)
|
|
exportsById.set(entry.id, {
|
|
htmlPath: exported.htmlPath!,
|
|
pngPath: exported.pngPath!,
|
|
imageDataUrl: exported.imageDataUrl!,
|
|
version: entry.version
|
|
})
|
|
}
|
|
|
|
await app.close()
|
|
app = await launch()
|
|
page = await app.firstWindow()
|
|
await page.waitForLoadState('domcontentloaded')
|
|
const restored = await page.evaluate(() => window.api.listReportTemplates())
|
|
for (const entry of entries) {
|
|
expect(
|
|
restored.some((template) => template.id === entry.id && template.version === entry.version)
|
|
).toBe(true)
|
|
}
|
|
|
|
const quickread = entries.find((entry) => entry.id.endsWith('.quickread'))!
|
|
const paperdaily = entries.find((entry) => entry.id.endsWith('.paperdaily'))!
|
|
const firstExport = exportsById.get(quickread.id)!
|
|
const saved = await page.evaluate(
|
|
async ({ firstExportValue, reportValue, metadataValue, templateId }) =>
|
|
window.api.saveGeneratedReport({
|
|
contactId: 'fixture-market-group',
|
|
contactName: metadataValue.groupName,
|
|
source: 'manual',
|
|
dateRange: metadataValue.dateRange,
|
|
messageCount: metadataValue.messageCount,
|
|
generatedAt: '2026-09-07T10:00:00.000Z',
|
|
reportDate: metadataValue.reportDate,
|
|
generatedImage: firstExportValue.imageDataUrl,
|
|
htmlPath: firstExportValue.htmlPath,
|
|
pngPath: firstExportValue.pngPath,
|
|
reportSnapshot: reportValue,
|
|
reportMetadata: metadataValue,
|
|
templateId
|
|
}),
|
|
{
|
|
firstExportValue: firstExport,
|
|
reportValue: report,
|
|
metadataValue: metadata,
|
|
templateId: `external:${quickread.id}@${quickread.version}`
|
|
}
|
|
)
|
|
expect(saved.success, saved.error).toBe(true)
|
|
const reportId = saved.record!.id
|
|
|
|
const switchedExport = await page.evaluate(
|
|
async ({ id, version, reportValue, metadataValue }) =>
|
|
window.api.exportGroupReport({
|
|
templateRef: { id, version },
|
|
report: reportValue,
|
|
metadata: metadataValue
|
|
}),
|
|
{ id: paperdaily.id, version: paperdaily.version, reportValue: report, metadataValue: metadata }
|
|
)
|
|
expect(switchedExport.success, switchedExport.error).toBe(true)
|
|
const switched = await page.evaluate(
|
|
async ({ reportIdValue, id, version, exported }) =>
|
|
window.api.updateGeneratedReportTemplate({
|
|
reportId: reportIdValue,
|
|
templateId: `external:${id}@${version}`,
|
|
templateRef: { id, version },
|
|
generatedImage: exported.imageDataUrl,
|
|
htmlPath: exported.htmlPath,
|
|
pngPath: exported.pngPath
|
|
}),
|
|
{
|
|
reportIdValue: reportId,
|
|
id: paperdaily.id,
|
|
version: paperdaily.version,
|
|
exported: {
|
|
imageDataUrl: switchedExport.imageDataUrl!,
|
|
htmlPath: switchedExport.htmlPath!,
|
|
pngPath: switchedExport.pngPath!
|
|
}
|
|
}
|
|
)
|
|
expect(switched.success, switched.error).toBe(true)
|
|
expect(switched.record?.id).toBe(reportId)
|
|
expect(switched.record?.templateId).toBe(`external:${paperdaily.id}@${paperdaily.version}`)
|
|
expect(switched.record?.reportSnapshot).toEqual(report)
|
|
expect(readFileSync(switched.record!.htmlPath!, 'utf8')).toContain('虚构的结构化日报内容')
|
|
|
|
const history = await page.evaluate(() => window.api.listGeneratedReports())
|
|
expect(history.success).toBe(true)
|
|
expect(history.reports?.find((record) => record.id === reportId)?.templateId).toBe(
|
|
`external:${paperdaily.id}@${paperdaily.version}`
|
|
)
|
|
|
|
const removed = await page.evaluate(
|
|
async ({ id, version }) => window.api.uninstallReportTemplate(id, version),
|
|
{ id: paperdaily.id, version: paperdaily.version }
|
|
)
|
|
expect(removed.success, removed.error).toBe(true)
|
|
const afterUninstall = await page.evaluate(
|
|
async ({ id, version, reportValue, metadataValue }) =>
|
|
window.api.exportGroupReport({
|
|
templateRef: { id, version },
|
|
report: reportValue,
|
|
metadata: metadataValue
|
|
}),
|
|
{ id: paperdaily.id, version: paperdaily.version, reportValue: report, metadataValue: metadata }
|
|
)
|
|
expect(afterUninstall.success).toBe(false)
|
|
expect(afterUninstall.error).toContain('模板不存在')
|
|
|
|
const legacyHtml = join(outputDir, 'legacy-without-structured-data.html')
|
|
writeFileSync(legacyHtml, '<!doctype html><html><body><h1>旧日报</h1></body></html>')
|
|
const legacy = await page.evaluate(
|
|
async ({ htmlPath, pngPath, imageDataUrl }) =>
|
|
window.api.saveGeneratedReport({
|
|
contactId: 'fixture-legacy-group',
|
|
contactName: '虚构旧日报',
|
|
dateRange: '今日',
|
|
messageCount: 1,
|
|
generatedAt: '2026-09-07T11:00:00.000Z',
|
|
htmlPath,
|
|
pngPath,
|
|
generatedImage: imageDataUrl
|
|
}),
|
|
{ htmlPath: legacyHtml, pngPath: firstExport.pngPath, imageDataUrl: firstExport.imageDataUrl }
|
|
)
|
|
expect(legacy.success).toBe(true)
|
|
const legacySwitch = await page.evaluate(
|
|
async ({ reportIdValue, id, version, imageDataUrl, htmlPath }) =>
|
|
window.api.updateGeneratedReportTemplate({
|
|
reportId: reportIdValue,
|
|
templateId: `external:${id}@${version}`,
|
|
templateRef: { id, version },
|
|
generatedImage: imageDataUrl,
|
|
htmlPath,
|
|
pngPath: htmlPath
|
|
}),
|
|
{
|
|
reportIdValue: legacy.record!.id,
|
|
id: quickread.id,
|
|
version: quickread.version,
|
|
imageDataUrl: firstExport.imageDataUrl,
|
|
htmlPath: legacyHtml
|
|
}
|
|
)
|
|
expect(legacySwitch.success).toBe(false)
|
|
expect(legacySwitch.error).toContain('旧报告未保存结构化数据')
|
|
} finally {
|
|
if (app) await app.close()
|
|
rmSync(outputDir, { recursive: true, force: true })
|
|
rmSync(userData, { recursive: true, force: true })
|
|
}
|
|
})
|