test: 起 Playwright(electron) E2E 骨架 + 金标准往返测试(建→写→存→重开字节一致)
This commit is contained in:
@@ -12,3 +12,8 @@ packages/*/src/**/*.js
|
||||
packages/*/src/**/*.d.ts
|
||||
packages/*/src/**/*.js.map
|
||||
*.tsbuildinfo
|
||||
|
||||
# Playwright E2E artifacts
|
||||
test-results/
|
||||
playwright-report/
|
||||
apps/desktop/tests/e2e/.report/
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"scripts": {
|
||||
"dev": "electron-vite dev",
|
||||
"build": "electron-vite build",
|
||||
"typecheck": "tsc -b"
|
||||
"typecheck": "tsc -b",
|
||||
"test:e2e": "electron-vite build && playwright test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@hunter/shared": "workspace:*",
|
||||
@@ -39,11 +40,13 @@
|
||||
"zustand": "^5.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@types/node": "^20.0.0",
|
||||
"@types/react": "^18.3.0",
|
||||
"@types/react-dom": "^18.3.0",
|
||||
"electron": "^30.0.0",
|
||||
"electron-vite": "^2.3.0",
|
||||
"playwright": "^1.62.1",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"typescript": "^5.6.0",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from '@playwright/test';
|
||||
|
||||
// Electron E2E:测试在 tests/e2e 内用 _electron.launch 启动 built main(dist-electron/main/index.js)
|
||||
export default defineConfig({
|
||||
testDir: './tests/e2e',
|
||||
timeout: 60_000,
|
||||
expect: { timeout: 15_000 },
|
||||
fullyParallel: false,
|
||||
workers: 1,
|
||||
retries: 0,
|
||||
reportDir: './tests/e2e/.report',
|
||||
use: { trace: 'retain-on-failure' },
|
||||
});
|
||||
@@ -0,0 +1,76 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { _electron as electron } from 'playwright';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import nodePath from 'node:path';
|
||||
import os from 'node:os';
|
||||
|
||||
const MAIN_JS = nodePath.join(process.cwd(), 'dist-electron/main/index.js');
|
||||
const VAULT_SWITCH = 'vault:switch';
|
||||
const FILE_CREATE = 'file:create';
|
||||
const DOC_WRITE = 'doc:write';
|
||||
const DOC_READ = 'doc:read';
|
||||
|
||||
// 含嵌套强调、转义、4/3 反引号围栏、GFM 表格对齐、任务列表、数学公式
|
||||
const KNOWN_MD = [
|
||||
'# 金标准往返语料',
|
||||
'',
|
||||
'嵌套强调:**外层 *内层* 收尾** 与 ***粗斜***。',
|
||||
'',
|
||||
'转义:写出 \\*不是斜体\\*;代码内 `*不生效*`。',
|
||||
'',
|
||||
'4 反引号围栏包裹 3 反引号:',
|
||||
'',
|
||||
'````md',
|
||||
'```js',
|
||||
'console.log("hi")',
|
||||
'```',
|
||||
'````',
|
||||
'',
|
||||
'| 左 | 中 | 右 |',
|
||||
'|:--|:-:|--:|',
|
||||
'| a | b | c |',
|
||||
'',
|
||||
'- [x] 已完成',
|
||||
'- [ ] 未完成',
|
||||
'',
|
||||
'行内 $a^2+b^2=c^2$ 与',
|
||||
'',
|
||||
'$$E=mc^2$$',
|
||||
].join('\n');
|
||||
|
||||
async function launch(userData: string) {
|
||||
const app = await electron.launch({ args: ['--user-data-dir=' + userData, MAIN_JS] });
|
||||
const page = await app.firstWindow();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
const invoke = (ch: string, args: any) =>
|
||||
page.evaluate(({ ch, args }) => (globalThis as any).dsh.invoke(ch, args), { ch, args });
|
||||
return { app, page, invoke };
|
||||
}
|
||||
|
||||
test('金标准:建→写→存→重开 字节一致', async () => {
|
||||
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-rt-'));
|
||||
const userData = nodePath.join(tmp, 'userdata');
|
||||
const vault = nodePath.join(tmp, 'vault');
|
||||
await fs.mkdir(userData, { recursive: true });
|
||||
await fs.mkdir(vault, { recursive: true });
|
||||
|
||||
// 建 + 写
|
||||
const a1 = await launch(userData);
|
||||
const ud: string = await a1.app.evaluate(({ app }) => app.getPath('userData'));
|
||||
expect(ud.startsWith(tmp)).toBeTruthy(); // 确认 --user-data-dir 生效,不污染真实数据
|
||||
await a1.invoke(VAULT_SWITCH, { root: vault });
|
||||
const created: { path: string } = await a1.invoke(FILE_CREATE, { basePath: '', name: 'roundtrip', isFolder: false });
|
||||
expect(created.path).toBe('roundtrip.md');
|
||||
await a1.invoke(DOC_WRITE, { path: 'roundtrip.md', meta: { title: '金标准往返' }, body: KNOWN_MD });
|
||||
const W1 = await fs.readFile(nodePath.join(vault, 'roundtrip.md'), 'utf8');
|
||||
await a1.app.close();
|
||||
|
||||
// 重开
|
||||
const a2 = await launch(userData);
|
||||
const read: { meta: any; body: string } = await a2.invoke(DOC_READ, { path: 'roundtrip.md' });
|
||||
const W2 = await fs.readFile(nodePath.join(vault, 'roundtrip.md'), 'utf8');
|
||||
await a2.app.close();
|
||||
|
||||
expect(read.body).toBe(KNOWN_MD);
|
||||
expect(W2).toBe(W1);
|
||||
});
|
||||
@@ -0,0 +1,37 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { _electron as electron } from 'playwright';
|
||||
import { promises as fs } from 'node:fs';
|
||||
import nodePath from 'node:path';
|
||||
import os from 'node:os';
|
||||
|
||||
const MAIN_JS = nodePath.join(process.cwd(), 'dist-electron/main/index.js');
|
||||
|
||||
async function launchApp() {
|
||||
const tmp = await fs.mkdtemp(nodePath.join(os.tmpdir(), 'hn-e2e-'));
|
||||
const userData = nodePath.join(tmp, 'userdata');
|
||||
const vault = nodePath.join(tmp, 'vault');
|
||||
await fs.mkdir(userData, { recursive: true });
|
||||
await fs.mkdir(vault, { recursive: true });
|
||||
// 预置 config,让 app 直接用临时库(FR-V 隔离)
|
||||
await fs.writeFile(
|
||||
nodePath.join(userData, 'config.json'),
|
||||
JSON.stringify({ vault: { currentRoot: vault, recents: [vault], defaultRoot: vault } }),
|
||||
'utf8',
|
||||
);
|
||||
const app = await electron.launch({ args: ['--user-data-dir=' + userData, MAIN_JS] });
|
||||
return { app, tmp, vault };
|
||||
}
|
||||
|
||||
test('app launches and renders title bar', async () => {
|
||||
const { app, tmp } = await launchApp();
|
||||
try {
|
||||
const page = await app.firstWindow();
|
||||
await page.waitForLoadState('domcontentloaded');
|
||||
await expect(page.getByText('狩夜人笔记').first()).toBeVisible();
|
||||
const shot = nodePath.join(tmp, 'smoke.png');
|
||||
await page.screenshot({ path: shot });
|
||||
console.log('SHOT=' + shot);
|
||||
} finally {
|
||||
await app.close();
|
||||
}
|
||||
});
|
||||
Generated
+41
@@ -117,6 +117,9 @@ importers:
|
||||
specifier: ^5.0.15
|
||||
version: 5.0.15(@types/react@18.3.31)(react@18.3.1)(use-sync-external-store@1.6.0(react@18.3.1))
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.62.1
|
||||
version: 1.62.1
|
||||
'@types/node':
|
||||
specifier: ^20.0.0
|
||||
version: 20.19.43
|
||||
@@ -132,6 +135,9 @@ importers:
|
||||
electron-vite:
|
||||
specifier: ^2.3.0
|
||||
version: 2.3.0(vite@5.4.21(@types/node@20.19.43))
|
||||
playwright:
|
||||
specifier: ^1.62.1
|
||||
version: 1.62.1
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@@ -467,6 +473,11 @@ packages:
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@playwright/test@1.62.1':
|
||||
resolution: {integrity: sha512-DTcUc8qii+cpHvtOwggMtBRMjKZHXYWdw8syRYu2vtzuq4Wxphqq4NfCs5Zt44L6mA8rfDfj+PHnxFc/FeK6mQ==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.62.5':
|
||||
resolution: {integrity: sha512-jfkGfTwhQpsiSckPF8r9bU3pn3vyd72NlWaO+TgEO6WPSDnUhXzrNYCHBMOYj0ACaUgjm6eERLF+XV9a6RstoA==}
|
||||
cpu: [arm]
|
||||
@@ -1275,6 +1286,11 @@ packages:
|
||||
resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
|
||||
engines: {node: '>=6 <7 || >=8'}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
@@ -1622,6 +1638,16 @@ packages:
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
playwright-core@1.62.1:
|
||||
resolution: {integrity: sha512-wPYSwEBJY9GHraISXqyqtx0na0LpO3XEX7jNDhntbex7tzUS7kLnZsOlFruFJB4Hi/rhDMjXGqHewDZ68nYZVw==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.62.1:
|
||||
resolution: {integrity: sha512-0M+L3LAD8/nm554LOla9Ayx0j0tmFZ0FBcoQ7F1VuVHpM/XpiC8RcDzBQB8W5+hA8L22THxELzeF+2WcUzvcLg==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
postcss@8.5.26:
|
||||
resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
@@ -2286,6 +2312,10 @@ snapshots:
|
||||
'@napi-rs/lzma-linux-x64-gnu@1.5.1':
|
||||
optional: true
|
||||
|
||||
'@playwright/test@1.62.1':
|
||||
dependencies:
|
||||
playwright: 1.62.1
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.62.5':
|
||||
optional: true
|
||||
|
||||
@@ -3090,6 +3120,9 @@ snapshots:
|
||||
jsonfile: 4.0.0
|
||||
universalify: 0.1.2
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
@@ -3633,6 +3666,14 @@ snapshots:
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
playwright-core@1.62.1: {}
|
||||
|
||||
playwright@1.62.1:
|
||||
dependencies:
|
||||
playwright-core: 1.62.1
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
|
||||
postcss@8.5.26:
|
||||
dependencies:
|
||||
nanoid: 3.3.18
|
||||
|
||||
Reference in New Issue
Block a user