refactor(electron): retain complete active observations
This commit is contained in:
1 parent
fa2b6c5a61
commit
e8c56e0c0a
8 files changed
+60
-161
No files matched your search
@@ -4,21 +4,10 @@ const {
|
||||
createElectronActiveBoundaryPatches,
|
||||
} = require("./electron-active-hook-boundaries.cjs");
|
||||
|
||||
const boundedEnvironmentNumber = (name, fallback) => {
|
||||
const value = Number(process.env[name]);
|
||||
return Number.isInteger(value)
|
||||
? Math.min(20_000, Math.max(1, value))
|
||||
: fallback;
|
||||
};
|
||||
|
||||
const MAX_EVENT_NAME_LENGTH = 128;
|
||||
const MAX_CHANNEL_LENGTH = 1_024;
|
||||
const MAX_IDENTIFIER_LENGTH = 256;
|
||||
const MAX_ARGUMENT_SHAPES = 32;
|
||||
const maxEvents = boundedEnvironmentNumber(
|
||||
"REA_ELECTRON_ACTIVE_MAX_RUNTIME_EVENTS",
|
||||
5_000,
|
||||
);
|
||||
const ipcEventKinds = new Set([
|
||||
"main-handler-invocation",
|
||||
"main-event-invocation",
|
||||
@@ -34,7 +23,6 @@ const events = [];
|
||||
let observed = 0;
|
||||
let observedIpc = 0;
|
||||
let observedRuntime = 0;
|
||||
let truncated = false;
|
||||
let sequence = 0;
|
||||
let correlationSequence = 0;
|
||||
|
||||
@@ -104,10 +92,6 @@ const record = (event) => {
|
||||
observed += 1;
|
||||
if (ipcEventKinds.has(event.kind)) observedIpc += 1;
|
||||
else observedRuntime += 1;
|
||||
if (events.length >= maxEvents) {
|
||||
truncated = true;
|
||||
return null;
|
||||
}
|
||||
const raw = {
|
||||
sequence: ++sequence,
|
||||
correlation_id:
|
||||
@@ -496,6 +480,5 @@ globalThis.__reaElectronActiveSnapshot = () => ({
|
||||
observed,
|
||||
observed_ipc: observedIpc,
|
||||
observed_runtime: observedRuntime,
|
||||
truncated,
|
||||
hook_error: globalThis.__reaElectronActiveHookError === true,
|
||||
});
|
||||
@@ -38,7 +38,6 @@ const scenarioProjection = (
|
||||
application_root: input.application_root,
|
||||
args: redactArguments(input.args),
|
||||
actions: input.actions.map(({ step_id, kind }) => ({ step_id, kind })),
|
||||
limits: input.limits,
|
||||
});
|
||||
|
||||
const parameters = (
|
||||
|
||||
@@ -10,6 +10,7 @@ import type {
|
||||
} from "../domain/electronActiveObservation.js";
|
||||
|
||||
const OPERATION = "capture_electron_scenario" as const;
|
||||
const ACTION_TIMEOUT_MS = 5_000;
|
||||
|
||||
const hookEventSchema = z.strictObject({
|
||||
sequence: z.number().int().min(1),
|
||||
@@ -70,11 +71,10 @@ const hookEventSchema = z.strictObject({
|
||||
});
|
||||
|
||||
const hookSnapshotSchema = z.strictObject({
|
||||
events: z.array(hookEventSchema).max(20_000),
|
||||
events: z.array(hookEventSchema),
|
||||
observed: z.number().int().min(0),
|
||||
observed_ipc: z.number().int().min(0),
|
||||
observed_runtime: z.number().int().min(0),
|
||||
truncated: z.boolean(),
|
||||
hook_error: z.boolean(),
|
||||
});
|
||||
|
||||
@@ -105,7 +105,7 @@ type ElectronPageSnapshot = {
|
||||
readonly title: string;
|
||||
};
|
||||
|
||||
/** Run bounded, explicit actions against provider-owned Electron windows. */
|
||||
/** Run explicit actions against provider-owned Electron windows. */
|
||||
export const runElectronActions = async (
|
||||
application: ElectronApplication,
|
||||
input: ElectronActiveObservationInput,
|
||||
@@ -129,7 +129,7 @@ export const runElectronActions = async (
|
||||
: await waitForWindow({
|
||||
application,
|
||||
windowIndex,
|
||||
timeout: input.limits.action_timeout_ms,
|
||||
timeout: ACTION_TIMEOUT_MS,
|
||||
signal: options.signal,
|
||||
deadline,
|
||||
});
|
||||
@@ -146,7 +146,7 @@ export const runElectronActions = async (
|
||||
action,
|
||||
selectedWindow,
|
||||
remaining,
|
||||
actionTimeoutMs: input.limits.action_timeout_ms,
|
||||
actionTimeoutMs: ACTION_TIMEOUT_MS,
|
||||
options,
|
||||
deadline,
|
||||
});
|
||||
@@ -319,11 +319,10 @@ const emitDeepLink = (
|
||||
{ delivery: action.delivery, url: action.url },
|
||||
);
|
||||
|
||||
/** Read the current Electron windows, metrics, and bounded hook snapshot. */
|
||||
/** Read the current Electron windows, metrics, and hook snapshot. */
|
||||
// oxlint-disable-next-line max-lines-per-function -- one state read must share one capture boundary.
|
||||
export const readApplicationState = async (
|
||||
application: ElectronApplication,
|
||||
maxWindows: number,
|
||||
): Promise<{
|
||||
readonly windows: ReadonlyArray<{
|
||||
readonly window_id: string;
|
||||
@@ -333,14 +332,13 @@ export const readApplicationState = async (
|
||||
readonly visible: boolean | null;
|
||||
readonly destroyed: boolean;
|
||||
}>;
|
||||
readonly windowsTruncated: boolean;
|
||||
readonly metrics: ElectronMetrics;
|
||||
readonly electronVersion: string;
|
||||
readonly hookSnapshot: ElectronHookSnapshot;
|
||||
}> => {
|
||||
const pages = application.windows();
|
||||
const pageSnapshotsPromise: Promise<ElectronPageSnapshot[]> = Promise.all(
|
||||
pages.slice(0, maxWindows).map(
|
||||
pages.map(
|
||||
async (page: Page, index): Promise<ElectronPageSnapshot> => ({
|
||||
index,
|
||||
url: page.url().slice(0, 65_536),
|
||||
@@ -397,7 +395,6 @@ export const readApplicationState = async (
|
||||
observed: 0,
|
||||
observed_ipc: 0,
|
||||
observed_runtime: 0,
|
||||
truncated: false,
|
||||
hook_error: true,
|
||||
};
|
||||
})
|
||||
@@ -429,7 +426,6 @@ export const readApplicationState = async (
|
||||
destroyed: metadata.destroyed,
|
||||
};
|
||||
}),
|
||||
windowsTruncated: pages.length > maxWindows,
|
||||
metrics: processMetrics,
|
||||
electronVersion,
|
||||
hookSnapshot,
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
} from "./PlaywrightElectronActiveActions.js";
|
||||
|
||||
const OPERATION = "capture_electron_scenario" as const;
|
||||
const RUN_TIMEOUT_MS = 60_000;
|
||||
|
||||
/** Public identity for provider-owned Electron runtime experiments. */
|
||||
export const PLAYWRIGHT_ELECTRON_ACTIVE_PROVIDER_IDENTITY: ProviderIdentity =
|
||||
@@ -136,11 +137,7 @@ const createCoverage = (hookSnapshot: ElectronHookSnapshot) => {
|
||||
unavailableEventFamilies.add("renderer-ipc");
|
||||
}
|
||||
return {
|
||||
status: hookSnapshot.hook_error
|
||||
? "hook_conflict"
|
||||
: hookSnapshot.truncated
|
||||
? "capture_truncated"
|
||||
: "partial_attach",
|
||||
status: hookSnapshot.hook_error ? "hook_conflict" : "partial_attach",
|
||||
observed_event_families: observedEventFamilies,
|
||||
unavailable_event_families: [...unavailableEventFamilies].sort(),
|
||||
observed_roles: observedRoles,
|
||||
@@ -168,16 +165,12 @@ export class PlaywrightElectronActiveProvider
|
||||
try {
|
||||
if (options.signal?.aborted === true)
|
||||
throw new BrowserObservationError(OPERATION, "cancelled");
|
||||
const deadline = Date.now() + input.limits.max_duration_ms;
|
||||
const deadline = Date.now() + RUN_TIMEOUT_MS;
|
||||
const paths = await canonicalPaths(input);
|
||||
application = await electron.launch({
|
||||
executablePath: paths.executable,
|
||||
cwd: paths.root,
|
||||
env: safeElectronEnvironment(
|
||||
input.limits.max_ipc_events,
|
||||
input.limits.max_runtime_events,
|
||||
runId,
|
||||
),
|
||||
env: safeElectronEnvironment(runId),
|
||||
args: ["-r", hookPath, paths.application, ...input.args],
|
||||
timeout: Math.max(1, deadline - Date.now()),
|
||||
});
|
||||
@@ -206,7 +199,7 @@ export class PlaywrightElectronActiveProvider
|
||||
deadline,
|
||||
);
|
||||
const state = await runWithExecutionLimits(
|
||||
readApplicationState(application, input.limits.max_windows),
|
||||
readApplicationState(application),
|
||||
options.signal,
|
||||
deadline,
|
||||
);
|
||||
@@ -316,7 +309,6 @@ const createResult = (
|
||||
readonly visible: boolean | null;
|
||||
readonly destroyed: boolean;
|
||||
}>;
|
||||
readonly windowsTruncated: boolean;
|
||||
readonly metrics: ElectronMetrics;
|
||||
readonly electronVersion: string;
|
||||
readonly hookSnapshot: ElectronHookSnapshot;
|
||||
@@ -335,42 +327,26 @@ const createResult = (
|
||||
cleanup: "terminated-owned-process",
|
||||
},
|
||||
actions,
|
||||
windows: state.windows.slice(0, input.limits.max_windows),
|
||||
windows_truncated: state.windowsTruncated,
|
||||
windows: state.windows,
|
||||
processes: {
|
||||
items: state.metrics.slice(0, input.limits.max_processes),
|
||||
truncated: state.metrics.length > input.limits.max_processes,
|
||||
items: state.metrics,
|
||||
},
|
||||
ipc: {
|
||||
events: ipcEvents.slice(0, input.limits.max_ipc_events),
|
||||
events: ipcEvents,
|
||||
observed: hookSnapshot.observed_ipc,
|
||||
retained: Math.min(ipcEvents.length, input.limits.max_ipc_events),
|
||||
truncated:
|
||||
hookSnapshot.truncated ||
|
||||
ipcEvents.length > input.limits.max_ipc_events,
|
||||
},
|
||||
timeline: {
|
||||
events: hookSnapshot.events.slice(0, input.limits.max_runtime_events),
|
||||
events: hookSnapshot.events,
|
||||
observed: hookSnapshot.observed,
|
||||
retained: Math.min(
|
||||
hookSnapshot.events.length,
|
||||
input.limits.max_runtime_events,
|
||||
),
|
||||
truncated:
|
||||
hookSnapshot.truncated ||
|
||||
hookSnapshot.events.length > input.limits.max_runtime_events,
|
||||
},
|
||||
coverage: createCoverage(hookSnapshot),
|
||||
limitations: [
|
||||
"IPC payloads are represented only by bounded value shapes; values are never retained, channels are capped at 1,024 characters, and argument-shape arrays at 32 entries.",
|
||||
"IPC direction and sender/receiver identifiers are observed only where Electron exposes them at the hooked boundary.",
|
||||
"The runtime timeline records bounded lifecycle, navigation, shell, permission, popup, download, protocol, preload, native-addon, process, and IPC events; activity before hook installation is unavailable.",
|
||||
"The runtime timeline records lifecycle, navigation, shell, permission, popup, download, protocol, preload, native-addon, process, and IPC events; activity before hook installation is unavailable.",
|
||||
"The preload and renderer process contexts are not instrumented by the main-process -r hook; preload configuration and contextBridge API-shape events are main-boundary observations, not proof of renderer-side execution.",
|
||||
"Process metrics are an Electron API snapshot and do not prove hostile-local-user isolation.",
|
||||
"External shell opens, external navigation, permission grants, downloads, popup windows, updater relaunches, and OS integration are blocked and recorded by the active hook; other application filesystem and network behavior is not sandboxed by this provider.",
|
||||
...(state.windowsTruncated
|
||||
? ["Windows beyond max_windows were not retained."]
|
||||
: []),
|
||||
...(hookSnapshot.hook_error
|
||||
? ["The active IPC hook could not be installed."]
|
||||
: []),
|
||||
@@ -391,11 +367,7 @@ const canonicalPaths = async (
|
||||
return { executable, application, root };
|
||||
};
|
||||
|
||||
const safeElectronEnvironment = (
|
||||
maxIpcEvents: number,
|
||||
maxRuntimeEvents: number,
|
||||
runId: string,
|
||||
): Record<string, string> =>
|
||||
const safeElectronEnvironment = (runId: string): Record<string, string> =>
|
||||
Object.fromEntries([
|
||||
...[
|
||||
"HOME",
|
||||
@@ -411,11 +383,6 @@ const safeElectronEnvironment = (
|
||||
.filter(
|
||||
(entry): entry is readonly [string, string] => entry[1] !== undefined,
|
||||
),
|
||||
["REA_ELECTRON_ACTIVE_MAX_IPC_EVENTS", String(maxIpcEvents)] as const,
|
||||
[
|
||||
"REA_ELECTRON_ACTIVE_MAX_RUNTIME_EVENTS",
|
||||
String(maxRuntimeEvents),
|
||||
] as const,
|
||||
["REA_PROCESS_RUN_ID", runId] as const,
|
||||
]);
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { electronActiveObservationResultSchema } from "./electronActiveObservation.js";
|
||||
|
||||
/** Create a bounded active-capture result for MCP contract tests. */
|
||||
/** Create an active-capture result for MCP contract tests. */
|
||||
export const createElectronActiveObservationFixtureResult = (
|
||||
applicationPath: string,
|
||||
) =>
|
||||
@@ -33,10 +33,8 @@ export const createElectronActiveObservationFixtureResult = (
|
||||
destroyed: false,
|
||||
},
|
||||
],
|
||||
windows_truncated: false,
|
||||
processes: {
|
||||
items: [{ pid: 1234, type: "Browser", name: null, service_name: null }],
|
||||
truncated: false,
|
||||
},
|
||||
ipc: {
|
||||
events: [
|
||||
@@ -51,8 +49,6 @@ export const createElectronActiveObservationFixtureResult = (
|
||||
},
|
||||
],
|
||||
observed: 1,
|
||||
retained: 1,
|
||||
truncated: false,
|
||||
},
|
||||
limitations: [
|
||||
"IPC payloads are represented only by bounded value shapes; values are never retained.",
|
||||
|
||||
@@ -8,7 +8,7 @@ const absolutePathSchema = z
|
||||
.max(16_384)
|
||||
.refine(isAbsolute, "path must be absolute");
|
||||
|
||||
const windowIndexSchema = z.number().int().min(0).max(99);
|
||||
const windowIndexSchema = z.number().int().min(0);
|
||||
|
||||
const deepLinkUrlSchema = z
|
||||
.string()
|
||||
@@ -32,7 +32,7 @@ const actionSchema = z.discriminatedUnion("kind", [
|
||||
z.strictObject({
|
||||
step_id: z.string().min(1).max(128),
|
||||
kind: z.literal("wait"),
|
||||
duration_ms: z.number().int().min(0).max(10_000),
|
||||
duration_ms: z.number().int().min(0),
|
||||
window_index: windowIndexSchema.optional(),
|
||||
}),
|
||||
z.strictObject({
|
||||
@@ -53,42 +53,14 @@ const actionSchema = z.discriminatedUnion("kind", [
|
||||
}),
|
||||
]);
|
||||
|
||||
const limitsSchema = z.strictObject({
|
||||
max_duration_ms: z.number().int().min(1).max(120_000).default(60_000),
|
||||
action_timeout_ms: z.number().int().min(1).max(30_000).default(5_000),
|
||||
max_actions: z.number().int().min(1).max(100).default(20),
|
||||
max_ipc_events: z.number().int().min(1).max(10_000).default(2_000),
|
||||
max_runtime_events: z.number().int().min(1).max(20_000).default(5_000),
|
||||
max_processes: z.number().int().min(1).max(100).default(32),
|
||||
max_windows: z.number().int().min(1).max(100).default(32),
|
||||
});
|
||||
|
||||
/** Input for one explicit, provider-owned Electron runtime experiment. */
|
||||
export const electronActiveObservationInputSchema = z
|
||||
.strictObject({
|
||||
executable_path: absolutePathSchema,
|
||||
application_path: absolutePathSchema,
|
||||
application_root: absolutePathSchema,
|
||||
args: z.array(z.string().max(4_096)).max(32).default([]),
|
||||
actions: z.array(actionSchema).max(100).default([]),
|
||||
limits: limitsSchema.default({
|
||||
max_duration_ms: 60_000,
|
||||
action_timeout_ms: 5_000,
|
||||
max_actions: 20,
|
||||
max_ipc_events: 2_000,
|
||||
max_runtime_events: 5_000,
|
||||
max_processes: 32,
|
||||
max_windows: 32,
|
||||
}),
|
||||
})
|
||||
.superRefine((input, context) => {
|
||||
if (input.actions.length > input.limits.max_actions)
|
||||
context.addIssue({
|
||||
code: "custom",
|
||||
path: ["actions"],
|
||||
message: "actions exceed the configured max_actions limit",
|
||||
});
|
||||
});
|
||||
export const electronActiveObservationInputSchema = z.strictObject({
|
||||
executable_path: absolutePathSchema,
|
||||
application_path: absolutePathSchema,
|
||||
application_root: absolutePathSchema,
|
||||
args: z.array(z.string().max(4_096)).max(32).default([]),
|
||||
actions: z.array(actionSchema).default([]),
|
||||
});
|
||||
export type ElectronActiveObservationInput = z.infer<
|
||||
typeof electronActiveObservationInputSchema
|
||||
>;
|
||||
@@ -222,7 +194,6 @@ const coverageSchema = z.strictObject({
|
||||
status: z.enum([
|
||||
"complete",
|
||||
"partial_attach",
|
||||
"capture_truncated",
|
||||
"hook_conflict",
|
||||
"target_exited",
|
||||
"cleanup_failed",
|
||||
@@ -260,7 +231,7 @@ const actionResultSchema = z.discriminatedUnion("status", [
|
||||
}),
|
||||
]);
|
||||
|
||||
/** Bounded result of a provider-owned Electron runtime experiment. */
|
||||
/** Result of a provider-owned Electron runtime experiment. */
|
||||
export const electronActiveObservationResultSchema = z.strictObject({
|
||||
application: z.strictObject({
|
||||
executable_path: absolutePathSchema,
|
||||
@@ -269,27 +240,21 @@ export const electronActiveObservationResultSchema = z.strictObject({
|
||||
process_ownership: z.literal("provider-owned"),
|
||||
cleanup: z.literal("terminated-owned-process"),
|
||||
}),
|
||||
actions: z.array(actionResultSchema).max(100),
|
||||
windows: z.array(windowResultSchema).max(100),
|
||||
windows_truncated: z.boolean(),
|
||||
actions: z.array(actionResultSchema),
|
||||
windows: z.array(windowResultSchema),
|
||||
processes: z.strictObject({
|
||||
items: z.array(processMetricSchema).max(100),
|
||||
truncated: z.boolean(),
|
||||
items: z.array(processMetricSchema),
|
||||
}),
|
||||
ipc: z.strictObject({
|
||||
events: z.array(ipcEventSchema).max(10_000),
|
||||
events: z.array(ipcEventSchema),
|
||||
observed: z.number().int().min(0),
|
||||
retained: z.number().int().min(0),
|
||||
truncated: z.boolean(),
|
||||
}),
|
||||
timeline: z
|
||||
.strictObject({
|
||||
events: z.array(timelineEventSchema).max(20_000),
|
||||
events: z.array(timelineEventSchema),
|
||||
observed: z.number().int().min(0),
|
||||
retained: z.number().int().min(0),
|
||||
truncated: z.boolean(),
|
||||
})
|
||||
.default({ events: [], observed: 0, retained: 0, truncated: false }),
|
||||
.default({ events: [], observed: 0 }),
|
||||
coverage: coverageSchema.default({
|
||||
status: "partial_attach",
|
||||
observed_event_families: [],
|
||||
|
||||
@@ -96,14 +96,7 @@ const normalizeInspection = (
|
||||
completeness: classifyBrowserCompleteness({
|
||||
policyFilteredSections: new Set(),
|
||||
attachLimitedSections: new Set(),
|
||||
truncatedSections: new Set(
|
||||
result.windows_truncated ||
|
||||
result.processes.truncated ||
|
||||
result.ipc.truncated ||
|
||||
result.timeline.truncated
|
||||
? ["timeline"]
|
||||
: [],
|
||||
),
|
||||
truncatedSections: new Set(),
|
||||
unavailableSections: new Set([
|
||||
"frames",
|
||||
"scripts",
|
||||
@@ -118,7 +111,7 @@ const normalizeInspection = (
|
||||
websocket_connections: 0,
|
||||
websocket_frames: 0,
|
||||
webmcp_tools: 0,
|
||||
timeline_events: result.timeline.truncated ? result.timeline.observed : 0,
|
||||
timeline_events: 0,
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -8,20 +8,7 @@ import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const temporary: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
temporary
|
||||
.splice(0)
|
||||
.map(async (path) => rm(path, { force: true, recursive: true })),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps active hook lifecycle and IPC evidence bounded at the process boundary", async () => {
|
||||
const root = await createTestTempDirectory("rea-electron-hook-");
|
||||
temporary.push(root);
|
||||
await writeFile(
|
||||
join(root, "electron.js"),
|
||||
`const { EventEmitter } = require("node:events");
|
||||
const electronModuleSource = `const { EventEmitter } = require("node:events");
|
||||
const app = new EventEmitter();
|
||||
app.relaunch = () => undefined;
|
||||
app.setAsDefaultProtocolClient = () => true;
|
||||
@@ -90,12 +77,9 @@ module.exports = {
|
||||
shell,
|
||||
utilityProcess,
|
||||
};
|
||||
`,
|
||||
);
|
||||
const script = join(root, "exercise.cjs");
|
||||
await writeFile(
|
||||
script,
|
||||
`(async () => {
|
||||
`;
|
||||
|
||||
const activeCaptureSource = `(async () => {
|
||||
const electron = require("electron");
|
||||
const window = new electron.BrowserWindow({ webPreferences: { preload: "/approved/preload.js" } });
|
||||
const channel = "x".repeat(2048);
|
||||
@@ -115,11 +99,26 @@ electron.shell.openExternal("https://example.invalid").catch(() => undefined);
|
||||
electron.app.relaunch();
|
||||
electron.contextBridge.exposeInMainWorld("api", { value: true });
|
||||
window.webContents.loadURL("https://example.invalid").catch(() => undefined);
|
||||
for (let index = 0; index < 6000; index++) electron.app.relaunch();
|
||||
try { process.dlopen({}, "/missing/native.node"); } catch {}
|
||||
process.stdout.write(JSON.stringify({ snapshot: globalThis.__reaElectronActiveSnapshot(), default_popup_decision: defaultPopupDecision, popup_decision: popupDecision }) + "\\n");
|
||||
})();
|
||||
`,
|
||||
`;
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
temporary
|
||||
.splice(0)
|
||||
.map(async (path) => rm(path, { force: true, recursive: true })),
|
||||
);
|
||||
});
|
||||
|
||||
it("retains active hook lifecycle and IPC evidence without a count ceiling", async () => {
|
||||
const root = await createTestTempDirectory("rea-electron-hook-");
|
||||
temporary.push(root);
|
||||
await writeFile(join(root, "electron.js"), electronModuleSource);
|
||||
const script = join(root, "exercise.cjs");
|
||||
await writeFile(script, activeCaptureSource);
|
||||
const hook = join(process.cwd(), "scripts/electron-active-hook.cjs");
|
||||
const output = await runNode(hook, script, root, root);
|
||||
const result = JSON.parse(output.trim().split("\n").at(-1) ?? "null") as {
|
||||
@@ -128,11 +127,14 @@ process.stdout.write(JSON.stringify({ snapshot: globalThis.__reaElectronActiveSn
|
||||
readonly snapshot: {
|
||||
readonly hook_error: boolean;
|
||||
readonly events: readonly Record<string, unknown>[];
|
||||
readonly observed: number;
|
||||
};
|
||||
};
|
||||
const { snapshot } = result;
|
||||
|
||||
expect(snapshot.hook_error).toBe(false);
|
||||
expect(snapshot.events).toHaveLength(snapshot.observed);
|
||||
expect(snapshot.observed).toBeGreaterThan(5_000);
|
||||
expect(snapshot.events).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "window-lifecycle", event: "created" }),
|
||||
@@ -189,7 +191,6 @@ const runNode = (
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_PATH: moduleRoot,
|
||||
REA_ELECTRON_ACTIVE_MAX_RUNTIME_EVENTS: "200",
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
@@ -229,7 +230,6 @@ const runNodeStatus = (
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_PATH: moduleRoot,
|
||||
REA_ELECTRON_ACTIVE_MAX_RUNTIME_EVENTS: "200",
|
||||
},
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user