Files
paseo/packages/desktop/e2e/settings-memory.electron.mjs
Mohamed Boudra d7b7016cc9 feat(settings): polish navigation and controls (#5459)
* feat(settings): polish navigation and controls

* test(settings): update navigation assertions
2026-09-26 20:26:04 +02:00

164 lines
5.0 KiB
JavaScript

import { createWriteStream } from "node:fs";
const GC_ATTEMPTS = 6;
const SETTINGS_PANE_TEST_ID = "settings-detail-pane";
const STRESS_ROUNDS = 4;
// React Native Web's document-level pointer listener can retain its last target.
const MAX_TRANSIENT_DETACHED_PANES = 2;
const SETTINGS_DESTINATIONS = [
"General",
"Appearance",
"Sidebar",
"Chat",
"Terminal",
"Browser",
"Editor",
"Shortcuts",
"Integrations",
"Notifications",
"Permissions",
"Diagnostics",
"About",
"Overview",
"Projects",
"Connections",
"Pair device",
"Agents",
"Metadata",
"Workspaces",
"Providers",
"Usage",
"Terminals",
"Plugins",
];
function assert(condition, message) {
if (!condition) throw new Error(message);
}
async function openSettingsDestination(page, title) {
const sidebar = page.locator('[data-testid="settings-sidebar"]:visible');
const button = sidebar.getByRole("button", { name: title, exact: true });
await button.waitFor({ state: "visible" });
await button.click();
await page.waitForFunction(
(expectedTitle) =>
[...document.querySelectorAll('[data-testid="settings-detail-header-title"]')].some(
(element) =>
element instanceof HTMLElement &&
element.getBoundingClientRect().width > 0 &&
element.textContent === expectedTitle,
),
title,
);
}
async function rotateSettings(page) {
await page.getByRole("button", { name: "Settings", exact: true }).click();
await page.locator('[data-testid="settings-sidebar"]:visible').waitFor({ state: "visible" });
for (const title of SETTINGS_DESTINATIONS) {
await openSettingsDestination(page, title);
}
await page.locator('[data-testid="settings-back-to-workspace"]:visible').click();
await page.waitForFunction(() => !location.pathname.startsWith("/settings"));
}
async function countDetachedSettingsPanes(session) {
const objectGroup = "settings-retention-check";
try {
const prototype = await session.send("Runtime.evaluate", {
expression: "HTMLDivElement.prototype",
objectGroup,
});
const prototypeObjectId = prototype.result.objectId;
assert(prototypeObjectId, "CDP did not return HTMLDivElement.prototype");
const objects = await session.send("Runtime.queryObjects", {
prototypeObjectId,
objectGroup,
});
const objectsId = objects.objects.objectId;
assert(objectsId, "CDP did not return HTMLDivElement instances");
const count = await session.send("Runtime.callFunctionOn", {
objectId: objectsId,
functionDeclaration: `function () {
return Array.prototype.reduce.call(this, (total, node) => {
const isDetachedSettingsPane =
node instanceof HTMLDivElement &&
!node.isConnected &&
node.getAttribute("data-testid") === ${JSON.stringify(SETTINGS_PANE_TEST_ID)};
return total + (isDetachedSettingsPane ? 1 : 0);
}, 0);
}`,
returnByValue: true,
});
assert(
typeof count.result.value === "number",
"CDP did not return a detached Settings pane count",
);
return count.result.value;
} finally {
await session.send("Runtime.releaseObjectGroup", { objectGroup }).catch(() => undefined);
}
}
async function collectDetachedSettingsPanes(session) {
let detachedPanes = Number.POSITIVE_INFINITY;
for (let attempt = 0; attempt < GC_ATTEMPTS; attempt += 1) {
await session.send("HeapProfiler.collectGarbage");
detachedPanes = await countDetachedSettingsPanes(session);
if (detachedPanes === 0) return 0;
}
return detachedPanes;
}
async function captureHeapSnapshot(session, outputPath) {
const output = createWriteStream(outputPath);
session.on("HeapProfiler.addHeapSnapshotChunk", ({ chunk }) => output.write(chunk));
await session.send("HeapProfiler.takeHeapSnapshot", {
reportProgress: false,
captureNumericValue: true,
exposeInternals: false,
});
await new Promise((resolve, reject) => {
output.end(resolve);
output.on("error", reject);
});
}
export async function runSettingsMemoryRegression(page) {
const session = await page.context().newCDPSession(page);
await session.send("HeapProfiler.enable");
await rotateSettings(page);
const detachedAfterWarmRound = await collectDetachedSettingsPanes(session);
for (let round = 0; round < STRESS_ROUNDS; round += 1) {
await rotateSettings(page);
}
const detachedAfterStress = await collectDetachedSettingsPanes(session);
if (process.env.PASEO_DESKTOP_SETTINGS_HEAP_SNAPSHOT) {
await captureHeapSnapshot(session, process.env.PASEO_DESKTOP_SETTINGS_HEAP_SNAPSHOT);
}
assert(
detachedAfterWarmRound <= MAX_TRANSIENT_DETACHED_PANES,
`A warm Settings rotation retained ${detachedAfterWarmRound} detached detail panes`,
);
assert(
detachedAfterStress <= detachedAfterWarmRound,
`Settings rotations grew detached detail panes from ${detachedAfterWarmRound} to ${detachedAfterStress}`,
);
return {
warmRounds: 1,
stressRounds: STRESS_ROUNDS,
detachedAfterWarmRound,
detachedAfterStress,
};
}