Skip to content

Commit 1411b1a

Browse files
authored
Merge pull request #95 from markwylde/codex/spec-local-diagnostics
Add local desktop diagnostics
2 parents 6f31b53 + e5407c6 commit 1411b1a

23 files changed

Lines changed: 4760 additions & 29 deletions

e2e/local-desktop-diagnostics.spec.ts

Lines changed: 574 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { readdir, readFile } from 'node:fs/promises';
2+
import path from 'node:path';
3+
import {
4+
type ElectronApplication,
5+
_electron as electron,
6+
} from '@playwright/test';
7+
8+
export type DiagnosticEvent = {
9+
component: string;
10+
event: string;
11+
fields?: Record<string, unknown>;
12+
launchId: string;
13+
message?: string;
14+
schemaVersion: number;
15+
severity: string;
16+
source?: string;
17+
stack?: string;
18+
timestamp: string;
19+
};
20+
21+
export function diagnosticsDirectory(userDataDirectory: string): string {
22+
return path.join(userDataDirectory, 'logs');
23+
}
24+
25+
/**
26+
* Launch the built Desktop entry point without Vite's development server. This
27+
* exercises the same file:// renderer path as a packaged build while keeping
28+
* each test's user data and diagnostics isolated.
29+
*/
30+
export async function launchPackagedStyleDesktop(options: {
31+
tempDirectory: string;
32+
userDataDirectory: string;
33+
}): Promise<ElectronApplication> {
34+
return electron.launch({
35+
args: ['.'],
36+
env: {
37+
...process.env,
38+
CI: '1',
39+
TEMP: options.tempDirectory,
40+
TERMINAY_E2E_TEMP_DIR: options.tempDirectory,
41+
TERMINAY_TEST: '1',
42+
TERMINAY_USER_DATA_DIR: options.userDataDirectory,
43+
TMP: options.tempDirectory,
44+
TMPDIR: options.tempDirectory,
45+
// An empty value is deliberately falsy in main.ts and prevents a host
46+
// environment from accidentally changing this into a development launch.
47+
VITE_DEV_SERVER_URL: '',
48+
},
49+
});
50+
}
51+
52+
export async function readDiagnosticText(
53+
userDataDirectory: string,
54+
): Promise<string> {
55+
const directory = diagnosticsDirectory(userDataDirectory);
56+
const names = await readdir(directory).catch(() => []);
57+
const segments = names.filter((name) => name.endsWith('.jsonl')).sort();
58+
return (
59+
await Promise.all(
60+
segments.map((name) =>
61+
readFile(path.join(directory, name), 'utf8').catch(() => ''),
62+
),
63+
)
64+
).join('');
65+
}
66+
67+
export async function readDiagnosticEvents(
68+
userDataDirectory: string,
69+
): Promise<DiagnosticEvent[]> {
70+
const text = await readDiagnosticText(userDataDirectory);
71+
const events: DiagnosticEvent[] = [];
72+
73+
for (const line of text.split('\n')) {
74+
if (line.length === 0) continue;
75+
try {
76+
events.push(JSON.parse(line) as DiagnosticEvent);
77+
} catch {
78+
// The writer promises complete atomic lines. Tolerate a concurrent final
79+
// line here so polling a live process does not itself make a test flaky;
80+
// post-exit assertions still parse every persisted line below.
81+
}
82+
}
83+
return events;
84+
}
85+
86+
export async function readStrictDiagnosticEvents(
87+
userDataDirectory: string,
88+
): Promise<DiagnosticEvent[]> {
89+
const text = await readDiagnosticText(userDataDirectory);
90+
return text
91+
.split('\n')
92+
.filter((line) => line.length > 0)
93+
.map((line) => JSON.parse(line) as DiagnosticEvent);
94+
}
95+
96+
export async function closeDesktop(app: ElectronApplication): Promise<void> {
97+
if (app.process().exitCode !== null) return;
98+
const closed = app.close();
99+
let timer: ReturnType<typeof setTimeout> | undefined;
100+
try {
101+
await Promise.race([
102+
closed,
103+
new Promise<never>((_resolve, reject) => {
104+
timer = setTimeout(
105+
() =>
106+
reject(
107+
new Error(
108+
'Electron did not finish graceful shutdown in 20 seconds.',
109+
),
110+
),
111+
20_000,
112+
);
113+
timer.unref?.();
114+
}),
115+
]);
116+
} finally {
117+
if (timer !== undefined) clearTimeout(timer);
118+
}
119+
}

0 commit comments

Comments
 (0)