test(perf): sample both J2 settle captures in one main-process snapshot

The settle sample read J1's capture (pending calls) and J2's capture
(call count) in two evaluate calls, so a call starting in between was
counted with a stale zero in flight and its completion went unseen. Both
states are now read in one synchronous pass, where no ipcMain event can
be handled in between.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-09-29 17:58:20 +02:00
1 parent 5ca9c0b4c7
commit 6f0477c3f5
3 files changed
+71 -19

No files matched your search

@@ -7,7 +7,7 @@ import {
installJourneyMainIpcCapture,
JOURNEY_MAIN_IPC_STATE_KEY,
JOURNEY_RENDERER_API_TRACE_CHANNEL,
peekJourneyMainIpcCapture,
peekJourneyMainIpcCaptures,
readJourneyMainIpcCapture,
} from '../performance/journey-main-ipc-capture';
import type { JourneyMockRequestLedger } from '../performance/journey-mock-request-ledger';
@@ -90,10 +90,13 @@ async function waitForQuiet(
}> {
const armMark = ledger.mark();
const sample = async (): Promise<ActivitySample> => {
const launchCapture = await peekJourneyMainIpcCapture(
electronApp,
JOURNEY_MAIN_IPC_STATE_KEY
);
// Both captures in one snapshot: a call counted by J2's capture is
// then also pending in J1's, never counted with a stale in-flight 0.
const [launchCapture, openSourceCapture] =
await peekJourneyMainIpcCaptures(electronApp, [
JOURNEY_MAIN_IPC_STATE_KEY,
OPEN_SOURCE_JOURNEY_MAIN_IPC_STATE_KEY,
]);
if (launchCapture.unmatchedCompletions > 0) {
throw new Error('open-source-journey-bridge-completions-unmatched');
}
@@ -101,12 +104,7 @@ async function waitForQuiet(
domMutations: await readPreStartMutations(page, probeStateKey),
httpInFlight: ledger.inFlight(),
httpRequests: ledger.mark(),
ipcCalls: (
await peekJourneyMainIpcCapture(
electronApp,
OPEN_SOURCE_JOURNEY_MAIN_IPC_STATE_KEY
)
).callsBeforeStart,
ipcCalls: openSourceCapture.callsBeforeStart,
ipcInFlight: countJourneyMainIpcInFlight(launchCapture),
};
};
@@ -13,6 +13,7 @@ import {
installJourneyMainIpcCapture,
JOURNEY_RENDERER_API_TRACE_CHANNEL,
peekJourneyMainIpcCapture,
peekJourneyMainIpcCaptures,
type JourneyMainIpcCaptureOptions,
type JourneyMainIpcCaptureState,
} from './journey-main-ipc-capture';
@@ -372,3 +373,39 @@ test('a detached capture stops listening and keeps its last state', async () =>
);
});
});
test('reads several captures in one main-process pass', async () => {
const fake = createFakeElectronApp();
let evaluations = 0;
const counting = {
evaluate: (...args: Parameters<ElectronApplication['evaluate']>) => {
evaluations += 1;
return fake.app.evaluate(...args);
},
} as unknown as ElectronApplication;
const target = globalThis as unknown as Record<string, unknown>;
const [first, second] = ['__journeyPeekA', '__journeyPeekB'];
target[first] = { callsBeforeStart: 1 };
target[second] = { callsBeforeStart: 2 };
try {
const states = await peekJourneyMainIpcCaptures(counting, [
first,
second,
]);
assert.equal(evaluations, 1);
assert.deepEqual(
states.map((state) => state.callsBeforeStart),
[1, 2]
);
await assert.rejects(
peekJourneyMainIpcCaptures(counting, [
first,
'__journeyPeekMissing',
]),
/capture-missing/
);
} finally {
delete target[first];
delete target[second];
}
});
@@ -213,20 +213,37 @@ export async function peekJourneyMainIpcCapture(
electronApp: ElectronApplication,
stateKey: string
): Promise<JourneyMainIpcCaptureState> {
const state = (await electronApp.evaluate(
(_electron, key) =>
const [state] = await peekJourneyMainIpcCaptures(electronApp, [stateKey]);
return state as JourneyMainIpcCaptureState;
}
/**
* Several captures read in one synchronous pass in the main process. No
* `ipcMain` event can be handled in between, so the states are one coherent
* snapshot: a call counted by one capture is also pending in the other.
*/
export async function peekJourneyMainIpcCaptures(
electronApp: ElectronApplication,
stateKeys: readonly string[]
): Promise<JourneyMainIpcCaptureState[]> {
const states = (await electronApp.evaluate(
(_electron, keys) =>
JSON.parse(
JSON.stringify(
(globalThis as unknown as Record<string, unknown>)[key] ??
null
keys.map(
(key) =>
(globalThis as unknown as Record<string, unknown>)[
key
] ?? null
)
)
) as unknown,
stateKey
)) as JourneyMainIpcCaptureState | null;
if (!state) {
[...stateKeys]
)) as (JourneyMainIpcCaptureState | null)[];
if (states.some((state) => !state)) {
throw new Error('journey-main-ipc-capture-missing');
}
return state;
return states as JourneyMainIpcCaptureState[];
}
export async function readJourneyMainIpcCapture(