feat: 解码 worker 化、Playwright E2E 测试体系与文件夹对比(0.6.0)
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { _electron as electron, type ElectronApplication, type Page } from '@playwright/test'
|
||||
import { mkdtemp, writeFile, rm } from 'fs/promises'
|
||||
import { tmpdir } from 'os'
|
||||
import { join } from 'path'
|
||||
import iconv from 'iconv-lite'
|
||||
|
||||
/**
|
||||
* E2E 辅助:启动构建产物(out/)对应的真实 Electron 应用,
|
||||
* 以及文件对话框 stub 与临时 fixture 生成。
|
||||
* Electron 模式复用项目自带 Electron 二进制,无需下载 Playwright 浏览器。
|
||||
*/
|
||||
|
||||
export interface LaunchedApp {
|
||||
app: ElectronApplication
|
||||
window: Page
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动应用并等待首个窗口就绪。
|
||||
* fresh(默认 true):清除上一轮测试残留的偏好持久化(localStorage 跨实例共享 userData)
|
||||
* 并重载页面,保证以默认选项启动;偏好持久化用例的验证实例显式传 false。
|
||||
*/
|
||||
export async function launchApp(options: { fresh?: boolean } = {}): Promise<LaunchedApp> {
|
||||
const { fresh = true } = options
|
||||
const app = await electron.launch({ args: ['.'] })
|
||||
const window = await app.firstWindow()
|
||||
await window.waitForLoadState('domcontentloaded')
|
||||
if (fresh) {
|
||||
await window.evaluate(() => window.localStorage.clear())
|
||||
await window.reload()
|
||||
await window.waitForLoadState('domcontentloaded')
|
||||
}
|
||||
return { app, window }
|
||||
}
|
||||
|
||||
/**
|
||||
* 主进程 stub:showOpenDialog 按调用序返回指定路径(每次调用取该组第一个文件)。
|
||||
* 闭包计数状态保留在主进程,覆盖「打开左侧 → 打开右侧」的连续调用。
|
||||
*/
|
||||
export async function stubOpenDialogSeq(app: ElectronApplication, seqPaths: string[][]): Promise<void> {
|
||||
await app.evaluate(({ dialog }, seq: string[][]) => {
|
||||
let i = 0
|
||||
dialog.showOpenDialog = async () => {
|
||||
const paths = seq[Math.min(i, seq.length - 1)] ?? []
|
||||
i++
|
||||
return { canceled: false, filePaths: paths }
|
||||
}
|
||||
}, seqPaths)
|
||||
}
|
||||
|
||||
/** 主进程 stub:showSaveDialog 固定返回指定保存路径 */
|
||||
export async function stubSaveDialog(app: ElectronApplication, savePath: string): Promise<void> {
|
||||
await app.evaluate(({ dialog }, p: string) => {
|
||||
dialog.showSaveDialog = async () => ({ canceled: false, filePath: p })
|
||||
}, savePath)
|
||||
}
|
||||
|
||||
/** 创建临时目录(存放测试 fixture / 导出产物) */
|
||||
export async function makeTmpDir(prefix = 'difflens-e2e-'): Promise<string> {
|
||||
return mkdtemp(join(tmpdir(), prefix))
|
||||
}
|
||||
|
||||
/** 清理临时目录(递归、不存在时忽略) */
|
||||
export async function removeTmpDir(dir: string): Promise<void> {
|
||||
await rm(dir, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入文本 fixture:encoding 为 'utf8'(可带 bom)或 'gbk'(iconv-lite 编码)。
|
||||
* 返回文件绝对路径。
|
||||
*/
|
||||
export async function writeTextFixture(
|
||||
dir: string,
|
||||
name: string,
|
||||
content: string,
|
||||
encoding: 'utf8' | 'utf8-bom' | 'gbk' = 'utf8'
|
||||
): Promise<string> {
|
||||
const path = join(dir, name)
|
||||
if (encoding === 'gbk') {
|
||||
await writeFile(path, iconv.encode(content, 'gbk'))
|
||||
} else if (encoding === 'utf8-bom') {
|
||||
await writeFile(path, '\ufeff' + content, 'utf8')
|
||||
} else {
|
||||
await writeFile(path, content, 'utf8')
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
/** 通过「粘贴文本」入口向指定侧粘贴内容(覆盖粘贴弹窗全链路) */
|
||||
export async function pasteText(window: Page, side: 'left' | 'right', text: string): Promise<void> {
|
||||
await window.getByRole('button', { name: '粘贴文本' }).click()
|
||||
await window.getByRole('button', { name: side === 'left' ? '粘贴到左侧' : '粘贴到右侧' }).click()
|
||||
await window.getByPlaceholder(/粘贴或输入/).fill(text)
|
||||
await window.getByRole('button', { name: '开始对比' }).click()
|
||||
}
|
||||
Reference in New Issue
Block a user