From d9d6c22523624e125c69d482dab5c110160015a1 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Wed, 23 Sep 2026 18:05:05 +0300 Subject: [PATCH 01/36] fix(release): set sync merge message through github-script --- .github/workflows/release.yml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d77d553d..785f6b91 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -111,12 +111,13 @@ jobs: run: node .github/scripts/releaseAutomation.cjs run - name: Sync main → develop - uses: devmasx/merge-branch@v1.4.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: - type: now - from_branch: main - target_branch: develop - message: "chore(sync): merge main into develop" - github_token: ${{ secrets.GITHUB_TOKEN }} - env: - HUSKY: 0 + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + await github.rest.repos.merge({ + ...context.repo, + base: "develop", + head: "main", + commit_message: "chore(sync): merge main into develop", + }); From 18748999dff72c309c40cb9d648d17f16fd2c6ef Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Thu, 24 Sep 2026 14:59:08 +0300 Subject: [PATCH 02/36] chore(deps): update browser and storage packages --- package-lock.json | 66 ++++++++++++++++++++++++++++------------------- package.json | 4 +-- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index c12c83c7..eade6d21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,9 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { - "@addon-core/browser": "^0.7.2", + "@addon-core/browser": "^0.9.0", "@addon-core/inject-script": "^0.5.0", - "@addon-core/storage": "^0.7.0", + "@addon-core/storage": "^0.8.0", "@rsdoctor/rspack-plugin": "^1.5.1", "@rspack/cli": "^1.7.5", "@rspack/core": "^1.7.5", @@ -129,18 +129,18 @@ "license": "MIT" }, "node_modules/@addon-core/browser": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@addon-core/browser/-/browser-0.7.2.tgz", - "integrity": "sha512-noDIPQktJOl7HVFUBZONL1iAVK4Spq1K+JgtN3OTsNPSvkgogrWUvpjq00p99R02y+YWORR70gu3tR+NDh09vw==", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@addon-core/browser/-/browser-0.9.0.tgz", + "integrity": "sha512-EiZnYVOZoYi49+RqOQKBPYnYSNtUtjyVDn3rrKZD3/PF0hU2vGShioP7VbeqP1pL4eX1ZrpWkbBkaNHwwf9s+A==", "license": "MIT", "dependencies": { - "@types/chrome": "^0.2.2" + "@types/chrome": "^0.3.0" } }, "node_modules/@addon-core/browser/node_modules/@types/chrome": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.2.7.tgz", - "integrity": "sha512-9kjBozQ+jyDVt1eai3VZqjHDTN95JCuRmbRHOryOltBJmzrTmUw/9r/DznmjRaQ8WlyIfeCA4WNzoj0IvormJA==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.3.0.tgz", + "integrity": "sha512-HPAMKyJwUnNJdVsxgypwvIStlwRhhaLnxgEwKIB99oOcffJrC4UMeD5xseVoBwDinSkr690DCBaAtSYdK6M1qw==", "license": "MIT", "dependencies": { "@types/filesystem": "*", @@ -156,21 +156,41 @@ "@addon-core/browser": "^0.7.2" } }, + "node_modules/@addon-core/inject-script/node_modules/@addon-core/browser": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@addon-core/browser/-/browser-0.7.2.tgz", + "integrity": "sha512-noDIPQktJOl7HVFUBZONL1iAVK4Spq1K+JgtN3OTsNPSvkgogrWUvpjq00p99R02y+YWORR70gu3tR+NDh09vw==", + "license": "MIT", + "dependencies": { + "@types/chrome": "^0.2.2" + } + }, + "node_modules/@addon-core/inject-script/node_modules/@types/chrome": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.2.9.tgz", + "integrity": "sha512-6rXrDPkkWv4D2Q23HqT+iED4voODU5CpOSc2VFgKyecSFItuEsIBvz9CY6jUQ3dd+Q7zo1bnCE9pKRMtkYut5g==", + "license": "MIT", + "dependencies": { + "@types/filesystem": "*", + "@types/har-format": "*" + } + }, "node_modules/@addon-core/storage": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/@addon-core/storage/-/storage-0.7.0.tgz", - "integrity": "sha512-MINCKg6sMKKeqRUnhmfPLveYBUwrvDrEvSmZgo+7QMxD0bWrbdmbO8Eb15DJHzNF/dX3M/CBwW4dxSzZXr03Fg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@addon-core/storage/-/storage-0.8.0.tgz", + "integrity": "sha512-2luHeyty/r64XJh7Jh81kEs3wKUq3VdRGK8Aw2oO4OpBd2YI3AZSZIdrG1+6fEyaYaQTsdaGr0D+Cve8FiT/yQ==", "license": "MIT", "dependencies": { - "@addon-core/browser": "^0.5.0", + "@addon-core/browser": "^0.9.0", "dequal": "^2.0.3" }, "peerDependencies": { "@types/chrome": "*", - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0", + "typescript": ">=5.4" }, "peerDependenciesMeta": { "@types/react": { @@ -178,18 +198,12 @@ }, "@types/react-dom": { "optional": true + }, + "typescript": { + "optional": true } } }, - "node_modules/@addon-core/storage/node_modules/@addon-core/browser": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/@addon-core/browser/-/browser-0.5.0.tgz", - "integrity": "sha512-xFi/dFcJYNblW+esBkJ2D5ind6Yydcj3yJnwVocILjDuV1oyxD9Qlh+K3FJnRprwdlqhxeo/E06KFoS6lWSO+g==", - "license": "MIT", - "peerDependencies": { - "@types/chrome": "*" - } - }, "node_modules/@adobe/css-tools": { "version": "4.4.4", "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", diff --git a/package.json b/package.json index 75c1a695..db2bc9c4 100644 --- a/package.json +++ b/package.json @@ -144,9 +144,9 @@ "test:run": "node scripts/test-run.mjs" }, "dependencies": { - "@addon-core/browser": "^0.7.2", + "@addon-core/browser": "^0.9.0", "@addon-core/inject-script": "^0.5.0", - "@addon-core/storage": "^0.7.0", + "@addon-core/storage": "^0.8.0", "@rsdoctor/rspack-plugin": "^1.5.1", "@rspack/cli": "^1.7.5", "@rspack/core": "^1.7.5", From 6b9204d8c764c9bd344531f6156ea3a7744acb0a Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Thu, 24 Sep 2026 14:59:25 +0300 Subject: [PATCH 03/36] test: use browser harness by default with audited legacy exceptions --- jest.config.ts | 9 +- scripts/test-inventory-utils.mjs | 182 +++++++++++ scripts/test-inventory.mjs | 6 + src/locale/providers/NativeLocale.test.ts | 32 +- tests/README.md | 76 ++++- tests/browser-harness-dom.test.ts | 26 ++ tests/browser-harness.test.ts | 290 ++++++++++++++++++ tests/browser-harness/BrowserTestSession.ts | 147 +++++++++ .../framework-state-inventory.json | 82 +++++ tests/browser-harness/framework-state.ts | 105 +++++++ tests/browser-harness/migration.json | 52 ++++ tests/browser-harness/session.ts | 27 ++ tests/jest-browser.setup.ts | 13 + tests/jest-legacy.setup.ts | 5 + tests/jest-modules.setup.ts | 72 ++--- tests/jest.setup.ts | 14 +- tests/raw-module-resolver.cjs | 5 + tsconfig.json | 3 +- 18 files changed, 1085 insertions(+), 61 deletions(-) create mode 100644 scripts/test-inventory-utils.mjs create mode 100644 tests/browser-harness-dom.test.ts create mode 100644 tests/browser-harness.test.ts create mode 100644 tests/browser-harness/BrowserTestSession.ts create mode 100644 tests/browser-harness/framework-state-inventory.json create mode 100644 tests/browser-harness/framework-state.ts create mode 100644 tests/browser-harness/migration.json create mode 100644 tests/browser-harness/session.ts create mode 100644 tests/jest-browser.setup.ts create mode 100644 tests/jest-legacy.setup.ts diff --git a/jest.config.ts b/jest.config.ts index 1e515f0f..92d5d07b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -5,7 +5,8 @@ const shared: Config = { rootDir: import.meta.dirname, testEnvironment: "node", globals: {ADNBN_TEST_ROOT: import.meta.dirname}, - setupFiles: ["/tests/jest.setup.ts"], + setupFilesAfterEnv: ["/tests/jest.setup.ts"], + transformIgnorePatterns: ["/node_modules/(?!(@addon-core/storage|nanoid)/)"], modulePathIgnorePatterns: ["/.cache/"], resolver: "/tests/raw-module-resolver.cjs", moduleNameMapper: { @@ -24,6 +25,7 @@ const shared: Config = { "^@transport/(.*)$": "/src/transport/$1", "^@main/(.*)$": "/src/main/$1", "^@typing/(.*)$": "/src/types/$1", + "^@tests/(.*)$": "/tests/$1", }, extensionsToTreatAsEsm: [".ts", ".tsx"], transform: { @@ -53,6 +55,7 @@ const buildTests = [ "/tests/integration/build/**/*.test.ts", ]; const domTests = [ + "/tests/browser-harness-dom.test.ts", "/src/entry/**/*.test.ts", "/src/frame/**/*.test.ts", "/src/sandbox/providers/**/*.test.ts", @@ -91,7 +94,7 @@ const config: Config = { { ...shared, displayName: "chrome", - setupFiles: [], + setupFilesAfterEnv: [], testMatch: [ "/tests/integration/browser/**/*.integration.test.ts", ...exclude(["/tests/integration/browser/**/*.firefox.integration.test.ts"]), @@ -100,7 +103,7 @@ const config: Config = { { ...shared, displayName: "firefox", - setupFiles: [], + setupFilesAfterEnv: [], testMatch: ["/tests/integration/browser/**/*.firefox.integration.test.ts"], }, ], diff --git a/scripts/test-inventory-utils.mjs b/scripts/test-inventory-utils.mjs new file mode 100644 index 00000000..dfcfff5f --- /dev/null +++ b/scripts/test-inventory-utils.mjs @@ -0,0 +1,182 @@ +import assert from "node:assert/strict"; +import {readFileSync} from "node:fs"; +import path from "node:path"; +import ts from "typescript"; + +const parse = (file, source) => ts.createSourceFile(file, source, ts.ScriptTarget.Latest, true); +const walk = (node, visitor) => { + visitor(node); + ts.forEachChild(node, child => walk(child, visitor)); +}; +const sorted = values => [...values].sort(); + +export function findModuleMocks(source, file = "test.ts") { + const modules = new Set(); + + walk(parse(file, source), node => { + if (!ts.isCallExpression(node) || !ts.isPropertyAccessExpression(node.expression)) { + return; + } + + const {expression, name} = node.expression; + const argument = node.arguments[0]; + + if ( + ts.isIdentifier(expression) && + expression.text === "jest" && + ["mock", "doMock", "setMock", "unstable_mockModule"].includes(name.text) && + argument && + ts.isStringLiteralLike(argument) && + (argument.text.startsWith("@addon-core/") || argument.text === "@main/env") + ) { + modules.add(argument.text); + } + }); + + return sorted(modules); +} + +export function findStaticState(source, file) { + const ast = parse(file, source); + const classes = {}; + + walk(ast, node => { + if (!ts.isClassDeclaration(node) || !node.name) { + return; + } + + const members = node.members.filter(member => + member.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.StaticKeyword) + ); + const fields = members.filter(ts.isPropertyDeclaration).map(member => member.name.getText(ast)); + const getInstance = members.some(member => member.name?.getText(ast) === "getInstance"); + + if (fields.length || getInstance) { + classes[node.name.text] = {file, fields, getInstance}; + } + }); + + return classes; +} + +export function verifyMigrationInventory(root, testFiles, sourceFiles) { + const read = file => readFileSync(path.join(root, file), "utf8"); + const {legacyFiles, localMocks} = JSON.parse(read("tests/browser-harness/migration.json")); + + assert.equal(new Set(legacyFiles).size, legacyFiles.length, "Duplicate legacy exceptions"); + + for (const file of [...legacyFiles, ...Object.keys(localMocks)]) { + assert(testFiles.has(file), `Stale migration entry: ${file}`); + } + + const actualMocks = {}; + + for (const file of testFiles) { + const modules = findModuleMocks(read(file), file); + + if (modules.length) { + actualMocks[file] = modules; + } + } + + assert.deepEqual( + actualMocks, + localMocks, + "Module-mock inventory changed; migrate mocks or explicitly review exceptions" + ); + const additional = Object.keys(actualMocks).filter(file => !legacyFiles.includes(file)); + const remaining = new Set([...legacyFiles, ...Object.keys(actualMocks)]); + + console.info( + `Migration: ${legacyFiles.length} legacy files, ${additional.length} additional local-mock files, ${remaining.size} files remaining` + ); + + for (const file of sorted(remaining)) { + console.info( + ` ${file}: ${[...(legacyFiles.includes(file) ? ["legacy"] : []), ...(actualMocks[file] ?? [])].join(", ")}` + ); + } + + const declared = JSON.parse(read("tests/browser-harness/framework-state-inventory.json")); + const discovered = {}; + const globalKeys = new Set(); + + for (const file of sourceFiles.filter( + file => + file.startsWith("src/") && + /\.tsx?$/.test(file) && + !file.endsWith(".d.ts") && + !/\.test\.tsx?$/.test(file) && + !file.includes("/tests/") + )) { + const source = read(file); + + for (const [name, owner] of Object.entries(findStaticState(source, file))) { + assert(!discovered[name], `Duplicate state-owner class name: ${name}`); + discovered[name] = owner; + } + + walk(parse(file, source), node => { + if ( + ts.isVariableStatement(node) && + node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword) + ) { + for (const declaration of node.declarationList.declarations) { + if (ts.isIdentifier(declaration.name) && declaration.name.text.endsWith("GlobalKey")) { + globalKeys.add(declaration.name.text); + } + } + } + }); + } + + const expected = Object.fromEntries( + Object.entries(declared).map(([name, {retained, ...owner}]) => { + if (retained !== undefined) { + assert(typeof retained === "string" && retained.trim(), `Missing retention reason: ${name}`); + } + + return [name, owner]; + }) + ); + + assert.deepEqual( + discovered, + expected, + "Static state inventory changed; register its reset policy or explain retention" + ); + const resetSource = parse("framework-state.ts", read("tests/browser-harness/framework-state.ts")); + const resetOwners = new Set(); + const resetKeys = new Set(); + + walk(resetSource, node => { + if ( + ts.isVariableDeclaration(node) && + node.name.getText(resetSource) === "frameworkStateResetters" && + node.initializer && + ts.isObjectLiteralExpression(node.initializer) + ) { + for (const property of node.initializer.properties) { + resetOwners.add(property.name.getText(resetSource)); + } + } + + if (ts.isCallExpression(node)) { + for (const argument of node.arguments) { + if (ts.isIdentifier(argument) && argument.text.endsWith("GlobalKey")) { + resetKeys.add(argument.text); + } + } + } + }); + + assert.deepEqual( + sorted(resetOwners), + sorted(Object.keys(declared).filter(name => !declared[name].retained)), + "Every session-state owner must have a resetter" + ); + assert.deepEqual(sorted(resetKeys), sorted(globalKeys), "Every exported GlobalKey must be covered by reset"); + console.info( + `Framework state: ${resetOwners.size} reset owners, ${globalKeys.size} global keys, ${Object.keys(declared).length - resetOwners.size} documented retained owners` + ); +} diff --git a/scripts/test-inventory.mjs b/scripts/test-inventory.mjs index aa59fbc5..1159261c 100644 --- a/scripts/test-inventory.mjs +++ b/scripts/test-inventory.mjs @@ -3,6 +3,7 @@ import {execFile} from "node:child_process"; import {existsSync} from "node:fs"; import path from "node:path"; import {promisify} from "node:util"; +import {verifyMigrationInventory} from "./test-inventory-utils.mjs"; const execute = promisify(execFile); const root = path.resolve(import.meta.dirname, ".."); @@ -36,3 +37,8 @@ for (const project of projects) { } assert.deepEqual(new Set(owners.keys()), expected, "Every test file must belong to exactly one Jest project"); console.info(`${expected.size} test files, each in exactly one project`); +verifyMigrationInventory( + root, + expected, + tracked.split("\0").filter(file => file && existsSync(path.join(root, file))) +); diff --git a/src/locale/providers/NativeLocale.test.ts b/src/locale/providers/NativeLocale.test.ts index ec2ca089..4eec796f 100644 --- a/src/locale/providers/NativeLocale.test.ts +++ b/src/locale/providers/NativeLocale.test.ts @@ -1,9 +1,6 @@ -jest.mock("@addon-core/browser", () => ({ - getI18nMessage: jest.fn(), -})); jest.mock("#adnbn/locale", () => ({keys: ["demo.empty", "demo.title"], languages: ["en", "fr", "ru"]})); -import {getI18nMessage} from "@addon-core/browser"; +import {getBrowserTest} from "@tests/browser-harness/session"; import NativeLocale from "./NativeLocale"; interface Structure { @@ -17,21 +14,18 @@ describe("NativeLocale", () => { let error: jest.SpyInstance; beforeEach(() => { - jest.mocked(getI18nMessage) - .mockReset() - .mockImplementation(key => (key === "locale" ? "ru" : "")); + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setImplementation(key => + key === "locale" ? "ru" : "" + ); + warn = jest.spyOn(console, "warn").mockImplementation(); error = jest.spyOn(console, "error").mockImplementation(); locale = new NativeLocale(); }); - afterEach(() => { - jest.restoreAllMocks(); - }); - test("preserves an empty message for a known build key without warning", () => { expect(locale.trans("demo.empty")).toBe(""); - expect(getI18nMessage).toHaveBeenLastCalledWith("demo_empty"); + expect(getBrowserTest().harness.configurable.chrome.i18n.getMessage.calls.at(-1)?.args[0]).toBe("demo_empty"); expect(warn).not.toHaveBeenCalled(); expect(error).not.toHaveBeenCalled(); }); @@ -43,7 +37,7 @@ describe("NativeLocale", () => { }); test.each(["Translated title", "0"])("preserves the nonempty message %j without consulting build keys", value => { - jest.mocked(getI18nMessage).mockReturnValue(value); + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setResult(value); const keys = jest.spyOn(locale, "keys"); expect(locale.trans("demo.title")).toBe(value); @@ -53,14 +47,17 @@ describe("NativeLocale", () => { }); test("preserves nonempty messages outside the default build contract", () => { - jest.mocked(getI18nMessage).mockReturnValue("Extra translation"); + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setResult("Extra translation"); expect(locale.get("demo.extra" as never)).toBe("Extra translation"); expect(warn).not.toHaveBeenCalled(); }); test("can translate a known message even when the language marker is missing", () => { - jest.mocked(getI18nMessage).mockImplementation(key => (key === "locale" ? "" : "Hello {{name}}")); + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setImplementation(key => + key === "locale" ? "" : "Hello {{name}}" + ); + const withoutMarker = new NativeLocale(); expect(withoutMarker.trans("demo.title")).toBe("Hello {{name}}"); @@ -68,7 +65,9 @@ describe("NativeLocale", () => { }); test("does not treat an undefined API result as a valid empty translation", () => { - jest.mocked(getI18nMessage).mockReturnValue(undefined); + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setImplementation( + () => undefined as unknown as string + ); expect(locale.trans("demo.empty")).toBe("demo.empty"); expect(warn).toHaveBeenCalledWith('Locale key "demo.empty" not found in "ru" language.'); @@ -95,6 +94,7 @@ describe("NativeLocale", () => { ["fr", "Français"], ["ru", "Русский"], ]); + expect(error).not.toHaveBeenCalled(); }); }); diff --git a/tests/README.md b/tests/README.md index d42fc000..469a85bd 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,7 +15,7 @@ The observed Node 24 crashes contain `Builtins_BaselineOutOfLinePrologue` and `C | `npm run test:firefox` | Real Firefox MV2/MV3 scenarios | Once | | `npm test` | Every Jest project, including both browsers | Once | | `npm run test:pre-push` | Framework typecheck, all non-browser tests and all fixture typechecks | Once | -| `npm run test:inventory` | Every test file belongs to exactly one project | No | +| `npm run test:inventory` | Test ownership, migration exceptions and framework reset inventory | No | `npm run typecheck` checks framework and test-runner types. `npm run typecheck:integration` builds the package, prepares every fixture application and checks it with its own `tsconfig.json`. Declaration consumers without an application config belong to the Jest `types` project. @@ -39,6 +39,80 @@ Preparation and fixture typechecks use a bounded queue of up to four processes. See [integration/README.md](integration/README.md) for browser requirements and fixture layout. Browser tests use separate profiles, ports and application copies. Tests that edit or watch a fixture must retain their own copy and build; generated outputs must not be shared between mutable scenarios. +## Browser harness migration + +`tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for +unit, build and types projects. Only the 12 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. +Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. +Real Chrome/Firefox integrations load neither setup. + +The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for +`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 20 files to review in total. +Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, +decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. +`npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. +Update the inventory alongside each migration so stale exceptions cannot remain unnoticed. + +The harness setup creates a fresh `BrowserTestSession` before every test. Use `getBrowserTest()` from +`@tests/browser-harness/session` inside tests and hooks, not at module scope. The `@tests/*` alias maps to the root `tests/` +directory in TypeScript and Jest. Browser and Storage imports are real unless the file declares a recorded local mock; +build-time virtual modules can still supply explicit test fixtures. `NativeLocale.test.ts` demonstrates API controls +and call history instead of function mocks. + +```ts +import {getBrowserTest} from "@tests/browser-harness/session"; + +const session = getBrowserTest(); +session.harness.configurable.chrome.i18n.getMessage.setResult("en"); +session.addCleanup(unsubscribe); +``` + +The default messaging context is an extension page with the Chrome profile. The session sets `BROWSER=chrome`, +`APP=test` and `MANIFEST_VERSION` from its manifest. Constructor options `profile`, `app` and `manifest` select alternatives. +Teardown restores the previous environment values, including previously absent variables. Framework environment +getters read these values when called. + +Create other contexts through `session.harness.contexts.create()` and install them with `session.useContext(context)`. +Use `session.useContext(context, "firefox")` to switch both browser globals and `BROWSER`; configure the reported browser +version through `harness.runtime.getBrowserInfo.setResult()`. The returned restore function and nested installations +follow reverse order; teardown restores remaining installations automatically. Use context-bound facades from +`harness.messaging.forContext()` for the other endpoints of a conversation. Global context changes are not async-local: +do not use `test.concurrent` or assume that global Browser wrappers remember the context of an earlier callback. +`environment: "preserve"` retains jsdom's window, document, location and navigator; it does not simulate a background DOM. + +`session.createScriptRuntime({clock: true})` binds a persistent guest runtime to the session's document registry and +registers disposal. Create the target tabs/documents first. Advance the guest clock explicitly; host timers and +consumer deadlines remain separate. Runtime and tab message delivery continue under Jest host fake timers without +advancing them. `harness.delays.downloadValidation` uses a host timer: advance it explicitly in tests using fake timers. +Shared teardown restores real host timers even when cleanup fails. Guest-clock checks use real timers inside the guest; +Jest fake timers affect only the host. + +Register consumer subscriptions, observers and other resources with `session.addCleanup()`. Async cleanup is awaited +in reverse order, and a failure does not prevent remaining cleanup, harness reset or global/environment restoration. +Teardown clears Message/Relay/Offscreen/Service/Sandbox managers and Relay permission registration. It resets +NativeLocale, Message, ObservableLocale (default, memory and driver caches), and OffscreenBridge singleton references, +and disposes cached SandboxMessage hosts. Clearing a cache does not dispose every previously returned provider or DOM +bridge: subscriptions, pending DOM work and manually created instances still need explicit cleanup callbacks. +Do not reset the harness underneath a live session; that invalidates contexts and detaches the scripting executor. +Jest mock history and spies are reset centrally. + +`framework-state-inventory.json` records source classes with static fields or `getInstance`, including retained constants +and the CLI TypeScript resolver cache. `test:inventory` compares the source fields with that inventory, checks registration +of the 11 runtime reset owners, and checks all six exported `*GlobalKey` declarations against the reset implementation. +New fields or owners require a reviewed reset policy. After runtime cleanup, any remaining own global property starting +with `adnbn` fails teardown rather than being silently deleted. This source inventory detects missing policies; it does +not prove that every reset implementation is correct, so session lifecycle tests verify the actual behavior too. + +### Remaining test-kit boundaries + +- `hasListeners` is not modeled. Check subscriptions through the context event's `listenerCount()`. +- VM scenarios require uninstrumented injected code. During Relay migration, isolate the injected function and exclude + only that module from Babel coverage; retain coverage for its host adapter and real-browser checks for the guest code. +- The session does not emulate Web Locks, DOM inside the guest, or extension HTML execution. Keep explicit external + adapters where needed and real-browser checks for DOM, execution worlds, browser lifecycle and vendor behavior. +- Consult the kit's `RAW_CAPABILITY_COVERAGE` before using another browser API. Extend the kit when a required capability + is missing; an existing modeled capability needs no new global mock. + ## Hooks Pre-commit checks only formatting of the **staged contents**, without changing the index or unstaged work. Format reported files and stage them before retrying. Tests, typechecking and builds run at pre-push and in CI. During development, run `test:unit` or `test:watch` explicitly for earlier feedback. Tests execute the working tree, so CI remains the full validation of the committed revision. diff --git a/tests/browser-harness-dom.test.ts b/tests/browser-harness-dom.test.ts new file mode 100644 index 00000000..b5aa93e6 --- /dev/null +++ b/tests/browser-harness-dom.test.ts @@ -0,0 +1,26 @@ +import {getManifest} from "@addon-core/browser"; + +import {getBrowserTest} from "./browser-harness/session"; + +const originalWindow = window; +const originalDocument = document; +const originalLocation = location; +const originalNavigator = navigator; + +test("preserves jsdom identities through context changes and teardown", async () => { + const session = getBrowserTest(); + const assertDom = () => { + expect(window).toBe(originalWindow); + expect(document).toBe(originalDocument); + expect(location).toBe(originalLocation); + expect(navigator).toBe(originalNavigator); + }; + + assertDom(); + expect(jest.isMockFunction(getManifest)).toBe(false); + expect(getManifest()).toEqual(session.harness.runtime.manifest); + session.useContext(session.harness.contexts.create({kind: "background"})); + assertDom(); + await session.dispose(); + assertDom(); +}); diff --git a/tests/browser-harness.test.ts b/tests/browser-harness.test.ts new file mode 100644 index 00000000..9cecbc7e --- /dev/null +++ b/tests/browser-harness.test.ts @@ -0,0 +1,290 @@ +jest.mock("#adnbn/locale", () => require("../src/locale/providers/tests/fixtures/dynamic")); + +import {executeScript, getBrowserInfo, getManifest, sendMessage, sendTabMessage} from "@addon-core/browser"; +import {createTabFixture} from "@addon-core/browser/testing"; +import {Storage} from "@addon-core/storage"; + +import NativeLocale from "@locale/providers/NativeLocale"; +import ObservableLocale from "@locale/providers/ObservableLocale"; +import {MemoryLocaleStorage} from "@locale/tests/fixtures"; +import OffscreenBridge from "@offscreen/OffscreenBridge"; +import SandboxMessage from "@sandbox/SandboxMessage"; +import {getApp, getBrowser, getManifestVersion, isBrowser} from "@main/env"; +import {Browser} from "@typing/browser"; +import MessageManager from "@message/MessageManager"; +import Message from "@message/providers/Message"; +import RelayManager from "@relay/RelayManager"; +import {MessageGlobalKey} from "@typing/message"; +import {RelayGlobalKey} from "@typing/relay"; + +import BrowserTestSession from "./browser-harness/BrowserTestSession"; +import {getBrowserTest, stopBrowserTest} from "./browser-harness/session"; + +const hostSetTimeout = setTimeout; +const initialBrowser = process.env.BROWSER; +const initialChrome = Object.getOwnPropertyDescriptor(globalThis, "chrome"); + +test.each([1, 2])("starts test %i with fresh API state, call history and framework singletons", async () => { + const {harness} = getBrowserTest(); + + expect(harness.calls).toEqual([]); + expect(harness.storage.local.data).toEqual({}); + expect(globalThis[MessageGlobalKey]).toBeUndefined(); + expect(globalThis[RelayGlobalKey]).toBeUndefined(); + expect(jest.isMockFunction(getManifest)).toBe(false); + expect(jest.isMockFunction(Storage.Local)).toBe(false); + + await harness.chrome.storage.local.set({probe: 42}); + MessageManager.getInstance(); + RelayManager.getInstance(); + + expect(harness.calls.length).toBeGreaterThan(0); +}); + +test("reads through the real Storage package and preserves browser failures", async () => { + const {harness} = getBrowserTest(); + const storage = Storage.Local<{locale: string}>({namespace: "adnbn"}); + + await expect(storage.get("locale")).resolves.toBeUndefined(); + harness.storage.local.get.failNext(new Error("Storage denied")); + await expect(storage.get("locale")).rejects.toThrow("Storage denied"); +}); + +test("registers real Message listeners in one context and restores the caller context", async () => { + const session = getBrowserTest(); + const receiver = session.harness.contexts.create({kind: "background"}); + const restore = session.useContext(receiver); + const message = new Message<{probe: () => number}>(); + + message.watch("probe", () => 42); + restore(); + + await expect(message.send("probe", undefined)).resolves.toBe(42); + await session.dispose(); + expect(receiver.onMessage.listenerCount()).toBe(0); + expect(globalThis[MessageGlobalKey]).toBeUndefined(); +}); + +test("disposes pending guest execution, listeners, framework caches and restores global descriptors", async () => { + const session = getBrowserTest(); + + await session.dispose(); + + const previousChrome = Object.getOwnPropertyDescriptor(globalThis, "chrome"); + const current = new BrowserTestSession({ + tabs: [createTabFixture({id: 7})], + documents: [{documentId: "main", tabId: 7, url: "https://example.test/"}], + }); + + try { + const runtime = current.createScriptRuntime({clock: true}); + const pending = executeScript({ + target: {tabId: 7}, + func: () => new Promise(resolve => setTimeout(() => resolve("late"), 300)), + }); + const rejected = expect(pending).rejects.toThrow(/disposed|cancelled by reset/); + const manager = RelayManager.getInstance(); + + manager.add("probe", {value: 42}); + current.harness.configurable.chrome.i18n.getMessage.setResult("en"); + + const locale = NativeLocale.getInstance(); + const message = Message.getInstance(); + + await current.dispose(); + await rejected; + + expect(runtime.realmCount).toBe(0); + expect(runtime.pendingExecutions).toBe(0); + expect(() => runtime.clock!.advance(300)).toThrow(/disposed/); + expect(manager.has("probe")).toBe(false); + expect(Object.getOwnPropertyDescriptor(globalThis, "chrome")).toEqual(previousChrome); + + const next = new BrowserTestSession(); + + try { + next.harness.configurable.chrome.i18n.getMessage.setResult("fr"); + expect(NativeLocale.getInstance()).not.toBe(locale); + expect(NativeLocale.getInstance().lang()).toBe("fr"); + expect(Message.getInstance()).not.toBe(message); + expect(RelayManager.getInstance()).not.toBe(manager); + } finally { + await next.dispose(); + } + } finally { + await current.dispose(); + } +}); + +test("continues teardown after a consumer cleanup fails and rejects pending message channels", async () => { + const session = getBrowserTest(); + const receiver = session.harness.contexts.create({kind: "background"}); + const cleaned = jest.fn(); + + receiver.onMessage.on(() => true); + + const pending = expect(sendMessage("probe")).rejects.toThrow(); + + session.addCleanup(cleaned); + session.addCleanup(() => { + throw new Error("Consumer cleanup failed"); + }); + + await expect(stopBrowserTest()).rejects.toThrow("Browser test cleanup failed"); + await pending; + expect(cleaned).toHaveBeenCalledTimes(1); + expect(session.harness.contexts.list()).toEqual([]); +}); + +test("keeps Firefox profile and runtime environment consistent and restores all environment values", async () => { + const session = getBrowserTest(); + const original = [process.env.BROWSER, process.env.APP, process.env.MANIFEST_VERSION]; + const restore = session.useContext(session.context, "firefox"); + + session.harness.runtime.getBrowserInfo.setResult({ + name: "Firefox", + vendor: "Mozilla", + version: "153.0", + buildID: "test", + }); + + expect(isBrowser(Browser.Firefox)).toBe(true); + await expect(getBrowserInfo()).resolves.toMatchObject({version: "153.0"}); + restore(); + expect(getBrowser()).toBe(Browser.Chrome); + + const nested = new BrowserTestSession({ + profile: "firefox", + app: "fixture", + manifest: {manifest_version: 2, name: "Fixture", version: "1.0.0"}, + }); + + try { + expect(getBrowser()).toBe(Browser.Firefox); + expect(getApp()).toBe("fixture"); + expect(getManifestVersion()).toBe(2); + process.env.APP = "changed-during-test"; + expect(getApp()).toBe("changed-during-test"); + process.env.MANIFEST_VERSION = "3"; + expect(getManifestVersion()).toBe(3); + } finally { + await nested.dispose(); + } + + expect([process.env.BROWSER, process.env.APP, process.env.MANIFEST_VERSION]).toEqual(original); +}); + +test("delivers runtime and tab messages through bound endpoints without advancing host fake timers", async () => { + jest.useFakeTimers(); + + const {harness} = getBrowserTest(); + + harness.tabs.set([createTabFixture({id: 7})]); + + const worker = harness.contexts.create({kind: "background"}); + const content = harness.contexts.create({ + kind: "contentScript", + tabId: 7, + frameId: 0, + url: "https://example.test/", + }); + const endpoint = harness.messaging.forContext(worker); + + endpoint.chrome.runtime.onMessage.addListener((_message, _sender, reply) => { + void Promise.resolve().then(() => reply("runtime response")); + + return true; + }); + content.onMessage.on((_message, _sender, reply) => reply("tab response")); + + await expect(sendMessage("probe")).resolves.toBe("runtime response"); + await expect(sendTabMessage(7, "probe", {frameId: 0})).resolves.toBe("tab response"); + expect(jest.getTimerCount()).toBe(0); + // Leave fake timers installed deliberately; shared teardown must restore them. +}); + +test("restores host timers before the next test", () => { + expect(setTimeout).toBe(hostSetTimeout); +}); + +test("advances guest clocks independently of frozen host time", async () => { + jest.useFakeTimers({now: 5000}); + + const session = getBrowserTest(); + + session.harness.tabs.set([createTabFixture({id: 7})]); + session.harness.contexts.documents.create({documentId: "clock", tabId: 7, url: "https://example.test/"}); + + const runtime = session.createScriptRuntime({clock: true}); + const pending = executeScript({ + target: {tabId: 7}, + func: () => new Promise(resolve => setTimeout(() => resolve(Date.now()), 300)), + }); + + runtime.clock!.advance(299); + expect(runtime.pendingExecutions).toBe(1); + runtime.clock!.advance(1); + await expect(pending).resolves.toEqual([expect.objectContaining({result: 300})]); + expect(Date.now()).toBe(5000); + expect(jest.getTimerCount()).toBe(0); +}); + +test("keeps download-validation delay on the host clock", async () => { + jest.useFakeTimers(); + + const {harness} = getBrowserTest(); + let completed = false; + const pending = harness.delays.downloadValidation.api(100).then(() => { + completed = true; + }); + + await Promise.resolve(); + expect(completed).toBe(false); + expect(jest.getTimerCount()).toBe(1); + await jest.advanceTimersByTimeAsync(100); + await pending; + expect(completed).toBe(true); +}); + +test("resets all ObservableLocale caches, OffscreenBridge and cached Sandbox hosts", async () => { + const driver = new MemoryLocaleStorage(); + const previous = [ + ObservableLocale.getInstance(), + ObservableLocale.getInstance(false), + ObservableLocale.getInstance(driver), + ]; + const bridge = OffscreenBridge.getInstance(); + const host = SandboxMessage.for("probe", {url: "sandbox.html"}); + + await getBrowserTest().dispose(); + + const next = new BrowserTestSession(); + + try { + const current = [ + ObservableLocale.getInstance(), + ObservableLocale.getInstance(false), + ObservableLocale.getInstance(driver), + ]; + + current.forEach((instance, index) => expect(instance).not.toBe(previous[index])); + expect(OffscreenBridge.getInstance()).not.toBe(bridge); + expect(SandboxMessage.for("probe", {url: "sandbox.html"})).not.toBe(host); + } finally { + await next.dispose(); + } +}); + +test("reports an unknown framework global without skipping global and environment restoration", async () => { + const unknownKey = "adnbnUnregisteredTestState"; + + Reflect.set(globalThis, unknownKey, {}); + + try { + await expect(stopBrowserTest()).rejects.toThrow("Browser test cleanup failed"); + expect(Object.getOwnPropertyDescriptor(globalThis, "chrome")).toEqual(initialChrome); + expect(process.env.BROWSER).toBe(initialBrowser); + } finally { + Reflect.deleteProperty(globalThis, unknownKey); + } +}); diff --git a/tests/browser-harness/BrowserTestSession.ts b/tests/browser-harness/BrowserTestSession.ts new file mode 100644 index 00000000..2f3fc751 --- /dev/null +++ b/tests/browser-harness/BrowserTestSession.ts @@ -0,0 +1,147 @@ +import { + createBrowserHarness, + installBrowserGlobals, + type BrowserContext, + type BrowserHarness, + type BrowserHarnessOptions, + type BrowserProfile, +} from "@addon-core/browser/testing"; +import { + createNodeScriptRuntime, + type NodeScriptRuntime, + type NodeScriptRuntimeOptions, +} from "@addon-core/browser/testing/node"; + +import {resetFrameworkState} from "./framework-state"; + +type BrowserTestCleanup = () => void | Promise; +type BrowserTestRuntimeOptions = Omit; +type BrowserTestProfile = Exclude; + +export interface BrowserTestSessionOptions extends BrowserHarnessOptions { + profile?: BrowserTestProfile; + app?: string; +} + +/** One test owns its globals, contexts, guest runtimes and consumer resources. */ +export default class BrowserTestSession { + public readonly harness: BrowserHarness; + public readonly context: BrowserContext; + + private readonly restoreGlobals: () => void; + private readonly restoreEnvironment: () => void; + private profile: BrowserTestProfile; + private readonly cleanups: BrowserTestCleanup[] = []; + private disposal?: Promise; + + public constructor({profile = "chrome", app = "test", ...options}: BrowserTestSessionOptions = {}) { + this.profile = profile; + const environment = ["BROWSER", "APP", "MANIFEST_VERSION"].map(key => [key, process.env[key]] as const); + + this.restoreEnvironment = () => { + for (const [key, value] of environment) { + if (value === undefined) { + delete process.env[key]; + } else { + process.env[key] = value; + } + } + }; + + this.harness = createBrowserHarness(options); + this.context = this.harness.contexts.create({kind: "extensionPage"}); + this.restoreGlobals = installBrowserGlobals(this.harness, { + environment: "preserve", + profile, + messageContext: this.context, + }); + process.env.BROWSER = profile; + process.env.APP = app; + process.env.MANIFEST_VERSION = String(this.harness.runtime.manifest.manifest_version); + } + + public addCleanup(cleanup: BrowserTestCleanup): void { + if (this.disposal) { + throw new Error("Browser test session is disposed."); + } + + this.cleanups.push(cleanup); + } + + /** Nested context installations must be restored in reverse order. */ + public useContext(context: BrowserContext, profile: BrowserTestProfile = this.profile): () => void { + if (this.disposal) { + throw new Error("Browser test session is disposed."); + } + + const previousProfile = this.profile; + const previousBrowser = process.env.BROWSER; + const restore = installBrowserGlobals(this.harness, { + environment: "preserve", + profile, + messageContext: context, + }); + + this.profile = profile; + process.env.BROWSER = profile; + + let restored = false; + const cleanup = () => { + if (!restored) { + restore(); + this.profile = previousProfile; + + if (previousBrowser === undefined) { + delete process.env.BROWSER; + } else { + process.env.BROWSER = previousBrowser; + } + + restored = true; + } + }; + + this.addCleanup(cleanup); + + return cleanup; + } + + public createScriptRuntime(options: BrowserTestRuntimeOptions = {}): NodeScriptRuntime { + if (this.disposal) { + throw new Error("Browser test session is disposed."); + } + + const runtime = createNodeScriptRuntime({...options, documents: this.harness.contexts.documents}); + + this.harness.scripting.setExecutor(runtime.executor); + this.addCleanup(() => runtime.dispose()); + + return runtime; + } + + public dispose(): Promise { + return (this.disposal ??= Promise.resolve().then(() => this.cleanup())); + } + + private async cleanup(): Promise { + const errors: unknown[] = []; + + for (const cleanup of [ + ...this.cleanups.reverse(), + resetFrameworkState, + () => this.harness.reset(), + this.restoreGlobals, + this.restoreEnvironment, + ]) { + try { + await cleanup(); + } catch (error) { + errors.push(error); + } + } + + if (errors.length > 0) { + throw new AggregateError(errors, "Browser test cleanup failed."); + } + } +} diff --git a/tests/browser-harness/framework-state-inventory.json b/tests/browser-harness/framework-state-inventory.json new file mode 100644 index 00000000..4a799938 --- /dev/null +++ b/tests/browser-harness/framework-state-inventory.json @@ -0,0 +1,82 @@ +{ + "TsResolver": { + "file": "src/cli/entrypoint/file/resolvers/TsResolver.ts", + "fields": ["instances"], + "getInstance": false, + "retained": "CLI TypeScript resolution cache; outside browser-session ownership." + }, + "BackgroundEntry": { + "file": "src/cli/plugins/background/BackgroundEntry.ts", + "fields": ["name"], + "getInstance": false, + "retained": "Static entrypoint name, not mutable session state." + }, + "NativeLocale": { + "file": "src/locale/providers/NativeLocale.ts", + "fields": ["instance"], + "getInstance": true + }, + "ObservableLocale": { + "file": "src/locale/providers/ObservableLocale.ts", + "fields": [ + "defaultInstance", + "memoryInstance", + "drivers" + ], + "getInstance": true + }, + "MessageManager": { + "file": "src/message/MessageManager.ts", + "fields": [], + "getInstance": true + }, + "Message": { + "file": "src/message/providers/Message.ts", + "fields": [ + "FirefoxDocumentIdMinVersion", + "instance" + ], + "getInstance": true + }, + "OffscreenBridge": { + "file": "src/offscreen/OffscreenBridge.ts", + "fields": ["instance"], + "getInstance": true + }, + "OffscreenManager": { + "file": "src/offscreen/OffscreenManager.ts", + "fields": [], + "getInstance": true + }, + "RelayManager": { + "file": "src/relay/RelayManager.ts", + "fields": [], + "getInstance": true + }, + "RelayPermission": { + "file": "src/relay/RelayPermission.ts", + "fields": [], + "getInstance": true + }, + "ProxyRelay": { + "file": "src/relay/providers/ProxyRelay.ts", + "fields": ["SelectorKeys"], + "getInstance": false, + "retained": "Static selector key list, not mutable session state." + }, + "SandboxManager": { + "file": "src/sandbox/SandboxManager.ts", + "fields": [], + "getInstance": true + }, + "SandboxMessage": { + "file": "src/sandbox/SandboxMessage.ts", + "fields": ["hosts"], + "getInstance": false + }, + "ServiceManager": { + "file": "src/service/ServiceManager.ts", + "fields": [], + "getInstance": true + } +} diff --git a/tests/browser-harness/framework-state.ts b/tests/browser-harness/framework-state.ts new file mode 100644 index 00000000..09633a4b --- /dev/null +++ b/tests/browser-harness/framework-state.ts @@ -0,0 +1,105 @@ +import {MessageGlobalKey} from "@typing/message"; +import {OffscreenGlobalAccess, OffscreenGlobalKey} from "@typing/offscreen"; +import {RelayGlobalKey} from "@typing/relay"; +import {SandboxGlobalAccess, SandboxGlobalKey} from "@typing/sandbox"; +import {ServiceGlobalKey} from "@typing/service"; +import {RelayPermissionGlobalKey} from "@relay/RelayPermission"; + +interface TestManager { + clear(): unknown; +} + +function clearManager(key: string): void { + try { + (globalThis[key] as TestManager | undefined)?.clear(); + } finally { + Reflect.deleteProperty(globalThis, key); + } +} + +// Explicit owners are audited by test:inventory. Resolve modules after each test's +// virtual-module fixtures, never while the shared setup is loading. +export const frameworkStateResetters = { + MessageManager: () => clearManager(MessageGlobalKey), + RelayManager: () => clearManager(RelayGlobalKey), + OffscreenManager: () => clearManager(OffscreenGlobalKey), + ServiceManager: () => clearManager(ServiceGlobalKey), + SandboxManager: () => clearManager(SandboxGlobalKey), + RelayPermission: () => { + // Browser event subscriptions are removed by harness.reset(). + Reflect.deleteProperty(globalThis, RelayPermissionGlobalKey); + }, + NativeLocale: () => { + const {default: NativeLocale} = jest.requireActual( + "@locale/providers/NativeLocale" + ); + + Reflect.deleteProperty(NativeLocale, "instance"); + }, + Message: () => { + const {default: Message} = + jest.requireActual("@message/providers/Message"); + + Reflect.deleteProperty(Message, "instance"); + }, + ObservableLocale: () => { + const {default: ObservableLocale} = jest.requireActual( + "@locale/providers/ObservableLocale" + ); + + Reflect.deleteProperty(ObservableLocale, "defaultInstance"); + Reflect.deleteProperty(ObservableLocale, "memoryInstance"); + Reflect.set(ObservableLocale, "drivers", new WeakMap()); + }, + OffscreenBridge: () => { + const {default: OffscreenBridge} = + jest.requireActual("@offscreen/OffscreenBridge"); + + Reflect.deleteProperty(OffscreenBridge, "instance"); + }, + SandboxMessage: () => { + const {default: SandboxMessage} = + jest.requireActual("@sandbox/SandboxMessage"); + const hosts = Reflect.get(SandboxMessage, "hosts") as Map>; + const errors: unknown[] = []; + + for (const host of hosts.values()) { + try { + host.dispose(); + } catch (error) { + errors.push(error); + } + } + + hosts.clear(); + + if (errors.length) { + throw new AggregateError(errors, "Sandbox host cleanup failed."); + } + }, +}; + +export function resetFrameworkState(): void { + const errors: unknown[] = []; + + for (const reset of Object.values(frameworkStateResetters)) { + try { + reset(); + } catch (error) { + errors.push(error); + } + } + + Reflect.deleteProperty(globalThis, OffscreenGlobalAccess); + Reflect.deleteProperty(globalThis, SandboxGlobalAccess); + + const remaining = Object.getOwnPropertyNames(globalThis).filter(key => key.startsWith("adnbn")); + + if (remaining.length) { + errors.push(new Error(`Unregistered framework globals after teardown: ${remaining.join(", ")}`)); + } + + if (errors.length) { + throw new AggregateError(errors, "Framework state cleanup failed."); + } +} diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json new file mode 100644 index 00000000..7b1f1db6 --- /dev/null +++ b/tests/browser-harness/migration.json @@ -0,0 +1,52 @@ +{ + "legacyFiles": [ + "src/relay/providers/Relay.test.ts", + "src/relay/discovery/RelayDiscovery.test.ts", + "src/relay/RelayPermission.test.ts", + "src/entry/relay/index.test.ts", + "src/entry/relay/Builder.test.ts", + "src/message/providers/Message.test.ts", + "src/message/adapters/react/useMessageHandler.test.ts", + "src/offscreen/providers/Offscreen.test.ts", + "src/offscreen/OffscreenBridge.test.ts", + "src/locale/providers/ObservableLocale.test.ts", + "src/service/providers/Service.test.ts", + "src/sandbox/SandboxMessage.test.ts" + ], + "localMocks": { + "src/service/providers/Service.test.ts": [ + "@addon-core/browser" + ], + "src/locale/helpers.test.ts": [ + "@addon-core/browser" + ], + "src/locale/adapters/react/hooks/use-native-locale.test.ts": [ + "@addon-core/browser" + ], + "src/locale/providers/DynamicLocale.test.ts": [ + "@addon-core/browser", + "@addon-core/storage" + ], + "src/locale/providers/ObservableLocale.test.ts": [ + "@addon-core/browser" + ], + "src/locale/adapters/react/hooks/use-locale.test.ts": [ + "@addon-core/browser" + ], + "src/locale/adapters/react/hooks/use-locale-attributes.test.ts": [ + "@addon-core/browser" + ], + "src/locale/storage/LocaleStorage.test.ts": [ + "@addon-core/storage" + ], + "src/entry/content/lifecycle/nodes/isolated-styles.test.ts": [ + "@addon-core/browser" + ], + "src/entry/content/lifecycle/nodes/FrameNode.test.ts": [ + "@addon-core/browser" + ], + "src/offscreen/providers/Offscreen.test.ts": [ + "@main/env" + ] + } +} diff --git a/tests/browser-harness/session.ts b/tests/browser-harness/session.ts new file mode 100644 index 00000000..11da8101 --- /dev/null +++ b/tests/browser-harness/session.ts @@ -0,0 +1,27 @@ +import BrowserTestSession from "./BrowserTestSession"; + +let session: BrowserTestSession | undefined; + +export function startBrowserTest(): void { + if (session) { + throw new Error("The previous browser test session has not been stopped."); + } + + session = new BrowserTestSession(); +} + +export function getBrowserTest(): BrowserTestSession { + if (!session) { + throw new Error("Browser harness is unavailable. Use it inside a test or hook outside the legacy exceptions."); + } + + return session; +} + +export async function stopBrowserTest(): Promise { + try { + await session?.dispose(); + } finally { + session = undefined; + } +} diff --git a/tests/jest-browser.setup.ts b/tests/jest-browser.setup.ts new file mode 100644 index 00000000..c4d5308a --- /dev/null +++ b/tests/jest-browser.setup.ts @@ -0,0 +1,13 @@ +import {startBrowserTest, stopBrowserTest} from "./browser-harness/session"; + +beforeEach(startBrowserTest); + +afterEach(async () => { + try { + await stopBrowserTest(); + } finally { + jest.restoreAllMocks(); + jest.clearAllMocks(); + jest.useRealTimers(); + } +}); diff --git a/tests/jest-legacy.setup.ts b/tests/jest-legacy.setup.ts new file mode 100644 index 00000000..c2ac7fd3 --- /dev/null +++ b/tests/jest-legacy.setup.ts @@ -0,0 +1,5 @@ +import "jest-webextension-mock"; + +import "./jest-message.setup"; +import "./jest-modules.setup"; +import "./jest-relay.setup"; diff --git a/tests/jest-modules.setup.ts b/tests/jest-modules.setup.ts index 91d320f5..b1e475a3 100644 --- a/tests/jest-modules.setup.ts +++ b/tests/jest-modules.setup.ts @@ -62,43 +62,43 @@ jest.mock("nanoid/non-secure", () => ({ nanoid: jest.fn(() => "mocked-id"), })); -jest.mock( - "@addon-core/storage", - () => { - type Unsubscribe = () => void; - type WatchMap = Record void>; - - const createMockStorage = () => { - const store = new Map(); - let watchers: WatchMap = {}; - - const get = jest.fn(async (key: string) => store.get(key)); - const set = jest.fn(async (key: string, value: any) => { - store.set(key, value); - const cb = watchers[key]; - if (cb) cb(value); - }); - const watch = jest.fn((map: WatchMap): Unsubscribe => { - watchers = map; - return () => { - watchers = {}; - }; - }); +jest.mock("@addon-core/storage", () => { + type Unsubscribe = () => void; + type WatchMap = Record void>; + + const createMockStorage = () => { + const store = new Map(); + let watchers: WatchMap = {}; + + const get = jest.fn(async (key: string) => store.get(key)); + const set = jest.fn(async (key: string, value: any) => { + store.set(key, value); + const cb = watchers[key]; + + if (cb) { + cb(value); + } + }); + const watch = jest.fn((map: WatchMap): Unsubscribe => { + watchers = map; - return {get, set, watch}; - }; - - return { - __esModule: true, - Storage: { - Local: () => createMockStorage(), - Sync: () => createMockStorage(), - Session: () => createMockStorage(), - }, - }; - }, - {virtual: true} -); + return () => { + watchers = {}; + }; + }); + + return {get, set, watch}; + }; + + return { + __esModule: true, + Storage: { + Local: () => createMockStorage(), + Sync: () => createMockStorage(), + Session: () => createMockStorage(), + }, + }; +}); jest.mock("@main/env", () => ({ ...jest.requireActual("@main/env"), diff --git a/tests/jest.setup.ts b/tests/jest.setup.ts index c2ac7fd3..139007eb 100644 --- a/tests/jest.setup.ts +++ b/tests/jest.setup.ts @@ -1,5 +1,11 @@ -import "jest-webextension-mock"; +import path from "node:path"; +import {legacyFiles} from "./browser-harness/migration.json"; -import "./jest-message.setup"; -import "./jest-modules.setup"; -import "./jest-relay.setup"; +// Temporary migration boundary. A file uses exactly one browser implementation. +const testFile = path.relative(path.resolve(__dirname, ".."), expect.getState().testPath!).split(path.sep).join("/"); + +if (legacyFiles.includes(testFile)) { + require("./jest-legacy.setup"); +} else { + require("./jest-browser.setup"); +} diff --git a/tests/raw-module-resolver.cjs b/tests/raw-module-resolver.cjs index a28ef334..2d86da7f 100644 --- a/tests/raw-module-resolver.cjs +++ b/tests/raw-module-resolver.cjs @@ -2,5 +2,10 @@ module.exports = (request, options) => { const suffix = "?raw"; const target = request.endsWith(suffix) ? request.slice(0, -suffix.length) : request; + // Storage publishes ESM only. SWC transforms that entry for Jest's CJS runtime. + if (target === "@addon-core/storage" || target.startsWith("@addon-core/storage/")) { + return options.defaultResolver(target, {...options, conditions: ["import", "default"]}); + } + return options.defaultResolver(target, options); }; diff --git a/tsconfig.json b/tsconfig.json index 9511d008..5a7701e7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -73,7 +73,8 @@ "@shared/*": ["shared/*"], "@storage/*": ["storage/*"], "@transport/*": ["transport/*"], - "@typing/*": ["types/*"] + "@typing/*": ["types/*"], + "@tests/*": ["../tests/*"] }, "isolatedModules": true, "allowJs": true, From 2903310793fb4918b5a682ab037f8598be6f88b2 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Thu, 24 Sep 2026 16:06:41 +0300 Subject: [PATCH 04/36] fix(relay): omit scripting permission for MV2 --- src/relay/RelayPermission.test.ts | 90 ++++++++++++++++++++-------- src/relay/RelayPermission.ts | 10 +++- tests/README.md | 4 +- tests/browser-harness/migration.json | 1 - 4 files changed, 74 insertions(+), 31 deletions(-) diff --git a/src/relay/RelayPermission.test.ts b/src/relay/RelayPermission.test.ts index c77e5d87..8ff7d25b 100644 --- a/src/relay/RelayPermission.test.ts +++ b/src/relay/RelayPermission.test.ts @@ -1,28 +1,13 @@ -import {containsPermissions, onPermissionsAdded, onPermissionsRemoved} from "@addon-core/browser"; +import {getBrowserTest} from "@tests/browser-harness/session"; -import RelayPermission, {RelayPermissionGlobalKey} from "./RelayPermission"; +import RelayPermission from "./RelayPermission"; import {ContentScriptDeclarative} from "@typing/content"; import {RelayMethod, type RelayOptionsMap} from "@typing/relay"; -const mockedContainsPermissions = containsPermissions as jest.MockedFunction; -const mockedOnPermissionsAdded = onPermissionsAdded as jest.MockedFunction; -const mockedOnPermissionsRemoved = onPermissionsRemoved as jest.MockedFunction; - describe("RelayPermission", () => { - beforeEach(() => { - delete globalThis[RelayPermissionGlobalKey]; - mockedContainsPermissions.mockReset(); - mockedOnPermissionsAdded.mockClear(); - mockedOnPermissionsRemoved.mockClear(); - }); - - afterEach(() => { - delete globalThis[RelayPermissionGlobalKey]; - }); - test("checks every Relay stored in its Map", async () => { - mockedContainsPermissions.mockResolvedValue(true); + getBrowserTest().harness.permissions.contains.setResult(true); const relays: RelayOptionsMap = new Map([ [ "scanner", @@ -39,13 +24,15 @@ describe("RelayPermission", () => { await Promise.resolve(); await Promise.resolve(); - expect(mockedContainsPermissions).toHaveBeenCalledWith({ - origins: ["https://example.com/*"], - permissions: ["scripting"], - }); + expect(getBrowserTest().harness.permissions.contains.calls[0].args).toEqual([ + { + origins: ["https://example.com/*"], + permissions: ["scripting"], + }, + ]); expect(permission.allow("scanner")).toBe(true); - expect(mockedOnPermissionsAdded).toHaveBeenCalledTimes(1); - expect(mockedOnPermissionsRemoved).toHaveBeenCalledTimes(1); + expect(getBrowserTest().harness.permissions.onAdded.listenerCount()).toBe(1); + expect(getBrowserTest().harness.permissions.onRemoved.listenerCount()).toBe(1); }); test("returns one started instance for the current runtime context", () => { @@ -68,7 +55,58 @@ describe("RelayPermission", () => { expect(second).toBe(first); expect(first.allow("messaging")).toBe(true); - expect(mockedOnPermissionsAdded).toHaveBeenCalledTimes(1); - expect(mockedOnPermissionsRemoved).toHaveBeenCalledTimes(1); + expect(getBrowserTest().harness.permissions.onAdded.listenerCount()).toBe(1); + expect(getBrowserTest().harness.permissions.onRemoved.listenerCount()).toBe(1); }); }); + +test("refreshes optional Relay permissions after grants and revocations", async () => { + const {harness} = getBrowserTest(); + const required: chrome.permissions.Permissions = {permissions: ["scripting"], origins: ["https://example.com/*"]}; + const permission = RelayPermission.getInstance( + new Map([ + [ + "scanner", + { + name: "scanner", + method: RelayMethod.Scripting, + declarative: ContentScriptDeclarative.Optional, + matches: required.origins, + }, + ], + ]) + ); + + await permission.contains("scanner"); + expect(permission.allow("scanner")).toBe(false); + await harness.permissions.grant(required); + expect(permission.allow("scanner")).toBe(true); + await harness.permissions.revoke(required); + expect(permission.allow("scanner")).toBe(false); + expect(harness.permissions.contains.calls).toHaveLength(4); +}); + +test("does not request the MV3 scripting permission for an MV2 Relay", async () => { + const {harness} = getBrowserTest(); + + harness.runtime.setManifest({manifest_version: 2, name: "MV2 Relay", version: "1.0.0"}); + harness.permissions.set({origins: ["https://example.com/*"]}); + + const permission = RelayPermission.getInstance( + new Map([ + [ + "scanner", + { + name: "scanner", + method: RelayMethod.Scripting, + declarative: ContentScriptDeclarative.Optional, + matches: ["https://example.com/*"], + }, + ], + ]) + ); + + await expect(permission.contains("scanner")).resolves.toBe(true); + expect(harness.permissions.contains.calls[0].args).toEqual([{origins: ["https://example.com/*"], permissions: []}]); + expect(permission.allow("scanner")).toBe(true); +}); diff --git a/src/relay/RelayPermission.ts b/src/relay/RelayPermission.ts index d8e4663e..52adf5f7 100644 --- a/src/relay/RelayPermission.ts +++ b/src/relay/RelayPermission.ts @@ -1,4 +1,10 @@ -import {containsPermissions, onPermissionsAdded, onPermissionsRemoved, requestPermissions} from "@addon-core/browser"; +import { + containsPermissions, + getManifestVersion, + onPermissionsAdded, + onPermissionsRemoved, + requestPermissions, +} from "@addon-core/browser"; import {RelayMethod, RelayOptionsMap} from "@typing/relay"; import {ContentScriptDeclarative} from "@typing/content"; @@ -38,7 +44,7 @@ export default class RelayPermission { ? undefined : { origins: !declarative || declarative === ContentScriptDeclarative.Optional ? matches : [], - permissions: ["scripting"], + permissions: getManifestVersion() === 3 ? ["scripting"] : [], }; this.set(name, {allow, permissions}); diff --git a/tests/README.md b/tests/README.md index 469a85bd..a5e890ed 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,12 +42,12 @@ See [integration/README.md](integration/README.md) for browser requirements and ## Browser harness migration `tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for -unit, build and types projects. Only the 12 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. +unit, build and types projects. Only the 11 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. Real Chrome/Firefox integrations load neither setup. The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for -`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 20 files to review in total. +`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 19 files to review in total. Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. `npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 7b1f1db6..68888357 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -2,7 +2,6 @@ "legacyFiles": [ "src/relay/providers/Relay.test.ts", "src/relay/discovery/RelayDiscovery.test.ts", - "src/relay/RelayPermission.test.ts", "src/entry/relay/index.test.ts", "src/entry/relay/Builder.test.ts", "src/message/providers/Message.test.ts", From 8d035a82fa5f218ef9a8709ff24675e075949f4a Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Thu, 24 Sep 2026 16:08:05 +0300 Subject: [PATCH 05/36] fix(relay): preserve scripting error envelopes Resolve missing-manager failures through the RPC envelope and reject malformed responses with RelayProtocolError. Preserve synchronous dispatch and the final retry at 2700ms. Add isolated-runtime and Chrome/Firefox regressions. Refs #109. --- jest.config.ts | 3 + package.json | 2 +- .../plugins/content/RelayDeclaration.test.ts | 1 + src/main/relay.ts | 1 + src/relay/README.md | 34 +++- .../adapters/RelayScriptingAdapter.test.ts | 150 ++++++++++++++++ src/relay/adapters/RelayScriptingAdapter.ts | 161 ++++++------------ src/relay/adapters/invoke-relay.ts | 100 +++++++++++ src/relay/adapters/scripting-response.ts | 16 ++ src/relay/providers/Relay.test.ts | 6 +- src/relay/result.ts | 7 + src/relay/tests/fixtures/runtime.ts | 29 ++++ src/relay/tests/runtime.ts | 42 +++++ tests/README.md | 5 +- tests/integration/README.md | 7 + .../browser/relay/scripting-utils.ts | 43 +++++ .../scripting.firefox.integration.test.ts | 6 + .../relay/scripting.integration.test.ts | 5 + .../browser/relay/scripting/adnbn.config.ts | 8 + .../browser/relay/scripting/package.json | 9 + .../browser/relay/scripting/site/empty.html | 6 + .../browser/relay/scripting/site/top.html | 6 + .../browser/relay/scripting/src/background.ts | 95 +++++++++++ .../relay/scripting/src/probe.relay.ts | 21 +++ .../browser/relay/scripting/tsconfig.json | 8 + 25 files changed, 652 insertions(+), 119 deletions(-) create mode 100644 src/relay/adapters/RelayScriptingAdapter.test.ts create mode 100644 src/relay/adapters/invoke-relay.ts create mode 100644 src/relay/adapters/scripting-response.ts create mode 100644 src/relay/tests/fixtures/runtime.ts create mode 100644 src/relay/tests/runtime.ts create mode 100644 tests/integration/browser/relay/scripting-utils.ts create mode 100644 tests/integration/browser/relay/scripting.firefox.integration.test.ts create mode 100644 tests/integration/browser/relay/scripting.integration.test.ts create mode 100644 tests/integration/browser/relay/scripting/adnbn.config.ts create mode 100644 tests/integration/browser/relay/scripting/package.json create mode 100644 tests/integration/browser/relay/scripting/site/empty.html create mode 100644 tests/integration/browser/relay/scripting/site/top.html create mode 100644 tests/integration/browser/relay/scripting/src/background.ts create mode 100644 tests/integration/browser/relay/scripting/src/probe.relay.ts create mode 100644 tests/integration/browser/relay/scripting/tsconfig.json diff --git a/jest.config.ts b/jest.config.ts index 92d5d07b..379ed5d2 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -4,6 +4,9 @@ import {availableParallelism} from "node:os"; const shared: Config = { rootDir: import.meta.dirname, testEnvironment: "node", + // This function is serialized into browser/VM realms; Istanbul closures cannot cross that boundary. + coveragePathIgnorePatterns: ["/node_modules/", "/src/relay/adapters/invoke-relay\\.ts$"], + globals: {ADNBN_TEST_ROOT: import.meta.dirname}, setupFilesAfterEnv: ["/tests/jest.setup.ts"], transformIgnorePatterns: ["/node_modules/(?!(@addon-core/storage|nanoid)/)"], diff --git a/package.json b/package.json index db2bc9c4..65b5089a 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "test:firefox": "npm run build && node scripts/test-run.mjs --selectProjects firefox", "test:message": "node scripts/test-run.mjs --testPathPatterns=src/message", "test:service": "node scripts/test-run.mjs --testPathPatterns=Service", - "test:relay": "npm run test -- --testPathPatterns=Relay", + "test:relay": "npm run test -- --testPathPatterns=\"Relay|src/entry/relay|browser/relay\"", "test:locale": "npm run test -- --testPathPatterns=Locale", "test:manifest": "node scripts/test-run.mjs --testPathPatterns=src/cli/builders/manifest", "test:entrypoint": "node scripts/test-run.mjs --testPathPatterns=src/cli/entrypoint", diff --git a/src/cli/plugins/content/RelayDeclaration.test.ts b/src/cli/plugins/content/RelayDeclaration.test.ts index 645628d8..426ffc94 100644 --- a/src/cli/plugins/content/RelayDeclaration.test.ts +++ b/src/cli/plugins/content/RelayDeclaration.test.ts @@ -143,6 +143,7 @@ describe("Relay declarations", () => { "RelayFramesResult", "RelayMethod", "RelayNonEmptyReadonlyArray", + "RelayProtocolError", "RelayProxyTarget", "RelayResultTarget", "RelayScalarOptions", diff --git a/src/main/relay.ts b/src/main/relay.ts index a986dff1..1ee67be1 100644 --- a/src/main/relay.ts +++ b/src/main/relay.ts @@ -20,6 +20,7 @@ import { export {RelayAllFrames, RelayFrameErrorKind, RelayMethod}; export {RelayDiscoveryError} from "@relay/discovery/RelayDiscovery"; +export {RelayProtocolError} from "@relay/result"; export type { RelayAllFramesOptions, diff --git a/src/relay/README.md b/src/relay/README.md index bc155ee3..46b547c2 100644 --- a/src/relay/README.md +++ b/src/relay/README.md @@ -78,7 +78,7 @@ The local accessor is not a way to address another frame. The remote accessor re ### Export ownership -- [`adnbn`](../main/relay.ts) exports `defineRelay`, the remote `getRelay`, and the public definition, call-target, proxy, result, and error contracts. Import `RelayAllFrames`, `RelayMethod`, `RelayFrameErrorKind`, and `RelayDiscoveryError` here. +- [`adnbn`](../main/relay.ts) exports `defineRelay`, the remote `getRelay`, and the public definition, call-target, proxy, result, and error contracts. Import `RelayAllFrames`, `RelayMethod`, `RelayFrameErrorKind`, `RelayDiscoveryError`, and `RelayProtocolError` here. - [`adnbn/relay`](./index.ts) exports only the local `getRelay` and `RelayRegistry`, `RelayName`, and `RelayTarget` types. The generated `.adnbn/relay.d.ts` augments this registry; both accessors derive their types from it. It does not redeclare accessor overloads. - [`adnbn/entry/relay`](../entry/relay/index.ts) is the internal bootstrap interface: `Builder`, `resolveDefinition`, the default `relay(definition, contentBuilder)` startup function, and `RelayUnresolvedDefinition`. The unresolved type represents merged runtime input, not the public `defineRelay` contract. @@ -296,21 +296,47 @@ Permission prompts, frame discovery, startup retries, and work inside the remote ### Verification -Run from the framework repository root. Build first so targeted tests can resolve the package's public exports: +Run from the framework repository root. `test:relay` builds the package and runs Relay unit, build, type and browser tests; it requires local Chrome and Firefox: ```bash -npm run build npm run typecheck npm run test:relay -- --runInBand npm run test:message -- --runInBand ``` +For Relay runtime unit tests without a build or browsers: + +```bash +npm run test:run -- --selectProjects unit-node unit-dom --testPathPatterns="src/relay/|src/entry/relay/" --runInBand +``` + `typecheck` checks both source and test files; passing Jest alone does not prove test files are type-correct. For the full non-browser regression suite, including shared transport and content aggregation tests: ```bash npm test -- --runInBand --testPathIgnorePatterns=tests/integration/browser ``` -Important coverage lives in [`Relay.test.ts`](./providers/Relay.test.ts), [`RelayDiscovery.test.ts`](./discovery/RelayDiscovery.test.ts), [`RelayPermission.test.ts`](./RelayPermission.test.ts), [`RelayParser.test.ts`](../cli/entrypoint/parser/RelayParser.test.ts), [`RelayDriver.test.ts`](../cli/plugins/content/RelayDriver.test.ts), [`RelayDeclaration.test.ts`](../cli/plugins/content/RelayDeclaration.test.ts), and [`ContentManager.test.ts`](../cli/plugins/content/ContentManager.test.ts). +Important coverage lives in [`Relay.test.ts`](./providers/Relay.test.ts), [`RelayScriptingAdapter.test.ts`](./adapters/RelayScriptingAdapter.test.ts), [`RelayDiscovery.test.ts`](./discovery/RelayDiscovery.test.ts), [`RelayPermission.test.ts`](./RelayPermission.test.ts), [`RelayParser.test.ts`](../cli/entrypoint/parser/RelayParser.test.ts), [`RelayDriver.test.ts`](../cli/plugins/content/RelayDriver.test.ts), [`RelayDeclaration.test.ts`](../cli/plugins/content/RelayDeclaration.test.ts), and [`ContentManager.test.ts`](../cli/plugins/content/ContentManager.test.ts). Keep regression cases for a rejected top frame plus successful iframe in Scripting `Any`, no manager retries in `Any`, partial explicit-batch failures, remote Messaging errors, document capability rejection, and permission precedence. Native frame enumeration, injection rejection semantics, and user activation require verification in a real extension context; browser mocks cannot establish those guarantees. + +### Scripting response protocol + +The injected `invoke-relay.ts` function envelopes successful values (including an explicit no-result marker), thrown +errors, rejected promises and both missing-manager failures. `Any` fails immediately when no manager is present; +addressed calls and `All` perform ten lookups at 0, 300, …, 2700 ms. A manager registered before the final lookup can +still fulfill the call. A host `timeoutMs` is independent of this retry schedule. MV2 checks the configured host permissions for the +`tabs.executeScript` path; MV3 also checks `scripting`. + +The host adapter validates every envelope before reading its fields. Null, primitives and malformed success/error +objects become `RelayProtocolError` outcomes with the existing `execution` kind. Valid remote errors retain the +`remote` kind. A malformed frame cannot discard another frame's success in a batch or in `Any`. +Scalar calls throw the public `RelayProtocolError` class exported by `adnbn`, so callers can use `instanceof`. +Remote errors with the same name retain their remote origin and do not become instances of this class. + +Scripting adapter tests use the shared Browser harness. A physical test fixture bundles the real RegisterRelay/RelayManager +into isolated guest realms; virtual guest time drives registration retries and asynchronous methods. This bundle is +independent of `dist`. Its esbuild configuration lives in `tests/runtime.ts`; adding non-JavaScript imports to the +guest module graph requires matching loaders there. Only the injected function module is excluded from Babel coverage, because serialized functions +cannot reference host Istanbul counters. The host adapter remains covered. Real Chrome MV3 and Firefox MV2/MV3 +regressions in `tests/integration/browser/relay` verify values and missing-manager errors across the browser boundary. diff --git a/src/relay/adapters/RelayScriptingAdapter.test.ts b/src/relay/adapters/RelayScriptingAdapter.test.ts new file mode 100644 index 00000000..70e2cd50 --- /dev/null +++ b/src/relay/adapters/RelayScriptingAdapter.test.ts @@ -0,0 +1,150 @@ +import {getBrowserTest} from "@tests/browser-harness/session"; +import {RelayProtocolError} from "@main/relay"; +import {RelayAllFrames, RelayFrameErrorKind} from "@typing/relay"; +import RelayScriptingAdapter from "./RelayScriptingAdapter"; +import {createRelayRuntime} from "../tests/runtime"; + +test.each([true, RelayAllFrames.Any] as const)( + "envelopes an absent manager immediately for any-frame calls %s", + async allFrames => { + const runtime = createRelayRuntime(false); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, allFrames}); + + await expect(adapter.invoke([], "empty")).resolves.toEqual([ + expect.objectContaining({ + status: "rejected", + error: expect.objectContaining({kind: RelayFrameErrorKind.Remote, message: "Relay manager not found."}), + }), + ]); + expect(runtime.clock!.now).toBe(0); + expect(runtime.pendingExecutions).toBe(0); + } +); + +test("makes the tenth manager lookup at 2700ms, not 2699ms", async () => { + const runtime = createRelayRuntime(false); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, frameId: 0}); + const result = adapter.invoke([], "empty"); + const rejected = expect(result).rejects.toThrow("Relay manager not found after 10 attempts."); + + runtime.clock!.advance(2699); + expect(runtime.pendingExecutions).toBe(1); + runtime.clock!.advance(1); + await rejected; +}); + +test("uses a real manager registered before the final lookup", async () => { + const runtime = createRelayRuntime(false); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, frameId: 0}); + const result = adapter.invoke([1, 2], "sum"); + + runtime.clock!.advance(2699); + runtime.evaluate({documentId: "document-0"}, {source: "relayFixture.register();"}); + runtime.clock!.advance(1); + await expect(result).resolves.toBe(3); +}); + +test.each([ + null, + false, + 7, + "wrong", + [], + {}, + {ok: true}, + {ok: true, hasResult: true}, + {ok: false}, + {ok: false, error: {name: 7, message: "bad"}}, +])("rejects malformed injection envelopes: %p", async value => { + getBrowserTest().harness.scripting.executeScript.setResult([{frameId: 0, documentId: "document-0", result: value}]); + const adapter = new RelayScriptingAdapter("math", {tabId: 1}); + const result = adapter.invoke([]); + + await expect(result).rejects.toBeInstanceOf(RelayProtocolError); + await expect(result).rejects.toThrow('Relay "math" returned an invalid scripting response envelope.'); +}); + +test("does not turn a remote error with the same name into a local protocol error", async () => { + getBrowserTest().harness.scripting.executeScript.setResult([ + { + frameId: 0, + documentId: "document-0", + result: {ok: false, error: {name: "RelayProtocolError", message: "From the remote handler"}}, + }, + ]); + const adapter = new RelayScriptingAdapter("math", {tabId: 1}); + const result = adapter.invoke([]); + + await expect(result).rejects.toBeInstanceOf(Error); + await expect(result).rejects.not.toBeInstanceOf(RelayProtocolError); + await expect(result).rejects.toMatchObject({name: "RelayProtocolError", message: "From the remote handler"}); +}); + +test.each(["empty", "nullable"])("preserves a successful %s result across serialization", async method => { + createRelayRuntime(); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, frameId: 0}); + + await expect(adapter.invoke([], method)).resolves.toBe(method === "empty" ? undefined : null); +}); + +test.each(["fail", "reject"])("preserves remote error details from %s", async method => { + createRelayRuntime(); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, frameId: 0}); + + await expect(adapter.invoke([], method)).rejects.toMatchObject({ + name: method === "fail" ? "TypeError" : "RangeError", + message: method === "fail" ? "Remote failure" : "Async failure", + }); +}); + +test("isolates malformed responses within a batch", async () => { + getBrowserTest().harness.scripting.executeScript.queueResult( + [{frameId: 2, documentId: "document-2", result: null}], + [{frameId: 0, documentId: "document-0", result: {ok: true, hasResult: false}}] + ); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, frameIds: [2, 0]}); + + await expect(adapter.invoke([])).resolves.toEqual([ + {target: {tabId: 1, frameId: 0, documentId: "document-0"}, status: "fulfilled", result: undefined}, + { + target: {tabId: 1, frameId: 2, documentId: "document-2"}, + status: "rejected", + error: expect.objectContaining({ + kind: RelayFrameErrorKind.Execution, + name: "RelayProtocolError", + }), + }, + ]); +}); + +test.each([true, RelayAllFrames.Any] as const)( + "selects a valid response despite a malformed sibling with allFrames %s", + async allFrames => { + getBrowserTest().harness.scripting.executeScript.setResult([ + {frameId: 0, documentId: "document-0", result: null}, + {frameId: 2, documentId: "document-2", result: {ok: true, hasResult: true, result: 42}}, + ]); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, allFrames}); + + await expect(adapter.invoke([])).resolves.toEqual([ + {target: {tabId: 1, allFrames: RelayAllFrames.Any}, status: "fulfilled", result: 42}, + ]); + } +); + +test.each(["document", "runtime"])("rejects pending execution when its %s is removed", async owner => { + const runtime = createRelayRuntime(); + const adapter = new RelayScriptingAdapter("math", {tabId: 1, frameId: 0}); + const rejected = expect(adapter.invoke([1, 2], "asyncSum")).rejects.toThrow(/removed|disposed|cancel/i); + + expect(runtime.pendingExecutions).toBe(1); + + if (owner === "document") { + getBrowserTest().harness.contexts.documents.remove("document-0"); + } else { + runtime.dispose(); + } + + await rejected; + expect(runtime.pendingExecutions).toBe(0); +}); diff --git a/src/relay/adapters/RelayScriptingAdapter.ts b/src/relay/adapters/RelayScriptingAdapter.ts index e2e52e60..db2df494 100644 --- a/src/relay/adapters/RelayScriptingAdapter.ts +++ b/src/relay/adapters/RelayScriptingAdapter.ts @@ -9,7 +9,6 @@ import injectScriptFactory, { import {restoreError} from "@message/error"; -import type {MessageError} from "@typing/message"; import { RelayAllFrames, RelayFrameErrorKind, @@ -22,23 +21,16 @@ import { type RelayScalarOptions, } from "@typing/relay"; -import {fulfilledRelayFrame, injectScriptErrorKind, rejectedRelayFrame, sortRelayFrameResults} from "../result"; +import { + RelayProtocolError, + fulfilledRelayFrame, + injectScriptErrorKind, + rejectedRelayFrame, + sortRelayFrameResults, +} from "../result"; import RelayAdapter from "./RelayAdapter"; - -type RelayInvocationResult = - | { - ok: true; - hasResult: false; - } - | { - ok: true; - hasResult: true; - result: any; - } - | { - ok: false; - error: MessageError; - }; +import {invokeRelay} from "./invoke-relay"; +import type {RelayInvocationResult} from "./scripting-response"; export default class RelayScriptingAdapter extends RelayAdapter { private _injectScript?: InjectScriptContract; @@ -52,96 +44,6 @@ export default class RelayScriptingAdapter extends RelayAdapter { } public async invoke(args: any[], path?: string): Promise { - const func = ( - name: string, - path: string | null, - args: JsonValue[], - key: string, - retryManager: boolean - ): Promise => { - const serialize = (error: unknown): MessageError => { - if (error instanceof Error) { - return { - name: error.name, - message: error.message, - ...(error.stack ? {stack: error.stack} : {}), - }; - } - - if (typeof error === "object" && error !== null) { - const record = error as Record; - - return { - name: typeof record.name === "string" ? record.name : "Error", - message: - typeof record.message === "string" - ? record.message - : (() => { - try { - return JSON.stringify(error) ?? String(error); - } catch { - return String(error); - } - })(), - ...(typeof record.stack === "string" ? {stack: record.stack} : {}), - }; - } - - return {name: "Error", message: String(error)}; - }; - - const invoke = (manager: any): Promise => { - try { - return Promise.resolve(manager.property(name, {path, args})).then( - result => - result === undefined - ? {ok: true as const, hasResult: false as const} - : {ok: true as const, hasResult: true as const, result}, - error => ({ok: false as const, error: serialize(error)}) - ); - } catch (error) { - return Promise.resolve({ok: false as const, error: serialize(error)}); - } - }; - - const manager = globalThis[key]; - - if (manager) { - // Keep the normal Scripting path synchronous until the remote method starts. - return invoke(manager); - } - - if (!retryManager) { - return Promise.reject(new Error("Relay manager not found.")); - } - - return new Promise((resolve, reject) => { - const maxAttempts = 10; - const delay = 300; - let attempts = 0; - - const findManager = () => { - const delayedManager = globalThis[key]; - - if (delayedManager) { - resolve(invoke(delayedManager)); - return; - } - - attempts++; - - if (attempts >= maxAttempts) { - reject(new Error(`Relay manager not found after ${maxAttempts} attempts.`)); - return; - } - - setTimeout(findManager, delay); - }; - - findManager(); - }); - }; - const scriptArgs: [string, string | null, JsonValue[], string, boolean] = [ this.name, path ?? null, @@ -149,7 +51,7 @@ export default class RelayScriptingAdapter extends RelayAdapter { RelayGlobalKey, !this.isAnyFramesTarget(), ]; - const outcomes = await this.injectScript.run(func, scriptArgs); + const outcomes = await this.injectScript.run(invokeRelay, scriptArgs); const results = this.normalize(outcomes); if (this.isAnyFramesTarget()) { @@ -172,7 +74,7 @@ export default class RelayScriptingAdapter extends RelayAdapter { return this.unwrap(results); } - private normalize(outcomes: readonly InjectScriptResult[]): RelayFramesResult { + private normalize(outcomes: readonly InjectScriptResult[]): RelayFramesResult { return sortRelayFrameResults( outcomes.map(outcome => { const target = this.resultTarget(outcome.target); @@ -181,6 +83,16 @@ export default class RelayScriptingAdapter extends RelayAdapter { return rejectedRelayFrame(target, outcome.error, injectScriptErrorKind(outcome.error.kind)); } + if (!this.isResponse(outcome.value)) { + const error = new RelayProtocolError(this.name); + + if (!this.isBatchTarget(this.target)) { + throw error; + } + + return rejectedRelayFrame(target, error, RelayFrameErrorKind.Execution); + } + if (!outcome.value.ok) { return rejectedRelayFrame(target, outcome.value.error, RelayFrameErrorKind.Remote); } @@ -190,6 +102,37 @@ export default class RelayScriptingAdapter extends RelayAdapter { ); } + private isResponse(value: unknown): value is RelayInvocationResult { + if (typeof value !== "object" || value === null || Array.isArray(value)) { + return false; + } + + const response = value as Record; + + if (response.ok === true) { + return response.hasResult === false + ? !Object.hasOwn(response, "result") + : response.hasResult === true && Object.hasOwn(response, "result"); + } + + if ( + response.ok !== false || + typeof response.error !== "object" || + response.error === null || + Array.isArray(response.error) + ) { + return false; + } + + const error = response.error as Record; + + return ( + typeof error.name === "string" && + typeof error.message === "string" && + (error.stack === undefined || typeof error.stack === "string") + ); + } + private resultTarget(target: InjectScriptResultTarget): RelayResultTarget { if (target.allFrames === true) { return { diff --git a/src/relay/adapters/invoke-relay.ts b/src/relay/adapters/invoke-relay.ts new file mode 100644 index 00000000..cf01412b --- /dev/null +++ b/src/relay/adapters/invoke-relay.ts @@ -0,0 +1,100 @@ +import type {JsonValue} from "@addon-core/inject-script"; +import type {MessageError} from "@typing/message"; +import type {RelayInvocationResult} from "./scripting-response"; + +// Serialized by scripting.executeScript: all runtime dependencies must stay inside this function. +export const invokeRelay = ( + name: string, + path: string | null, + args: JsonValue[], + key: string, + retryManager: boolean +): Promise => { + const serialize = (error: unknown): MessageError => { + if (error instanceof Error) { + return { + name: error.name, + message: error.message, + ...(error.stack ? {stack: error.stack} : {}), + }; + } + + if (typeof error === "object" && error !== null) { + const record = error as Record; + + return { + name: typeof record.name === "string" ? record.name : "Error", + message: + typeof record.message === "string" + ? record.message + : (() => { + try { + return JSON.stringify(error) ?? String(error); + } catch { + return String(error); + } + })(), + ...(typeof record.stack === "string" ? {stack: record.stack} : {}), + }; + } + + return {name: "Error", message: String(error)}; + }; + + const invoke = (manager: any): Promise => { + try { + return Promise.resolve(manager.property(name, {path, args})).then( + result => + result === undefined + ? {ok: true as const, hasResult: false as const} + : {ok: true as const, hasResult: true as const, result}, + error => ({ok: false as const, error: serialize(error)}) + ); + } catch (error) { + return Promise.resolve({ok: false as const, error: serialize(error)}); + } + }; + + const manager = globalThis[key]; + + if (manager) { + // Keep the normal Scripting path synchronous until the remote method starts. + return invoke(manager); + } + + if (!retryManager) { + return Promise.resolve({ok: false, error: serialize(new Error("Relay manager not found."))}); + } + + return new Promise(resolve => { + const maxAttempts = 10; + const delay = 300; + // The synchronous lookup above is the first attempt. + let attempts = 1; + + const findManager = () => { + const delayedManager = globalThis[key]; + + if (delayedManager) { + resolve(invoke(delayedManager)); + + return; + } + + attempts++; + + if (attempts >= maxAttempts) { + resolve({ + ok: false, + error: serialize(new Error(`Relay manager not found after ${maxAttempts} attempts.`)), + }); + + return; + } + + setTimeout(findManager, delay); + }; + + setTimeout(findManager, delay); + }); +}; diff --git a/src/relay/adapters/scripting-response.ts b/src/relay/adapters/scripting-response.ts new file mode 100644 index 00000000..841508e6 --- /dev/null +++ b/src/relay/adapters/scripting-response.ts @@ -0,0 +1,16 @@ +import type {MessageError} from "@typing/message"; + +export type RelayInvocationResult = + | { + ok: true; + hasResult: false; + } + | { + ok: true; + hasResult: true; + result: any; + } + | { + ok: false; + error: MessageError; + }; diff --git a/src/relay/providers/Relay.test.ts b/src/relay/providers/Relay.test.ts index 4ea8892d..c6fb1b7e 100644 --- a/src/relay/providers/Relay.test.ts +++ b/src/relay/providers/Relay.test.ts @@ -358,9 +358,9 @@ describe("ProxyRelay", () => { expect(setTimeoutSpy).not.toHaveBeenCalled(); setTimeoutSpy.mockRestore(); - const error = await execution.catch((error: unknown) => error); + const result = await execution; - return [{frameId: 0, error}]; + return [{frameId: 0, result}]; }); const relay = createProxyRelay(options, { tabId: 1, @@ -372,7 +372,7 @@ describe("ProxyRelay", () => { target: {tabId: 1, allFrames: RelayAllFrames.Any}, status: "rejected", error: expect.objectContaining({ - kind: RelayFrameErrorKind.Execution, + kind: RelayFrameErrorKind.Remote, message: "Relay manager not found.", }), }, diff --git a/src/relay/result.ts b/src/relay/result.ts index b72ca9e1..544569b7 100644 --- a/src/relay/result.ts +++ b/src/relay/result.ts @@ -16,6 +16,13 @@ export class RelayFrameTimeoutError extends Error { } } +export class RelayProtocolError extends Error { + public constructor(relayName: string) { + super(`Relay "${relayName}" returned an invalid scripting response envelope.`); + this.name = "RelayProtocolError"; + } +} + export const fulfilledRelayFrame = (target: RelayResultTarget, result: T): RelayFrameResult => ({ target, status: "fulfilled", diff --git a/src/relay/tests/fixtures/runtime.ts b/src/relay/tests/fixtures/runtime.ts new file mode 100644 index 00000000..c091fe16 --- /dev/null +++ b/src/relay/tests/fixtures/runtime.ts @@ -0,0 +1,29 @@ +import RelayManager from "@relay/RelayManager"; +import RegisterRelay from "@relay/providers/RegisterRelay"; +import {RelayMethod} from "@typing/relay"; + +const math = { + sum: (a: number, b: number): number => a + b, + asyncSum: (a: number, b: number): Promise => new Promise(resolve => setTimeout(() => resolve(a + b), 100)), + activation: (): boolean => { + globalThis.relayFixture.started = true; + + return true; + }, + fail: (): never => { + throw new TypeError("Remote failure"); + }, + reject: (): Promise => Promise.reject(new RangeError("Async failure")), + empty: (): void => {}, + nullable: (): null => null, + one: 1, + obj: {concat: (a: string, b: string): string => `${a} ${b}`, zero: 0}, +}; + +globalThis.relayFixture = { + started: false, + register: () => new RegisterRelay("math", RelayMethod.Scripting, () => math).register(), + has: () => RelayManager.getInstance().has("math"), +}; + +export type RelayFixture = typeof math; diff --git a/src/relay/tests/runtime.ts b/src/relay/tests/runtime.ts new file mode 100644 index 00000000..ffca838a --- /dev/null +++ b/src/relay/tests/runtime.ts @@ -0,0 +1,42 @@ +import path from "node:path"; +import {buildSync} from "esbuild"; +import type {NodeScriptRuntime} from "@addon-core/browser/testing/node"; +import {createTabFixture} from "@addon-core/browser/testing"; +import {getBrowserTest} from "@tests/browser-harness/session"; + +const source = buildSync({ + entryPoints: [path.resolve(__dirname, "fixtures/runtime.ts")], + tsconfig: path.resolve(__dirname, "../../../tsconfig.json"), + bundle: true, + write: false, + format: "iife", + platform: "browser", + target: "es2020", +}).outputFiles[0].text; + +export function createRelayRuntime(register = true): NodeScriptRuntime { + const session = getBrowserTest(); + + session.harness.tabs.set([createTabFixture({id: 1})]); + + for (const frameId of [0, 2]) { + session.harness.contexts.documents.create({ + documentId: `document-${frameId}`, + tabId: 1, + frameId, + url: "https://example.com/", + }); + } + + const runtime = session.createScriptRuntime({clock: true}); + + for (const frameId of [0, 2]) { + runtime.evaluate({documentId: `document-${frameId}`}, {source}); + + if (register) { + runtime.evaluate({documentId: `document-${frameId}`}, {source: "relayFixture.register();"}); + } + } + + return runtime; +} diff --git a/tests/README.md b/tests/README.md index a5e890ed..b8e4d685 100644 --- a/tests/README.md +++ b/tests/README.md @@ -106,8 +106,9 @@ not prove that every reset implementation is correct, so session lifecycle tests ### Remaining test-kit boundaries - `hasListeners` is not modeled. Check subscriptions through the context event's `listenerCount()`. -- VM scenarios require uninstrumented injected code. During Relay migration, isolate the injected function and exclude - only that module from Babel coverage; retain coverage for its host adapter and real-browser checks for the guest code. +- VM scenarios require uninstrumented injected code. Only `src/relay/adapters/invoke-relay.ts` is excluded from Babel + coverage in every Jest project. Its host adapter remains instrumented; VM tests run the real bundled Relay manager, + and Chrome MV3 plus Firefox MV2/MV3 integrations check the injected function in actual extensions. - The session does not emulate Web Locks, DOM inside the guest, or extension HTML execution. Keep explicit external adapters where needed and real-browser checks for DOM, execution worlds, browser lifecycle and vendor behavior. - Consult the kit's `RAW_CAPABILITY_COVERAGE` before using another browser API. Extend the kit when a required capability diff --git a/tests/integration/README.md b/tests/integration/README.md index 9104ae0f..d7f2054a 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -32,6 +32,11 @@ tests/integration/ │ │ └── views/ │ └── … ├── browser/ +│ ├── relay/ +│ │ ├── scripting.integration.test.ts +│ │ ├── scripting.firefox.integration.test.ts +│ │ ├── scripting-utils.ts +│ │ └── scripting/ │ ├── content/ │ │ ├── entrypoint-assets.integration.test.ts │ │ ├── entrypoint-assets.firefox.integration.test.ts @@ -150,3 +155,5 @@ Browser execution covers the installed Chrome in MV3 and Firefox in MV2/MV3. It `browser/content/entrypoint-assets/site` is the ordinary website receiving the content scripts, not an extension entrypoint. A local HTTP server serves `top.html`, `frames.html`, and `child.html` with a restrictive CSP: `default-src 'none'; frame-src 'self'; img-src 'self'`. Same-origin image requests allow the browser's automatic favicon request, which receives an empty 204 response; scripts and styles remain restricted by `default-src`. The frame pages exercise `allFrames` and independent execution in the top document and its child iframe. Keep these HTML files outside the application's `src` directory. They need no separate package or build tool. The server uses an ephemeral loopback port and stops after the test. + +- `browser/relay/scripting`: Chrome MV3 and Firefox MV2/MV3 check real Relay scripting success, undefined/null results, remote errors, immediate missing-manager errors and exhausted retries (#109). diff --git a/tests/integration/browser/relay/scripting-utils.ts b/tests/integration/browser/relay/scripting-utils.ts new file mode 100644 index 00000000..21a0e8c0 --- /dev/null +++ b/tests/integration/browser/relay/scripting-utils.ts @@ -0,0 +1,43 @@ +import path from "node:path"; +import {createIntegrationFixture} from "@tests/integration/utils/fixture"; +import {startBrowserSession} from "@tests/integration/browser/utils/session"; +import {startIntegrationSite} from "@tests/integration/browser/utils/site"; +import {waitFor} from "@tests/integration/browser/utils/browser"; + +type BrowserSession = Awaited>; + +export async function expectRelayScripting(browser: "chrome" | "firefox", manifestVersion: 2 | 3): Promise { + const root = path.resolve(__dirname, "../../../.."); + const fixture = await createIntegrationFixture(root, path.join(__dirname, "scripting")); + const site = await startIntegrationSite(path.join(fixture.directory, "site"), null); + let session: BrowserSession | undefined; + + try { + const extension = await fixture.build({browser, manifestVersion}); + + session = await startBrowserSession(browser, root, extension); + await session.navigate(`${site.origin}/top.html`); + + const result = await waitFor(async () => { + const value = await session!.evaluate("document.body?.dataset.result"); + + return value ? JSON.parse(value) : undefined; + }).catch(error => { + throw new Error(`${error}; browser errors: ${JSON.stringify(session!.errors)}`); + }); + + expect(result).toEqual({ + empty: true, + nullable: true, + thrown: {name: "TypeError", message: "Remote failure"}, + rejected: {name: "RangeError", message: "Async failure"}, + any: [{status: "rejected", kind: "remote", name: "Error", message: "Relay manager not found."}], + missing: {name: "Error", message: "Relay manager not found after 10 attempts."}, + }); + expect(session.errors).toEqual([]); + } finally { + await session?.close(); + await site.close(); + await fixture.dispose(); + } +} diff --git a/tests/integration/browser/relay/scripting.firefox.integration.test.ts b/tests/integration/browser/relay/scripting.firefox.integration.test.ts new file mode 100644 index 00000000..4533bca7 --- /dev/null +++ b/tests/integration/browser/relay/scripting.firefox.integration.test.ts @@ -0,0 +1,6 @@ +import {expectRelayScripting} from "./scripting-utils"; + +jest.setTimeout(90_000); +test.each([2, 3] as const)("Firefox MV%s preserves Relay scripting responses and missing-manager errors", version => + expectRelayScripting("firefox", version) +); diff --git a/tests/integration/browser/relay/scripting.integration.test.ts b/tests/integration/browser/relay/scripting.integration.test.ts new file mode 100644 index 00000000..811ab25a --- /dev/null +++ b/tests/integration/browser/relay/scripting.integration.test.ts @@ -0,0 +1,5 @@ +import {expectRelayScripting} from "./scripting-utils"; + +jest.setTimeout(90_000); +test("Chrome MV3 preserves Relay scripting responses and missing-manager errors", () => + expectRelayScripting("chrome", 3)); diff --git a/tests/integration/browser/relay/scripting/adnbn.config.ts b/tests/integration/browser/relay/scripting/adnbn.config.ts new file mode 100644 index 00000000..0afafe19 --- /dev/null +++ b/tests/integration/browser/relay/scripting/adnbn.config.ts @@ -0,0 +1,8 @@ +import {defineConfig} from "adnbn"; + +export default defineConfig({ + name: "Relay scripting protocol", + version: "1.0.0", + manifest: {permissions: ["tabs"], host_permissions: ["http://127.0.0.1/*"]}, + specific: {gecko: {id: "relay-scripting@adnbn.test"}}, +}); diff --git a/tests/integration/browser/relay/scripting/package.json b/tests/integration/browser/relay/scripting/package.json new file mode 100644 index 00000000..22aa8402 --- /dev/null +++ b/tests/integration/browser/relay/scripting/package.json @@ -0,0 +1,9 @@ +{ + "name": "adnbn-test-relay-scripting", + "private": true, + "type": "module", + "version": "1.0.0", + "dependencies": { + "adnbn": "file:../../../../.." + } +} diff --git a/tests/integration/browser/relay/scripting/site/empty.html b/tests/integration/browser/relay/scripting/site/empty.html new file mode 100644 index 00000000..d0d0cd31 --- /dev/null +++ b/tests/integration/browser/relay/scripting/site/empty.html @@ -0,0 +1,6 @@ + + + + No Relay registration + + diff --git a/tests/integration/browser/relay/scripting/site/top.html b/tests/integration/browser/relay/scripting/site/top.html new file mode 100644 index 00000000..e60ecf47 --- /dev/null +++ b/tests/integration/browser/relay/scripting/site/top.html @@ -0,0 +1,6 @@ + + + + Relay scripting probe + + diff --git a/tests/integration/browser/relay/scripting/src/background.ts b/tests/integration/browser/relay/scripting/src/background.ts new file mode 100644 index 00000000..46a0a8a7 --- /dev/null +++ b/tests/integration/browser/relay/scripting/src/background.ts @@ -0,0 +1,95 @@ +import {createTab, getTab, removeTab, queryTabs, onTabUpdated} from "adnbn/browser"; +import {defineBackground, getRelay, RelayAllFrames} from "adnbn"; + +const errorDetails = (error: unknown) => { + if (error instanceof Error) { + return {name: error.name, message: error.message}; + } + + throw error; +}; + +export default defineBackground({ + main() { + const started = new Set(); + const run = (tabId: number, url?: string) => { + if (!url?.startsWith("http://127.0.0.1:") || !url.endsWith("/top.html") || started.has(tabId)) { + return; + } + + started.add(tabId); + + void (async () => { + const relay = getRelay("probe", tabId); + + await relay.ready(); + + const empty = await relay.empty(); + const nullable = await relay.nullable(); + const thrown = await relay.fail().catch(errorDetails); + const rejected = await relay.reject().catch(errorDetails); + const emptyUrl = url.replace("/top.html", "/empty.html"); + const blank = await createTab({url: emptyUrl, active: true}); + + try { + const deadline = Date.now() + 10000; + + while (true) { + const current = await getTab(blank.id!); + + if (current.status === "complete" && current.url === emptyUrl) { + break; + } + + if (Date.now() >= deadline) { + throw new Error("Empty Relay target did not finish navigation"); + } + + await new Promise(resolve => setTimeout(resolve, 20)); + } + + const any = await getRelay("probe", {tabId: blank.id!, allFrames: RelayAllFrames.Any}).ready(); + const missing = await getRelay("probe", blank.id!).ready().catch(errorDetails); + + await relay.report({ + empty: empty === undefined, + nullable: nullable === null, + thrown, + rejected, + any: any.map(outcome => + outcome.status === "rejected" + ? { + status: outcome.status, + kind: outcome.error.kind, + name: outcome.error.name, + message: outcome.error.message, + } + : {status: outcome.status} + ), + missing, + }); + } finally { + await removeTab(blank.id!); + } + })().catch(async error => { + await getRelay("probe", tabId) + .report({error: String(error)}) + .catch(() => {}); + console.error("Relay scripting probe failed", error); + }); + }; + + onTabUpdated((tabId, change, tab) => { + if (change.status === "complete") { + run(tabId, tab.url); + } + }); + void queryTabs({}).then(tabs => { + for (const tab of tabs) { + if (tab.id !== undefined) { + run(tab.id, tab.url); + } + } + }); + }, +}); diff --git a/tests/integration/browser/relay/scripting/src/probe.relay.ts b/tests/integration/browser/relay/scripting/src/probe.relay.ts new file mode 100644 index 00000000..123ecc99 --- /dev/null +++ b/tests/integration/browser/relay/scripting/src/probe.relay.ts @@ -0,0 +1,21 @@ +import {defineRelay, RelayMethod} from "adnbn"; + +export default defineRelay({ + name: "probe", + method: RelayMethod.Scripting, + declarative: true, + matches: ["http://127.0.0.1/*"], + excludeMatches: ["http://127.0.0.1/empty.html"], + init: () => ({ + ready: () => true, + empty: (): void => {}, + nullable: (): null => null, + fail: (): never => { + throw new TypeError("Remote failure"); + }, + reject: (): Promise => Promise.reject(new RangeError("Async failure")), + report: (result: object): void => { + document.body.dataset.result = JSON.stringify(result); + }, + }), +}); diff --git a/tests/integration/browser/relay/scripting/tsconfig.json b/tests/integration/browser/relay/scripting/tsconfig.json new file mode 100644 index 00000000..983db6c9 --- /dev/null +++ b/tests/integration/browser/relay/scripting/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "./.adnbn/tsconfig.json", + "include": [ + "src", + ".adnbn/**/*.d.ts", + "adnbn.config.ts" + ] +} From dc98d0668510ff878666658cdab301c377571ded Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Thu, 24 Sep 2026 16:08:44 +0300 Subject: [PATCH 06/36] test(relay): migrate remaining suites to browser harness --- src/entry/relay/Builder.test.ts | 12 +- src/entry/relay/index.test.ts | 14 +- src/relay/README.md | 4 +- src/relay/discovery/RelayDiscovery.test.ts | 29 +- .../{Relay.test.ts => ProxyRelay.test.ts} | 366 +++++------------- src/relay/providers/RegisterRelay.test.ts | 72 ++++ tests/README.md | 4 +- tests/browser-harness/migration.json | 4 - tests/jest-legacy.setup.ts | 1 - tests/jest-relay.setup.ts | 43 -- 10 files changed, 202 insertions(+), 347 deletions(-) rename src/relay/providers/{Relay.test.ts => ProxyRelay.test.ts} (58%) create mode 100644 src/relay/providers/RegisterRelay.test.ts delete mode 100644 tests/jest-relay.setup.ts diff --git a/src/entry/relay/Builder.test.ts b/src/entry/relay/Builder.test.ts index ae36088d..0cf53fb5 100644 --- a/src/entry/relay/Builder.test.ts +++ b/src/entry/relay/Builder.test.ts @@ -2,23 +2,17 @@ import Builder from "./Builder"; import VanillaBuilder from "../content/adapters/vanilla/Builder"; import MountBuilder from "../content/lifecycle/MountBuilder"; import RelayManager from "@relay/RelayManager"; -import {isRelayContext} from "@relay/utils"; import {RelayAllFrames, RelayMethod, type RelayOptions} from "@typing/relay"; import type {ContentScriptDefinition} from "@typing/content"; -// Extend the shared random-ID mock for content marker attributes. +// Keep content marker IDs deterministic for lifecycle assertions. jest.mock("nanoid", () => ({nanoid: jest.fn(() => "mocked-id"), customAlphabet: () => () => "relaymarker"})); describe("Relay Builder", () => { - const manager = RelayManager.getInstance(); + let manager: ReturnType; beforeEach(() => { - // The shared harness mocks context detection; exercise its actual predicate here. - jest.mocked(isRelayContext).mockImplementation( - jest.requireActual("@relay/utils").isRelayContext - ); - - manager.clear(); + manager = RelayManager.getInstance(); }); afterEach(() => { diff --git a/src/entry/relay/index.test.ts b/src/entry/relay/index.test.ts index 6ea77c71..c9198647 100644 --- a/src/entry/relay/index.test.ts +++ b/src/entry/relay/index.test.ts @@ -1,14 +1,13 @@ import relay, {resolveDefinition} from "./index"; import VanillaBuilder from "../content/adapters/vanilla/Builder"; import RelayManager from "@relay/RelayManager"; -import {isRelayContext} from "@relay/utils"; import {RelayMethod} from "@typing/relay"; import type {ContentScriptDefinition} from "@typing/content"; jest.mock("nanoid", () => ({nanoid: jest.fn(() => "mocked-id"), customAlphabet: () => () => "relaymarker"})); describe("Relay startup", () => { - const manager = RelayManager.getInstance(); + let manager: ReturnType; let content: VanillaBuilder | undefined; class ContentBuilder extends VanillaBuilder { @@ -19,11 +18,7 @@ describe("Relay startup", () => { } beforeEach(() => { - // Use the real predicate through the existing context mock in the shared harness. - jest.mocked(isRelayContext).mockImplementation( - jest.requireActual("@relay/utils").isRelayContext - ); - manager.clear(); + manager = RelayManager.getInstance(); content = undefined; }); @@ -80,7 +75,10 @@ describe("Relay startup", () => { { method: RelayMethod.Scripting, init: () => { - if (phase === "init") throw error; + if (phase === "init") { + throw error; + } + return {ready: true}; }, main: async () => { diff --git a/src/relay/README.md b/src/relay/README.md index 46b547c2..afbc1a05 100644 --- a/src/relay/README.md +++ b/src/relay/README.md @@ -316,7 +316,7 @@ npm run test:run -- --selectProjects unit-node unit-dom --testPathPatterns="src/ npm test -- --runInBand --testPathIgnorePatterns=tests/integration/browser ``` -Important coverage lives in [`Relay.test.ts`](./providers/Relay.test.ts), [`RelayScriptingAdapter.test.ts`](./adapters/RelayScriptingAdapter.test.ts), [`RelayDiscovery.test.ts`](./discovery/RelayDiscovery.test.ts), [`RelayPermission.test.ts`](./RelayPermission.test.ts), [`RelayParser.test.ts`](../cli/entrypoint/parser/RelayParser.test.ts), [`RelayDriver.test.ts`](../cli/plugins/content/RelayDriver.test.ts), [`RelayDeclaration.test.ts`](../cli/plugins/content/RelayDeclaration.test.ts), and [`ContentManager.test.ts`](../cli/plugins/content/ContentManager.test.ts). +Important coverage lives in [`ProxyRelay.test.ts`](./providers/ProxyRelay.test.ts), [`RegisterRelay.test.ts`](./providers/RegisterRelay.test.ts), [`RelayScriptingAdapter.test.ts`](./adapters/RelayScriptingAdapter.test.ts), [`RelayDiscovery.test.ts`](./discovery/RelayDiscovery.test.ts), [`RelayPermission.test.ts`](./RelayPermission.test.ts), [`RelayParser.test.ts`](../cli/entrypoint/parser/RelayParser.test.ts), [`RelayDriver.test.ts`](../cli/plugins/content/RelayDriver.test.ts), [`RelayDeclaration.test.ts`](../cli/plugins/content/RelayDeclaration.test.ts), and [`ContentManager.test.ts`](../cli/plugins/content/ContentManager.test.ts). Keep regression cases for a rejected top frame plus successful iframe in Scripting `Any`, no manager retries in `Any`, partial explicit-batch failures, remote Messaging errors, document capability rejection, and permission precedence. Native frame enumeration, injection rejection semantics, and user activation require verification in a real extension context; browser mocks cannot establish those guarantees. @@ -334,7 +334,7 @@ objects become `RelayProtocolError` outcomes with the existing `execution` kind. Scalar calls throw the public `RelayProtocolError` class exported by `adnbn`, so callers can use `instanceof`. Remote errors with the same name retain their remote origin and do not become instances of this class. -Scripting adapter tests use the shared Browser harness. A physical test fixture bundles the real RegisterRelay/RelayManager +Relay unit tests use the shared Browser harness. A physical test fixture bundles the real RegisterRelay/RelayManager into isolated guest realms; virtual guest time drives registration retries and asynchronous methods. This bundle is independent of `dist`. Its esbuild configuration lives in `tests/runtime.ts`; adding non-JavaScript imports to the guest module graph requires matching loaders there. Only the injected function module is excluded from Babel coverage, because serialized functions diff --git a/src/relay/discovery/RelayDiscovery.test.ts b/src/relay/discovery/RelayDiscovery.test.ts index 5e54cdf7..108f25fc 100644 --- a/src/relay/discovery/RelayDiscovery.test.ts +++ b/src/relay/discovery/RelayDiscovery.test.ts @@ -1,10 +1,8 @@ -import {getAllFrames, getManifest} from "@addon-core/browser"; +import {getManifest} from "@addon-core/browser"; +import {getBrowserTest} from "@tests/browser-harness/session"; import RelayDiscovery, {RelayDiscoveryError} from "./RelayDiscovery"; -const mockedGetAllFrames = getAllFrames as jest.MockedFunction; -const mockedGetManifest = getManifest as jest.MockedFunction; - const manifest = { manifest_version: 3, name: "Relay test", @@ -12,11 +10,10 @@ const manifest = { } satisfies ReturnType; describe("RelayDiscovery", () => { + const frames = () => getBrowserTest().harness.configurable.chrome.webNavigation.getAllFrames; + beforeEach(() => { - jest.clearAllMocks(); - mockedGetAllFrames.mockReset(); - mockedGetManifest.mockReset(); - mockedGetManifest.mockReturnValue({...manifest, permissions: []}); + getBrowserTest().harness.runtime.setManifest({...manifest, permissions: []}); }); test("requires webNavigation for strict Messaging allFrames discovery", async () => { @@ -28,11 +25,11 @@ describe("RelayDiscovery", () => { message: expect.stringContaining('requires the "webNavigation" permission'), }) ); - expect(mockedGetAllFrames).not.toHaveBeenCalled(); + expect(frames().calls).toHaveLength(0); }); test("reports an unavailable manifest instead of silently degrading discovery", async () => { - mockedGetManifest.mockImplementation(() => { + getBrowserTest().harness.runtime.getManifest.setImplementation(() => { throw new Error("runtime.getManifest is unavailable"); }); const discovery = new RelayDiscovery(); @@ -44,11 +41,11 @@ describe("RelayDiscovery", () => { cause: expect.objectContaining({message: "runtime.getManifest is unavailable"}), }) ); - expect(mockedGetAllFrames).not.toHaveBeenCalled(); + expect(frames().calls).toHaveLength(0); }); test("returns deterministic unique targets through webNavigation", async () => { - mockedGetManifest.mockReturnValue({...manifest, permissions: ["webNavigation"]}); + getBrowserTest().harness.runtime.setManifest({...manifest, permissions: ["webNavigation"]}); const topFrame: chrome.webNavigation.GetAllFrameResultDetails = { frameId: 0, documentId: "document-0", @@ -68,19 +65,19 @@ describe("RelayDiscovery", () => { parentDocumentId: "document-0", url: "https://example.com/frame", }; - mockedGetAllFrames.mockResolvedValue([childFrame, topFrame, childFrame]); + frames().setResult([childFrame, topFrame, childFrame]); const discovery = new RelayDiscovery(); await expect(discovery.discover(5)).resolves.toEqual([ {tabId: 5, frameId: 0, documentId: "document-0"}, {tabId: 5, frameId: 2, documentId: "document-2"}, ]); - expect(mockedGetAllFrames).toHaveBeenCalledWith(5); + expect(frames().calls[0].args).toEqual([{tabId: 5}]); }); test("reports webNavigation discovery failures", async () => { - mockedGetManifest.mockReturnValue({...manifest, permissions: ["webNavigation"]}); - mockedGetAllFrames.mockRejectedValue(new Error("No tab with id 5")); + getBrowserTest().harness.runtime.setManifest({...manifest, permissions: ["webNavigation"]}); + frames().failNext(new Error("No tab with id 5")); const discovery = new RelayDiscovery(); await expect(discovery.discover(5)).rejects.toEqual( diff --git a/src/relay/providers/Relay.test.ts b/src/relay/providers/ProxyRelay.test.ts similarity index 58% rename from src/relay/providers/Relay.test.ts rename to src/relay/providers/ProxyRelay.test.ts index c6fb1b7e..106b3f95 100644 --- a/src/relay/providers/Relay.test.ts +++ b/src/relay/providers/ProxyRelay.test.ts @@ -1,13 +1,16 @@ -import {getAllFrames, getManifest} from "@addon-core/browser"; +import {getManifest} from "@addon-core/browser"; -import TransportMessage from "@transport/TransportMessage"; -import {markRemoteMessageError} from "@message/error"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import type {NodeScriptRuntime} from "@addon-core/browser/testing/node"; +import {createRelayRuntime} from "../tests/runtime"; +import type {RelayFixture} from "../tests/fixtures/runtime"; +import {MessageResultEnvelopeProperty, type MessageSendOptions} from "@typing/message"; +import type {TransportMessageData} from "@typing/transport"; import ProxyRelay, {type ProxyRelayParams} from "./ProxyRelay"; -import RegisterRelay from "./RegisterRelay"; + import RelayManager from "../RelayManager"; import RelayPermission from "../RelayPermission"; -import {isRelayContext} from "../utils"; import { RelayAllFrames, @@ -19,54 +22,28 @@ import { } from "@typing/relay"; import {RpcAsyncProxy} from "@typing/rpc"; -const mockedGetAllFrames = getAllFrames as jest.MockedFunction; -const mockedGetManifest = getManifest as jest.MockedFunction; -const activationOrder: string[] = []; -let relayPermission: RelayPermission; - -const manifest = { - manifest_version: 3, - name: "Relay test", - version: "1.0.0", -} satisfies ReturnType; - -beforeEach(async () => { - jest.clearAllMocks(); - activationOrder.splice(0); +const scripting = () => getBrowserTest().harness.scripting.executeScript; +const messaging = () => { + const {harness, context} = getBrowserTest(); - RelayManager.getInstance().clear(); - - new RegisterRelay(relayName, RelayMethod.Scripting, () => MatchRelay).register(); + return harness.messaging.forContext(context).tabs.sendMessage; +}; +const frames = () => getBrowserTest().harness.configurable.chrome.webNavigation.getAllFrames; +let runtime: NodeScriptRuntime; +let relayPermission: RelayPermission; - relayPermission = { - allow: jest.fn().mockReturnValue(true), - request: jest.fn().mockResolvedValue(true), - } as unknown as RelayPermission; +const manifest = {manifest_version: 3, name: "Relay test", version: "1.0.0"} satisfies ReturnType; - mockedGetManifest.mockReturnValue({...manifest, permissions: []}); - mockedGetAllFrames.mockResolvedValue([]); +beforeEach(() => { + runtime = createRelayRuntime(); + getBrowserTest().harness.runtime.setManifest({...manifest, permissions: []}); + getBrowserTest().harness.permissions.contains.setResult(true); + relayPermission = RelayPermission.getInstance( + new Map([[relayName, {...options, name: relayName, declarative: true}]]) + ); }); -const MatchRelay = { - sum: (a: number, b: number): number => a + b, - asyncSum: (a: number, b: number): Promise => { - return new Promise(resolve => setTimeout(() => resolve(a + b), 100)); - }, - activation: (): boolean => { - activationOrder.push("relay"); - return true; - }, - fail: (): never => { - throw new TypeError("Remote failure"); - }, - one: 1, - obj: { - concat: (a: string, b: string): string => a + " " + b, - zero: 0, - }, -}; - -type RelayType = typeof MatchRelay; +type RelayType = RelayFixture; type RelayProxyType = RpcAsyncProxy; type RelayBatchProxyType = RelayBatchRpcProxy; @@ -82,24 +59,24 @@ const createProxyRelay = (relayOptions: RelayOptions, params }; const expectScriptInjection = (expected: Partial>) => { - const [injection] = (chrome.scripting.executeScript as jest.Mock).mock.calls.at(-1); - - expect(injection).toEqual(expect.objectContaining(expected)); + expect(scripting().calls.at(-1)?.args[0]).toEqual(expect.objectContaining(expected)); }; const expectScriptTargets = (expected: chrome.scripting.InjectionTarget[]) => { - const targets = (chrome.scripting.executeScript as jest.Mock).mock.calls.map(([injection]) => injection.target); + expect( + scripting().calls.map(call => (call.args[0] as chrome.scripting.ScriptInjection).target) + ).toEqual(expected); +}; + +const expectMessageCall = (index: number, data: TransportMessageData, target: Exclude) => { + const {tabId, ...options} = target; - expect(targets).toEqual(expected); + expect(messaging().calls[index].args).toEqual([tabId, expect.objectContaining({data}), options]); }; describe("ProxyRelay", () => { - beforeEach(async () => { - (isRelayContext as jest.Mock).mockReturnValue(false); - }); - test("throws an error when get() is called in content script context", async () => { - (isRelayContext as jest.Mock).mockReturnValue(true); + RelayManager.getInstance(); const proxy = createProxyRelay(options, 1); @@ -137,7 +114,7 @@ describe("ProxyRelay", () => { expect(await relay.sum(1, 2)).toBe(3); - expect(chrome.scripting.executeScript).toHaveBeenCalledTimes(1); + expect(scripting().calls).toHaveLength(1); expectScriptInjection({ target: {tabId: 1}, @@ -195,16 +172,20 @@ describe("ProxyRelay", () => { test("calls async method on proxy and returns resolved value", async () => { const relay = createProxyRelay(options, 1).get(); - expect(await relay.asyncSum(1, 2)).toBe(3); + const result = relay.asyncSum(1, 2); + + runtime.clock!.advance(100); + expect(await result).toBe(3); }); test("starts a Scripting relay method before returning control to the caller", async () => { const relay = createProxyRelay(options, 1).get(); const result = relay.activation(); - activationOrder.push("caller"); - - expect(activationOrder).toEqual(["relay", "caller"]); + runtime.evaluate( + {documentId: "document-0"}, + {source: 'if (!relayFixture.started) { throw new Error("Relay method has not started"); }'} + ); await expect(result).resolves.toBe(true); }); @@ -251,7 +232,7 @@ describe("ProxyRelay", () => { }); test("normalizes an unobservable Scripting frame outcome", async () => { - (chrome.scripting.executeScript as jest.Mock).mockImplementationOnce(async () => [{frameId: 0}]); + scripting().queueResult([{frameId: 0, documentId: "document-0"}]); const relay = createProxyRelay(options, { tabId: 1, frameIds: [0], @@ -259,7 +240,7 @@ describe("ProxyRelay", () => { await expect(relay.sum(1, 2)).resolves.toEqual([ { - target: {tabId: 1, frameId: 0}, + target: {tabId: 1, frameId: 0, documentId: "document-0"}, status: "rejected", error: expect.objectContaining({ kind: RelayFrameErrorKind.Unobservable, @@ -270,29 +251,23 @@ describe("ProxyRelay", () => { }); test("keeps a target-gone failure isolated in an explicit Scripting batch", async () => { - (chrome.scripting.executeScript as jest.Mock) - .mockImplementationOnce(async injection => [ - { - frameId: 0, - result: await injection.func(...injection.args), - }, - ]) - .mockImplementationOnce(async () => { - throw new Error("No frame with id 2 in tab 1"); - }); + scripting().queueResult([ + {frameId: 0, documentId: "document-0", result: {ok: true, hasResult: true, result: 3}}, + ]); + scripting().failNext(new Error("No frame with id 2 in tab 1")); const relay = createProxyRelay(options, { tabId: 1, - frameIds: [0, 2], + frameIds: [2, 0], }).get(); await expect(relay.sum(1, 2)).resolves.toEqual([ - {target: {tabId: 1, frameId: 0}, status: "fulfilled", result: 3}, + {target: {tabId: 1, frameId: 0, documentId: "document-0"}, status: "fulfilled", result: 3}, { target: {tabId: 1, frameId: 2}, status: "rejected", error: expect.objectContaining({ kind: RelayFrameErrorKind.TargetGone, - message: "No frame with id 2 in tab 1", + message: expect.stringMatching(/frame.*2/i), }), }, ]); @@ -319,49 +294,21 @@ describe("ProxyRelay", () => { ]); expectScriptInjection({target: {tabId: 1, allFrames: true}}); - expect(mockedGetAllFrames).not.toHaveBeenCalled(); + expect(frames().calls).toHaveLength(0); } ); test.each([true, RelayAllFrames.Any] as const)( "returns a fulfilled Scripting outcome for allFrames %s when another frame is rejected", async allFrames => { - (chrome.scripting.executeScript as jest.Mock).mockResolvedValueOnce([ - {frameId: 0, error: new Error("Relay manager not found.")}, - {frameId: 2, result: {ok: true, hasResult: true, result: 3}}, - ]); - const relay = createProxyRelay(options, { - tabId: 1, - allFrames, - }).get(); - - await expect(relay.sum(1, 2)).resolves.toEqual([ + scripting().queueResult([ { - target: {tabId: 1, allFrames: RelayAllFrames.Any}, - status: "fulfilled", - result: 3, + frameId: 0, + documentId: "document-0", + result: {ok: false, error: {name: "Error", message: "Relay manager not found."}}, }, + {frameId: 2, documentId: "document-2", result: {ok: true, hasResult: true, result: 3}}, ]); - } - ); - - test.each([true, RelayAllFrames.Any] as const)( - "does not retry a missing Relay manager for Scripting allFrames %s", - async allFrames => { - (chrome.scripting.executeScript as jest.Mock).mockImplementationOnce(async injection => { - const injectedArgs = [...injection.args]; - injectedArgs[3] = `${RelayGlobalKey}Missing`; - const setTimeoutSpy = jest.spyOn(globalThis, "setTimeout"); - const execution = injection.func(...injectedArgs); - - expect(injectedArgs.at(-1)).toBe(false); - expect(setTimeoutSpy).not.toHaveBeenCalled(); - setTimeoutSpy.mockRestore(); - - const result = await execution; - - return [{frameId: 0, result}]; - }); const relay = createProxyRelay(options, { tabId: 1, allFrames, @@ -370,37 +317,13 @@ describe("ProxyRelay", () => { await expect(relay.sum(1, 2)).resolves.toEqual([ { target: {tabId: 1, allFrames: RelayAllFrames.Any}, - status: "rejected", - error: expect.objectContaining({ - kind: RelayFrameErrorKind.Remote, - message: "Relay manager not found.", - }), + status: "fulfilled", + result: 3, }, ]); } ); - test("keeps manager retries for addressed Scripting calls", async () => { - (chrome.scripting.executeScript as jest.Mock).mockImplementationOnce(async injection => { - const injectedArgs = [...injection.args]; - injectedArgs[3] = `${RelayGlobalKey}Missing`; - const setTimeoutSpy = jest - .spyOn(globalThis, "setTimeout") - .mockImplementation(() => 0 as unknown as ReturnType); - - injection.func(...injectedArgs); - - expect(injectedArgs.at(-1)).toBe(true); - expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 300); - setTimeoutSpy.mockRestore(); - - return [{frameId: 0, error: new Error("Relay manager not found.")}]; - }); - const relay = createProxyRelay(options, {tabId: 1, frameId: 0}).get(); - - await expect(relay.sum(1, 2)).rejects.toThrow("Relay manager not found."); - }); - test("returns every Scripting outcome for RelayAllFrames.All", async () => { const relay = createProxyRelay(options, { tabId: 1, @@ -429,15 +352,12 @@ describe("ProxyRelay", () => { ]); expectScriptInjection({target: {tabId: 1, allFrames: true}}); - expect(mockedGetAllFrames).not.toHaveBeenCalled(); + expect(frames().calls).toHaveLength(0); }); test("checks Relay permission once for a batch call", async () => { - const permission = { - allow: jest.fn().mockReturnValue(true), - request: jest.fn(), - }; - relayPermission = permission as unknown as RelayPermission; + relayPermission.set(relayName, {allow: false}); + getBrowserTest().harness.permissions.request.setResult(true); const relay = createProxyRelay(options, { tabId: 1, @@ -446,20 +366,14 @@ describe("ProxyRelay", () => { await relay.sum(1, 2); - expect(permission.allow).toHaveBeenCalledTimes(1); - expect(permission.request).not.toHaveBeenCalled(); + expect(getBrowserTest().harness.permissions.request.calls).toHaveLength(1); }); test("fans Messaging calls out to explicit frameIds and keeps partial failures", async () => { - const send = jest.spyOn(TransportMessage.prototype, "send").mockImplementation(async (_data, target) => { - const frameId = typeof target === "object" ? target.frameId : undefined; - - if (frameId === 2) { - throw new Error("Frame with ID 2 was removed"); - } + const send = messaging(); - return frameId; - }); + send.failNext(new Error("Frame with ID 2 was removed")); + send.queueResult({[MessageResultEnvelopeProperty]: true, ok: true, payload: 0}); const relay = createProxyRelay( {...options, method: RelayMethod.Messaging}, {tabId: 1, frameIds: [2, 0]} @@ -477,12 +391,19 @@ describe("ProxyRelay", () => { }, ]); - expect(send).toHaveBeenCalledTimes(2); - send.mockRestore(); + expect(send.calls).toHaveLength(2); }); test("returns a per-frame timeout from Messaging", async () => { - const send = jest.spyOn(TransportMessage.prototype, "send").mockImplementation(() => new Promise(() => {})); + const receiver = getBrowserTest().harness.contexts.create({ + kind: "contentScript", + tabId: 1, + frameId: 2, + documentId: "document-2", + url: "https://example.com/", + }); + + receiver.onMessage.on(() => true); const relay = createProxyRelay( {...options, method: RelayMethod.Messaging}, {tabId: 1, frameIds: [2], timeoutMs: 5} @@ -498,12 +419,15 @@ describe("ProxyRelay", () => { }), }, ]); - - send.mockRestore(); }); test("passes timeoutMs to Inject Script", async () => { - (chrome.scripting.executeScript as jest.Mock).mockImplementationOnce(() => new Promise(() => {})); + getBrowserTest().harness.contexts.documents.remove("document-0"); + getBrowserTest().harness.contexts.documents.create({ + documentId: "pending", + tabId: 1, + url: "https://example.com/", + }); const relay = createProxyRelay(options, { tabId: 1, frameIds: [0], @@ -523,9 +447,11 @@ describe("ProxyRelay", () => { }); test("classifies restored remote Messaging errors structurally", async () => { - const send = jest - .spyOn(TransportMessage.prototype, "send") - .mockRejectedValue(markRemoteMessageError(new TypeError("Remote failure"))); + messaging().setResult({ + [MessageResultEnvelopeProperty]: true, + ok: false, + error: {name: "TypeError", message: "Remote failure"}, + }); const relay = createProxyRelay( {...options, method: RelayMethod.Messaging}, {tabId: 1, frameIds: [0]} @@ -542,14 +468,12 @@ describe("ProxyRelay", () => { }), }, ]); - - send.mockRestore(); }); test.each([true, RelayAllFrames.Any] as const)( "returns one native Messaging outcome for allFrames %s without discovery", async allFrames => { - const send = jest.spyOn(TransportMessage.prototype, "send").mockResolvedValue(3); + messaging().setResult({[MessageResultEnvelopeProperty]: true, ok: true, payload: 3}); const relay = createProxyRelay( {...options, method: RelayMethod.Messaging}, {tabId: 1, allFrames} @@ -563,14 +487,13 @@ describe("ProxyRelay", () => { }, ]); - expect(mockedGetAllFrames).not.toHaveBeenCalled(); - expect(send).toHaveBeenCalledWith({path: "sum", args: [1, 2]}, {tabId: 1}); - send.mockRestore(); + expect(frames().calls).toHaveLength(0); + expectMessageCall(0, {path: "sum", args: [1, 2]}, {tabId: 1}); } ); test("keeps allFrames false on the scalar top-frame contract", async () => { - const send = jest.spyOn(TransportMessage.prototype, "send").mockResolvedValue(3); + messaging().setResult({[MessageResultEnvelopeProperty]: true, ok: true, payload: 3}); const relay = createProxyRelay( {...options, method: RelayMethod.Messaging}, {tabId: 1, allFrames: false} @@ -578,13 +501,12 @@ describe("ProxyRelay", () => { await expect(relay.sum(1, 2)).resolves.toBe(3); - expect(mockedGetAllFrames).not.toHaveBeenCalled(); - expect(send).toHaveBeenCalledWith({path: "sum", args: [1, 2]}, {tabId: 1, frameId: 0}); - send.mockRestore(); + expect(frames().calls).toHaveLength(0); + expectMessageCall(0, {path: "sum", args: [1, 2]}, {tabId: 1, frameId: 0}); }); test("discovers strict Messaging allFrames targets through webNavigation", async () => { - mockedGetManifest.mockReturnValue({...manifest, permissions: ["webNavigation"]}); + getBrowserTest().harness.runtime.setManifest({...manifest, permissions: ["webNavigation"]}); const topFrame: chrome.webNavigation.GetAllFrameResultDetails = { frameId: 0, documentId: "document-0", @@ -604,10 +526,11 @@ describe("ProxyRelay", () => { parentDocumentId: "document-0", url: "https://example.com/frame", }; - mockedGetAllFrames.mockResolvedValue([childFrame, topFrame]); - const send = jest.spyOn(TransportMessage.prototype, "send").mockImplementation(async (_data, target) => { - return typeof target === "object" ? target.frameId : undefined; - }); + frames().setResult([childFrame, topFrame]); + messaging().queueResult( + {[MessageResultEnvelopeProperty]: true, ok: true, payload: 0}, + {[MessageResultEnvelopeProperty]: true, ok: true, payload: 3} + ); const relay = createProxyRelay( {...options, method: RelayMethod.Messaging}, {tabId: 1, allFrames: RelayAllFrames.All} @@ -626,89 +549,8 @@ describe("ProxyRelay", () => { }, ]); - expect(mockedGetAllFrames).toHaveBeenCalledWith(1); - expect(send).toHaveBeenNthCalledWith( - 1, - {path: "sum", args: [1, 2]}, - {tabId: 1, frameId: 0, documentId: "document-0"} - ); - expect(send).toHaveBeenNthCalledWith( - 2, - {path: "sum", args: [1, 2]}, - {tabId: 1, frameId: 3, documentId: "document-3"} - ); - send.mockRestore(); - }); -}); - -describe("RegisterRelay", () => { - beforeEach(async () => { - (isRelayContext as jest.Mock).mockReturnValue(true); - }); - - test("throws if trying to get registered relay from non-content script", async () => { - (isRelayContext as jest.Mock).mockReturnValue(false); - - const proxy = new RegisterRelay(relayName, RelayMethod.Scripting, () => MatchRelay); - - expect(() => proxy.get()).toThrow(`Relay "${relayName}" can be getting only from content script`); - }); - - test("returns real relay when called in content script context", () => { - const relay = new RegisterRelay( - relayName, - RelayMethod.Scripting, - () => MatchRelay - ).get(); - - expect(relay["__proxy"]).toBe(undefined); - }); - - test("calls method directly in content script without chrome.scripting", async () => { - const relay = new RegisterRelay( - relayName, - RelayMethod.Scripting, - () => MatchRelay - ).get(); - - expect(relay.sum(1, 2)).toBe(3); - expect(chrome.scripting.executeScript).toHaveBeenCalledTimes(0); - }); - - test("throws an error when attempting to register the same relay twice", async () => { - const relay = new RegisterRelay( - relayName, - RelayMethod.Scripting, - () => MatchRelay - ); - - expect(() => relay.register()).toThrow( - `A relay with the name "${relayName}" already exists. The relay name must be unique.` - ); - }); - - test("does not call parent register method when RelayMethod is 'scripting'", () => { - const registerRelay = new RegisterRelay(relayName, RelayMethod.Scripting, () => MatchRelay); - const parentRegisterSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(registerRelay)), "register"); - jest.spyOn(RelayManager.getInstance(), "has").mockReturnValue(false); - - registerRelay.register(); - - expect(parentRegisterSpy).not.toHaveBeenCalled(); - - parentRegisterSpy.mockRestore(); - }); - - test("calls parent register method when method is 'messaging'", () => { - const registerRelay = new RegisterRelay(relayName, RelayMethod.Messaging, () => MatchRelay); - const parentRegisterSpy = jest.spyOn(Object.getPrototypeOf(Object.getPrototypeOf(registerRelay)), "register"); - parentRegisterSpy.mockReturnValue(MatchRelay); - - const result = registerRelay.register(); - - expect(result).toBe(MatchRelay); - expect(parentRegisterSpy).toHaveBeenCalledWith(); - - parentRegisterSpy.mockRestore(); + expect(frames().calls[0].args).toEqual([{tabId: 1}]); + expectMessageCall(0, {path: "sum", args: [1, 2]}, {tabId: 1, frameId: 0, documentId: "document-0"}); + expectMessageCall(1, {path: "sum", args: [1, 2]}, {tabId: 1, frameId: 3, documentId: "document-3"}); }); }); diff --git a/src/relay/providers/RegisterRelay.test.ts b/src/relay/providers/RegisterRelay.test.ts new file mode 100644 index 00000000..60817635 --- /dev/null +++ b/src/relay/providers/RegisterRelay.test.ts @@ -0,0 +1,72 @@ +import {createTabFixture} from "@addon-core/browser/testing"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import {RelayGlobalKey, RelayMethod} from "@typing/relay"; +import RelayManager from "../RelayManager"; +import RegisterRelay from "./RegisterRelay"; +import RelayMessagingAdapter from "../adapters/RelayMessagingAdapter"; + +const math = {sum: (a: number, b: number): number => a + b}; + +beforeEach(() => { + new RegisterRelay("math", RelayMethod.Scripting, () => math).register(); +}); + +test("rejects access outside the Relay content context", () => { + Reflect.deleteProperty(globalThis, RelayGlobalKey); + + expect(() => new RegisterRelay("math", RelayMethod.Scripting, () => math).get()).toThrow( + 'Relay "math" can be getting only from content script' + ); +}); + +test("returns the registered object and calls it directly without scripting", () => { + const relay = new RegisterRelay("math", RelayMethod.Scripting, () => math).get(); + + expect(relay).toBe(math); + expect(relay.sum(1, 2)).toBe(3); + expect(getBrowserTest().harness.scripting.executeScript.calls).toHaveLength(0); +}); + +test("rejects duplicate scripting registrations", () => { + expect(() => new RegisterRelay("math", RelayMethod.Scripting, () => math).register()).toThrow( + 'A relay with the name "math" already exists. The relay name must be unique.' + ); +}); + +test.each([RelayMethod.Scripting, RelayMethod.Messaging])("owns registration and cleanup for %s", method => { + const session = getBrowserTest(); + const register = new RegisterRelay("another", method, () => math); + + expect(register.register()).toBe(math); + expect(RelayManager.getInstance().get("another")).toBe(math); + expect(session.context.onMessage.listenerCount()).toBe(method === RelayMethod.Messaging ? 1 : 0); + register.destroy(); + expect(RelayManager.getInstance().has("another")).toBe(false); + expect(session.context.onMessage.listenerCount()).toBe(0); +}); + +test("serves a real Relay messaging call from a different context", async () => { + const session = getBrowserTest(); + session.harness.tabs.set([createTabFixture({id: 1})]); + + const receiver = session.harness.contexts.create({ + kind: "contentScript", + tabId: 1, + frameId: 0, + url: "https://example.com/", + }); + const restore = session.useContext(receiver); + const register = new RegisterRelay("remote", RelayMethod.Messaging, () => math); + + register.register(); + session.addCleanup(() => register.destroy()); + restore(); + + const adapter = new RelayMessagingAdapter("remote", {tabId: 1, frameId: 0}); + + await expect(adapter.invoke([2, 3], "sum")).resolves.toBe(5); + expect(receiver.onMessage.listenerCount()).toBe(1); + register.destroy(); + expect(receiver.onMessage.listenerCount()).toBe(0); + await expect(adapter.invoke([2, 3], "sum")).rejects.toThrow(/Receiving end does not exist/); +}); diff --git a/tests/README.md b/tests/README.md index b8e4d685..84ec174d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,12 +42,12 @@ See [integration/README.md](integration/README.md) for browser requirements and ## Browser harness migration `tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for -unit, build and types projects. Only the 11 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. +unit, build and types projects. Only the 7 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. Real Chrome/Firefox integrations load neither setup. The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for -`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 19 files to review in total. +`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 15 files to review in total. Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. `npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 68888357..6af25fee 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -1,9 +1,5 @@ { "legacyFiles": [ - "src/relay/providers/Relay.test.ts", - "src/relay/discovery/RelayDiscovery.test.ts", - "src/entry/relay/index.test.ts", - "src/entry/relay/Builder.test.ts", "src/message/providers/Message.test.ts", "src/message/adapters/react/useMessageHandler.test.ts", "src/offscreen/providers/Offscreen.test.ts", diff --git a/tests/jest-legacy.setup.ts b/tests/jest-legacy.setup.ts index c2ac7fd3..42d6d39f 100644 --- a/tests/jest-legacy.setup.ts +++ b/tests/jest-legacy.setup.ts @@ -2,4 +2,3 @@ import "jest-webextension-mock"; import "./jest-message.setup"; import "./jest-modules.setup"; -import "./jest-relay.setup"; diff --git a/tests/jest-relay.setup.ts b/tests/jest-relay.setup.ts deleted file mode 100644 index d56547e3..00000000 --- a/tests/jest-relay.setup.ts +++ /dev/null @@ -1,43 +0,0 @@ -const resolveScriptingResult = async ( - injection: Parameters[0] -): Promise[]> => { - const {func, target} = injection; - - if (!func) { - return []; - } - - const args = "args" in injection ? injection.args : []; - const frameIds = target.allFrames ? [0, 2] : target.frameIds || [0]; - const documentIds = target.documentIds || []; - const targets = documentIds.length - ? documentIds.map(documentId => ({ - documentId, - frameId: Number.parseInt(documentId.match(/\d+$/)?.[0] ?? "0", 10), - })) - : frameIds.map(frameId => ({frameId, documentId: `document-${frameId}`})); - - return Promise.all( - targets.map(async target => ({ - ...target, - result: await func(...args), - })) - ); -}; - -chrome.scripting = { - ...chrome.scripting, - executeScript: jest.fn().mockImplementation((injection, callback) => { - const result = resolveScriptingResult(injection); - - if (callback) { - result.then(callback); - } - - return result; - }), -}; - -jest.mock("../src/relay/utils", () => ({ - isRelayContext: jest.fn(), -})); From 938e9bef7401d9bbe6cf6c2fb12f82b4e50c4214 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Thu, 24 Sep 2026 21:21:33 +0300 Subject: [PATCH 07/36] fix(message): unwrap successful responses without payload --- src/message/message-response.test.ts | 76 ++++++++++++++++++++++++++++ src/message/providers/Message.ts | 3 +- 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 src/message/message-response.test.ts diff --git a/src/message/message-response.test.ts b/src/message/message-response.test.ts new file mode 100644 index 00000000..2c1a53a0 --- /dev/null +++ b/src/message/message-response.test.ts @@ -0,0 +1,76 @@ +import {createTabFixture} from "@addon-core/browser/testing"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import {MessageResultEnvelopeProperty, type MessageSendOptions} from "@typing/message"; +import {isRemoteMessageError} from "./error"; +import {Message} from "./providers"; + +type ResponseHandler = () => unknown; +type ResponseMessages = {probe: ResponseHandler}; + +describe.each(["runtime", "tab"] as const)("Message response over %s messaging", target => { + let message: Message; + let options: MessageSendOptions | undefined; + let restoreContext: () => void; + + beforeEach(() => { + const session = getBrowserTest(); + + session.harness.tabs.set([createTabFixture({id: 7})]); + + const receiver = session.harness.contexts.create( + target === "runtime" + ? {kind: "background"} + : {kind: "contentScript", tabId: 7, frameId: 0, url: "https://example.test/"} + ); + + restoreContext = session.useContext(receiver); + message = new Message(); + options = target === "runtime" ? undefined : {tabId: 7, frameId: 0}; + }); + + const send = (handler: ResponseHandler): Promise => { + message.watch("probe", handler); + restoreContext(); + + return message.send("probe", undefined, options); + }; + + test("returns undefined when an async handler has no return value", async () => { + await expect(send(async () => {})).resolves.toBeUndefined(); + }); + + test("preserves the null response of a sync handler with no return value", async () => { + await expect(send(() => {})).resolves.toBeNull(); + }); + + test("preserves the channel error when a general handler sends no response", async () => { + message.watch(() => {}); + restoreContext(); + + const result = message.send("probe", undefined, options); + + await expect(result).rejects.toThrow("The message port closed before a response was received."); + expect(isRemoteMessageError(await result.catch(error => error))).toBe(false); + }); + + test.each([null, false, 0, ""])("preserves the async result %p", async value => { + await expect(send(async () => value)).resolves.toBe(value); + }); + + test("unwraps only the outer envelope when the payload resembles a response", async () => { + const payload = {[MessageResultEnvelopeProperty]: true, ok: true}; + + await expect(send(async () => payload)).resolves.toEqual(payload); + }); + + test.each([false, true])("preserves remote errors from an async=%s handler", async asynchronous => { + const fail = () => { + throw new TypeError("Response failed"); + }; + const result = send(asynchronous ? async () => fail() : fail); + + await expect(result).rejects.toBeInstanceOf(TypeError); + await expect(result).rejects.toThrow("Response failed"); + expect(isRemoteMessageError(await result.catch(error => error))).toBe(true); + }); +}); diff --git a/src/message/providers/Message.ts b/src/message/providers/Message.ts index f4673fc5..83f94f2c 100644 --- a/src/message/providers/Message.ts +++ b/src/message/providers/Message.ts @@ -130,7 +130,8 @@ export default class Message extends AbstractMessag } if (response.ok) { - return "payload" in response; + // JSON serialization omits payload when a successful handler returns undefined. + return true; } return this.isSerializedError(response.error); From 938598b05a5ceb42043a9febda9c024ffa9e83e3 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:34:29 +0300 Subject: [PATCH 08/36] test(message): migrate suites to browser harness --- src/message/adapters/react/index.ts | 2 +- .../react/use-message-handler.test.ts | 159 +++++++++ ...ssageHandler.ts => use-message-handler.ts} | 0 .../adapters/react/useMessageHandler.test.ts | 173 --------- src/message/providers/Message.test.ts | 327 ++++++++++++------ tests/README.md | 4 +- tests/browser-harness/migration.json | 2 - 7 files changed, 380 insertions(+), 287 deletions(-) create mode 100644 src/message/adapters/react/use-message-handler.test.ts rename src/message/adapters/react/{useMessageHandler.ts => use-message-handler.ts} (100%) delete mode 100644 src/message/adapters/react/useMessageHandler.test.ts diff --git a/src/message/adapters/react/index.ts b/src/message/adapters/react/index.ts index 9651bc92..8b0bff3e 100644 --- a/src/message/adapters/react/index.ts +++ b/src/message/adapters/react/index.ts @@ -1 +1 @@ -export {default as useMessageHandler} from "./useMessageHandler"; +export {default as useMessageHandler} from "./use-message-handler"; diff --git a/src/message/adapters/react/use-message-handler.test.ts b/src/message/adapters/react/use-message-handler.test.ts new file mode 100644 index 00000000..cc67e533 --- /dev/null +++ b/src/message/adapters/react/use-message-handler.test.ts @@ -0,0 +1,159 @@ +import React from "react"; +import {act, cleanup, renderHook} from "@testing-library/react"; +import type {BrowserContextMessaging} from "@addon-core/browser/testing"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import type {MessageData, MessageTargetHandler, MessageType} from "@typing/message"; +import {Message} from "@message/providers"; +import {useMessageHandler} from "./index"; + +type MessageMap = { + getStringLength: (data: string) => number; + toUpperCase: (str: string) => string; + sayHello: (data?: string) => string; +}; +type TextMessageType = "getStringLength" | "toUpperCase"; +type TextMessageHandler = MessageTargetHandler; +type TextHandlerProps = {type: TextMessageType; handler: TextMessageHandler}; + +let caller: BrowserContextMessaging; +let addListener: jest.SpyInstance; +let removeListener: jest.SpyInstance; + +beforeEach(() => { + const session = getBrowserTest(); + const sender = session.harness.contexts.create({kind: "background"}); + + caller = session.harness.messaging.forContext(sender); + // Observe the browser boundary while retaining its real registration behavior. + addListener = jest.spyOn(chrome.runtime.onMessage, "addListener"); + removeListener = jest.spyOn(chrome.runtime.onMessage, "removeListener"); + session.addCleanup(cleanup); +}); + +const listenerCount = () => getBrowserTest().context.onMessage.listenerCount(); + +function send>(type: K, data: MessageData): Promise { + return caller.chrome.runtime.sendMessage({id: "hook-test", type, data, timestamp: 0}); +} + +function useHarness>(type: K, handler: MessageTargetHandler) { + const rendersRef = React.useRef(0); + + rendersRef.current++; + useMessageHandler(type, handler); + + return {renders: rendersRef.current}; +} + +test("registers a handler and responds to another browser context", async () => { + expect(listenerCount()).toBe(0); + + renderHook(() => useMessageHandler<"getStringLength", MessageMap>("getStringLength", str => str.length)); + + expect(listenerCount()).toBe(1); + await expect(send("getStringLength", "test")).resolves.toMatchObject({ok: true, payload: 4}); +}); + +test("adds and removes the listener on mount and unmount", async () => { + const {unmount} = renderHook(() => + useMessageHandler<"getStringLength", MessageMap>("getStringLength", str => str.length) + ); + + expect(listenerCount()).toBe(1); + unmount(); + expect(listenerCount()).toBe(0); + await expect(send("getStringLength", "test")).rejects.toThrow("Receiving end does not exist"); +}); + +test("updates the handler without resubscribing and resubscribes on type changes", async () => { + const initialProps: TextHandlerProps = {type: "getStringLength", handler: str => str.length}; + const {rerender} = renderHook( + ({type, handler}: TextHandlerProps) => useMessageHandler(type, handler), + {initialProps} + ); + + await expect(send("getStringLength", "test")).resolves.toMatchObject({payload: 4}); + expect(addListener).toHaveBeenCalledTimes(1); + expect(removeListener).not.toHaveBeenCalled(); + + rerender({type: "getStringLength", handler: str => str.length / 2}); + + expect(addListener).toHaveBeenCalledTimes(1); + expect(removeListener).not.toHaveBeenCalled(); + await expect(send("getStringLength", "test")).resolves.toMatchObject({payload: 2}); + + rerender({type: "toUpperCase", handler: str => str.toUpperCase()}); + + expect(addListener).toHaveBeenCalledTimes(2); + expect(removeListener).toHaveBeenCalledTimes(1); + expect(listenerCount()).toBe(1); + await expect(send("toUpperCase", "test")).resolves.toMatchObject({payload: "TEST"}); + await expect(send("getStringLength", "test")).resolves.toBeUndefined(); +}); + +test("uses Message.getInstance once across rerenders", () => { + const spy = jest.spyOn(Message, "getInstance"); + const {rerender, unmount} = renderHook( + ({handler}) => useMessageHandler<"sayHello", MessageMap>("sayHello", handler), + {initialProps: {handler: (name?: string) => `Hello ${name ?? ""}`.trim()}} + ); + + expect(spy).toHaveBeenCalledTimes(1); + rerender({handler: (name?: string) => `Hi ${name ?? ""}`.trim()}); + expect(spy).toHaveBeenCalledTimes(1); + unmount(); + expect(listenerCount()).toBe(0); +}); + +test("does not rerender on messages when the handler does not update state", async () => { + const {result} = renderHook(() => useHarness("sayHello", (data?: string) => (data ? `Hello ${data}` : "Hello"))); + + expect(result.current.renders).toBe(1); + + await act(async () => { + await expect(send("sayHello", "John")).resolves.toMatchObject({payload: "Hello John"}); + }); + + expect(result.current.renders).toBe(1); +}); + +test("changing the handler adds one render without resubscribing", () => { + const {result, rerender} = renderHook(({handler}) => useHarness("getStringLength", handler), { + initialProps: {handler: (str: string) => str.length}, + }); + const rendersBefore = result.current.renders; + + rerender({handler: str => Math.floor(str.length / 2)}); + + expect(addListener).toHaveBeenCalledTimes(1); + expect(removeListener).not.toHaveBeenCalled(); + expect(result.current.renders).toBe(rendersBefore + 1); +}); + +test("changing the type resubscribes and adds one render", () => { + const initialProps: TextHandlerProps = {type: "getStringLength", handler: str => str.length}; + const {result, rerender} = renderHook(({type, handler}: TextHandlerProps) => useHarness(type, handler), { + initialProps, + }); + const rendersBefore = result.current.renders; + + rerender({type: "toUpperCase", handler: str => str.toUpperCase()}); + + expect(addListener).toHaveBeenCalledTimes(2); + expect(removeListener).toHaveBeenCalledTimes(1); + expect(result.current.renders).toBe(rendersBefore + 1); +}); + +test("keeps one live subscription under StrictMode and removes it on unmount", async () => { + const Wrapper: React.FC<{children: React.ReactNode}> = ({children}) => + React.createElement(React.StrictMode, null, children); + const {unmount} = renderHook(() => useMessageHandler<"sayHello", MessageMap>("sayHello", () => "pong"), { + wrapper: Wrapper, + }); + + expect(listenerCount()).toBe(1); + await expect(send("sayHello", undefined)).resolves.toMatchObject({payload: "pong"}); + unmount(); + expect(listenerCount()).toBe(0); + expect(removeListener.mock.calls.length).toBe(addListener.mock.calls.length); +}); diff --git a/src/message/adapters/react/useMessageHandler.ts b/src/message/adapters/react/use-message-handler.ts similarity index 100% rename from src/message/adapters/react/useMessageHandler.ts rename to src/message/adapters/react/use-message-handler.ts diff --git a/src/message/adapters/react/useMessageHandler.test.ts b/src/message/adapters/react/useMessageHandler.test.ts deleted file mode 100644 index dc5700cf..00000000 --- a/src/message/adapters/react/useMessageHandler.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -import React from "react"; -import {act, renderHook} from "@testing-library/react"; -import useMessageHandler from "./useMessageHandler"; -import {Message} from "../../providers"; - -type MessageMap = { - getStringLength: (data: string) => number; - toUpperCase: (str: string) => string; - sayHello: (data?: string) => string; - fetchUser: (name: string) => Promise<{name: string}>; -}; - -let message: Message; - -beforeEach(() => { - jest.clearAllMocks(); - message = new Message(); - // @ts-ignore - test-only access to manager - message["manager"].clear(); -}); - -function useHarness(type: K, handler: MessageMap[K]) { - const rendersRef = React.useRef(0); - rendersRef.current++; - - // Using explicit generics to match hook signature order - useMessageHandler(type as any, handler as any); - - return {renders: rendersRef.current}; -} - -test("registers a specific handler for a given message type", async () => { - expect(chrome.runtime.onMessage.hasListeners()).toBe(false); - - renderHook(() => useMessageHandler<"getStringLength", MessageMap>("getStringLength", str => str.length)); - - expect(chrome.runtime.onMessage.hasListeners()).toBe(true); - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledWith(expect.any(Function)); - - const result = await message.send("getStringLength", "test"); - - expect(result).toBe(4); -}); - -test("adds and removes the listener on mount and unmount", async () => { - expect(chrome.runtime.onMessage.hasListeners()).toBe(false); - - const {unmount} = renderHook(() => { - useMessageHandler<"getStringLength", MessageMap>("getStringLength", str => str.length); - }); - - expect(chrome.runtime.onMessage.hasListeners()).toBe(true); - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledWith(expect.any(Function)); - - unmount(); - - expect(chrome.runtime.onMessage.hasListeners()).toBe(false); - expect(chrome.runtime.onMessage.removeListener).toHaveBeenCalledWith(expect.any(Function)); -}); - -test("does not resubscribe on handler change, resubscribes on type change", async () => { - expect(chrome.runtime.onMessage.hasListeners()).toBe(false); - - const {rerender} = renderHook( - ({type, handler}) => useMessageHandler<"getStringLength" | "toUpperCase", MessageMap>(type as any, handler), - { - initialProps: {type: "getStringLength" as const, handler: (str: string) => str.length}, - } - ); - - expect(chrome.runtime.onMessage.hasListeners()).toBe(true); - - expect(await message.send("getStringLength", "test")).toBe(4); - - const addCallsAfterMount = (chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length; - const removeCallsAfterMount = (chrome.runtime.onMessage.removeListener as jest.Mock).mock.calls.length; - - rerender({type: "getStringLength", handler: (str: string) => str.length / 2}); - - // No resubscribe on handler change - expect((chrome.runtime.onMessage.removeListener as jest.Mock).mock.calls.length).toBe(removeCallsAfterMount); - expect((chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length).toBe(addCallsAfterMount); - - expect(await message.send("getStringLength", "test")).toBe(2); - - // Change type -> should resubscribe - // @ts-ignore - rerender({type: "toUpperCase", handler: (str: string) => str.toUpperCase()}); - - expect((chrome.runtime.onMessage.removeListener as jest.Mock).mock.calls.length).toBe(removeCallsAfterMount + 1); - expect((chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length).toBe(addCallsAfterMount + 1); - - expect(await message.send("toUpperCase", "test")).toBe("TEST"); - expect(await message.send("getStringLength", "test")).toBe(undefined); -}); - -test("uses Message.getInstance and does not recreate between rerenders", () => { - const spy = jest.spyOn(Message, "getInstance"); - - const {rerender, unmount} = renderHook( - ({handler}) => useMessageHandler<"sayHello", MessageMap>("sayHello", handler), - { - initialProps: {handler: (name?: string) => `Hello ${name ?? ""}`.trim()}, - } - ); - - expect(spy).toHaveBeenCalledTimes(1); - - rerender({handler: (name?: string) => `Hi ${name ?? ""}`.trim()}); - - expect(spy).toHaveBeenCalledTimes(1); - - unmount(); -}); - -test("does not rerender on incoming message if handler doesn't touch state", async () => { - const {result} = renderHook(() => useHarness("sayHello", (data?: string) => (data ? `Hello ${data}` : "Hello"))); - - expect(result.current.renders).toBe(1); - - await act(async () => { - await message.send("sayHello", "John"); - }); - - expect(result.current.renders).toBe(1); -}); - -test("changing handler causes no resubscribe and only the expected one rerender", () => { - const {result, rerender} = renderHook(({handler}) => useHarness("getStringLength", handler as any), { - initialProps: {handler: (str: string) => str.length}, - }); - - const addCalls = (chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length; - const removeCalls = (chrome.runtime.onMessage.removeListener as jest.Mock).mock.calls.length; - const rendersBefore = result.current.renders; - - rerender({handler: (str: string) => Math.floor(str.length / 2)}); - - expect((chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length).toBe(addCalls); - expect((chrome.runtime.onMessage.removeListener as jest.Mock).mock.calls.length).toBe(removeCalls); - expect(result.current.renders).toBe(rendersBefore + 1); -}); - -test("changing type resubscribes and only one rerender occurs", () => { - const {result, rerender} = renderHook(({type}) => useHarness(type as any, ((s: string) => s.length) as any), { - initialProps: {type: "getStringLength" as const}, - }); - - const addCalls = (chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length; - const removeCalls = (chrome.runtime.onMessage.removeListener as jest.Mock).mock.calls.length; - const rendersBefore = result.current.renders; - - rerender({type: "toUpperCase" as any}); - - expect((chrome.runtime.onMessage.addListener as jest.Mock).mock.calls.length).toBe(addCalls + 1); - expect((chrome.runtime.onMessage.removeListener as jest.Mock).mock.calls.length).toBe(removeCalls + 1); - expect(result.current.renders).toBe(rendersBefore + 1); -}); - -test("works correctly under StrictMode (no leaked listeners)", () => { - const Wrapper: React.FC<{children: React.ReactNode}> = ({children}) => - React.createElement(React.StrictMode, null, children); - - const {unmount} = renderHook(() => useMessageHandler<"sayHello", MessageMap>("sayHello", () => "pong"), { - wrapper: Wrapper as any, - }); - - expect(chrome.runtime.onMessage.hasListeners()).toBe(true); - - unmount(); - - expect(chrome.runtime.onMessage.hasListeners()).toBe(false); -}); diff --git a/src/message/providers/Message.test.ts b/src/message/providers/Message.test.ts index b74f0ca4..1dbc63fb 100644 --- a/src/message/providers/Message.test.ts +++ b/src/message/providers/Message.test.ts @@ -1,6 +1,7 @@ import Message from "./Message"; -import {getBrowserInfo} from "@addon-core/browser"; -import * as env from "@main/env"; +import {createTabFixture, type BrowserContext} from "@addon-core/browser/testing"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import type {MessageData, MessageResponse, MessageSendOptions, MessageType} from "@typing/message"; import {isRemoteMessageError, UnsupportedMessageTargetError} from "../error"; @@ -19,53 +20,94 @@ type MessageMap = { }; let message: Message; -const mockedEnv = env as jest.Mocked; -const mockedGetBrowserInfo = getBrowserInfo as jest.MockedFunction; - -beforeEach(async () => { - jest.clearAllMocks(); - mockedEnv.isBrowser.mockReturnValue(false); - mockedGetBrowserInfo.mockResolvedValue({ - name: "Firefox", - vendor: "Mozilla", - version: "153.0", - buildID: "test", - }); +let receiver: BrowserContext; + +beforeEach(() => { + const session = getBrowserTest(); + + receiver = session.harness.contexts.create({kind: "background"}); + session.useContext(receiver); message = new Message(); - message["manager"].clear(); }); +const caller = () => { + const session = getBrowserTest(); + + return session.harness.messaging.forContext(session.context); +}; + +// Each call completes before restoring the receiver globals used for registrations. +async function sendFromCaller>( + type: K, + data: MessageData, + options?: MessageSendOptions +): Promise> { + const session = getBrowserTest(); + const restore = session.useContext(session.context); + + try { + return await message.send(type, data, options); + } finally { + restore(); + } +} + +function useTabReceiver(): void { + const session = getBrowserTest(); + + session.harness.tabs.set([createTabFixture({id: 123})]); + session.harness.contexts.documents.create({ + documentId: "document-1", + tabId: 123, + frameId: 1, + url: "https://example.test/", + }); + receiver = session.harness.contexts.create({ + kind: "contentScript", + tabId: 123, + frameId: 1, + documentId: "document-1", + url: "https://example.test/", + }); + session.useContext(receiver); +} + +function useFirefox(version = "153.0"): void { + const session = getBrowserTest(); + + session.useContext(receiver, "firefox"); + session.harness.runtime.getBrowserInfo.setResult({name: "Firefox", vendor: "Mozilla", version, buildID: "test"}); +} + describe("watch method", () => { test("adds and removes the message listener on subscribe and unsubscribe", async () => { - expect(chrome.runtime.onMessage.hasListeners()).toBe(false); + expect(receiver.onMessage.listenerCount()).toBe(0); const unsubscribe = message.watch("getStringLength", (str: string) => str.length); - expect(chrome.runtime.onMessage.hasListeners()).toBe(true); - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledWith(expect.any(Function)); + expect(receiver.onMessage.listenerCount()).toBe(1); unsubscribe(); - expect(chrome.runtime.onMessage.hasListeners()).toBe(false); - expect(chrome.runtime.onMessage.removeListener).toHaveBeenCalled(); + expect(receiver.onMessage.listenerCount()).toBe(0); }); test("registers a specific handler for a given message type", async () => { message.watch("getStringLength", (str: string) => str.length); message.watch("toUpperCase", (str: string) => str.toUpperCase()); - const result_1 = await message.send("getStringLength", "test"); - const result_2 = await message.send("toUpperCase", "test"); + const firstResult = await sendFromCaller("getStringLength", "test"); + const secondResult = await sendFromCaller("toUpperCase", "test"); - expect(result_1).toBe(4); - expect(result_2).toBe("TEST"); - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledTimes(1); + expect(firstResult).toBe(4); + expect(secondResult).toBe("TEST"); + expect(receiver.onMessage.listenerCount()).toBe(1); }); test("registers async handler and resolves with its returned value", async () => { - message.watch("fetchUser", name => new Promise(resolve => setTimeout(() => resolve({name}), 100))); + message.watch("fetchUser", async name => ({name})); - const result = await message.send("fetchUser", "Tom"); + const result = await sendFromCaller("fetchUser", "Tom"); expect(result).toEqual({name: "Tom"}); }); @@ -76,39 +118,42 @@ describe("watch method", () => { getStringLength: str => str.length, }); - const result_1 = await message.send("getStringLength", "test"); - const result_2 = await message.send("toUpperCase", "test"); + const firstResult = await sendFromCaller("getStringLength", "test"); + const secondResult = await sendFromCaller("toUpperCase", "test"); - expect(result_1).toBe(4); - expect(result_2).toBe("TEST"); - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledTimes(1); + expect(firstResult).toBe(4); + expect(secondResult).toBe("TEST"); + expect(receiver.onMessage.listenerCount()).toBe(1); }); - test("registers a general handler for all message types", async () => { + test("delivers every type to a general handler even when it sends no response", async () => { + const received: unknown[] = []; + message.watch((type, data) => { - console.log(`TYPE: ${type}, DATA: ${data}`); + received.push({type, data}); }); - const result_1 = await message.send("getStringLength", "test"); - const result_2 = await message.send("toUpperCase", "test"); - - expect(result_1).toBe(undefined); - expect(result_2).toBe(undefined); - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledTimes(1); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow("message port closed"); + await expect(sendFromCaller("toUpperCase", "test")).rejects.toThrow("message port closed"); + expect(received).toEqual([ + {type: "getStringLength", data: "test"}, + {type: "toUpperCase", data: "test"}, + ]); + expect(receiver.onMessage.listenerCount()).toBe(1); }); - test("prioritizes the first registered handler over others for the same message type", async () => { - message.watch("getStringLength", (str: string) => str.length); - message.watch((type, data) => { - console.log(`TYPE: ${type}, DATA: ${data}`); - }); + test("uses the response handler alongside a general observer", async () => { + const received: string[] = []; - const result_1 = await message.send("getStringLength", "test"); - const result_2 = await message.send("toUpperCase", "test"); + message.watch("getStringLength", str => str.length); + message.watch(type => { + received.push(type); + }); - expect(result_1).toBe(4); - expect(result_2).toBe(undefined); - expect(chrome.runtime.onMessage.addListener).toHaveBeenCalledTimes(1); + await expect(sendFromCaller("getStringLength", "test")).resolves.toBe(4); + await expect(sendFromCaller("toUpperCase", "test")).rejects.toThrow("message port closed"); + expect(received).toEqual(["getStringLength", "toUpperCase"]); + expect(receiver.onMessage.listenerCount()).toBe(1); }); }); @@ -116,7 +161,7 @@ describe("send method", () => { test("sends a message and returns the correct response from the handler", async () => { message.watch("getStringLength", str => str.length); - const result = await message.send("getStringLength", "test"); + const result = await sendFromCaller("getStringLength", "test"); expect(result).toBe(4); }); @@ -124,7 +169,7 @@ describe("send method", () => { test("sends a message without data and receive a correct response from the handler", async () => { message.watch("sayHello", () => "Hello"); - const result = await message.send("sayHello", undefined); + const result = await sendFromCaller("sayHello", undefined); expect(result).toBe("Hello"); }); @@ -132,88 +177,152 @@ describe("send method", () => { test("sends a message with correct structure", async () => { message.watch("getStringLength", str => str.length); - const result = await message.send("getStringLength", "test"); + const result = await sendFromCaller("getStringLength", "test"); expect(result).toBe(4); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( + expect(caller().runtime.sendMessage.calls[0].args).toEqual([ { id: expect.any(String), type: "getStringLength", data: "test", timestamp: expect.any(Number), }, - expect.any(Function) - ); + ]); }); test("sends a message to tab when options is a number", async () => { + useTabReceiver(); message.watch("getStringLength", str => str.length); - const result = await message.send("getStringLength", "test", 123); + const result = await sendFromCaller("getStringLength", "test", 123); - expect(chrome.tabs.sendMessage).toHaveBeenCalledWith( + expect(caller().tabs.sendMessage.calls[0].args).toEqual([ 123, expect.objectContaining({type: "getStringLength", data: "test"}), - undefined, - expect.any(Function) - ); + {}, + ]); expect(result).toBe(4); }); - test("sends a message to tab when options is a object with tabId, frameId and documentId", async () => { + test("sends a message to the addressed tab, frame and document", async () => { + useTabReceiver(); message.watch("getStringLength", str => str.length); - const result = await message.send("getStringLength", "test", {tabId: 123, frameId: 1, documentId: "1"}); + const result = await sendFromCaller("getStringLength", "test", { + tabId: 123, + frameId: 1, + documentId: "document-1", + }); - expect(chrome.tabs.sendMessage).toHaveBeenCalledWith( + expect(caller().tabs.sendMessage.calls[0].args).toEqual([ 123, expect.objectContaining({type: "getStringLength", data: "test"}), - {frameId: 1, documentId: "1"}, - expect.any(Function) - ); + {frameId: 1, documentId: "document-1"}, + ]); expect(result).toBe(4); }); test("preserves documentId for Firefox 153 and newer", async () => { - mockedEnv.isBrowser.mockReturnValue(true); + useTabReceiver(); + useFirefox(); message.watch("getStringLength", str => str.length); - const result = await message.send("getStringLength", "test", {tabId: 123, frameId: 1, documentId: "1"}); + const result = await sendFromCaller("getStringLength", "test", { + tabId: 123, + frameId: 1, + documentId: "document-1", + }); - expect(chrome.tabs.sendMessage).toHaveBeenCalledWith( + expect(caller().tabs.sendMessage.calls[0].args).toEqual([ 123, expect.objectContaining({type: "getStringLength", data: "test"}), - {frameId: 1, documentId: "1"}, - expect.any(Function) - ); - expect(mockedGetBrowserInfo).toHaveBeenCalledTimes(1); + {frameId: 1, documentId: "document-1"}, + ]); + expect(getBrowserTest().harness.runtime.getBrowserInfo.calls).toHaveLength(1); expect(result).toBe(4); }); test("rejects documentId targeting on Firefox older than 153", async () => { - mockedEnv.isBrowser.mockReturnValue(true); - mockedGetBrowserInfo.mockResolvedValue({ - name: "Firefox", - vendor: "Mozilla", - version: "152.0", - buildID: "test", - }); + useTabReceiver(); + useFirefox("152.0"); await expect( - message.send("getStringLength", "test", {tabId: 123, documentId: "document-1"}) + sendFromCaller("getStringLength", "test", {tabId: 123, documentId: "document-1"}) ).rejects.toBeInstanceOf(UnsupportedMessageTargetError); - expect(chrome.tabs.sendMessage).not.toHaveBeenCalled(); + expect(caller().tabs.sendMessage.calls).toHaveLength(0); }); test("caches the Firefox version used for documentId capability checks", async () => { - mockedEnv.isBrowser.mockReturnValue(true); + useTabReceiver(); + useFirefox(); + message.watch("getStringLength", str => str.length); + + await sendFromCaller("getStringLength", "test", {tabId: 123, documentId: "document-1"}); + await sendFromCaller("getStringLength", "again", {tabId: 123, documentId: "document-1"}); + + expect(getBrowserTest().harness.runtime.getBrowserInfo.calls).toHaveLength(1); + }); + + test.each(["frame", "document"] as const)("delivers only to the selected %s", async selector => { + useTabReceiver(); + message.watch("getStringLength", str => str.length); + + const {harness} = getBrowserTest(); + const sibling = harness.contexts.create({ + kind: "contentScript", + tabId: 123, + frameId: 2, + url: "https://example.test/other", + }); + const received: unknown[] = []; + + sibling.onMessage.on((body, _sender, reply) => { + received.push(body); + reply("wrong frame"); + }); + + const options: MessageSendOptions = + selector === "frame" ? {tabId: 123, frameId: 1} : {tabId: 123, documentId: "document-1"}; + + await expect(sendFromCaller("getStringLength", "test", options)).resolves.toBe(4); + expect(received).toEqual([]); + }); + + test("does not deliver to another tab", async () => { + useTabReceiver(); message.watch("getStringLength", str => str.length); + getBrowserTest().harness.tabs.set([createTabFixture({id: 123}), createTabFixture({id: 456})]); + + const result = sendFromCaller("getStringLength", "test", 456); + + await expect(result).rejects.toThrow("Receiving end does not exist"); + expect(isRemoteMessageError(await result.catch(error => error))).toBe(false); + }); + + test("rejects an unknown Firefox version before sending to a document", async () => { + useTabReceiver(); + useFirefox("unknown"); + + await expect( + sendFromCaller("getStringLength", "test", {tabId: 123, documentId: "document-1"}) + ).rejects.toBeInstanceOf(UnsupportedMessageTargetError); + expect(caller().tabs.sendMessage.calls).toHaveLength(0); + }); + + test("preserves a failed Firefox version lookup as the capability error cause", async () => { + useTabReceiver(); + useFirefox(); + + const cause = new Error("Browser info unavailable"); + + getBrowserTest().harness.runtime.getBrowserInfo.failNext(cause); - await message.send("getStringLength", "test", {tabId: 123, documentId: "document-1"}); - await message.send("getStringLength", "test", {tabId: 123, documentId: "document-2"}); + const result = sendFromCaller("getStringLength", "test", {tabId: 123, documentId: "document-1"}); - expect(mockedGetBrowserInfo).toHaveBeenCalledTimes(1); + await expect(result).rejects.toBeInstanceOf(UnsupportedMessageTargetError); + await expect(result).rejects.toMatchObject({cause: {message: cause.message}}); + expect(caller().tabs.sendMessage.calls).toHaveLength(0); }); test("rejects when a sync handler throws", async () => { @@ -221,11 +330,11 @@ describe("send method", () => { throw new TypeError(data); }); - await expect(message.send("throwSync", "sync boom")).rejects.toMatchObject({ + await expect(sendFromCaller("throwSync", "sync boom")).rejects.toMatchObject({ name: "TypeError", message: "sync boom", }); - await expect(message.send("throwSync", "sync boom")).rejects.toBeInstanceOf(TypeError); + await expect(sendFromCaller("throwSync", "sync boom")).rejects.toBeInstanceOf(TypeError); }); test("marks restored handler errors as remote", async () => { @@ -233,7 +342,7 @@ describe("send method", () => { throw new TypeError(data); }); - const error = await message.send("throwSync", "sync boom").catch(cause => cause); + const error = await sendFromCaller("throwSync", "sync boom").catch(cause => cause); expect(error).toBeInstanceOf(TypeError); expect(isRemoteMessageError(error)).toBe(true); @@ -244,11 +353,11 @@ describe("send method", () => { throw new RangeError(data); }); - await expect(message.send("throwAsync", "async boom")).rejects.toMatchObject({ + await expect(sendFromCaller("throwAsync", "async boom")).rejects.toMatchObject({ name: "RangeError", message: "async boom", }); - await expect(message.send("throwAsync", "async boom")).rejects.toBeInstanceOf(RangeError); + await expect(sendFromCaller("throwAsync", "async boom")).rejects.toBeInstanceOf(RangeError); }); test("rejects when a handler throws a primitive value", async () => { @@ -256,7 +365,7 @@ describe("send method", () => { throw data; }); - await expect(message.send("throwPrimitive", "primitive boom")).rejects.toMatchObject({ + await expect(sendFromCaller("throwPrimitive", "primitive boom")).rejects.toMatchObject({ name: "Error", message: "primitive boom", }); @@ -267,7 +376,7 @@ describe("send method", () => { throw {name: "CustomError", message: data}; }); - await expect(message.send("throwPlainObject", "plain object boom")).rejects.toMatchObject({ + await expect(sendFromCaller("throwPlainObject", "plain object boom")).rejects.toMatchObject({ name: "CustomError", message: "plain object boom", }); @@ -276,29 +385,29 @@ describe("send method", () => { test("returns envelope-like user data as payload", async () => { message.watch("envelopeLikePayload", () => ({ok: false, error: "user payload"})); - await expect(message.send("envelopeLikePayload", undefined)).resolves.toEqual({ + await expect(sendFromCaller("envelopeLikePayload", undefined)).resolves.toEqual({ ok: false, error: "user payload", }); }); test("returns raw invalid failure envelope as payload", async () => { - (chrome.runtime.sendMessage as jest.Mock).mockImplementationOnce((msg, callback) => { - callback?.({ok: false, error: "raw payload"}); + receiver.onMessage.on((_message, _sender, reply) => { + reply({ok: false, error: "raw payload"}); }); - await expect(message.send("rawEnvelopeLikePayload", undefined)).resolves.toEqual({ + await expect(sendFromCaller("rawEnvelopeLikePayload", undefined)).resolves.toEqual({ ok: false, error: "raw payload", }); }); test("returns raw success envelope-like response as payload", async () => { - (chrome.runtime.sendMessage as jest.Mock).mockImplementationOnce((msg, callback) => { - callback?.({ok: true, payload: "raw payload"}); + receiver.onMessage.on((_message, _sender, reply) => { + reply({ok: true, payload: "raw payload"}); }); - await expect(message.send("rawSuccessEnvelopeLikePayload", undefined)).resolves.toEqual({ + await expect(sendFromCaller("rawSuccessEnvelopeLikePayload", undefined)).resolves.toEqual({ ok: true, payload: "raw payload", }); @@ -313,42 +422,42 @@ describe("multiple handlers error for same message type", () => { message.watch("getStringLength", () => 1); message.watch("getStringLength", () => 2); - await expect(message.send("getStringLength", "test")).rejects.toThrow(errorMessage); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow(errorMessage); }); test('with two "map" handlers', async () => { message.watch({getStringLength: () => 1}); message.watch({getStringLength: () => 2}); - await expect(message.send("getStringLength", "test")).rejects.toThrow(errorMessage); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow(errorMessage); }); test('with two "general" handlers', async () => { message.watch(() => 1); message.watch(() => 2); - await expect(message.send("getStringLength", "test")).rejects.toThrow(errorMessage); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow(errorMessage); }); test('with "type" and "map" handlers', async () => { message.watch("getStringLength", () => 1); message.watch({getStringLength: () => 1}); - await expect(message.send("getStringLength", "test")).rejects.toThrow(errorMessage); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow(errorMessage); }); test('with "type" and "general" handlers', async () => { message.watch("getStringLength", () => 1); message.watch(() => 2); - await expect(message.send("getStringLength", "test")).rejects.toThrow(errorMessage); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow(errorMessage); }); test('with "map" and "general" handlers', async () => { message.watch({getStringLength: () => 1}); message.watch(() => 2); - await expect(message.send("getStringLength", "test")).rejects.toThrow(errorMessage); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow(errorMessage); }); test("with two instances watching the same message type", async () => { @@ -356,7 +465,7 @@ describe("multiple handlers error for same message type", () => { message.watch("getStringLength", data => data.length); secondMessage.watch("getStringLength", data => data.length); - await expect(message.send("getStringLength", "test")).rejects.toThrow(errorMessage); + await expect(sendFromCaller("getStringLength", "test")).rejects.toThrow(errorMessage); }); test("allows multiple handlers if one of them don't return value", async () => { @@ -367,6 +476,6 @@ describe("multiple handlers error for same message type", () => { } }); - expect(await message.send("getStringLength", "test")).toBe(4); + expect(await sendFromCaller("getStringLength", "test")).toBe(4); }); }); diff --git a/tests/README.md b/tests/README.md index 84ec174d..c74f0eb7 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,12 +42,12 @@ See [integration/README.md](integration/README.md) for browser requirements and ## Browser harness migration `tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for -unit, build and types projects. Only the 7 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. +unit, build and types projects. Only the 5 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. Real Chrome/Firefox integrations load neither setup. The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for -`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 15 files to review in total. +`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 13 files to review in total. Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. `npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 6af25fee..946efc90 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -1,7 +1,5 @@ { "legacyFiles": [ - "src/message/providers/Message.test.ts", - "src/message/adapters/react/useMessageHandler.test.ts", "src/offscreen/providers/Offscreen.test.ts", "src/offscreen/OffscreenBridge.test.ts", "src/locale/providers/ObservableLocale.test.ts", From 1eb9141a55e772a2007880b7401f0c0ebd251fa5 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:34:55 +0300 Subject: [PATCH 09/36] fix(message): preserve synchronous falsy responses --- .../react/use-message-handler.test.ts | 10 +++ src/message/handlers/MapHandler.ts | 4 +- src/message/handlers/SingleHandler.ts | 4 +- src/message/message-response.test.ts | 71 +++++++++++-------- 4 files changed, 57 insertions(+), 32 deletions(-) diff --git a/src/message/adapters/react/use-message-handler.test.ts b/src/message/adapters/react/use-message-handler.test.ts index cc67e533..5cd3e723 100644 --- a/src/message/adapters/react/use-message-handler.test.ts +++ b/src/message/adapters/react/use-message-handler.test.ts @@ -14,6 +14,7 @@ type MessageMap = { type TextMessageType = "getStringLength" | "toUpperCase"; type TextMessageHandler = MessageTargetHandler; type TextHandlerProps = {type: TextMessageType; handler: TextMessageHandler}; +type BooleanMessages = {isEnabled: () => boolean}; let caller: BrowserContextMessaging; let addListener: jest.SpyInstance; @@ -54,6 +55,15 @@ test("registers a handler and responds to another browser context", async () => await expect(send("getStringLength", "test")).resolves.toMatchObject({ok: true, payload: 4}); }); +test("preserves a synchronous false response through the hook", async () => { + renderHook(() => useMessageHandler<"isEnabled", BooleanMessages>("isEnabled", () => false)); + + await expect(caller.chrome.runtime.sendMessage({type: "isEnabled"})).resolves.toMatchObject({ + ok: true, + payload: false, + }); +}); + test("adds and removes the listener on mount and unmount", async () => { const {unmount} = renderHook(() => useMessageHandler<"getStringLength", MessageMap>("getStringLength", str => str.length) diff --git a/src/message/handlers/MapHandler.ts b/src/message/handlers/MapHandler.ts index 27351621..299498ee 100644 --- a/src/message/handlers/MapHandler.ts +++ b/src/message/handlers/MapHandler.ts @@ -22,7 +22,9 @@ export default class MapHandler extends AbstractHan if (handler) { if (typeof handler === "function") { - return handler(data, sender) || null; + const result = handler(data, sender); + + return result === undefined ? null : result; } else { throw new Error(`Handler for type "${type}" in object of handlers must be a function`); } diff --git a/src/message/handlers/SingleHandler.ts b/src/message/handlers/SingleHandler.ts index c291fbda..dbdbcd28 100644 --- a/src/message/handlers/SingleHandler.ts +++ b/src/message/handlers/SingleHandler.ts @@ -22,7 +22,9 @@ export default class SingleHandler extends Abstract sender: MessageSender ): MessageResponse> | undefined | null { if (type === this.messageType) { - return this.handler(data, sender) || null; + const result = this.handler(data, sender); + + return result === undefined ? null : result; } } } diff --git a/src/message/message-response.test.ts b/src/message/message-response.test.ts index 2c1a53a0..38c3de67 100644 --- a/src/message/message-response.test.ts +++ b/src/message/message-response.test.ts @@ -28,21 +28,6 @@ describe.each(["runtime", "tab"] as const)("Message response over %s messaging", options = target === "runtime" ? undefined : {tabId: 7, frameId: 0}; }); - const send = (handler: ResponseHandler): Promise => { - message.watch("probe", handler); - restoreContext(); - - return message.send("probe", undefined, options); - }; - - test("returns undefined when an async handler has no return value", async () => { - await expect(send(async () => {})).resolves.toBeUndefined(); - }); - - test("preserves the null response of a sync handler with no return value", async () => { - await expect(send(() => {})).resolves.toBeNull(); - }); - test("preserves the channel error when a general handler sends no response", async () => { message.watch(() => {}); restoreContext(); @@ -53,24 +38,50 @@ describe.each(["runtime", "tab"] as const)("Message response over %s messaging", expect(isRemoteMessageError(await result.catch(error => error))).toBe(false); }); - test.each([null, false, 0, ""])("preserves the async result %p", async value => { - await expect(send(async () => value)).resolves.toBe(value); - }); + describe.each(["single", "map"] as const)("with %s registration", registration => { + const send = (handler: ResponseHandler): Promise => { + if (registration === "single") { + message.watch("probe", handler); + } else { + message.watch({probe: handler}); + } - test("unwraps only the outer envelope when the payload resembles a response", async () => { - const payload = {[MessageResultEnvelopeProperty]: true, ok: true}; + restoreContext(); - await expect(send(async () => payload)).resolves.toEqual(payload); - }); - - test.each([false, true])("preserves remote errors from an async=%s handler", async asynchronous => { - const fail = () => { - throw new TypeError("Response failed"); + return message.send("probe", undefined, options); }; - const result = send(asynchronous ? async () => fail() : fail); - await expect(result).rejects.toBeInstanceOf(TypeError); - await expect(result).rejects.toThrow("Response failed"); - expect(isRemoteMessageError(await result.catch(error => error))).toBe(true); + test("returns undefined when an async handler has no return value", async () => { + await expect(send(async () => {})).resolves.toBeUndefined(); + }); + + test("preserves the null response of a sync handler with no return value", async () => { + await expect(send(() => {})).resolves.toBeNull(); + }); + + test.each([null, false, 0, ""])("preserves the sync result %p", async value => { + await expect(send(() => value)).resolves.toBe(value); + }); + + test.each([null, false, 0, ""])("preserves the async result %p", async value => { + await expect(send(async () => value)).resolves.toBe(value); + }); + + test("unwraps only the outer envelope when the payload resembles a response", async () => { + const payload = {[MessageResultEnvelopeProperty]: true, ok: true}; + + await expect(send(async () => payload)).resolves.toEqual(payload); + }); + + test.each([false, true])("preserves remote errors from an async=%s handler", async asynchronous => { + const fail = () => { + throw new TypeError("Response failed"); + }; + const result = send(asynchronous ? async () => fail() : fail); + + await expect(result).rejects.toBeInstanceOf(TypeError); + await expect(result).rejects.toThrow("Response failed"); + expect(isRemoteMessageError(await result.catch(error => error))).toBe(true); + }); }); }); From 3aa1c4a0e2a59ea487c1da66e157b98e085cb495 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 14:35:09 +0300 Subject: [PATCH 10/36] fix(message): update React handlers after render commits --- .../react/use-message-handler.test.ts | 65 ++++++++++++++++++- .../adapters/react/use-message-handler.ts | 10 ++- 2 files changed, 71 insertions(+), 4 deletions(-) diff --git a/src/message/adapters/react/use-message-handler.test.ts b/src/message/adapters/react/use-message-handler.test.ts index 5cd3e723..0b1efda2 100644 --- a/src/message/adapters/react/use-message-handler.test.ts +++ b/src/message/adapters/react/use-message-handler.test.ts @@ -1,5 +1,5 @@ import React from "react"; -import {act, cleanup, renderHook} from "@testing-library/react"; +import {act, cleanup, render, renderHook} from "@testing-library/react"; import type {BrowserContextMessaging} from "@addon-core/browser/testing"; import {getBrowserTest} from "@tests/browser-harness/session"; import type {MessageData, MessageTargetHandler, MessageType} from "@typing/message"; @@ -14,6 +14,7 @@ type MessageMap = { type TextMessageType = "getStringLength" | "toUpperCase"; type TextMessageHandler = MessageTargetHandler; type TextHandlerProps = {type: TextMessageType; handler: TextMessageHandler}; +type SuspendedHandlerProps = {value: string}; type BooleanMessages = {isEnabled: () => boolean}; let caller: BrowserContextMessaging; @@ -64,6 +65,68 @@ test("preserves a synchronous false response through the hook", async () => { }); }); +test.each(["commit", "replace"] as const)( + "keeps the committed handler during a suspended render until its %s", + async completion => { + let ready = false; + let release!: () => void; + let update!: React.Dispatch>; + const pending = new Promise(resolve => { + release = resolve; + }); + + function SuspendedHandler({value}: SuspendedHandlerProps) { + useMessageHandler<"sayHello", MessageMap>("sayHello", () => value); + + if (value === "pending" && !ready) { + throw pending; + } + + return React.createElement("div", null, value); + } + + function Parent() { + const [value, setValue] = React.useState("committed"); + + update = setValue; + + return React.createElement( + React.Suspense, + {fallback: "loading"}, + React.createElement(SuspendedHandler, {value}) + ); + } + + const view = render(React.createElement(Parent)); + + await act(async () => { + React.startTransition(() => update("pending")); + }); + + expect(view.container.textContent).toBe("committed"); + await expect(send("sayHello", undefined)).resolves.toMatchObject({payload: "committed"}); + expect(addListener).toHaveBeenCalledTimes(1); + expect(removeListener).not.toHaveBeenCalled(); + + await act(async () => { + if (completion === "replace") { + update("replacement"); + } else { + ready = true; + release(); + await pending; + } + }); + + const expected = completion === "replace" ? "replacement" : "pending"; + + expect(view.container.textContent).toBe(expected); + await expect(send("sayHello", undefined)).resolves.toMatchObject({payload: expected}); + expect(addListener).toHaveBeenCalledTimes(1); + expect(removeListener).not.toHaveBeenCalled(); + } +); + test("adds and removes the listener on mount and unmount", async () => { const {unmount} = renderHook(() => useMessageHandler<"getStringLength", MessageMap>("getStringLength", str => str.length) diff --git a/src/message/adapters/react/use-message-handler.ts b/src/message/adapters/react/use-message-handler.ts index 801b4542..677ef0e3 100644 --- a/src/message/adapters/react/use-message-handler.ts +++ b/src/message/adapters/react/use-message-handler.ts @@ -1,4 +1,4 @@ -import {useCallback, useEffect, useRef} from "react"; +import {useCallback, useLayoutEffect, useRef} from "react"; import {MessageDictionary, MessageTargetHandler, MessageType} from "@typing/message"; import {Message} from "@message/providers"; @@ -14,13 +14,17 @@ export default function useMessageHandler, T extends Me } const handlerRef = useRef(handler); - handlerRef.current = handler; + + useLayoutEffect(() => { + // A suspended or abandoned render must not replace the active handler. + handlerRef.current = handler; + }, [handler]); const stableWrapper = useCallback>((data, sender) => { return handlerRef.current(data, sender); }, []); - useEffect(() => { + useLayoutEffect(() => { return messageRef.current!.watch(type, stableWrapper); }, [type, stableWrapper]); } From 25fe7e4bc78fd8e95a04cbc03a3684097b5d3c1b Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 15:10:18 +0300 Subject: [PATCH 11/36] test(offscreen): migrate lifecycle suites to browser harness --- src/offscreen/OffscreenBridge.test.ts | 170 ++++++-- src/offscreen/providers/Offscreen.test.ts | 392 ------------------ .../providers/ProxyOffscreen.test.ts | 354 ++++++++++++++++ .../providers/RegisterOffscreen.test.ts | 58 +++ tests/README.md | 12 +- tests/browser-harness/migration.json | 5 - tests/offscreen/MockLockManager.ts | 90 ++++ 7 files changed, 652 insertions(+), 429 deletions(-) delete mode 100644 src/offscreen/providers/Offscreen.test.ts create mode 100644 src/offscreen/providers/ProxyOffscreen.test.ts create mode 100644 src/offscreen/providers/RegisterOffscreen.test.ts create mode 100644 tests/offscreen/MockLockManager.ts diff --git a/src/offscreen/OffscreenBridge.test.ts b/src/offscreen/OffscreenBridge.test.ts index 2661d0a4..ee184795 100644 --- a/src/offscreen/OffscreenBridge.test.ts +++ b/src/offscreen/OffscreenBridge.test.ts @@ -1,13 +1,11 @@ -import {isBackground} from "@addon-core/browser"; - -import {Message} from "@message/providers"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import {OffscreenBackground, ProxyOffscreen, RegisterOffscreen} from "./index"; +import type {RpcAsyncProxy} from "@typing/rpc"; import OffscreenBridge from "./OffscreenBridge"; import {OffscreenBridgeReadyMessageType} from "@typing/offscreen"; -const mockedIsBackground = isBackground as jest.MockedFunction; - const parameters = { reasons: ["TESTING" as const], url: "offscreen.html", @@ -27,17 +25,24 @@ const dispatchReady = (iframe: HTMLIFrameElement) => { describe("OffscreenBridge", () => { beforeEach(() => { - jest.clearAllMocks(); - jest.restoreAllMocks(); - - document.body.innerHTML = ""; - mockedIsBackground.mockReturnValue(true); - - (OffscreenBridge as any).instance = undefined; - }); - - afterEach(() => { - jest.useRealTimers(); + const session = getBrowserTest(); + const originalUrl = location.href; + const background = session.harness.contexts.create({kind: "background"}); + + session.harness.runtime.setManifest({ + name: "Offscreen test", + version: "1.0.0", + manifest_version: 2, + background: {scripts: ["background.js"]}, + }); + process.env.MANIFEST_VERSION = "2"; + session.useContext(background, "firefox"); + history.replaceState(null, "", "/_generated_background_page.html"); + document.body.replaceChildren(); + session.addCleanup(() => { + document.body.replaceChildren(); + history.replaceState(null, "", originalUrl); + }); }); test("creates an iframe in background context", async () => { @@ -74,28 +79,60 @@ describe("OffscreenBridge", () => { expect(resolved).toBe(true); }); - test("sends creation request through message outside background context", async () => { - mockedIsBackground.mockReturnValue(false); + test("sends creation request through real messaging outside background context", async () => { + jest.useFakeTimers(); - const send = jest.spyOn(Message.prototype, "send").mockResolvedValue(undefined); - const bridge = new OffscreenBridge(); + const session = getBrowserTest(); - await expect(bridge.create(parameters)).resolves.toBeUndefined(); + new OffscreenBackground().build(); + session.useContext(session.context); + history.replaceState(null, "", "/popup.html"); + + const creation = new OffscreenBridge().create(parameters); + + await jest.advanceTimersByTimeAsync(0); + + const iframe = document.querySelector("iframe"); - expect(send).toHaveBeenCalledWith("offscreen-background", parameters); + expect(iframe?.getAttribute("src")).toBe(parameters.url); + dispatchReady(iframe!); + + await expect(creation).resolves.toBeUndefined(); + expect(session.harness.messaging.forContext(session.context).runtime.sendMessage.calls[0].args).toEqual([ + expect.objectContaining({type: "offscreen-background", data: parameters}), + ]); + }); + + test("propagates a failed creation response from the background context", async () => { + const session = getBrowserTest(); + + new OffscreenBackground().build(); + session.useContext(session.context); + history.replaceState(null, "", "/popup.html"); + + const creation = new OffscreenBridge().create(parameters); + const rejection = expect(creation).rejects.toThrow(`Offscreen iframe failed to load: ${parameters.url}`); + + await wait(); + document.querySelector("iframe")!.dispatchEvent(new Event("error")); + + await rejection; expect(document.querySelector("iframe")).toBeNull(); }); - test("uses a singleton instance for static createOffscreen", async () => { - const create = jest.spyOn(OffscreenBridge.prototype, "create").mockResolvedValue(undefined); + test("uses a singleton instance and shares pending static creation", async () => { + const bridge = OffscreenBridge.getInstance(); + const first = OffscreenBridge.createOffscreen(parameters); + const second = OffscreenBridge.createOffscreen(parameters); + const iframe = document.querySelector("iframe"); - await OffscreenBridge.createOffscreen(parameters); - await OffscreenBridge.createOffscreen(parameters); + expect(OffscreenBridge.getInstance()).toBe(bridge); + expect(document.querySelectorAll("iframe")).toHaveLength(1); + dispatchReady(iframe!); - expect(create).toHaveBeenCalledTimes(2); - expect(create.mock.instances[0]).toBe(create.mock.instances[1]); - expect(create).toHaveBeenNthCalledWith(1, parameters); - expect(create).toHaveBeenNthCalledWith(2, parameters); + await expect(Promise.all([first, second])).resolves.toEqual([undefined, undefined]); + await OffscreenBridge.createOffscreen(parameters); + expect(document.querySelector("iframe")).toBe(iframe); }); test("waits for an in-flight iframe creation when the same URL is requested again", async () => { @@ -176,6 +213,31 @@ describe("OffscreenBridge", () => { await expect(second).resolves.toBeUndefined(); }); + test.each(["source", "origin", "type"] as const)("ignores a ready message with the wrong %s", async field => { + const bridge = new OffscreenBridge(); + const creation = bridge.create(parameters); + const iframe = document.querySelector("iframe")!; + let resolved = false; + + creation.then(() => { + resolved = true; + }); + + window.dispatchEvent( + new MessageEvent("message", { + data: {type: field === "type" ? "unrelated" : OffscreenBridgeReadyMessageType}, + origin: field === "origin" ? "https://other.test" : location.origin, + source: field === "source" ? window : iframe.contentWindow, + }) + ); + + await wait(); + expect(resolved).toBe(false); + dispatchReady(iframe); + + await expect(creation).resolves.toBeUndefined(); + }); + test("removes the iframe when ready message times out", async () => { jest.useFakeTimers(); @@ -201,3 +263,51 @@ describe("OffscreenBridge", () => { await expect(second).resolves.toBeUndefined(); }); }); + +test.each([ + {profile: "firefox" as const, version: 2 as const}, + {profile: "firefox" as const, version: 3 as const}, + {profile: "chrome" as const, version: 2 as const}, +])("ProxyOffscreen waits for iframe readiness on $profile MV$version", async ({profile, version}) => { + const session = getBrowserTest(); + const originalUrl = location.href; + const background = session.harness.contexts.create({kind: "background"}); + const frameContext = session.harness.contexts.create({kind: "extensionPage"}); + const service = {sum: (a: number, b: number) => a + b}; + + const manifest = { + name: "Offscreen test", + version: "1.0.0", + background: {scripts: ["background.js"]}, + }; + + // Firefox MV3 uses background.scripts; the kit accepts manifests through Chrome's narrower type. + session.harness.runtime.setManifest({...manifest, manifest_version: version} as chrome.runtime.Manifest); + process.env.MANIFEST_VERSION = String(version); + session.useContext(background, profile); + history.replaceState(null, "", "/_generated_background_page.html"); + session.addCleanup(() => { + document.body.replaceChildren(); + history.replaceState(null, "", originalUrl); + }); + + const restore = session.useContext(frameContext); + const registration = new RegisterOffscreen("math", () => service); + + registration.register(); + session.addCleanup(() => registration.destroy()); + restore(); + + const proxy = new ProxyOffscreen<"math", RpcAsyncProxy>("math", parameters).get(); + const result = proxy.sum(1, 2); + const iframe = document.querySelector("iframe"); + const caller = session.harness.messaging.forContext(background); + + expect(iframe).not.toBeNull(); + expect(caller.runtime.sendMessage.calls).toHaveLength(0); + dispatchReady(iframe!); + + await expect(result).resolves.toBe(3); + expect(caller.runtime.sendMessage.calls).toHaveLength(1); + expect(session.harness.offscreen.createDocument.calls).toHaveLength(0); +}); diff --git a/src/offscreen/providers/Offscreen.test.ts b/src/offscreen/providers/Offscreen.test.ts deleted file mode 100644 index 8e5dcfd2..00000000 --- a/src/offscreen/providers/Offscreen.test.ts +++ /dev/null @@ -1,392 +0,0 @@ -import {closeOffscreen, createOffscreen, hasOffscreen, hasOffscreenPath} from "@addon-core/browser"; -import {isOffscreen} from "@offscreen/utils"; - -import {isBrowser} from "@main/env"; - -import MessageManager from "@message/MessageManager"; - -import ProxyOffscreen from "./ProxyOffscreen"; -import RegisterOffscreen from "./RegisterOffscreen"; -import OffscreenManager from "../OffscreenManager"; - -import {RpcAsyncProxy} from "@typing/rpc"; -import {MessageTypeSeparator} from "@typing/message"; - -jest.mock("@offscreen/utils", () => ({isOffscreen: jest.fn()})); -jest.mock("@main/env", () => ({isBrowser: jest.fn()})); - -const mockedCloseOffscreen = closeOffscreen as jest.MockedFunction; -const mockedCreateOffscreen = createOffscreen as jest.MockedFunction; -const mockedHasOffscreen = hasOffscreen as jest.MockedFunction; -const mockedHasOffscreenPath = hasOffscreenPath as jest.MockedFunction; -const mockedIsBrowser = isBrowser as unknown as jest.Mock; -const mockedIsOffscreen = isOffscreen as jest.MockedFunction; - -type MockLockMode = "exclusive" | "shared"; -type MockLockOptions = {mode: MockLockMode}; -type MockLockCallback = () => unknown | Promise; - -type LockTask = { - mode: MockLockMode; - callback: MockLockCallback; - resolve: (value: unknown) => void; - reject: (error: unknown) => void; -}; - -class MockLockManager { - private readonly queues = new Map(); - - private readonly states = new Map(); - - public request(name: string, options: MockLockOptions, callback: MockLockCallback): Promise { - return new Promise((resolve, reject) => { - const queue = this.queues.get(name) ?? []; - queue.push({mode: options.mode, callback, resolve, reject}); - this.queues.set(name, queue); - this.drain(name); - }); - } - - private state(name: string): {shared: number; exclusive: boolean} { - const state = this.states.get(name) ?? {shared: 0, exclusive: false}; - - this.states.set(name, state); - - return state; - } - - private drain(name: string): void { - const queue = this.queues.get(name); - const state = this.state(name); - - if (!queue?.length || state.exclusive) { - return; - } - - const task = queue[0]; - - if (task.mode === "exclusive") { - if (state.shared > 0) { - return; - } - - queue.shift(); - state.exclusive = true; - this.run(name, task); - - return; - } - - while (queue[0]?.mode === "shared" && !state.exclusive) { - const sharedTask = queue.shift()!; - - state.shared++; - this.run(name, sharedTask); - } - } - - private run(name: string, task: LockTask): void { - Promise.resolve() - .then(task.callback) - .then(task.resolve, task.reject) - .finally(() => { - const state = this.state(name); - - if (task.mode === "exclusive") { - state.exclusive = false; - } else { - state.shared--; - } - - this.drain(name); - }); - } -} - -let existingOffscreenPath: string | undefined; -let blockedResolvers: Array<() => void> = []; -let blockedStarted = 0; - -const wait = (timeout = 0) => new Promise(resolve => setTimeout(resolve, timeout)); -const waitForBlockedCalls = async (count: number): Promise => { - for (let attempt = 0; attempt < 10; attempt++) { - if (blockedStarted >= count) { - return; - } - - await wait(); - } - - throw new Error(`Expected ${count} blocked offscreen calls to start, got ${blockedStarted}`); -}; - -beforeEach(async () => { - jest.clearAllMocks(); - - existingOffscreenPath = parameters.url; - blockedResolvers = []; - blockedStarted = 0; - - Object.defineProperty(navigator, "locks", { - configurable: true, - value: new MockLockManager(), - }); - - mockedHasOffscreen.mockImplementation(async () => existingOffscreenPath !== undefined); - mockedHasOffscreenPath.mockImplementation(async (path: string) => existingOffscreenPath === path); - mockedCloseOffscreen.mockImplementation(async () => { - existingOffscreenPath = undefined; - }); - mockedCreateOffscreen.mockImplementation(async (offscreenParameters: chrome.offscreen.CreateParameters) => { - existingOffscreenPath = offscreenParameters.url; - }); - - OffscreenManager.getInstance().clear(); - MessageManager.getInstance().clear(); - - new RegisterOffscreen(offscreenName, () => MatchService).register(); -}); - -const MatchService = { - sum: (a: number, b: number): number => a + b, - asyncSum: (a: number, b: number): Promise => { - return new Promise(resolve => setTimeout(() => resolve(a + b), 100)); - }, - blockedSum: (a: number, b: number): Promise => { - blockedStarted++; - - return new Promise(resolve => { - blockedResolvers.push(() => resolve(a + b)); - }); - }, - one: 1, - obj: { - concat: (a: string, b: string): string => a + " " + b, - zero: 0, - }, -}; - -type OffscreenType = typeof MatchService; -type OffscreenProxyType = RpcAsyncProxy; - -const offscreenName = "math"; -const parameters = { - reasons: ["TESTING" as const], - url: "offscreen.html", - justification: "for testing", -}; - -describe("ProxyOffscreen", () => { - beforeEach(async () => { - mockedIsOffscreen.mockReturnValue(false); - mockedIsBrowser.mockReturnValue(false); - }); - - test("throws an error when get() is called in offscreen context", async () => { - mockedIsOffscreen.mockReturnValue(true); - - const proxy = new ProxyOffscreen(offscreenName, parameters); - - expect(() => proxy.get()).toThrow( - `You are trying to get proxy offscreen service "${offscreenName}" from offscreen. You can get original offscreen service instead` - ); - }); - - test("returns a proxy when not in offscreen context", () => { - const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); - - //@ts-ignore - expect(offscreen.__proxy).toBe(true); - }); - - test("invokes remote methods using Message.send", async () => { - const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); - - expect(await offscreen.sum(1, 2)).toBe(3); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `offscreen${MessageTypeSeparator}${offscreenName}`, - data: { - path: "sum", - args: [1, 2], - }, - }), - expect.any(Function) - ); - }); - - test("accesses property on offscreen service object ", async () => { - const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); - - expect(await offscreen.one()).toBe(1); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `offscreen${MessageTypeSeparator}${offscreenName}`, - data: { - path: "one", - args: [], - }, - }), - expect.any(Function) - ); - }); - - test("accesses nested method or property ", async () => { - const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); - - expect(await offscreen.obj.concat("Hello", "world")).toBe("Hello world"); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `offscreen${MessageTypeSeparator}${offscreenName}`, - data: { - path: "obj.concat", - args: ["Hello", "world"], - }, - }), - expect.any(Function) - ); - - expect(await offscreen.obj.zero()).toBe(0); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `offscreen${MessageTypeSeparator}${offscreenName}`, - data: { - path: "obj.zero", - args: [], - }, - }), - expect.any(Function) - ); - }); - - test("handles proxied async methods that return promises", async () => { - const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); - - expect(await offscreen.asyncSum(1, 2)).toBe(3); - }); - - test("does not recreate offscreen when URL hasn't changed", async () => { - jest.clearAllMocks(); - existingOffscreenPath = undefined; - - const proxyInstance = new ProxyOffscreen(offscreenName, parameters); - const offscreen = proxyInstance.get(); - - await offscreen.sum(1, 2); - - await offscreen.sum(3, 4); - - expect(createOffscreen).toHaveBeenCalledTimes(1); - expect(closeOffscreen).toHaveBeenCalledTimes(0); - }); - - test("recreates offscreen when URL changes", async () => { - jest.clearAllMocks(); - existingOffscreenPath = undefined; - - const proxyInstance1 = new ProxyOffscreen(offscreenName, parameters); - const offscreen1 = proxyInstance1.get(); - await offscreen1.sum(1, 2); - - const differentParams = {...parameters, url: "different-offscreen.html"}; - const proxyInstance2 = new ProxyOffscreen( - offscreenName, - differentParams - ); - const offscreen2 = proxyInstance2.get(); - await offscreen2.sum(3, 4); - - expect(createOffscreen).toHaveBeenCalledTimes(2); - expect(closeOffscreen).toHaveBeenCalledTimes(1); - }); - - test("runs same URL calls in parallel without recreating offscreen", async () => { - const proxyInstance = new ProxyOffscreen(offscreenName, parameters); - const offscreen = proxyInstance.get(); - - const first = offscreen.blockedSum(1, 2); - const second = offscreen.blockedSum(3, 4); - - await waitForBlockedCalls(2); - - blockedResolvers.forEach(resolve => resolve()); - - await expect(Promise.all([first, second])).resolves.toEqual([3, 7]); - - expect(createOffscreen).toHaveBeenCalledTimes(0); - expect(closeOffscreen).toHaveBeenCalledTimes(0); - }); - - test("waits for active calls before switching to a different URL", async () => { - const proxyInstance1 = new ProxyOffscreen(offscreenName, parameters); - const offscreen1 = proxyInstance1.get(); - const first = offscreen1.blockedSum(1, 2); - - await waitForBlockedCalls(1); - - const differentParams = {...parameters, url: "different-offscreen.html"}; - const proxyInstance2 = new ProxyOffscreen( - offscreenName, - differentParams - ); - const offscreen2 = proxyInstance2.get(); - const second = offscreen2.sum(3, 4); - - await wait(); - - expect(createOffscreen).toHaveBeenCalledTimes(0); - expect(closeOffscreen).toHaveBeenCalledTimes(0); - - blockedResolvers[0](); - - await expect(first).resolves.toBe(3); - await expect(second).resolves.toBe(7); - - expect(closeOffscreen).toHaveBeenCalledTimes(1); - expect(createOffscreen).toHaveBeenCalledWith(differentParams); - }); -}); - -describe("RegisterOffscreen", () => { - beforeEach(async () => { - mockedIsOffscreen.mockReturnValue(true); - }); - - test("throws an error when get() is called outside offscreen context", async () => { - mockedIsOffscreen.mockReturnValue(false); - - const proxy = new RegisterOffscreen(offscreenName, () => MatchService); - - expect(() => proxy.get()).toThrow( - `Offscreen service "${offscreenName}" can be getting only from offscreen context.` - ); - }); - - test("returns real offscreen service when called in offscreen context", () => { - const offscreen = new RegisterOffscreen( - offscreenName, - () => MatchService - ).get(); - - //@ts-ignore - expect(offscreen.__proxy).toBe(undefined); - }); - - test("invokes methods directly without using Message.send in offscreen", async () => { - const offscreen = new RegisterOffscreen( - offscreenName, - () => MatchService - ).get(); - - expect(offscreen.sum(1, 2)).toBe(3); - expect(chrome.runtime.sendMessage).toHaveBeenCalledTimes(0); - }); - - test("throws an error when attempting to register the same offscreen service twice", async () => { - const offscreen = new RegisterOffscreen(offscreenName, () => MatchService); - - expect(() => offscreen.register()).toThrow( - `A instance with name "${offscreenName}" already exists. The name must be unique.` - ); - }); -}); diff --git a/src/offscreen/providers/ProxyOffscreen.test.ts b/src/offscreen/providers/ProxyOffscreen.test.ts new file mode 100644 index 00000000..0e175864 --- /dev/null +++ b/src/offscreen/providers/ProxyOffscreen.test.ts @@ -0,0 +1,354 @@ +import {closeOffscreen} from "@addon-core/browser"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import MockLockManager from "@tests/offscreen/MockLockManager"; +import {ProxyOffscreen, RegisterOffscreen} from "./index"; +import {RpcAsyncProxy} from "@typing/rpc"; +import {MessageTypeSeparator} from "@typing/message"; +import {OffscreenGlobalAccess} from "@typing/offscreen"; + +let blockedResolvers: Array<() => void> = []; +let blockedStarted = 0; +let locks: MockLockManager; + +const wait = () => new Promise(resolve => setTimeout(resolve)); +const waitUntil = async (condition: () => boolean, description: string): Promise => { + const deadline = Date.now() + 2000; + + while (Date.now() < deadline) { + if (condition()) { + return; + } + + await wait(); + } + + throw new Error(`Timed out waiting for ${description}`); +}; + +const waitForBlockedCalls = (count: number) => + waitUntil(() => blockedStarted >= count, `${count} blocked offscreen calls`); + +const lifecycle = () => getBrowserTest().harness.offscreen; +const calls = () => getBrowserTest().harness.messaging.forContext(getBrowserTest().context).runtime.sendMessage.calls; + +beforeEach(() => { + const session = getBrowserTest(); + const descriptor = Object.getOwnPropertyDescriptor(navigator, "locks"); + + blockedResolvers = []; + blockedStarted = 0; + locks = new MockLockManager(); + Object.defineProperty(navigator, "locks", {configurable: true, value: locks}); + session.addCleanup(() => { + if (descriptor) { + Object.defineProperty(navigator, "locks", descriptor); + } else { + Reflect.deleteProperty(navigator, "locks"); + } + }); + + // The kit owns document lifetime; this boundary hook starts the real application transport. + // Replace the passthrough spy with a post-creation hook when the kit provides one (tests/README.md). + const createContext = session.harness.contexts.create.bind(session.harness.contexts); + + jest.spyOn(session.harness.contexts, "create").mockImplementation(options => { + const context = createContext(options); + + if (options.kind === "offscreen") { + const restore = session.useContext(context); + const registration = new RegisterOffscreen(offscreenName, () => mathService); + + try { + registration.register(); + } finally { + restore(); + } + + const detach = context.onDispose(() => registration.destroy()); + + session.addCleanup(() => { + detach(); + registration.destroy(); + }); + } + + return context; + }); + + session.harness.contexts.create({kind: "offscreen", url: chrome.runtime.getURL(parameters.url)}); +}); + +const mathService = { + sum: (a: number, b: number): number => a + b, + asyncSum: (a: number, b: number): Promise => { + return new Promise(resolve => setTimeout(() => resolve(a + b), 100)); + }, + blockedSum: (a: number, b: number): Promise => { + blockedStarted++; + + return new Promise(resolve => { + blockedResolvers.push(() => resolve(a + b)); + }); + }, + one: 1, + obj: { + concat: (a: string, b: string): string => a + " " + b, + zero: 0, + }, +}; + +type OffscreenType = typeof mathService; +type OffscreenProxyType = RpcAsyncProxy; + +const offscreenName = "math"; +const parameters = { + reasons: ["TESTING" as const], + url: "offscreen.html", + justification: "for testing", +}; + +describe("ProxyOffscreen", () => { + test("throws an error when get() is called in offscreen context", async () => { + globalThis[OffscreenGlobalAccess] = true; + + const proxy = new ProxyOffscreen(offscreenName, parameters); + + expect(() => proxy.get()).toThrow( + `You are trying to get proxy offscreen service "${offscreenName}" from offscreen. You can get original offscreen service instead` + ); + }); + + test("returns a proxy when not in offscreen context", () => { + const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); + + expect(Reflect.get(offscreen, "__proxy")).toBe(true); + }); + + test("invokes remote methods using Message.send", async () => { + const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); + + expect(await offscreen.sum(1, 2)).toBe(3); + expect(calls().at(-1)?.args).toEqual([ + expect.objectContaining({ + type: `offscreen${MessageTypeSeparator}${offscreenName}`, + data: { + path: "sum", + args: [1, 2], + }, + }), + ]); + }); + + test("accesses property on offscreen service object ", async () => { + const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); + + expect(await offscreen.one()).toBe(1); + expect(calls().at(-1)?.args).toEqual([ + expect.objectContaining({ + type: `offscreen${MessageTypeSeparator}${offscreenName}`, + data: { + path: "one", + args: [], + }, + }), + ]); + }); + + test("accesses nested method or property ", async () => { + const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); + + expect(await offscreen.obj.concat("Hello", "world")).toBe("Hello world"); + expect(calls().at(-1)?.args).toEqual([ + expect.objectContaining({ + type: `offscreen${MessageTypeSeparator}${offscreenName}`, + data: { + path: "obj.concat", + args: ["Hello", "world"], + }, + }), + ]); + + expect(await offscreen.obj.zero()).toBe(0); + expect(calls().at(-1)?.args).toEqual([ + expect.objectContaining({ + type: `offscreen${MessageTypeSeparator}${offscreenName}`, + data: { + path: "obj.zero", + args: [], + }, + }), + ]); + }); + + test("handles proxied async methods that return promises", async () => { + const offscreen = new ProxyOffscreen(offscreenName, parameters).get(); + + jest.useFakeTimers(); + + const result = offscreen.asyncSum(1, 2); + + await jest.advanceTimersByTimeAsync(100); + await expect(result).resolves.toBe(3); + }); + + test("does not recreate offscreen when URL hasn't changed", async () => { + getBrowserTest().harness.contexts.remove(lifecycle().context!.info.contextId); + + const proxyInstance = new ProxyOffscreen(offscreenName, parameters); + const offscreen = proxyInstance.get(); + + await offscreen.sum(1, 2); + + await offscreen.sum(3, 4); + + expect(lifecycle().createDocument.calls).toHaveLength(1); + expect(lifecycle().closeDocument.calls).toHaveLength(0); + }); + + test("recreates offscreen when URL changes", async () => { + getBrowserTest().harness.contexts.remove(lifecycle().context!.info.contextId); + + const proxyInstance1 = new ProxyOffscreen(offscreenName, parameters); + const offscreen1 = proxyInstance1.get(); + await offscreen1.sum(1, 2); + + const differentParams = {...parameters, url: "different-offscreen.html"}; + const proxyInstance2 = new ProxyOffscreen( + offscreenName, + differentParams + ); + const offscreen2 = proxyInstance2.get(); + await offscreen2.sum(3, 4); + + expect(lifecycle().createDocument.calls).toHaveLength(2); + expect(lifecycle().closeDocument.calls).toHaveLength(1); + }); + + test("runs same URL calls in parallel without recreating offscreen", async () => { + const proxyInstance = new ProxyOffscreen(offscreenName, parameters); + const offscreen = proxyInstance.get(); + + const first = offscreen.blockedSum(1, 2); + const second = offscreen.blockedSum(3, 4); + + await waitForBlockedCalls(2); + + blockedResolvers.forEach(resolve => resolve()); + + await expect(Promise.all([first, second])).resolves.toEqual([3, 7]); + + expect(lifecycle().createDocument.calls).toHaveLength(0); + expect(lifecycle().closeDocument.calls).toHaveLength(0); + }); + + test("waits for active calls before switching to a different URL", async () => { + const proxyInstance1 = new ProxyOffscreen(offscreenName, parameters); + const offscreen1 = proxyInstance1.get(); + const first = offscreen1.blockedSum(1, 2); + + await waitForBlockedCalls(1); + + const differentParams = {...parameters, url: "different-offscreen.html"}; + const proxyInstance2 = new ProxyOffscreen( + offscreenName, + differentParams + ); + const offscreen2 = proxyInstance2.get(); + const second = offscreen2.sum(3, 4); + + await waitUntil(() => locks.pending("adnbn:offscreen:active") === 1, "the exclusive URL switch lock"); + + expect(lifecycle().createDocument.calls).toHaveLength(0); + expect(lifecycle().closeDocument.calls).toHaveLength(0); + + blockedResolvers[0](); + + await expect(first).resolves.toBe(3); + await expect(second).resolves.toBe(7); + + expect(lifecycle().closeDocument.calls).toHaveLength(1); + expect(lifecycle().createDocument.calls.at(-1)?.args).toEqual([differentParams]); + }); +}); + +test("recreates a document closed between calls", async () => { + const proxy = new ProxyOffscreen(offscreenName, parameters).get(); + const previous = lifecycle().context!; + + await expect(proxy.sum(1, 2)).resolves.toBe(3); + await closeOffscreen(); + expect(previous.disposed).toBe(true); + await expect(proxy.sum(3, 4)).resolves.toBe(7); + expect(lifecycle().context).not.toBe(previous); + expect(lifecycle().createDocument.calls).toHaveLength(1); +}); + +test("releases lifecycle locks after creation failure so a later call can retry", async () => { + getBrowserTest().harness.contexts.remove(lifecycle().context!.info.contextId); + lifecycle().beforeCreate.failNext(new Error("Creation failed")); + + const proxy = new ProxyOffscreen(offscreenName, parameters).get(); + + await expect(proxy.sum(1, 2)).rejects.toThrow("Creation failed"); + expect(lifecycle().context).toBeUndefined(); + await expect(proxy.sum(3, 4)).resolves.toBe(7); + expect(lifecycle().createDocument.calls).toHaveLength(2); +}); + +test("keeps the existing document and releases locks after closure failure", async () => { + const previous = lifecycle().context!; + const otherParameters = {...parameters, url: "other-offscreen.html"}; + const proxy = new ProxyOffscreen(offscreenName, otherParameters).get(); + + lifecycle().beforeClose.failNext(new Error("Closure failed")); + + await expect(proxy.sum(1, 2)).rejects.toThrow("Closure failed"); + expect(lifecycle().context).toBe(previous); + expect(previous.disposed).toBe(false); + expect(lifecycle().createDocument.calls).toHaveLength(0); + await expect(proxy.sum(3, 4)).resolves.toBe(7); + expect(previous.disposed).toBe(true); + expect(lifecycle().createDocument.calls).toHaveLength(1); +}); + +test("releases the active lock after messaging failure before switching URL", async () => { + const session = getBrowserTest(); + const sender = session.harness.messaging.forContext(session.context); + const proxy = new ProxyOffscreen(offscreenName, parameters).get(); + + sender.runtime.sendMessage.failNext(new Error("Channel closed")); + + await expect(proxy.sum(1, 2)).rejects.toThrow("Channel closed"); + + const other = new ProxyOffscreen(offscreenName, { + ...parameters, + url: "other-offscreen.html", + }).get(); + + await expect(other.sum(3, 4)).resolves.toBe(7); + expect(lifecycle().closeDocument.calls).toHaveLength(1); +}); + +test("serializes concurrent creation and sends only after the document is ready", async () => { + getBrowserTest().harness.contexts.remove(lifecycle().context!.info.contextId); + + let release!: () => void; + const pending = new Promise(resolve => { + release = resolve; + }); + + lifecycle().beforeCreate.setImplementation(() => pending); + + const proxy = new ProxyOffscreen(offscreenName, parameters).get(); + const first = proxy.sum(1, 2); + const second = proxy.sum(3, 4); + + await waitUntil(() => lifecycle().beforeCreate.calls.length === 1, "offscreen creation to start"); + expect(lifecycle().context).toBeUndefined(); + expect(calls()).toHaveLength(0); + expect(lifecycle().createDocument.calls).toHaveLength(1); + release(); + + await expect(Promise.all([first, second])).resolves.toEqual([3, 7]); + expect(lifecycle().createDocument.calls).toHaveLength(1); +}); diff --git a/src/offscreen/providers/RegisterOffscreen.test.ts b/src/offscreen/providers/RegisterOffscreen.test.ts new file mode 100644 index 00000000..e97ae25e --- /dev/null +++ b/src/offscreen/providers/RegisterOffscreen.test.ts @@ -0,0 +1,58 @@ +import {getBrowserTest} from "@tests/browser-harness/session"; +import {RegisterOffscreen} from "./index"; +import {OffscreenGlobalAccess} from "@typing/offscreen"; + +const offscreenName = "math"; +const mathService = {sum: (a: number, b: number): number => a + b}; +type OffscreenType = typeof mathService; + +beforeEach(() => { + const session = getBrowserTest(); + const context = session.harness.contexts.create({kind: "offscreen"}); + + session.useContext(context); + new RegisterOffscreen(offscreenName, () => mathService).register(); +}); + +describe("RegisterOffscreen", () => { + beforeEach(async () => { + globalThis[OffscreenGlobalAccess] = true; + }); + + test("throws an error when get() is called outside offscreen context", async () => { + globalThis[OffscreenGlobalAccess] = false; + + const proxy = new RegisterOffscreen(offscreenName, () => mathService); + + expect(() => proxy.get()).toThrow( + `Offscreen service "${offscreenName}" can be getting only from offscreen context.` + ); + }); + + test("returns real offscreen service when called in offscreen context", () => { + const offscreen = new RegisterOffscreen( + offscreenName, + () => mathService + ).get(); + + expect(offscreen).toBe(mathService); + }); + + test("invokes methods directly without using Message.send in offscreen", async () => { + const offscreen = new RegisterOffscreen( + offscreenName, + () => mathService + ).get(); + + expect(offscreen.sum(1, 2)).toBe(3); + expect(getBrowserTest().harness.messaging.calls).toHaveLength(0); + }); + + test("throws an error when attempting to register the same offscreen service twice", async () => { + const offscreen = new RegisterOffscreen(offscreenName, () => mathService); + + expect(() => offscreen.register()).toThrow( + `A instance with name "${offscreenName}" already exists. The name must be unique.` + ); + }); +}); diff --git a/tests/README.md b/tests/README.md index c74f0eb7..8c805496 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,12 +42,12 @@ See [integration/README.md](integration/README.md) for browser requirements and ## Browser harness migration `tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for -unit, build and types projects. Only the 5 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. +unit, build and types projects. Only the 3 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. Real Chrome/Firefox integrations load neither setup. The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for -`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 13 files to review in total. +`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 11 files to review in total. Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. `npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. @@ -106,9 +106,17 @@ not prove that every reset implementation is correct, so session lifecycle tests ### Remaining test-kit boundaries - `hasListeners` is not modeled. Check subscriptions through the context event's `listenerCount()`. +- Offscreen has no post-creation hook. `ProxyOffscreen.test.ts` temporarily intercepts `contexts.create` with a + passthrough spy to register the real transport before the first message is sent. Replace this interception when + the kit provides a hook such as `offscreen.onCreated(context)` that completes before `createDocument` resolves. + `beforeCreate` cannot serve this purpose because the receiving context does not exist yet. - VM scenarios require uninstrumented injected code. Only `src/relay/adapters/invoke-relay.ts` is excluded from Babel coverage in every Jest project. Its host adapter remains instrumented; VM tests run the real bundled Relay manager, and Chrome MV3 plus Firefox MV2/MV3 integrations check the injected function in actual extensions. +- Native Offscreen unit tests attach the real `RegisterOffscreen` transport when the harness creates its context and + dispose it with that context. The kit owns creation, closure and messaging; it does not execute the extension HTML. + `tests/offscreen/MockLockManager.ts` models the Web Locks boundary for scheduling tests. Firefox/MV2 bridge tests use + jsdom frames and explicit ready/error events; real-browser integrations remain the lifecycle check. - The session does not emulate Web Locks, DOM inside the guest, or extension HTML execution. Keep explicit external adapters where needed and real-browser checks for DOM, execution worlds, browser lifecycle and vendor behavior. - Consult the kit's `RAW_CAPABILITY_COVERAGE` before using another browser API. Extend the kit when a required capability diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 946efc90..3a03eccf 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -1,7 +1,5 @@ { "legacyFiles": [ - "src/offscreen/providers/Offscreen.test.ts", - "src/offscreen/OffscreenBridge.test.ts", "src/locale/providers/ObservableLocale.test.ts", "src/service/providers/Service.test.ts", "src/sandbox/SandboxMessage.test.ts" @@ -37,9 +35,6 @@ ], "src/entry/content/lifecycle/nodes/FrameNode.test.ts": [ "@addon-core/browser" - ], - "src/offscreen/providers/Offscreen.test.ts": [ - "@main/env" ] } } diff --git a/tests/offscreen/MockLockManager.ts b/tests/offscreen/MockLockManager.ts new file mode 100644 index 00000000..d673aee7 --- /dev/null +++ b/tests/offscreen/MockLockManager.ts @@ -0,0 +1,90 @@ +type MockLockMode = "exclusive" | "shared"; +type LockState = {shared: number; exclusive: boolean}; +type MockLockOptions = {mode: MockLockMode}; +type MockLockCallback = () => unknown | Promise; + +type LockTask = { + mode: MockLockMode; + callback: MockLockCallback; + resolve: (value: unknown) => void; + reject: (error: unknown) => void; +}; + +/** + * Models only ProxyOffscreen's Web Locks usage: queued shared/exclusive requests, + * with release after callback settlement, including rejection. Web Locks are outside the browser test-kit. + * Does not implement ifAvailable, steal or signal; extend this double if production starts using them. + */ +export default class MockLockManager { + private readonly queues = new Map(); + + private readonly states = new Map(); + + public request(name: string, options: MockLockOptions, callback: MockLockCallback): Promise { + return new Promise((resolve, reject) => { + const queue = this.queues.get(name) ?? []; + queue.push({mode: options.mode, callback, resolve, reject}); + this.queues.set(name, queue); + this.drain(name); + }); + } + + public pending(name: string): number { + return this.queues.get(name)?.length ?? 0; + } + + private state(name: string): LockState { + const state = this.states.get(name) ?? {shared: 0, exclusive: false}; + + this.states.set(name, state); + + return state; + } + + private drain(name: string): void { + const queue = this.queues.get(name); + const state = this.state(name); + + if (!queue?.length || state.exclusive) { + return; + } + + const task = queue[0]; + + if (task.mode === "exclusive") { + if (state.shared > 0) { + return; + } + + queue.shift(); + state.exclusive = true; + this.run(name, task); + + return; + } + + while (queue[0]?.mode === "shared" && !state.exclusive) { + const sharedTask = queue.shift()!; + + state.shared++; + this.run(name, sharedTask); + } + } + + private run(name: string, task: LockTask): void { + Promise.resolve() + .then(task.callback) + .then(task.resolve, task.reject) + .finally(() => { + const state = this.state(name); + + if (task.mode === "exclusive") { + state.exclusive = false; + } else { + state.shared--; + } + + this.drain(name); + }); + } +} From e3133d5dc19fa63a1a3ac2c0bae165880b31b88c Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:28:58 +0300 Subject: [PATCH 12/36] refactor(locale): move observable state to observers --- src/locale/README.md | 6 ++++-- .../adapters/react/hooks/use-locale.test.ts | 2 +- src/locale/adapters/react/hooks/use-locale.ts | 2 +- src/locale/index.ts | 1 + .../ObservableLocale.test.ts | 6 +++--- .../ObservableLocale.ts | 5 +++-- src/locale/observers/index.ts | 1 + src/locale/providers/index.ts | 1 - tests/browser-harness.test.ts | 2 +- .../framework-state-inventory.json | 2 +- tests/browser-harness/framework-state.ts | 4 ++-- tests/browser-harness/migration.json | 4 ++-- .../types/fixtures/locale/substitutions.ts | 17 ++++++++++++++++- 13 files changed, 36 insertions(+), 17 deletions(-) rename src/locale/{providers => observers}/ObservableLocale.test.ts (98%) rename src/locale/{providers => observers}/ObservableLocale.ts (98%) create mode 100644 src/locale/observers/index.ts diff --git a/src/locale/README.md b/src/locale/README.md index 9b979e5b..93f6f361 100644 --- a/src/locale/README.md +++ b/src/locale/README.md @@ -13,13 +13,13 @@ description: Runtime translation providers, shared formatting, catalogue access, | `providers/AbstractLocale.ts` | Common string rendering, missing-message diagnostics, and language names. | | `providers/NativeLocale.ts` | Browser i18n message lookup and a singleton used by the public helpers. | | `providers/DynamicLocale.ts` | Compiled translations, language state per instance, and optional storage synchronization. | -| `providers/ObservableLocale.ts` | Observable dynamic state, immutable snapshots, shared instances, and subscription lifetimes. | +| `observers/ObservableLocale.ts` | Observable dynamic state, immutable snapshots, shared instances, and subscription lifetimes. | | `providers/CustomLocale.ts` | Internal provider for an explicitly supplied language and flat message dictionary; not re-exported by the public entrypoint. | | `helpers.ts` | Native shortcuts: `t`, `choice`, `key` for browser message references, and `resolve` for strings prefixed with `@`. | | `catalogue/` | Empty package fallback and typing for the generated data module. | | `storage/` | Built-in `LocaleStorage` implementation of the public `LocaleStorageDriver` contract. | | `adapters/react/` | React node rendering, native and dynamic locale hooks, and DOM language attributes through `useLocaleAttributes`. | -| `index.ts` | Public providers, helpers, and selected shared contracts. | +| `index.ts` | Public providers, observers, helpers, and selected shared contracts. | React hooks and their tests live in `adapters/react/hooks/`. The adapter's `index.ts` re-exports the hooks through `adnbn/locale/react`. @@ -111,6 +111,8 @@ Construction does not read or watch storage. The first subscriber connects one s `change()` publishes immediately and serializes persistence, including repeated selections of the current language. Old reads and storage echoes cannot overwrite a pending local selection. After successful writes, the provider reconciles with current storage. Save failures reject the returned promise without rolling back the selected language. Errors from automatic synchronization are logged; explicit `sync()` errors propagate to its caller. +A rejected `change()` does not mean that the language stayed unchanged: subscribers have already seen the optimistic selection. Once pending writes finish, a later `sync()` or storage notification may replace that selection with the saved language. + ## Dynamic translations in React `useLocale(storage?)` returns `LocaleReactDynamicContract`, extending the native React translation contract with `change(lang): Promise`. It connects the shared `ObservableLocale` through React's `useSyncExternalStore`. Components using the same instance update together on local selections and storage changes. The hook renders immediately using the initial language, then restores a saved selection asynchronously. It does not modify DOM attributes. diff --git a/src/locale/adapters/react/hooks/use-locale.test.ts b/src/locale/adapters/react/hooks/use-locale.test.ts index 2f69210b..6aea84e1 100644 --- a/src/locale/adapters/react/hooks/use-locale.test.ts +++ b/src/locale/adapters/react/hooks/use-locale.test.ts @@ -7,7 +7,7 @@ import {createElement, StrictMode, useState} from "react"; import {renderToString} from "react-dom/server"; import {act, cleanup, fireEvent, render, renderHook, waitFor} from "@testing-library/react"; import {useLocale, useNativeLocale, type LocaleReactDynamicContract} from "../index"; -import {ObservableLocale} from "@locale/providers"; +import {ObservableLocale} from "@locale/observers"; import {MemoryLocaleStorage} from "@locale/tests/fixtures"; import {Language, LocaleDir, type LocaleStorageDriver} from "@typing/locale"; diff --git a/src/locale/adapters/react/hooks/use-locale.ts b/src/locale/adapters/react/hooks/use-locale.ts index 39d0638a..73a8da4e 100644 --- a/src/locale/adapters/react/hooks/use-locale.ts +++ b/src/locale/adapters/react/hooks/use-locale.ts @@ -2,7 +2,7 @@ import {useMemo, useSyncExternalStore} from "react"; import ReactLocale from "../ReactLocale"; -import {ObservableLocale} from "@locale/providers"; +import {ObservableLocale} from "@locale/observers"; import type {LocaleReactDynamicContract} from "../types"; import type {LocaleRegistry, LocaleStorageDriver} from "@typing/locale"; diff --git a/src/locale/index.ts b/src/locale/index.ts index bf6bbc7c..2c5940a8 100644 --- a/src/locale/index.ts +++ b/src/locale/index.ts @@ -1,4 +1,5 @@ export * from "./providers"; +export {ObservableLocale} from "./observers"; export * from "./helpers"; export * from "./storage"; diff --git a/src/locale/providers/ObservableLocale.test.ts b/src/locale/observers/ObservableLocale.test.ts similarity index 98% rename from src/locale/providers/ObservableLocale.test.ts rename to src/locale/observers/ObservableLocale.test.ts index 6861617b..b60f35e7 100644 --- a/src/locale/providers/ObservableLocale.test.ts +++ b/src/locale/observers/ObservableLocale.test.ts @@ -1,12 +1,12 @@ jest.mock("@addon-core/browser", () => ({getI18nMessage: jest.fn(() => "en")})); -jest.mock("#adnbn/locale", () => require("./tests/fixtures/dynamic")); +jest.mock("#adnbn/locale", () => require("@locale/providers/tests/fixtures/dynamic")); import {getI18nMessage} from "@addon-core/browser"; -import {ObservableLocale} from "@locale/providers"; +import {ObservableLocale} from "@locale/observers"; import {MemoryLocaleStorage} from "../tests/fixtures"; import {Language, type LocaleDynamicProvider, type LocaleStorageDriver} from "@typing/locale"; -import type {Structure} from "./tests/fixtures/dynamic"; +import type {Structure} from "@locale/providers/tests/fixtures/dynamic"; afterEach(() => jest.restoreAllMocks()); diff --git a/src/locale/providers/ObservableLocale.ts b/src/locale/observers/ObservableLocale.ts similarity index 98% rename from src/locale/providers/ObservableLocale.ts rename to src/locale/observers/ObservableLocale.ts index dbf87939..7b228a03 100644 --- a/src/locale/providers/ObservableLocale.ts +++ b/src/locale/observers/ObservableLocale.ts @@ -1,5 +1,5 @@ -import DynamicLocale from "./DynamicLocale"; -import CustomLocale from "./CustomLocale"; +import {DynamicLocale} from "../providers"; +import CustomLocale from "../providers/CustomLocale"; import {LocaleStorage} from "../storage"; @@ -119,6 +119,7 @@ export default class ObservableLocale impleme if (!storage) { this.publish(); + return lang; } diff --git a/src/locale/observers/index.ts b/src/locale/observers/index.ts new file mode 100644 index 00000000..3412a20d --- /dev/null +++ b/src/locale/observers/index.ts @@ -0,0 +1 @@ +export {default as ObservableLocale} from "./ObservableLocale"; diff --git a/src/locale/providers/index.ts b/src/locale/providers/index.ts index f9db42bb..481d401d 100644 --- a/src/locale/providers/index.ts +++ b/src/locale/providers/index.ts @@ -1,4 +1,3 @@ export {default as AbstractLocale} from "./AbstractLocale"; export {default as DynamicLocale} from "./DynamicLocale"; export {default as NativeLocale} from "./NativeLocale"; -export {default as ObservableLocale} from "./ObservableLocale"; diff --git a/tests/browser-harness.test.ts b/tests/browser-harness.test.ts index 9cecbc7e..f9c4d491 100644 --- a/tests/browser-harness.test.ts +++ b/tests/browser-harness.test.ts @@ -5,7 +5,7 @@ import {createTabFixture} from "@addon-core/browser/testing"; import {Storage} from "@addon-core/storage"; import NativeLocale from "@locale/providers/NativeLocale"; -import ObservableLocale from "@locale/providers/ObservableLocale"; +import {ObservableLocale} from "@locale/observers"; import {MemoryLocaleStorage} from "@locale/tests/fixtures"; import OffscreenBridge from "@offscreen/OffscreenBridge"; import SandboxMessage from "@sandbox/SandboxMessage"; diff --git a/tests/browser-harness/framework-state-inventory.json b/tests/browser-harness/framework-state-inventory.json index 4a799938..08a82647 100644 --- a/tests/browser-harness/framework-state-inventory.json +++ b/tests/browser-harness/framework-state-inventory.json @@ -17,7 +17,7 @@ "getInstance": true }, "ObservableLocale": { - "file": "src/locale/providers/ObservableLocale.ts", + "file": "src/locale/observers/ObservableLocale.ts", "fields": [ "defaultInstance", "memoryInstance", diff --git a/tests/browser-harness/framework-state.ts b/tests/browser-harness/framework-state.ts index 09633a4b..3b9c0380 100644 --- a/tests/browser-harness/framework-state.ts +++ b/tests/browser-harness/framework-state.ts @@ -43,8 +43,8 @@ export const frameworkStateResetters = { Reflect.deleteProperty(Message, "instance"); }, ObservableLocale: () => { - const {default: ObservableLocale} = jest.requireActual( - "@locale/providers/ObservableLocale" + const {default: ObservableLocale} = jest.requireActual( + "@locale/observers/ObservableLocale" ); Reflect.deleteProperty(ObservableLocale, "defaultInstance"); diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 3a03eccf..4966a1a0 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -1,6 +1,6 @@ { "legacyFiles": [ - "src/locale/providers/ObservableLocale.test.ts", + "src/locale/observers/ObservableLocale.test.ts", "src/service/providers/Service.test.ts", "src/sandbox/SandboxMessage.test.ts" ], @@ -18,7 +18,7 @@ "@addon-core/browser", "@addon-core/storage" ], - "src/locale/providers/ObservableLocale.test.ts": [ + "src/locale/observers/ObservableLocale.test.ts": [ "@addon-core/browser" ], "src/locale/adapters/react/hooks/use-locale.test.ts": [ diff --git a/tests/integration/types/fixtures/locale/substitutions.ts b/tests/integration/types/fixtures/locale/substitutions.ts index 98a5201e..b387aa0e 100644 --- a/tests/integration/types/fixtures/locale/substitutions.ts +++ b/tests/integration/types/fixtures/locale/substitutions.ts @@ -1,4 +1,11 @@ -import type {LocaleNonPluralKeys, LocalePluralKeys, LocaleSubstitutionArgs} from "adnbn/locale"; +import { + ObservableLocale, + Language, + type LocaleDynamicProvider, + type LocaleNonPluralKeys, + type LocalePluralKeys, + type LocaleSubstitutionArgs, +} from "adnbn/locale"; interface Structure { "app.name": {plural: false; substitutions: []}; @@ -6,6 +13,14 @@ interface Structure { "app.cars": {plural: true; substitutions: ["count"]}; } +const observable = new ObservableLocale(false); +const provider: LocaleDynamicProvider = observable; + +provider.trans("app.greeting", {name: "Alice"}); +provider.choice("app.cars", 2, {count: 2}); +void provider.change(Language.French); +observable.snapshot().get("app.name"); + declare function trans>( key: K, ...args: LocaleSubstitutionArgs From c99f5983728e5c79c7eea9a59ddf9581cffe1d58 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:29:12 +0300 Subject: [PATCH 13/36] test(locale): migrate observable locale to browser harness --- src/locale/observers/ObservableLocale.test.ts | 90 +++++++++++++++++-- tests/README.md | 4 +- tests/browser-harness/migration.json | 4 - 3 files changed, 83 insertions(+), 15 deletions(-) diff --git a/src/locale/observers/ObservableLocale.test.ts b/src/locale/observers/ObservableLocale.test.ts index b60f35e7..625f51f9 100644 --- a/src/locale/observers/ObservableLocale.test.ts +++ b/src/locale/observers/ObservableLocale.test.ts @@ -1,14 +1,17 @@ -jest.mock("@addon-core/browser", () => ({getI18nMessage: jest.fn(() => "en")})); jest.mock("#adnbn/locale", () => require("@locale/providers/tests/fixtures/dynamic")); -import {getI18nMessage} from "@addon-core/browser"; -import {ObservableLocale} from "@locale/observers"; +import {Storage} from "@addon-core/storage"; +import {PackageName} from "@typing/app"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import {ObservableLocale} from "./index"; import {MemoryLocaleStorage} from "../tests/fixtures"; import {Language, type LocaleDynamicProvider, type LocaleStorageDriver} from "@typing/locale"; import type {Structure} from "@locale/providers/tests/fixtures/dynamic"; -afterEach(() => jest.restoreAllMocks()); +beforeEach(() => { + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setResult("en"); +}); test("provides current translations while snapshots retain their original language", async () => { const storage = new MemoryLocaleStorage(); @@ -113,6 +116,7 @@ test("serializes writes, saves equal selections and ignores old storage echoes", await blocked.promise; await originalSet(lang); }); + const first = locale.change(Language.French); const second = locale.change(Language.EnglishGreatBritain); await Promise.resolve(); @@ -200,6 +204,7 @@ test("a failed subscription does not leave a listener behind", async () => { jest.spyOn(storage, "watch").mockImplementationOnce(() => { throw new Error("cannot subscribe"); }); + const locale = new ObservableLocale(storage); const failed = jest.fn(); expect(() => locale.subscribe(failed)).toThrow("cannot subscribe"); @@ -218,8 +223,11 @@ test("a subscriber can select another language without reordering persistence", const locale = new ObservableLocale(storage); let next: Promise | undefined; const stop = locale.subscribe(() => { - if (locale.lang() === Language.French) next = locale.change(Language.EnglishGreatBritain); + if (locale.lang() === Language.French) { + next = locale.change(Language.EnglishGreatBritain); + } }); + const first = locale.change(Language.French); expect(locale.lang()).toBe(Language.EnglishGreatBritain); expect(locale.snapshot().lang()).toBe(Language.EnglishGreatBritain); @@ -301,9 +309,7 @@ test("reports background synchronization errors and still releases the listener" }); test("custom storage and memory mode work when browser i18n is unavailable", async () => { - jest.mocked(getI18nMessage).mockImplementation(() => { - throw new Error("No i18n"); - }); + getBrowserTest().harness.capabilities.set("i18n.getMessage", false); const locale = new ObservableLocale(new MemoryLocaleStorage(Language.English)); expect(locale.lang()).toBe(Language.French); await locale.sync(); @@ -312,5 +318,71 @@ test("custom storage and memory mode work when browser i18n is unavailable", asy await memory.change(Language.English); expect(memory.lang()).toBe(Language.English); await expect(memory.sync()).rejects.toThrow("Language is not saving in storage"); - jest.mocked(getI18nMessage).mockReturnValue("en"); +}); + +test("restores, persists and observes the selected language through real browser storage", async () => { + const session = getBrowserTest(); + const storage = Storage.Local<{locale: Language}>({namespace: PackageName}); + const locale = new ObservableLocale(); + const restored = Promise.withResolvers(); + const externalChange = Promise.withResolvers(); + const listenersBefore = session.harness.storage.onChanged.listenerCount(); + + await storage.set("locale", Language.French); + await session.harness.storage.flushChanges(); + + const stop = locale.subscribe(() => { + if (locale.lang() === Language.French) { + restored.resolve(); + } + + if (locale.lang() === Language.English) { + externalChange.resolve(); + } + }); + + session.addCleanup(stop); + await restored.promise; + expect(locale.trans("app.title")).toBe("Catalogue français"); + expect(session.harness.storage.onChanged.listenerCount()).toBe(listenersBefore + 1); + + const previous = locale.snapshot(); + + await locale.change(Language.EnglishGreatBritain); + await expect(storage.get("locale")).resolves.toBe(Language.EnglishGreatBritain); + expect(previous.lang()).toBe(Language.French); + await storage.set("locale", Language.English); + await session.harness.storage.flushChanges(); + await externalChange.promise; + expect(locale.lang()).toBe(Language.English); + expect(session.harness.storage.local.set.calls).toHaveLength(3); + stop(); + expect(session.harness.storage.onChanged.listenerCount()).toBe(listenersBefore); +}); + +test("preserves browser storage read errors and allows a later synchronization", async () => { + const session = getBrowserTest(); + const storage = Storage.Local<{locale: Language}>({namespace: PackageName}); + const locale = new ObservableLocale(); + + session.harness.storage.local.get.failNext(new Error("Browser read denied")); + + await expect(locale.sync()).rejects.toThrow("Browser read denied"); + expect(locale.lang()).toBe(Language.English); + await storage.set("locale", Language.French); + await expect(locale.sync()).resolves.toBe(Language.French); +}); + +test("continues persistence after a browser storage write error", async () => { + const session = getBrowserTest(); + const storage = Storage.Local<{locale: Language}>({namespace: PackageName}); + const locale = new ObservableLocale(); + + session.harness.storage.local.set.failNext(new Error("Browser write denied")); + + await expect(locale.change(Language.French)).rejects.toThrow("Browser write denied"); + expect(locale.lang()).toBe(Language.French); + await expect(storage.get("locale")).resolves.toBeUndefined(); + await expect(locale.change(Language.EnglishGreatBritain)).resolves.toBe(Language.EnglishGreatBritain); + await expect(storage.get("locale")).resolves.toBe(Language.EnglishGreatBritain); }); diff --git a/tests/README.md b/tests/README.md index 8c805496..3161b13a 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,12 +42,12 @@ See [integration/README.md](integration/README.md) for browser requirements and ## Browser harness migration `tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for -unit, build and types projects. Only the 3 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. +unit, build and types projects. Only the 2 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. Real Chrome/Firefox integrations load neither setup. The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for -`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 11 files to review in total. +`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 10 files to review in total. Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. `npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 4966a1a0..2b6ae1cc 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -1,6 +1,5 @@ { "legacyFiles": [ - "src/locale/observers/ObservableLocale.test.ts", "src/service/providers/Service.test.ts", "src/sandbox/SandboxMessage.test.ts" ], @@ -18,9 +17,6 @@ "@addon-core/browser", "@addon-core/storage" ], - "src/locale/observers/ObservableLocale.test.ts": [ - "@addon-core/browser" - ], "src/locale/adapters/react/hooks/use-locale.test.ts": [ "@addon-core/browser" ], From d7c7157c3cd30da818b53b8c61071f52efa54aa8 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:31:16 +0300 Subject: [PATCH 14/36] refactor(locale): import native provider through barrel --- src/locale/adapters/react/hooks/use-native-locale.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/locale/adapters/react/hooks/use-native-locale.ts b/src/locale/adapters/react/hooks/use-native-locale.ts index bc5efbe3..829580e3 100644 --- a/src/locale/adapters/react/hooks/use-native-locale.ts +++ b/src/locale/adapters/react/hooks/use-native-locale.ts @@ -1,9 +1,13 @@ import {useMemo} from "react"; -import NativeLocale from "@locale/providers/NativeLocale"; + import ReactLocale from "../ReactLocale"; -import type {LocaleRegistry} from "@typing/locale"; + +import {NativeLocale} from "@locale/providers"; + import type {LocaleReactContract} from "../types"; +import type {LocaleRegistry} from "@typing/locale"; + /** Uses browser-selected translations with React substitutions. */ export const useNativeLocale = (): LocaleReactContract => { return useMemo(() => new ReactLocale(NativeLocale.getInstance()), []); From 046184e333ad4ef662c4b4ed64d3ad58063c2118 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 16:53:23 +0300 Subject: [PATCH 15/36] test(service): migrate suites to browser harness Replace Browser module mocks and hand-written runtime stubs with real background registration and context-bound harness messaging. Merge all ProxyService.context.test.ts scenarios into ProxyService.test.ts and split registration coverage into RegisterService.test.ts. Preserve offscreen capability and error cases, cover MV2 background-page detection, transport recovery and listener cleanup without asserting logging policy. Remove Service from the legacy inventory and update migration progress. Validation: 23 Service/transport tests, typecheck and test inventory pass. The full six-project suite passed 202 suites and 1998 tests before the final two MV2 cases and removal of the logging-count assertion. --- .../providers/ProxyService.context.test.ts | 126 ------------- src/service/providers/ProxyService.test.ts | 171 ++++++++++++++++++ src/service/providers/RegisterService.test.ts | 72 ++++++++ src/service/providers/Service.test.ts | 169 ----------------- .../providers/tests/fixtures/math-service.ts | 18 ++ tests/README.md | 4 +- tests/browser-harness/migration.json | 4 - 7 files changed, 263 insertions(+), 301 deletions(-) delete mode 100644 src/service/providers/ProxyService.context.test.ts create mode 100644 src/service/providers/ProxyService.test.ts create mode 100644 src/service/providers/RegisterService.test.ts delete mode 100644 src/service/providers/Service.test.ts create mode 100644 src/service/providers/tests/fixtures/math-service.ts diff --git a/src/service/providers/ProxyService.context.test.ts b/src/service/providers/ProxyService.context.test.ts deleted file mode 100644 index 4b52ca3e..00000000 --- a/src/service/providers/ProxyService.context.test.ts +++ /dev/null @@ -1,126 +0,0 @@ -/** @jest-environment node */ - -jest.unmock("@addon-core/browser"); - -import {getService} from "@main/service"; - -import ProxyService from "./ProxyService"; - -import {MessageResultEnvelopeProperty, MessageTypeSeparator} from "@typing/message"; - -type ServiceProxy = { - someMethod(value: string): Promise; -}; - -type RuntimeSendMessage = (message: unknown, callback: (response: unknown) => void) => void; - -const serviceName = "test"; - -const setRuntime = (runtime?: {sendMessage?: RuntimeSendMessage; getManifest?: () => chrome.runtime.Manifest}) => { - Object.defineProperty(globalThis, "chrome", { - configurable: true, - writable: true, - value: runtime - ? { - runtime: { - id: "extension-id", - ...runtime, - }, - } - : undefined, - }); -}; - -const setExtensionDocument = (pathname: string) => { - Object.defineProperty(globalThis, "window", {configurable: true, value: {}}); - Object.defineProperty(globalThis, "location", {configurable: true, value: {pathname}}); -}; - -const getTestService = (): ServiceProxy => getService(serviceName as never) as ServiceProxy; - -describe("ProxyService context detection", () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - test("creates a service proxy in an offscreen-like context with no runtime.getManifest", async () => { - const expectedResult = "background:value"; - const sendMessage = jest.fn, Parameters>( - (_message, callback) => { - callback({[MessageResultEnvelopeProperty]: true, ok: true, payload: expectedResult}); - } - ); - - setRuntime({sendMessage}); - setExtensionDocument("/offscreen.html"); - - expect(() => getTestService()).not.toThrow(); - - const service = getTestService(); - - // @ts-expect-error - The runtime proxy marker is intentionally hidden from its public type. - expect(service.__proxy).toBe(true); - await expect(service.someMethod("value")).resolves.toBe(expectedResult); - expect(sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `service${MessageTypeSeparator}${serviceName}`, - data: { - path: "someMethod", - args: ["value"], - }, - }), - expect.any(Function) - ); - }); - - test("restores serialized background errors in an offscreen-like context", async () => { - const sendMessage = jest.fn, Parameters>( - (_message, callback) => { - callback({ - [MessageResultEnvelopeProperty]: true, - ok: false, - error: {name: "TypeError", message: "background failed"}, - }); - } - ); - - setRuntime({sendMessage}); - setExtensionDocument("/offscreen.html"); - - await expect(getTestService().someMethod("value")).rejects.toThrow("background failed"); - await expect(getTestService().someMethod("value")).rejects.toBeInstanceOf(TypeError); - }); - - test.each([ - ["popup", "/popup.html"], - ["sidebar", "/sidebar.html"], - ["content script", "/content-script.html"], - ])("creates a proxy in %s context", (_context, pathname) => { - setRuntime({ - getManifest: () => - ({manifest_version: 3, background: {service_worker: "background.js"}}) as chrome.runtime.Manifest, - }); - setExtensionDocument(pathname); - - expect(() => getTestService()).not.toThrow(); - }); - - test("creates a proxy when the WebExtension API is unavailable", () => { - setRuntime(); - setExtensionDocument("/standalone.html"); - - expect(() => getTestService()).not.toThrow(); - }); - - test("continues to reject service proxies in an MV3 service worker", () => { - setRuntime({ - getManifest: () => - ({manifest_version: 3, background: {service_worker: "background.js"}}) as chrome.runtime.Manifest, - }); - Object.defineProperty(globalThis, "window", {configurable: true, value: undefined}); - - expect(() => new ProxyService(serviceName).get()).toThrow( - `You are trying to get proxy service "${serviceName}" from background. You can get original service instead` - ); - }); -}); diff --git a/src/service/providers/ProxyService.test.ts b/src/service/providers/ProxyService.test.ts new file mode 100644 index 00000000..51fb3943 --- /dev/null +++ b/src/service/providers/ProxyService.test.ts @@ -0,0 +1,171 @@ +import { + createManifestFixture, + createTabFixture, + installGlobals, + type BrowserContext, +} from "@addon-core/browser/testing"; +import {getService} from "@main/service"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import {ProxyService, RegisterService} from "./index"; +import {mathService, serviceName, type MathService} from "./tests/fixtures/math-service"; +import type {RpcAsyncProxy} from "@typing/rpc"; +import {MessageTypeSeparator} from "@typing/message"; + +type ServiceProxy = RpcAsyncProxy; + +let background: BrowserContext; + +const getTestService = (): ServiceProxy => getService(serviceName as never) as ServiceProxy; +const sender = () => getBrowserTest().harness.messaging.forContext(getBrowserTest().context); + +beforeEach(() => { + const session = getBrowserTest(); + background = session.harness.contexts.create({kind: "background"}); + + session.harness.runtime.setManifest( + createManifestFixture({manifest_version: 3, background: {service_worker: "background.js"}}) + ); + session.addCleanup(installGlobals({window: undefined})); + + const restore = session.useContext(background); + const registration = new RegisterService(serviceName, () => mathService); + + registration.register(); + session.addCleanup(() => registration.destroy()); + restore(); + session.addCleanup(installGlobals({window: {}, location: {pathname: "/popup.html"}})); +}); + +describe("ProxyService", () => { + test("rejects proxies in an MV3 background service worker", () => { + const session = getBrowserTest(); + + session.useContext(background); + session.addCleanup(installGlobals({window: undefined})); + + expect(() => new ProxyService(serviceName).get()).toThrow( + `You are trying to get proxy service "${serviceName}" from background. You can get original service instead` + ); + }); + + test("returns a proxy outside background", () => { + expect(Reflect.get(getTestService(), "__proxy")).toBe(true); + }); + + test("rejects proxies in an MV2 generated background page", () => { + const session = getBrowserTest(); + + session.harness.runtime.setManifest( + createManifestFixture({manifest_version: 2, background: {scripts: ["background.js"]}}) + ); + session.useContext(background); + session.addCleanup(installGlobals({window: {}, location: {pathname: "/_generated_background_page.html"}})); + + expect(() => getTestService()).toThrow( + `You are trying to get proxy service "${serviceName}" from background. You can get original service instead` + ); + }); + + test("invokes a real background method through runtime messaging", async () => { + await expect(getTestService().sum(1, 2)).resolves.toBe(3); + expect(sender().runtime.sendMessage.calls.at(-1)?.args).toEqual([ + expect.objectContaining({ + type: `service${MessageTypeSeparator}${serviceName}`, + data: {path: "sum", args: [1, 2]}, + }), + ]); + }); + + test("reads a service property", async () => { + await expect(getTestService().one()).resolves.toBe(1); + expect(sender().runtime.sendMessage.calls.at(-1)?.args).toEqual([ + expect.objectContaining({data: {path: "one", args: []}}), + ]); + }); + + test("accesses nested methods and falsy properties", async () => { + const service = getTestService(); + + await expect(service.obj.concat("Hello", "world")).resolves.toBe("Hello world"); + expect(sender().runtime.sendMessage.calls.at(-1)?.args).toEqual([ + expect.objectContaining({data: {path: "obj.concat", args: ["Hello", "world"]}}), + ]); + await expect(service.obj.zero()).resolves.toBe(0); + expect(sender().runtime.sendMessage.calls.at(-1)?.args).toEqual([ + expect.objectContaining({data: {path: "obj.zero", args: []}}), + ]); + }); + + test("awaits asynchronous background methods", async () => { + jest.useFakeTimers(); + + const result = getTestService().asyncSum(1, 2); + + await jest.advanceTimersByTimeAsync(100); + await expect(result).resolves.toBe(3); + }); + + test("calls background from an offscreen context without runtime.getManifest", async () => { + const session = getBrowserTest(); + const offscreen = session.harness.contexts.create({kind: "offscreen"}); + + session.harness.capabilities.set("runtime.getManifest", false); + session.useContext(offscreen); + session.addCleanup(installGlobals({location: {pathname: "/offscreen.html"}})); + + expect(chrome.runtime.getManifest).toBeUndefined(); + expect(Reflect.get(getTestService(), "__proxy")).toBe(true); + await expect(getTestService().sum(1, 2)).resolves.toBe(3); + expect(session.harness.messaging.forContext(offscreen).runtime.sendMessage.calls).toHaveLength(1); + }); + + test("restores real background errors in an offscreen context", async () => { + const session = getBrowserTest(); + const offscreen = session.harness.contexts.create({kind: "offscreen"}); + + jest.spyOn(console, "error").mockImplementation(() => {}); + + session.harness.capabilities.set("runtime.getManifest", false); + session.useContext(offscreen); + session.addCleanup(installGlobals({location: {pathname: "/offscreen.html"}})); + + await expect(getTestService().fail()).rejects.toThrow("background failed"); + await expect(getTestService().fail()).rejects.toBeInstanceOf(TypeError); + }); + + test.each([ + ["popup", "/popup.html"], + ["sidebar", "/sidebar.html"], + ["content script", "/content-script.html"], + ])("calls background from %s", async (name, pathname) => { + const session = getBrowserTest(); + session.harness.tabs.set([createTabFixture({id: 1, url: "https://example.com"})]); + + const context = session.harness.contexts.create( + name === "content script" + ? {kind: "contentScript", tabId: 1, frameId: 0, url: "https://example.com"} + : {kind: "extensionPage", url: chrome.runtime.getURL(pathname.slice(1))} + ); + + session.useContext(context); + session.addCleanup(installGlobals({location: {pathname}})); + + await expect(getTestService().sum(1, 2)).resolves.toBe(3); + expect(session.harness.messaging.forContext(context).runtime.sendMessage.calls).toHaveLength(1); + }); + + test("creates a proxy when WebExtension APIs are unavailable", () => { + getBrowserTest().addCleanup(installGlobals({chrome: undefined, browser: undefined})); + + expect(() => getTestService()).not.toThrow(); + }); + + test("propagates a transport failure and allows a later call", async () => { + const service = getTestService(); + + sender().runtime.sendMessage.failNext(new Error("Channel closed")); + + await expect(service.sum(1, 2)).rejects.toThrow("Channel closed"); + await expect(service.sum(3, 4)).resolves.toBe(7); + }); +}); diff --git a/src/service/providers/RegisterService.test.ts b/src/service/providers/RegisterService.test.ts new file mode 100644 index 00000000..ad6ba76c --- /dev/null +++ b/src/service/providers/RegisterService.test.ts @@ -0,0 +1,72 @@ +import {createManifestFixture, installGlobals, type BrowserContext} from "@addon-core/browser/testing"; +import {getBrowserTest} from "@tests/browser-harness/session"; +import {RegisterService} from "./index"; +import {mathService, serviceName, type MathService} from "./tests/fixtures/math-service"; + +let background: BrowserContext; +let registration: RegisterService; + +beforeEach(() => { + const session = getBrowserTest(); + + background = session.harness.contexts.create({kind: "background"}); + session.harness.runtime.setManifest( + createManifestFixture({manifest_version: 3, background: {service_worker: "background.js"}}) + ); + session.useContext(background); + session.addCleanup(installGlobals({window: undefined})); + registration = new RegisterService(serviceName, () => mathService); + registration.register(); + session.addCleanup(() => registration.destroy()); +}); + +describe("RegisterService", () => { + test("rejects direct access outside background", () => { + const session = getBrowserTest(); + + session.useContext(session.context); + session.addCleanup(installGlobals({window: {}, location: {pathname: "/popup.html"}})); + + expect(() => registration.get()).toThrow( + `Service "${serviceName}" can be getting only from background context.` + ); + }); + + test("returns the original service in background", () => { + expect(registration.get()).toBe(mathService); + }); + + test("returns the original service in an MV2 generated background page", () => { + const session = getBrowserTest(); + + session.harness.runtime.setManifest( + createManifestFixture({manifest_version: 2, background: {scripts: ["background.js"]}}) + ); + session.addCleanup(installGlobals({window: {}, location: {pathname: "/_generated_background_page.html"}})); + + expect(registration.get()).toBe(mathService); + }); + + test("invokes methods directly without runtime messaging", () => { + expect(registration.get().sum(1, 2)).toBe(3); + expect(getBrowserTest().harness.messaging.calls).toHaveLength(0); + }); + + test("rejects a duplicate service name", () => { + const duplicate = new RegisterService(serviceName, () => mathService); + + expect(() => duplicate.register()).toThrow( + `A instance with name "${serviceName}" already exists. The name must be unique.` + ); + expect(background.onMessage.listenerCount()).toBe(1); + }); + + test("removes its listener on destroy and allows registration again", () => { + expect(background.onMessage.listenerCount()).toBe(1); + expect(getBrowserTest().context.onMessage.listenerCount()).toBe(0); + registration.destroy(); + expect(background.onMessage.listenerCount()).toBe(0); + expect(registration.register()).toBe(mathService); + expect(background.onMessage.listenerCount()).toBe(1); + }); +}); diff --git a/src/service/providers/Service.test.ts b/src/service/providers/Service.test.ts deleted file mode 100644 index ffca1087..00000000 --- a/src/service/providers/Service.test.ts +++ /dev/null @@ -1,169 +0,0 @@ -import {isBackground} from "@addon-core/browser"; - -import MessageManager from "@message/MessageManager"; - -import ProxyService from "./ProxyService"; -import RegisterService from "./RegisterService"; -import ServiceManager from "../ServiceManager"; - -import {RpcAsyncProxy} from "@typing/rpc"; -import {MessageTypeSeparator} from "@typing/message"; - -jest.mock("@addon-core/browser", () => { - const actual = jest.requireActual("@addon-core/browser"); - return { - ...actual, - isBackground: jest.fn(), - }; -}); - -beforeEach(async () => { - jest.clearAllMocks(); - - ServiceManager.getInstance().clear(); - MessageManager.getInstance().clear(); - - new RegisterService(serviceName, () => MatchService).register(); -}); - -const MatchService = { - sum: (a: number, b: number): number => a + b, - asyncSum: (a: number, b: number): Promise => { - return new Promise(resolve => setTimeout(() => resolve(a + b), 100)); - }, - one: 1, - obj: { - concat: (a: string, b: string): string => a + " " + b, - zero: 0, - }, -}; - -type ServiceType = typeof MatchService; -type ServiceProxyType = RpcAsyncProxy; - -const serviceName = "math"; - -describe("ProxyService", () => { - beforeEach(async () => { - (isBackground as jest.Mock).mockReturnValue(false); - }); - - test("throws an error when get() is called in background context", async () => { - (isBackground as jest.Mock).mockReturnValue(true); - - const proxy = new ProxyService(serviceName); - - expect(() => proxy.get()).toThrow( - `You are trying to get proxy service "${serviceName}" from background. You can get original service instead` - ); - }); - - test("returns a proxy when not in background context", () => { - const service = new ProxyService(serviceName).get(); - - //@ts-ignore - expect(service.__proxy).toBe(true); - }); - - test("invokes remote methods using Message.send", async () => { - const service = new ProxyService(serviceName).get(); - - expect(await service.sum(1, 2)).toBe(3); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `service${MessageTypeSeparator}${serviceName}`, - data: { - path: "sum", - args: [1, 2], - }, - }), - expect.any(Function) - ); - }); - - test("accesses property on service object ", async () => { - const service = new ProxyService(serviceName).get(); - - expect(await service.one()).toBe(1); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `service${MessageTypeSeparator}${serviceName}`, - data: { - path: "one", - args: [], - }, - }), - expect.any(Function) - ); - }); - - test("accesses nested method or property ", async () => { - const service = new ProxyService(serviceName).get(); - - expect(await service.obj.concat("Hello", "world")).toBe("Hello world"); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `service${MessageTypeSeparator}${serviceName}`, - data: { - path: "obj.concat", - args: ["Hello", "world"], - }, - }), - expect.any(Function) - ); - - expect(await service.obj.zero()).toBe(0); - expect(chrome.runtime.sendMessage).toHaveBeenCalledWith( - expect.objectContaining({ - type: `service${MessageTypeSeparator}${serviceName}`, - data: { - path: "obj.zero", - args: [], - }, - }), - expect.any(Function) - ); - }); - - test("handles proxied async methods that return promises", async () => { - const service = new ProxyService(serviceName).get(); - - expect(await service.asyncSum(1, 2)).toBe(3); - }); -}); - -describe("RegisterService", () => { - beforeEach(async () => { - (isBackground as jest.Mock).mockReturnValue(true); - }); - - test("throws an error when get() is called outside background context", async () => { - (isBackground as jest.Mock).mockReturnValue(false); - - const proxy = new RegisterService(serviceName, () => MatchService); - - expect(() => proxy.get()).toThrow(`Service "${serviceName}" can be getting only from background context.`); - }); - - test("returns real service when called in background context", () => { - const service = new RegisterService(serviceName, () => MatchService).get(); - - //@ts-ignore - expect(service.__proxy).toBe(undefined); - }); - - test("invokes methods directly without using Message.send in background", async () => { - const service = new RegisterService(serviceName, () => MatchService).get(); - - expect(service.sum(1, 2)).toBe(3); - expect(chrome.runtime.sendMessage).toHaveBeenCalledTimes(0); - }); - - test("throws an error when attempting to register the same service twice", async () => { - const service = new RegisterService(serviceName, () => MatchService); - - expect(() => service.register()).toThrow( - `A instance with name "${serviceName}" already exists. The name must be unique.` - ); - }); -}); diff --git a/src/service/providers/tests/fixtures/math-service.ts b/src/service/providers/tests/fixtures/math-service.ts new file mode 100644 index 00000000..04cd7cc5 --- /dev/null +++ b/src/service/providers/tests/fixtures/math-service.ts @@ -0,0 +1,18 @@ +export const serviceName = "math"; + +export const mathService = { + sum: (a: number, b: number): number => a + b, + asyncSum: (a: number, b: number): Promise => { + return new Promise(resolve => setTimeout(() => resolve(a + b), 100)); + }, + fail: (): never => { + throw new TypeError("background failed"); + }, + one: 1, + obj: { + concat: (a: string, b: string): string => a + " " + b, + zero: 0, + }, +}; + +export type MathService = typeof mathService; diff --git a/tests/README.md b/tests/README.md index 3161b13a..efbd0d50 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,12 +42,12 @@ See [integration/README.md](integration/README.md) for browser requirements and ## Browser harness migration `tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for -unit, build and types projects. Only the 2 files in `browser-harness/migration.json` use `jest-legacy.setup.ts`. +unit, build and types projects. Only `SandboxMessage.test.ts`, listed in `browser-harness/migration.json`, still uses `jest-legacy.setup.ts`. Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. Real Chrome/Firefox integrations load neither setup. The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for -`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 10 files to review in total. +`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 9 files to review in total. Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. `npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 2b6ae1cc..3c1f8f2b 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -1,12 +1,8 @@ { "legacyFiles": [ - "src/service/providers/Service.test.ts", "src/sandbox/SandboxMessage.test.ts" ], "localMocks": { - "src/service/providers/Service.test.ts": [ - "@addon-core/browser" - ], "src/locale/helpers.test.ts": [ "@addon-core/browser" ], From 7c4e136ec013b979fa34310ba418fb0d2f596da5 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 17:51:08 +0300 Subject: [PATCH 16/36] test(sandbox): migrate messaging and remove legacy setup Run SandboxMessage tests with real request IDs and session-owned cleanup. Preserve request, error and cache scenarios, verify out-of-order responses, and cover deterministic deadlines and disposal of pending requests. Consolidate harness lifecycle hooks in jest.setup.ts. Remove setup selection, the legacy message dispatcher and module mocks, and jest-webextension-mock. Update the inventory and README while retaining the eight local-mock entries. Validation: 202 suites and 2001 tests passed across all six Jest projects, including Chrome and Firefox. Typecheck, test inventory and formatting passed. --- package-lock.json | 8 -- package.json | 1 - scripts/test-inventory-utils.mjs | 19 ++--- src/sandbox/SandboxMessage.test.ts | 100 ++++++++++++++++++------- tests/README.md | 18 +++-- tests/browser-harness/migration.json | 3 - tests/browser-harness/session.ts | 2 +- tests/jest-browser.setup.ts | 13 ---- tests/jest-legacy.setup.ts | 4 - tests/jest-message.setup.ts | 37 ---------- tests/jest-modules.setup.ts | 106 --------------------------- tests/jest.setup.ts | 20 ++--- 12 files changed, 102 insertions(+), 229 deletions(-) delete mode 100644 tests/jest-browser.setup.ts delete mode 100644 tests/jest-legacy.setup.ts delete mode 100644 tests/jest-message.setup.ts delete mode 100644 tests/jest-modules.setup.ts diff --git a/package-lock.json b/package-lock.json index eade6d21..a1809a66 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,6 @@ "istanbul-reports": "^3.2.0", "jest": "^30.2.0", "jest-environment-jsdom": "^30.0.0", - "jest-webextension-mock": "^4.0.0", "prettier": "^3.6.2", "react": "^19.2.8", "react-dom": "^19.2.8", @@ -14743,13 +14742,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/jest-webextension-mock": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/jest-webextension-mock/-/jest-webextension-mock-4.1.1.tgz", - "integrity": "sha512-smCGBtl+5MpjKnJhyjCeaL/ZtdUa0T41tbHsEdb1nY3UEVogbYvehVw2MuMSvysFD3wNv4iYSIEgAwnR3H0YIw==", - "dev": true, - "license": "ISC" - }, "node_modules/jest-worker": { "version": "30.3.0", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", diff --git a/package.json b/package.json index 65b5089a..9b189a34 100644 --- a/package.json +++ b/package.json @@ -212,7 +212,6 @@ "istanbul-reports": "^3.2.0", "jest": "^30.2.0", "jest-environment-jsdom": "^30.0.0", - "jest-webextension-mock": "^4.0.0", "prettier": "^3.6.2", "react": "^19.2.8", "react-dom": "^19.2.8", diff --git a/scripts/test-inventory-utils.mjs b/scripts/test-inventory-utils.mjs index dfcfff5f..be9a9c73 100644 --- a/scripts/test-inventory-utils.mjs +++ b/scripts/test-inventory-utils.mjs @@ -61,11 +61,9 @@ export function findStaticState(source, file) { export function verifyMigrationInventory(root, testFiles, sourceFiles) { const read = file => readFileSync(path.join(root, file), "utf8"); - const {legacyFiles, localMocks} = JSON.parse(read("tests/browser-harness/migration.json")); + const {localMocks} = JSON.parse(read("tests/browser-harness/migration.json")); - assert.equal(new Set(legacyFiles).size, legacyFiles.length, "Duplicate legacy exceptions"); - - for (const file of [...legacyFiles, ...Object.keys(localMocks)]) { + for (const file of Object.keys(localMocks)) { assert(testFiles.has(file), `Stale migration entry: ${file}`); } @@ -84,17 +82,10 @@ export function verifyMigrationInventory(root, testFiles, sourceFiles) { localMocks, "Module-mock inventory changed; migrate mocks or explicitly review exceptions" ); - const additional = Object.keys(actualMocks).filter(file => !legacyFiles.includes(file)); - const remaining = new Set([...legacyFiles, ...Object.keys(actualMocks)]); + console.info(`Migration: ${Object.keys(actualMocks).length} local-mock files remaining`); - console.info( - `Migration: ${legacyFiles.length} legacy files, ${additional.length} additional local-mock files, ${remaining.size} files remaining` - ); - - for (const file of sorted(remaining)) { - console.info( - ` ${file}: ${[...(legacyFiles.includes(file) ? ["legacy"] : []), ...(actualMocks[file] ?? [])].join(", ")}` - ); + for (const file of sorted(Object.keys(actualMocks))) { + console.info(` ${file}: ${actualMocks[file].join(", ")}`); } const declared = JSON.parse(read("tests/browser-harness/framework-state-inventory.json")); diff --git a/src/sandbox/SandboxMessage.test.ts b/src/sandbox/SandboxMessage.test.ts index 1d325e75..424a912c 100644 --- a/src/sandbox/SandboxMessage.test.ts +++ b/src/sandbox/SandboxMessage.test.ts @@ -1,15 +1,23 @@ -import {nanoid} from "nanoid"; +import {getBrowserTest} from "@tests/browser-harness/session"; import SandboxMessage from "./SandboxMessage"; import {SandboxMemory} from "./ports"; -import type {SandboxParameters} from "@typing/sandbox"; +import type {SandboxParameters, SandboxPort} from "@typing/sandbox"; + +const createMessage = (port: SandboxPort, parameters: Partial = {}): SandboxMessage => { + const message = new SandboxMessage("parser", port, parameters); + + getBrowserTest().addCleanup(() => message.dispose()); + + return message; +}; describe("SandboxMessage", () => { test("round-trips a request to the sandbox handler and resolves the response", async () => { const [hostPort, guestPort] = SandboxMemory.pair(); - const host = new SandboxMessage("parser", hostPort, {requestTimeout: 1000}); - const guest = new SandboxMessage("parser", guestPort); + const host = createMessage(hostPort, {requestTimeout: 1000}); + const guest = createMessage(guestPort); let receivedPath: string | undefined; @@ -25,8 +33,8 @@ describe("SandboxMessage", () => { test("propagates handler errors back to the caller", async () => { const [hostPort, guestPort] = SandboxMemory.pair(); - const host = new SandboxMessage("parser", hostPort, {requestTimeout: 1000}); - const guest = new SandboxMessage("parser", guestPort); + const host = createMessage(hostPort, {requestTimeout: 1000}); + const guest = createMessage(guestPort); guest.watch(() => { throw new TypeError("bad html"); @@ -39,31 +47,73 @@ describe("SandboxMessage", () => { }); test("rejects when no response arrives before requestTimeout", async () => { - const [hostPort] = SandboxMemory.pair(); // nothing watches the guest end + jest.useFakeTimers(); + + const [hostPort, guestPort] = SandboxMemory.pair(); + const host = createMessage(hostPort, {requestTimeout: 10}); + + createMessage(guestPort); // nothing watches the guest end + + const result = expect(host.send({path: "parse", args: []})).rejects.toThrow('Sandbox "parser" request'); + + await jest.advanceTimersByTimeAsync(0); + expect(jest.getTimerCount()).toBe(1); + await jest.advanceTimersByTimeAsync(9); + expect(jest.getTimerCount()).toBe(1); + await jest.advanceTimersByTimeAsync(1); + await result; + expect(jest.getTimerCount()).toBe(0); + }); + + test("correlates concurrent requests when responses arrive in reverse order", async () => { + jest.useFakeTimers(); + + const [hostPort, guestPort] = SandboxMemory.pair(); + const host = createMessage(hostPort, {requestTimeout: 1000}); + const guest = createMessage(guestPort); + const completed: number[] = []; + + guest.watch(({args}) => { + const value = args[0] as number; + + return new Promise(resolve => { + setTimeout( + () => { + completed.push(value); + resolve(value); + }, + (4 - value) * 10 + ); + }); + }); - const host = new SandboxMessage("parser", hostPort, {requestTimeout: 10}); + const result = Promise.all([ + host.send({path: "echo", args: [1]}), + host.send({path: "echo", args: [2]}), + host.send({path: "echo", args: [3]}), + ]); - await expect(host.send({path: "parse", args: []})).rejects.toThrow('Sandbox "parser" request'); + await jest.advanceTimersByTimeAsync(30); + await expect(result).resolves.toEqual([1, 2, 3]); + expect(completed).toEqual([3, 2, 1]); + expect(jest.getTimerCount()).toBe(0); }); - test("concurrent requests share the channel and all resolve", async () => { - // nanoid is globally mocked to a constant in tests; give each request a unique id. - let counter = 0; - jest.mocked(nanoid).mockImplementation(() => `req-${++counter}`); + test("rejects pending requests and clears their timers on dispose", async () => { + jest.useFakeTimers(); const [hostPort, guestPort] = SandboxMemory.pair(); - const host = new SandboxMessage("parser", hostPort, {requestTimeout: 1000}); - const guest = new SandboxMessage("parser", guestPort); - - guest.watch(({args}) => args[0]); - - await expect( - Promise.all([ - host.send({path: "echo", args: [1]}), - host.send({path: "echo", args: [2]}), - host.send({path: "echo", args: [3]}), - ]) - ).resolves.toEqual([1, 2, 3]); + const host = createMessage(hostPort, {requestTimeout: 1000}); + + createMessage(guestPort); + + const result = expect(host.send({path: "parse", args: []})).rejects.toThrow('Sandbox "parser" was disposed.'); + + await jest.advanceTimersByTimeAsync(0); + expect(jest.getTimerCount()).toBe(1); + host.dispose(); + await result; + expect(jest.getTimerCount()).toBe(0); }); test("caches one host channel per name and re-creates after dispose", () => { diff --git a/tests/README.md b/tests/README.md index efbd0d50..6220cd2d 100644 --- a/tests/README.md +++ b/tests/README.md @@ -15,7 +15,7 @@ The observed Node 24 crashes contain `Builtins_BaselineOutOfLinePrologue` and `C | `npm run test:firefox` | Real Firefox MV2/MV3 scenarios | Once | | `npm test` | Every Jest project, including both browsers | Once | | `npm run test:pre-push` | Framework typecheck, all non-browser tests and all fixture typechecks | Once | -| `npm run test:inventory` | Test ownership, migration exceptions and framework reset inventory | No | +| `npm run test:inventory` | Test ownership, local mocks and framework reset inventory | No | `npm run typecheck` checks framework and test-runner types. `npm run typecheck:integration` builds the package, prepares every fixture application and checks it with its own `tsconfig.json`. Declaration consumers without an application config belong to the Jest `types` project. @@ -41,16 +41,15 @@ See [integration/README.md](integration/README.md) for browser requirements and ## Browser harness migration -`tests/jest.setup.ts` selects one setup per file before importing the test module. The harness is the default for -unit, build and types projects. Only `SandboxMessage.test.ts`, listed in `browser-harness/migration.json`, still uses `jest-legacy.setup.ts`. -Remove exceptions as their tests migrate; new tests use the harness without registration or per-file `jest.unmock`. -Real Chrome/Firefox integrations load neither setup. +`tests/jest.setup.ts` installs the browser harness for every unit, build and types test. Real Chrome/Firefox +integrations load no setup. Each test gets real Browser/Storage modules unless it declares an inventoried local mock. +There is no global module mock or per-file setup selection. -The same inventory records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` calls for -`@addon-core/*` and `@main/env`. Eight additional files still use these local mocks, making 9 files to review in total. +`browser-harness/migration.json` records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` +calls for `@addon-core/*` and `@main/env`. Eight files still use these local mocks. Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. -`npm run test:inventory` rejects missing files, duplicate legacy entries and unrecorded or removed module mocks. +`npm run test:inventory` rejects missing files and unrecorded or removed module mocks. Update the inventory alongside each migration so stale exceptions cannot remain unnoticed. The harness setup creates a fresh `BrowserTestSession` before every test. Use `getBrowserTest()` from @@ -117,6 +116,9 @@ not prove that every reset implementation is correct, so session lifecycle tests dispose it with that context. The kit owns creation, closure and messaging; it does not execute the extension HTML. `tests/offscreen/MockLockManager.ts` models the Web Locks boundary for scheduling tests. Firefox/MV2 bridge tests use jsdom frames and explicit ready/error events; real-browser integrations remain the lifecycle check. +- Sandbox messaging uses `postMessage`, not WebExtension runtime messaging. `SandboxMessage.test.ts` uses the real + `SandboxMemory` port pair and real request IDs, with channels registered for session cleanup. Fake-timer tests cover + request deadlines, disposal and responses arriving out of order; DOM/provider and real-browser tests cover frame wiring. - The session does not emulate Web Locks, DOM inside the guest, or extension HTML execution. Keep explicit external adapters where needed and real-browser checks for DOM, execution worlds, browser lifecycle and vendor behavior. - Consult the kit's `RAW_CAPABILITY_COVERAGE` before using another browser API. Extend the kit when a required capability diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json index 3c1f8f2b..9bcf967b 100644 --- a/tests/browser-harness/migration.json +++ b/tests/browser-harness/migration.json @@ -1,7 +1,4 @@ { - "legacyFiles": [ - "src/sandbox/SandboxMessage.test.ts" - ], "localMocks": { "src/locale/helpers.test.ts": [ "@addon-core/browser" diff --git a/tests/browser-harness/session.ts b/tests/browser-harness/session.ts index 11da8101..03a56f23 100644 --- a/tests/browser-harness/session.ts +++ b/tests/browser-harness/session.ts @@ -12,7 +12,7 @@ export function startBrowserTest(): void { export function getBrowserTest(): BrowserTestSession { if (!session) { - throw new Error("Browser harness is unavailable. Use it inside a test or hook outside the legacy exceptions."); + throw new Error("Browser harness is unavailable. Use it inside a test or hook."); } return session; diff --git a/tests/jest-browser.setup.ts b/tests/jest-browser.setup.ts deleted file mode 100644 index c4d5308a..00000000 --- a/tests/jest-browser.setup.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {startBrowserTest, stopBrowserTest} from "./browser-harness/session"; - -beforeEach(startBrowserTest); - -afterEach(async () => { - try { - await stopBrowserTest(); - } finally { - jest.restoreAllMocks(); - jest.clearAllMocks(); - jest.useRealTimers(); - } -}); diff --git a/tests/jest-legacy.setup.ts b/tests/jest-legacy.setup.ts deleted file mode 100644 index 42d6d39f..00000000 --- a/tests/jest-legacy.setup.ts +++ /dev/null @@ -1,4 +0,0 @@ -import "jest-webextension-mock"; - -import "./jest-message.setup"; -import "./jest-modules.setup"; diff --git a/tests/jest-message.setup.ts b/tests/jest-message.setup.ts deleted file mode 100644 index e18826b0..00000000 --- a/tests/jest-message.setup.ts +++ /dev/null @@ -1,37 +0,0 @@ -type MessageSender = chrome.runtime.MessageSender; - -type MessageListener = (...args: any[]) => boolean | void; - -const listeners = new Set(); - -chrome.runtime.onMessage.addListener = jest.fn((callback: MessageListener) => listeners.add(callback)); -chrome.runtime.onMessage.removeListener = jest.fn((callback: MessageListener) => listeners.delete(callback)); -chrome.runtime.onMessage.hasListeners = jest.fn(() => listeners.size > 0); - -const dispatch = (message: unknown, sender: MessageSender, callback?: (response: any) => void): void => { - let called = false; - let asynchronous = false; - - for (const listener of listeners) { - const result = listener(message, sender, (response: any) => { - if (called) return; - - called = true; - callback?.(response); - }); - - asynchronous ||= result === true; - } - - if (!asynchronous && !called) { - callback?.(undefined); - } -}; - -chrome.runtime.sendMessage = jest.fn().mockImplementation((message, callback) => { - dispatch(message, {} as MessageSender, callback); -}); - -chrome.tabs.sendMessage = jest.fn().mockImplementation((tabId, message, options, callback) => { - dispatch(message, {} as MessageSender, callback); -}); diff --git a/tests/jest-modules.setup.ts b/tests/jest-modules.setup.ts deleted file mode 100644 index b1e475a3..00000000 --- a/tests/jest-modules.setup.ts +++ /dev/null @@ -1,106 +0,0 @@ -jest.mock("@addon-core/browser", () => ({ - __esModule: true, - - throwRuntimeError: jest.fn(), - getManifest: jest.fn(() => ({manifest_version: 3, name: "Test extension", version: "1.0.0", permissions: []})), - getManifestVersion: jest.fn(), - isAvailableScripting: jest.fn(), - - browser: jest.fn(() => chrome), - isBackground: jest.fn(() => false), - isManifestVersion3: jest.fn(() => true), - - hasOffscreen: jest.fn(), - getOffscreenContext: jest.fn(), - getOffscreenUrl: jest.fn(), - getOffscreenPath: jest.fn(), - hasOffscreenUrl: jest.fn(), - hasOffscreenPath: jest.fn(), - closeOffscreen: jest.fn(), - createOffscreen: jest.fn(), - - containsPermissions: jest.fn(() => true), - requestPermissions: jest.fn(() => true), - onPermissionsAdded: jest.fn(() => jest.fn()), - onPermissionsRemoved: jest.fn(() => jest.fn()), - - getContexts: jest.fn(), - getAllFrames: jest.fn(), - getBrowserInfo: jest.fn(() => Promise.resolve({name: "Firefox", version: "153.0"})), - - executeScript: (...args: Parameters) => { - return chrome.scripting.executeScript(...args); - }, - - sendMessage: (msg: any) => { - return new Promise(resolve => { - chrome.runtime.sendMessage(msg, (response: any) => { - resolve(response); - }); - }); - }, - - sendTabMessage: (tabId: number, msg: any, options: any) => { - return new Promise(resolve => { - chrome.tabs.sendMessage(tabId, msg, options, (response: any) => { - resolve(response); - }); - }); - }, - - onMessage: (callback: any) => { - chrome.runtime.onMessage.addListener(callback); - return () => chrome.runtime.onMessage.removeListener(callback); - }, -})); - -jest.mock("nanoid", () => ({ - nanoid: jest.fn(() => "mocked-id"), -})); - -jest.mock("nanoid/non-secure", () => ({ - nanoid: jest.fn(() => "mocked-id"), -})); - -jest.mock("@addon-core/storage", () => { - type Unsubscribe = () => void; - type WatchMap = Record void>; - - const createMockStorage = () => { - const store = new Map(); - let watchers: WatchMap = {}; - - const get = jest.fn(async (key: string) => store.get(key)); - const set = jest.fn(async (key: string, value: any) => { - store.set(key, value); - const cb = watchers[key]; - - if (cb) { - cb(value); - } - }); - const watch = jest.fn((map: WatchMap): Unsubscribe => { - watchers = map; - - return () => { - watchers = {}; - }; - }); - - return {get, set, watch}; - }; - - return { - __esModule: true, - Storage: { - Local: () => createMockStorage(), - Sync: () => createMockStorage(), - Session: () => createMockStorage(), - }, - }; -}); - -jest.mock("@main/env", () => ({ - ...jest.requireActual("@main/env"), - isBrowser: jest.fn().mockReturnValue(false), -})); diff --git a/tests/jest.setup.ts b/tests/jest.setup.ts index 139007eb..c4d5308a 100644 --- a/tests/jest.setup.ts +++ b/tests/jest.setup.ts @@ -1,11 +1,13 @@ -import path from "node:path"; -import {legacyFiles} from "./browser-harness/migration.json"; +import {startBrowserTest, stopBrowserTest} from "./browser-harness/session"; -// Temporary migration boundary. A file uses exactly one browser implementation. -const testFile = path.relative(path.resolve(__dirname, ".."), expect.getState().testPath!).split(path.sep).join("/"); +beforeEach(startBrowserTest); -if (legacyFiles.includes(testFile)) { - require("./jest-legacy.setup"); -} else { - require("./jest-browser.setup"); -} +afterEach(async () => { + try { + await stopBrowserTest(); + } finally { + jest.restoreAllMocks(); + jest.clearAllMocks(); + jest.useRealTimers(); + } +}); From c20da37c0e7e2cededddde73632742a6ace6615b Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:21:02 +0300 Subject: [PATCH 17/36] test(browser): replace remaining local module mocks Exercise locale storage, i18n and content URLs through the Browser harness. Rename the empty local-mock inventory and retain explicit generated-module and custom-driver boundaries. --- scripts/test-inventory-utils.mjs | 8 +- scripts/test-inventory.mjs | 4 +- .../content/lifecycle/nodes/FrameNode.test.ts | 15 +- .../lifecycle/nodes/isolated-styles.test.ts | 2 +- .../react/hooks/use-locale-attributes.test.ts | 8 +- .../adapters/react/hooks/use-locale.test.ts | 12 +- .../react/hooks/use-native-locale.test.ts | 9 +- src/locale/helpers.test.ts | 28 +-- src/locale/providers/DynamicLocale.test.ts | 177 ++++++++++-------- src/locale/storage/LocaleStorage.test.ts | 80 ++++---- tests/README.md | 18 +- tests/browser-harness/local-mocks.json | 3 + tests/browser-harness/migration.json | 29 --- 13 files changed, 190 insertions(+), 203 deletions(-) create mode 100644 tests/browser-harness/local-mocks.json delete mode 100644 tests/browser-harness/migration.json diff --git a/scripts/test-inventory-utils.mjs b/scripts/test-inventory-utils.mjs index be9a9c73..be7350f6 100644 --- a/scripts/test-inventory-utils.mjs +++ b/scripts/test-inventory-utils.mjs @@ -59,12 +59,12 @@ export function findStaticState(source, file) { return classes; } -export function verifyMigrationInventory(root, testFiles, sourceFiles) { +export function verifyTestInfrastructure(root, testFiles, sourceFiles) { const read = file => readFileSync(path.join(root, file), "utf8"); - const {localMocks} = JSON.parse(read("tests/browser-harness/migration.json")); + const {localMocks} = JSON.parse(read("tests/browser-harness/local-mocks.json")); for (const file of Object.keys(localMocks)) { - assert(testFiles.has(file), `Stale migration entry: ${file}`); + assert(testFiles.has(file), `Stale local-mock entry: ${file}`); } const actualMocks = {}; @@ -82,7 +82,7 @@ export function verifyMigrationInventory(root, testFiles, sourceFiles) { localMocks, "Module-mock inventory changed; migrate mocks or explicitly review exceptions" ); - console.info(`Migration: ${Object.keys(actualMocks).length} local-mock files remaining`); + console.info(`Local module mocks: ${Object.keys(actualMocks).length} files`); for (const file of sorted(Object.keys(actualMocks))) { console.info(` ${file}: ${actualMocks[file].join(", ")}`); diff --git a/scripts/test-inventory.mjs b/scripts/test-inventory.mjs index 1159261c..ac317a6f 100644 --- a/scripts/test-inventory.mjs +++ b/scripts/test-inventory.mjs @@ -3,7 +3,7 @@ import {execFile} from "node:child_process"; import {existsSync} from "node:fs"; import path from "node:path"; import {promisify} from "node:util"; -import {verifyMigrationInventory} from "./test-inventory-utils.mjs"; +import {verifyTestInfrastructure} from "./test-inventory-utils.mjs"; const execute = promisify(execFile); const root = path.resolve(import.meta.dirname, ".."); @@ -37,7 +37,7 @@ for (const project of projects) { } assert.deepEqual(new Set(owners.keys()), expected, "Every test file must belong to exactly one Jest project"); console.info(`${expected.size} test files, each in exactly one project`); -verifyMigrationInventory( +verifyTestInfrastructure( root, expected, tracked.split("\0").filter(file => file && existsSync(path.join(root, file))) diff --git a/src/entry/content/lifecycle/nodes/FrameNode.test.ts b/src/entry/content/lifecycle/nodes/FrameNode.test.ts index e5ae54bb..abd098b0 100644 --- a/src/entry/content/lifecycle/nodes/FrameNode.test.ts +++ b/src/entry/content/lifecycle/nodes/FrameNode.test.ts @@ -9,11 +9,10 @@ import {ContentScriptEvent} from "@typing/content"; import {FrameNode, Node, MountNode, EventNode} from "./index"; import {ContainerRegistry, EventEmitter} from "../context"; import IsolationSetup from "../IsolationSetup"; -import {getContentScriptStylesRuntime} from "./isolated-styles"; +import {readContentStyles} from "#adnbn/runtime"; -jest.mock("@addon-core/browser", () => ({getUrl: (file: string) => `chrome-extension://fixture/${file}`})); jest.mock("#adnbn/page", () => ({aliases: {panel: "panel.html"}})); -jest.mock("./isolated-styles", () => ({getContentScriptStylesRuntime: jest.fn()})); +jest.mock("#adnbn/runtime", () => ({readContentStyles: jest.fn()})); test("FrameNode explains a disconnected host and allows mounting again once the mounter connects it", () => { const runtime = { @@ -23,7 +22,7 @@ test("FrameNode explains a disconnected host and allows mounting again once the delete: jest.fn(), load: jest.fn(), }; - jest.mocked(getContentScriptStylesRuntime).mockReturnValue(runtime); + jest.mocked(readContentStyles).mockReturnValue(runtime); const anchor = document.createElement("section"); const container = document.createElement("div"); document.body.append(anchor); @@ -158,7 +157,7 @@ test("FrameNode replaces lost targets, unregisters old styles and releases its l delete: jest.fn(), load: jest.fn(), }; - jest.mocked(getContentScriptStylesRuntime).mockReturnValue(runtime); + jest.mocked(readContentStyles).mockReturnValue(runtime); const anchor = document.createElement("section"); document.body.append(anchor); const recover = jest.fn(); @@ -246,7 +245,7 @@ test.each(["throw", "unmount"])("FrameNode cleans up when target creation trigge delete: jest.fn(), load: jest.fn(), }; - jest.mocked(getContentScriptStylesRuntime).mockReturnValue(runtime); + jest.mocked(readContentStyles).mockReturnValue(runtime); const anchor = document.createElement("section"); document.body.append(anchor); @@ -306,7 +305,7 @@ const createBoundaryNode = ( delete: jest.fn(), load: jest.fn(), }; - jest.mocked(getContentScriptStylesRuntime).mockReturnValue(runtime); + jest.mocked(readContentStyles).mockReturnValue(runtime); const mountedNode = new MountNode( new Node(document.body, document.createElement("section")), @@ -365,7 +364,7 @@ test.each([ expect(frame.style.borderTopWidth).toBe("2px"); expect(frame.getAttribute("src")).toBe( - options.page ? "chrome-extension://fixture/panel.html" : (options.src ?? null) + options.page ? `chrome-extension://${chrome.runtime.id}/panel.html` : (options.src ?? null) ); frame.dispatchEvent(new Event("load")); diff --git a/src/entry/content/lifecycle/nodes/isolated-styles.test.ts b/src/entry/content/lifecycle/nodes/isolated-styles.test.ts index 35ad06a8..dbe92b47 100644 --- a/src/entry/content/lifecycle/nodes/isolated-styles.test.ts +++ b/src/entry/content/lifecycle/nodes/isolated-styles.test.ts @@ -6,7 +6,6 @@ import type {ContentScriptStylesRuntime} from "@typing/content"; import {getContentScriptStylesRuntime} from "./isolated-styles"; jest.mock("#adnbn/runtime", () => ({readContentStyles: jest.fn()})); -jest.mock("@addon-core/browser", () => ({getUrl: jest.fn()})); beforeEach(() => { jest.resetAllMocks(); @@ -31,5 +30,6 @@ test("initializes and returns the styles runtime supplied by the facade", () => expect(getContentScriptStylesRuntime()).toBe(runtime); expect(runtime.initialize).toHaveBeenCalledWith(getUrl); + expect(getUrl("style.css")).toBe(`chrome-extension://${chrome.runtime.id}/style.css`); expect(runtime.add).not.toHaveBeenCalled(); }); diff --git a/src/locale/adapters/react/hooks/use-locale-attributes.test.ts b/src/locale/adapters/react/hooks/use-locale-attributes.test.ts index 3f45d0ea..3914a0db 100644 --- a/src/locale/adapters/react/hooks/use-locale-attributes.test.ts +++ b/src/locale/adapters/react/hooks/use-locale-attributes.test.ts @@ -1,4 +1,5 @@ -jest.mock("@addon-core/browser", () => ({getI18nMessage: jest.fn(() => "en")})); +import {getBrowserTest} from "@tests/browser-harness/session"; + jest.mock("#adnbn/locale", () => require("./tests/fixtures/dynamic-locale")); import {createElement, StrictMode} from "react"; @@ -17,12 +18,15 @@ const arabic: Pick = { dir: LocaleDir.RightToLeft, }; +beforeEach(() => { + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setImplementation(() => "en"); +}); + afterEach(() => { cleanup(); document.documentElement.removeAttribute("lang"); document.documentElement.removeAttribute("dir"); document.body.replaceChildren(); - jest.restoreAllMocks(); }); test("defaults to html and restores existing and absent attributes in StrictMode", () => { diff --git a/src/locale/adapters/react/hooks/use-locale.test.ts b/src/locale/adapters/react/hooks/use-locale.test.ts index 6aea84e1..e2fd8cd4 100644 --- a/src/locale/adapters/react/hooks/use-locale.test.ts +++ b/src/locale/adapters/react/hooks/use-locale.test.ts @@ -1,6 +1,5 @@ -jest.mock("@addon-core/browser", () => ({ - getI18nMessage: jest.fn((key: string) => (key === "locale" ? "en" : "Native {{name}}")), -})); +import {getBrowserTest} from "@tests/browser-harness/session"; + jest.mock("#adnbn/locale", () => require("./tests/fixtures/dynamic-locale")); import {createElement, StrictMode, useState} from "react"; @@ -17,9 +16,14 @@ import type {Structure} from "./tests/fixtures/dynamic-locale"; const useFixtureLocale = (storage?: LocaleStorageDriver | false) => useLocale(storage) as LocaleReactDynamicContract; +beforeEach(() => { + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setImplementation(key => + key === "locale" ? "en" : "Native {{name}}" + ); +}); + afterEach(() => { cleanup(); - jest.restoreAllMocks(); }); test("two roots share an instance, update immediately and keep previous render functions consistent", async () => { diff --git a/src/locale/adapters/react/hooks/use-native-locale.test.ts b/src/locale/adapters/react/hooks/use-native-locale.test.ts index 9479732f..f80acfb2 100644 --- a/src/locale/adapters/react/hooks/use-native-locale.test.ts +++ b/src/locale/adapters/react/hooks/use-native-locale.test.ts @@ -1,4 +1,5 @@ -jest.mock("@addon-core/browser", () => ({getI18nMessage: jest.fn()})); +import {getBrowserTest} from "@tests/browser-harness/session"; + jest.mock("#adnbn/locale", () => ({ keys: Object.keys(require("./tests/fixtures/native-messages.json")).map(key => key.replaceAll("_", ".")), languages: ["en", "fr"], @@ -7,7 +8,6 @@ jest.mock("#adnbn/locale", () => ({ import {createElement, createRef, StrictMode, useState} from "react"; import {createPortal} from "react-dom"; import {cleanup, fireEvent, render, renderHook} from "@testing-library/react"; -import {getI18nMessage} from "@addon-core/browser"; import {useNativeLocale, type LocaleReactContract} from "../index"; import {NativeLocale} from "@locale/providers"; import {Language, LocaleDir} from "@typing/locale"; @@ -28,12 +28,13 @@ const useFixtureLocale = () => useNativeLocale() as LocaleReactContract { beforeEach(() => { - jest.mocked(getI18nMessage).mockImplementation(key => messages[key as keyof typeof messages] ?? ""); + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setImplementation( + key => messages[key as keyof typeof messages] ?? "" + ); }); afterEach(() => { cleanup(); - jest.restoreAllMocks(); }); test("works without a Provider and shares the existing native singleton across roots", () => { diff --git a/src/locale/helpers.test.ts b/src/locale/helpers.test.ts index 01ce02c7..4ef06b59 100644 --- a/src/locale/helpers.test.ts +++ b/src/locale/helpers.test.ts @@ -1,37 +1,27 @@ -jest.mock("@addon-core/browser", () => ({ - getI18nMessage: jest.fn((key: string) => { - const messages: Record = { - locale: "en", - app_title: "App title", - app_greeting: "Hello {{name}}", - }; - - return messages[key] ?? ""; - }), -})); - -import {getI18nMessage} from "@addon-core/browser"; +import {getBrowserTest} from "@tests/browser-harness/session"; import {resolve} from "./index"; jest.mock("#adnbn/locale", () => ({keys: ["locale", "app.title", "app.greeting"], languages: ["en"]})); describe("locale resolve", () => { beforeEach(() => { - jest.clearAllMocks(); - }); + const messages: Record = { + locale: "en", + app_title: "App title", + app_greeting: "Hello {{name}}", + }; - afterEach(() => { - jest.restoreAllMocks(); + getBrowserTest().harness.configurable.chrome.i18n.getMessage.setImplementation(key => messages[key] ?? ""); }); test.each(["Plain title", "", "@"])('leaves "%s" unchanged without reading native messages', input => { expect(resolve(input)).toBe(input); - expect(getI18nMessage).not.toHaveBeenCalled(); + expect(getBrowserTest().harness.configurable.chrome.i18n.getMessage.calls).toHaveLength(0); }); test("resolves runtime markers through the native provider", () => { expect(resolve("@app.title")).toBe("App title"); - expect(getI18nMessage).toHaveBeenCalledWith("app_title"); + expect(getBrowserTest().harness.configurable.chrome.i18n.getMessage.calls.at(-1)?.args[0]).toBe("app_title"); }); test("keeps placeholders when resolving without substitutions", () => { diff --git a/src/locale/providers/DynamicLocale.test.ts b/src/locale/providers/DynamicLocale.test.ts index 4a7af6ad..caa4839e 100644 --- a/src/locale/providers/DynamicLocale.test.ts +++ b/src/locale/providers/DynamicLocale.test.ts @@ -1,44 +1,35 @@ -jest.mock("@addon-core/browser", () => ({getI18nMessage: jest.fn(() => "en")})); -jest.mock("@addon-core/storage", () => ({Storage: {Local: jest.fn()}})); jest.mock("#adnbn/locale", () => require("./tests/fixtures/dynamic")); -import {getI18nMessage} from "@addon-core/browser"; -import {Storage, type StorageProvider} from "@addon-core/storage"; +import {getBrowserTest} from "@tests/browser-harness/session"; import DynamicLocale from "./DynamicLocale"; import {MemoryLocaleStorage} from "../tests/fixtures"; import {Language} from "@typing/locale"; import type {Structure} from "./tests/fixtures/dynamic"; -type Watcher = Record void>; const keys = ["greeting", "items", "app.title", "fallback", "empty", "__proto__"]; -let stored: string | undefined; -const listeners = new Set(); -const notify = (key: string, value: string | undefined) => { - for (const watcher of listeners) watcher[key]?.(value); +const i18n = () => getBrowserTest().harness.configurable.chrome.i18n.getMessage; +const storageHarness = () => getBrowserTest().harness.storage; +const stored = () => storageHarness().local.data["adnbn:locale"]; + +const notify = async (value: string | undefined) => { + if (value === undefined) { + await chrome.storage.local.remove("adnbn:locale"); + } else { + await chrome.storage.local.set({"adnbn:locale": value}); + } + + await storageHarness().flushChanges(); }; -const driver = { - get: jest.fn(async (_key: string) => stored), - set: jest.fn(async (key: string, value: string) => { - stored = value; - notify(key, value); - }), - watch: jest.fn((watcher: Watcher) => { - listeners.add(watcher); - return () => listeners.delete(watcher); - }), + +const seed = async (value: string | undefined) => { + await notify(value); + // Fixture preparation should not count as a write by the provider under test. + storageHarness().local.set.reset(); }; beforeEach(() => { - stored = undefined; - listeners.clear(); - jest.clearAllMocks(); - jest.mocked(getI18nMessage).mockReturnValue("en"); - jest.mocked(Storage.Local).mockReturnValue(driver as unknown as StorageProvider>); -}); - -afterEach(() => { - jest.restoreAllMocks(); + i18n().setResult("en"); }); test("reads the initial language from browser i18n and translates synchronously from the catalogue", () => { @@ -49,10 +40,10 @@ test("reads the initial language from browser i18n and translates synchronously expect(locale.trans("empty")).toBe(""); expect(locale.trans("__proto__")).toBe("Ordinary message"); expect(locale.choice("items", 2, {count: 2})).toBe("2 items"); - expect(getI18nMessage).toHaveBeenCalledTimes(1); - expect(getI18nMessage).toHaveBeenCalledWith("locale"); - expect(driver.get).not.toHaveBeenCalled(); - expect(driver.watch).not.toHaveBeenCalled(); + expect(i18n().calls).toHaveLength(1); + expect(i18n().calls[0].args[0]).toBe("locale"); + expect(storageHarness().local.get.calls).toHaveLength(0); + expect(storageHarness().onChanged.listenerCount()).toBe(0); }); test("exposes completed messages while retaining previously selected dictionaries", async () => { @@ -81,36 +72,35 @@ test("exposes completed messages while retaining previously selected dictionarie }); test("normalizes the native marker before selecting catalogue data", () => { - jest.mocked(getI18nMessage).mockReturnValue("en-GB"); + i18n().setResult("en-GB"); const locale = new DynamicLocale(false); expect(locale.lang()).toBe(Language.English); expect(locale.trans("app.title")).toBe("Catalogue"); }); test.each([undefined, "", "unknown"])("uses the build language when the native marker is invalid: %s", marker => { - jest.mocked(getI18nMessage).mockReturnValue(marker); + i18n().setImplementation(() => marker as string); const locale = new DynamicLocale(false); expect(locale.lang()).toBe(Language.French); expect(locale.trans("greeting", {name: "Ada"})).toBe("Bonjour Ada"); }); test("uses the build language when browser i18n is unavailable and changes synchronously in memory", async () => { - jest.mocked(getI18nMessage).mockImplementationOnce(() => { - throw new TypeError("Cannot read properties of undefined (reading 'getMessage')"); - }); + getBrowserTest().harness.capabilities.set("i18n.getMessage", false); const locale = new DynamicLocale(false); expect(locale.lang()).toBe(Language.French); expect(locale.trans("greeting", {name: "Ada"})).toBe("Bonjour Ada"); const changed = locale.change(Language.English); expect(locale.trans("greeting", {name: "Ada"})).toBe("Hello Ada"); await expect(changed).resolves.toBe(Language.English); - expect(Storage.Local).not.toHaveBeenCalled(); - expect(getI18nMessage).toHaveBeenCalledTimes(1); + expect(storageHarness().local.get.calls).toHaveLength(0); + expect(storageHarness().onChanged.listenerCount()).toBe(0); + expect(i18n().calls).toHaveLength(0); }); test("rejects a recognized native language that is absent from the catalogue", () => { const marker = "de"; - jest.mocked(getI18nMessage).mockReturnValue(marker); + i18n().setResult(marker); expect(() => new DynamicLocale(false)).toThrow('Language "de" is not available'); }); @@ -127,52 +117,63 @@ test("keeps public dot keys separate from catalogue keys and exposes available l expect(locale.keys().size).toBe(keys.length); }); -test("selects without saving and throws synchronously for an unavailable language without changing state", () => { - stored = Language.English; +test("selects without saving and throws synchronously for an unavailable language without changing state", async () => { + await seed(Language.English); const locale = new DynamicLocale(); expect(locale.select(Language.French)).toBe(Language.French); expect(locale.lang()).toBe(Language.French); expect(locale.trans("greeting", {name: "Ada"})).toBe("Bonjour Ada"); - expect(driver.set).not.toHaveBeenCalled(); - expect(stored).toBe(Language.English); + expect(storageHarness().local.set.calls).toHaveLength(0); + expect(stored()).toBe(Language.English); const messages = locale.messages(); expect(() => locale.select(Language.German)).toThrow('Language "de" is not available'); expect(locale.lang()).toBe(Language.French); expect(locale.messages()).toBe(messages); - expect(driver.set).not.toHaveBeenCalled(); - expect(stored).toBe(Language.English); + expect(storageHarness().local.set.calls).toHaveLength(0); + expect(stored()).toBe(Language.English); }); test("switches messages immediately while waiting for storage persistence", async () => { const write = Promise.withResolvers(); - driver.set.mockImplementationOnce(() => write.promise); - const locale = new DynamicLocale(); + const storage = new MemoryLocaleStorage(); + const persist = storage.set.bind(storage); + const save = jest.spyOn(storage, "set").mockImplementationOnce(async lang => { + await write.promise; + await persist(lang); + }); + + const locale = new DynamicLocale(storage); const saved = locale.change(Language.French); expect(locale.lang()).toBe(Language.French); expect(locale.trans("greeting", {name: "Ada"})).toBe("Bonjour Ada"); expect(locale.choice("items", 0, {count: 0})).toBe("0 article"); expect(locale.trans("fallback")).toBe("Shared default message"); expect(locale.trans("empty")).toBe(""); - expect(driver.set).toHaveBeenCalledWith("locale", Language.French); + expect(save).toHaveBeenCalledWith(Language.French); + expect(storage.value).toBeUndefined(); write.resolve(); await expect(saved).resolves.toBe(Language.French); + expect(storage.value).toBe(Language.French); }); test("persists a repeated selection of the current language under the framework namespace and locale key", async () => { const locale = new DynamicLocale(); await locale.change(Language.English); - expect(Storage.Local).toHaveBeenCalledWith({namespace: "adnbn"}); - expect(driver.set).toHaveBeenCalledWith("locale", Language.English); - expect(stored).toBe(Language.English); + expect(storageHarness().local.data).toEqual({"adnbn:locale": Language.English}); + expect(storageHarness().local.set.calls.at(-1)?.args).toEqual([{"adnbn:locale": Language.English}]); + expect(stored()).toBe(Language.English); }); test("rejects write errors without rolling back a newer selection", async () => { const write = Promise.withResolvers(); - driver.set.mockImplementationOnce(() => write.promise); - const locale = new DynamicLocale(); + const storage = new MemoryLocaleStorage(); + + jest.spyOn(storage, "set").mockImplementationOnce(() => write.promise); + + const locale = new DynamicLocale(storage); const first = locale.change(Language.French); await locale.change(Language.EnglishGreatBritain); const error = new Error("Storage write failed"); @@ -189,34 +190,38 @@ test("ignores prototype properties and rejects languages absent from the applica await expect(locale.change(Language.German)).rejects.toThrow('Language "de" is not available'); await expect(locale.change("__proto__" as Language)).rejects.toThrow("is not available"); expect(locale.lang()).toBe(Language.English); - expect(driver.set).not.toHaveBeenCalled(); + expect(storageHarness().local.set.calls).toHaveLength(0); }); test("restores the saved language only through explicit sync, without writing it back", async () => { - stored = Language.French; + await seed(Language.French); const locale = new DynamicLocale(); expect(locale.lang()).toBe(Language.English); - expect(driver.get).not.toHaveBeenCalled(); + expect(storageHarness().local.get.calls).toHaveLength(0); await expect(locale.sync()).resolves.toBe(Language.French); - expect(driver.get).toHaveBeenCalledWith("locale"); - expect(driver.set).not.toHaveBeenCalled(); + expect(storageHarness().local.get.calls.at(-1)?.args).toEqual(["adnbn:locale"]); + expect(storageHarness().local.set.calls).toHaveLength(0); }); test.each([undefined, "de"])("keeps the current language for invalid or absent storage: %s", async value => { - stored = value; + await seed(value); const warn = jest.spyOn(console, "warn").mockImplementation(); const locale = new DynamicLocale(); await expect(locale.sync()).resolves.toBe(Language.English); - expect(driver.set).not.toHaveBeenCalled(); - if (value) expect(warn).toHaveBeenCalledWith(`Incorrect language code in storage - "${value}"`); - else expect(warn).not.toHaveBeenCalled(); + expect(storageHarness().local.set.calls).toHaveLength(0); + + if (value) { + expect(warn).toHaveBeenCalledWith(`Incorrect language code in storage - "${value}"`); + } else { + expect(warn).not.toHaveBeenCalled(); + } }); test("propagates storage read errors without changing the language", async () => { const error = new Error("Storage read failed"); - driver.get.mockRejectedValueOnce(error); + storageHarness().local.get.failNext(error); const locale = new DynamicLocale(); - await expect(locale.sync()).rejects.toBe(error); + await expect(locale.sync()).rejects.toThrow(error.message); expect(locale.lang()).toBe(Language.English); }); @@ -224,43 +229,49 @@ test("applies external changes and own storage events without another write", as const locale = new DynamicLocale(); const handler = jest.fn(); const stop = locale.watch(handler); - notify("locale", Language.French); + + getBrowserTest().addCleanup(stop); + await notify(Language.French); expect(locale.lang()).toBe(Language.French); expect(handler).toHaveBeenLastCalledWith(Language.French); - expect(driver.set).not.toHaveBeenCalled(); + expect(storageHarness().local.set.calls).toHaveLength(1); await locale.change(Language.English); + await storageHarness().flushChanges(); expect(handler).toHaveBeenLastCalledWith(Language.English); - expect(driver.set).toHaveBeenCalledTimes(1); + expect(storageHarness().local.set.calls).toHaveLength(2); expect(handler).toHaveBeenCalledTimes(2); stop(); stop(); - notify("locale", Language.French); + await notify(Language.French); expect(locale.lang()).toBe(Language.English); - expect(listeners.size).toBe(0); + expect(storageHarness().onChanged.listenerCount()).toBe(0); }); test("retains the single-listener contract and permits subscribing again after unwatch", () => { const locale = new DynamicLocale(); + getBrowserTest().addCleanup(() => locale.unwatch()); locale.watch(); expect(() => locale.watch()).toThrow("Already subscribed"); locale.unwatch(); locale.watch(); - expect(listeners.size).toBe(1); + expect(storageHarness().onChanged.listenerCount()).toBe(1); locale.unwatch(); - expect(listeners.size).toBe(0); + expect(storageHarness().onChanged.listenerCount()).toBe(0); }); -test("reports invalid external changes without changing state or notifying the handler", () => { +test("reports invalid external changes without changing state or notifying the handler", async () => { const locale = new DynamicLocale(); const error = jest.spyOn(console, "error").mockImplementation(); const handler = jest.fn(); const stop = locale.watch(handler); - notify("locale", Language.German); - notify("locale", undefined); + + getBrowserTest().addCleanup(stop); + await notify(Language.German); + await notify(undefined); expect(error).toHaveBeenCalledWith("Error while changing language:", expect.any(Error)); expect(handler).not.toHaveBeenCalled(); expect(locale.lang()).toBe(Language.English); - expect(driver.set).not.toHaveBeenCalled(); + expect(storageHarness().local.set.calls).toHaveLength(1); stop(); }); @@ -269,17 +280,16 @@ test("supports memory-only changes without creating storage", async () => { const changed = locale.change(Language.French); expect(locale.lang()).toBe(Language.French); await expect(changed).resolves.toBe(Language.French); - expect(Storage.Local).not.toHaveBeenCalled(); - expect(driver.set).not.toHaveBeenCalled(); + expect(storageHarness().local.get.calls).toHaveLength(0); + expect(storageHarness().onChanged.listenerCount()).toBe(0); + expect(storageHarness().local.set.calls).toHaveLength(0); await expect(locale.sync()).rejects.toThrow("Language is not saving in storage"); expect(() => locale.watch()).toThrow("Language is not saved in storage"); expect(() => locale.unwatch()).not.toThrow(); }); test("uses a custom driver without constructing extension storage, including without browser i18n", async () => { - jest.mocked(getI18nMessage).mockImplementationOnce(() => { - throw new TypeError("Browser i18n is unavailable"); - }); + getBrowserTest().harness.capabilities.set("i18n.getMessage", false); const storage = new MemoryLocaleStorage(Language.English); const write = jest.spyOn(storage, "set"); const locale = new DynamicLocale(storage); @@ -287,6 +297,8 @@ test("uses a custom driver without constructing extension storage, including wit await expect(locale.sync()).resolves.toBe(Language.English); const handler = jest.fn(); const stop = locale.watch(handler); + + getBrowserTest().addCleanup(stop); const saved = locale.change(Language.French); expect(locale.trans("greeting", {name: "Ada"})).toBe("Bonjour Ada"); await expect(saved).resolves.toBe(Language.French); @@ -297,7 +309,8 @@ test("uses a custom driver without constructing extension storage, including wit await storage.set(Language.English); expect(handler).toHaveBeenCalledTimes(1); expect(locale.lang()).toBe(Language.French); - expect(Storage.Local).not.toHaveBeenCalled(); + expect(storageHarness().local.get.calls).toHaveLength(0); + expect(storageHarness().onChanged.listenerCount()).toBe(0); }); test("shares a supplied driver while keeping different drivers independent", async () => { diff --git a/src/locale/storage/LocaleStorage.test.ts b/src/locale/storage/LocaleStorage.test.ts index 2a310142..5f27c20d 100644 --- a/src/locale/storage/LocaleStorage.test.ts +++ b/src/locale/storage/LocaleStorage.test.ts @@ -1,50 +1,36 @@ -jest.mock("@addon-core/storage", () => ({Storage: {Local: jest.fn()}})); - -import {Storage, type StorageProvider} from "@addon-core/storage"; +import {getBrowserTest} from "@tests/browser-harness/session"; import {LocaleStorage} from "./index"; import {Language} from "@typing/locale"; -type Watcher = {locale?: (value: unknown) => void}; -let value: unknown; -const listeners = new Set(); -const emit = (next: unknown) => { - value = next; - for (const watcher of listeners) watcher.locale?.(next); -}; -const provider = { - get: jest.fn(async (_key: string): Promise => value), - set: jest.fn(async (_key: string, next: Language) => emit(next)), - watch: jest.fn((watcher: Watcher) => { - listeners.add(watcher); - return () => listeners.delete(watcher); - }), -}; +const storageHarness = () => getBrowserTest().harness.storage; -beforeEach(() => { - value = undefined; - listeners.clear(); - jest.clearAllMocks(); - jest.mocked(Storage.Local).mockReturnValue(provider as unknown as StorageProvider<{locale: Language}>); -}); +const emit = async (value: unknown) => { + if (value === undefined) { + await chrome.storage.local.remove("adnbn:locale"); + } else { + await chrome.storage.local.set({"adnbn:locale": value}); + } -afterEach(() => jest.restoreAllMocks()); + await storageHarness().flushChanges(); +}; test("uses the package namespace and locale key without reading or subscribing at construction", async () => { const storage = new LocaleStorage(); - expect(Storage.Local).toHaveBeenCalledWith({namespace: "adnbn"}); - expect(provider.get).not.toHaveBeenCalled(); - expect(provider.watch).not.toHaveBeenCalled(); + + expect(storageHarness().local.get.calls).toHaveLength(0); + expect(storageHarness().onChanged.listenerCount()).toBe(0); await expect(storage.get()).resolves.toBeUndefined(); - expect(provider.get).toHaveBeenCalledWith("locale"); + expect(storageHarness().local.get.calls.at(-1)?.args).toEqual(["adnbn:locale"]); await storage.set(Language.French); - expect(provider.set).toHaveBeenCalledWith("locale", Language.French); + expect(storageHarness().local.set.calls.at(-1)?.args).toEqual([{"adnbn:locale": Language.French}]); + expect(storageHarness().local.data).toEqual({"adnbn:locale": Language.French}); await expect(storage.get()).resolves.toBe(Language.French); }); test.each([Language.English, Language.EnglishGreatBritain, Language.German])( "reads a language independently of the application catalogue: %s", async lang => { - value = lang; + await emit(lang); await expect(new LocaleStorage().get()).resolves.toBe(lang); } ); @@ -56,11 +42,14 @@ test.each([null, "", "unknown", "__proto__", "en-GB", 1, {}, ["en"]])( const storage = new LocaleStorage(); const handler = jest.fn(); const stop = storage.watch(handler); - emit(invalid); + + getBrowserTest().addCleanup(stop); + await emit(invalid); await expect(storage.get()).resolves.toBeUndefined(); expect(handler).not.toHaveBeenCalled(); expect(warn).toHaveBeenCalledWith("[LocaleStorage] Invalid language code in storage:", invalid); - expect(provider.set).not.toHaveBeenCalled(); + // Only the external write; validation must not rewrite the invalid value. + expect(storageHarness().local.set.calls).toHaveLength(1); stop(); } ); @@ -71,27 +60,34 @@ test("notifies every subscriber of own and external selections, ignores deletion const second = jest.fn(); const stopFirst = storage.watch(first); const stopSecond = storage.watch(second); + + getBrowserTest().addCleanup(stopFirst); + getBrowserTest().addCleanup(stopSecond); expect(first).not.toHaveBeenCalled(); - emit(Language.German); + await emit(Language.German); await storage.set(Language.French); - emit(undefined); + await storageHarness().flushChanges(); + await emit(undefined); expect(first.mock.calls).toEqual([[Language.German], [Language.French]]); expect(second.mock.calls).toEqual(first.mock.calls); stopFirst(); stopFirst(); - emit(Language.English); + await emit(Language.English); expect(first).toHaveBeenCalledTimes(2); expect(second).toHaveBeenLastCalledWith(Language.English); stopSecond(); - expect(listeners.size).toBe(0); - expect(provider.set).toHaveBeenCalledTimes(1); + expect(storageHarness().onChanged.listenerCount()).toBe(0); + expect(storageHarness().local.set.calls).toHaveLength(3); }); test("propagates storage errors to the caller", async () => { const error = new Error("Storage denied"); - provider.get.mockRejectedValueOnce(error); - provider.set.mockRejectedValueOnce(error); + + storageHarness().local.get.failNext(error); + storageHarness().local.set.failNext(error); + const storage = new LocaleStorage(); - await expect(storage.get()).rejects.toBe(error); - await expect(storage.set(Language.French)).rejects.toBe(error); + + await expect(storage.get()).rejects.toThrow("Storage denied"); + await expect(storage.set(Language.French)).rejects.toThrow("Storage denied"); }); diff --git a/tests/README.md b/tests/README.md index 6220cd2d..00b23d17 100644 --- a/tests/README.md +++ b/tests/README.md @@ -39,18 +39,18 @@ Preparation and fixture typechecks use a bounded queue of up to four processes. See [integration/README.md](integration/README.md) for browser requirements and fixture layout. Browser tests use separate profiles, ports and application copies. Tests that edit or watch a fixture must retain their own copy and build; generated outputs must not be shared between mutable scenarios. -## Browser harness migration +## Browser harness `tests/jest.setup.ts` installs the browser harness for every unit, build and types test. Real Chrome/Firefox integrations load no setup. Each test gets real Browser/Storage modules unless it declares an inventoried local mock. There is no global module mock or per-file setup selection. -`browser-harness/migration.json` records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` -calls for `@addon-core/*` and `@main/env`. Eight files still use these local mocks. -Passing under harness setup does not prove that a locally mocked dependency was exercised. For each remaining mock, -decide whether to replace it with Browser/Storage controls or retain an explicit dependency boundary. +`browser-harness/local-mocks.json` records local `jest.mock`, `jest.doMock`, `jest.setMock` and `jest.unstable_mockModule` +calls for `@addon-core/*` and `@main/env`. The list is currently empty. Any future package mock requires an explicit inventory entry and review. +Passing under harness setup does not prove that a locally mocked dependency was exercised. Review any future exception +against the available Browser/Storage controls and document the dependency boundary it represents. `npm run test:inventory` rejects missing files and unrecorded or removed module mocks. -Update the inventory alongside each migration so stale exceptions cannot remain unnoticed. +Update the inventory alongside test changes so stale exceptions cannot remain unnoticed. The harness setup creates a fresh `BrowserTestSession` before every test. Use `getBrowserTest()` from `@tests/browser-harness/session` inside tests and hooks, not at module scope. The `@tests/*` alias maps to the root `tests/` @@ -58,6 +58,12 @@ directory in TypeScript and Jest. Browser and Storage imports are real unless th build-time virtual modules can still supply explicit test fixtures. `NativeLocale.test.ts` demonstrates API controls and call history instead of function mocks. +Locale tests configure the raw i18n API and exercise the real Storage package over harness storage, including namespaced +keys, external changes and failures. `MemoryLocaleStorage` remains a custom-driver fixture for controlled pending +writes and driver-sharing contracts. Build-generated `#adnbn/locale`, `#adnbn/page` and `#adnbn/runtime` retain explicit +fixtures; they are not Browser/Storage replacements. Content node tests inject the styles runtime through that generated +facade and use the real Browser URL helper. + ```ts import {getBrowserTest} from "@tests/browser-harness/session"; diff --git a/tests/browser-harness/local-mocks.json b/tests/browser-harness/local-mocks.json new file mode 100644 index 00000000..7beee656 --- /dev/null +++ b/tests/browser-harness/local-mocks.json @@ -0,0 +1,3 @@ +{ + "localMocks": {} +} diff --git a/tests/browser-harness/migration.json b/tests/browser-harness/migration.json deleted file mode 100644 index 9bcf967b..00000000 --- a/tests/browser-harness/migration.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "localMocks": { - "src/locale/helpers.test.ts": [ - "@addon-core/browser" - ], - "src/locale/adapters/react/hooks/use-native-locale.test.ts": [ - "@addon-core/browser" - ], - "src/locale/providers/DynamicLocale.test.ts": [ - "@addon-core/browser", - "@addon-core/storage" - ], - "src/locale/adapters/react/hooks/use-locale.test.ts": [ - "@addon-core/browser" - ], - "src/locale/adapters/react/hooks/use-locale-attributes.test.ts": [ - "@addon-core/browser" - ], - "src/locale/storage/LocaleStorage.test.ts": [ - "@addon-core/storage" - ], - "src/entry/content/lifecycle/nodes/isolated-styles.test.ts": [ - "@addon-core/browser" - ], - "src/entry/content/lifecycle/nodes/FrameNode.test.ts": [ - "@addon-core/browser" - ] - } -} From c534157544834bc47b325d291b551ada280c126d Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:21:32 +0300 Subject: [PATCH 18/36] chore(test): organize commands and pre-push checks Group test commands by execution scope and select features through Jest filters. Separate fixture typechecks from declaration tests and use check for pre-push. Update CI and contributor documentation without adding redundant package builds. --- .github/workflows/ci.yml | 2 +- .husky/pre-push | 2 +- CONTRIBUTING.md | 14 ++---- package.json | 37 +++++--------- src/relay/README.md | 10 ++-- tests/README.md | 51 ++++++++++++++------ tests/{pre-push.ts => check.ts} | 4 +- tests/integration/README.md | 19 ++++---- tests/integration/browser/options/README.md | 2 +- tests/integration/browser/override/README.md | 2 +- tsconfig.json | 2 +- 11 files changed, 77 insertions(+), 68 deletions(-) rename tests/{pre-push.ts => check.ts} (95%) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 26af4ee9..8f2911c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -126,7 +126,7 @@ jobs: - name: Install dependencies run: npm ci - name: Build package, prepare and typecheck every application - run: npm run typecheck:integration + run: npm run typecheck:fixtures browser: name: Browser (${{ matrix.browser }}, Node ${{ matrix.node }}) diff --git a/.husky/pre-push b/.husky/pre-push index ec7e6fe2..76299cc2 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,4 +1,4 @@ #!/usr/bin/env sh # Browser scenarios and coverage run in CI; build the package only once here. -npm run test:pre-push +npm run check diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 309caaa1..127d8c4e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,16 +83,10 @@ Releases and versioning: 3. Make changes and ensure commits follow Conventional Commits. 4. Before opening a PR, run local checks: - Formatting: `npm run format` - - Tests: `npm test` (the full suite includes a Chrome MV3 integration test; use `ADNBN_CHROME_BIN=/path/to/chrome` to override automatic Chrome discovery) - - By area (if needed): - - `npm run test:relay` - - `npm run test:service` - - `npm run test:storage` - - `npm run test:message` - - `npm run test:locale` - - `npm run test:manifest` - - `npm run test:entrypoint` - - `npm run test:plugins` + - Non-browser checks: `npm run check` (framework types, package build, non-browser tests and fixture typechecks). + - Full tests: `npm test` (includes Chrome and Firefox; use `ADNBN_CHROME_BIN` and `ADNBN_FIREFOX_BIN` to override discovery). + - Focused units: `npm run test:unit -- --testPathPatterns=src/locale` or `npm run test:unit -- --runTestsByPath src/sandbox/SandboxMessage.test.ts`. + - See [tests/README.md](tests/README.md) for test groups, watch mode, coverage and fixture commands. 5. Open a PR to the appropriate branch (usually `develop`) and include: - motivation and solution; - alternatives and trade-offs (if any); diff --git a/package.json b/package.json index 9b189a34..7f01ec14 100644 --- a/package.json +++ b/package.json @@ -114,34 +114,23 @@ "prepublishOnly": "npm run build", "build": "node ./scripts/clean-dist.js && tsup && tsc -p tsconfig.build.json && node ./scripts/copy-dts.js && tsc-alias -p tsconfig.build.json -f -fe .js", "format": "prettier --write .", - "typecheck": "tsc -p tsconfig.json --noEmit", - "prepare:integration": "npm run build && tsx tests/integration/prepare.ts", - "typecheck:integration": "npm run build && tsx tests/integration/typecheck.ts --prepare", - "test:integration": "npm run build && node scripts/test-run.mjs --selectProjects build types chrome firefox", - "test:integration:build": "npm run test:build --", + "check": "tsx tests/check.ts", "check:node-version": "node ./scripts/check-node-version.js", + "typecheck": "tsc -p tsconfig.json --noEmit", + "typecheck:fixtures": "npm run build && tsx tests/integration/typecheck.ts --prepare", + "fixtures:prepare": "npm run build && tsx tests/integration/prepare.ts", "test": "npm run build && node scripts/test-run.mjs", - "test:ci": "npm run test -- --ci --coverage", - "test:ci:non-browser": "npm run test -- --ci --coverage --selectProjects unit-node unit-dom build types", - "test:watch": "node scripts/test-run.mjs --selectProjects unit-node unit-dom --watch", - "test:offscreen": "node scripts/test-run.mjs --testPathPatterns=Offscreen", - "test:chrome": "npm run build && node scripts/test-run.mjs --selectProjects chrome", - "test:firefox": "npm run build && node scripts/test-run.mjs --selectProjects firefox", - "test:message": "node scripts/test-run.mjs --testPathPatterns=src/message", - "test:service": "node scripts/test-run.mjs --testPathPatterns=Service", - "test:relay": "npm run test -- --testPathPatterns=\"Relay|src/entry/relay|browser/relay\"", - "test:locale": "npm run test -- --testPathPatterns=Locale", - "test:manifest": "node scripts/test-run.mjs --testPathPatterns=src/cli/builders/manifest", - "test:entrypoint": "node scripts/test-run.mjs --testPathPatterns=src/cli/entrypoint", - "test:plugins": "npm run test -- --testPathPatterns=src/cli/plugins", - "release": "release-it", - "release:preview": "release-it --no-github.release --no-npm.publish --no-git.tag --ci", "test:unit": "node scripts/test-run.mjs --selectProjects unit-node unit-dom --verbose=false", - "test:build": "npm run build && node scripts/test-run.mjs --selectProjects build", - "test:types": "npm run typecheck:integration && node scripts/test-run.mjs --selectProjects types", + "test:watch": "node scripts/test-run.mjs --selectProjects unit-node unit-dom --watch", + "test:build": "npm run build && node scripts/test-run.mjs --selectProjects build --verbose=false", + "test:types": "npm run build && node scripts/test-run.mjs --selectProjects types --verbose=false", + "test:browser": "npm run build && node scripts/test-run.mjs --selectProjects chrome firefox --verbose=false", + "test:browser:chrome": "npm run build && node scripts/test-run.mjs --selectProjects chrome --verbose=false", + "test:browser:firefox": "npm run build && node scripts/test-run.mjs --selectProjects firefox --verbose=false", "test:inventory": "node scripts/test-inventory.mjs", - "test:pre-push": "tsx tests/pre-push.ts", - "test:run": "node scripts/test-run.mjs" + "test:run": "node scripts/test-run.mjs", + "release": "release-it", + "release:preview": "release-it --no-github.release --no-npm.publish --no-git.tag --ci" }, "dependencies": { "@addon-core/browser": "^0.9.0", diff --git a/src/relay/README.md b/src/relay/README.md index afbc1a05..d291a77e 100644 --- a/src/relay/README.md +++ b/src/relay/README.md @@ -296,24 +296,24 @@ Permission prompts, frame discovery, startup retries, and work inside the remote ### Verification -Run from the framework repository root. `test:relay` builds the package and runs Relay unit, build, type and browser tests; it requires local Chrome and Firefox: +Run from the framework repository root. The filtered `npm test` command builds the package and runs Relay unit, build, type and browser tests; it requires local Chrome and Firefox: ```bash npm run typecheck -npm run test:relay -- --runInBand -npm run test:message -- --runInBand +npm test -- --testPathPatterns="Relay|src/entry/relay|browser/relay" --runInBand +npm run test:unit -- --testPathPatterns=src/message --runInBand ``` For Relay runtime unit tests without a build or browsers: ```bash -npm run test:run -- --selectProjects unit-node unit-dom --testPathPatterns="src/relay/|src/entry/relay/" --runInBand +npm run test:unit -- --testPathPatterns="src/relay/|src/entry/relay/" --runInBand ``` `typecheck` checks both source and test files; passing Jest alone does not prove test files are type-correct. For the full non-browser regression suite, including shared transport and content aggregation tests: ```bash -npm test -- --runInBand --testPathIgnorePatterns=tests/integration/browser +npm run check ``` Important coverage lives in [`ProxyRelay.test.ts`](./providers/ProxyRelay.test.ts), [`RegisterRelay.test.ts`](./providers/RegisterRelay.test.ts), [`RelayScriptingAdapter.test.ts`](./adapters/RelayScriptingAdapter.test.ts), [`RelayDiscovery.test.ts`](./discovery/RelayDiscovery.test.ts), [`RelayPermission.test.ts`](./RelayPermission.test.ts), [`RelayParser.test.ts`](../cli/entrypoint/parser/RelayParser.test.ts), [`RelayDriver.test.ts`](../cli/plugins/content/RelayDriver.test.ts), [`RelayDeclaration.test.ts`](../cli/plugins/content/RelayDeclaration.test.ts), and [`ContentManager.test.ts`](../cli/plugins/content/ContentManager.test.ts). diff --git a/tests/README.md b/tests/README.md index 00b23d17..46b28eb1 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,18 +6,41 @@ Use the npm test commands below, or `npm run test:run --` to pass arguments dire The observed Node 24 crashes contain `Builtins_BaselineOutOfLinePrologue` and `ClearStaleLeftTrimmedPointerVisitor`, matching the Sparkplug report in [nodejs/node#62393](https://github.com/nodejs/node/issues/62393). The earlier `--no-maglev` workaround did not prevent the crash. Successful repetitions measure observed stability; they do not prove a rare native fault impossible. Keep the workaround scoped to the reproduced environment and reassess it when the upstream fix is verified. Jest workers inherit the flag through `execArgv`; CLI fixture child processes and real browsers keep their normal settings. Tests are never automatically retried or skipped to hide a native crash. -| Command | Checks | Builds the package | -| ------------------------ | --------------------------------------------------------------------- | ------------------ | -| `npm run test:unit` | Node and DOM unit tests | No | -| `npm run test:build` | Compiler plugins, the built CLI and build integrations | Once | -| `npm run test:types` | All fixture applications and declaration consumer tests | Once | -| `npm run test:chrome` | Real Chrome scenarios | Once | -| `npm run test:firefox` | Real Firefox MV2/MV3 scenarios | Once | -| `npm test` | Every Jest project, including both browsers | Once | -| `npm run test:pre-push` | Framework typecheck, all non-browser tests and all fixture typechecks | Once | -| `npm run test:inventory` | Test ownership, local mocks and framework reset inventory | No | - -`npm run typecheck` checks framework and test-runner types. `npm run typecheck:integration` builds the package, prepares every fixture application and checks it with its own `tsconfig.json`. Declaration consumers without an application config belong to the Jest `types` project. +| Command | Checks | Builds the package | +| ------------------------------ | -------------------------------------------------------------- | ------------------ | +| `npm test` | Every Jest project, including both browsers | Once | +| `npm run test:unit` | Node and DOM unit tests | No | +| `npm run test:watch` | Node and DOM unit tests in watch mode | No | +| `npm run test:build` | Compiler plugins, the built CLI and build integrations | Once | +| `npm run test:types` | Jest declaration consumer tests | Once | +| `npm run test:browser` | Chrome and Firefox scenarios | Once | +| `npm run test:browser:chrome` | Chrome scenarios | Once | +| `npm run test:browser:firefox` | Firefox scenarios | Once | +| `npm run test:inventory` | Test ownership, local mocks and framework reset inventory | No | +| `npm run test:run -- …` | Direct Jest arguments; caller selects groups and prepares dist | No | + +`npm run typecheck` checks framework and test-runner types. `npm run fixtures:prepare` builds the package and prepares +fixture applications for editor/manual use. `npm run typecheck:fixtures` builds the package, prepares every fixture +application and checks it with its own `tsconfig.json`. Declaration consumers without an application config belong to +the Jest `types` project and run through `test:types`; they are separate from fixture application typechecks. + +`npm run check` runs framework typechecking, one package build, all non-browser Jest projects and fixture preparation/typechecks. +Husky pre-push uses this same command. It does not launch browsers or collect coverage. + +Select a feature by path instead of adding a script for each module: + +```sh +npm run test:unit -- --testPathPatterns=src/locale +npm run test:unit -- --testPathPatterns=src/relay +npm run test:unit -- --runTestsByPath src/sandbox/SandboxMessage.test.ts +npm run test:watch -- --testPathPatterns=src/message +npm run test:unit -- --coverage +``` + +The selected group remains the boundary: `test:unit` never includes build or browser tests even when their paths match. +Commands that launch Jest forward arguments after `--` to Jest. Fixture preparation, fixture typechecking and `check` are separate +workflows, not Jest argument forwarders. Public build-dependent test commands always build first. CI and `check` reuse +one explicit build and invoke `test:run` (or its underlying wrapper) directly, avoiding nested build commands. ## Groups and workers @@ -33,7 +56,7 @@ npm run test:run -- --selectProjects chrome --maxWorkers=4 Jest uses up to eight workers, leaving one CPU available on smaller machines. Override the pool with `-- --maxWorkers=N`, or use `-- --runInBand` for a serial diagnostic run. The limit applies to the whole Jest invocation; do not pass both flags together. CI sets explicit smaller pools for compiler and browser jobs. -The trailing `--verbose=false` in `test:unit` terminates Jest's variadic `--selectProjects` option so an appended test filename is treated as a filename, not another project name. +The trailing `--verbose=false` in group commands terminates Jest's variadic `--selectProjects` option so an appended test filename is treated as a filename, not another project name. `test:watch` terminates it with `--watch`. Preparation and fixture typechecks use a bounded queue of up to four processes. Set `ADNBN_TEST_WORKERS` to a positive integer to override it. Each application keeps its own configuration and TypeScript process. Queued builds and typechecks allow up to 120 seconds per command. Builds inside Jest scenarios retain the 30-second limit so a hung CLI does not outlive its enclosing test deadline. A failure is reported after all running work has finished, with the child process diagnostics preserved. @@ -134,7 +157,7 @@ not prove that every reset implementation is correct, so session lifecycle tests Pre-commit checks only formatting of the **staged contents**, without changing the index or unstaged work. Format reported files and stage them before retrying. Tests, typechecking and builds run at pre-push and in CI. During development, run `test:unit` or `test:watch` explicitly for earlier feedback. Tests execute the working tree, so CI remains the full validation of the committed revision. -Pre-push runs `test:pre-push`, without browser launches or coverage. It includes both framework and fixture typechecks, compiler and CLI checks, and declaration consumers. Framework typechecking runs alongside the package build; after the build, Jest runs alongside the fixture queue. Jest inherits the terminal and streams its output live; fixture diagnostics remain buffered per command. Each fixture is typechecked as soon as its own build finishes. All tasks finish before failures are reported. Each task has a five-minute deadline; timeout or interruption terminates its complete process tree, including wrappers, Jest workers and child CLI processes. This uses a separate process group on macOS/Linux and `taskkill /T /F` on Windows. Browser checks run in CI and are also available through the explicit commands above. +Pre-push runs `npm run check`, without browser launches or coverage. It includes both framework and fixture typechecks, compiler and CLI checks, and declaration consumers. Framework typechecking runs alongside the package build; after the build, Jest runs alongside the fixture queue. Jest inherits the terminal and streams its output live; fixture diagnostics remain buffered per command. Each fixture is typechecked as soon as its own build finishes. All tasks finish before failures are reported. Each task has a five-minute deadline; timeout or interruption terminates its complete process tree, including wrappers, Jest workers and child CLI processes. This uses a separate process group on macOS/Linux and `taskkill /T /F` on Windows. Browser checks run in CI and are also available through the explicit commands above. ## CI and coverage diff --git a/tests/pre-push.ts b/tests/check.ts similarity index 95% rename from tests/pre-push.ts rename to tests/check.ts index 80190928..13f53460 100644 --- a/tests/pre-push.ts +++ b/tests/check.ts @@ -4,7 +4,9 @@ import {runQueue} from "./integration/utils/queue"; const root = path.resolve(import.meta.dirname, ".."); const npm = process.env.npm_execpath; -if (!npm) throw new Error("Run this check with npm run test:pre-push"); +if (!npm) { + throw new Error("Run this check with npm run check"); +} const phase = (tasks: {name: string; args: string[]; stdio?: "inherit"}[]) => runQueue(tasks, tasks.length, async ({name, args, stdio}) => { diff --git a/tests/integration/README.md b/tests/integration/README.md index d7f2054a..9b6ba23c 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -94,7 +94,7 @@ Each application retains its own `package.json`, `adnbn.config.ts`, and `tsconfi Install dependencies once at the repository root, then run: ```bash -npm run prepare:integration +npm run fixtures:prepare ``` This builds the framework, links each fixture's declared dependencies to the local framework or root `node_modules`, and builds all fixture applications. The generated `.adnbn` configuration and declarations remain beside their sources, so the editor can resolve `adnbn`, virtual imports such as `adnbn/browser`, CSS/SVG modules, and generated transport contracts. @@ -106,25 +106,26 @@ The generated `.adnbn`, `node_modules`, and `dist` directories are ignored by Gi To prepare and typecheck every fixture, including its configuration: ```bash -npm run typecheck:integration +npm run typecheck:fixtures ``` -The root `typecheck` checks framework and test-runner code; `typecheck:integration` additionally checks the fixture applications against their generated declarations. CI runs both. +The root `typecheck` checks framework and test-runner code; `typecheck:fixtures` additionally checks the fixture applications against their generated declarations. CI runs both. -The fixtures in `types` are checked by their Jest tests with isolated TypeScript programs against source and built package APIs. They do not have application configs and are not part of `prepare:integration` or `typecheck:integration`. +The fixtures in `types` are checked by their Jest tests with isolated TypeScript programs against source and built package APIs. They do not have application configs and are not part of `fixtures:prepare` or `typecheck:fixtures`. ## Run tests Run these commands from the repository root: ```bash -npm run test:integration -npm run test:integration:build -npm run test:chrome -npm run test:firefox +npm run test:build +npm run test:types +npm run test:browser +npm run test:browser:chrome +npm run test:browser:firefox ``` -They run all integration tests, only build checks, only Chrome checks, or only Firefox checks respectively. Each command builds the framework first. Browser tests require Chrome with `Extensions.loadUnpacked` support and Firefox with WebDriver BiDi `webExtension.install` support. Set `ADNBN_CHROME_BIN` or `ADNBN_FIREFOX_BIN` to the browser's absolute executable path if automatic discovery selects the wrong browser. Node must provide the built-in `WebSocket` API. CI installs both browsers on Linux; Windows runs the non-browser suite. +These commands run build integrations, declaration tests, both browsers, or an individual browser respectively. Each command builds the framework first. Browser tests require Chrome with `Extensions.loadUnpacked` support and Firefox with WebDriver BiDi `webExtension.install` support. Set `ADNBN_CHROME_BIN` or `ADNBN_FIREFOX_BIN` to the browser's absolute executable path if automatic discovery selects the wrong browser. Node must provide the built-in `WebSocket` API. CI installs both browsers on Linux; Windows runs the non-browser suite. These groups run test files in parallel, with up to eight workers by default (one fewer than the available CPUs on smaller machines). Pass `-- --maxWorkers=N` to tune the pool. Preparation and fixture typechecks run up to four independent applications concurrently; `ADNBN_TEST_WORKERS` overrides that limit. See [the test guide](../README.md) for project selection, hooks, coverage and the complete validation commands. diff --git a/tests/integration/browser/options/README.md b/tests/integration/browser/options/README.md index 9d12d33d..c2306a8f 100644 --- a/tests/integration/browser/options/README.md +++ b/tests/integration/browser/options/README.md @@ -15,4 +15,4 @@ Each Options entrypoint keeps its `styles.css` in the same directory. `backgroun Both cases open Options from the real background service worker in Chrome MV3. They check the exact `options_ui` object, the absence of `options_page`, a View chunk shared with the Help page, CSS application, state changes, and runtime errors. Expected pages are `options.html` and `ui/preferences.options.html`. -The embedded Options manifest matrix lives separately in `tests/integration/build/options/embedded` and runs without Chrome through `npm run test:integration:build`. It does not verify embedded settings UI in a browser. +The embedded Options manifest matrix lives separately in `tests/integration/build/options/embedded` and runs without Chrome through `npm run test:build`. It does not verify embedded settings UI in a browser. diff --git a/tests/integration/browser/override/README.md b/tests/integration/browser/override/README.md index 722d0705..7b648041 100644 --- a/tests/integration/browser/override/README.md +++ b/tests/integration/browser/override/README.md @@ -15,4 +15,4 @@ Each entrypoint keeps its `styles.css` in the same directory. Chromium accepts a The cases navigate a real Chrome MV3 tab to `chrome://newtab`, `chrome://bookmarks`, and `chrome://history`. They check the exact `chrome_url_overrides` object, that the loaded document is the extension page, CSS application, state changes, and runtime errors. Expected pages are `ui/dashboard.newtab.html`, `bookmarks.html`, and `history.html`. -The browser support matrix and the competing-entrypoints failure live separately in `tests/integration/build/override` and run without Chrome through `npm run test:integration:build`. +The browser support matrix and the competing-entrypoints failure live separately in `tests/integration/build/override` and run without Chrome through `npm run test:build`. diff --git a/tsconfig.json b/tsconfig.json index 5a7701e7..ae6b805a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -88,7 +88,7 @@ "./src/cli/virtual/virtual.d.ts", "node_modules/@types/node/*.d.ts", "tests/**/*.test.ts", - "tests/pre-push.ts", + "tests/check.ts", "tests/integration/*.ts", "tests/jest*.ts" ], From 71451b8fdd3b9cf6b5a075ef6e45870ff3ec53a2 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:22:01 +0300 Subject: [PATCH 19/36] perf(relay): preserve named definitions during type inference Match full Relay definitions before the partial bootstrap input and share the content constructor contract. Emitted runtime JavaScript remains unchanged. Keep registry and path checks in the declaration matrix; check constructors, prepared data, adapters and virtual bootstrap in source/package type fixtures. Validation: 203 suites and 2003 tests passed, including Chrome and Firefox. --- .../plugins/content/RelayDeclaration.test.ts | 8 -- .../tests/fixtures/relay-types/consumer.ts | 32 ------ src/entry/relay/Builder.ts | 22 ++++- src/entry/relay/index.ts | 25 ++++- src/types/content/definition.ts | 6 ++ tests/README.md | 4 + tests/integration/README.md | 2 + .../types/fixtures/relay/definition.ts | 99 +++++++++++++++++++ .../types/fixtures/relay}/virtual.ts | 0 .../types/relay.integration.test.ts | 58 +++++++++++ 10 files changed, 210 insertions(+), 46 deletions(-) create mode 100644 tests/integration/types/fixtures/relay/definition.ts rename {src/cli/plugins/content/tests/fixtures/relay-types => tests/integration/types/fixtures/relay}/virtual.ts (100%) create mode 100644 tests/integration/types/relay.integration.test.ts diff --git a/src/cli/plugins/content/RelayDeclaration.test.ts b/src/cli/plugins/content/RelayDeclaration.test.ts index 426ffc94..5e9c035e 100644 --- a/src/cli/plugins/content/RelayDeclaration.test.ts +++ b/src/cli/plugins/content/RelayDeclaration.test.ts @@ -67,14 +67,6 @@ describe("Relay declarations", () => { }; const rootNames = [consumerFile, generatedPath]; - if (mode === "source") { - rootNames.push( - path.join(fixtureDir, "virtual.ts"), - path.join(projectDir, "src/cli/virtual/relay.ts"), - path.join(projectDir, "src/cli/virtual/virtual.d.ts") - ); - } - // Give the real generated declaration a consumer-side location so package self-resolution works. // The package case deliberately has neither source aliases nor ambient virtual module declarations. const host = ts.createCompilerHost(options); diff --git a/src/cli/plugins/content/tests/fixtures/relay-types/consumer.ts b/src/cli/plugins/content/tests/fixtures/relay-types/consumer.ts index a07580e3..9d55d85a 100644 --- a/src/cli/plugins/content/tests/fixtures/relay-types/consumer.ts +++ b/src/cli/plugins/content/tests/fixtures/relay-types/consumer.ts @@ -1,41 +1,9 @@ import * as api from "adnbn"; import * as local from "adnbn/relay"; import * as entry from "adnbn/entry/relay"; -import {Builder as ContentBuilder} from "adnbn/entry/content/vanilla"; - type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false; type Expect = T; -const definition = api.defineRelay({ - name: "scanner", - allFrames: api.RelayAllFrames.All, - method: api.RelayMethod.Scripting, - init: () => ({scan: (text: string) => text.length}), -}); - -new entry.Builder(definition, ContentBuilder); - -const preparedDefinition = api.defineRelay({ - name: "scanner", - init: () => ({scan: (text: string) => text.length}), - prepare: async () => ({title: "Prepared"}), - - render: ({data}) => { - const title: string = data.title; - // @ts-expect-error: Runtime constructors preserve the inferred prepare data. - data.missing; - - return title; - }, -}); - -new entry.Builder(preparedDefinition, ContentBuilder); -entry.default(preparedDefinition, ContentBuilder); - -const unresolved: entry.RelayUnresolvedDefinition> = {}; -new entry.Builder(unresolved, ContentBuilder); -entry.default(entry.resolveDefinition({default: definition}, "scanner"), ContentBuilder); - const original = local.getRelay("scanner"); const scalar = api.getRelay("scanner", 1); const top = api.getRelay("scanner", {tabId: 1}); diff --git a/src/entry/relay/Builder.ts b/src/entry/relay/Builder.ts index bb038896..c6a89e2c 100644 --- a/src/entry/relay/Builder.ts +++ b/src/entry/relay/Builder.ts @@ -4,8 +4,13 @@ import TransportBuilder from "./TransportBuilder"; import EntrypointBuilder from "../core/Builder"; -import {RelayUnresolvedDefinition} from "@typing/relay"; -import {ContentScriptBuilder, ContentScriptDefinition, ContentScriptIsolation} from "@typing/content"; +import {RelayDefinition, RelayUnresolvedDefinition} from "@typing/relay"; +import { + ContentScriptBuilder, + ContentScriptBuilderConstructor, + ContentScriptDefinition, + ContentScriptIsolation, +} from "@typing/content"; import {TransportType} from "@typing/transport"; export default class Builder< @@ -18,9 +23,20 @@ export default class Builder< protected readonly _transport: TransportBuilder; protected readonly _content: ContentScriptBuilder; + // Preserve the named definition during inference instead of expanding its DOM option unions. + constructor( + definition: RelayDefinition, + contentBuilder: ContentScriptBuilderConstructor + ); + + constructor( + definition: RelayUnresolvedDefinition, + contentBuilder: ContentScriptBuilderConstructor + ); + constructor( protected readonly definition: RelayUnresolvedDefinition, - contentBuilder: new (definition: ContentScriptDefinition>) => ContentScriptBuilder + contentBuilder: ContentScriptBuilderConstructor ) { super(); diff --git a/src/entry/relay/index.ts b/src/entry/relay/index.ts index 74fbf0b8..39b43a46 100644 --- a/src/entry/relay/index.ts +++ b/src/entry/relay/index.ts @@ -1,21 +1,40 @@ import Builder from "./Builder"; -import type {RelayUnresolvedDefinition} from "@typing/relay"; +import type {RelayDefinition, RelayUnresolvedDefinition} from "@typing/relay"; import type {TransportType} from "@typing/transport"; -import type {ContentScriptIsolation} from "@typing/content"; +import type {ContentScriptBuilderConstructor, ContentScriptIsolation} from "@typing/content"; export {Builder}; export {resolveDefinition} from "./resolvers/definition"; export type {RelayUnresolvedDefinition} from "@typing/relay"; +// Match Builder's full-definition overload before the partial bootstrap input. +export default function relay< + T extends TransportType, + Data = unknown, + Isolation extends `${ContentScriptIsolation}` = `${ContentScriptIsolation}`, +>( + definition: RelayDefinition, + contentBuilder: ContentScriptBuilderConstructor +): void; + +export default function relay< + T extends TransportType, + Data = unknown, + Isolation extends `${ContentScriptIsolation}` = `${ContentScriptIsolation}`, +>( + definition: RelayUnresolvedDefinition, + contentBuilder: ContentScriptBuilderConstructor +): void; + export default function relay< T extends TransportType, Data = unknown, Isolation extends `${ContentScriptIsolation}` = `${ContentScriptIsolation}`, >( definition: RelayUnresolvedDefinition, - contentBuilder: ConstructorParameters>>[1] + contentBuilder: ContentScriptBuilderConstructor ): void { new Builder(definition, contentBuilder).build().catch(error => { console.error("Failed to build relay: ", error); diff --git a/src/types/content/definition.ts b/src/types/content/definition.ts index f1b32be8..f143ad0a 100644 --- a/src/types/content/definition.ts +++ b/src/types/content/definition.ts @@ -4,6 +4,7 @@ import type {ContentScriptBoundaryHandler} from "./boundary"; import type { ContentScriptAnchor, ContentScriptAnchorGetter, + ContentScriptBuilder, ContentScriptAppend, ContentScriptContainerCreator, ContentScriptContainerFactory, @@ -97,6 +98,11 @@ export type ContentScriptDefinition< Isolation extends `${ContentScriptIsolation}` = `${ContentScriptIsolation}`, > = ContentScriptDefinitionBase & ContentScriptRenderDefinition; +export type ContentScriptBuilderConstructor< + Data = unknown, + Isolation extends `${ContentScriptIsolation}` = `${ContentScriptIsolation}`, +> = new (definition: ContentScriptDefinition>) => ContentScriptBuilder; + export interface ContentScriptResolvedDefinition extends Omit< ContentScriptDefinitionBase, "anchor" | "marker" | "mount" | "container" | "watch" diff --git a/tests/README.md b/tests/README.md index 46b28eb1..1ccd44ab 100644 --- a/tests/README.md +++ b/tests/README.md @@ -46,6 +46,10 @@ one explicit build and invoke `test:run` (or its underlying wrapper) directly, a `jest.config.ts` defines six projects: `unit-node`, `unit-dom`, `build`, `types`, `chrome` and `firefox`. Put DOM tests in the `unit-dom` patterns. Tests that execute the built CLI, consume package declarations or compile imports from the built package belong to `build` or `types`. Keep unit tests runnable with no `dist` directory. Each browser file contains scenarios for one browser only. +Relay declaration tests keep the source/package × POSIX/Windows matrix for the generated registry, selectors and exports. +Constructor, prepared-data and bootstrap contracts live in `integration/types/relay.integration.test.ts`, checked against +both source aliases and the built package. Only its source case also checks the generated virtual entry module. + After a package build, direct Jest invocations can reuse that build: ```sh diff --git a/tests/integration/README.md b/tests/integration/README.md index 9b6ba23c..7494f82b 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -70,10 +70,12 @@ tests/integration/ ├── types/ │ ├── content.integration.test.ts │ ├── registries.integration.test.ts +│ ├── relay.integration.test.ts │ ├── view.integration.test.ts │ └── fixtures/ │ ├── content/ │ ├── registries/ +│ ├── relay/ │ └── view/ ├── utils/ │ ├── fixture.ts diff --git a/tests/integration/types/fixtures/relay/definition.ts b/tests/integration/types/fixtures/relay/definition.ts new file mode 100644 index 00000000..94b1645a --- /dev/null +++ b/tests/integration/types/fixtures/relay/definition.ts @@ -0,0 +1,99 @@ +import * as api from "adnbn"; +import * as entry from "adnbn/entry/relay"; +import {Builder as ContentBuilder} from "adnbn/entry/content/vanilla"; +import {Builder as ReactContentBuilder} from "adnbn/entry/content/react"; + +const definition = api.defineRelay({ + name: "scanner", + allFrames: api.RelayAllFrames.All, + method: api.RelayMethod.Scripting, + init: () => ({scan: (text: string) => text.length}), +}); + +new entry.Builder(definition, ContentBuilder); + +const preparedDefinition = api.defineRelay({ + name: "scanner", + init: () => ({scan: (text: string) => text.length}), + prepare: async () => ({title: "Prepared"}), + + render: ({data}) => { + const title: string = data.title; + // @ts-expect-error: Runtime constructors preserve the inferred prepare data. + data.missing; + + return title; + }, +}); + +const preparedBuilder = new entry.Builder(preparedDefinition, ContentBuilder); +entry.default(preparedDefinition, ContentBuilder); + +const unresolved: entry.RelayUnresolvedDefinition> = {}; +new entry.Builder(unresolved, ContentBuilder); +entry.default(entry.resolveDefinition({default: definition}, "scanner"), ContentBuilder); + +type PreparedData = {title: string}; +type Scanner = ReturnType; +type Equal = (() => T extends A ? 1 : 2) extends () => T extends B ? 1 : 2 ? true : false; +type Expect = T; + +type PreparedBuilder = Expect>>; + +new entry.Builder(preparedDefinition, ReactContentBuilder); +entry.default(preparedDefinition, ReactContentBuilder); + +class ShadowBuilder extends ContentBuilder {} + +const shadowDefinition = api.defineRelay({ + name: "shadow", + init: definition.init, + isolation: "shadow", + prepare: () => ({title: "Shadow"}), + container: {tagName: "input", value: "initial"}, + render: ({data}) => data.title, +}); + +new entry.Builder(shadowDefinition, ShadowBuilder); +entry.default(shadowDefinition, ShadowBuilder); + +const partial: entry.RelayUnresolvedDefinition = { + isolation: "shadow", + prepare: shadowDefinition.prepare, +}; + +new entry.Builder(partial, ShadowBuilder); +entry.default(partial, ShadowBuilder); + +// Inference is checked above; explicit arguments keep rejection checks from exploring fallback inference candidates. +// @ts-expect-error: A specialized adapter cannot receive a different isolation mode. +new entry.Builder(preparedDefinition, ShadowBuilder); +// @ts-expect-error: Bootstrap preserves the same adapter isolation constraint. +entry.default(preparedDefinition, ShadowBuilder); + +class NumericBuilder extends ContentBuilder {} + +// @ts-expect-error: The adapter must accept the data produced by prepare. +new entry.Builder(preparedDefinition, NumericBuilder); +// @ts-expect-error: Bootstrap must not erase the prepared data contract. +entry.default(preparedDefinition, NumericBuilder); + +new entry.Builder( + { + init: definition.init, + prepare: () => ({title: "Inline"}), + + render: ({data}) => { + // @ts-expect-error: Inline definitions still infer the prepare result. + data.missing; + + return data.title; + }, + }, + ContentBuilder +); + +// @ts-expect-error: Container properties remain specific to the selected HTML tag. +api.defineRelay({name: "invalid-tag", init: definition.init, container: {tagName: "div", href: "https://example.com"}}); +// @ts-expect-error: Container options do not accept DOM methods. +api.defineRelay({name: "invalid-method", init: definition.init, container: {tagName: "input", focus: () => {}}}); diff --git a/src/cli/plugins/content/tests/fixtures/relay-types/virtual.ts b/tests/integration/types/fixtures/relay/virtual.ts similarity index 100% rename from src/cli/plugins/content/tests/fixtures/relay-types/virtual.ts rename to tests/integration/types/fixtures/relay/virtual.ts diff --git a/tests/integration/types/relay.integration.test.ts b/tests/integration/types/relay.integration.test.ts new file mode 100644 index 00000000..4005ee28 --- /dev/null +++ b/tests/integration/types/relay.integration.test.ts @@ -0,0 +1,58 @@ +import path from "path"; +import ts from "typescript"; + +describe("Relay entrypoint contracts", () => { + const projectDir = path.resolve(__dirname, "../../.."); + const fixture = path.join(__dirname, "fixtures/relay/definition.ts"); + + test.each(["source", "package"])( + "checks Relay constructors and bootstrap through the %s API", + mode => { + const config = ts.readConfigFile(path.join(projectDir, "tsconfig.json"), ts.sys.readFile); + const parsed = ts.parseJsonConfigFileContent(config.config, ts.sys, projectDir); + expect(config.error).toBeUndefined(); + expect(parsed.errors).toEqual([]); + + const options: ts.CompilerOptions = { + ...(mode === "source" ? parsed.options : {}), + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + target: ts.ScriptTarget.ESNext, + jsx: ts.JsxEmit.ReactJSX, + strict: true, + noEmit: true, + skipLibCheck: mode === "source", + types: ["node", "chrome"], + }; + + const rootNames = [fixture]; + + if (mode === "source") { + rootNames.push( + path.join(__dirname, "fixtures/relay/virtual.ts"), + path.join(projectDir, "src/cli/virtual/relay.ts"), + path.join(projectDir, "src/cli/virtual/virtual.d.ts") + ); + } + + const host = ts.createCompilerHost(options); + const program = ts.createProgram(rootNames, options, host); + + const diagnostics = ts.getPreEmitDiagnostics(program).map(diagnostic => { + const message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); + const position = diagnostic.file?.getLineAndCharacterOfPosition(diagnostic.start ?? 0); + + return `${diagnostic.file?.fileName ?? "compiler"}:${(position?.line ?? 0) + 1}: ${message}`; + }); + + expect(diagnostics).toEqual([]); + + const apiFile = ts.resolveModuleName("adnbn", fixture, options, host).resolvedModule?.resolvedFileName; + + expect(apiFile).toBe( + path.join(projectDir, mode === "source" ? "src/index.ts" : "dist/index.d.ts").replace(/\\/g, "/") + ); + }, + 30_000 + ); +}); From 5fe410b9b5bb11a8fbcedff8c1746fe96727f980 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:22:22 +0300 Subject: [PATCH 20/36] docs(test): record migration and relay benchmarks Record raw local measurements, reproducible commands and comparison limits for the harness migration and subsequent Relay type-inference optimization. Distinguish repeated group timings from single full-suite and pre-push runs. --- tests/README.md | 3 + tests/benchmarks/2026-09-25-relay-types.json | 1612 ++++++++++++++++++ tests/benchmarks/2026-09-25-relay-types.md | 102 ++ tests/benchmarks/2026-09-25.json | 621 +++++++ tests/benchmarks/2026-09-25.md | 103 ++ 5 files changed, 2441 insertions(+) create mode 100644 tests/benchmarks/2026-09-25-relay-types.json create mode 100644 tests/benchmarks/2026-09-25-relay-types.md create mode 100644 tests/benchmarks/2026-09-25.json create mode 100644 tests/benchmarks/2026-09-25.md diff --git a/tests/README.md b/tests/README.md index 1ccd44ab..db5b17f9 100644 --- a/tests/README.md +++ b/tests/README.md @@ -42,6 +42,9 @@ Commands that launch Jest forward arguments after `--` to Jest. Fixture preparat workflows, not Jest argument forwarders. Public build-dependent test commands always build first. CI and `check` reuse one explicit build and invoke `test:run` (or its underlying wrapper) directly, avoiding nested build commands. +The [2026-09-25 benchmark](benchmarks/2026-09-25.md) records final migration timings, raw runs and comparison limits. +The [Relay type-checking follow-up](benchmarks/2026-09-25-relay-types.md) records the subsequent constructor inference fix. + ## Groups and workers `jest.config.ts` defines six projects: `unit-node`, `unit-dom`, `build`, `types`, `chrome` and `firefox`. Put DOM tests in the `unit-dom` patterns. Tests that execute the built CLI, consume package declarations or compile imports from the built package belong to `build` or `types`. Keep unit tests runnable with no `dist` directory. Each browser file contains scenarios for one browser only. diff --git a/tests/benchmarks/2026-09-25-relay-types.json b/tests/benchmarks/2026-09-25-relay-types.json new file mode 100644 index 00000000..435a1367 --- /dev/null +++ b/tests/benchmarks/2026-09-25-relay-types.json @@ -0,0 +1,1612 @@ +{ + "baselineRef": "7c4e136e", + "environment": { + "node": "24.5.0", + "typescript": "5.9.3", + "machine": "Apple M3 Max, 16 logical CPUs, macOS arm64", + "jestWorkers": 8, + "jestFlag": "--no-sparkplug", + "probeNodeFlags": [] + }, + "probe": { + "script": "const path = require('node:path');\nconst {execFileSync} = require('node:child_process');\nconst ts = require(path.join(process.cwd(), 'node_modules/typescript'));\nconst root = process.cwd();\nconst state = process.argv[2];\nconst options = {...ts.parseJsonConfigFileContent(ts.readConfigFile(path.join(root, 'tsconfig.json'), ts.sys.readFile).config, ts.sys, root).options, noEmit: true, types: ['node', 'chrome']};\nconst filename = path.join(root, 'tests/integration/types/fixtures/relay-probe.ts');\nconst source = `import {defineRelay} from \"adnbn\";\nimport * as entry from \"adnbn/entry/relay\";\nimport {Builder as ContentBuilder} from \"adnbn/entry/content/vanilla\";\nconst definition = defineRelay({name: \"scanner\", init: () => ({scan: (text: string) => text.length})});\nnew entry.Builder(definition, ContentBuilder);`;\nconst before = new Map();\nif (state !== 'after') {\n for (const file of ['src/entry/relay/Builder.ts', 'src/entry/relay/index.ts', 'src/types/content/definition.ts']) {\n before.set(path.join(root, file), execFileSync('git', ['show', `7c4e136e:${file}`], {encoding: 'utf8'}));\n }\n}\nconst host = ts.createCompilerHost(options);\nconst read = host.readFile.bind(host);\nhost.readFile = file => {\n if (file === filename) return source;\n let text = before.has(file) ? before.get(file) : read(file);\n if (state === 'single-tag' && file === path.join(root, 'src/types/content/common.ts')) {\n text = text.replace('Exclude', '\"div\"');\n }\n return text;\n};\nif (process.env.TRACE_DIR) ts.startTracing('project', process.env.TRACE_DIR);\nconst start = performance.now();\nconst program = ts.createProgram([filename, path.join(root, 'src/cli/virtual/virtual.d.ts')], options, host);\nconst created = performance.now();\nconst diagnostics = ts.getPreEmitDiagnostics(program);\nconst result = {state, createSeconds: (created-start)/1000, checkSeconds: (performance.now()-created)/1000, types: program.getTypeCount(), instantiations: program.getInstantiationCount(), diagnostics: diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\\n'))};\nif (ts.tracing) ts.tracing.stopTracing();\nconsole.log(JSON.stringify(result));\nprocess.exitCode = diagnostics.length ? 1 : 0;\n", + "runs": [ + { + "state": "before", + "createSeconds": 0.340453375, + "checkSeconds": 4.2901852090000006, + "types": 32827, + "instantiations": 519633, + "diagnostics": [], + "warmup": true + }, + { + "state": "after", + "createSeconds": 0.343325333, + "checkSeconds": 1.229511334, + "types": 32020, + "instantiations": 408563, + "diagnostics": [], + "warmup": true + }, + { + "state": "before", + "createSeconds": 0.344232667, + "checkSeconds": 4.200363708, + "types": 32827, + "instantiations": 519633, + "diagnostics": [], + "warmup": false + }, + { + "state": "after", + "createSeconds": 0.362251417, + "checkSeconds": 1.225805791, + "types": 32020, + "instantiations": 408563, + "diagnostics": [], + "warmup": false + }, + { + "state": "before", + "createSeconds": 0.3462235000000001, + "checkSeconds": 4.053631041, + "types": 32827, + "instantiations": 519633, + "diagnostics": [], + "warmup": false + }, + { + "state": "after", + "createSeconds": 0.349394875, + "checkSeconds": 1.1200603329999999, + "types": 32020, + "instantiations": 408563, + "diagnostics": [], + "warmup": false + }, + { + "state": "before", + "createSeconds": 0.341954542, + "checkSeconds": 4.284888, + "types": 32827, + "instantiations": 519633, + "diagnostics": [], + "warmup": false + }, + { + "state": "after", + "createSeconds": 0.39237950000000005, + "checkSeconds": 1.331300375, + "types": 32020, + "instantiations": 408563, + "diagnostics": [], + "warmup": false + }, + { + "state": "single-tag", + "createSeconds": 0.3891805, + "checkSeconds": 1.082017625, + "types": 30735, + "instantiations": 334384, + "diagnostics": [] + } + ], + "finalTraceExpressions": [] + }, + "testRuns": [ + { + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/relay-test-benchmark/build-types-0.json" + ], + "warmup": true, + "wallSeconds": 23.730027416953817, + "exitCode": 0, + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.833 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 7.035 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 7.115 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 7.479 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 8.113 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 8.75 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 10.237 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.554 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.185 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.767 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.326 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 5.044 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.992 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.375 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.795 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.974 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.013 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.522 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.364 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.861 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.635 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.386 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.333 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.148 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.58 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.059 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.154 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.894 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.046 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.035 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.117 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.046 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.044 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.029 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.824 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.548 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 23.016 + } + ] + }, + { + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/relay-test-benchmark/build-types-1.json" + ], + "warmup": false, + "wallSeconds": 23.09723254200071, + "exitCode": 0, + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.985 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.085 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.215 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.788 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 7.024 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.872 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 10.056 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 4.038 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.853 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.815 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.301 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 5.395 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 6.22 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 2.129 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.507 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 2.124 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.751 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 4.123 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.435 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.904 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.597 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.927 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.693 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.401 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.123 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.181 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.408 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.05 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.06 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.286 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.059 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.566 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.029 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.034 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.048 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.147 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 22.53 + } + ] + }, + { + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/relay-test-benchmark/build-types-2.json" + ], + "warmup": false, + "wallSeconds": 22.268417458981276, + "exitCode": 0, + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.759 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.342 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.389 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.961 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 7.254 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 8.03 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 9.876 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.486 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.685 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.878 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 5.083 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.687 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 6.316 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.875 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.158 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.637 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 2.756 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.847 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.509 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.603 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.847 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.129 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.474 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 1.034 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.508 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.276 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.043 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.067 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.06 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.12 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.038 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.057 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.035 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.136 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.137 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.214 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 21.686 + } + ] + }, + { + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/relay-test-benchmark/build-types-3.json" + ], + "warmup": false, + "wallSeconds": 22.588490209076554, + "exitCode": 0, + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 6.058 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.167 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.187 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.936 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 7.032 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.823 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 9.936 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.503 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.455 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.742 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 5.043 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.518 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.799 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.165 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.766 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.998 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.356 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.677 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.064 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.377 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.826 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.745 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.145 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.897 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.402 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.609 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.058 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.053 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.113 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.057 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.033 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.038 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.188 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.406 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.041 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.332 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 22.009 + } + ] + }, + { + "name": "check", + "command": [ + "npm", + "run", + "check" + ], + "wallSeconds": 42.11238812515512, + "exitCode": 0 + }, + { + "name": "full", + "command": [ + "npm", + "test", + "--", + "--json", + "--outputFile=/tmp/relay-test-benchmark/full.json" + ], + "wallSeconds": 52.32188737508841, + "exitCode": 0 + } + ], + "fullRun": { + "suites": 203, + "tests": 2003, + "passed": true, + "files": [ + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.953 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.54 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 7.711 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 8.663 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 10.083 + }, + { + "path": "tests/integration/browser/options/options.integration.test.ts", + "seconds": 3.9 + }, + { + "path": "tests/integration/browser/override/override.integration.test.ts", + "seconds": 10.587 + }, + { + "path": "tests/integration/browser/relay/scripting.firefox.integration.test.ts", + "seconds": 11.611 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 8.195 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 6.017 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 8.216 + }, + { + "path": "tests/integration/browser/content/relay-styles.firefox.integration.test.ts", + "seconds": 8.182 + }, + { + "path": "tests/integration/browser/content/frame-pages.firefox.integration.test.ts", + "seconds": 9.134 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 5.876 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 9.449 + }, + { + "path": "tests/integration/browser/content/isolation-iframe.firefox.integration.test.ts", + "seconds": 11.639 + }, + { + "path": "tests/integration/browser/content/frame-pages.integration.test.ts", + "seconds": 5.206 + }, + { + "path": "tests/integration/browser/content/entrypoint-assets.firefox.integration.test.ts", + "seconds": 8.36 + }, + { + "path": "tests/integration/browser/relay/scripting.integration.test.ts", + "seconds": 10.27 + }, + { + "path": "src/cli/entrypoint/parser/ContentParser.test.ts", + "seconds": 5.061 + }, + { + "path": "tests/integration/browser/content/isolation-shadow.integration.test.ts", + "seconds": 4.344 + }, + { + "path": "tests/integration/browser/content/render-props.integration.test.ts", + "seconds": 3.962 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 5.233 + }, + { + "path": "tests/integration/browser/content/isolation-iframe.integration.test.ts", + "seconds": 4.168 + }, + { + "path": "tests/integration/browser/content/render-props.firefox.integration.test.ts", + "seconds": 5.433 + }, + { + "path": "tests/integration/browser/content/isolation-shadow-mv2.firefox.integration.test.ts", + "seconds": 5.186 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 29.868 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 5.422 + }, + { + "path": "tests/integration/browser/content/selective-split.firefox.integration.test.ts", + "seconds": 5.901 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 4.862 + }, + { + "path": "tests/integration/browser/content/isolation-spike.firefox.integration.test.ts", + "seconds": 5.377 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.927 + }, + { + "path": "tests/integration/browser/locale/dynamic.firefox.integration.test.ts", + "seconds": 3.719 + }, + { + "path": "src/cli/entrypoint/file/ExpressionFile.test.ts", + "seconds": 3.673 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 4.759 + }, + { + "path": "tests/integration/browser/locale/chunks.firefox.integration.test.ts", + "seconds": 3.761 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 4.507 + }, + { + "path": "tests/integration/browser/content/isolation-shadow-mv3.firefox.integration.test.ts", + "seconds": 6.769 + }, + { + "path": "tests/integration/browser/offscreen/service.integration.test.ts", + "seconds": 4.385 + }, + { + "path": "tests/integration/browser/locale/chunks.integration.test.ts", + "seconds": 7.521 + }, + { + "path": "tests/integration/browser/locale/dollars.firefox.integration.test.ts", + "seconds": 6.214 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 5.119 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 6.45 + }, + { + "path": "tests/integration/browser/locale/dynamic.integration.test.ts", + "seconds": 5.434 + }, + { + "path": "tests/integration/utils/process.test.ts", + "seconds": 2.537 + }, + { + "path": "tests/integration/browser/content/entrypoint-assets.integration.test.ts", + "seconds": 4.694 + }, + { + "path": "tests/integration/browser/content/css-fonts.firefox.integration.test.ts", + "seconds": 8.136 + }, + { + "path": "tests/integration/browser/content/isolation-spike.integration.test.ts", + "seconds": 2.335 + }, + { + "path": "tests/integration/browser/content/selective-split.integration.test.ts", + "seconds": 2.94 + }, + { + "path": "tests/integration/browser/locale/dollars.integration.test.ts", + "seconds": 3.321 + }, + { + "path": "tests/integration/browser/view/render.integration.test.ts", + "seconds": 4.25 + }, + { + "path": "tests/integration/browser/content/css-fonts.integration.test.ts", + "seconds": 1.423 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 4.108 + }, + { + "path": "tests/release-it.test.ts", + "seconds": 2.109 + }, + { + "path": "tests/main-source-policy.test.ts", + "seconds": 0.741 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 1.647 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 1.609 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 3.14 + }, + { + "path": "tests/integration/browser/content/relay-styles.integration.test.ts", + "seconds": 3.152 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.854 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.669 + }, + { + "path": "src/cli/builders/manifest/Manifest.test.ts", + "seconds": 0.713 + }, + { + "path": "src/cli/plugins/locale/Locale.test.ts", + "seconds": 0.436 + }, + { + "path": "src/entry/content/adapters/vanilla/Builder.test.ts", + "seconds": 0.511 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.273 + }, + { + "path": "src/cli/entrypoint/finder/LocaleFinder.test.ts", + "seconds": 0.515 + }, + { + "path": "src/entry/content/lifecycle/nodes/RenderNode.test.ts", + "seconds": 0.966 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 3.44 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.328 + }, + { + "path": "src/relay/providers/RegisterRelay.test.ts", + "seconds": 0.09 + }, + { + "path": "src/cli/plugins/locale/index.test.ts", + "seconds": 0.25 + }, + { + "path": "src/cli/plugins/override/Override.test.ts", + "seconds": 0.263 + }, + { + "path": "src/service/providers/RegisterService.test.ts", + "seconds": 0.06 + }, + { + "path": "src/relay/providers/ProxyRelay.test.ts", + "seconds": 0.255 + }, + { + "path": "src/cli/entrypoint/file/OptionFile.test.ts", + "seconds": 0.297 + }, + { + "path": "src/locale/adapters/react/hooks/use-locale.test.ts", + "seconds": 0.406 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.092 + }, + { + "path": "src/relay/adapters/RelayScriptingAdapter.test.ts", + "seconds": 0.175 + }, + { + "path": "src/entry/content/lifecycle/nodes/FrameNode.test.ts", + "seconds": 0.132 + }, + { + "path": "tests/release-automation.test.ts", + "seconds": 0.216 + }, + { + "path": "src/entry/sandbox/index.test.ts", + "seconds": 0.123 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.055 + }, + { + "path": "src/cli/entrypoint/finder/PluginFinder.test.ts", + "seconds": 0.067 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.236 + }, + { + "path": "src/offscreen/OffscreenBridge.test.ts", + "seconds": 0.168 + }, + { + "path": "src/locale/adapters/react/hooks/use-native-locale.test.ts", + "seconds": 0.233 + }, + { + "path": "src/message/adapters/react/use-message-handler.test.ts", + "seconds": 0.157 + }, + { + "path": "src/message/message-response.test.ts", + "seconds": 0.206 + }, + { + "path": "src/entry/relay/Builder.test.ts", + "seconds": 0.1 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.128 + }, + { + "path": "src/entry/view/adapters/react/Builder.test.ts", + "seconds": 0.112 + }, + { + "path": "src/cli/entrypoint/finder/AbstractOverrideFinder.test.ts", + "seconds": 0.248 + }, + { + "path": "src/cli/entrypoint/parser/SandboxParser.test.ts", + "seconds": 0.157 + }, + { + "path": "src/entry/content/adapters/react/Builder.test.ts", + "seconds": 0.122 + }, + { + "path": "src/entry/content/lifecycle/MountBuilder.test.ts", + "seconds": 0.154 + }, + { + "path": "src/message/providers/Message.test.ts", + "seconds": 0.174 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.148 + }, + { + "path": "src/sandbox/providers/Sandbox.test.ts", + "seconds": 0.053 + }, + { + "path": "src/cli/entrypoint/parser/override-parsers.test.ts", + "seconds": 0.126 + }, + { + "path": "src/entry/content/lifecycle/context/ContainerRegistry.test.ts", + "seconds": 0.068 + }, + { + "path": "src/entry/content/resolvers/watch.test.ts", + "seconds": 0.135 + }, + { + "path": "src/cli/entrypoint/parser/RelayParser.test.ts", + "seconds": 0.151 + }, + { + "path": "src/cli/builders/manifest/utils/host-patterns.test.ts", + "seconds": 0.114 + }, + { + "path": "src/entry/content/lifecycle/markers/marker.test.ts", + "seconds": 0.156 + }, + { + "path": "src/cli/plugins/style/utils.test.ts", + "seconds": 0.241 + }, + { + "path": "src/cli/entrypoint/parser/BackgroundParser.test.ts", + "seconds": 0.097 + }, + { + "path": "tests/browser-harness.test.ts", + "seconds": 0.169 + }, + { + "path": "src/cli/entrypoint/finder/CommandFinder.test.ts", + "seconds": 0.111 + }, + { + "path": "src/entry/content/lifecycle/nodes/MountNode.test.ts", + "seconds": 0.085 + }, + { + "path": "src/cli/builders/manifest/ManifestV3.test.ts", + "seconds": 0.138 + }, + { + "path": "src/cli/entrypoint/name/NameGenerator.test.ts", + "seconds": 0.084 + }, + { + "path": "src/cli/entrypoint/parser/PopupParser.test.ts", + "seconds": 0.121 + }, + { + "path": "src/cli/entrypoint/parser/AbstractParser.test.ts", + "seconds": 0.156 + }, + { + "path": "src/cli/entrypoint/parser/SidebarParser.test.ts", + "seconds": 0.125 + }, + { + "path": "src/locale/observers/ObservableLocale.test.ts", + "seconds": 0.146 + }, + { + "path": "src/cli/entrypoint/parser/ServiceParser.test.ts", + "seconds": 0.122 + }, + { + "path": "src/offscreen/providers/ProxyOffscreen.test.ts", + "seconds": 0.119 + }, + { + "path": "src/cli/entrypoint/parser/OptionsParser.test.ts", + "seconds": 0.128 + }, + { + "path": "src/cli/entrypoint/parser/OffscreenParser.test.ts", + "seconds": 0.129 + }, + { + "path": "src/cli/plugins/content/ContentManager.test.ts", + "seconds": 0.256 + }, + { + "path": "src/entry/core/react.test.ts", + "seconds": 0.081 + }, + { + "path": "src/service/providers/ProxyService.test.ts", + "seconds": 0.096 + }, + { + "path": "src/cli/plugins/meta/Author.test.ts", + "seconds": 0.112 + }, + { + "path": "src/locale/adapters/react/ReactLocale.test.ts", + "seconds": 0.084 + }, + { + "path": "src/cli/plugins/content/RelayDriver.test.ts", + "seconds": 0.14 + }, + { + "path": "src/entry/content/lifecycle/IsolationSetup.test.ts", + "seconds": 0.079 + }, + { + "path": "src/cli/plugins/meta/SpecificSettings.test.ts", + "seconds": 0.109 + }, + { + "path": "src/cli/builders/manifest/ManifestV2.test.ts", + "seconds": 0.104 + }, + { + "path": "src/locale/providers/DynamicLocale.test.ts", + "seconds": 0.141 + }, + { + "path": "src/entry/content/lifecycle/nodes/ShadowNode.test.ts", + "seconds": 0.097 + }, + { + "path": "src/cli/entrypoint/parser/PageParser.test.ts", + "seconds": 0.091 + }, + { + "path": "src/cli/plugins/meta/Incognito.test.ts", + "seconds": 0.099 + }, + { + "path": "src/cli/plugins/view/View.test.ts", + "seconds": 0.155 + }, + { + "path": "src/cli/resolvers/config.test.ts", + "seconds": 0.08 + }, + { + "path": "src/locale/adapters/react/hooks/use-locale-attributes.test.ts", + "seconds": 0.123 + }, + { + "path": "src/cli/plugins/meta/Homepage.test.ts", + "seconds": 0.095 + }, + { + "path": "src/shared/content/isolation.test.ts", + "seconds": 0.135 + }, + { + "path": "src/cli/builders/manifest/utils/web-accessible-resources.test.ts", + "seconds": 0.15 + }, + { + "path": "src/cli/entrypoint/finder/OptionsFinder.test.ts", + "seconds": 0.089 + }, + { + "path": "src/cli/entrypoint/finder/ViewPermissionsFinder.test.ts", + "seconds": 0.203 + }, + { + "path": "src/cli/plugins/options/Options.test.ts", + "seconds": 0.085 + }, + { + "path": "src/cli/builders/manifest/utils/data-collection-permissions.test.ts", + "seconds": 0.055 + }, + { + "path": "src/frame/ReadyFrame.test.ts", + "seconds": 0.089 + }, + { + "path": "src/cli/entrypoint/finder/PageFinder.test.ts", + "seconds": 0.082 + }, + { + "path": "src/locale/providers/NativeLocale.test.ts", + "seconds": 0.05 + }, + { + "path": "src/cli/entrypoint/parser/CommandParser.test.ts", + "seconds": 0.09 + }, + { + "path": "src/entry/relay/index.test.ts", + "seconds": 0.072 + }, + { + "path": "src/locale/storage/LocaleStorage.test.ts", + "seconds": 0.079 + }, + { + "path": "src/locale/providers/CustomLocale.test.ts", + "seconds": 0.083 + }, + { + "path": "src/entry/offscreen/index.test.ts", + "seconds": 0.11 + }, + { + "path": "src/entry/view/resolvers/definition.test.ts", + "seconds": 0.067 + }, + { + "path": "src/entry/transport/resolvers/definition.test.ts", + "seconds": 0.053 + }, + { + "path": "src/entry/view/adapters/vanilla/Builder.test.ts", + "seconds": 0.057 + }, + { + "path": "src/entry/core/render.test.ts", + "seconds": 0.074 + }, + { + "path": "src/entry/command/resolvers/definition.test.ts", + "seconds": 0.056 + }, + { + "path": "src/cli/entrypoint/finder/AbstractEntrypointFinder.test.ts", + "seconds": 0.047 + }, + { + "path": "src/shared/locale/messages.test.ts", + "seconds": 0.053 + }, + { + "path": "src/cli/plugins/dotenv/utils.test.ts", + "seconds": 0.044 + }, + { + "path": "src/entry/background/resolvers/definition.test.ts", + "seconds": 0.047 + }, + { + "path": "src/cli/entrypoint/finder/AbstractViewFinder.test.ts", + "seconds": 0.06 + }, + { + "path": "src/entry/content/adapters/vanilla/definition.test.ts", + "seconds": 0.038 + }, + { + "path": "src/sandbox/SandboxMessage.test.ts", + "seconds": 0.057 + }, + { + "path": "tests/integration/browser/utils/browser.test.ts", + "seconds": 0.052 + }, + { + "path": "src/message/error.test.ts", + "seconds": 0.045 + }, + { + "path": "src/cli/builders/locale/LocaleBuilder.test.ts", + "seconds": 0.069 + }, + { + "path": "src/locale/helpers.test.ts", + "seconds": 0.047 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.054 + }, + { + "path": "src/entry/view/adapters/vanilla/definition.test.ts", + "seconds": 0.046 + }, + { + "path": "src/cli/plugins/content/utils.test.ts", + "seconds": 0.07 + }, + { + "path": "src/cli/plugins/typescript/declaration/transport/TransportDeclaration.test.ts", + "seconds": 0.05 + }, + { + "path": "src/cli/entrypoint/finder/PopupFinder.test.ts", + "seconds": 0.053 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.053 + }, + { + "path": "src/entry/content/resolvers/target.test.ts", + "seconds": 0.136 + }, + { + "path": "src/cli/entrypoint/finder/ViewCspFinder.test.ts", + "seconds": 0.038 + }, + { + "path": "src/cli/builders/locale/LocaleStructureValidator.test.ts", + "seconds": 0.069 + }, + { + "path": "src/entry/content/adapters/react/definition.test.ts", + "seconds": 0.06 + }, + { + "path": "src/entry/content/resolvers/definition.test.ts", + "seconds": 0.056 + }, + { + "path": "src/relay/discovery/RelayDiscovery.test.ts", + "seconds": 0.046 + }, + { + "path": "src/cli/entrypoint/parser/schemas/permissions.test.ts", + "seconds": 0.056 + }, + { + "path": "src/main/relay.test.ts", + "seconds": 0.05 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.045 + }, + { + "path": "tests/integration/browser/utils/site.test.ts", + "seconds": 0.038 + }, + { + "path": "src/transport/RegisterTransport.test.ts", + "seconds": 0.04 + }, + { + "path": "src/offscreen/providers/RegisterOffscreen.test.ts", + "seconds": 0.048 + }, + { + "path": "src/entry/view/adapters/react/definition.test.ts", + "seconds": 0.062 + }, + { + "path": "src/cli/builders/manifest/utils/permissions.test.ts", + "seconds": 0.046 + }, + { + "path": "src/entry/content/adapters/react/Node.test.ts", + "seconds": 0.083 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.036 + }, + { + "path": "src/entry/content/lifecycle/markers/AttributeMarker.test.ts", + "seconds": 0.04 + }, + { + "path": "src/relay/RelayPermission.test.ts", + "seconds": 0.038 + }, + { + "path": "src/cli/entrypoint/name/InlineNameGenerator.test.ts", + "seconds": 0.042 + }, + { + "path": "src/cli/builders/csp/SandboxCsp.test.ts", + "seconds": 0.037 + }, + { + "path": "src/entry/content/adapters/vanilla/Node.test.ts", + "seconds": 0.034 + }, + { + "path": "src/cli/bundler/utils/app-filename.test.ts", + "seconds": 0.041 + }, + { + "path": "src/cli/builders/csp/Csp.test.ts", + "seconds": 0.033 + }, + { + "path": "src/cli/plugins/content/bundler.test.ts", + "seconds": 0.035 + }, + { + "path": "src/entry/content/lifecycle/context/Context.test.ts", + "seconds": 0.039 + }, + { + "path": "src/entry/content/lifecycle/nodes/isolated-styles.test.ts", + "seconds": 0.043 + }, + { + "path": "src/entry/relay/resolvers/definition.test.ts", + "seconds": 0.036 + }, + { + "path": "src/entry/content/lifecycle/markers/WeakMarker.test.ts", + "seconds": 0.067 + }, + { + "path": "tests/integration/utils/queue.test.ts", + "seconds": 0.031 + }, + { + "path": "tests/browser-harness-dom.test.ts", + "seconds": 0.023 + }, + { + "path": "src/locale/tests/language-names.test.ts", + "seconds": 0.037 + } + ] + }, + "beforeOverrideSeconds": [ + 22.815, 21.953, 22.327 + ], + "afterSourceHashes": { + "src/entry/relay/Builder.ts": "97bb31d1c95db5f867771466be8f4db7ef4f1f5faa3c15c26f2952fda7f99baf", + "src/entry/relay/index.ts": "70e76b237fb09a828a66051b879bf72f5304fe7f60b7817f5566d4c0dd29e0b5", + "src/types/content/definition.ts": "b7c5d5e69f1ffbdde58743657f0b45e68102bff9bf715d43c9402c55e049b88a" + } +} diff --git a/tests/benchmarks/2026-09-25-relay-types.md b/tests/benchmarks/2026-09-25-relay-types.md new file mode 100644 index 00000000..8943bcec --- /dev/null +++ b/tests/benchmarks/2026-09-25-relay-types.md @@ -0,0 +1,102 @@ +# Relay type-checking follow-up — 2026-09-25 + +This follows the [migration benchmark](2026-09-25.md). Measurements use the same Apple M3 Max, 16 logical CPUs, +macOS arm64, Node 24.5.0 and TypeScript 5.9.3. Jest retains eight workers and `--no-sparkplug`. +All reported test runs passed. These are local measurements, not Linux/Windows or Node 22 CI results. + +## Diagnosis and change + +An independent compiler-host probe confirmed that narrowing `ContentScriptContainerTag` to `"div"` in memory reduces +checking of a minimal Relay constructor consumer from about 4.2 seconds to 1.08 seconds. The repository's container type +was not narrowed. The baseline trace placed 3.12 seconds in `checkExpression` for `new entry.Builder(...)`; CPU profiling +also identified type inference and relation checks. Instantiation counts alone do not explain elapsed time. + +The expensive boundary accepted a full `RelayDefinition` only through `RelayUnresolvedDefinition`, a partial mapped type. +`Builder` and the Relay bootstrap function now try a full-definition overload first. This preserves the named definition +for inference; the existing partial-input signature remains last, including its parameter-reflection behavior. +`ContentScriptBuilderConstructor` names the constructor contract shared by these signatures. + +Container tags, tag-specific attributes, callback data and isolation restrictions remain unchanged. TypeScript transpilation +with comments removed produces identical JavaScript for all three changed implementation/contract files. No JIT flags, +worker settings or runtime implementation were changed. + +The declaration test retains source/package × POSIX/Windows coverage for registry augmentation, selectors and exports. +Constructors and bootstrap moved to `types/relay.integration.test.ts`, with source and package modes. It preserves the +original complete, prepared, unresolved and resolved-input calls and source virtual-entry checks. Additional checks cover +React adapters, inferred prepared data, specialized adapters, invalid data/isolation and invalid container options. + +Failed overload resolution with fully inferred incompatible adapters can still be expensive: four negative calls took +about 4.4 seconds each in a diagnostic probe. Their rejection was verified. The permanent rejection checks supply explicit +type arguments; inference is checked separately by positive calls and an exact inferred Builder type assertion. This change +optimizes valid named definitions, not every possible error-reporting path. + +## Minimal consumer comparison + +The probe uses the same virtual consumer file, compiler options and installed dependencies in both states. Before each +baseline program, the compiler host reads only the three changed production files from `7c4e136e`; the fixed state reads +the working tree. It does not check out files or modify the repository. Both use source aliases and ordinary Node without +Jest or a JIT flag. Each state has one warm-up and three measured runs, alternating before/after in separate Node processes. + +| Metric | Before | After | +| ---------------------------- | ------------- | ------------- | +| Median diagnostic/check time | 4.200 s | 1.226 s | +| Check-time range | 4.054–4.285 s | 1.120–1.331 s | +| Type instantiations | 519,633 | 408,563 | + +Checking this complete source program is **3.43× faster**. Program construction is excluded from that figure and is recorded +separately in the raw results. The separate single-tag control is a diagnostic experiment, not a proposed API change. + +## Test-suite comparison + +Build + types ran sequentially with one warm-up and three measured runs. These commands reuse an existing package build +and include Jest JSON reporting, matching the earlier benchmark's group measurement. The new suite has one additional +file and two additional Jest cases; existing constructor scenarios were moved rather than discarded. + +| Measurement | Earlier benchmark | After this change | +| ------------------------------------- | ---------------------------- | ----------------- | +| Build + types median wall | 54.48 s | 22.59 s | +| Build + types range | 52.89–54.48 s | 22.27–23.10 s | +| Suites / tests | 36 / 283 | 37 / 285 | +| RelayDeclaration file median | 53.91 s | 3.88 s | +| New Relay constructor file median | Included in declaration file | 4.69 s | +| Override build file median | 22.33 s | 22.01 s | +| Whole pre-push/check command, one run | 64.03 s | 42.11 s | +| Whole npm test, one run | 66.16 s | 52.32 s | + +Build + types is **2.41× faster**. The remaining longest file is the override build integration, with 33 CLI build scenarios. +Its timing is effectively unchanged; these measurements do not establish a type-checking bottleneck in View. +The two Relay files can now run concurrently, but the group remains bounded by the override file and available workers. + +`check` passed **172 suites / 1961 tests**, framework typechecking and preparation/typechecking of 24 fixture applications. +The full run passed **203 suites / 2003 tests**, including Chrome and Firefox. The whole-command figures are single +observations, not medians or guarantees: pre-push/check improved about 1.52× and the full command about 1.26× in these runs. +The earlier pre-push command was renamed to `check` without changing its stages. + +## Raw results and reproduction + +The [raw results](2026-09-25-relay-types.json) contain all probe runs, the exact probe script, group warm-up and measured runs, +per-file timings, full-suite results and command exit codes. The embedded probe script pins the baseline source revision. +Restore it into a temporary file and run it from the repository root: + +```sh +node -e "require('node:fs').writeFileSync('/tmp/relay-type-comparison.cjs', require('./tests/benchmarks/2026-09-25-relay-types.json').probe.script)" +node /tmp/relay-type-comparison.cjs before +node /tmp/relay-type-comparison.cjs after +node /tmp/relay-type-comparison.cjs single-tag +``` + +Set `TRACE_DIR` to an empty temporary directory to record the compiler trace for a probe. The source consumer exists only +in the compiler host. It instantiates `defineRelay({name: 'scanner', init: () => ({scan: (text: string) => text.length})})` +through `new entry.Builder(definition, ContentBuilder)` using the vanilla adapter. + +For the suite measurements, build once, warm up the group once, then repeat it three times with unique report filenames: + +```sh +npm run build +npm run test:run -- --selectProjects build types --json --outputFile=/tmp/relay-build-types.json +npm run check +npm test -- --json --outputFile=/tmp/relay-full.json +``` + +Measure whole commands with an external monotonic timer. Do not compare diagnostic-only probe time with Jest wall time, +or per-file sums with CPU time. The full/check commands each include their own single package build. diff --git a/tests/benchmarks/2026-09-25.json b/tests/benchmarks/2026-09-25.json new file mode 100644 index 00000000..06138dee --- /dev/null +++ b/tests/benchmarks/2026-09-25.json @@ -0,0 +1,621 @@ +{ + "environment": { + "platform": "macOS-26.6.2-arm64-arm-64bit-Mach-O", + "node": "v24.5.0", + "logicalCpus": 16, + "cpu": "Apple M3 Max", + "revision": "7c4e136ec013b979fa34310ba418fb0d2f596da5", + "dirty": true, + "workers": 8 + }, + "runs": [ + { + "group": "build", + "run": 0, + "warmup": true, + "command": [ + "npm", + "run", + "build" + ], + "seconds": 3.335339958081022, + "exitCode": 0 + }, + { + "group": "build", + "run": 1, + "warmup": false, + "command": [ + "npm", + "run", + "build" + ], + "seconds": 3.304855166003108, + "exitCode": 0 + }, + { + "group": "build", + "run": 2, + "warmup": false, + "command": [ + "npm", + "run", + "build" + ], + "seconds": 3.349509875057265, + "exitCode": 0 + }, + { + "group": "build", + "run": 3, + "warmup": false, + "command": [ + "npm", + "run", + "build" + ], + "seconds": 3.2570682920049876, + "exitCode": 0 + }, + { + "group": "unit", + "run": 0, + "warmup": true, + "command": [ + "npm", + "run", + "test:unit", + "--", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-0.jest.json" + ], + "seconds": 5.005515957949683, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 28.503999999999998, + "jestSeconds": 4.518 + }, + { + "group": "unit", + "run": 1, + "warmup": false, + "command": [ + "npm", + "run", + "test:unit", + "--", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-1.jest.json" + ], + "seconds": 5.151686375029385, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 30.551, + "jestSeconds": 4.708 + }, + { + "group": "unit", + "run": 2, + "warmup": false, + "command": [ + "npm", + "run", + "test:unit", + "--", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-2.jest.json" + ], + "seconds": 5.455427624983713, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 30.965, + "jestSeconds": 4.942 + }, + { + "group": "unit", + "run": 3, + "warmup": false, + "command": [ + "npm", + "run", + "test:unit", + "--", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-3.jest.json" + ], + "seconds": 4.92035145778209, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 26.595, + "jestSeconds": 4.318 + }, + { + "group": "unit-coverage", + "run": 0, + "warmup": true, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "unit-node", + "unit-dom", + "--coverage", + "--coverageProvider=babel", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-coverage-0.jest.json" + ], + "seconds": 8.700660167029127, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 34.702, + "jestSeconds": 8.131 + }, + { + "group": "unit-coverage", + "run": 1, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "unit-node", + "unit-dom", + "--coverage", + "--coverageProvider=babel", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-coverage-1.jest.json" + ], + "seconds": 8.086944292066619, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 31.598, + "jestSeconds": 7.543 + }, + { + "group": "unit-coverage", + "run": 2, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "unit-node", + "unit-dom", + "--coverage", + "--coverageProvider=babel", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-coverage-2.jest.json" + ], + "seconds": 8.519587750080973, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 32.342999999999996, + "jestSeconds": 7.938 + }, + { + "group": "unit-coverage", + "run": 3, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "unit-node", + "unit-dom", + "--coverage", + "--coverageProvider=babel", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/unit-coverage-3.jest.json" + ], + "seconds": 11.017556832870468, + "exitCode": 0, + "suites": 135, + "tests": 1676, + "passed": 1676, + "sumFileSeconds": 45.572, + "jestSeconds": 10.32 + }, + { + "group": "build-types", + "run": 0, + "warmup": true, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/build-types-0.jest.json" + ], + "seconds": 54.95127954194322, + "exitCode": 0, + "suites": 36, + "tests": 283, + "passed": 283, + "sumFileSeconds": 177.17000000000002, + "jestSeconds": 54.513 + }, + { + "group": "build-types", + "run": 1, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/build-types-1.jest.json" + ], + "seconds": 52.88551312498748, + "exitCode": 0, + "suites": 36, + "tests": 283, + "passed": 283, + "sumFileSeconds": 167.33, + "jestSeconds": 52.486 + }, + { + "group": "build-types", + "run": 2, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/build-types-2.jest.json" + ], + "seconds": 54.48285354208201, + "exitCode": 0, + "suites": 36, + "tests": 283, + "passed": 283, + "sumFileSeconds": 163.808, + "jestSeconds": 54.134 + }, + { + "group": "build-types", + "run": 3, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/build-types-3.jest.json" + ], + "seconds": 54.48391433409415, + "exitCode": 0, + "suites": 36, + "tests": 283, + "passed": 283, + "sumFileSeconds": 166.48, + "jestSeconds": 54.129 + }, + { + "group": "chrome", + "run": 0, + "warmup": true, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "chrome", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/chrome-0.jest.json" + ], + "seconds": 14.210940917022526, + "exitCode": 0, + "suites": 17, + "tests": 20, + "passed": 20, + "sumFileSeconds": 104.067, + "jestSeconds": 13.744 + }, + { + "group": "chrome", + "run": 1, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "chrome", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/chrome-1.jest.json" + ], + "seconds": 8.6537630839739, + "exitCode": 0, + "suites": 17, + "tests": 20, + "passed": 20, + "sumFileSeconds": 60.949, + "jestSeconds": 8.259 + }, + { + "group": "chrome", + "run": 2, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "chrome", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/chrome-2.jest.json" + ], + "seconds": 8.633810291998088, + "exitCode": 0, + "suites": 17, + "tests": 20, + "passed": 20, + "sumFileSeconds": 60.577, + "jestSeconds": 8.309 + }, + { + "group": "chrome", + "run": 3, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "chrome", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/chrome-3.jest.json" + ], + "seconds": 8.815473000053316, + "exitCode": 0, + "suites": 17, + "tests": 20, + "passed": 20, + "sumFileSeconds": 61.57, + "jestSeconds": 8.363 + }, + { + "group": "firefox", + "run": 0, + "warmup": true, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "firefox", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/firefox-0.jest.json" + ], + "seconds": 14.199823916889727, + "exitCode": 0, + "suites": 14, + "tests": 22, + "passed": 22, + "sumFileSeconds": 95.919, + "jestSeconds": 13.734 + }, + { + "group": "firefox", + "run": 1, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "firefox", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/firefox-1.jest.json" + ], + "seconds": 12.811804458033293, + "exitCode": 0, + "suites": 14, + "tests": 22, + "passed": 22, + "sumFileSeconds": 91.847, + "jestSeconds": 12.407 + }, + { + "group": "firefox", + "run": 2, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "firefox", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/firefox-2.jest.json" + ], + "seconds": 13.957261291798204, + "exitCode": 0, + "suites": 14, + "tests": 22, + "passed": 22, + "sumFileSeconds": 92.166, + "jestSeconds": 13.574 + }, + { + "group": "firefox", + "run": 3, + "warmup": false, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "firefox", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/firefox-3.jest.json" + ], + "seconds": 13.349818667164072, + "exitCode": 0, + "suites": 14, + "tests": 22, + "passed": 22, + "sumFileSeconds": 92.161, + "jestSeconds": 12.916 + }, + { + "group": "fixture-typecheck", + "run": 0, + "warmup": true, + "command": [ + "node", + "node_modules/tsx/dist/cli.mjs", + "tests/integration/typecheck.ts", + "--prepare" + ], + "seconds": 9.378492458024994, + "exitCode": 0 + }, + { + "group": "fixture-typecheck", + "run": 1, + "warmup": false, + "command": [ + "node", + "node_modules/tsx/dist/cli.mjs", + "tests/integration/typecheck.ts", + "--prepare" + ], + "seconds": 9.46627854090184, + "exitCode": 0 + }, + { + "group": "fixture-typecheck", + "run": 2, + "warmup": false, + "command": [ + "node", + "node_modules/tsx/dist/cli.mjs", + "tests/integration/typecheck.ts", + "--prepare" + ], + "seconds": 8.924615582916886, + "exitCode": 0 + }, + { + "group": "fixture-typecheck", + "run": 3, + "warmup": false, + "command": [ + "node", + "node_modules/tsx/dist/cli.mjs", + "tests/integration/typecheck.ts", + "--prepare" + ], + "seconds": 8.819596541812643, + "exitCode": 0 + }, + { + "group": "full", + "run": 0, + "warmup": false, + "command": [ + "npm", + "run", + "test", + "--", + "--json", + "--outputFile=/tmp/addon-bone-final-bench/full-0.jest.json" + ], + "seconds": 66.16412891703658, + "exitCode": 0, + "suites": 202, + "tests": 2001, + "passed": 2001, + "sumFileSeconds": 357.851, + "jestSeconds": 61.758 + }, + { + "group": "pre-push", + "run": 0, + "warmup": false, + "command": [ + "npm", + "run", + "test:pre-push" + ], + "seconds": 64.03328733285889, + "exitCode": 0, + "jestSeconds": 58.354 + } + ], + "matchedUnitComparison": { + "command": [ + "npm", + "run", + "test:unit" + ], + "before": [ + { + "run": 0, + "warmup": true, + "seconds": 4.9167931659612805, + "exitCode": 0 + }, + { + "run": 1, + "warmup": false, + "seconds": 4.4468214579392225, + "exitCode": 0 + }, + { + "run": 2, + "warmup": false, + "seconds": 4.580718582961708, + "exitCode": 0 + }, + { + "run": 3, + "warmup": false, + "seconds": 4.678294291952625, + "exitCode": 0 + } + ], + "after": [ + { + "run": 0, + "warmup": true, + "seconds": 4.722068292088807, + "exitCode": 0 + }, + { + "run": 1, + "warmup": false, + "seconds": 4.354938208125532, + "exitCode": 0 + }, + { + "run": 2, + "warmup": false, + "seconds": 4.3520949999801815, + "exitCode": 0 + }, + { + "run": 3, + "warmup": false, + "seconds": 4.317166750086471, + "exitCode": 0 + } + ] + } +} diff --git a/tests/benchmarks/2026-09-25.md b/tests/benchmarks/2026-09-25.md new file mode 100644 index 00000000..8f62c856 --- /dev/null +++ b/tests/benchmarks/2026-09-25.md @@ -0,0 +1,103 @@ +# Test benchmark — 2026-09-25 + +Measured locally on Apple M3 Max, 16 logical CPUs, macOS 26.6.2 arm64, Node 24.5.0. +Base revision: `7c4e136e` plus the local eight-file mock migration. Runtime source was unchanged. +Browser 0.9.0 and Storage 0.8.0; Jest uses eight workers, fixture preparation/typechecking uses four processes. +The shared macOS arm64 runner enables `--no-sparkplug`. Browser launches and child CLI processes keep their normal flags. + +## Method + +Commands ran sequentially, outside the filesystem/network sandbox so HTTP fixtures could bind localhost. +One warm-up preceded three measured runs for each individual group. Caches stayed warm; fixture applications were rebuilt +where their commands require it. Full `npm test` and pre-push each ran once after the individual groups. +Wall time uses Python `time.perf_counter()` around the entire command, including startup, reporting and process exit. +Jest groups in the main series include `--json --outputFile=...`; their JSON/report overhead is included. Builds are excluded +from group-only Jest commands and included only in the build/full/pre-push rows. Coverage uses the existing Babel provider. + +The raw [results](2026-09-25.json) include every command, warm-up, exit code, measured wall time, Jest-reported time, +and sum of per-file elapsed time where available. Per-file elapsed sums are **not CPU time**. All reported runs passed. +An initial restricted unit warm-up failed with `listen EPERM`; it was excluded and the entire before-series rerun outside +that sandbox. No test failures were retried within the reported benchmark series. + +## Final measurements + +| Command/group | Measured runs | Median wall, s | Range, s | Suites / tests | +| ---------------------------------------- | ------------- | -------------- | ----------- | -------------- | +| Package build | 3 | 3.30 | 3.26–3.35 | — | +| Units with JSON report | 3 | 5.15 | 4.92–5.46 | 135 / 1676 | +| Units + Babel coverage + JSON report | 3 | 8.52 | 8.09–11.02 | 135 / 1676 | +| Build + types | 3 | 54.48 | 52.89–54.48 | 36 / 283 | +| Chrome | 3 | 8.65 | 8.63–8.82 | 17 / 20 | +| Firefox | 3 | 13.35 | 12.81–13.96 | 14 / 22 | +| Prepare + typecheck 24 fixtures | 3 | 8.92 | 8.82–9.47 | — | +| Full npm test, including package build | 1 | 66.16 | 66.16–66.16 | 202 / 2001 | +| Pre-push, including build and typechecks | 1 | 64.03 | 64.03–64.03 | — | + +The full run passed **202 suites / 2001 tests** across all six projects. Pre-push passed **171 suites / 1959 tests**, +framework typechecking and preparation/typechecking of **24 fixture applications**. These are local results, not proof +of the Linux/Windows × Node 22/24 CI matrix. + +## Cost of the last eight-file migration + +For a like-for-like check, the exact command `npm run test:unit` ran before and after these edits, without JSON reporting. +Both versions contain **135 suites / 1676 tests**. Each series has one warm-up and three measured runs: + +| State | Median wall, s | Range, s | +| --------------------------- | -------------- | --------- | +| Before local-mock migration | 4.58 | 4.45–4.68 | +| After local-mock migration | 4.35 | 4.32–4.35 | + +The observed change is -5.0%. Three runs do not establish a statistically significant speedup; they show +no observed slowdown for this migration. The separate JSON-report series was slower; do not mix those timings when +estimating the cost of the code changes or attribute the entire difference to the reporter. + +## Historical comparison + +The original user-provided measurements used a smaller test set and did not fully specify the command/reporting settings. +They are reference points, not a controlled A/B benchmark. The initial unit suite had 98 files / 1197 tests, while the final +suite has 135 files / 1676 tests. Build + types grew from 10 to 36 files; Chrome grew from 12 to 17 files. + +| Group | Original wall, s | Final wall, s | Interpretation | +| --------------------------- | ---------------- | ------------- | --------------------------------------------------------------- | +| Package build | 3.4 | 3.30 | Essentially unchanged | +| Units, ordinary npm command | 8.3 | 4.35 | About 1.9× faster, with ~40% more tests | +| Units + Babel coverage | 7.9 | 8.52 | No demonstrated speedup; final series includes JSON reporting | +| Build + types | 14.3 | 54.48 | Slower; expanded suite and current Relay declaration bottleneck | +| Chrome | 17.4 | 8.65 | About 2.0× faster with more files | +| Firefox | Not run | 13.35 | No initial local baseline | + +Do not compare the old CI fixture step (54–60 s) directly with the new local 8.92 s: hardware, application count and build +inclusion differ. The old ~5-minute CI job also cannot be compared directly with local full-suite time. No current CI +performance measurement was made here. The initial report gives no comparable whole-hook timing. Pre-commit now checks +staged formatting only; timing it with an empty index would not represent a real commit, so no pre-commit figure is claimed. + +## Remaining bottleneck + +The later [Relay type-checking follow-up](2026-09-25-relay-types.md) isolates the expensive constructor inference, +records its fix and splits consumer responsibilities. The measurements below describe the pre-fix state. + +In the three measured build/types runs, `RelayDeclaration.test.ts` took **52.26, 53.91 and 53.92 s**. The group finished +in **52.89–54.48 s**, so this file almost entirely determines its wall time. It constructs separate TypeScript programs +for source/package consumers and POSIX/Windows declaration paths. The next largest file, `override.integration.test.ts`, +took **21.95–22.82 s** and ran alongside it. More workers cannot shorten the longest file. + +This benchmark does not establish the cause of the difference from the historical 6.5 s Relay declaration figure. +A separate optimization should profile program construction/type checking and investigate safe reuse or splitting of those +four consumer checks while preserving both public surfaces and path variants. This migration did not change that test. + +## Reproduce + +From the repository root, run each individual command once to warm up and then three times, sequentially. Use an external +monotonic timer for wall time; append `--json --outputFile=` to Jest commands to reproduce the main series. + +```sh +npm run build +npm run test:unit +node scripts/test-run.mjs --selectProjects unit-node unit-dom --coverage --coverageProvider=babel +node scripts/test-run.mjs --selectProjects build types +node scripts/test-run.mjs --selectProjects chrome +node scripts/test-run.mjs --selectProjects firefox +node node_modules/tsx/dist/cli.mjs tests/integration/typecheck.ts --prepare +npm run test +npm run test:pre-push +``` From 773055542d0bd10786ae5be1f6d1a7f813ef3575 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:56:10 +0300 Subject: [PATCH 21/36] test(override): split build scenarios across page suites Preserve all 33 cases and isolated fixture builds while removing the single-file tail. Record repeated benchmarks at eight and two workers and full-suite validation. --- tests/README.md | 1 + tests/benchmarks/2026-09-25-override.json | 2561 +++++++++++++++++ tests/benchmarks/2026-09-25-override.md | 65 + tests/benchmarks/2026-09-25-relay-types.md | 3 + tests/integration/README.md | 8 +- .../override/bookmarks.integration.test.ts | 10 + .../override/competing.integration.test.ts | 60 + .../override/history.integration.test.ts | 10 + .../build/override/newtab.integration.test.ts | 10 + .../build/override/override-utils.ts | 64 + .../override/override.integration.test.ts | 122 - 11 files changed, 2790 insertions(+), 124 deletions(-) create mode 100644 tests/benchmarks/2026-09-25-override.json create mode 100644 tests/benchmarks/2026-09-25-override.md create mode 100644 tests/integration/build/override/bookmarks.integration.test.ts create mode 100644 tests/integration/build/override/competing.integration.test.ts create mode 100644 tests/integration/build/override/history.integration.test.ts create mode 100644 tests/integration/build/override/newtab.integration.test.ts create mode 100644 tests/integration/build/override/override-utils.ts delete mode 100644 tests/integration/build/override/override.integration.test.ts diff --git a/tests/README.md b/tests/README.md index db5b17f9..758798b3 100644 --- a/tests/README.md +++ b/tests/README.md @@ -44,6 +44,7 @@ one explicit build and invoke `test:run` (or its underlying wrapper) directly, a The [2026-09-25 benchmark](benchmarks/2026-09-25.md) records final migration timings, raw runs and comparison limits. The [Relay type-checking follow-up](benchmarks/2026-09-25-relay-types.md) records the subsequent constructor inference fix. +The [override scheduling follow-up](benchmarks/2026-09-25-override.md) compares the unchanged build matrix at eight and two workers. ## Groups and workers diff --git a/tests/benchmarks/2026-09-25-override.json b/tests/benchmarks/2026-09-25-override.json new file mode 100644 index 00000000..892e9c6f --- /dev/null +++ b/tests/benchmarks/2026-09-25-override.json @@ -0,0 +1,2561 @@ +{ + "baselineRef": "5fe410b9", + "environment": { + "node": "24.5.0", + "platform": "macOS arm64", + "logicalCpus": 16, + "jestFlag": "--no-sparkplug" + }, + "overrideScenarios": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "summary": { + "before-8": { + "median": 22.506157167023048, + "min": 22.25215541711077, + "max": 22.71305362507701 + }, + "before-2": { + "median": 52.397480624960735, + "min": 51.31795075000264, + "max": 53.63272133283317 + }, + "after-8": { + "median": 16.5209129168652, + "min": 16.205771875102073, + "max": 17.61266070790589 + }, + "after-2": { + "median": 53.16435362491757, + "min": 52.022176374914125, + "max": 54.29423633287661 + } + }, + "runs": [ + { + "phase": "before", + "workers": 8, + "warmup": true, + "run": 0, + "wallSeconds": 26.11322008399293, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/before-8-0.json" + ], + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 6.242 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 6.298 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 6.407 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 9.189 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 4.256 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 10.802 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 10.976 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 12.289 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.077 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 7.656 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.907 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 8.181 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.697 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.422 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 6.72 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.434 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.027 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 5.985 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.598 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.879 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.978 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.967 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.264 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.137 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.154 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.047 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.127 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.255 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.055 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.055 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.042 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.067 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.036 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.363 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.551 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.177 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 25.153 + } + ] + }, + { + "phase": "before", + "workers": 8, + "warmup": false, + "run": 1, + "wallSeconds": 22.71305362507701, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/before-8-1.json" + ], + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.729 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 6.236 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.322 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 7.026 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 7.16 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 8.03 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 9.55 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.415 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.935 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.82 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.782 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 4.072 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.863 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.387 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.058 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.669 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.551 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.537 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.779 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.318 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.871 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.262 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.645 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.138 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.148 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.612 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.043 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.061 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.053 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.323 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.038 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.032 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.054 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.423 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.164 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.512 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 21.935 + } + ] + }, + { + "phase": "before", + "workers": 8, + "warmup": false, + "run": 2, + "wallSeconds": 22.506157167023048, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/before-8-2.json" + ], + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.786 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 5.88 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.023 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.694 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.77 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.552 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 10.04 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 4.283 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 5.228 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 4.504 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 5.584 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 4.173 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 6.535 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.71 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.908 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.47 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.16 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.587 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.506 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.846 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.106 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.304 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.943 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.217 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.122 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.165 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.132 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.063 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.055 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.059 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.059 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.033 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.045 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.124 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.583 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.341 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 21.911 + } + ] + }, + { + "phase": "before", + "workers": 8, + "warmup": false, + "run": 3, + "wallSeconds": 22.25215541711077, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/before-8-3.json" + ], + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.848 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.264 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.434 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 7.244 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 7.365 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.99 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 9.841 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.521 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.688 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.76 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.684 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.393 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 6.086 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.827 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.448 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.02 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.762 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.524 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.586 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.863 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.915 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.296 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.125 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.114 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.07 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.134 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.055 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.578 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.163 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.044 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.047 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.245 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.352 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.04 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.031 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.173 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 21.669 + } + ] + }, + { + "phase": "before", + "workers": 2, + "warmup": false, + "run": 1, + "wallSeconds": 51.31795075000264, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=2", + "--json", + "--outputFile=/tmp/override-benchmark/before-2-1.json" + ], + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 8.631 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 6.774 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 19.463 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 5.91 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.247 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 5.221 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 5.448 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.07 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.321 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 4.841 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.16 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.043 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.0 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 2.726 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 2.65 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.518 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.433 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 1.846 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.279 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.524 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.036 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 0.979 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.728 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.706 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.326 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.519 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.181 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.102 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.271 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.093 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.108 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.049 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.035 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.054 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.036 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.043 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.024 + } + ] + }, + { + "phase": "after", + "workers": 8, + "warmup": true, + "run": 0, + "wallSeconds": 15.487127125030383, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/after-8-0.json" + ], + "suites": 40, + "tests": 285, + "files": [ + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 5.949 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.625 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.625 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.694 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.725 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.81 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.415 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 9.211 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.625 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.397 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.561 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.805 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.036 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.465 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.437 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 6.098 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.245 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.195 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.942 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.215 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.193 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.663 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.733 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.313 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.626 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 1.075 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.235 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.477 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.16 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.068 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.876 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.063 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.037 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.121 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.043 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.033 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.205 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.073 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.445 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.465 + } + ] + }, + { + "phase": "after", + "workers": 8, + "warmup": false, + "run": 1, + "wallSeconds": 16.5209129168652, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/after-8-1.json" + ], + "suites": 40, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.803 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.64 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.643 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.706 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.732 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.759 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.501 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 9.45 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.706 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.363 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.623 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.939 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.269 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.623 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.497 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 4.115 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.96 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.861 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 2.429 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.928 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.924 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.763 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 2.016 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.737 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.657 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.473 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.973 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.186 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.316 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.06 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.436 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.066 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.076 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.893 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.03 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.05 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.132 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.049 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.174 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.447 + } + ] + }, + { + "phase": "after", + "workers": 8, + "warmup": false, + "run": 2, + "wallSeconds": 16.205771875102073, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/after-8-2.json" + ], + "suites": 40, + "tests": 285, + "files": [ + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.07 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.534 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.534 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.653 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.661 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.729 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.473 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 9.557 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 2.737 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.564 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.759 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 5.066 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 6.048 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.791 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 3.706 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 6.777 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.596 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.959 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.552 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.851 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.215 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.531 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.678 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.672 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.925 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.604 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.128 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 1.154 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.066 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.511 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.051 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.371 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.644 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.045 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.164 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.03 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.037 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.712 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.055 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.798 + } + ] + }, + { + "phase": "after", + "workers": 8, + "warmup": false, + "run": 3, + "wallSeconds": 17.61266070790589, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=8", + "--json", + "--outputFile=/tmp/override-benchmark/after-8-3.json" + ], + "suites": 40, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 7.642 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 7.786 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 7.826 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 8.734 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 8.835 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 8.921 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 9.229 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 10.635 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.994 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.651 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.749 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.876 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.914 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.513 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.634 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 7.205 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 3.113 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 2.106 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.252 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.938 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.684 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.697 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.256 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.752 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.531 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.923 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.361 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.153 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.192 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.086 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 3.259 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.265 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.068 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.06 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.061 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.045 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.631 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.033 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.913 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.605 + } + ] + }, + { + "phase": "after", + "workers": 2, + "warmup": false, + "run": 1, + "wallSeconds": 54.29423633287661, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=2", + "--json", + "--outputFile=/tmp/override-benchmark/after-2-1.json" + ], + "suites": 40, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.551 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 8.765 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.062 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 5.986 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 5.677 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.138 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 5.484 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.356 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.314 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 5.912 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.128 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.372 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.094 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.361 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.514 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 3.269 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 2.483 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 2.901 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.703 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.893 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.565 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.592 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.198 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.179 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.564 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.33 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.77 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.544 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.332 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.107 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.258 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.123 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.203 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.052 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.038 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.128 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.037 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.039 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.03 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.026 + } + ] + }, + { + "phase": "before", + "workers": 2, + "warmup": false, + "run": 2, + "wallSeconds": 52.397480624960735, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=2", + "--json", + "--outputFile=/tmp/override-benchmark/before-2-2.json" + ], + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 8.74 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 6.877 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 19.988 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.112 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.292 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 5.444 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 5.278 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.192 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 4.961 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.244 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.076 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.259 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 2.852 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 2.987 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 2.658 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.557 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 2.007 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.547 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.454 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.278 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.026 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.076 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.751 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.567 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.532 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.386 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.378 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.217 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.118 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.151 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.065 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.109 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.06 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.037 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.031 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.058 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.029 + } + ] + }, + { + "phase": "after", + "workers": 2, + "warmup": false, + "run": 2, + "wallSeconds": 52.022176374914125, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=2", + "--json", + "--outputFile=/tmp/override-benchmark/after-2-2.json" + ], + "suites": 40, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.067 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 8.787 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 5.905 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.101 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.329 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.011 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 5.964 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 5.275 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 5.314 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.38 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 3.869 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 4.924 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.321 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.182 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 2.981 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 2.796 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 2.68 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.514 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 2.064 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.87 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.577 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.374 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 0.931 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.305 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 0.896 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.732 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.627 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.516 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.232 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.339 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.102 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.113 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.276 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.05 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.04 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.111 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.037 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.042 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.026 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.033 + } + ] + }, + { + "phase": "before", + "workers": 2, + "warmup": false, + "run": 3, + "wallSeconds": 53.63272133283317, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=2", + "--json", + "--outputFile=/tmp/override-benchmark/before-2-3.json" + ], + "suites": 37, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 8.849 + }, + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 6.912 + }, + { + "path": "tests/integration/build/override/override.integration.test.ts", + "seconds": 19.621 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.272 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 6.481 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.492 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 6.03 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 6.002 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 4.959 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.269 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.162 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.318 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.098 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 2.967 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 2.739 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.597 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.574 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 2.006 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.404 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.308 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.016 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 1.042 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.743 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.753 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.536 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.434 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.227 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.118 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.259 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.12 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.116 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.068 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.044 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.04 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.06 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.029 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.038 + } + ] + }, + { + "phase": "after", + "workers": 2, + "warmup": false, + "run": 3, + "wallSeconds": 53.16435362491757, + "exitCode": 0, + "command": [ + "node", + "scripts/test-run.mjs", + "--selectProjects", + "build", + "types", + "--maxWorkers=2", + "--json", + "--outputFile=/tmp/override-benchmark/after-2-3.json" + ], + "suites": 40, + "tests": 285, + "files": [ + { + "path": "tests/integration/build/locale/dynamic.integration.test.ts", + "seconds": 7.136 + }, + { + "path": "tests/integration/build/isolation/isolation-watch.integration.test.ts", + "seconds": 8.768 + }, + { + "path": "tests/integration/build/options/options.integration.test.ts", + "seconds": 5.947 + }, + { + "path": "tests/integration/build/locale/locale.integration.test.ts", + "seconds": 6.295 + }, + { + "path": "tests/integration/build/locale/chunks.integration.test.ts", + "seconds": 5.284 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.212 + }, + { + "path": "tests/integration/types/registries.integration.test.ts", + "seconds": 5.288 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 5.885 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 5.82 + }, + { + "path": "tests/integration/build/locale/chunks-threshold.integration.test.ts", + "seconds": 5.053 + }, + { + "path": "tests/integration/build/relay-runtime/relay-runtime.integration.test.ts", + "seconds": 5.027 + }, + { + "path": "tests/integration/types/relay.integration.test.ts", + "seconds": 4.478 + }, + { + "path": "tests/integration/types/content.integration.test.ts", + "seconds": 4.526 + }, + { + "path": "src/cli/plugins/content/RelayDeclaration.test.ts", + "seconds": 3.258 + }, + { + "path": "src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", + "seconds": 3.162 + }, + { + "path": "tests/integration/types/view.integration.test.ts", + "seconds": 2.985 + }, + { + "path": "src/cli/index.test.ts", + "seconds": 2.762 + }, + { + "path": "tests/integration/build/permissions/permissions.integration.test.ts", + "seconds": 2.661 + }, + { + "path": "src/cli/bundler/plugins/build-assets-map/BuildAssetsMapPlugin.test.ts", + "seconds": 1.913 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.921 + }, + { + "path": "tests/integration/types/locale.integration.test.ts", + "seconds": 1.725 + }, + { + "path": "tests/integration/build/style-routing/delivery-spike.integration.test.ts", + "seconds": 1.539 + }, + { + "path": "tests/integration/build/virtual-modules/virtual-modules.integration.test.ts", + "seconds": 1.377 + }, + { + "path": "src/cli/bundler/plugins/isolated-styles/IsolatedStylesPlugin.test.ts", + "seconds": 0.952 + }, + { + "path": "tests/integration/build/style-routing/selective-split.integration.test.ts", + "seconds": 0.692 + }, + { + "path": "tests/integration/build/style-routing/style-routing.integration.test.ts", + "seconds": 1.237 + }, + { + "path": "tests/integration/build/locale/chunks-disabled.integration.test.ts", + "seconds": 0.844 + }, + { + "path": "src/cli/virtual/virtual.test.ts", + "seconds": 0.604 + }, + { + "path": "tests/integration/build/style-routing/routing-spike.integration.test.ts", + "seconds": 0.416 + }, + { + "path": "src/cli/bundler/plugins/generate-module/GenerateModulePlugin.test.ts", + "seconds": 0.267 + }, + { + "path": "tests/integration/build/style-routing/content-chunks.integration.test.ts", + "seconds": 0.21 + }, + { + "path": "src/cli/bundler/plugins/manifest/ManifestPlugin.test.ts", + "seconds": 0.121 + }, + { + "path": "src/cli/bundler/plugins/resource-access/ResourceAccessPlugin.test.ts", + "seconds": 0.109 + }, + { + "path": "tests/integration/build/content/content-api.integration.test.ts", + "seconds": 0.049 + }, + { + "path": "src/cli/bundler/plugins/generate-json/GenerateJsonPlugin.test.ts", + "seconds": 0.109 + }, + { + "path": "src/cli/bundler/plugins/entrypoint/EntrypointPlugin.test.ts", + "seconds": 0.041 + }, + { + "path": "src/cli/bundler/plugins/chunk-loader/ChunkLoaderPlugin.test.ts", + "seconds": 0.044 + }, + { + "path": "src/cli/bundler/plugins/utils/runtime-template.test.ts", + "seconds": 0.026 + }, + { + "path": "tests/build-output.test.ts", + "seconds": 0.036 + }, + { + "path": "src/cli/bundler/plugins/resource-access/utils.test.ts", + "seconds": 0.033 + } + ] + } + ], + "validation": [ + { + "name": "check", + "command": [ + "npm", + "run", + "check" + ], + "wallSeconds": 31.93171454197727, + "exitCode": 0 + }, + { + "name": "full", + "command": [ + "npm", + "test", + "--", + "--json", + "--outputFile=/tmp/override-benchmark/full-after.json" + ], + "wallSeconds": 58.474541374947876, + "exitCode": 0 + } + ], + "fullResult": { + "success": true, + "suites": 206, + "tests": 2003 + } +} diff --git a/tests/benchmarks/2026-09-25-override.md b/tests/benchmarks/2026-09-25-override.md new file mode 100644 index 00000000..18a30358 --- /dev/null +++ b/tests/benchmarks/2026-09-25-override.md @@ -0,0 +1,65 @@ +# Override scheduling follow-up — 2026-09-25 + +This follows the [Relay type-checking benchmark](2026-09-25-relay-types.md). The baseline is `5fe410b9`. +Only tests and documentation changed; runtime source, fixtures, worker limits and timeouts are unchanged. + +## Change and preserved coverage + +The single override file became New Tab, Bookmarks, History and competing-entrypoint files. A shared helper +registers the page support checks. Every scenario still creates and disposes its own fixture and launches its own CLI build. +There is no shared build cache, shared mutable application or `test.concurrent`. + +All **33 unique full test names** match the baseline, and AST comparison found the same **17 assertion statements**. +The matrix remains three pages × five browsers × two manifest versions, plus three competing-entrypoint cases. +The isolated override run passed four files / 33 cases in 6.33 seconds of Jest-reported time. + +## Measurements + +Apple M3 Max, 16 logical CPUs, macOS arm64, Node 24.5.0. The existing macOS `--no-sparkplug` workaround remains enabled. +Commands ran sequentially outside the sandbox. Package build time is excluded from group measurements; the package was built +once before the series and production code was not edited afterward. Whole-command time uses Python `time.perf_counter()`. +All group commands include Jest JSON reporting and an explicit worker count. + +At eight workers each state has one warm-up followed by three measured runs. At two workers the first before/after pair +followed those series; two additional alternating before/after pairs checked the initial difference. For these repeats, +a script temporarily restored only the original override test or the four new test files, verifying their contents and +restoring the final split in `finally`. Other sources and dependencies stayed unchanged. + +| Workers | Before: median (range) | After: median (range) | +| ------- | ---------------------- | --------------------- | +| 8 | 22.51 s (22.25–22.71) | 16.52 s (16.21–17.61) | +| 2 | 52.40 s (51.32–53.63) | 53.16 s (52.02–54.29) | + +At eight workers the group is **1.36× faster**. +At two workers the ranges overlap; no reliable speedup is established. The build count remains 33, so splitting removes +the serial tail when workers are available but does not reduce the compilation work. Two workers match the current CI +setting; these local macOS runs are not measurements of the Linux/Windows CI matrix or its coverage-enabled job. + +Build + types passes **40 suites / 285 tests**, versus 37 suites / 285 tests before the split. +The next longest file in the eight-worker runs is the isolation watch integration, around ten seconds; the group also +includes other jobs and scheduling overhead, so its total does not equal that file's duration. + +## Final validation + +- `typecheck`, test inventory and formatting passed; inventory has 206 files, each in exactly one project, and zero local module mocks. +- `npm run check`: 175 suites / 1961 tests plus framework and 24 fixture typechecks, 31.93 seconds for the whole command. +- Full `npm test`: 206 suites / 2003 tests, including Chrome and Firefox, 58.47 seconds for the whole command. + +The full/check timings are single observations, not new before/after series. The preceding Relay report recorded +52.32 seconds for the full command, versus 58.47 seconds here, so these results do **not** establish a full-suite speedup. +The unchanged Chrome override browser file took 16.46 seconds here versus 10.59 seconds in that earlier full run; +this observation does not establish the cause of the difference. + +## Reproduce + +[Raw results](2026-09-25-override.json) include all runs, per-file durations, scenario names, worker counts and exit codes. +At each revision, build once, warm up the selected group, then measure it three times. Use unique JSON output filenames. + +```sh +npm run build +npm run test:run -- --selectProjects build types --maxWorkers=8 --json --outputFile=/tmp/override-8.json +npm run test:run -- --selectProjects build types --maxWorkers=2 --json --outputFile=/tmp/override-2.json +npm run test:run -- --selectProjects build --testPathPatterns=tests/integration/build/override +npm run check +npm test +``` diff --git a/tests/benchmarks/2026-09-25-relay-types.md b/tests/benchmarks/2026-09-25-relay-types.md index 8943bcec..cf232cb8 100644 --- a/tests/benchmarks/2026-09-25-relay-types.md +++ b/tests/benchmarks/2026-09-25-relay-types.md @@ -4,6 +4,9 @@ This follows the [migration benchmark](2026-09-25.md). Measurements use the same macOS arm64, Node 24.5.0 and TypeScript 5.9.3. Jest retains eight workers and `--no-sparkplug`. All reported test runs passed. These are local measurements, not Linux/Windows or Node 22 CI results. +The subsequent [override scheduling follow-up](2026-09-25-override.md) records the split of the remaining long build file. +The measurements here describe the state before that split. + ## Diagnosis and change An independent compiler-host probe confirmed that narrowing `ContentScriptContainerTag` to `"div"` in memory reduces diff --git a/tests/integration/README.md b/tests/integration/README.md index 7494f82b..7ad4a876 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -23,7 +23,11 @@ tests/integration/ │ │ ├── options.integration.test.ts │ │ └── embedded/ │ ├── override/ -│ │ ├── override.integration.test.ts +│ │ ├── newtab.integration.test.ts +│ │ ├── bookmarks.integration.test.ts +│ │ ├── history.integration.test.ts +│ │ ├── competing.integration.test.ts +│ │ ├── override-utils.ts │ │ ├── newtab/ │ │ ├── bookmarks/ │ │ └── history/ @@ -141,7 +145,7 @@ Tests copy application inputs to unique directories under `.cache/integration`. - `build/options/embedded`: ten manifest checks covering explicit `openInTab: false` across Chrome, Edge, Opera, Safari, and Firefox in MV2 and MV3. No browser is launched. - `browser/view`: one Chrome MV3 case covering React offscreen and sandbox views rendered by the injected builder with their props and titles, a headless offscreen without a view container, and strings rendered as text by the React view, Vanilla view and React content adapters. - `browser/options`: two Chrome MV3 cases covering Vanilla and React rendering, CSS, state/events, opening Options from background, and a View chunk shared with a Page. -- `build/override`: thirty manifest checks covering the New Tab, Bookmarks, and History applications across Chrome, Edge, Opera, Safari, and Firefox in MV2 and MV3. Supporting browsers receive `chrome_url_overrides`, the page HTML, its CSP, and the permission declared by the entrypoint; the others receive none of them. Three more cases add the History entrypoint to a copy of the New Tab application: Chrome and Edge builds fail naming both entrypoints, while Firefox keeps the New Tab with its own permission and without the History one. No browser is launched. +- `build/override`: separate New Tab, Bookmarks, History and competing-entrypoint files let Jest schedule the scenarios across workers. The files share assertions, while each case keeps its own application copy and build. Thirty manifest checks cover the New Tab, Bookmarks, and History applications across Chrome, Edge, Opera, Safari, and Firefox in MV2 and MV3. Supporting browsers receive `chrome_url_overrides`, the page HTML, its CSP, and the permission declared by the entrypoint; the others receive none of them. Three more cases add the History entrypoint to a copy of the New Tab application: Chrome and Edge builds fail naming both entrypoints, while Firefox keeps the New Tab with its own permission and without the History one. No browser is launched. - `build/permissions/views`: four manifest checks of one application with two popups, two sidebars, and an options page that each declare permissions. Every built view contributes, including a popup and a sidebar that are not applied by default; Chrome MV2 has no sidebar, so its permissions are not requested; Firefox builds the sidebar action without `sidePanel`; MV2 declares hosts as permissions. No browser is launched. - `browser/override`: three Chrome MV3 cases opening `chrome://newtab`, `chrome://bookmarks`, and `chrome://history`. They verify that Chrome serves the extension page, React and Vanilla rendering, CSS, state/events, custom `as` and `htmlDir` naming, and a View chunk shared with a Page. - `browser/offscreen/service`: one Chrome MV3 round trip from background through Offscreen to a registered background service. diff --git a/tests/integration/build/override/bookmarks.integration.test.ts b/tests/integration/build/override/bookmarks.integration.test.ts new file mode 100644 index 00000000..091c0ebd --- /dev/null +++ b/tests/integration/build/override/bookmarks.integration.test.ts @@ -0,0 +1,10 @@ +import {testOverridePage} from "./override-utils"; + +jest.setTimeout(90_000); + +testOverridePage({ + page: "bookmarks", + permission: "bookmarks", + supported: ["chrome", "edge"], + unsupported: ["firefox", "safari", "opera"], +}); diff --git a/tests/integration/build/override/competing.integration.test.ts b/tests/integration/build/override/competing.integration.test.ts new file mode 100644 index 00000000..0dfe0333 --- /dev/null +++ b/tests/integration/build/override/competing.integration.test.ts @@ -0,0 +1,60 @@ +import {readdir, readFile, writeFile} from "fs/promises"; +import path from "path"; + +import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; +import {readOverrideManifest} from "./override-utils"; + +const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); + +jest.setTimeout(90_000); + +/** Adds the History entrypoint to a copy of the New Tab application, so two overrides compete. */ +const createCompetingFixture = async (): Promise => { + const fixture = await createIntegrationFixture(rootDir, path.join(__dirname, "newtab")); + + try { + await writeFile( + path.join(fixture.directory, "src", "history.ts"), + await readFile(path.join(__dirname, "history", "src", "history.ts")) + ); + } catch (error) { + await fixture.dispose(); + + throw error; + } + + return fixture; +}; + +describe("competing overrides", () => { + test.each(["chrome", "edge"])("%s build fails and names both entrypoints", async browser => { + const fixture = await createCompetingFixture(); + + try { + const build = fixture.build({browser}); + + await expect(build).rejects.toThrow( + `An extension can override only one browser page, but app "myapp" enables 2 override entrypoints for ${browser}:` + ); + await expect(build).rejects.toThrow(" - newtab: src/newtab.ts"); + await expect(build).rejects.toThrow(" - history: src/history.ts"); + } finally { + await fixture.dispose(); + } + }); + + test("firefox build keeps the new tab and its permission without the unsupported history page", async () => { + const fixture = await createCompetingFixture(); + + try { + const extensionDir = await fixture.build({browser: "firefox"}); + const manifest = await readOverrideManifest(extensionDir); + + expect(manifest.chrome_url_overrides).toEqual({newtab: "newtab.html"}); + expect(manifest.permissions).toEqual(["search"]); + expect(await readdir(extensionDir)).not.toContain("history.html"); + } finally { + await fixture.dispose(); + } + }); +}); diff --git a/tests/integration/build/override/history.integration.test.ts b/tests/integration/build/override/history.integration.test.ts new file mode 100644 index 00000000..5497f82f --- /dev/null +++ b/tests/integration/build/override/history.integration.test.ts @@ -0,0 +1,10 @@ +import {testOverridePage} from "./override-utils"; + +jest.setTimeout(90_000); + +testOverridePage({ + page: "history", + permission: "history", + supported: ["chrome", "edge"], + unsupported: ["firefox", "safari", "opera"], +}); diff --git a/tests/integration/build/override/newtab.integration.test.ts b/tests/integration/build/override/newtab.integration.test.ts new file mode 100644 index 00000000..386c9e14 --- /dev/null +++ b/tests/integration/build/override/newtab.integration.test.ts @@ -0,0 +1,10 @@ +import {testOverridePage} from "./override-utils"; + +jest.setTimeout(90_000); + +testOverridePage({ + page: "newtab", + permission: "search", + supported: ["chrome", "edge", "firefox", "safari"], + unsupported: ["opera"], +}); diff --git a/tests/integration/build/override/override-utils.ts b/tests/integration/build/override/override-utils.ts new file mode 100644 index 00000000..4d698487 --- /dev/null +++ b/tests/integration/build/override/override-utils.ts @@ -0,0 +1,64 @@ +import {readdir, readFile} from "fs/promises"; +import path from "path"; + +import {createIntegrationFixture} from "../../utils/fixture"; + +interface OverridePageScenario { + page: string; + permission: string; + supported: readonly string[]; + unsupported: readonly string[]; +} + +const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); + +export const readOverrideManifest = async (extensionDir: string) => { + return JSON.parse(await readFile(path.join(extensionDir, "manifest.json"), "utf8")); +}; + +export const testOverridePage = ({page, permission, supported, unsupported}: OverridePageScenario): void => { + describe(`${page} override`, () => { + const fixtureDir = path.join(__dirname, page); + const source = `https://${page}.example.com`; + + describe.each(supported)("%s", browser => { + test.each([2, 3] as const)("MV%s build replaces the browser page", async manifestVersion => { + const fixture = await createIntegrationFixture(rootDir, fixtureDir); + + try { + const extensionDir = await fixture.build({browser, manifestVersion}); + const manifest = await readOverrideManifest(extensionDir); + const html = await readFile(path.join(extensionDir, `${page}.html`), "utf8"); + + expect(manifest.manifest_version).toBe(manifestVersion); + expect(manifest.chrome_url_overrides).toEqual({[page]: `${page}.html`}); + expect(JSON.stringify(manifest.content_security_policy)).toContain(source); + expect(manifest.permissions).toContain(permission); + expect(html).toContain("Custom"); + expect(html).toContain("<script"); + } finally { + await fixture.dispose(); + } + }); + }); + + describe.each(unsupported)("%s", browser => { + test.each([2, 3] as const)("MV%s build skips the unsupported page", async manifestVersion => { + const fixture = await createIntegrationFixture(rootDir, fixtureDir); + + try { + const extensionDir = await fixture.build({browser, manifestVersion}); + const manifest = await readOverrideManifest(extensionDir); + + expect(manifest.manifest_version).toBe(manifestVersion); + expect(manifest).not.toHaveProperty("chrome_url_overrides"); + expect(JSON.stringify(manifest)).not.toContain(source); + expect(manifest.permissions ?? []).not.toContain(permission); + expect(await readdir(extensionDir)).not.toContain(`${page}.html`); + } finally { + await fixture.dispose(); + } + }); + }); + }); +}; diff --git a/tests/integration/build/override/override.integration.test.ts b/tests/integration/build/override/override.integration.test.ts deleted file mode 100644 index 200e8171..00000000 --- a/tests/integration/build/override/override.integration.test.ts +++ /dev/null @@ -1,122 +0,0 @@ -import {readdir, readFile, writeFile} from "fs/promises"; -import path from "path"; - -import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; - -const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); - -jest.setTimeout(90_000); - -const readManifest = async (extensionDir: string) => { - return JSON.parse(await readFile(path.join(extensionDir, "manifest.json"), "utf8")); -}; - -/** Adds the History entrypoint to a copy of the New Tab application, so two overrides compete. */ -const createCompetingFixture = async (): Promise<IntegrationFixture> => { - const fixture = await createIntegrationFixture(rootDir, path.join(__dirname, "newtab")); - - try { - await writeFile( - path.join(fixture.directory, "src", "history.ts"), - await readFile(path.join(__dirname, "history", "src", "history.ts")) - ); - } catch (error) { - await fixture.dispose(); - - throw error; - } - - return fixture; -}; - -describe.each([ - {page: "newtab", permission: "search", supported: ["chrome", "edge", "firefox", "safari"], unsupported: ["opera"]}, - { - page: "bookmarks", - permission: "bookmarks", - supported: ["chrome", "edge"], - unsupported: ["firefox", "safari", "opera"], - }, - { - page: "history", - permission: "history", - supported: ["chrome", "edge"], - unsupported: ["firefox", "safari", "opera"], - }, -])("$page override", ({page, permission, supported, unsupported}) => { - const fixtureDir = path.join(__dirname, page); - const source = `https://${page}.example.com`; - - describe.each(supported)("%s", browser => { - test.each([2, 3] as const)("MV%s build replaces the browser page", async manifestVersion => { - const fixture = await createIntegrationFixture(rootDir, fixtureDir); - - try { - const extensionDir = await fixture.build({browser, manifestVersion}); - const manifest = await readManifest(extensionDir); - const html = await readFile(path.join(extensionDir, `${page}.html`), "utf8"); - - expect(manifest.manifest_version).toBe(manifestVersion); - expect(manifest.chrome_url_overrides).toEqual({[page]: `${page}.html`}); - expect(JSON.stringify(manifest.content_security_policy)).toContain(source); - expect(manifest.permissions).toContain(permission); - expect(html).toContain("<title>Custom"); - expect(html).toContain("<script"); - } finally { - await fixture.dispose(); - } - }); - }); - - describe.each(unsupported)("%s", browser => { - test.each([2, 3] as const)("MV%s build skips the unsupported page", async manifestVersion => { - const fixture = await createIntegrationFixture(rootDir, fixtureDir); - - try { - const extensionDir = await fixture.build({browser, manifestVersion}); - const manifest = await readManifest(extensionDir); - - expect(manifest.manifest_version).toBe(manifestVersion); - expect(manifest).not.toHaveProperty("chrome_url_overrides"); - expect(JSON.stringify(manifest)).not.toContain(source); - expect(manifest.permissions ?? []).not.toContain(permission); - expect(await readdir(extensionDir)).not.toContain(`${page}.html`); - } finally { - await fixture.dispose(); - } - }); - }); -}); - -describe("competing overrides", () => { - test.each(["chrome", "edge"])("%s build fails and names both entrypoints", async browser => { - const fixture = await createCompetingFixture(); - - try { - const build = fixture.build({browser}); - - await expect(build).rejects.toThrow( - `An extension can override only one browser page, but app "myapp" enables 2 override entrypoints for ${browser}:` - ); - await expect(build).rejects.toThrow(" - newtab: src/newtab.ts"); - await expect(build).rejects.toThrow(" - history: src/history.ts"); - } finally { - await fixture.dispose(); - } - }); - - test("firefox build keeps the new tab and its permission without the unsupported history page", async () => { - const fixture = await createCompetingFixture(); - - try { - const extensionDir = await fixture.build({browser: "firefox"}); - const manifest = await readManifest(extensionDir); - - expect(manifest.chrome_url_overrides).toEqual({newtab: "newtab.html"}); - expect(manifest.permissions).toEqual(["search"]); - expect(await readdir(extensionDir)).not.toContain("history.html"); - } finally { - await fixture.dispose(); - } - }); -}); From 3b52058aed6a10235240f0875f485650b2115bad Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:08:27 +0300 Subject: [PATCH 22/36] test(browser): centralize startup deadlines and timing reports Route browser integrations through a shared session with one startup deadline and cleanup. Collect per-start timings and failures, and upload reports from browser CI jobs. Validate deadline handling, report aggregation, and two full runs of 208 suites and 2008 tests. --- .github/workflows/ci.yml | 9 + jest.config.ts | 1 + tests/BrowserStartupReporter.cjs | 66 ++++++ tests/BrowserStartupReporter.test.ts | 63 ++++++ tests/integration/README.md | 23 ++ ...rypoint-assets.firefox.integration.test.ts | 76 +++---- .../entrypoint-assets.integration.test.ts | 61 ++--- .../browser/content/isolated-styles-utils.ts | 117 +--------- .../locale/chunks.firefox.integration.test.ts | 36 ++- .../browser/locale/chunks.integration.test.ts | 47 +--- .../dynamic.firefox.integration.test.ts | 50 ++-- .../locale/dynamic.integration.test.ts | 47 +--- .../offscreen/service.integration.test.ts | 54 +---- .../options/options.integration.test.ts | 61 ++--- tests/integration/browser/utils/chrome.ts | 5 +- tests/integration/browser/utils/session.ts | 214 +++++++++++++++--- .../integration/browser/utils/startup.test.ts | 89 ++++++++ tests/integration/browser/utils/startup.ts | 46 ++++ .../browser/view/render.integration.test.ts | 54 +---- 19 files changed, 627 insertions(+), 492 deletions(-) create mode 100644 tests/BrowserStartupReporter.cjs create mode 100644 tests/BrowserStartupReporter.test.ts create mode 100644 tests/integration/browser/utils/startup.test.ts create mode 100644 tests/integration/browser/utils/startup.ts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8f2911c1..ecc7fdca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -168,6 +168,15 @@ jobs: env: ADNBN_CHROME_BIN: ${{ steps.chrome.outputs.chrome-path }} ADNBN_FIREFOX_BIN: ${{ steps.firefox.outputs.firefox-path }} + - name: Upload browser startup timings + if: always() + uses: actions/upload-artifact@v4 + with: + name: browser-startup-${{ matrix.browser }}-node-${{ matrix.node }} + path: .cache/integration/browser-startup.json + include-hidden-files: true + if-no-files-found: ignore + retention-days: 7 coverage: name: Coverage (ubuntu-latest, Node 22) diff --git a/jest.config.ts b/jest.config.ts index 379ed5d2..ffc86026 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -71,6 +71,7 @@ const exclude = (patterns: string[]) => const config: Config = { verbose: false, + reporters: ["default", "<rootDir>/tests/BrowserStartupReporter.cjs"], maxWorkers: Math.max(1, Math.min(8, availableParallelism() - 1)), coverageProvider: "babel", projects: [ diff --git a/tests/BrowserStartupReporter.cjs b/tests/BrowserStartupReporter.cjs new file mode 100644 index 00000000..57cf03f9 --- /dev/null +++ b/tests/BrowserStartupReporter.cjs @@ -0,0 +1,66 @@ +const {mkdir, writeFile} = require("node:fs/promises"); +const path = require("node:path"); + +/** Collect structured session diagnostics from Jest workers, including failed test files. */ +class BrowserStartupReporter { + constructor(globalConfig, options = {}) { + this.outputFile = path.resolve( + globalConfig.rootDir, + options.outputFile ?? process.env.ADNBN_BROWSER_STARTUP_REPORT ?? ".cache/integration/browser-startup.json" + ); + this.samples = []; + } + + onTestResult(test, result) { + const prefix = "[browser-startup] "; + + for (const entry of result.console ?? []) { + if (entry.message.startsWith(prefix)) { + this.samples.push({testFile: test.path, ...JSON.parse(entry.message.slice(prefix.length))}); + } + } + } + + async onRunComplete() { + const summary = {}; + + for (const browser of ["chrome", "firefox"]) { + const samples = this.samples.filter(sample => sample.browser === browser); + const durations = samples.filter(sample => sample.status === "ready").map(sample => sample.durationMs); + + durations.sort((a, b) => a - b); + + const middle = Math.floor(durations.length / 2); + summary[browser] = { + attempts: samples.length, + failures: samples.filter(sample => sample.status === "failed").length, + medianMs: durations.length + ? durations.length % 2 + ? durations[middle] + : (durations[middle - 1] + durations[middle]) / 2 + : null, + p95Ms: durations[Math.ceil(durations.length * 0.95) - 1] ?? null, + maxMs: durations.at(-1) ?? null, + }; + } + + await mkdir(path.dirname(this.outputFile), {recursive: true}); + await writeFile( + this.outputFile, + JSON.stringify( + { + generatedAt: new Date().toISOString(), + node: process.version, + platform: process.platform, + arch: process.arch, + summary, + samples: this.samples, + }, + null, + 2 + ) + "\n" + ); + } +} + +module.exports = BrowserStartupReporter; diff --git a/tests/BrowserStartupReporter.test.ts b/tests/BrowserStartupReporter.test.ts new file mode 100644 index 00000000..0def5269 --- /dev/null +++ b/tests/BrowserStartupReporter.test.ts @@ -0,0 +1,63 @@ +import {mkdtemp, readFile, rm} from "fs/promises"; +import os from "os"; +import path from "path"; + +import BrowserStartupReporter from "./BrowserStartupReporter.cjs"; + +test("the startup report retains successes and failures across files and excludes failures from duration percentiles", async () => { + const rootDir = await mkdtemp(path.join(os.tmpdir(), "adnbn-startup-report-")); + const outputFile = path.join(rootDir, "report.json"); + + try { + const reporter = new BrowserStartupReporter({rootDir}, {outputFile}); + + for (const [index, durationMs] of [100, 300, 200, 400].entries()) { + reporter.onTestResult( + {path: path.join(rootDir, `${index}.test.ts`)}, + { + console: [ + { + message: + "[browser-startup] " + + JSON.stringify({ + browser: "chrome", + testName: "starts", + status: "ready", + durationMs, + timeoutMs: 30_000, + }), + }, + ], + } + ); + } + + reporter.onTestResult( + {path: path.join(rootDir, "failed.test.ts")}, + { + console: [ + { + message: + "[browser-startup] " + + JSON.stringify({ + browser: "firefox", + status: "failed", + durationMs: 30_000, + error: "startup deadline", + }), + }, + ], + } + ); + await reporter.onRunComplete(); + const report = JSON.parse(await readFile(outputFile, "utf8")); + + expect(report.samples).toHaveLength(5); + expect(new Set(report.samples.map(sample => sample.testFile)).size).toBe(5); + expect(report.summary.chrome).toEqual({attempts: 4, failures: 0, medianMs: 250, p95Ms: 400, maxMs: 400}); + expect(report.summary.firefox).toEqual({attempts: 1, failures: 1, medianMs: null, p95Ms: null, maxMs: null}); + expect(report.samples[4].error).toBe("startup deadline"); + } finally { + await rm(rootDir, {recursive: true, force: true}); + } +}); diff --git a/tests/integration/README.md b/tests/integration/README.md index 7ad4a876..96f4e140 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -137,6 +137,29 @@ These groups run test files in parallel, with up to eight workers by default (on Tests copy application inputs to unique directories under `.cache/integration`. Prepared dependencies and generated files are excluded from the copy and recreated there. Cleanup removes only the run's copy and temporary Chrome profile; it does not remove the editor environment in the source fixture or change the `addon` playground. +### Browser startup + +Every browser integration uses `startBrowserSession` in `browser/utils/session.ts`. It owns the browser process, +protocol connection and profile cleanup, including failed startup. The default `startupTimeout` is 30 seconds: +one budget from spawning the process through Chrome's `Browser.getVersion` or Firefox's `session.new` response. +The Chrome HTTP probe is limited to the smaller of five seconds and the remaining budget; protocol connection +attempts and readiness requests also use the remaining budget. Extension installation and page readiness are +separate operations. Scenario `waitFor` calls retain their 15-second default and test deadlines are unchanged. + +Tests that manage multiple targets use `{createPage: false}` and the session's `chrome` or `firefox` client, +`port` and `extensionId`. Firefox subscriptions needed before extension installation go in `firefoxEvents`. +Always close the session in `finally`; its `output` getter supplies browser stderr for failure diagnostics. + +The Jest reporter writes `.cache/integration/browser-startup.json` after each run. It contains per-start test +names, files, durations and failures, plus successful-start median, nearest-rank p95 and maximum per browser. +Durations exclude binary discovery, profile allocation, extension installation and cleanup. A successful startup +does not imply that extension installation or the test passed. CI uploads this report even when browser tests fail. +For comparison runs, set `ADNBN_BROWSER_STARTUP_REPORT` to distinct output paths; the default file describes only +the latest completed Jest invocation, including an empty report when it starts no browsers. Collection uses +buffered console diagnostics, so keep the default `--verbose=false` and do not pass Jest `--silent` when measuring startup. An interrupted/killed Jest +worker may not deliver its diagnostics. Use repeated CI reports to assess distributions and failure frequency; +a larger timeout or one green local run does not establish stability. + ## Coverage - `types/content`: shared Content and adapter render types through the public source and built package APIs, callback props inference, and iframe-navigation restrictions for both define functions. diff --git a/tests/integration/browser/content/entrypoint-assets.firefox.integration.test.ts b/tests/integration/browser/content/entrypoint-assets.firefox.integration.test.ts index 6620689b..407a79a1 100644 --- a/tests/integration/browser/content/entrypoint-assets.firefox.integration.test.ts +++ b/tests/integration/browser/content/entrypoint-assets.firefox.integration.test.ts @@ -1,11 +1,9 @@ -import {mkdtemp, readFile, rm, stat} from "fs/promises"; -import os from "os"; +import {readFile, stat} from "fs/promises"; import path from "path"; -import {spawn, type ChildProcess} from "child_process"; import BidiClient from "../utils/BidiClient"; -import {findFirefoxBinary} from "../utils/firefox"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; +import {waitFor} from "../utils/browser"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; import {documentStateExpression, expectLoadedProbe, type DocumentState} from "./utils"; @@ -23,20 +21,10 @@ jest.setTimeout(90_000); test.each([2, 3] as const)( "Firefox MV%s loads entrypoint assets in the effective execution world", async manifestVersion => { - const firefoxBinary = findFirefoxBinary(); - - if (!firefoxBinary || !path.isAbsolute(firefoxBinary)) { - throw new Error( - "Firefox is not installed. Install Firefox or set ADNBN_FIREFOX_BIN to its absolute executable path." - ); - } - - const userDataDir = await mkdtemp(path.join(os.tmpdir(), "adnbn-content-firefox-")); - let firefox: ChildProcess | undefined; + let session: BrowserSession | undefined; let browser: BidiClient | undefined; let fixture: IntegrationFixture | undefined; let site: IntegrationSite | undefined; - let output = ""; let lastState: DocumentState | FrameState | undefined; const expectDocument = (state: DocumentState, frame: string): void => { @@ -54,6 +42,7 @@ test.each([2, 3] as const)( const extensionDir = await fixture.build({browser: "firefox", manifestVersion}); const manifest = JSON.parse(await readFile(path.join(extensionDir, "manifest.json"), "utf8")); const scripts = manifest.content_scripts as Array<{js: string[]; css?: string[]; world?: string}>; + const resources = manifestVersion === 2 ? (manifest.web_accessible_resources as string[]) @@ -68,13 +57,16 @@ test.each([2, 3] as const)( if (manifestVersion === 2) { expect(scripts.every(script => script.world === undefined)).toBe(true); + expect( manifest.web_accessible_resources.every((resource: unknown) => typeof resource === "string") ).toBe(true); + const common = scripts[0].js.filter(file => scripts.every(script => script.js.includes(file))); expect(common).toEqual([expect.stringMatching(/^js\/common\.content\..+\.js$/)]); } else { expect(scripts.map(script => script.world).sort()).toEqual(["ISOLATED", "ISOLATED", "MAIN", "MAIN"]); + expect( manifest.web_accessible_resources.every( (entry: {resources: string[]; matches: string[]}) => @@ -95,39 +87,27 @@ test.each([2, 3] as const)( } site = await startIntegrationSite(path.join(fixture.directory, "site")); - const port = await getFreePort(); - firefox = spawn( - firefoxBinary, - [ - "--headless", - "--no-remote", - "--profile", - userDataDir, - "--remote-debugging-port", - String(port), - "about:blank", - ], - {stdio: ["ignore", "ignore", "pipe"]} - ); - firefox.stderr?.on("data", chunk => (output += chunk)); - - browser = await waitFor(() => BidiClient.connect(`ws://127.0.0.1:${port}/session`)); - const session = await browser.send("session.new", {capabilities: {alwaysMatch: {}}}); - console.info(`Firefox ${session.capabilities.browserVersion}: testing Manifest V${manifestVersion}`); - await browser.send("session.subscribe", {events: ["log.entryAdded"]}); - const extension = await browser.send("webExtension.install", { - extensionData: {path: extensionDir, type: "path"}, + + session = await startBrowserSession("firefox", rootDir, extensionDir, { + createPage: false, + firefoxEvents: ["log.entryAdded"], }); - expect(typeof extension.extension).toBe("string"); + + browser = session.firefox!; + console.info(`${session.version}: testing Manifest V${manifestVersion}`); + expect(typeof session.extensionId).toBe("string"); const {context} = await browser.send("browsingContext.create", {type: "tab"}); await browser.send("browsingContext.navigate", {context, url: `${site.origin}/top.html`, wait: "complete"}); + const top = await waitFor(async () => { const state = await browser!.evaluate<DocumentState>(context, `${documentStateExpression}(document)`); lastState = state; const statuses = [state?.isolated?.async, state?.main?.async]; + return statuses.every(status => status !== undefined && status !== "pending") ? state : undefined; }); + expectDocument(top, "top"); await browser.send("browsingContext.navigate", { @@ -135,6 +115,7 @@ test.each([2, 3] as const)( url: `${site.origin}/frames.html`, wait: "complete", }); + const framed = await waitFor(async () => { const state = await browser!.evaluate<FrameState>( context, @@ -144,36 +125,31 @@ test.each([2, 3] as const)( return {top: ${documentStateExpression}(document), child: ${documentStateExpression}(child)}; })()` ); + lastState = state; + const statuses = [ state?.top?.isolated?.async, state?.top?.main?.async, state?.child?.isolated?.async, state?.child?.main?.async, ]; + return statuses.every(status => status !== undefined && status !== "pending") ? state : undefined; }); + expectDocument(framed.top, "top"); expectDocument(framed.child, "child"); expect(browser.runtimeErrors).toEqual([]); } catch (error) { throw new Error( - `${error instanceof Error ? error.message : String(error)}; last state: ${JSON.stringify(lastState)}; Runtime errors: ${JSON.stringify(browser?.runtimeErrors ?? [])}; Firefox output: ${output}`, + `${error instanceof Error ? error.message : String(error)}; last state: ${JSON.stringify(lastState)}; Runtime errors: ${JSON.stringify(browser?.runtimeErrors ?? [])}; Firefox output: ${session?.output ?? ""}`, {cause: error} ); } finally { - if (browser) { - try { - await browser.send("session.end", {}, 2_000); - } catch { - // Firefox may close the socket before returning the session.end response. - } - await browser.close(); - } - if (firefox) await stop(firefox); + await session?.close(); await site?.close(); await fixture?.dispose(); - await rm(userDataDir, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } } ); diff --git a/tests/integration/browser/content/entrypoint-assets.integration.test.ts b/tests/integration/browser/content/entrypoint-assets.integration.test.ts index e7783f9c..125cd11d 100644 --- a/tests/integration/browser/content/entrypoint-assets.integration.test.ts +++ b/tests/integration/browser/content/entrypoint-assets.integration.test.ts @@ -1,11 +1,10 @@ -import {mkdtemp, readFile, rm, stat} from "fs/promises"; -import os from "os"; +import {readFile, stat} from "fs/promises"; import path from "path"; -import {spawn, type ChildProcess} from "child_process"; -import {browserVersion, findChromeBinary, targets} from "../utils/chrome"; +import {targets} from "../utils/chrome"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; import CdpClient from "../utils/CdpClient"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {waitFor} from "../utils/browser"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; @@ -13,7 +12,6 @@ import {documentStateExpression, expectLoadedProbe, type DocumentState} from "./ const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); const fixtureDir = path.join(__dirname, "entrypoint-assets"); -const chromeBinary = findChromeBinary(rootDir); const evaluate = async (browser: CdpClient, sessionId: string, expression: string): Promise<any> => { const result = await browser.send( @@ -54,19 +52,10 @@ const waitForLoadedDocument = async (browser: CdpClient, sessionId: string): Pro jest.setTimeout(90_000); test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in the initial graph", async () => { - if (!chromeBinary || !path.isAbsolute(chromeBinary)) { - throw new Error( - "Chrome is not installed or could not be found. Install Chrome or set ADNBN_CHROME_BIN to its absolute executable path." - ); - } - - const userDataDir = await mkdtemp(path.join(os.tmpdir(), "adnbn-content-entrypoint-assets-")); - const debuggingPort = await getFreePort(); - let chrome: ChildProcess | undefined; + let session: BrowserSession | undefined; let browser: CdpClient | undefined; let site: IntegrationSite | undefined; let fixture: IntegrationFixture | undefined; - let chromeOutput = ""; try { fixture = await createIntegrationFixture(rootDir, fixtureDir); @@ -75,6 +64,7 @@ test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in const manifest = JSON.parse(await readFile(path.join(extensionDir, "manifest.json"), "utf8")); const contentScripts = manifest.content_scripts as Array<{css?: string[]; js: string[]; world?: string}>; const backgroundFile = manifest.background.service_worker as string; + const resources = (manifest.web_accessible_resources ?? []).flatMap( (item: {resources?: string[]}) => item.resources ?? [] ); @@ -85,6 +75,7 @@ test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in const isolatedScripts = contentScripts.filter(script => script.world === "ISOLATED"); const mainScripts = contentScripts.filter(script => script.world === "MAIN"); + const sharedFiles = (scripts: Array<{js: string[]}>): string[] => { return scripts[0].js.filter(file => scripts.slice(1).every(script => script.js.includes(file))); }; @@ -97,6 +88,7 @@ test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in expect(isolated.js.filter(file => main.js.includes(file))).toEqual([]); } } + expect(resources.some((file: string) => /^assets\/probe-[a-f0-9]{4}\.svg$/.test(file))).toBe(true); expect(resources.some((file: string) => /^js\/.+\.js$/.test(file))).toBe(true); expect(resources.some((file: string) => /^css\/.+\.css$/.test(file))).toBe(true); @@ -108,27 +100,10 @@ test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in } site = await startIntegrationSite(path.join(fixture.directory, "site")); - chrome = spawn( - chromeBinary, - [ - "--headless=new", - "--no-sandbox", - "--no-first-run", - "--no-default-browser-check", - "--enable-logging=stderr", - "--v=0", - `--remote-debugging-port=${debuggingPort}`, - `--user-data-dir=${userDataDir}`, - "about:blank", - ], - {stdio: ["ignore", "ignore", "pipe"]} - ); - chrome.stderr?.on("data", chunk => (chromeOutput += chunk)); - - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(debuggingPort)); - browser = await CdpClient.connect(webSocketDebuggerUrl); - const extension = await browser.send("Extensions.loadUnpacked", {path: extensionDir}); - const extensionId = extension.id as string | undefined; + session = await startBrowserSession("chrome", rootDir, extensionDir, {createPage: false}); + browser = session.chrome!; + const debuggingPort = session.port; + const extensionId = session.extensionId; if (!extensionId) { throw new Error("Chrome did not return an extension ID after loading the content fixture"); @@ -141,12 +116,14 @@ test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in target.url === `chrome-extension://${extensionId}/${backgroundFile}` ); }); + const {sessionId: workerSessionId} = await browser.send("Target.attachToTarget", { targetId: worker.id, flatten: true, }); await browser.send("Runtime.enable", {}, workerSessionId); + await waitFor(async () => { return (await evaluate( browser!, @@ -191,6 +168,7 @@ test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in }; })()` ); + const statuses = [ state?.top?.isolated?.async, state?.top?.main?.async, @@ -218,18 +196,13 @@ test("Chrome MV3 loads ISOLATED chunks lazily and keeps MAIN dynamic imports in throw new Error( `${error instanceof Error ? error.message : String(error)}; Runtime errors: ${JSON.stringify( browser?.runtimeErrors ?? [] - )}; Chrome output: ${chromeOutput}`, + )}; Chrome output: ${session?.output ?? ""}`, {cause: error} ); } finally { - await browser?.close(); - - if (chrome) { - await stop(chrome); - } + await session?.close(); await site?.close(); await fixture?.dispose(); - await rm(userDataDir, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); diff --git a/tests/integration/browser/content/isolated-styles-utils.ts b/tests/integration/browser/content/isolated-styles-utils.ts index 6bf1eb1b..c27c1a46 100644 --- a/tests/integration/browser/content/isolated-styles-utils.ts +++ b/tests/integration/browser/content/isolated-styles-utils.ts @@ -1,13 +1,10 @@ -import {spawn, type ChildProcess} from "child_process"; -import {mkdtemp, readFile, rm, stat, writeFile} from "fs/promises"; -import os from "os"; +import {readFile, stat, writeFile} from "fs/promises"; import path from "path"; -import {getFreePort, stop, waitFor} from "../utils/browser"; -import {browserVersion, findChromeBinary} from "../utils/chrome"; +import {waitFor} from "../utils/browser"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; import CdpClient from "../utils/CdpClient"; import BidiClient from "../utils/BidiClient"; -import {findFirefoxBinary} from "../utils/firefox"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; @@ -142,19 +139,11 @@ export const runIsolatedStylesIntegration = async ( fixtureName = "isolation-shadow" ): Promise<void> => { const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); - const binary = name === "chrome" ? findChromeBinary(rootDir) : findFirefoxBinary(); - - if (!binary || !path.isAbsolute(binary)) { - throw new Error("Install " + name + " or set its ADNBN_*_BIN path"); - } - - const profile = await mkdtemp(path.join(os.tmpdir(), "adnbn-isolation-integration-")); - let browserProcess: ChildProcess | undefined; + let session: BrowserSession | undefined; let chrome: CdpClient | undefined; let firefox: BidiClient | undefined; let fixture: IntegrationFixture | undefined; let site: IntegrationSite | undefined; - let processOutput = ""; let lastState: unknown; try { @@ -203,80 +192,12 @@ export const runIsolatedStylesIntegration = async ( await expect(stat(path.join(extensionDir, file))).resolves.toBeDefined(); } - const port = await getFreePort(); - let navigate: (url: string) => Promise<unknown>; - let evaluate: (expression: string) => Promise<any>; - let version: string; - - if (name === "chrome") { - browserProcess = spawn( - binary, - [ - "--headless=new", - "--no-sandbox", - "--no-first-run", - "--no-default-browser-check", - "--remote-debugging-port=" + port, - "--user-data-dir=" + profile, - "about:blank", - ], - {stdio: ["ignore", "ignore", "pipe"]} - ); - - browserProcess.stderr?.on("data", chunk => (processOutput += chunk)); - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(port)); - chrome = await CdpClient.connect(webSocketDebuggerUrl); - version = (await chrome.send("Browser.getVersion")).product; - const extension = await chrome.send("Extensions.loadUnpacked", {path: extensionDir}); - expect(typeof extension.id).toBe("string"); - const {targetId} = await chrome.send("Target.createTarget", {url: "about:blank"}); - const {sessionId} = await chrome.send("Target.attachToTarget", {targetId, flatten: true}); - await chrome.send("Runtime.enable", {}, sessionId); - await chrome.send("Page.enable", {}, sessionId); - navigate = url => chrome!.send("Page.navigate", {url}, sessionId); - - evaluate = async expression => { - const result = await chrome!.send( - "Runtime.evaluate", - {expression, awaitPromise: true, returnByValue: true}, - sessionId - ); + session = await startBrowserSession(name, rootDir, extensionDir); + chrome = session.chrome; + firefox = session.firefox; + const {navigate, evaluate, version} = session; - if (result.exceptionDetails) { - throw new Error(JSON.stringify(result.exceptionDetails)); - } - - return result.result.value; - }; - } else { - browserProcess = spawn( - binary, - [ - "--headless", - "--no-remote", - "--profile", - profile, - "--remote-debugging-port", - String(port), - "about:blank", - ], - {stdio: ["ignore", "ignore", "pipe"]} - ); - - browserProcess.stderr?.on("data", chunk => (processOutput += chunk)); - firefox = await waitFor(() => BidiClient.connect("ws://127.0.0.1:" + port + "/session")); - const session = await firefox.send("session.new", {capabilities: {alwaysMatch: {}}}); - version = "Firefox/" + session.capabilities.browserVersion; - - const extension = await firefox.send("webExtension.install", { - extensionData: {path: extensionDir, type: "path"}, - }); - - expect(typeof extension.extension).toBe("string"); - const {context} = await firefox.send("browsingContext.create", {type: "tab"}); - navigate = url => firefox!.send("browsingContext.navigate", {context, url, wait: "complete"}); - evaluate = expression => firefox!.evaluate(context, expression); - } + expect(typeof session.extensionId).toBe("string"); const measurements: Array<{policy: string; top: ShadowDocumentState; frames: FrameState}> = []; @@ -472,28 +393,12 @@ export const runIsolatedStylesIntegration = async ( expect(name === "chrome" ? chrome?.runtimeErrors : firefox?.runtimeErrors).toEqual([]); } catch (error) { throw new Error( - `${error instanceof Error ? error.message : String(error)}; last state: ${JSON.stringify(lastState)}; runtime errors: ${JSON.stringify(chrome?.runtimeErrors ?? firefox?.runtimeErrors ?? [])}; browser output: ${processOutput}`, + `${error instanceof Error ? error.message : String(error)}; last state: ${JSON.stringify(lastState)}; runtime errors: ${JSON.stringify(chrome?.runtimeErrors ?? firefox?.runtimeErrors ?? [])}; browser output: ${session?.output ?? ""}`, {cause: error} ); } finally { - await chrome?.close(); - - if (firefox) { - try { - await firefox.send("session.end", {}, 2_000); - } catch { - // Firefox may close before replying. - } - - await firefox.close(); - } - - if (browserProcess) { - await stop(browserProcess); - } - + await session?.close(); await site?.close(); await fixture?.dispose(); - await rm(profile, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }; diff --git a/tests/integration/browser/locale/chunks.firefox.integration.test.ts b/tests/integration/browser/locale/chunks.firefox.integration.test.ts index 854ed316..beb9e79e 100644 --- a/tests/integration/browser/locale/chunks.firefox.integration.test.ts +++ b/tests/integration/browser/locale/chunks.firefox.integration.test.ts @@ -1,55 +1,47 @@ -import {spawn, type ChildProcess} from "child_process"; -import {mkdtemp, rm} from "fs/promises"; -import os from "os"; import path from "path"; import {createIntegrationFixture} from "../../utils/fixture"; import BidiClient from "../utils/BidiClient"; -import {findFirefoxBinary} from "../utils/firefox"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; +import {waitFor} from "../utils/browser"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; import {changeLanguage, expectPanels, localeFixtureDirectory, readPanels} from "./utils"; jest.setTimeout(90_000); test("Firefox MV3 loads locale in ISOLATED and shares the MAIN catalogue through common content", async () => { - const binary = findFirefoxBinary(); - if (!binary) throw new Error("Install Firefox or set ADNBN_FIREFOX_BIN"); const fixture = await createIntegrationFixture(ADNBN_TEST_ROOT, localeFixtureDirectory); - const profile = await mkdtemp(path.join(os.tmpdir(), "adnbn-locale-firefox-")); - let process: ChildProcess | undefined; + let session: BrowserSession | undefined; let client: BidiClient | undefined; let site: IntegrationSite | undefined; try { const directory = await fixture.build({browser: "firefox"}); - const port = await getFreePort(); - process = spawn( - binary, - ["--headless", "--no-remote", "--profile", profile, "--remote-debugging-port", String(port), "about:blank"], - {stdio: "ignore"} - ); - client = await waitFor(() => BidiClient.connect(`ws://127.0.0.1:${port}/session`)); - await client.send("session.new", {capabilities: {alwaysMatch: {}}}); - await client.send("session.subscribe", {events: ["log.entryAdded"]}); - await client.send("webExtension.install", {extensionData: {path: directory, type: "path"}}); + + session = await startBrowserSession("firefox", ADNBN_TEST_ROOT, directory, { + createPage: false, + firefoxEvents: ["log.entryAdded"], + }); + + client = session.firefox!; site = await startIntegrationSite(path.join(fixture.directory, "site")); const {context} = await client.send("browsingContext.create", {type: "tab"}); await client.send("browsingContext.navigate", {context, url: site.origin, wait: "complete"}); const contexts = ["isolated", "isolated-secondary", "main", "main-secondary"]; + await waitFor(async () => { expectPanels(await client!.evaluate(context, readPanels), contexts); + return true; }); + expect(await client.evaluate(context, "typeof globalThis.browser?.i18n")).toBe("undefined"); await client.evaluate(context, changeLanguage); expectPanels(await client.evaluate(context, readPanels), contexts, "fr"); expect(client.runtimeErrors).toEqual([]); } finally { - await client?.close(); - if (process) await stop(process); + await session?.close(); await site?.close(); await fixture.dispose(); - await rm(profile, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); diff --git a/tests/integration/browser/locale/chunks.integration.test.ts b/tests/integration/browser/locale/chunks.integration.test.ts index 0383a9ee..959ba86f 100644 --- a/tests/integration/browser/locale/chunks.integration.test.ts +++ b/tests/integration/browser/locale/chunks.integration.test.ts @@ -1,53 +1,27 @@ -import {spawn, type ChildProcess} from "child_process"; -import {mkdtemp, readFile, rm} from "fs/promises"; -import os from "os"; +import {readFile} from "fs/promises"; import path from "path"; import {createIntegrationFixture} from "../../utils/fixture"; -import {browserVersion, findChromeBinary} from "../utils/chrome"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; import CdpClient from "../utils/CdpClient"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {waitFor} from "../utils/browser"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; import {changeLanguage, expectPanels, localeFixtureDirectory, readPanels} from "./utils"; jest.setTimeout(90_000); test("Chrome shares locale with views and ISOLATED, and loads the MAIN catalogue through common content", async () => { - const binary = findChromeBinary(ADNBN_TEST_ROOT); - - if (!binary) { - throw new Error("Install Chrome for Testing or set ADNBN_CHROME_BIN"); - } - const fixture = await createIntegrationFixture(ADNBN_TEST_ROOT, localeFixtureDirectory); - const profile = await mkdtemp(path.join(os.tmpdir(), "adnbn-locale-chrome-")); - let process: ChildProcess | undefined; + let session: BrowserSession | undefined; let client: CdpClient | undefined; let site: IntegrationSite | undefined; try { const directory = await fixture.build(); const manifest = JSON.parse(await readFile(path.join(directory, "manifest.json"), "utf8")); - const port = await getFreePort(); - - process = spawn( - binary, - [ - "--headless=new", - "--no-sandbox", - "--no-first-run", - "--no-default-browser-check", - `--user-data-dir=${profile}`, - `--remote-debugging-port=${port}`, - "about:blank", - ], - {stdio: "ignore"} - ); - - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(port)); - client = await CdpClient.connect(webSocketDebuggerUrl); - const extension = await client.send("Extensions.loadUnpacked", {path: directory}); - const origin = `chrome-extension://${extension.id}`; + session = await startBrowserSession("chrome", ADNBN_TEST_ROOT, directory, {createPage: false}); + client = session.chrome!; + const origin = `chrome-extension://${session.extensionId}`; const attach = async (targetId: string): Promise<string> => { const {sessionId} = await client!.send("Target.attachToTarget", {targetId, flatten: true}); @@ -114,14 +88,9 @@ test("Chrome shares locale with views and ISOLATED, and loads the MAIN catalogue expect(client.runtimeErrors).toEqual([]); } finally { - await client?.close(); - - if (process) { - await stop(process); - } + await session?.close(); await site?.close(); await fixture.dispose(); - await rm(profile, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); diff --git a/tests/integration/browser/locale/dynamic.firefox.integration.test.ts b/tests/integration/browser/locale/dynamic.firefox.integration.test.ts index 96c46e5f..adda23e5 100644 --- a/tests/integration/browser/locale/dynamic.firefox.integration.test.ts +++ b/tests/integration/browser/locale/dynamic.firefox.integration.test.ts @@ -1,39 +1,33 @@ -import {spawn, type ChildProcess} from "child_process"; -import {mkdtemp, rm} from "fs/promises"; -import os from "os"; import path from "path"; import {createIntegrationFixture} from "../../utils/fixture"; import BidiClient from "../utils/BidiClient"; -import {findFirefoxBinary} from "../utils/firefox"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; +import {waitFor} from "../utils/browser"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; jest.setTimeout(90_000); test("Firefox uses DynamicLocale in both worlds and restores stored selection only in ISOLATED without requests", async () => { - const binary = findFirefoxBinary(); - if (!binary) throw new Error("Install Firefox or set ADNBN_FIREFOX_BIN"); const fixture = await createIntegrationFixture( ADNBN_TEST_ROOT, path.join(ADNBN_TEST_ROOT, "tests/integration/build/locale/dynamic-fixture") ); - const profile = await mkdtemp(path.join(os.tmpdir(), "adnbn-dynamic-locale-firefox-")); - let browser: ChildProcess | undefined; + + let session: BrowserSession | undefined; let client: BidiClient | undefined; let site: IntegrationSite | undefined; + try { const directory = await fixture.build({browser: "firefox"}); - const port = await getFreePort(); - browser = spawn( - binary, - ["--headless", "--no-remote", "--profile", profile, "--remote-debugging-port", String(port), "about:blank"], - {stdio: "ignore"} - ); - client = await waitFor(() => BidiClient.connect(`ws://127.0.0.1:${port}/session`)); - await client.send("session.new", {capabilities: {alwaysMatch: {}}}); - await client.send("session.subscribe", {events: ["log.entryAdded", "network.beforeRequestSent"]}); - await client.send("webExtension.install", {extensionData: {path: directory, type: "path"}}); + + session = await startBrowserSession("firefox", ADNBN_TEST_ROOT, directory, { + createPage: false, + firefoxEvents: ["log.entryAdded", "network.beforeRequestSent"], + }); + + client = session.firefox!; site = await startIntegrationSite(path.join(fixture.directory, "site")); + const expectLanguage = async (context: string, language: string) => waitFor(async () => { expect( @@ -42,11 +36,14 @@ test("Firefox uses DynamicLocale in both worlds and restores stored selection on `document.querySelector('[data-locale-context="isolated"]')?.dataset.language` ) ).toBe(language); + return true; }); + const {context} = await client.send("browsingContext.create", {type: "tab"}); await client.send("browsingContext.navigate", {context, url: site.origin, wait: "complete"}); await expectLanguage(context, "en"); + await waitFor(async () => { expect( await client!.evaluate( @@ -54,9 +51,12 @@ test("Firefox uses DynamicLocale in both worlds and restores stored selection on `document.querySelector('[data-locale-context="main"]')?.dataset.language` ) ).toBe("en"); + return true; }); + const before = client.requests.length; + const greeting = await client.evaluate( context, `(() => { @@ -67,10 +67,12 @@ test("Firefox uses DynamicLocale in both worlds and restores stored selection on return panel.querySelector('p').textContent; })()` ); + expect(greeting).toBe("Bonjour depuis DynamicLocale !"); const second = await client.send("browsingContext.create", {type: "tab"}); await client.send("browsingContext.navigate", {context: second.context, url: site.origin, wait: "complete"}); await expectLanguage(second.context, "fr"); + await waitFor(async () => { expect( await client!.evaluate( @@ -78,8 +80,10 @@ test("Firefox uses DynamicLocale in both worlds and restores stored selection on `document.querySelector('[data-locale-context="main"]')?.dataset.language` ) ).toBe("en"); + return true; }); + expect( await client.evaluate( context, @@ -92,6 +96,7 @@ test("Firefox uses DynamicLocale in both worlds and restores stored selection on })()` ) ).toEqual({lang: "fr", message: "Bonjour depuis DynamicLocale !"}); + await client.evaluate( second.context, `(() => { @@ -100,18 +105,19 @@ test("Firefox uses DynamicLocale in both worlds and restores stored selection on select.dispatchEvent(new Event('change', {bubbles: true})); })()` ); + await expectLanguage(context, "en"); + expect( await client.evaluate(context, `document.querySelector('[data-locale-context="main"]').dataset.language`) ).toBe("fr"); + expect(client.requests.slice(before).filter(url => url.startsWith("moz-extension://"))).toEqual([]); expect(client.requests.filter(url => /_locales\/|messages\.json/.test(url))).toEqual([]); expect(client.runtimeErrors).toEqual([]); } finally { - await client?.close(); - if (browser) await stop(browser); + await session?.close(); await site?.close(); await fixture.dispose(); - await rm(profile, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); diff --git a/tests/integration/browser/locale/dynamic.integration.test.ts b/tests/integration/browser/locale/dynamic.integration.test.ts index 33f347f5..75cf4162 100644 --- a/tests/integration/browser/locale/dynamic.integration.test.ts +++ b/tests/integration/browser/locale/dynamic.integration.test.ts @@ -1,55 +1,29 @@ -import {spawn, type ChildProcess} from "child_process"; -import {mkdtemp, readFile, rm} from "fs/promises"; -import os from "os"; +import {readFile} from "fs/promises"; import path from "path"; import {createIntegrationFixture} from "../../utils/fixture"; -import {browserVersion, findChromeBinary} from "../utils/chrome"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; import CdpClient from "../utils/CdpClient"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {waitFor} from "../utils/browser"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; jest.setTimeout(90_000); test("Chrome switches DynamicLocale in both worlds and synchronizes extension storage without loading translations", async () => { - const binary = findChromeBinary(ADNBN_TEST_ROOT); - - if (!binary) { - throw new Error("Install Chrome for Testing or set ADNBN_CHROME_BIN"); - } - const fixture = await createIntegrationFixture( ADNBN_TEST_ROOT, path.join(ADNBN_TEST_ROOT, "tests/integration/build/locale/dynamic-fixture") ); - const profile = await mkdtemp(path.join(os.tmpdir(), "adnbn-dynamic-locale-chrome-")); - let browser: ChildProcess | undefined; + let session: BrowserSession | undefined; let client: CdpClient | undefined; let site: IntegrationSite | undefined; try { const directory = await fixture.build(); const manifest = JSON.parse(await readFile(path.join(directory, "manifest.json"), "utf8")); - const port = await getFreePort(); - - browser = spawn( - binary, - [ - "--headless=new", - "--no-sandbox", - "--no-first-run", - "--no-default-browser-check", - `--user-data-dir=${profile}`, - `--remote-debugging-port=${port}`, - "about:blank", - ], - {stdio: "ignore"} - ); - - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(port)); - client = await CdpClient.connect(webSocketDebuggerUrl); - const extension = await client.send("Extensions.loadUnpacked", {path: directory}); - const origin = `chrome-extension://${extension.id}`; + session = await startBrowserSession("chrome", ADNBN_TEST_ROOT, directory, {createPage: false}); + client = session.chrome!; + const origin = `chrome-extension://${session.extensionId}`; const attach = async (targetId: string): Promise<string> => { const {sessionId} = await client!.send("Target.attachToTarget", {targetId, flatten: true}); @@ -218,14 +192,9 @@ test("Chrome switches DynamicLocale in both worlds and synchronizes extension st expect(client.requests.filter(url => /_locales\/|messages\.json/.test(url))).toEqual([]); expect(client.runtimeErrors).toEqual([]); } finally { - await client?.close(); - - if (browser) { - await stop(browser); - } + await session?.close(); await site?.close(); await fixture.dispose(); - await rm(profile, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); diff --git a/tests/integration/browser/offscreen/service.integration.test.ts b/tests/integration/browser/offscreen/service.integration.test.ts index 9f7380a0..7703b922 100644 --- a/tests/integration/browser/offscreen/service.integration.test.ts +++ b/tests/integration/browser/offscreen/service.integration.test.ts @@ -1,58 +1,29 @@ -import {mkdtemp, rm} from "fs/promises"; -import os from "os"; import path from "path"; -import {spawn, type ChildProcess} from "child_process"; -import {browserVersion, type CdpTarget, findChromeBinary, targets} from "../utils/chrome"; +import {type CdpTarget, targets} from "../utils/chrome"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; import CdpClient from "../utils/CdpClient"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {waitFor} from "../utils/browser"; import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); const fixtureDir = path.join(__dirname, "service"); -const chromeBinary = findChromeBinary(rootDir); jest.setTimeout(60_000); test("Chrome MV3 offscreen calls the registered background service", async () => { - if (!chromeBinary || !path.isAbsolute(chromeBinary)) { - throw new Error( - "Chrome is not installed or could not be found. Install Chrome or set ADNBN_CHROME_BIN to its absolute executable path." - ); - } - - const userDataDir = await mkdtemp(path.join(os.tmpdir(), "adnbn-offscreen-service-")); - const debuggingPort = await getFreePort(); - let chrome: ChildProcess | undefined; + let session: BrowserSession | undefined; let browser: CdpClient | undefined; let fixture: IntegrationFixture | undefined; - let chromeOutput = ""; try { fixture = await createIntegrationFixture(rootDir, fixtureDir); const extensionDir = await fixture.build(); - chrome = spawn( - chromeBinary, - [ - "--headless=new", - "--no-sandbox", - "--no-first-run", - "--no-default-browser-check", - "--enable-logging=stderr", - "--v=0", - `--remote-debugging-port=${debuggingPort}`, - `--user-data-dir=${userDataDir}`, - "about:blank", - ], - {stdio: ["ignore", "ignore", "pipe"]} - ); - chrome.stderr?.on("data", chunk => (chromeOutput += chunk)); - - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(debuggingPort)); - browser = await CdpClient.connect(webSocketDebuggerUrl); - const extension = await browser.send("Extensions.loadUnpacked", {path: extensionDir}); - const extensionId = extension.id as string | undefined; + session = await startBrowserSession("chrome", rootDir, extensionDir, {createPage: false}); + browser = session.chrome!; + const debuggingPort = session.port; + const extensionId = session.extensionId; if (!extensionId) { throw new Error("Chrome did not return an extension ID after loading the MV3 fixture"); @@ -75,7 +46,7 @@ test("Chrome MV3 offscreen calls the registered background service", async () => throw new Error( `${error instanceof Error ? error.message : String(error)}; CDP targets: ${JSON.stringify( chromeTargets.map(target => ({type: target.type, url: target.url})) - )}; Chrome output: ${chromeOutput}` + )}; Chrome output: ${session?.output ?? ""}` ); } @@ -123,13 +94,8 @@ test("Chrome MV3 offscreen calls the registered background service", async () => expect(result.exceptionDetails).toBeUndefined(); expect(result.result.value).toBe("background:ping"); } finally { - await browser?.close(); - - if (chrome) { - await stop(chrome); - } + await session?.close(); await fixture?.dispose(); - await rm(userDataDir, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); diff --git a/tests/integration/browser/options/options.integration.test.ts b/tests/integration/browser/options/options.integration.test.ts index 89eaa4d8..150ad11b 100644 --- a/tests/integration/browser/options/options.integration.test.ts +++ b/tests/integration/browser/options/options.integration.test.ts @@ -1,15 +1,13 @@ -import {mkdtemp, readFile, rm} from "fs/promises"; -import os from "os"; +import {readFile} from "fs/promises"; import path from "path"; -import {spawn, type ChildProcess} from "child_process"; -import {browserVersion, findChromeBinary, targets} from "../utils/chrome"; +import {targets} from "../utils/chrome"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; import CdpClient from "../utils/CdpClient"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {waitFor} from "../utils/browser"; import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); -const chromeBinary = findChromeBinary(rootDir); jest.setTimeout(90_000); @@ -17,19 +15,10 @@ test.each([ {adapter: "vanilla", page: "options.html", title: "Vanilla Options", help: "help.html"}, {adapter: "react", page: "ui/preferences.options.html", title: "React Options", help: "ui/help.html"}, ])("Chrome MV3 opens and renders $adapter options from the background", async ({adapter, page, title, help}) => { - if (!chromeBinary || !path.isAbsolute(chromeBinary)) { - throw new Error( - "Chrome is not installed or could not be found. Install Chrome or set ADNBN_CHROME_BIN to its absolute executable path." - ); - } - const fixtureDir = path.join(__dirname, adapter); - const userDataDir = await mkdtemp(path.join(os.tmpdir(), `adnbn-options-${adapter}-`)); - const debuggingPort = await getFreePort(); - let chrome: ChildProcess | undefined; + let session: BrowserSession | undefined; let browser: CdpClient | undefined; let fixture: IntegrationFixture | undefined; - let chromeOutput = ""; try { fixture = await createIntegrationFixture(rootDir, fixtureDir); @@ -45,27 +34,10 @@ test.each([ expect(optionsHtml).toContain("common.view.js"); expect(helpHtml).toContain("common.view.js"); - chrome = spawn( - chromeBinary, - [ - "--headless=new", - "--no-sandbox", - "--no-first-run", - "--no-default-browser-check", - "--enable-logging=stderr", - "--v=0", - `--remote-debugging-port=${debuggingPort}`, - `--user-data-dir=${userDataDir}`, - "about:blank", - ], - {stdio: ["ignore", "ignore", "pipe"]} - ); - chrome.stderr?.on("data", chunk => (chromeOutput += chunk)); - - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(debuggingPort)); - browser = await CdpClient.connect(webSocketDebuggerUrl); - const extension = await browser.send("Extensions.loadUnpacked", {path: extensionDir}); - const extensionId = extension.id as string | undefined; + session = await startBrowserSession("chrome", rootDir, extensionDir, {createPage: false}); + browser = session.chrome!; + const debuggingPort = session.port; + const extensionId = session.extensionId; if (!extensionId) { throw new Error("Chrome did not return an extension ID after loading the Options fixture"); @@ -92,15 +64,18 @@ test.each([ target.url === `chrome-extension://${extensionId}/${manifest.background.service_worker}` ); }); + const {sessionId: workerSessionId} = await browser.send("Target.attachToTarget", { targetId: worker.id, flatten: true, }); await browser.send("Runtime.enable", {}, workerSessionId); + await waitFor(async () => { return (await evaluate(workerSessionId, "globalThis.__adnbnOptionsReady === true")) ? true : undefined; }); + await evaluate(workerSessionId, "chrome.runtime.openOptionsPage()"); const optionsTarget = await waitFor(async () => { @@ -108,12 +83,14 @@ test.each([ target => target.type === "page" && target.url === `chrome-extension://${extensionId}/${page}` ); }); + const {sessionId: optionsSessionId} = await browser.send("Target.attachToTarget", { targetId: optionsTarget.id, flatten: true, }); await browser.send("Runtime.enable", {}, optionsSessionId); + const rendered = await waitFor(async () => { return ( (await evaluate( @@ -136,6 +113,7 @@ test.each([ expect(rendered).toEqual({adapter, title, count: "0", color: "rgb(31, 78, 121)", topLevel: true}); await evaluate(optionsSessionId, "document.querySelector('[data-testid=increment]').click()"); + const count = await waitFor(async () => { const value = await evaluate(optionsSessionId, "document.querySelector('[data-testid=count]').textContent"); @@ -148,17 +126,12 @@ test.each([ throw new Error( `${error instanceof Error ? error.message : String(error)}; Runtime errors: ${JSON.stringify( browser?.runtimeErrors ?? [] - )}; Chrome output: ${chromeOutput}`, + )}; Chrome output: ${session?.output ?? ""}`, {cause: error} ); } finally { - await browser?.close(); - - if (chrome) { - await stop(chrome); - } + await session?.close(); await fixture?.dispose(); - await rm(userDataDir, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); diff --git a/tests/integration/browser/utils/chrome.ts b/tests/integration/browser/utils/chrome.ts index 9fb72127..93e6b1a5 100644 --- a/tests/integration/browser/utils/chrome.ts +++ b/tests/integration/browser/utils/chrome.ts @@ -17,6 +17,7 @@ export const findChromeBinary = (rootDir: string): string | undefined => { [path.join(rootDir, "node_modules", "chrome-launcher", "bin", "print-chrome-path.cjs")], {encoding: "utf8"} ); + const chromePath = result.status === 0 ? result.stdout.trim() : ""; return chromePath || undefined; @@ -28,8 +29,8 @@ export const targets = async (port: number): Promise<CdpTarget[]> => { return response.json() as Promise<CdpTarget[]>; }; -export const browserVersion = async (port: number): Promise<{webSocketDebuggerUrl: string}> => { - const response = await fetch(`http://127.0.0.1:${port}/json/version`, {signal: AbortSignal.timeout(5_000)}); +export const browserVersion = async (port: number, timeout = 5_000): Promise<{webSocketDebuggerUrl: string}> => { + const response = await fetch(`http://127.0.0.1:${port}/json/version`, {signal: AbortSignal.timeout(timeout)}); return response.json() as Promise<{webSocketDebuggerUrl: string}>; }; diff --git a/tests/integration/browser/utils/session.ts b/tests/integration/browser/utils/session.ts index a3523846..d0dacb5a 100644 --- a/tests/integration/browser/utils/session.ts +++ b/tests/integration/browser/utils/session.ts @@ -3,18 +3,35 @@ import {mkdtemp, rm} from "fs/promises"; import os from "os"; import path from "path"; -import {getFreePort, stop, waitFor} from "./browser"; +import {getFreePort, stop} from "./browser"; import {browserVersion, findChromeBinary} from "./chrome"; import CdpClient from "./CdpClient"; import BidiClient from "./BidiClient"; import {findFirefoxBinary} from "./firefox"; +import {waitForBrowserStartup} from "./startup"; -/** A real extension browser session; owns its process, connection and temporary profile. */ -export const startBrowserSession = async (name: "chrome" | "firefox", rootDir: string, extensionDir: string) => { +interface BrowserSessionOptions { + startupTimeout?: number; + createPage?: boolean; + firefoxEvents?: string[]; +} + +/** Owns the browser process, protocol connection and temporary profile. */ +export const startBrowserSession = async ( + name: "chrome" | "firefox", + rootDir: string, + extensionDir: string, + {startupTimeout = 30_000, createPage = true, firefoxEvents = []}: BrowserSessionOptions = {} +) => { const binary = name === "chrome" ? findChromeBinary(rootDir) : findFirefoxBinary(); - if (!binary || !path.isAbsolute(binary)) throw new Error(`Install ${name} or set its ADNBN_*_BIN path`); + + if (!binary || !path.isAbsolute(binary)) { + throw new Error(`Install ${name} or set its ADNBN_*_BIN path`); + } + const port = await getFreePort(); - const profile = await mkdtemp(path.join(os.tmpdir(), "adnbn-isolation-")); + const profile = await mkdtemp(path.join(os.tmpdir(), "adnbn-browser-")); + const args = name === "chrome" ? [ @@ -22,6 +39,8 @@ export const startBrowserSession = async (name: "chrome" | "firefox", rootDir: s "--no-sandbox", "--no-first-run", "--no-default-browser-check", + "--enable-logging=stderr", + "--v=0", `--remote-debugging-port=${port}`, `--user-data-dir=${profile}`, "about:blank", @@ -35,68 +54,189 @@ export const startBrowserSession = async (name: "chrome" | "firefox", rootDir: s String(port), "about:blank", ]; + + const startedAt = performance.now(); const process = spawn(binary, args, {stdio: ["ignore", "ignore", "pipe"]}); let output = ""; + let spawnError: Error | undefined; + + process.once("error", error => { + spawnError = error; + }); + process.stderr?.on("data", chunk => { output += chunk; }); + let chrome: CdpClient | undefined; let firefox: BidiClient | undefined; + const close = async () => { try { await chrome?.close(); + if (firefox) { await firefox.send("session.end", {}, 2000).catch(() => undefined); await firefox.close(); } } finally { - await stop(process); - await rm(profile, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); + try { + if (process.pid) { + await stop(process); + } + } finally { + await rm(profile, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); + } } }; + + let startupReported = false; + + const reportStartup = (status: "ready" | "failed", error?: unknown) => { + startupReported = true; + + console.info( + "[browser-startup] " + + JSON.stringify({ + browser: name, + testName: expect.getState().currentTestName, + durationMs: Math.round(performance.now() - startedAt), + timeoutMs: startupTimeout, + status, + ...(error ? {error: String(error)} : {}), + }) + ); + }; + try { - let version: string; - let navigate: (url: string) => Promise<unknown>; - let evaluate: (expression: string) => Promise<any>; - if (name === "chrome") { - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(port)); - chrome = await CdpClient.connect(webSocketDebuggerUrl); - version = (await chrome.send("Browser.getVersion")).product; - await chrome.send("Extensions.loadUnpacked", {path: extensionDir}); - const {targetId} = await chrome.send("Target.createTarget", {url: "about:blank"}); - const {sessionId} = await chrome.send("Target.attachToTarget", {targetId, flatten: true}); - await chrome.send("Runtime.enable", {}, sessionId); - await chrome.send("Page.enable", {}, sessionId); - navigate = url => chrome!.send("Page.navigate", {url}, sessionId); - evaluate = async expression => { - const result = await chrome!.send( - "Runtime.evaluate", - {expression, awaitPromise: true, returnByValue: true}, - sessionId - ); - if (result.exceptionDetails) throw new Error(JSON.stringify(result.exceptionDetails)); - return result.result.value; - }; + const version = await waitForBrowserStartup( + async remaining => { + if (name === "chrome") { + const {webSocketDebuggerUrl} = await browserVersion(port, Math.min(5_000, remaining())); + chrome = await CdpClient.connect(webSocketDebuggerUrl, remaining()); + + try { + return (await chrome.send("Browser.getVersion", {}, undefined, remaining())).product as string; + } catch (error) { + await chrome.close(); + chrome = undefined; + + throw error; + } + } + + firefox = await BidiClient.connect(`ws://127.0.0.1:${port}/session`, Math.min(1_000, remaining())); + + try { + const session = await firefox.send("session.new", {capabilities: {alwaysMatch: {}}}, remaining()); + + return "Firefox/" + session.capabilities.browserVersion; + } catch (error) { + await firefox.close(); + firefox = undefined; + + throw error; + } + }, + { + timeout: startupTimeout, + startedAt, + description: `${name} startup`, + checkProcess: () => { + if (spawnError) { + throw spawnError; + } + + if (process.exitCode !== null || process.signalCode !== null) { + throw new Error(`${name} exited before startup: ${process.exitCode ?? process.signalCode}`); + } + }, + } + ); + + reportStartup("ready"); + + let extensionId: string; + + let navigate: (url: string) => Promise<unknown> = async () => { + throw new Error("This session delegates page creation to the test"); + }; + + let evaluate: (expression: string) => Promise<any> = navigate; + + if (chrome) { + extensionId = (await chrome.send("Extensions.loadUnpacked", {path: extensionDir})).id; + + if (createPage) { + const {targetId} = await chrome.send("Target.createTarget", {url: "about:blank"}); + const {sessionId} = await chrome.send("Target.attachToTarget", {targetId, flatten: true}); + + await chrome.send("Runtime.enable", {}, sessionId); + await chrome.send("Page.enable", {}, sessionId); + navigate = url => chrome!.send("Page.navigate", {url}, sessionId); + + evaluate = async expression => { + const result = await chrome!.send( + "Runtime.evaluate", + {expression, awaitPromise: true, returnByValue: true}, + sessionId + ); + + if (result.exceptionDetails) { + throw new Error(JSON.stringify(result.exceptionDetails)); + } + + return result.result.value; + }; + } } else { - firefox = await waitFor(() => BidiClient.connect(`ws://127.0.0.1:${port}/session`)); - const session = await firefox.send("session.new", {capabilities: {alwaysMatch: {}}}); - version = "Firefox/" + session.capabilities.browserVersion; - await firefox.send("webExtension.install", {extensionData: {path: extensionDir, type: "path"}}); - const {context} = await firefox.send("browsingContext.create", {type: "tab"}); - navigate = url => firefox!.send("browsingContext.navigate", {context, url, wait: "complete"}); - evaluate = expression => firefox!.evaluate(context, expression); + if (firefoxEvents.length) { + await firefox!.send("session.subscribe", {events: firefoxEvents}); + } + + extensionId = ( + await firefox!.send("webExtension.install", { + extensionData: {path: extensionDir, type: "path"}, + }) + ).extension; + + if (createPage) { + const {context} = await firefox!.send("browsingContext.create", {type: "tab"}); + + navigate = url => firefox!.send("browsingContext.navigate", {context, url, wait: "complete"}); + evaluate = expression => firefox!.evaluate(context, expression); + } } + return { version, + extensionId, + port, + chrome, + firefox, navigate, evaluate, close, + get output() { + return output; + }, get errors() { return chrome?.runtimeErrors ?? firefox?.runtimeErrors ?? []; }, }; } catch (error) { - await close(); + if (!startupReported) { + reportStartup("failed", error); + } + + try { + await close(); + } catch (cleanupError) { + throw new AggregateError([error, cleanupError], `${name} failed; browser output: ${output}`); + } + throw new Error(`${String(error)}; browser output: ${output}`, {cause: error}); } }; + +export type BrowserSession = Awaited<ReturnType<typeof startBrowserSession>>; diff --git a/tests/integration/browser/utils/startup.test.ts b/tests/integration/browser/utils/startup.test.ts new file mode 100644 index 00000000..3382a81e --- /dev/null +++ b/tests/integration/browser/utils/startup.test.ts @@ -0,0 +1,89 @@ +import {waitForBrowserStartup} from "./startup"; + +afterEach(() => { + jest.useRealTimers(); +}); + +test("startup retries share one deadline, including time spent connecting", async () => { + jest.useFakeTimers(); + const startedAt = performance.now(); + const budgets: number[] = []; + const failure = new Error("debugging endpoint unavailable"); + const result = waitForBrowserStartup( + async remaining => { + budgets.push(remaining()); + await new Promise(resolve => setTimeout(resolve, 400)); + + throw failure; + }, + {timeout: 900, startedAt, description: "chrome startup", checkProcess: () => {}} + ); + const assertion = expect(result).rejects.toMatchObject({ + message: "Timed out waiting for chrome startup after 900 ms", + cause: failure, + }); + + await jest.advanceTimersByTimeAsync(900); + await assertion; + expect(budgets).toEqual([900, 400]); +}); + +test("a later successful connection receives only the remaining startup budget", async () => { + jest.useFakeTimers(); + let attempts = 0; + const result = waitForBrowserStartup( + async remaining => { + attempts++; + + if (attempts === 1) { + throw new Error("not listening yet"); + } + + return remaining(); + }, + {timeout: 1_000, startedAt: performance.now(), description: "firefox startup", checkProcess: () => {}} + ); + + await jest.advanceTimersByTimeAsync(100); + await expect(result).resolves.toBe(900); +}); + +test("browser process failure aborts startup without retrying until the deadline", async () => { + const failure = new Error("browser exited with code 1"); + let attempts = 0; + + await expect( + waitForBrowserStartup( + async () => { + attempts++; + + return true; + }, + { + timeout: 30_000, + startedAt: performance.now(), + description: "firefox startup", + checkProcess: () => { + throw failure; + }, + } + ) + ).rejects.toBe(failure); + expect(attempts).toBe(0); +}); + +test("a readiness response delivered after the deadline is not accepted", async () => { + jest.useFakeTimers(); + const result = waitForBrowserStartup( + async () => { + await new Promise(resolve => setTimeout(resolve, 200)); + + return "ready"; + }, + {timeout: 100, startedAt: performance.now(), description: "chrome startup", checkProcess: () => {}} + ); + const assertion = expect(result).rejects.toThrow("Timed out waiting for chrome startup after 100 ms"); + + await jest.advanceTimersByTimeAsync(200); + await assertion; +}); diff --git a/tests/integration/browser/utils/startup.ts b/tests/integration/browser/utils/startup.ts new file mode 100644 index 00000000..203029eb --- /dev/null +++ b/tests/integration/browser/utils/startup.ts @@ -0,0 +1,46 @@ +interface BrowserStartupOptions { + timeout: number; + startedAt: number; + description: string; + checkProcess(): void; +} + +/** Each protocol operation must use remaining() as its own cancellable timeout. */ +export const waitForBrowserStartup = async <T>( + connect: (remaining: () => number) => Promise<T>, + {timeout, startedAt, description, checkProcess}: BrowserStartupOptions +): Promise<T> => { + const remaining = () => Math.max(0, Math.ceil(timeout - (performance.now() - startedAt))); + let lastError: unknown; + + while (remaining() > 0) { + checkProcess(); + + try { + const value = await connect(() => { + const budget = remaining(); + + if (!budget) { + throw new Error(`${description} deadline reached`); + } + + return budget; + }); + + if (remaining() > 0) { + return value; + } + } catch (error) { + lastError = error; + } + + checkProcess(); + const delay = Math.min(100, remaining()); + + if (delay > 0) { + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + + throw new Error(`Timed out waiting for ${description} after ${timeout} ms`, {cause: lastError}); +}; diff --git a/tests/integration/browser/view/render.integration.test.ts b/tests/integration/browser/view/render.integration.test.ts index d59889ca..d126f3d0 100644 --- a/tests/integration/browser/view/render.integration.test.ts +++ b/tests/integration/browser/view/render.integration.test.ts @@ -1,33 +1,21 @@ -import {mkdtemp, readFile, rm} from "fs/promises"; -import os from "os"; +import {readFile} from "fs/promises"; import path from "path"; -import {spawn, type ChildProcess} from "child_process"; -import {browserVersion, findChromeBinary} from "../utils/chrome"; +import {startBrowserSession, type BrowserSession} from "../utils/session"; import CdpClient from "../utils/CdpClient"; -import {getFreePort, stop, waitFor} from "../utils/browser"; +import {waitFor} from "../utils/browser"; import {startIntegrationSite, type IntegrationSite} from "../utils/site"; import {createIntegrationFixture, type IntegrationFixture} from "../../utils/fixture"; const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); -const chromeBinary = findChromeBinary(rootDir); jest.setTimeout(90_000); test("Chrome MV3 renders offscreen, sandbox, page and content views through the injected builders", async () => { - if (!chromeBinary || !path.isAbsolute(chromeBinary)) { - throw new Error( - "Chrome is not installed or could not be found. Install Chrome or set ADNBN_CHROME_BIN to its absolute executable path." - ); - } - - const userDataDir = await mkdtemp(path.join(os.tmpdir(), "adnbn-view-render-")); - const debuggingPort = await getFreePort(); - let chrome: ChildProcess | undefined; + let session: BrowserSession | undefined; let browser: CdpClient | undefined; let fixture: IntegrationFixture | undefined; let site: IntegrationSite | undefined; - let chromeOutput = ""; try { fixture = await createIntegrationFixture(rootDir, path.join(__dirname, "render")); @@ -39,26 +27,9 @@ test("Chrome MV3 renders offscreen, sandbox, page and content views through the expect(manifest.manifest_version).toBe(3); expect(manifest.permissions).toEqual(["offscreen"]); - chrome = spawn( - chromeBinary, - [ - "--headless=new", - "--no-sandbox", - "--no-first-run", - "--no-default-browser-check", - `--remote-debugging-port=${debuggingPort}`, - `--user-data-dir=${userDataDir}`, - "about:blank", - ], - {stdio: ["ignore", "ignore", "pipe"]} - ); - chrome.stderr?.on("data", chunk => (chromeOutput += chunk)); - - const {webSocketDebuggerUrl} = await waitFor(() => browserVersion(debuggingPort)); - browser = await CdpClient.connect(webSocketDebuggerUrl); - - const extension = await browser.send("Extensions.loadUnpacked", {path: extensionDir}); - const extensionId = extension.id as string | undefined; + session = await startBrowserSession("chrome", rootDir, extensionDir, {createPage: false}); + browser = session.chrome!; + const extensionId = session.extensionId; if (!extensionId) { throw new Error("Chrome did not return an extension ID after loading the view fixture"); @@ -97,6 +68,7 @@ test("Chrome MV3 renders offscreen, sandbox, page and content views through the }; const popup = await open(`chrome-extension://${extensionId}/popup.html`); + const result = JSON.parse( await waitFor(() => popup("document.getElementById('result')?.value || undefined"), 30_000, "view calls") ); @@ -120,6 +92,7 @@ test("Chrome MV3 renders offscreen, sandbox, page and content views through the }); const page = await open(`${site.origin}/top.html`); + const note = await waitFor(() => page(`(() => { const note = [...document.body.children].find(element => element.textContent.includes("React content text")); @@ -131,16 +104,11 @@ test("Chrome MV3 renders offscreen, sandbox, page and content views through the expect(note).toEqual({text: "<b>React content text</b>", markup: false}); expect(browser.runtimeErrors).toEqual([]); } catch (error) { - throw new Error(`${String(error)}; Chrome output: ${chromeOutput}`, {cause: error}); + throw new Error(`${String(error)}; Chrome output: ${session?.output ?? ""}`, {cause: error}); } finally { - await browser?.close(); - - if (chrome) { - await stop(chrome); - } + await session?.close(); await site?.close(); await fixture?.dispose(); - await rm(userDataDir, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); From 211adfe3deba64aef94d69c116435af2901dfaff Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:29:37 +0300 Subject: [PATCH 23/36] fix(cli): await build completion and close failed compilers Return Stats after compiler shutdown and preserve compilation and close failures. Keep one-shot build output and exit codes in the CLI; leave watch behavior unchanged. Cover real Rspack lifecycle and CLI failures with tests, fixtures, and documentation. --- jest.config.ts | 1 + src/cli/builders/app/build.test.ts | 179 ++++++++++++++++++ src/cli/builders/app/build.ts | 66 +++++++ src/cli/builders/app/index.ts | 29 ++- src/cli/builders/app/tests/fixtures/entry.js | 1 + .../app/tests/fixtures/missing-import.js | 1 + src/cli/builders/app/{command.ts => watch.ts} | 29 --- src/cli/index.test.ts | 59 +++++- src/cli/index.ts | 23 ++- src/cli/tests/fixtures/build-api.mjs | 19 ++ .../exit-code/lifecycle/adnbn.config.ts | 41 ++++ tests/integration/README.md | 11 ++ 12 files changed, 410 insertions(+), 49 deletions(-) create mode 100644 src/cli/builders/app/build.test.ts create mode 100644 src/cli/builders/app/build.ts create mode 100644 src/cli/builders/app/tests/fixtures/entry.js create mode 100644 src/cli/builders/app/tests/fixtures/missing-import.js rename src/cli/builders/app/{command.ts => watch.ts} (55%) create mode 100644 src/cli/tests/fixtures/build-api.mjs create mode 100644 src/cli/tests/fixtures/exit-code/lifecycle/adnbn.config.ts diff --git a/jest.config.ts b/jest.config.ts index ffc86026..ca393a2e 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -51,6 +51,7 @@ const shared: Config = { const buildTests = [ "<rootDir>/src/cli/bundler/plugins/**/*.test.ts", "<rootDir>/src/cli/index.test.ts", + "<rootDir>/src/cli/builders/app/**/*.test.ts", "<rootDir>/src/cli/virtual/virtual.test.ts", "<rootDir>/src/cli/plugins/content/RelayDeclaration.test.ts", "<rootDir>/src/cli/plugins/locale/declaration/LocaleDeclaration.test.ts", diff --git a/src/cli/builders/app/build.test.ts b/src/cli/builders/app/build.test.ts new file mode 100644 index 00000000..c16a8a6e --- /dev/null +++ b/src/cli/builders/app/build.test.ts @@ -0,0 +1,179 @@ +import {rspack, Stats, type Compiler} from "@rspack/core"; +import {mkdtemp, readFile, rm} from "fs/promises"; +import os from "os"; +import path from "path"; + +import {BuildError, build} from "./build"; + +const fixtures = path.join(__dirname, "tests", "fixtures"); +let output: string; + +beforeEach(async () => { + output = await mkdtemp(path.join(os.tmpdir(), "adnbn-build-lifecycle-")); +}); + +afterEach(async () => { + await rm(output, {recursive: true, force: true}); +}); + +const createCompiler = (entry = "entry.js"): Compiler => { + return rspack({ + mode: "development", + context: fixtures, + entry: path.join(fixtures, entry), + output: {path: output, filename: "bundle.js"}, + }); +}; + +// A deliberately failing shutdown hook prevents Rspack from reaching native cleanup. +// It fails once; a second close in these tests releases the real compiler after checking the error. +const closeCompiler = (compiler: Compiler): Promise<void> => { + return new Promise((resolve, reject) => { + compiler.close(error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); +}; + +test("returns the real Stats only after asynchronous compiler shutdown finishes", async () => { + const compiler = createCompiler(); + const closing = Promise.withResolvers<void>(); + const release = Promise.withResolvers<void>(); + let compilation: Stats | undefined; + let settled = false; + let closes = 0; + + compiler.hooks.done.tap("CaptureStats", stats => { + compilation = stats; + }); + compiler.hooks.shutdown.tapPromise("HoldShutdown", async () => { + closes++; + closing.resolve(); + await release.promise; + }); + + const result = build(compiler).then(stats => { + settled = true; + + return stats; + }); + + try { + await closing.promise; + expect(settled).toBe(false); + expect(compilation?.hasErrors()).toBe(false); + } finally { + release.resolve(); + } + + expect(await result).toBe(compilation); + expect(closes).toBe(1); + expect(await readFile(path.join(output, "bundle.js"), "utf8")).toContain("buildLifecycleFixture"); +}); + +test("closes a failed compilation and preserves its diagnostic Stats", async () => { + const compiler = createCompiler("missing-import.js"); + const closing = Promise.withResolvers<void>(); + const release = Promise.withResolvers<void>(); + let settled = false; + let closed = false; + + compiler.hooks.shutdown.tapPromise("ObserveShutdown", async () => { + closing.resolve(); + await release.promise; + closed = true; + }); + + const result = build(compiler).catch(error => { + settled = true; + + return error; + }); + + try { + await closing.promise; + expect(settled).toBe(false); + } finally { + release.resolve(); + } + + const error = await result; + + expect(error).toBeInstanceOf(BuildError); + expect(closed).toBe(true); + expect(error.stats).toBeInstanceOf(Stats); + expect(error.stats.hasErrors()).toBe(true); + expect(error.stats.toString({all: false, errors: true})).toContain("not-present.js"); +}); + +test("closes after a fatal plugin error and preserves the original cause", async () => { + const compiler = createCompiler(); + const failure = new Error("beforeRun failed"); + let closed = false; + + compiler.hooks.beforeRun.tap("FailBuild", () => { + throw failure; + }); + compiler.hooks.shutdown.tap("ObserveShutdown", () => { + closed = true; + }); + + await expect(build(compiler)).rejects.toMatchObject({name: "BuildError", cause: failure}); + expect(closed).toBe(true); +}); + +test("rejects a successful compilation when closing fails and retains Stats", async () => { + const compiler = createCompiler(); + const failure = new Error("shutdown failed"); + let closes = 0; + + compiler.hooks.shutdown.tap("FailFirstClose", () => { + if (++closes === 1) { + throw failure; + } + }); + + try { + const error = await build(compiler).catch(error => error); + + expect(error).toBeInstanceOf(BuildError); + expect(error.message).toBe("Rspack close error"); + expect(error.cause).toBe(failure); + expect(error.stats.hasErrors()).toBe(false); + expect(closes).toBe(1); + } finally { + await closeCompiler(compiler); + } +}); + +test("preserves both failures when compilation and shutdown fail", async () => { + const compiler = createCompiler(); + const compilationFailure = new Error("beforeRun failed"); + const closeFailure = new Error("shutdown failed"); + let closes = 0; + + compiler.hooks.beforeRun.tap("FailBuild", () => { + throw compilationFailure; + }); + compiler.hooks.shutdown.tap("FailFirstClose", () => { + if (++closes === 1) { + throw closeFailure; + } + }); + + try { + const error = await build(compiler).catch(error => error); + + expect(error).toBeInstanceOf(BuildError); + expect(error.message).toBe("Rspack compilation and close failed"); + expect(error.cause).toBeInstanceOf(AggregateError); + expect(error.cause.errors).toEqual([compilationFailure, closeFailure]); + expect(closes).toBe(1); + } finally { + await closeCompiler(compiler); + } +}); diff --git a/src/cli/builders/app/build.ts b/src/cli/builders/app/build.ts new file mode 100644 index 00000000..3ba9b6d1 --- /dev/null +++ b/src/cli/builders/app/build.ts @@ -0,0 +1,66 @@ +import type {Compiler, Stats} from "@rspack/core"; + +export class BuildError extends Error { + constructor( + message: string, + public readonly stats: Stats | undefined, + options?: ErrorOptions + ) { + super(message, options); + this.name = "BuildError"; + } +} + +/** Owns a one-shot compiler; settles only after its close callback, including failed builds. */ +export const build = async (compiler: Compiler): Promise<Stats> => { + let stats: Stats | undefined; + let message = "Rspack compilation error"; + const failures: unknown[] = []; + + try { + await new Promise<void>((resolve, reject) => { + compiler.run((error, result) => { + stats = result; + + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + + if (!stats) { + throw new Error("Rspack completed without compilation statistics"); + } + + if (stats.hasErrors()) { + throw new Error("Rspack reported compilation errors"); + } + } catch (error) { + failures.push(error); + } + + try { + await new Promise<void>((resolve, reject) => { + compiler.close(error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + } catch (error) { + message = failures.length ? "Rspack compilation and close failed" : "Rspack close error"; + failures.push(error); + } + + if (failures.length) { + throw new BuildError(message, stats, { + cause: failures.length === 1 ? failures[0] : new AggregateError(failures, message), + }); + } + + return stats!; +}; diff --git a/src/cli/builders/app/index.ts b/src/cli/builders/app/index.ts index 621c3ec9..bcb81e43 100644 --- a/src/cli/builders/app/index.ts +++ b/src/cli/builders/app/index.ts @@ -1,6 +1,7 @@ -import {rspack} from "@rspack/core"; +import {rspack, type Stats} from "@rspack/core"; -import {build, watch} from "./command"; +import {build} from "./build"; +import {watch} from "./watch"; import configResolver from "@cli/resolvers/config"; import bundlerResolver from "@cli/resolvers/bundler"; @@ -9,30 +10,28 @@ import {processPluginHandler} from "@cli/resolvers/plugin"; import {OptionalConfig, ReadonlyConfig} from "@typing/config"; import {Command} from "@typing/app"; +export {BuildError} from "./build"; + const startup = async (config: ReadonlyConfig): Promise<void> => { await Array.fromAsync(processPluginHandler(config.plugins, "startup", {config})); }; -export default async (config: OptionalConfig): Promise<void> => { +export default async (config: OptionalConfig): Promise<Stats | undefined> => { const resolverConfig = await configResolver(config); + if (resolverConfig.command !== Command.Build && resolverConfig.command !== Command.Watch) { + throw new Error("Unknown command"); + } + await startup(resolverConfig); const rspackConfig = await bundlerResolver(resolverConfig); const compiler = rspack(rspackConfig); - switch (resolverConfig.command) { - case Command.Build: - build(compiler); - break; - - case Command.Watch: - watch(compiler); - break; - - default: - console.error("Unknown command"); - process.exit(1); + if (resolverConfig.command === Command.Build) { + return await build(compiler); } + + watch(compiler); }; diff --git a/src/cli/builders/app/tests/fixtures/entry.js b/src/cli/builders/app/tests/fixtures/entry.js new file mode 100644 index 00000000..ab9655b0 --- /dev/null +++ b/src/cli/builders/app/tests/fixtures/entry.js @@ -0,0 +1 @@ +globalThis.buildLifecycleFixture = "ready"; diff --git a/src/cli/builders/app/tests/fixtures/missing-import.js b/src/cli/builders/app/tests/fixtures/missing-import.js new file mode 100644 index 00000000..bfa2dfd4 --- /dev/null +++ b/src/cli/builders/app/tests/fixtures/missing-import.js @@ -0,0 +1 @@ +import "./not-present.js"; diff --git a/src/cli/builders/app/command.ts b/src/cli/builders/app/watch.ts similarity index 55% rename from src/cli/builders/app/command.ts rename to src/cli/builders/app/watch.ts index 415eb57d..8d720810 100644 --- a/src/cli/builders/app/command.ts +++ b/src/cli/builders/app/watch.ts @@ -1,34 +1,5 @@ import {Compiler} from "@rspack/core"; -export const build = (compiler: Compiler) => { - compiler.run((err, stats) => { - if (err) { - console.error("Rspack compilation error:", err); - process.exit(1); - } - - if (stats?.hasErrors()) { - console.error( - stats.toString({ - colors: true, - errors: true, - }) - ); - - process.exit(1); - } - - console.log(stats?.toString({colors: true})); - - compiler.close(closeErr => { - if (closeErr) { - console.error("Rspack close error:", closeErr); - process.exit(1); - } - }); - }); -}; - export const watch = (compiler: Compiler) => { const watching = compiler.watch( { diff --git a/src/cli/index.test.ts b/src/cli/index.test.ts index 2cc7785d..32da2787 100644 --- a/src/cli/index.test.ts +++ b/src/cli/index.test.ts @@ -4,6 +4,7 @@ import {spawnSync} from "child_process"; import fs from "fs"; import os from "os"; import path from "path"; +import {stripVTControlCharacters} from "util"; const cli = path.resolve(__dirname, "../../bin/adnbn.js"); const fixtures = path.join(__dirname, "tests/fixtures/exit-code"); @@ -21,7 +22,7 @@ describe("CLI exit codes", () => { fs.rmSync(root, {recursive: true, force: true}); }); - const run = (command: "build" | "watch", fixture: string) => { + const run = (command: "build" | "watch", fixture: string, failure?: string) => { fs.cpSync(path.join(fixtures, fixture), root, {recursive: true}); const result = spawnSync(process.execPath, [cli, command, root, "-a", "exit-code", "-b", "chrome"], { @@ -29,6 +30,7 @@ describe("CLI exit codes", () => { encoding: "utf8", timeout: 30_000, maxBuffer: 4 * 1024 * 1024, + env: {...process.env, ADNBN_TEST_BUILD_FAILURE: failure ?? ""}, }); if (result.error) { @@ -37,7 +39,7 @@ describe("CLI exit codes", () => { expect(result.signal).toBeNull(); - return {status: result.status, output: `${result.stdout}\n${result.stderr}`}; + return {status: result.status, output: stripVTControlCharacters(`${result.stdout}\n${result.stderr}`)}; }; test("build exits with 0 and emits an extension on success", () => { @@ -72,4 +74,57 @@ describe("CLI exit codes", () => { expect(result.status).toBe(1); expect(fs.existsSync(path.join(artifact, "manifest.json"))).toBe(false); }); + + test("prints successful build statistics only after compiler shutdown", () => { + const result = run("build", "lifecycle"); + + expect(result.status).toBe(0); + expect(fs.readFileSync(path.join(artifact, "compiler-closed.txt"), "utf8")).toBe("closed"); + expect(result.output).toContain("Lifecycle fixture shutdown finished"); + expect(result.output).toContain("compiled successfully"); + expect(result.output.indexOf("Lifecycle fixture shutdown finished")).toBeLessThan( + result.output.indexOf("compiled successfully") + ); + }); + + test.each([ + {failure: "compilation", messages: ["Lifecycle fixture compilation failure"]}, + {failure: "run", messages: ["Lifecycle fixture run failure"]}, + {failure: "close", messages: ["Lifecycle fixture close failure"]}, + {failure: "both", messages: ["Lifecycle fixture run failure", "Lifecycle fixture close failure"]}, + ])("build exits with 1 after shutdown for $failure failure", ({failure, messages}) => { + const result = run("build", "lifecycle", failure); + + expect(result.status).toBe(1); + expect(fs.readFileSync(path.join(artifact, "compiler-closed.txt"), "utf8")).toBe("closed"); + + for (const message of messages) { + expect(result.output).toContain(message); + } + }); + + test("the internal app build returns Stats after shutdown without printing them", () => { + fs.cpSync(path.join(fixtures, "lifecycle"), root, {recursive: true}); + const result = spawnSync( + process.execPath, + [ + path.join(__dirname, "tests/fixtures/build-api.mjs"), + path.resolve(__dirname, "../../dist/cli/builders/app/index.js"), + root, + artifact, + ], + { + cwd: root, + encoding: "utf8", + timeout: 30_000, + env: {...process.env, ADNBN_TEST_BUILD_FAILURE: ""}, + } + ); + + expect(result.error).toBeUndefined(); + expect(result.signal).toBeNull(); + expect(result.status).toBe(0); + expect(result.stdout).toContain("Build API resolved after shutdown"); + expect(stripVTControlCharacters(result.stdout)).not.toContain("compiled successfully"); + }); }); diff --git a/src/cli/index.ts b/src/cli/index.ts index 72e7fb3e..bbea1b82 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -2,7 +2,7 @@ import cac from "cac"; import {consola} from "consola"; import fs from "fs"; -import app from "./builders/app"; +import app, {BuildError} from "./builders/app"; import {Command} from "@typing/app"; import {Browser} from "@typing/browser"; @@ -51,7 +51,7 @@ cli.command("build [root]", "Build for production") .option("--analyze", "Visualize extension bundle") .action(async (root, options) => { try { - await app({ + const stats = await app({ command: Command.Build, mode: options.mode, debug: options.debug, @@ -62,8 +62,25 @@ cli.command("build [root]", "Build for production") configFile: options.config, analyze: options.analyze, }); + + if (stats) { + console.log(stats.toString({colors: true})); + } } catch (e) { - consola.error(e); + if (e instanceof BuildError) { + console.error(e.message); + + if (e.stats) { + console.error(e.stats.toString({colors: true, errors: true})); + } + + if (e.cause !== undefined) { + console.error(e.cause); + } + } else { + consola.error(e); + } + process.exitCode = 1; } }); diff --git a/src/cli/tests/fixtures/build-api.mjs b/src/cli/tests/fixtures/build-api.mjs new file mode 100644 index 00000000..6f257b66 --- /dev/null +++ b/src/cli/tests/fixtures/build-api.mjs @@ -0,0 +1,19 @@ +import {readFile} from "node:fs/promises"; +import path from "node:path"; +import {pathToFileURL} from "node:url"; + +const [modulePath, rootDir, outputPath] = process.argv.slice(2); +const {default: app} = await import(pathToFileURL(modulePath).href); +const stats = await app({command: "build", rootDir, app: "exit-code", browser: "chrome"}); + +if (!stats || stats.hasErrors()) { + throw new Error("Expected successful build statistics"); +} + +const marker = await readFile(path.join(outputPath, "compiler-closed.txt"), "utf8"); + +if (marker !== "closed") { + throw new Error("Build resolved before compiler shutdown"); +} + +console.log("Build API resolved after shutdown"); diff --git a/src/cli/tests/fixtures/exit-code/lifecycle/adnbn.config.ts b/src/cli/tests/fixtures/exit-code/lifecycle/adnbn.config.ts new file mode 100644 index 00000000..ae3c2c35 --- /dev/null +++ b/src/cli/tests/fixtures/exit-code/lifecycle/adnbn.config.ts @@ -0,0 +1,41 @@ +import {mkdir, writeFile} from "fs/promises"; +import path from "path"; +import type {Compiler} from "@rspack/core"; + +const failure = process.env.ADNBN_TEST_BUILD_FAILURE; + +export default { + version: "1.0.0", + plugins: [ + { + name: "build-lifecycle-fixture", + bundler: { + plugins: [ + { + apply(compiler: Compiler) { + compiler.hooks.beforeRun.tap("FailRun", () => { + if (failure === "run" || failure === "both") { + throw new Error("Lifecycle fixture run failure"); + } + }); + compiler.hooks.thisCompilation.tap("FailCompilation", compilation => { + if (failure === "compilation") { + compilation.errors.push(new Error("Lifecycle fixture compilation failure")); + } + }); + compiler.hooks.shutdown.tapPromise("ObserveClose", async () => { + await mkdir(compiler.outputPath, {recursive: true}); + await writeFile(path.join(compiler.outputPath, "compiler-closed.txt"), "closed"); + console.log("Lifecycle fixture shutdown finished"); + + if (failure === "close" || failure === "both") { + throw new Error("Lifecycle fixture close failure"); + } + }); + }, + }, + ], + }, + }, + ], +}; diff --git a/tests/integration/README.md b/tests/integration/README.md index 96f4e140..1027d3d6 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -162,6 +162,17 @@ a larger timeout or one green local run does not establish stability. ## Coverage +The one-shot build lifecycle is checked with real Rspack compilers in `src/cli/builders/app/build.test.ts`. +`build` returns `Stats` only after compiler shutdown and attempts `close` after compilation errors too. +`BuildError` retains available statistics and the original cause; simultaneous compilation and close failures +are both retained in an `AggregateError` cause. A close failure is reported, not treated as successful cleanup. +The CLI owns statistics formatting, colors and the exit code. Its subprocess tests additionally verify exit codes, +shutdown before success output, and awaiting the internal app build. Watch still has a separate lifecycle and +remains covered through the CLI, including `isolation-watch`. + +This does not yet make app builds safe to run concurrently in one process: configuration resolution and user +plugins still share `process.env`. Fixture builds continue to use child processes until that contract is addressed. + - `types/content`: shared Content and adapter render types through the public source and built package APIs, callback props inference, and iframe-navigation restrictions for both define functions. - `types/view`: shared View and adapter render types through the public source and built package APIs, render and container props inference, the render contract adopted by Offscreen and Sandbox, and the rejection of Promise and plain-object render values. - `types/registries`: generated registry augmentation, empty fallbacks, public and internal type agreement, and message contracts against source and built package APIs. Compiler-host path checks cover both slash styles. From a285791378da03ddae21a96b6bde2f7355b8dc06 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Sun, 27 Sep 2026 22:50:44 +0300 Subject: [PATCH 24/36] fix(cli): load dotenv only from application and project paths --- jest.config.ts | 1 + src/cli/resolvers/config.ts | 2 +- src/cli/resolvers/dotenv.test.ts | 138 ++++++++++++++++++ .../tests/fixtures/dotenv/adnbn.config.mjs | 13 ++ .../tests/fixtures/dotenv/environment.cjs | 1 + .../tests/fixtures/dotenv/inspect.mjs | 33 +++++ tests/integration/README.md | 19 +++ 7 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 src/cli/resolvers/dotenv.test.ts create mode 100644 src/cli/resolvers/tests/fixtures/dotenv/adnbn.config.mjs create mode 100644 src/cli/resolvers/tests/fixtures/dotenv/environment.cjs create mode 100644 src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs diff --git a/jest.config.ts b/jest.config.ts index ca393a2e..a2401b88 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -51,6 +51,7 @@ const shared: Config = { const buildTests = [ "<rootDir>/src/cli/bundler/plugins/**/*.test.ts", "<rootDir>/src/cli/index.test.ts", + "<rootDir>/src/cli/resolvers/dotenv.test.ts", "<rootDir>/src/cli/builders/app/**/*.test.ts", "<rootDir>/src/cli/virtual/virtual.test.ts", "<rootDir>/src/cli/plugins/content/RelayDeclaration.test.ts", diff --git a/src/cli/resolvers/config.ts b/src/cli/resolvers/config.ts index bbc30e1e..fa7a296e 100644 --- a/src/cli/resolvers/config.ts +++ b/src/cli/resolvers/config.ts @@ -75,7 +75,7 @@ const getUserConfig = async (config: ReadonlyConfig): Promise<UserConfig> => { if (existsSync(configFilePath)) { const {config: userConfig} = await loadConfig<UserConfig>({ configFile: configFilePath, - dotenv: true, + dotenv: false, }); if (config.debug) { diff --git a/src/cli/resolvers/dotenv.test.ts b/src/cli/resolvers/dotenv.test.ts new file mode 100644 index 00000000..b1240b82 --- /dev/null +++ b/src/cli/resolvers/dotenv.test.ts @@ -0,0 +1,138 @@ +import {spawnSync} from "node:child_process"; +import {cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync} from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const fixtures = path.join(__dirname, "tests/fixtures/dotenv"); +const resolver = path.resolve(__dirname, "../../../dist/cli/resolvers/config.js"); +const filenames = [ + ".env.development.chrome.local", + ".env.development.chrome", + ".env.chrome.local", + ".env.chrome", + ".env.development.local", + ".env.development", + ".env.local", + ".env", +]; + +describe("dotenv resolution through the built config loader", () => { + let temporary: string; + let root: string; + let cwd: string; + + beforeEach(() => { + temporary = mkdtempSync(path.join(os.tmpdir(), "adnbn-dotenv-")); + root = path.join(temporary, "project"); + cwd = path.join(temporary, "caller"); + mkdirSync(root); + mkdirSync(cwd); + }); + + afterEach(() => { + rmSync(temporary, {recursive: true, force: true}); + }); + + const inspect = (env: NodeJS.ProcessEnv = {}, configFile = "missing.config.mjs") => { + const inherited = Object.fromEntries( + Object.entries(process.env).filter(([key]) => !key.startsWith("ADNBN_ENV_")) + ); + const result = spawnSync(process.execPath, [path.join(fixtures, "inspect.mjs"), resolver, root, configFile], { + cwd, + env: {...inherited, ...env}, + encoding: "utf8", + timeout: 30_000, + }); + + expect(result.error).toBeUndefined(); + expect({status: result.status, stderr: result.stderr}).toEqual({status: 0, stderr: ""}); + + return JSON.parse(result.stdout); + }; + + test("preserves all 24 file priorities, process precedence and reserved keys", () => { + const directories = [path.join(root, "src/apps/sample/source"), path.join(root, "src/apps/sample"), root]; + const expected: Record<string, string> = {}; + let rank = 0; + + for (const directory of directories) { + mkdirSync(directory, {recursive: true}); + + for (const filename of filenames) { + const values: Record<string, string> = { + ADNBN_ENV_CALLER: "file", + APP: "file", + BROWSER: "file", + MODE: "file", + MANIFEST_VERSION: "file", + }; + + for (let key = 0; key <= rank; key++) { + values[`ADNBN_ENV_PRIORITY_${key}`] = String(rank); + } + + expected[`ADNBN_ENV_PRIORITY_${rank}`] = String(rank); + writeFileSync( + path.join(directory, filename), + Object.entries(values) + .map(([key, value]) => `${key}=${value}`) + .join("\n") + ); + rank++; + } + } + + const result = inspect({ + ADNBN_ENV_CALLER: "caller", + APP: "caller", + BROWSER: "caller", + MODE: "caller", + MANIFEST_VERSION: "caller", + }); + const reserved = {APP: "sample", BROWSER: "chrome", MODE: "development", MANIFEST_VERSION: "3"}; + + expect(result.host).toMatchObject({...expected, ...reserved, ADNBN_ENV_CALLER: "caller"}); + expect(result.bundled).toMatchObject({...expected, ...reserved, ADNBN_ENV_CALLER: "file"}); + }); + + test("loads framework dotenv before config, repeats after target changes and leaves references literal", () => { + cpSync(path.join(fixtures, "adnbn.config.mjs"), path.join(root, "adnbn.config.mjs")); + writeFileSync( + path.join(root, ".env.development.chrome"), + "ADNBN_ENV_VALUE=chrome\nADNBN_ENV_REFERENCE=${ADNBN_ENV_VALUE}" + ); + writeFileSync(path.join(root, ".env.production.firefox"), "ADNBN_ENV_VALUE=firefox\nADNBN_ENV_SECOND=second"); + const result = inspect({ADNBN_ENV_SWITCH: "1"}, "adnbn.config.mjs"); + + expect(result.config).toMatchObject({ + ADNBN_ENV_VALUE: "chrome", + ADNBN_ENV_REFERENCE: "${ADNBN_ENV_VALUE}", + BROWSER: "chrome", + MODE: "development", + MANIFEST_VERSION: "3", + }); + expect(result.host).toMatchObject({ + ADNBN_ENV_VALUE: "chrome", + ADNBN_ENV_SECOND: "second", + BROWSER: "firefox", + MODE: "production", + MANIFEST_VERSION: "2", + }); + expect(result.bundled).toMatchObject({ + ADNBN_ENV_VALUE: "firefox", + ADNBN_ENV_SECOND: "second", + ADNBN_ENV_REFERENCE: "${ADNBN_ENV_VALUE}", + }); + }); + + test("ignores unrelated cwd dotenv when loading user config", () => { + cpSync(path.join(fixtures, "adnbn.config.mjs"), path.join(root, "adnbn.config.mjs")); + writeFileSync(path.join(cwd, ".env"), "ADNBN_ENV_CWD=cwd\nADNBN_ENV_CWD_REFERENCE=${ADNBN_ENV_CWD}"); + const result = inspect({}, "adnbn.config.mjs"); + + expect(result.config).not.toHaveProperty("ADNBN_ENV_CWD"); + expect(result.config).not.toHaveProperty("ADNBN_ENV_CWD_REFERENCE"); + expect(result.host).not.toHaveProperty("ADNBN_ENV_CWD"); + expect(result.bundled).not.toHaveProperty("ADNBN_ENV_CWD"); + }); +}); diff --git a/src/cli/resolvers/tests/fixtures/dotenv/adnbn.config.mjs b/src/cli/resolvers/tests/fixtures/dotenv/adnbn.config.mjs new file mode 100644 index 00000000..c8ebc166 --- /dev/null +++ b/src/cli/resolvers/tests/fixtures/dotenv/adnbn.config.mjs @@ -0,0 +1,13 @@ +process.env.ADNBN_ENV_CONFIG_SNAPSHOT = JSON.stringify( + Object.fromEntries( + Object.entries(process.env).filter( + ([key]) => key.startsWith("ADNBN_ENV_") || ["APP", "BROWSER", "MODE", "MANIFEST_VERSION"].includes(key) + ) + ) +); + +export default { + browser: process.env.ADNBN_ENV_SWITCH ? "firefox" : "chrome", + mode: process.env.ADNBN_ENV_SWITCH ? "production" : "development", + manifestVersion: process.env.ADNBN_ENV_SWITCH ? 2 : 3, +}; diff --git a/src/cli/resolvers/tests/fixtures/dotenv/environment.cjs b/src/cli/resolvers/tests/fixtures/dotenv/environment.cjs new file mode 100644 index 00000000..25f2f3cc --- /dev/null +++ b/src/cli/resolvers/tests/fixtures/dotenv/environment.cjs @@ -0,0 +1 @@ +module.exports = process.env; diff --git a/src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs b/src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs new file mode 100644 index 00000000..08fb0cf7 --- /dev/null +++ b/src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs @@ -0,0 +1,33 @@ +import path from "node:path"; +import {createRequire} from "node:module"; +import {rspack} from "@rspack/core"; +import {pathToFileURL} from "node:url"; + +const [modulePath, rootDir, configFile] = process.argv.slice(2); +const {default: resolveConfig} = await import(pathToFileURL(modulePath).href); +const config = await resolveConfig({rootDir, configFile, app: "sample", appSrcDir: "source"}); +const plugin = config.plugins.find(plugin => plugin.name === "adnbn:dotenv"); +const bundler = plugin.bundler({config}); +const {build} = await import(pathToFileURL(path.resolve(modulePath, "../../builders/app/build.js")).href); +const output = path.join(rootDir, "output"); +await build( + rspack({ + mode: "development", + target: "node", + entry: path.join(import.meta.dirname, "environment.cjs"), + output: {path: output, filename: "environment.cjs", library: {type: "commonjs2"}}, + plugins: bundler.plugins, + }) +); +const bundled = createRequire(import.meta.url)(path.join(output, "environment.cjs")); +const snapshot = process.env.ADNBN_ENV_CONFIG_SNAPSHOT; +const select = env => + Object.fromEntries( + Object.entries(env).filter( + ([key]) => key.startsWith("ADNBN_ENV_") || ["APP", "BROWSER", "MODE", "MANIFEST_VERSION"].includes(key) + ) + ); + +console.log( + JSON.stringify({host: select(process.env), bundled, config: snapshot ? select(JSON.parse(snapshot)) : null}) +); diff --git a/tests/integration/README.md b/tests/integration/README.md index 1027d3d6..80edbc0e 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -198,3 +198,22 @@ Browser execution covers the installed Chrome in MV3 and Firefox in MV2/MV3. It Keep these HTML files outside the application's `src` directory. They need no separate package or build tool. The server uses an ephemeral loopback port and stops after the test. - `browser/relay/scripting`: Chrome MV3 and Firefox MV2/MV3 check real Relay scripting success, undefined/null results, remote errors, immediate missing-manager errors and exhausted retries (#109). + +## Build environment contract + +Only the framework loads dotenv files. Within each directory it reads, in order: +`.env.<mode>.<browser>.local`, `.env.<mode>.<browser>`, `.env.<browser>.local`, `.env.<browser>`, +`.env.<mode>.local`, `.env.<mode>`, `.env.local`, `.env`. It reads all eight names in the app source +directory first, then the app directory, then the project root. The first file value wins. +Existing process variables take precedence in the host process; `APP`, `BROWSER`, `MODE`, and +`MANIFEST_VERSION` are always replaced with the current build target. + +Loading happens before the user config executes and again after its overrides are applied. On the second +pass, existing host variables remain, but newly selected file values replace earlier values for the bundled +`process.env` substitution. The bundle receives filtered file values and the four reserved values, not a copy +of the host environment. `${VAR}` references remain literal; the framework does not interpolate dotenv values. +`src/cli/resolvers/dotenv.test.ts` verifies both host values and actual Rspack substitution. + +The extra dotenv pass in `c12` is disabled. **Behavior change:** running the CLI from a directory outside the +project no longer reads that directory's `.env`, and its interpolated values no longer reach user config or +plugins. Put build variables in the app/project dotenv files or export them explicitly in the calling process. From 5c43c0ad0e0875b7e50da9e17c9dc72880e45b70 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 01:04:21 +0300 Subject: [PATCH 25/36] refactor(cli): isolate sequential internal app builds Restore the environment after one-shot builds and reject overlapping calls. Keep the pilot limited to TypeScript configs and place package-level tests in integrations. --- jest.config.ts | 2 - src/cli/builders/app/index.ts | 61 +++++- tests/integration/README.md | 41 +++- .../build/cli/build-api.integration.test.ts | 70 +++++++ .../build/cli/cli.integration.test.ts | 60 +++--- .../build/cli/dotenv.integration.test.ts | 20 +- .../cli}/fixtures/dotenv/adnbn.config.mjs | 0 .../cli}/fixtures/dotenv/environment.cjs | 0 .../cli/fixtures/environment/adnbn.config.ts | 82 ++++++++ .../exit-code/invalid-config/adnbn.config.ts | 0 .../exit-code/javascript/adnbn.config.mjs | 0 .../exit-code/lifecycle/adnbn.config.ts | 0 .../exit-code/missing-plural}/adnbn.config.ts | 0 .../missing-plural/src/locales/en.yaml | 0 .../missing-plural/src/locales/fr.yaml | 0 .../exit-code/success/adnbn.config.ts | 3 + .../exit-code/success/src/locales/en.yaml | 0 .../exit-code/success/src/locales/fr.yaml | 0 .../build/cli/scripts}/build-api.mjs | 0 .../build/cli/scripts/inspect-dotenv.mjs | 2 +- .../build/cli/scripts/verify-environment.mjs | 179 ++++++++++++++++++ 21 files changed, 460 insertions(+), 60 deletions(-) create mode 100644 tests/integration/build/cli/build-api.integration.test.ts rename src/cli/index.test.ts => tests/integration/build/cli/cli.integration.test.ts (77%) rename src/cli/resolvers/dotenv.test.ts => tests/integration/build/cli/dotenv.integration.test.ts (91%) rename {src/cli/resolvers/tests => tests/integration/build/cli}/fixtures/dotenv/adnbn.config.mjs (100%) rename {src/cli/resolvers/tests => tests/integration/build/cli}/fixtures/dotenv/environment.cjs (100%) create mode 100644 tests/integration/build/cli/fixtures/environment/adnbn.config.ts rename {src/cli/tests => tests/integration/build/cli}/fixtures/exit-code/invalid-config/adnbn.config.ts (100%) rename src/cli/tests/fixtures/exit-code/missing-plural/adnbn.config.ts => tests/integration/build/cli/fixtures/exit-code/javascript/adnbn.config.mjs (100%) rename {src/cli/tests => tests/integration/build/cli}/fixtures/exit-code/lifecycle/adnbn.config.ts (100%) rename {src/cli/tests/fixtures/exit-code/success => tests/integration/build/cli/fixtures/exit-code/missing-plural}/adnbn.config.ts (100%) rename {src/cli/tests => tests/integration/build/cli}/fixtures/exit-code/missing-plural/src/locales/en.yaml (100%) rename {src/cli/tests => tests/integration/build/cli}/fixtures/exit-code/missing-plural/src/locales/fr.yaml (100%) create mode 100644 tests/integration/build/cli/fixtures/exit-code/success/adnbn.config.ts rename {src/cli/tests => tests/integration/build/cli}/fixtures/exit-code/success/src/locales/en.yaml (100%) rename {src/cli/tests => tests/integration/build/cli}/fixtures/exit-code/success/src/locales/fr.yaml (100%) rename {src/cli/tests/fixtures => tests/integration/build/cli/scripts}/build-api.mjs (100%) rename src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs => tests/integration/build/cli/scripts/inspect-dotenv.mjs (94%) create mode 100644 tests/integration/build/cli/scripts/verify-environment.mjs diff --git a/jest.config.ts b/jest.config.ts index a2401b88..02896a3b 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -50,8 +50,6 @@ const shared: Config = { const buildTests = [ "<rootDir>/src/cli/bundler/plugins/**/*.test.ts", - "<rootDir>/src/cli/index.test.ts", - "<rootDir>/src/cli/resolvers/dotenv.test.ts", "<rootDir>/src/cli/builders/app/**/*.test.ts", "<rootDir>/src/cli/virtual/virtual.test.ts", "<rootDir>/src/cli/plugins/content/RelayDeclaration.test.ts", diff --git a/src/cli/builders/app/index.ts b/src/cli/builders/app/index.ts index bcb81e43..7dae53b5 100644 --- a/src/cli/builders/app/index.ts +++ b/src/cli/builders/app/index.ts @@ -1,4 +1,6 @@ -import {rspack, type Stats} from "@rspack/core"; +import path from "node:path"; + +import {rspack, type Compiler, type Stats} from "@rspack/core"; import {build} from "./build"; import {watch} from "./watch"; @@ -16,6 +18,57 @@ const startup = async (config: ReadonlyConfig): Promise<void> => { await Array.fromAsync(processPluginHandler(config.plugins, "startup", {config})); }; +export type AppBuildConfig = Omit<OptionalConfig, "command">; + +let buildActive = false; + +const createCompiler = async (config: ReadonlyConfig): Promise<Compiler> => { + await startup(config); + + const rspackConfig = await bundlerResolver(config); + + return rspack(rspackConfig); +}; + +/** Sequential one-shot builds only; user config and plugins temporarily share the host environment. */ +export const buildApp = async (config: AppBuildConfig): Promise<Stats> => { + if (buildActive) { + throw new Error("An app build is already running in this process; await it before starting another"); + } + + const configFile = config.configFile ?? "adnbn.config.ts"; + + if (![".ts", ".mts", ".cts"].includes(path.extname(configFile))) { + throw new Error( + "buildApp requires a TypeScript config (.ts, .mts or .cts); use the CLI for other config formats" + ); + } + + const environment = process.env; + const snapshot = {...environment}; + buildActive = true; + + try { + const resolved = await configResolver({...config, command: Command.Build}); + + if (resolved.command !== Command.Build) { + throw new Error("buildApp only supports the build command"); + } + + return await build(await createCompiler(resolved)); + } finally { + for (const key of Object.keys(environment)) { + if (!Object.hasOwn(snapshot, key)) { + delete environment[key]; + } + } + + Object.assign(environment, snapshot); + process.env = environment; + buildActive = false; + } +}; + export default async (config: OptionalConfig): Promise<Stats | undefined> => { const resolverConfig = await configResolver(config); @@ -23,11 +76,7 @@ export default async (config: OptionalConfig): Promise<Stats | undefined> => { throw new Error("Unknown command"); } - await startup(resolverConfig); - - const rspackConfig = await bundlerResolver(resolverConfig); - - const compiler = rspack(rspackConfig); + const compiler = await createCompiler(resolverConfig); if (resolverConfig.command === Command.Build) { return await build(compiler); diff --git a/tests/integration/README.md b/tests/integration/README.md index 80edbc0e..60088cc0 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -1,6 +1,6 @@ # Integration tests -These tests cover application builds, browser execution, and generated TypeScript contracts. Build checks through the public CLI live in `build`; tests that launch Chrome or Firefox live in `browser`; declaration and consumer type checks live in `types`. Each area keeps its tests beside their fixtures. +These tests cover application builds, browser execution, and generated TypeScript contracts. Build checks through the CLI and the built package live in `build`; tests that launch Chrome or Firefox live in `browser`; declaration and consumer type checks live in `types`. Each area keeps its tests beside their fixtures. ## Layout @@ -9,6 +9,15 @@ Selected scenario files and shared infrastructure: ```text tests/integration/ ├── build/ +│ ├── cli/ +│ │ ├── cli.integration.test.ts +│ │ ├── build-api.integration.test.ts +│ │ ├── dotenv.integration.test.ts +│ │ ├── fixtures/ +│ │ │ ├── exit-code/ +│ │ │ ├── environment/ +│ │ │ └── dotenv/ +│ │ └── scripts/ │ ├── locale/ │ │ ├── locale.integration.test.ts │ │ ├── dynamic.integration.test.ts @@ -166,12 +175,32 @@ The one-shot build lifecycle is checked with real Rspack compilers in `src/cli/b `build` returns `Stats` only after compiler shutdown and attempts `close` after compilation errors too. `BuildError` retains available statistics and the original cause; simultaneous compilation and close failures are both retained in an `AggregateError` cause. A close failure is reported, not treated as successful cleanup. -The CLI owns statistics formatting, colors and the exit code. Its subprocess tests additionally verify exit codes, -shutdown before success output, and awaiting the internal app build. Watch still has a separate lifecycle and +The CLI owns statistics formatting, colors and the exit code. `build/cli/cli.integration.test.ts` launches the +real CLI to check exit codes, diagnostics and shutdown before success output. `build-api.integration.test.ts` +checks the built app API, including awaiting shutdown without printing statistics. Their application fixtures +live in `build/cli/fixtures`; Node subprocess drivers live in `build/cli/scripts`. These tests deliberately load +`dist` to exercise Node's real module/config loader. The source-level `build.test.ts` above imports `./build` +directly and has no dependency on `dist`. Watch still has a separate lifecycle and remains covered through the CLI, including `isolation-watch`. -This does not yet make app builds safe to run concurrently in one process: configuration resolution and user -plugins still share `process.env`. Fixture builds continue to use child processes until that contract is addressed. +The internal `buildApp(config): Promise<Stats>` entry in `src/cli/builders/app/index.ts` is a sequential +one-shot pilot. It snapshots the existing `process.env` object before config resolution and restores its values +and identity in `finally`, including added, changed and deleted keys. Config, startup, bundler, compilation and +shutdown hooks see the build environment until compiler shutdown settles. A second `buildApp` call rejects +before reading config or modifying the active build's environment, including while shutdown is pending. + +This is a transitional environment transaction, not process isolation. Do not mix it with watch, direct calls +to the config resolver, or unrelated asynchronous work that writes `process.env`. Plugins must await their own +work; detached tasks, module caches and other global state are not restored. The CLI and integration fixtures +continue to use their existing process boundary; watch is outside the pilot. + +`build/cli/build-api.integration.test.ts` exercises real builds with TypeScript configs: A → B → A after a dotenv change, observations +inside config and plugin hooks, concurrent-call rejection at startup and shutdown, and recovery after config, +startup, bundler, run, compilation and close failures. The pilot accepts only `.ts`, `.mts` and `.cts` configs; +other formats reject before config execution. Native JS configs may retain evaluated exports in Node's module +cache across builds; environment restoration alone does not refresh them. Use the ordinary CLI for those +projects. This restriction belongs only to `buildApp`, not the CLI. Repeated builds are checked for all three +TypeScript extensions; imported native modules still retain their normal Node cache semantics. - `types/content`: shared Content and adapter render types through the public source and built package APIs, callback props inference, and iframe-navigation restrictions for both define functions. - `types/view`: shared View and adapter render types through the public source and built package APIs, render and container props inference, the render contract adopted by Offscreen and Sandbox, and the rejection of Promise and plain-object render values. @@ -212,7 +241,7 @@ Loading happens before the user config executes and again after its overrides ar pass, existing host variables remain, but newly selected file values replace earlier values for the bundled `process.env` substitution. The bundle receives filtered file values and the four reserved values, not a copy of the host environment. `${VAR}` references remain literal; the framework does not interpolate dotenv values. -`src/cli/resolvers/dotenv.test.ts` verifies both host values and actual Rspack substitution. +`build/cli/dotenv.integration.test.ts` verifies both host values and actual Rspack substitution. The extra dotenv pass in `c12` is disabled. **Behavior change:** running the CLI from a directory outside the project no longer reads that directory's `.env`, and its interpolated values no longer reach user config or diff --git a/tests/integration/build/cli/build-api.integration.test.ts b/tests/integration/build/cli/build-api.integration.test.ts new file mode 100644 index 00000000..e22c5a3d --- /dev/null +++ b/tests/integration/build/cli/build-api.integration.test.ts @@ -0,0 +1,70 @@ +import {spawnSync} from "node:child_process"; +import {cpSync, mkdtempSync, rmSync} from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import {stripVTControlCharacters} from "node:util"; + +const scripts = path.join(__dirname, "scripts"); +const appModule = path.resolve(__dirname, "../../../../dist/cli/builders/app/index.js"); + +test.each([ + "sequence", + "sequence-mts", + "sequence-cts", + "unsupported-js", + "unsupported-mjs", + "unsupported-cjs", + "overlap", + "overlap-close", + "config", + "startup", + "bundler", + "run", + "compilation", + "close", +])("buildApp restores environment and permits the next build after %s", scenario => { + const root = mkdtempSync(path.join(os.tmpdir(), "adnbn-app-environment-")); + const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("ADNBN_BUILD_"))); + + try { + const result = spawnSync( + process.execPath, + [path.join(scripts, "verify-environment.mjs"), appModule, root, scenario], + { + env, + encoding: "utf8", + timeout: 30_000, + maxBuffer: 4 * 1024 * 1024, + } + ); + + expect(result.error).toBeUndefined(); + expect({status: result.status, stderr: result.stderr}).toEqual({status: 0, stderr: ""}); + expect(result.stdout).toContain("Environment contract verified"); + } finally { + rmSync(root, {recursive: true, force: true}); + } +}); + +test("the internal app build returns Stats after shutdown without printing them", () => { + const root = mkdtempSync(path.join(os.tmpdir(), "adnbn-app-lifecycle-")); + const artifact = path.join(root, "dist/exit-code-chrome-mv3"); + + try { + cpSync(path.join(__dirname, "fixtures/exit-code/lifecycle"), root, {recursive: true}); + const result = spawnSync(process.execPath, [path.join(scripts, "build-api.mjs"), appModule, root, artifact], { + cwd: root, + encoding: "utf8", + timeout: 30_000, + env: {...process.env, ADNBN_TEST_BUILD_FAILURE: ""}, + }); + + expect(result.error).toBeUndefined(); + expect(result.signal).toBeNull(); + expect(result.status).toBe(0); + expect(result.stdout).toContain("Build API resolved after shutdown"); + expect(stripVTControlCharacters(result.stdout)).not.toContain("compiled successfully"); + } finally { + rmSync(root, {recursive: true, force: true}); + } +}); diff --git a/src/cli/index.test.ts b/tests/integration/build/cli/cli.integration.test.ts similarity index 77% rename from src/cli/index.test.ts rename to tests/integration/build/cli/cli.integration.test.ts index 32da2787..018f3723 100644 --- a/src/cli/index.test.ts +++ b/tests/integration/build/cli/cli.integration.test.ts @@ -1,13 +1,11 @@ -/** @jest-environment node */ - import {spawnSync} from "child_process"; import fs from "fs"; import os from "os"; import path from "path"; import {stripVTControlCharacters} from "util"; -const cli = path.resolve(__dirname, "../../bin/adnbn.js"); -const fixtures = path.join(__dirname, "tests/fixtures/exit-code"); +const cli = path.resolve(__dirname, "../../../../bin/adnbn.js"); +const fixtures = path.join(__dirname, "fixtures/exit-code"); describe("CLI exit codes", () => { let root: string; @@ -22,16 +20,20 @@ describe("CLI exit codes", () => { fs.rmSync(root, {recursive: true, force: true}); }); - const run = (command: "build" | "watch", fixture: string, failure?: string) => { + const run = (command: "build" | "watch", fixture: string, failure?: string, configFile = "adnbn.config.ts") => { fs.cpSync(path.join(fixtures, fixture), root, {recursive: true}); - const result = spawnSync(process.execPath, [cli, command, root, "-a", "exit-code", "-b", "chrome"], { - cwd: root, - encoding: "utf8", - timeout: 30_000, - maxBuffer: 4 * 1024 * 1024, - env: {...process.env, ADNBN_TEST_BUILD_FAILURE: failure ?? ""}, - }); + const result = spawnSync( + process.execPath, + [cli, command, root, "-a", "exit-code", "-b", "chrome", "--config", configFile], + { + cwd: root, + encoding: "utf8", + timeout: 30_000, + maxBuffer: 4 * 1024 * 1024, + env: {...process.env, ADNBN_TEST_BUILD_FAILURE: failure ?? ""}, + } + ); if (result.error) { throw result.error; @@ -57,6 +59,15 @@ describe("CLI exit codes", () => { }); }); + test("keeps native JavaScript configs supported by the ordinary CLI", () => { + const result = run("build", "javascript", undefined, "adnbn.config.mjs"); + + expect(result.status).toBe(0); + expect(JSON.parse(fs.readFileSync(path.join(artifact, "manifest.json"), "utf8"))).toMatchObject({ + version: "1.0.0", + }); + }); + test.each(["build", "watch"] as const)("%s exits with 1 when a plural key is missing at startup", command => { const result = run(command, "missing-plural"); @@ -102,29 +113,4 @@ describe("CLI exit codes", () => { expect(result.output).toContain(message); } }); - - test("the internal app build returns Stats after shutdown without printing them", () => { - fs.cpSync(path.join(fixtures, "lifecycle"), root, {recursive: true}); - const result = spawnSync( - process.execPath, - [ - path.join(__dirname, "tests/fixtures/build-api.mjs"), - path.resolve(__dirname, "../../dist/cli/builders/app/index.js"), - root, - artifact, - ], - { - cwd: root, - encoding: "utf8", - timeout: 30_000, - env: {...process.env, ADNBN_TEST_BUILD_FAILURE: ""}, - } - ); - - expect(result.error).toBeUndefined(); - expect(result.signal).toBeNull(); - expect(result.status).toBe(0); - expect(result.stdout).toContain("Build API resolved after shutdown"); - expect(stripVTControlCharacters(result.stdout)).not.toContain("compiled successfully"); - }); }); diff --git a/src/cli/resolvers/dotenv.test.ts b/tests/integration/build/cli/dotenv.integration.test.ts similarity index 91% rename from src/cli/resolvers/dotenv.test.ts rename to tests/integration/build/cli/dotenv.integration.test.ts index b1240b82..18a0373b 100644 --- a/src/cli/resolvers/dotenv.test.ts +++ b/tests/integration/build/cli/dotenv.integration.test.ts @@ -3,8 +3,8 @@ import {cpSync, mkdirSync, mkdtempSync, rmSync, writeFileSync} from "node:fs"; import os from "node:os"; import path from "node:path"; -const fixtures = path.join(__dirname, "tests/fixtures/dotenv"); -const resolver = path.resolve(__dirname, "../../../dist/cli/resolvers/config.js"); +const fixtures = path.join(__dirname, "fixtures/dotenv"); +const resolver = path.resolve(__dirname, "../../../../dist/cli/resolvers/config.js"); const filenames = [ ".env.development.chrome.local", ".env.development.chrome", @@ -37,12 +37,16 @@ describe("dotenv resolution through the built config loader", () => { const inherited = Object.fromEntries( Object.entries(process.env).filter(([key]) => !key.startsWith("ADNBN_ENV_")) ); - const result = spawnSync(process.execPath, [path.join(fixtures, "inspect.mjs"), resolver, root, configFile], { - cwd, - env: {...inherited, ...env}, - encoding: "utf8", - timeout: 30_000, - }); + const result = spawnSync( + process.execPath, + [path.join(__dirname, "scripts/inspect-dotenv.mjs"), resolver, root, configFile], + { + cwd, + env: {...inherited, ...env}, + encoding: "utf8", + timeout: 30_000, + } + ); expect(result.error).toBeUndefined(); expect({status: result.status, stderr: result.stderr}).toEqual({status: 0, stderr: ""}); diff --git a/src/cli/resolvers/tests/fixtures/dotenv/adnbn.config.mjs b/tests/integration/build/cli/fixtures/dotenv/adnbn.config.mjs similarity index 100% rename from src/cli/resolvers/tests/fixtures/dotenv/adnbn.config.mjs rename to tests/integration/build/cli/fixtures/dotenv/adnbn.config.mjs diff --git a/src/cli/resolvers/tests/fixtures/dotenv/environment.cjs b/tests/integration/build/cli/fixtures/dotenv/environment.cjs similarity index 100% rename from src/cli/resolvers/tests/fixtures/dotenv/environment.cjs rename to tests/integration/build/cli/fixtures/dotenv/environment.cjs diff --git a/tests/integration/build/cli/fixtures/environment/adnbn.config.ts b/tests/integration/build/cli/fixtures/environment/adnbn.config.ts new file mode 100644 index 00000000..e5c5bd25 --- /dev/null +++ b/tests/integration/build/cli/fixtures/environment/adnbn.config.ts @@ -0,0 +1,82 @@ +import {appendFileSync} from "node:fs"; +import path from "node:path"; + +const failure = process.env.ADNBN_BUILD_FAILURE; +const record = stage => { + appendFileSync( + path.join(import.meta.dirname, "observations.jsonl"), + JSON.stringify({ + stage, + value: process.env.ADNBN_BUILD_VALUE, + onlyA: process.env.ADNBN_BUILD_ONLY_A, + browser: process.env.BROWSER, + mode: process.env.MODE, + manifestVersion: process.env.MANIFEST_VERSION, + }) + "\n" + ); +}; + +record("config"); +process.env.ADNBN_BUILD_ADDED = "from-config"; +process.env.ADNBN_BUILD_CHANGED = "from-config"; +delete process.env.ADNBN_BUILD_DELETED; + +if (failure === "config") { + throw new Error("Environment fixture config failure"); +} + +export default { + name: process.env.ADNBN_BUILD_VALUE, + version: "1.0.0", + plugins: [ + { + name: "environment-fixture", + startup() { + record("startup"); + + if (failure === "startup") { + throw new Error("Environment fixture startup failure"); + } + }, + bundler() { + record("bundler"); + + if (failure === "bundler") { + throw new Error("Environment fixture bundler failure"); + } + + return { + plugins: [ + { + apply(compiler) { + compiler.hooks.beforeRun.tap("EnvironmentFixture", () => { + record("run"); + + if (failure === "run") { + throw new Error("Environment fixture run failure"); + } + }); + + compiler.hooks.thisCompilation.tap("EnvironmentFixture", compilation => { + if (failure === "compilation") { + compilation.errors.push(new Error("Environment fixture compilation failure")); + } + }); + + let closes = 0; + + compiler.hooks.shutdown.tap("EnvironmentFixture", () => { + record("close"); + + if (failure === "close" && ++closes === 1) { + throw new Error("Environment fixture close failure"); + } + }); + }, + }, + ], + }; + }, + }, + ], +}; diff --git a/src/cli/tests/fixtures/exit-code/invalid-config/adnbn.config.ts b/tests/integration/build/cli/fixtures/exit-code/invalid-config/adnbn.config.ts similarity index 100% rename from src/cli/tests/fixtures/exit-code/invalid-config/adnbn.config.ts rename to tests/integration/build/cli/fixtures/exit-code/invalid-config/adnbn.config.ts diff --git a/src/cli/tests/fixtures/exit-code/missing-plural/adnbn.config.ts b/tests/integration/build/cli/fixtures/exit-code/javascript/adnbn.config.mjs similarity index 100% rename from src/cli/tests/fixtures/exit-code/missing-plural/adnbn.config.ts rename to tests/integration/build/cli/fixtures/exit-code/javascript/adnbn.config.mjs diff --git a/src/cli/tests/fixtures/exit-code/lifecycle/adnbn.config.ts b/tests/integration/build/cli/fixtures/exit-code/lifecycle/adnbn.config.ts similarity index 100% rename from src/cli/tests/fixtures/exit-code/lifecycle/adnbn.config.ts rename to tests/integration/build/cli/fixtures/exit-code/lifecycle/adnbn.config.ts diff --git a/src/cli/tests/fixtures/exit-code/success/adnbn.config.ts b/tests/integration/build/cli/fixtures/exit-code/missing-plural/adnbn.config.ts similarity index 100% rename from src/cli/tests/fixtures/exit-code/success/adnbn.config.ts rename to tests/integration/build/cli/fixtures/exit-code/missing-plural/adnbn.config.ts diff --git a/src/cli/tests/fixtures/exit-code/missing-plural/src/locales/en.yaml b/tests/integration/build/cli/fixtures/exit-code/missing-plural/src/locales/en.yaml similarity index 100% rename from src/cli/tests/fixtures/exit-code/missing-plural/src/locales/en.yaml rename to tests/integration/build/cli/fixtures/exit-code/missing-plural/src/locales/en.yaml diff --git a/src/cli/tests/fixtures/exit-code/missing-plural/src/locales/fr.yaml b/tests/integration/build/cli/fixtures/exit-code/missing-plural/src/locales/fr.yaml similarity index 100% rename from src/cli/tests/fixtures/exit-code/missing-plural/src/locales/fr.yaml rename to tests/integration/build/cli/fixtures/exit-code/missing-plural/src/locales/fr.yaml diff --git a/tests/integration/build/cli/fixtures/exit-code/success/adnbn.config.ts b/tests/integration/build/cli/fixtures/exit-code/success/adnbn.config.ts new file mode 100644 index 00000000..626d6c0d --- /dev/null +++ b/tests/integration/build/cli/fixtures/exit-code/success/adnbn.config.ts @@ -0,0 +1,3 @@ +export default { + version: "1.0.0", +}; diff --git a/src/cli/tests/fixtures/exit-code/success/src/locales/en.yaml b/tests/integration/build/cli/fixtures/exit-code/success/src/locales/en.yaml similarity index 100% rename from src/cli/tests/fixtures/exit-code/success/src/locales/en.yaml rename to tests/integration/build/cli/fixtures/exit-code/success/src/locales/en.yaml diff --git a/src/cli/tests/fixtures/exit-code/success/src/locales/fr.yaml b/tests/integration/build/cli/fixtures/exit-code/success/src/locales/fr.yaml similarity index 100% rename from src/cli/tests/fixtures/exit-code/success/src/locales/fr.yaml rename to tests/integration/build/cli/fixtures/exit-code/success/src/locales/fr.yaml diff --git a/src/cli/tests/fixtures/build-api.mjs b/tests/integration/build/cli/scripts/build-api.mjs similarity index 100% rename from src/cli/tests/fixtures/build-api.mjs rename to tests/integration/build/cli/scripts/build-api.mjs diff --git a/src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs b/tests/integration/build/cli/scripts/inspect-dotenv.mjs similarity index 94% rename from src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs rename to tests/integration/build/cli/scripts/inspect-dotenv.mjs index 08fb0cf7..f0307e59 100644 --- a/src/cli/resolvers/tests/fixtures/dotenv/inspect.mjs +++ b/tests/integration/build/cli/scripts/inspect-dotenv.mjs @@ -14,7 +14,7 @@ await build( rspack({ mode: "development", target: "node", - entry: path.join(import.meta.dirname, "environment.cjs"), + entry: path.join(import.meta.dirname, "../fixtures/dotenv/environment.cjs"), output: {path: output, filename: "environment.cjs", library: {type: "commonjs2"}}, plugins: bundler.plugins, }) diff --git a/tests/integration/build/cli/scripts/verify-environment.mjs b/tests/integration/build/cli/scripts/verify-environment.mjs new file mode 100644 index 00000000..de82160b --- /dev/null +++ b/tests/integration/build/cli/scripts/verify-environment.mjs @@ -0,0 +1,179 @@ +import assert from "node:assert/strict"; +import {cp, mkdir, readFile, writeFile} from "node:fs/promises"; +import path from "node:path"; +import {pathToFileURL} from "node:url"; + +const [modulePath, directory, scenario] = process.argv.slice(2); +const {buildApp} = await import(pathToFileURL(modulePath).href); +const configFile = + scenario === "sequence-mts" + ? "adnbn.config.mts" + : scenario === "sequence-cts" + ? "adnbn.config.cts" + : "adnbn.config.ts"; +const rootA = path.join(directory, "a"); +const rootB = path.join(directory, "b"); + +for (const root of [rootA, rootB]) { + await mkdir(root); + await cp(path.join(import.meta.dirname, "../fixtures/environment/adnbn.config.ts"), path.join(root, configFile)); +} + +await writeFile(path.join(rootA, ".env"), "ADNBN_BUILD_VALUE=A\nADNBN_BUILD_ONLY_A=only-a"); +await writeFile(path.join(rootB, ".env"), "ADNBN_BUILD_VALUE=B"); +process.env.ADNBN_BUILD_CHANGED = "caller"; +process.env.ADNBN_BUILD_DELETED = "caller"; +const environment = process.env; +const snapshot = {...environment}; +const options = rootDir => ({rootDir, configFile, app: "environment"}); +const restored = () => { + assert.equal(process.env, environment); + assert.deepEqual({...process.env}, snapshot); +}; +const verifyBuild = async (root, browser, manifestVersion, expected) => { + const stats = await buildApp({...options(root), browser, manifestVersion}); + assert.equal(stats.hasErrors(), false); + restored(); + const manifest = JSON.parse( + await readFile(path.join(root, `dist/environment-${browser}-mv${manifestVersion}/manifest.json`), "utf8") + ); + assert.equal(manifest.name, expected); + assert.equal(manifest.manifest_version, manifestVersion); +}; + +if (scenario.startsWith("sequence")) { + await verifyBuild(rootA, "chrome", 3, "A"); + await verifyBuild(rootB, "firefox", 2, "B"); + await writeFile(path.join(rootA, ".env"), "ADNBN_BUILD_VALUE=A-again\nADNBN_BUILD_ONLY_A=only-a"); + await verifyBuild(rootA, "chrome", 3, "A-again"); + + for (const [root, expected, browser, manifestVersion] of [ + [rootA, ["A", "A-again"], "chrome", "3"], + [rootB, ["B"], "firefox", "2"], + ]) { + const records = (await readFile(path.join(root, "observations.jsonl"), "utf8")) + .trim() + .split("\n") + .map(JSON.parse); + assert.deepEqual( + records.map(record => record.stage), + expected.flatMap(() => ["config", "startup", "bundler", "run", "close"]) + ); + + for (const [index, value] of expected.entries()) { + for (const record of records.slice(index * 5, (index + 1) * 5)) { + assert.equal(record.value, value); + assert.equal(record.browser, browser); + assert.equal(record.manifestVersion, manifestVersion); + assert.equal(record.onlyA, root === rootA ? "only-a" : undefined); + } + } + } +} else if (scenario.startsWith("unsupported-")) { + const filename = `adnbn.config.${scenario.slice("unsupported-".length)}`; + await cp(path.join(import.meta.dirname, "../fixtures/environment/adnbn.config.ts"), path.join(rootA, filename)); + await assert.rejects(buildApp({...options(rootA), configFile: filename}), /requires a TypeScript config/); + restored(); + await assert.rejects(readFile(path.join(rootA, "observations.jsonl")), {code: "ENOENT"}); + await verifyBuild(rootB, "firefox", 2, "B"); +} else if (scenario.startsWith("overlap")) { + const entered = Promise.withResolvers(); + const release = Promise.withResolvers(); + const running = buildApp({ + ...options(rootA), + plugins: [ + { + name: "hold-startup", + async startup() { + if (scenario === "overlap") { + entered.resolve(); + await release.promise; + } + }, + bundler: { + plugins: [ + { + apply(compiler) { + if (scenario === "overlap-close") { + compiler.hooks.shutdown.tapPromise("HoldShutdown", async () => { + entered.resolve(); + await release.promise; + }); + } + }, + }, + ], + }, + }, + ], + }); + + try { + await entered.promise; + const active = {...process.env}; + await assert.rejects(buildApp(options(rootB)), /already running/); + assert.deepEqual({...process.env}, active); + } finally { + release.resolve(); + await running; + } + + restored(); + await verifyBuild(rootB, "firefox", 2, "B"); +} else { + process.env.ADNBN_BUILD_FAILURE = scenario; + const failingSnapshot = {...process.env}; + let compiler; + + try { + await assert.rejects( + buildApp({ + ...options(rootA), + plugins: [ + { + name: "capture-compiler", + bundler: { + plugins: [ + { + apply(current) { + compiler = current; + }, + }, + ], + }, + }, + ], + }), + error => { + const diagnostic = [ + error.message, + error.cause?.message, + error.stats?.toString({all: false, errors: true}), + ].join("\n"); + assert.match(diagnostic, new RegExp(`Environment fixture ${scenario} failure`)); + + return true; + } + ); + assert.deepEqual({...process.env}, failingSnapshot); + } finally { + // A failing shutdown hook interrupts native cleanup; release the real compiler after checking restoration. + if (scenario === "close" && compiler) { + await new Promise((resolve, reject) => + compiler.close(error => { + if (error) { + reject(error); + } else { + resolve(); + } + }) + ); + } + } + + delete process.env.ADNBN_BUILD_FAILURE; + restored(); + await verifyBuild(rootB, "firefox", 2, "B"); +} + +console.log("Environment contract verified"); From ed60c4a25aac297487bf2fb79b2d0c68a4329ead Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:14:04 +0300 Subject: [PATCH 26/36] fix(cli): resolve builds from the project root Preserve absolute entrypoint imports and resolve source aliases, compiler context and virtual-module package dependencies from the consumer root. Use portable virtual names and separate temporary directories per project; prepare them recursively to avoid creation and cleanup races across workers. Resolve the offscreen background entry through its direct virtual alias. Cover CLI and buildApp from an unrelated cwd with Chrome/Firefox builds, relative imports, aliases, A-B-A state restoration and controlled overlap. --- .../plugins/entrypoint/EntrypointPlugin.ts | 18 ++- src/cli/bundler/utils/index.ts | 1 + src/cli/bundler/utils/virtual-module.ts | 15 +++ src/cli/plugins/offscreen/index.ts | 10 +- .../plugins/typescript/TypescriptConfig.ts | 4 +- src/cli/resolvers/bundler.ts | 8 ++ src/cli/utils/path.test.ts | 18 +++ src/cli/utils/path.ts | 2 +- tests/integration/README.md | 16 ++- .../build/cli/fixtures/root-dir-b/title.ts | 1 + .../build/cli/fixtures/root-dir-b/values.ts | 2 + .../cli/fixtures/root-dir/adnbn.config.ts | 6 + .../cli/fixtures/root-dir/src/background.ts | 7 ++ .../build/cli/fixtures/root-dir/src/newtab.ts | 10 ++ .../cli/fixtures/root-dir/src/offscreen.ts | 12 ++ .../build/cli/fixtures/root-dir/src/title.ts | 1 + .../build/cli/fixtures/root-dir/src/values.ts | 2 + .../build/cli/fixtures/root-dir/tsconfig.json | 3 + .../build/cli/root-dir.integration.test.ts | 88 ++++++++++++++ .../build/cli/scripts/verify-root-dir.mjs | 115 ++++++++++++++++++ 20 files changed, 329 insertions(+), 10 deletions(-) create mode 100644 src/cli/bundler/utils/virtual-module.ts create mode 100644 src/cli/utils/path.test.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir-b/title.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir-b/values.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir/adnbn.config.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir/src/background.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir/src/newtab.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir/src/offscreen.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir/src/title.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir/src/values.ts create mode 100644 tests/integration/build/cli/fixtures/root-dir/tsconfig.json create mode 100644 tests/integration/build/cli/root-dir.integration.test.ts create mode 100644 tests/integration/build/cli/scripts/verify-root-dir.mjs diff --git a/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts b/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts index b65bacf8..d930a712 100644 --- a/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts +++ b/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts @@ -1,8 +1,10 @@ import _ from "lodash"; import path from "path"; +import {createHash} from "node:crypto"; import {Compiler, DynamicEntryPlugin, type EntryDescription, EntryNormalized} from "@rspack/core"; import {RspackVirtualModulePlugin as VirtualModulesPlugin} from "rspack-plugin-virtual-module"; +import {prepareVirtualModuleDirectory} from "../../utils/virtual-module"; import {EntrypointEntries, EntrypointFile} from "@typing/entrypoint"; @@ -35,6 +37,7 @@ export default class EntrypointPlugin { private _plugin?: VirtualModulesPlugin; private _modules?: EntrypointPluginEntryModules; + private context?: string; private readonly _entryOptions: EntrypointPluginEntryOptionsResolver[] = []; protected template?: EntrypointPluginTemplate; @@ -46,6 +49,11 @@ export default class EntrypointPlugin { if (file.external) { const {ext} = path.parse(name); name = file.import + ext; + } else if (path.isAbsolute(name)) { + // A drive or UNC root cannot be embedded inside the virtual directory. + // Keep distinct absolute files distinct without carrying their root into it. + const key = createHash("sha256").update(name).digest("hex"); + name = path.join(key, path.basename(name)); } return path.join("virtual", name); @@ -58,7 +66,14 @@ export default class EntrypointPlugin { const modules = Object.fromEntries(this.getModuleContents(this.modules)); - return (this._plugin = new VirtualModulesPlugin(modules, "entrypoint")); + if (!this.context) { + throw new Error("EntrypointPlugin must be applied before writing virtual modules"); + } + + return (this._plugin = new VirtualModulesPlugin( + modules, + prepareVirtualModuleDirectory(this.context, "entrypoint") + )); } protected get modules(): EntrypointPluginEntryModules { @@ -99,6 +114,7 @@ export default class EntrypointPlugin { } public apply(compiler: Compiler): void { + this.context = compiler.context; this.plugin.apply(compiler); compiler.hooks.entryOption.tap(this.pluginName, (_, entry) => { diff --git a/src/cli/bundler/utils/index.ts b/src/cli/bundler/utils/index.ts index bae2efef..bd4de9b9 100644 --- a/src/cli/bundler/utils/index.ts +++ b/src/cli/bundler/utils/index.ts @@ -1,3 +1,4 @@ export * from "./optimization"; export * from "./app-filename"; export * from "./assets"; +export {prepareVirtualModuleDirectory} from "./virtual-module"; diff --git a/src/cli/bundler/utils/virtual-module.ts b/src/cli/bundler/utils/virtual-module.ts new file mode 100644 index 00000000..86ac2298 --- /dev/null +++ b/src/cli/bundler/utils/virtual-module.ts @@ -0,0 +1,15 @@ +import {createHash} from "node:crypto"; +import {mkdirSync} from "node:fs"; +import path from "node:path"; + +// The external virtual-module plugin stores files under cwd/node_modules and removes +// the entire directory on close. Independent project builds must not share it. +export const prepareVirtualModuleDirectory = (context: string, namespace: string): string => { + const key = createHash("sha256").update(path.resolve(context)).digest("hex"); + const directory = `${namespace}-${key}`; + + // Its existsSync + mkdirSync sequence races when workers share a fresh cwd. + mkdirSync(path.resolve("node_modules", directory), {recursive: true}); + + return directory; +}; diff --git a/src/cli/plugins/offscreen/index.ts b/src/cli/plugins/offscreen/index.ts index db6354f9..a581f0db 100644 --- a/src/cli/plugins/offscreen/index.ts +++ b/src/cli/plugins/offscreen/index.ts @@ -1,11 +1,10 @@ -import path from "path"; import {Configuration as RspackConfig, HtmlRspackPlugin, Plugins} from "@rspack/core"; import {RspackVirtualModulePlugin} from "rspack-plugin-virtual-module"; import HtmlRspackTagsPlugin from "html-rspack-tags-plugin"; import {definePlugin} from "@main/plugin"; -import {EntrypointPlugin, GenerateModulePlugin} from "@cli/bundler"; +import {EntrypointPlugin, GenerateModulePlugin, prepareVirtualModuleDirectory} from "@cli/bundler"; import {virtualOffscreenBackgroundModule} from "@cli/virtual"; import Offscreen from "./Offscreen"; @@ -17,7 +16,6 @@ import {Browser} from "@typing/browser"; import {BackgroundEntryName} from "@typing/background"; import type {OffscreenParametersMap} from "@typing/offscreen"; -const OffscreenTempDir = "virtual"; const OffscreenBackgroundModule = "offscreen.background.ts"; export default definePlugin(() => { @@ -70,19 +68,21 @@ export default definePlugin(() => { plugins.push(plugin, ...htmlPlugins, ...tagsPlugins); if (config.manifestVersion === 2 || config.browser === Browser.Firefox) { + const directory = prepareVirtualModuleDirectory(config.rootDir, "offscreen"); + plugins.push( new RspackVirtualModulePlugin( { [OffscreenBackgroundModule]: virtualOffscreenBackgroundModule(), }, - OffscreenTempDir + directory ) ); rspack = { entry: { [BackgroundEntryName]: { - import: [path.join(OffscreenTempDir, OffscreenBackgroundModule)], + import: [OffscreenBackgroundModule], }, }, }; diff --git a/src/cli/plugins/typescript/TypescriptConfig.ts b/src/cli/plugins/typescript/TypescriptConfig.ts index 3ea3c2af..3544aa56 100644 --- a/src/cli/plugins/typescript/TypescriptConfig.ts +++ b/src/cli/plugins/typescript/TypescriptConfig.ts @@ -4,7 +4,7 @@ import {TsConfigJson} from "type-fest"; import FileBuilder from "./FileBuilder"; -import {getResolvePath} from "@cli/resolvers/path"; +import {resolveRootPath} from "@cli/resolvers/path"; import {ReadonlyConfig} from "@typing/config"; import {PackageName} from "@typing/app"; @@ -50,7 +50,7 @@ export default class extends FileBuilder { public aliases(): Record<string, string> { return _.merge( this.vendorAliases, - _.mapValues(this.alias(), value => getResolvePath(value)) + _.mapValues(this.alias(), value => path.resolve(resolveRootPath(this.config), value)) ); } diff --git a/src/cli/resolvers/bundler.ts b/src/cli/resolvers/bundler.ts index 8e3b12ff..5b35dd9a 100644 --- a/src/cli/resolvers/bundler.ts +++ b/src/cli/resolvers/bundler.ts @@ -1,9 +1,11 @@ import {Configuration as RspackConfig, RspackPluginInstance} from "@rspack/core"; import {RsdoctorRspackPlugin} from "@rsdoctor/rspack-plugin"; import {merge as mergeConfig} from "webpack-merge"; +import path from "node:path"; import manifestFactory from "../builders/manifest"; import {processPluginHandler} from "./plugin"; +import {resolveRootPath} from "./path"; import ManifestPlugin from "@cli/bundler/plugins/manifest"; import WatchPlugin from "@cli/bundler/plugins/watch"; @@ -53,9 +55,15 @@ const getConfigForManifest = async (config: ReadonlyConfig): Promise<RspackConfi export default async (config: ReadonlyConfig): Promise<RspackConfig> => { let rspack: RspackConfig = { + context: resolveRootPath(config), entry: {}, mode: config.mode, cache: false, + resolve: { + // Virtual entry modules may live outside the application. Keep issuer-local + // lookup first, then resolve the consumer's packages from its project root. + modules: ["node_modules", path.join(resolveRootPath(config), "node_modules")], + }, }; // prettier-ignore diff --git a/src/cli/utils/path.test.ts b/src/cli/utils/path.test.ts new file mode 100644 index 00000000..08dedad8 --- /dev/null +++ b/src/cli/utils/path.test.ts @@ -0,0 +1,18 @@ +import path from "node:path"; + +import {toPosix, toPosixPath} from "./path"; + +test("absolute entrypoint imports preserve their filesystem root", () => { + const directory = path.resolve("project with spaces", "src"); + const filename = path.join(directory, "newtab.ts"); + + expect(toPosix(filename)).toBe(filename.split(path.sep).join("/")); + expect(toPosixPath(filename)).toBe(path.join(directory, "newtab").split(path.sep).join("/")); + expect(toPosixPath(path.join(directory, "index.ts"))).toBe(directory.split(path.sep).join("/")); +}); + +test("relative entrypoint imports keep their directory and index conventions", () => { + expect(toPosixPath("index.ts")).toBe("."); + expect(toPosixPath(path.join("src", "newtab.ts"))).toBe("src/newtab"); + expect(toPosixPath(path.join("src", "newtab", "index.ts"))).toBe("src/newtab"); +}); diff --git a/src/cli/utils/path.ts b/src/cli/utils/path.ts index eeb02aa3..23bb0220 100644 --- a/src/cli/utils/path.ts +++ b/src/cli/utils/path.ts @@ -2,7 +2,7 @@ import path from "path"; import _ from "lodash"; export const toPosix = (to: string): string => { - return path.posix.join(...to.split(path.sep)); + return path.normalize(to).split(path.sep).join("/"); }; export const toPosixPath = (pathname: string): string => { diff --git a/tests/integration/README.md b/tests/integration/README.md index 60088cc0..db369604 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -13,10 +13,13 @@ tests/integration/ │ │ ├── cli.integration.test.ts │ │ ├── build-api.integration.test.ts │ │ ├── dotenv.integration.test.ts +│ │ ├── root-dir.integration.test.ts │ │ ├── fixtures/ │ │ │ ├── exit-code/ │ │ │ ├── environment/ -│ │ │ └── dotenv/ +│ │ │ ├── dotenv/ +│ │ │ ├── root-dir/ +│ │ │ └── root-dir-b/ │ │ └── scripts/ │ ├── locale/ │ │ ├── locale.integration.test.ts @@ -202,6 +205,17 @@ cache across builds; environment restoration alone does not refresh them. Use th projects. This restriction belongs only to `buildApp`, not the CLI. Repeated builds are checked for all three TypeScript extensions; imported native modules still retain their normal Node cache semantics. +`build/cli/root-dir.integration.test.ts` builds two projects in the sequence A → B → A through both the CLI +and `buildApp`, from an unrelated working directory. Absolute roots, paths containing spaces, relative +imports and the generated `@/` alias are covered. Compiler context, source aliases and fallback package +resolution use the project root; the caller's working directory is unchanged. The external virtual-module +plugin still creates temporary files under the caller's `node_modules` and removes its module directory on +compiler shutdown; these builds therefore require a writable working directory. Entrypoint and offscreen +module directories are keyed by project root so independent workers do not remove each other's files. +An IPC-controlled regression pauses two compilers after module creation, closes one, then runs the other. +The sequence includes Chrome and Firefox with Offscreen, including its generated background module. +Temporary directory creation is recursive, avoiding the external plugin's check-then-create race in a fresh cwd. + - `types/content`: shared Content and adapter render types through the public source and built package APIs, callback props inference, and iframe-navigation restrictions for both define functions. - `types/view`: shared View and adapter render types through the public source and built package APIs, render and container props inference, the render contract adopted by Offscreen and Sandbox, and the rejection of Promise and plain-object render values. - `types/registries`: generated registry augmentation, empty fallbacks, public and internal type agreement, and message contracts against source and built package APIs. Compiler-host path checks cover both slash styles. diff --git a/tests/integration/build/cli/fixtures/root-dir-b/title.ts b/tests/integration/build/cli/fixtures/root-dir-b/title.ts new file mode 100644 index 00000000..d9719d09 --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir-b/title.ts @@ -0,0 +1 @@ +export const title = "Absolute root B"; diff --git a/tests/integration/build/cli/fixtures/root-dir-b/values.ts b/tests/integration/build/cli/fixtures/root-dir-b/values.ts new file mode 100644 index 00000000..520378e2 --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir-b/values.ts @@ -0,0 +1,2 @@ +export const source = "https://root-b.example.com"; +export const markup = "<main>Root B runtime</main>"; diff --git a/tests/integration/build/cli/fixtures/root-dir/adnbn.config.ts b/tests/integration/build/cli/fixtures/root-dir/adnbn.config.ts new file mode 100644 index 00000000..7e53b47d --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir/adnbn.config.ts @@ -0,0 +1,6 @@ +import {defineConfig} from "adnbn"; + +export default defineConfig({ + name: "Root Directory Integration", + version: "1.0.0", +}); diff --git a/tests/integration/build/cli/fixtures/root-dir/src/background.ts b/tests/integration/build/cli/fixtures/root-dir/src/background.ts new file mode 100644 index 00000000..b99a8e26 --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir/src/background.ts @@ -0,0 +1,7 @@ +import {defineBackground} from "adnbn"; + +export default defineBackground({ + main() { + console.info("Root background runtime"); + }, +}); diff --git a/tests/integration/build/cli/fixtures/root-dir/src/newtab.ts b/tests/integration/build/cli/fixtures/root-dir/src/newtab.ts new file mode 100644 index 00000000..d6c30b92 --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir/src/newtab.ts @@ -0,0 +1,10 @@ +import {defineNewtab} from "adnbn"; + +import {title} from "./title"; +import {source, markup} from "@/values"; + +export default defineNewtab({ + title, + csp: {sources: {connect: [source]}}, + render: markup, +}); diff --git a/tests/integration/build/cli/fixtures/root-dir/src/offscreen.ts b/tests/integration/build/cli/fixtures/root-dir/src/offscreen.ts new file mode 100644 index 00000000..7212f8ca --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir/src/offscreen.ts @@ -0,0 +1,12 @@ +import {defineOffscreen, OffscreenReason} from "adnbn"; + +export default defineOffscreen({ + name: "rootProbe", + reasons: [OffscreenReason.DOMParser], + justification: "Check offscreen entry resolution from an absolute project root.", + init: () => ({ + read() { + return "Offscreen root runtime"; + }, + }), +}); diff --git a/tests/integration/build/cli/fixtures/root-dir/src/title.ts b/tests/integration/build/cli/fixtures/root-dir/src/title.ts new file mode 100644 index 00000000..494a9f50 --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir/src/title.ts @@ -0,0 +1 @@ +export const title = "Absolute root A"; diff --git a/tests/integration/build/cli/fixtures/root-dir/src/values.ts b/tests/integration/build/cli/fixtures/root-dir/src/values.ts new file mode 100644 index 00000000..4d22fb1f --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir/src/values.ts @@ -0,0 +1,2 @@ +export const source = "https://root-a.example.com"; +export const markup = "<main>Root A runtime</main>"; diff --git a/tests/integration/build/cli/fixtures/root-dir/tsconfig.json b/tests/integration/build/cli/fixtures/root-dir/tsconfig.json new file mode 100644 index 00000000..eadf1be8 --- /dev/null +++ b/tests/integration/build/cli/fixtures/root-dir/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.adnbn/tsconfig.json" +} diff --git a/tests/integration/build/cli/root-dir.integration.test.ts b/tests/integration/build/cli/root-dir.integration.test.ts new file mode 100644 index 00000000..c3f150f0 --- /dev/null +++ b/tests/integration/build/cli/root-dir.integration.test.ts @@ -0,0 +1,88 @@ +import {fork, spawnSync} from "node:child_process"; +import {mkdirSync, mkdtempSync, rmSync} from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +jest.setTimeout(90_000); + +test.each(["api", "cli"])("%s builds absolute roots from an unrelated working directory", mode => { + const directory = mkdtempSync(path.join(os.tmpdir(), "adnbn-root-dir-")); + const projectRoot = path.resolve(__dirname, "../../../.."); + + try { + const result = spawnSync( + process.execPath, + [path.join(__dirname, "scripts/verify-root-dir.mjs"), projectRoot, directory, mode], + {encoding: "utf8", timeout: 60_000, maxBuffer: 4 * 1024 * 1024} + ); + + expect(result.error).toBeUndefined(); + expect({status: result.status, stderr: result.stderr}).toEqual({status: 0, stderr: ""}); + expect(result.stdout).toContain("Absolute root contract verified"); + } finally { + rmSync(directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); + } +}); + +test("closing one project does not remove another project's pending virtual entrypoints", async () => { + const directory = mkdtempSync(path.join(os.tmpdir(), "adnbn-root-dir-parallel-")); + const projectRoot = path.resolve(__dirname, "../../../.."); + const sharedCwd = path.join(directory, "caller"); + mkdirSync(sharedCwd); + + const start = (name: string) => { + const root = path.join(directory, name); + mkdirSync(root); + const child = fork( + path.join(__dirname, "scripts/verify-root-dir.mjs"), + [projectRoot, root, "parallel", sharedCwd], + { + execArgv: [], + stdio: ["ignore", "pipe", "pipe", "ipc"], + timeout: 60_000, + killSignal: "SIGKILL", + } + ); + let stderr = ""; + child.stdout!.resume(); + child.stderr!.on("data", data => { + stderr += data; + }); + const completed = new Promise(resolve => + child.once("close", (code, signal) => resolve({code, signal, stderr})) + ); + const ready = new Promise<void>((resolve, reject) => { + child.once("message", message => { + if (message === "ready") { + resolve(); + } else { + reject(new Error(`Unexpected build message: ${message}`)); + } + }); + child.once("error", reject); + child.once("close", () => reject(new Error(`Build stopped before ready: ${stderr}`))); + }); + + return {child, ready, completed}; + }; + const builds = [start("first"), start("second")]; + + try { + await Promise.all(builds.map(build => build.ready)); + // Both compilers have installed their virtual modules. Finish and close one + // before the other's compilation starts, so shared-directory deletion is observable. + for (const build of builds) { + build.child.send("build"); + expect(await build.completed).toEqual({code: 0, signal: null, stderr: ""}); + } + } finally { + for (const {child} of builds) { + if (child.exitCode === null && child.signalCode === null) { + child.kill("SIGKILL"); + } + } + + await Promise.all(builds.map(build => build.completed)); + rmSync(directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); + } +}); diff --git a/tests/integration/build/cli/scripts/verify-root-dir.mjs b/tests/integration/build/cli/scripts/verify-root-dir.mjs new file mode 100644 index 00000000..f6b0ce86 --- /dev/null +++ b/tests/integration/build/cli/scripts/verify-root-dir.mjs @@ -0,0 +1,115 @@ +import assert from "node:assert/strict"; +import {spawnSync} from "node:child_process"; +import {cp, mkdir, readFile, readdir, symlink} from "node:fs/promises"; +import path from "node:path"; +import {pathToFileURL} from "node:url"; + +const [projectRoot, directory, mode, sharedCwd] = process.argv.slice(2); +const {buildApp} = await import(pathToFileURL(path.join(projectRoot, "dist/cli/builders/app/index.js")).href); +const rootA = path.join(directory, "project a"); +const rootB = path.join(directory, "project b"); +const cwd = sharedCwd ?? path.join(directory, "unrelated"); + +await mkdir(cwd, {recursive: true}); + +for (const root of [rootA, rootB]) { + await cp(path.join(import.meta.dirname, "../fixtures/root-dir"), root, {recursive: true}); + await mkdir(path.join(root, "node_modules")); + await symlink( + projectRoot, + path.join(root, "node_modules/adnbn"), + process.platform === "win32" ? "junction" : "dir" + ); +} + +await cp(path.join(import.meta.dirname, "../fixtures/root-dir-b"), path.join(rootB, "src"), {recursive: true}); +process.chdir(cwd); +const workingDirectory = process.cwd(); +const environment = process.env; +const snapshot = {...environment}; + +const sequence = [ + [rootA, "A", "chrome"], + [rootB, "B", "firefox"], + [rootA, "A", "chrome"], +]; + +for (const [root, label, browser] of mode === "parallel" ? sequence.slice(1, 2) : sequence) { + if (mode !== "cli") { + const stats = await buildApp({ + rootDir: root, + browser, + mode: "production", + plugins: + mode === "parallel" + ? [ + { + name: "hold-before-run", + bundler: { + plugins: [ + { + apply(compiler) { + compiler.hooks.beforeRun.tapPromise( + "HoldBeforeRun", + () => + new Promise(resolve => { + process.once("message", message => { + assert.equal(message, "build"); + resolve(); + }); + process.send("ready"); + }) + ); + }, + }, + ], + }, + }, + ] + : [], + }).catch(error => { + if (error.stats) { + console.error(error.stats.toString({all: false, errors: true})); + } + + throw error; + }); + assert.equal(stats.hasErrors(), false); + } else { + const result = spawnSync( + process.execPath, + [path.join(projectRoot, "bin/adnbn.js"), "build", root, "-b", browser], + { + encoding: "utf8", + timeout: 30_000, + } + ); + assert.equal(result.error, undefined); + assert.equal(result.status, 0, result.stderr + result.stdout); + } + + assert.equal(process.cwd(), workingDirectory); + assert.equal(process.env, environment); + assert.deepEqual({...process.env}, snapshot); + const output = path.join(root, `dist/myapp-${browser}-mv3`); + const html = await readFile(path.join(output, "newtab.html"), "utf8"); + const manifest = JSON.parse(await readFile(path.join(output, "manifest.json"), "utf8")); + assert.ok(html.includes(`<title>Absolute root ${label}`)); + assert.ok( + JSON.stringify(manifest.content_security_policy).includes(`https://root-${label.toLowerCase()}.example.com`) + ); + const scripts = (await readdir(output, {recursive: true})).filter(file => file.endsWith(".js")); + const code = (await Promise.all(scripts.map(file => readFile(path.join(output, file), "utf8")))).join("\n"); + assert.ok(code.includes(`Root ${label} runtime`)); + assert.ok(code.includes("Offscreen root runtime")); + + if (browser === "firefox") { + assert.ok(manifest.background.scripts.length > 0); + } +} + +console.log("Absolute root contract verified"); + +if (process.connected) { + process.disconnect(); +} From 29780063cb70356367a82742eb8b00c495f85228 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:14:51 +0300 Subject: [PATCH 27/36] test: add opt-in in-process override builds Select the backend in the fixture helper without changing the 33 override scenarios. Keep CLI as the default and retain it for competing-entry tests. Use the native loader with temporary environment restoration, passing an absolute project root without changing the worker cwd. Recognize consumer fixtures by both package.json and adnbn.config.ts so internal CLI config fixtures are not prepared as standalone applications. --- tests/integration/README.md | 27 +++++++- .../build/override/override-utils.ts | 10 ++- tests/integration/utils/fixture.test.ts | 19 +++++ tests/integration/utils/fixture.ts | 69 ++++++++++++++----- 4 files changed, 105 insertions(+), 20 deletions(-) create mode 100644 tests/integration/utils/fixture.test.ts diff --git a/tests/integration/README.md b/tests/integration/README.md index db369604..ad6b8f3e 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -95,6 +95,7 @@ tests/integration/ │ └── view/ ├── utils/ │ ├── fixture.ts +│ ├── fixture.test.ts │ ├── process.ts │ ├── process.test.ts │ ├── queue.ts @@ -105,7 +106,9 @@ tests/integration/ Use kebab-case for directory names and for filenames containing multiple words, including helpers and scenario tests. Files whose primary export is a class or React component use PascalCase matching that export. Tests for a specific class also preserve its name, for example `ContentManager.test.ts`. Keep framework entrypoint suffixes such as `.content.ts` and `.page.ts`. Put an entrypoint or component with its own styles in one directory; standalone entrypoints can remain single files. -Each application retains its own `package.json`, `adnbn.config.ts`, and `tsconfig.json`. The package boundary prevents the framework package's `sideEffects: false` setting from discarding fixture CSS imports. +The preparation/typecheck inventory recognizes standalone applications by both `adnbn.config.ts` and +`package.json`. Internal CLI configuration fixtures are exercised by their owning tests and are not prepared +as consumer applications. Each application retains its own `package.json`, `adnbn.config.ts`, and `tsconfig.json`. The package boundary prevents the framework package's `sideEffects: false` setting from discarding fixture CSS imports. ## Prepare the editor environment @@ -260,3 +263,25 @@ of the host environment. `${VAR}` references remain literal; the framework does The extra dotenv pass in `c12` is disabled. **Behavior change:** running the CLI from a directory outside the project no longer reads that directory's `.env`, and its interpolated values no longer reach user config or plugins. Put build variables in the app/project dotenv files or export them explicitly in the calling process. + +## Override in-process pilot + +Override page tests can select `ADNBN_OVERRIDE_BUILD_MODE=in-process`; the default is `cli`. +The fixture helper owns the alternative build path, while application copies, scenario names and assertions +are identical. Thirty page builds use `buildApp`; the three competing-entrypoint scenarios always use the CLI. + +The in-process helper uses Node's native loader inside the Jest worker. Jest substitutes its own loader and +`process.env`, so the helper temporarily lends the native process the test environment and restores it in +`finally`. It passes an absolute fixture root without changing the worker's cwd. No concurrent tests +or background builds are allowed. This is a test-only execution adapter, not a change to ordinary CLI behavior. + +```bash +npx cross-env ADNBN_OVERRIDE_BUILD_MODE=in-process npm run test:run -- --selectProjects build --testPathPatterns=tests/integration/build/override --maxWorkers=2 +npm run build +node tests/benchmarks/override-pilot.mjs +``` + +The pilot is opt-in: it is faster locally, but repeated builds retain memory even after GC. The benchmark runner +records worker heap/RSS, verifies environment/cwd restoration and cleanup, and compares both worker counts. +Its 90-build diagnostic creates a temporary test file and removes it in `finally`; run benchmarks exclusively. +See [the pilot report](../benchmarks/2026-09-28-override-pilot.md) for measurements, limitations and the decision. diff --git a/tests/integration/build/override/override-utils.ts b/tests/integration/build/override/override-utils.ts index 4d698487..28a3ef02 100644 --- a/tests/integration/build/override/override-utils.ts +++ b/tests/integration/build/override/override-utils.ts @@ -10,6 +10,12 @@ interface OverridePageScenario { unsupported: readonly string[]; } +const buildMode = process.env.ADNBN_OVERRIDE_BUILD_MODE ?? "cli"; + +if (buildMode !== "cli" && buildMode !== "in-process") { + throw new Error(`Unknown override build mode: ${buildMode}`); +} + const rootDir = path.resolve(__dirname, "..", "..", "..", ".."); export const readOverrideManifest = async (extensionDir: string) => { @@ -23,7 +29,7 @@ export const testOverridePage = ({page, permission, supported, unsupported}: Ove describe.each(supported)("%s", browser => { test.each([2, 3] as const)("MV%s build replaces the browser page", async manifestVersion => { - const fixture = await createIntegrationFixture(rootDir, fixtureDir); + const fixture = await createIntegrationFixture(rootDir, fixtureDir, buildMode); try { const extensionDir = await fixture.build({browser, manifestVersion}); @@ -44,7 +50,7 @@ export const testOverridePage = ({page, permission, supported, unsupported}: Ove describe.each(unsupported)("%s", browser => { test.each([2, 3] as const)("MV%s build skips the unsupported page", async manifestVersion => { - const fixture = await createIntegrationFixture(rootDir, fixtureDir); + const fixture = await createIntegrationFixture(rootDir, fixtureDir, buildMode); try { const extensionDir = await fixture.build({browser, manifestVersion}); diff --git a/tests/integration/utils/fixture.test.ts b/tests/integration/utils/fixture.test.ts new file mode 100644 index 00000000..4b199e2a --- /dev/null +++ b/tests/integration/utils/fixture.test.ts @@ -0,0 +1,19 @@ +import path from "node:path"; + +import {findIntegrationFixtures} from "./fixture"; + +const integration = path.resolve(__dirname, ".."); + +test("does not prepare internal CLI configs as standalone consumer applications", async () => { + expect(await findIntegrationFixtures(path.join(integration, "build/cli"))).toEqual([]); +}); + +test("discovers the three complete override applications", async () => { + const override = path.join(integration, "build/override"); + + expect(await findIntegrationFixtures(override)).toEqual([ + path.join(override, "bookmarks"), + path.join(override, "history"), + path.join(override, "newtab"), + ]); +}); diff --git a/tests/integration/utils/fixture.ts b/tests/integration/utils/fixture.ts index 1b28fa15..ead7fae8 100644 --- a/tests/integration/utils/fixture.ts +++ b/tests/integration/utils/fixture.ts @@ -1,9 +1,19 @@ import {cp, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, symlink} from "fs/promises"; import path from "path"; +import type {buildApp} from "@cli/builders/app"; +import {Browser} from "@typing/browser"; +import {Mode} from "@typing/app"; import {run} from "./process"; +interface AppBuildModule { + buildApp: typeof buildApp; +} + +export type IntegrationFixtureBuildMode = "cli" | "in-process"; + export interface IntegrationFixtureBuildOptions { + buildMode?: IntegrationFixtureBuildMode; browser?: string; manifestVersion?: 2 | 3; } @@ -41,7 +51,7 @@ const linkDependency = async (source: string, destination: string): Promise => { const manifest = JSON.parse(await readFile(path.join(directory, "package.json"), "utf8")) as { @@ -55,26 +65,48 @@ export const prepareIntegrationFixture = async ( ); } - await run( - process.execPath, - [ - path.join(projectRoot, "bin", "adnbn.js"), - "build", - ".", - "-b", - browser, - ...(manifestVersion === 2 ? ["--mv2"] : []), - ], - directory, - timeout - ); + if (buildMode === "in-process") { + // Jest replaces createRequire and process.env. Use Node's loader in this same worker, + // lending it the test environment only for the awaited build. + const nodeProcess = process.getBuiltinModule("process"); + const require = process.getBuiltinModule("module").createRequire(path.join(projectRoot, "package.json")); + const environment = nodeProcess.env; + nodeProcess.env = process.env; + + try { + const app = require(path.join(projectRoot, "dist/cli/builders/app/index.js")) as AppBuildModule; + await app.buildApp({ + rootDir: path.resolve(directory), + browser: browser as Browser, + manifestVersion, + mode: Mode.Production, + }); + } finally { + nodeProcess.env = environment; + } + } else { + await run( + process.execPath, + [ + path.join(projectRoot, "bin", "adnbn.js"), + "build", + ".", + "-b", + browser, + ...(manifestVersion === 2 ? ["--mv2"] : []), + ], + directory, + timeout + ); + } return path.join(directory, "dist", `myapp-${browser}-mv${manifestVersion}`); }; export const createIntegrationFixture = async ( projectRoot: string, - sourceDirectory: string + sourceDirectory: string, + buildMode: IntegrationFixtureBuildMode = "cli" ): Promise => { const cacheDirectory = path.join(projectRoot, ".cache", "integration"); @@ -95,7 +127,7 @@ export const createIntegrationFixture = async ( return { directory, - build: options => prepareIntegrationFixture(projectRoot, directory, options), + build: options => prepareIntegrationFixture(projectRoot, directory, {buildMode, ...options}), dispose, }; }; @@ -103,7 +135,10 @@ export const createIntegrationFixture = async ( export const findIntegrationFixtures = async (directory: string): Promise => { const entries = await readdir(directory, {withFileTypes: true}); - if (entries.some(entry => entry.isFile() && entry.name === "adnbn.config.ts")) { + const hasConfig = entries.some(entry => entry.isFile() && entry.name === "adnbn.config.ts"); + const hasPackage = entries.some(entry => entry.isFile() && entry.name === "package.json"); + + if (hasConfig && hasPackage) { return [directory]; } From 5074e10b29f51af56b8265150276bc1915b9b3cd Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:15:34 +0300 Subject: [PATCH 28/36] test: record override benchmarks and Rspack retention Add repeatable comparisons with worker heap/RSS, scenario identity, fixture cleanup and open-handle checks, plus a 90-build retention probe. Keep CLI as default despite local speedups. Include a standalone Rspack WeakRef reproduction with static/function controls, raw measurements and validation limits, including the observed Chrome startup timeout. --- .../benchmarks/2026-09-28-override-pilot.json | 14185 ++++++++++++++++ tests/benchmarks/2026-09-28-override-pilot.md | 155 + .../2026-09-28-rspack-retention.json | 105 + tests/benchmarks/override-pilot-metrics.cjs | 32 + tests/benchmarks/override-pilot.mjs | 214 + tests/benchmarks/rspack-retention.mjs | 183 + 6 files changed, 14874 insertions(+) create mode 100644 tests/benchmarks/2026-09-28-override-pilot.json create mode 100644 tests/benchmarks/2026-09-28-override-pilot.md create mode 100644 tests/benchmarks/2026-09-28-rspack-retention.json create mode 100644 tests/benchmarks/override-pilot-metrics.cjs create mode 100644 tests/benchmarks/override-pilot.mjs create mode 100644 tests/benchmarks/rspack-retention.mjs diff --git a/tests/benchmarks/2026-09-28-override-pilot.json b/tests/benchmarks/2026-09-28-override-pilot.json new file mode 100644 index 00000000..919da75b --- /dev/null +++ b/tests/benchmarks/2026-09-28-override-pilot.json @@ -0,0 +1,14185 @@ +{ + "node": "v24.5.0", + "platform": "darwin", + "arch": "arm64", + "cpus": 16, + "flags": [ + "--expose-gc", + "--no-sparkplug" + ], + "runs": [ + { + "id": "1-warmup-2-cli", + "phase": "warmup", + "mode": "cli", + "workers": 2, + "wallSeconds": 13.979217416, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115834880, + "heapTotal": 60964864, + "heapUsed": 24928304, + "external": 5717870, + "arrayBuffers": 2056770, + "maxRssKiB": 113248 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116965376, + "heapTotal": 60964864, + "heapUsed": 24829416, + "external": 5717582, + "arrayBuffers": 2056770, + "maxRssKiB": 114384 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116719616, + "heapTotal": 61489152, + "heapUsed": 25962624, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113984 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117702656, + "heapTotal": 61227008, + "heapUsed": 25861952, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 114944 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117325824, + "heapTotal": 62537728, + "heapUsed": 27032792, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114576 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118128640, + "heapTotal": 62537728, + "heapUsed": 26924680, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 115360 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 125485056, + "heapTotal": 66781184, + "heapUsed": 29280384, + "external": 7763345, + "arrayBuffers": 2073154, + "maxRssKiB": 122672 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118603776, + "heapTotal": 63586304, + "heapUsed": 28000944, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 115824 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 125714432, + "heapTotal": 67305472, + "heapUsed": 30317280, + "external": 5715239, + "arrayBuffers": 2081346, + "maxRssKiB": 122768 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119422976, + "heapTotal": 64897024, + "heapUsed": 29059840, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 116624 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 120094720, + "heapTotal": 66207744, + "heapUsed": 30091160, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 117280 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 125878272, + "heapTotal": 67829760, + "heapUsed": 31361056, + "external": 5723400, + "arrayBuffers": 2081346, + "maxRssKiB": 122928 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120750080, + "heapTotal": 67256320, + "heapUsed": 31112968, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 117920 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 128794624, + "heapTotal": 102432768, + "heapUsed": 32424984, + "external": 5715237, + "arrayBuffers": 2081346, + "maxRssKiB": 125776 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 129220608, + "heapTotal": 103481344, + "heapUsed": 33470328, + "external": 5722276, + "arrayBuffers": 2081346, + "maxRssKiB": 126192 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121520128, + "heapTotal": 68304896, + "heapUsed": 32137320, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 118672 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 129925120, + "heapTotal": 104529920, + "heapUsed": 34517968, + "external": 5714084, + "arrayBuffers": 2081346, + "maxRssKiB": 126880 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122273792, + "heapTotal": 69615616, + "heapUsed": 33216728, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 119408 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 130662400, + "heapTotal": 105578496, + "heapUsed": 35544760, + "external": 5722120, + "arrayBuffers": 2081346, + "maxRssKiB": 127600 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122994688, + "heapTotal": 70664192, + "heapUsed": 34247776, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 120112 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 131383296, + "heapTotal": 106889216, + "heapUsed": 36566664, + "external": 5713928, + "arrayBuffers": 2081346, + "maxRssKiB": 128304 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 139624448, + "heapTotal": 106364928, + "heapUsed": 29495824, + "external": 7755125, + "arrayBuffers": 2073154, + "maxRssKiB": 136480 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 132104192, + "heapTotal": 107937792, + "heapUsed": 37619664, + "external": 5722119, + "arrayBuffers": 2081346, + "maxRssKiB": 129008 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 140361728, + "heapTotal": 100859904, + "heapUsed": 30483776, + "external": 5715213, + "arrayBuffers": 2081346, + "maxRssKiB": 137072 + }, + { + "pid": 39250, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 132775936, + "heapTotal": 109248512, + "heapUsed": 38647208, + "external": 5722119, + "arrayBuffers": 2081346, + "maxRssKiB": 129664 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 140541952, + "heapTotal": 101908480, + "heapUsed": 31522240, + "external": 5723374, + "arrayBuffers": 2081346, + "maxRssKiB": 137248 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 140607488, + "heapTotal": 103219200, + "heapUsed": 32564272, + "external": 5715211, + "arrayBuffers": 2081346, + "maxRssKiB": 137312 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 140623872, + "heapTotal": 104267776, + "heapUsed": 33619072, + "external": 5722268, + "arrayBuffers": 2081346, + "maxRssKiB": 137328 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 140623872, + "heapTotal": 105316352, + "heapUsed": 34648040, + "external": 5714076, + "arrayBuffers": 2081346, + "maxRssKiB": 137328 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140656640, + "heapTotal": 106364928, + "heapUsed": 35669440, + "external": 5722112, + "arrayBuffers": 2081346, + "maxRssKiB": 137360 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 140689408, + "heapTotal": 107413504, + "heapUsed": 36694712, + "external": 5713920, + "arrayBuffers": 2081346, + "maxRssKiB": 137392 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141213696, + "heapTotal": 108724224, + "heapUsed": 37734672, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 137904 + }, + { + "pid": 39251, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 141852672, + "heapTotal": 109772800, + "heapUsed": 38762664, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 138528 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.305 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.924 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.499 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.37 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.924 s, 32 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.499 s, 36 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.37 s, 36 MB heap size)" + ] + }, + { + "id": "2-warmup-2-in-process", + "phase": "warmup", + "mode": "in-process", + "workers": 2, + "wallSeconds": 3.6898622499999982, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357105664, + "heapTotal": 231440384, + "heapUsed": 92241440, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 348864 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 356433920, + "heapTotal": 231178240, + "heapUsed": 92235192, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 348208 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 367755264, + "heapTotal": 233799680, + "heapUsed": 94973216, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 359264 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 367706112, + "heapTotal": 233275392, + "heapUsed": 94968400, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 359216 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 378421248, + "heapTotal": 235896832, + "heapUsed": 97693200, + "external": 6027417, + "arrayBuffers": 2183442, + "maxRssKiB": 369680 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 379092992, + "heapTotal": 235896832, + "heapUsed": 97690144, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 370336 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 391593984, + "heapTotal": 238256128, + "heapUsed": 100284688, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 382544 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 388726784, + "heapTotal": 238256128, + "heapUsed": 100285312, + "external": 6035667, + "arrayBuffers": 2183442, + "maxRssKiB": 379744 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 390496256, + "heapTotal": 239828992, + "heapUsed": 101897472, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 381472 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 402718720, + "heapTotal": 240877568, + "heapUsed": 102921872, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 393408 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 392347648, + "heapTotal": 241664000, + "heapUsed": 103463880, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 383280 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 413384704, + "heapTotal": 243499008, + "heapUsed": 105528312, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 403824 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 394231808, + "heapTotal": 242974720, + "heapUsed": 104948088, + "external": 6023735, + "arrayBuffers": 2183442, + "maxRssKiB": 385120 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 395837440, + "heapTotal": 244547584, + "heapUsed": 106507776, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 386688 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 423460864, + "heapTotal": 246120448, + "heapUsed": 108031336, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 413664 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 398082048, + "heapTotal": 246644736, + "heapUsed": 108272816, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 388880 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 431931392, + "heapTotal": 248479744, + "heapUsed": 110642528, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 421936 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 399998976, + "heapTotal": 248479744, + "heapUsed": 109919384, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 390752 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 433799168, + "heapTotal": 250314752, + "heapUsed": 112418560, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 423760 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 435781632, + "heapTotal": 252411904, + "heapUsed": 114070056, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 425696 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 413515776, + "heapTotal": 252461056, + "heapUsed": 106721568, + "external": 8083708, + "arrayBuffers": 2191634, + "maxRssKiB": 403952 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 422543360, + "heapTotal": 248004608, + "heapUsed": 109270968, + "external": 6043819, + "arrayBuffers": 2199826, + "maxRssKiB": 412768 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 431177728, + "heapTotal": 250363904, + "heapUsed": 111833080, + "external": 6043759, + "arrayBuffers": 2199826, + "maxRssKiB": 421200 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 439812096, + "heapTotal": 252198912, + "heapUsed": 114355672, + "external": 6052009, + "arrayBuffers": 2199826, + "maxRssKiB": 429632 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 440860672, + "heapTotal": 254033920, + "heapUsed": 115861528, + "external": 6040418, + "arrayBuffers": 2199826, + "maxRssKiB": 430656 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 442449920, + "heapTotal": 255868928, + "heapUsed": 117389904, + "external": 6048610, + "arrayBuffers": 2199826, + "maxRssKiB": 432208 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 444186624, + "heapTotal": 257703936, + "heapUsed": 118910456, + "external": 6040107, + "arrayBuffers": 2199826, + "maxRssKiB": 433904 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 445661184, + "heapTotal": 259014656, + "heapUsed": 120399496, + "external": 6048299, + "arrayBuffers": 2199826, + "maxRssKiB": 435344 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 447528960, + "heapTotal": 260849664, + "heapUsed": 121948016, + "external": 6040106, + "arrayBuffers": 2199826, + "maxRssKiB": 437168 + }, + { + "pid": 39496, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 449200128, + "heapTotal": 262422528, + "heapUsed": 123476336, + "external": 6048298, + "arrayBuffers": 2199826, + "maxRssKiB": 438800 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 439500800, + "heapTotal": 254820352, + "heapUsed": 109306048, + "external": 8082211, + "arrayBuffers": 2199826, + "maxRssKiB": 429328 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 439779328, + "heapTotal": 250626048, + "heapUsed": 110313824, + "external": 6042260, + "arrayBuffers": 2208018, + "maxRssKiB": 429472 + }, + { + "pid": 39495, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 439910400, + "heapTotal": 251412480, + "heapUsed": 111359752, + "external": 6042865, + "arrayBuffers": 2208018, + "maxRssKiB": 429600 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.189 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.267 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 0.49 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.943 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (117 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (106 MB heap size)" + ] + }, + { + "id": "3-measured-2-cli", + "phase": "measured", + "mode": "cli", + "workers": 2, + "wallSeconds": 13.297347042000002, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115752960, + "heapTotal": 60702720, + "heapUsed": 24930800, + "external": 5717871, + "arrayBuffers": 2056770, + "maxRssKiB": 113168 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116719616, + "heapTotal": 60440576, + "heapUsed": 24831192, + "external": 5717583, + "arrayBuffers": 2056770, + "maxRssKiB": 114192 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116572160, + "heapTotal": 61751296, + "heapUsed": 25989072, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113840 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117407744, + "heapTotal": 61227008, + "heapUsed": 25861552, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 114656 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117112832, + "heapTotal": 62799872, + "heapUsed": 27056560, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114368 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118013952, + "heapTotal": 62275584, + "heapUsed": 26924536, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 115248 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 125042688, + "heapTotal": 67043328, + "heapUsed": 29302632, + "external": 7763346, + "arrayBuffers": 2073154, + "maxRssKiB": 122240 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118767616, + "heapTotal": 63586304, + "heapUsed": 28003056, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 115984 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 125353984, + "heapTotal": 67567616, + "heapUsed": 30339264, + "external": 5715239, + "arrayBuffers": 2081346, + "maxRssKiB": 122416 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119504896, + "heapTotal": 64897024, + "heapUsed": 29061280, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 116704 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 125665280, + "heapTotal": 68616192, + "heapUsed": 31380784, + "external": 5723400, + "arrayBuffers": 2081346, + "maxRssKiB": 122720 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 120225792, + "heapTotal": 66207744, + "heapUsed": 30092184, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 117408 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 128712704, + "heapTotal": 102694912, + "heapUsed": 32442776, + "external": 5715237, + "arrayBuffers": 2081346, + "maxRssKiB": 125696 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120963072, + "heapTotal": 67256320, + "heapUsed": 31115864, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 118128 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 129318912, + "heapTotal": 103481344, + "heapUsed": 33492984, + "external": 5722276, + "arrayBuffers": 2081346, + "maxRssKiB": 126288 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121700352, + "heapTotal": 68567040, + "heapUsed": 32138600, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 118848 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 129826816, + "heapTotal": 104529920, + "heapUsed": 34540240, + "external": 5714084, + "arrayBuffers": 2081346, + "maxRssKiB": 126784 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122470400, + "heapTotal": 69615616, + "heapUsed": 33216968, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 119600 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 130482176, + "heapTotal": 105578496, + "heapUsed": 35566344, + "external": 5722120, + "arrayBuffers": 2081346, + "maxRssKiB": 127424 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123191296, + "heapTotal": 70926336, + "heapUsed": 34247968, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 120304 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 131219456, + "heapTotal": 106889216, + "heapUsed": 36587480, + "external": 5713928, + "arrayBuffers": 2081346, + "maxRssKiB": 128144 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 139837440, + "heapTotal": 105316352, + "heapUsed": 29494936, + "external": 7755126, + "arrayBuffers": 2073154, + "maxRssKiB": 136688 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 131973120, + "heapTotal": 108199936, + "heapUsed": 37639984, + "external": 5722119, + "arrayBuffers": 2081346, + "maxRssKiB": 128880 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 140623872, + "heapTotal": 101122048, + "heapUsed": 30483896, + "external": 5715213, + "arrayBuffers": 2081346, + "maxRssKiB": 137328 + }, + { + "pid": 39609, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 132677632, + "heapTotal": 109248512, + "heapUsed": 38669448, + "external": 5722119, + "arrayBuffers": 2081346, + "maxRssKiB": 129568 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 140738560, + "heapTotal": 101908480, + "heapUsed": 31521272, + "external": 5723374, + "arrayBuffers": 2081346, + "maxRssKiB": 137440 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 140738560, + "heapTotal": 102694912, + "heapUsed": 32566616, + "external": 5715211, + "arrayBuffers": 2081346, + "maxRssKiB": 137440 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 140754944, + "heapTotal": 104005632, + "heapUsed": 33619928, + "external": 5722268, + "arrayBuffers": 2081346, + "maxRssKiB": 137456 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 140771328, + "heapTotal": 105054208, + "heapUsed": 34646480, + "external": 5714076, + "arrayBuffers": 2081346, + "maxRssKiB": 137472 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140787712, + "heapTotal": 106102784, + "heapUsed": 35667976, + "external": 5722112, + "arrayBuffers": 2081346, + "maxRssKiB": 137488 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 141066240, + "heapTotal": 107413504, + "heapUsed": 36694464, + "external": 5713920, + "arrayBuffers": 2081346, + "maxRssKiB": 137760 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141639680, + "heapTotal": 108462080, + "heapUsed": 37734552, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 138320 + }, + { + "pid": 39610, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 142262272, + "heapTotal": 109510656, + "heapUsed": 38764064, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 138928 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.005 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.443 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.246 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.379 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.443 s, 32 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.246 s, 36 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.379 s, 36 MB heap size)" + ] + }, + { + "id": "4-measured-2-in-process", + "phase": "measured", + "mode": "in-process", + "workers": 2, + "wallSeconds": 3.6261648330000025, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358400000, + "heapTotal": 231440384, + "heapUsed": 92239232, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 350128 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358514688, + "heapTotal": 231702528, + "heapUsed": 92240304, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 350240 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371097600, + "heapTotal": 233275392, + "heapUsed": 94968344, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 362528 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371965952, + "heapTotal": 233799680, + "heapUsed": 94975928, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 363376 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 382713856, + "heapTotal": 235896832, + "heapUsed": 97694104, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 373872 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 381976576, + "heapTotal": 235896832, + "heapUsed": 97691592, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 373152 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 393805824, + "heapTotal": 238256128, + "heapUsed": 100286128, + "external": 6035625, + "arrayBuffers": 2183442, + "maxRssKiB": 384704 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 393576448, + "heapTotal": 238518272, + "heapUsed": 100285320, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 384480 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 395821056, + "heapTotal": 240091136, + "heapUsed": 101898224, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 386672 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 402718720, + "heapTotal": 240615424, + "heapUsed": 102922728, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 393408 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 397426688, + "heapTotal": 241926144, + "heapUsed": 103464168, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 388240 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 399458304, + "heapTotal": 243499008, + "heapUsed": 104937512, + "external": 6023723, + "arrayBuffers": 2183442, + "maxRssKiB": 390224 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 412368896, + "heapTotal": 243236864, + "heapUsed": 105528888, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 402832 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 401276928, + "heapTotal": 245071872, + "heapUsed": 106502096, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 392000 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 421773312, + "heapTotal": 245858304, + "heapUsed": 108037056, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 412016 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 403308544, + "heapTotal": 246382592, + "heapUsed": 108270096, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 393984 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 405127168, + "heapTotal": 248479744, + "heapUsed": 109921744, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 395760 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 431783936, + "heapTotal": 248479744, + "heapUsed": 110643800, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 421792 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 434061312, + "heapTotal": 250314752, + "heapUsed": 112419688, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 424016 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 436142080, + "heapTotal": 251887616, + "heapUsed": 114072784, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 426096 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 418234368, + "heapTotal": 251936768, + "heapUsed": 106716320, + "external": 8083756, + "arrayBuffers": 2191634, + "maxRssKiB": 408560 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 425181184, + "heapTotal": 248528896, + "heapUsed": 109270072, + "external": 6043861, + "arrayBuffers": 2199826, + "maxRssKiB": 415344 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 434274304, + "heapTotal": 250363904, + "heapUsed": 111827480, + "external": 6043801, + "arrayBuffers": 2199826, + "maxRssKiB": 424224 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 442482688, + "heapTotal": 252723200, + "heapUsed": 114356632, + "external": 6052051, + "arrayBuffers": 2199826, + "maxRssKiB": 432240 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 443809792, + "heapTotal": 254558208, + "heapUsed": 115859752, + "external": 6040430, + "arrayBuffers": 2199826, + "maxRssKiB": 433536 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 445644800, + "heapTotal": 256131072, + "heapUsed": 117389264, + "external": 6048622, + "arrayBuffers": 2199826, + "maxRssKiB": 435328 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 447430656, + "heapTotal": 257703936, + "heapUsed": 118910312, + "external": 6040119, + "arrayBuffers": 2199826, + "maxRssKiB": 437072 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 449249280, + "heapTotal": 259538944, + "heapUsed": 120399816, + "external": 6048311, + "arrayBuffers": 2199826, + "maxRssKiB": 438848 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 451117056, + "heapTotal": 260587520, + "heapUsed": 121947592, + "external": 6040118, + "arrayBuffers": 2199826, + "maxRssKiB": 440672 + }, + { + "pid": 39861, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 452870144, + "heapTotal": 262422528, + "heapUsed": 123476544, + "external": 6048310, + "arrayBuffers": 2199826, + "maxRssKiB": 442384 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 439730176, + "heapTotal": 254558208, + "heapUsed": 109305704, + "external": 8082215, + "arrayBuffers": 2199826, + "maxRssKiB": 429552 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 440090624, + "heapTotal": 249315328, + "heapUsed": 110315880, + "external": 6042260, + "arrayBuffers": 2208018, + "maxRssKiB": 429776 + }, + { + "pid": 39860, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 440107008, + "heapTotal": 249839616, + "heapUsed": 111357776, + "external": 6042865, + "arrayBuffers": 2208018, + "maxRssKiB": 429792 + } + ], + "files": [ + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.188 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.268 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.478 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.871 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (117 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (106 MB heap size)" + ] + }, + { + "id": "5-measured-2-in-process", + "phase": "measured", + "mode": "in-process", + "workers": 2, + "wallSeconds": 2.6574647909999984, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 39906, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116703232, + "heapTotal": 61227008, + "heapUsed": 24933096, + "external": 5717879, + "arrayBuffers": 2056770, + "maxRssKiB": 114096 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 356696064, + "heapTotal": 231702528, + "heapUsed": 92234480, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 348464 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 369999872, + "heapTotal": 233275392, + "heapUsed": 94965016, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 361456 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 379781120, + "heapTotal": 235372544, + "heapUsed": 97697808, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 371008 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 391348224, + "heapTotal": 237993984, + "heapUsed": 100282584, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 382304 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 400752640, + "heapTotal": 240615424, + "heapUsed": 102921392, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 391488 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 409812992, + "heapTotal": 243236864, + "heapUsed": 105528200, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 400336 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 419889152, + "heapTotal": 245858304, + "heapUsed": 108035624, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 410176 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 429752320, + "heapTotal": 248479744, + "heapUsed": 110647920, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 419808 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 431685632, + "heapTotal": 250314752, + "heapUsed": 112418896, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 421696 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 433717248, + "heapTotal": 252674048, + "heapUsed": 114070512, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 423680 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 446889984, + "heapTotal": 256393216, + "heapUsed": 110868184, + "external": 8091903, + "arrayBuffers": 2199826, + "maxRssKiB": 436544 + }, + { + "pid": 39906, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 117571584, + "heapTotal": 61751296, + "heapUsed": 25988664, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 114976 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 455049216, + "heapTotal": 252985344, + "heapUsed": 113417992, + "external": 6052011, + "arrayBuffers": 2208018, + "maxRssKiB": 444512 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 461815808, + "heapTotal": 254820352, + "heapUsed": 115969608, + "external": 6051951, + "arrayBuffers": 2208018, + "maxRssKiB": 451120 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 469221376, + "heapTotal": 256393216, + "heapUsed": 118493184, + "external": 6060201, + "arrayBuffers": 2208018, + "maxRssKiB": 458352 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 470319104, + "heapTotal": 257966080, + "heapUsed": 120006400, + "external": 6048610, + "arrayBuffers": 2208018, + "maxRssKiB": 459424 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 472055808, + "heapTotal": 259538944, + "heapUsed": 121532064, + "external": 6056802, + "arrayBuffers": 2208018, + "maxRssKiB": 461120 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 473858048, + "heapTotal": 261373952, + "heapUsed": 123052064, + "external": 6048299, + "arrayBuffers": 2208018, + "maxRssKiB": 462864 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 475693056, + "heapTotal": 263208960, + "heapUsed": 124542528, + "external": 6056491, + "arrayBuffers": 2208018, + "maxRssKiB": 464672 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 477593600, + "heapTotal": 265043968, + "heapUsed": 126095120, + "external": 6048298, + "arrayBuffers": 2208018, + "maxRssKiB": 466528 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 479395840, + "heapTotal": 266092544, + "heapUsed": 127613288, + "external": 6056490, + "arrayBuffers": 2208018, + "maxRssKiB": 468288 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 488521728, + "heapTotal": 267665408, + "heapUsed": 119923752, + "external": 8108331, + "arrayBuffers": 2191634, + "maxRssKiB": 477648 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 495763456, + "heapTotal": 261373952, + "heapUsed": 122438808, + "external": 6043861, + "arrayBuffers": 2199826, + "maxRssKiB": 484272 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 502530048, + "heapTotal": 263471104, + "heapUsed": 125000928, + "external": 6043801, + "arrayBuffers": 2199826, + "maxRssKiB": 490880 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 508411904, + "heapTotal": 265830400, + "heapUsed": 127517752, + "external": 6052051, + "arrayBuffers": 2199826, + "maxRssKiB": 496624 + }, + { + "pid": 39906, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 118112256, + "heapTotal": 62799872, + "heapUsed": 27053528, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 115344 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 509247488, + "heapTotal": 267403264, + "heapUsed": 129022216, + "external": 6040430, + "arrayBuffers": 2199826, + "maxRssKiB": 497440 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 509984768, + "heapTotal": 268976128, + "heapUsed": 130522920, + "external": 6048622, + "arrayBuffers": 2199826, + "maxRssKiB": 498160 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 511148032, + "heapTotal": 270811136, + "heapUsed": 132003456, + "external": 6040119, + "arrayBuffers": 2199826, + "maxRssKiB": 499296 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 513097728, + "heapTotal": 271859712, + "heapUsed": 133480552, + "external": 6048311, + "arrayBuffers": 2199826, + "maxRssKiB": 501200 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 514834432, + "heapTotal": 273432576, + "heapUsed": 134988704, + "external": 6040118, + "arrayBuffers": 2199826, + "maxRssKiB": 502896 + }, + { + "pid": 39907, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 516456448, + "heapTotal": 275005440, + "heapUsed": 136486200, + "external": 6048310, + "arrayBuffers": 2199826, + "maxRssKiB": 504480 + } + ], + "files": [ + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.21 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 0.483 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.999 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.505 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (121 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (130 MB heap size)" + ] + }, + { + "id": "6-measured-2-cli", + "phase": "measured", + "mode": "cli", + "workers": 2, + "wallSeconds": 13.360745750000001, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116768768, + "heapTotal": 60702720, + "heapUsed": 24956880, + "external": 5717873, + "arrayBuffers": 2056770, + "maxRssKiB": 114272 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116129792, + "heapTotal": 60440576, + "heapUsed": 24827304, + "external": 5717585, + "arrayBuffers": 2056770, + "maxRssKiB": 113536 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 117489664, + "heapTotal": 61751296, + "heapUsed": 25983440, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 114736 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116916224, + "heapTotal": 61227008, + "heapUsed": 25859552, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 114176 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 118128640, + "heapTotal": 62537728, + "heapUsed": 27056768, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 115360 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117407744, + "heapTotal": 62537728, + "heapUsed": 26924088, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 114656 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 124944384, + "heapTotal": 66519040, + "heapUsed": 29300264, + "external": 7763348, + "arrayBuffers": 2073154, + "maxRssKiB": 122144 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118161408, + "heapTotal": 63586304, + "heapUsed": 28004336, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 115392 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 125599744, + "heapTotal": 67567616, + "heapUsed": 30337656, + "external": 5715239, + "arrayBuffers": 2081346, + "maxRssKiB": 122656 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118816768, + "heapTotal": 65159168, + "heapUsed": 29061296, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 116032 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 125992960, + "heapTotal": 68616192, + "heapUsed": 31382288, + "external": 5723400, + "arrayBuffers": 2081346, + "maxRssKiB": 123040 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119439360, + "heapTotal": 66207744, + "heapUsed": 30089096, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 116640 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 129171456, + "heapTotal": 103219200, + "heapUsed": 32444944, + "external": 5715237, + "arrayBuffers": 2081346, + "maxRssKiB": 126144 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120242176, + "heapTotal": 67256320, + "heapUsed": 31112584, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 117424 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 129531904, + "heapTotal": 104005632, + "heapUsed": 33491216, + "external": 5722276, + "arrayBuffers": 2081346, + "maxRssKiB": 126496 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120946688, + "heapTotal": 68567040, + "heapUsed": 32138120, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 118112 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 130088960, + "heapTotal": 105054208, + "heapUsed": 34538168, + "external": 5714084, + "arrayBuffers": 2081346, + "maxRssKiB": 127040 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121716736, + "heapTotal": 69615616, + "heapUsed": 33217352, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 118864 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 130744320, + "heapTotal": 106102784, + "heapUsed": 35564176, + "external": 5722120, + "arrayBuffers": 2081346, + "maxRssKiB": 127680 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122437632, + "heapTotal": 70926336, + "heapUsed": 34247392, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 119568 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 131481600, + "heapTotal": 107413504, + "heapUsed": 36587616, + "external": 5713928, + "arrayBuffers": 2081346, + "maxRssKiB": 128400 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 139591680, + "heapTotal": 103743488, + "heapUsed": 29496160, + "external": 7755128, + "arrayBuffers": 2073154, + "maxRssKiB": 136448 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 132218880, + "heapTotal": 108724224, + "heapUsed": 37639560, + "external": 5722119, + "arrayBuffers": 2081346, + "maxRssKiB": 129120 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 139837440, + "heapTotal": 100859904, + "heapUsed": 30485536, + "external": 5715213, + "arrayBuffers": 2081346, + "maxRssKiB": 136560 + }, + { + "pid": 40018, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 132841472, + "heapTotal": 109772800, + "heapUsed": 38669104, + "external": 5722119, + "arrayBuffers": 2081346, + "maxRssKiB": 129728 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 139853824, + "heapTotal": 101908480, + "heapUsed": 31521256, + "external": 5723374, + "arrayBuffers": 2081346, + "maxRssKiB": 136576 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 139853824, + "heapTotal": 102170624, + "heapUsed": 32565976, + "external": 5715211, + "arrayBuffers": 2081346, + "maxRssKiB": 136576 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 139870208, + "heapTotal": 103481344, + "heapUsed": 33619376, + "external": 5722268, + "arrayBuffers": 2081346, + "maxRssKiB": 136592 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 139919360, + "heapTotal": 104529920, + "heapUsed": 34648152, + "external": 5714076, + "arrayBuffers": 2081346, + "maxRssKiB": 136656 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140378112, + "heapTotal": 105840640, + "heapUsed": 35668144, + "external": 5722112, + "arrayBuffers": 2081346, + "maxRssKiB": 137088 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 141131776, + "heapTotal": 106889216, + "heapUsed": 36694488, + "external": 5713920, + "arrayBuffers": 2081346, + "maxRssKiB": 137824 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141836288, + "heapTotal": 107937792, + "heapUsed": 37734736, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 138512 + }, + { + "pid": 40019, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 142540800, + "heapTotal": 109248512, + "heapUsed": 38764728, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 139200 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.963 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.566 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.453 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.336 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.566 s, 32 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.453 s, 36 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.336 s, 36 MB heap size)" + ] + }, + { + "id": "7-measured-2-cli", + "phase": "measured", + "mode": "cli", + "workers": 2, + "wallSeconds": 13.366349958000006, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116834304, + "heapTotal": 60440576, + "heapUsed": 24831016, + "external": 5717585, + "arrayBuffers": 2056770, + "maxRssKiB": 114288 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115916800, + "heapTotal": 60178432, + "heapUsed": 24826624, + "external": 5717624, + "arrayBuffers": 2056770, + "maxRssKiB": 113440 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116572160, + "heapTotal": 60964864, + "heapUsed": 25861208, + "external": 5698855, + "arrayBuffers": 2064962, + "maxRssKiB": 113840 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117506048, + "heapTotal": 61227008, + "heapUsed": 25862080, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 114752 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118194176, + "heapTotal": 62537728, + "heapUsed": 26924472, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 115424 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117112832, + "heapTotal": 62537728, + "heapUsed": 26925168, + "external": 5707016, + "arrayBuffers": 2064962, + "maxRssKiB": 114368 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117702656, + "heapTotal": 63324160, + "heapUsed": 28000552, + "external": 5698853, + "arrayBuffers": 2064962, + "maxRssKiB": 114944 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118898688, + "heapTotal": 63848448, + "heapUsed": 28002448, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 116112 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118472704, + "heapTotal": 64634880, + "heapUsed": 29071544, + "external": 5705892, + "arrayBuffers": 2064962, + "maxRssKiB": 115696 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119586816, + "heapTotal": 65159168, + "heapUsed": 29059712, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 116784 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119128064, + "heapTotal": 65683456, + "heapUsed": 30098392, + "external": 5697700, + "arrayBuffers": 2064962, + "maxRssKiB": 116336 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 120307712, + "heapTotal": 66207744, + "heapUsed": 30092120, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 117488 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119865344, + "heapTotal": 66994176, + "heapUsed": 31123224, + "external": 5705736, + "arrayBuffers": 2064962, + "maxRssKiB": 117056 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120979456, + "heapTotal": 67256320, + "heapUsed": 31115032, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 118144 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120487936, + "heapTotal": 68042752, + "heapUsed": 32146848, + "external": 5697544, + "arrayBuffers": 2064962, + "maxRssKiB": 117664 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121733120, + "heapTotal": 68567040, + "heapUsed": 32139064, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 118880 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121307136, + "heapTotal": 69091328, + "heapUsed": 33211728, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 118464 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122454016, + "heapTotal": 69615616, + "heapUsed": 33217880, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 119584 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121995264, + "heapTotal": 70402048, + "heapUsed": 34246144, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 119136 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123158528, + "heapTotal": 70926336, + "heapUsed": 34247936, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 120272 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 139902976, + "heapTotal": 105840640, + "heapUsed": 29633512, + "external": 7755405, + "arrayBuffers": 2073154, + "maxRssKiB": 136752 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 138559488, + "heapTotal": 105578496, + "heapUsed": 29494720, + "external": 7755128, + "arrayBuffers": 2073154, + "maxRssKiB": 135440 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 140410880, + "heapTotal": 100597760, + "heapUsed": 30613048, + "external": 5715457, + "arrayBuffers": 2093433, + "maxRssKiB": 137120 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 139313152, + "heapTotal": 100597760, + "heapUsed": 30482664, + "external": 5715213, + "arrayBuffers": 2081346, + "maxRssKiB": 136048 + }, + { + "pid": 40254, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 140886016, + "heapTotal": 101384192, + "heapUsed": 31645656, + "external": 5716062, + "arrayBuffers": 2081346, + "maxRssKiB": 137584 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 139575296, + "heapTotal": 101384192, + "heapUsed": 31521264, + "external": 5723374, + "arrayBuffers": 2081346, + "maxRssKiB": 136304 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 139591680, + "heapTotal": 101908480, + "heapUsed": 32563848, + "external": 5715211, + "arrayBuffers": 2081346, + "maxRssKiB": 136320 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 139640832, + "heapTotal": 103219200, + "heapUsed": 33617984, + "external": 5722268, + "arrayBuffers": 2081346, + "maxRssKiB": 136368 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 139640832, + "heapTotal": 104529920, + "heapUsed": 34644984, + "external": 5714076, + "arrayBuffers": 2081346, + "maxRssKiB": 136432 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 139673600, + "heapTotal": 105578496, + "heapUsed": 35667208, + "external": 5722112, + "arrayBuffers": 2081346, + "maxRssKiB": 136432 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 139706368, + "heapTotal": 106627072, + "heapUsed": 36687584, + "external": 5713920, + "arrayBuffers": 2081346, + "maxRssKiB": 136432 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 139902976, + "heapTotal": 107675648, + "heapUsed": 37732392, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 136624 + }, + { + "pid": 40255, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 140558336, + "heapTotal": 108724224, + "heapUsed": 38761920, + "external": 5722111, + "arrayBuffers": 2081346, + "maxRssKiB": 137280 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.397 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.464 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.988 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.584 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.397 s, 32 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.464 s, 32 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (30 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.584 s, 36 MB heap size)" + ] + }, + { + "id": "8-measured-2-in-process", + "phase": "measured", + "mode": "in-process", + "workers": 2, + "wallSeconds": 3.6841440409999997, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358400000, + "heapTotal": 230916096, + "heapUsed": 92235168, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 350128 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357629952, + "heapTotal": 231702528, + "heapUsed": 92239360, + "external": 6002361, + "arrayBuffers": 2177217, + "maxRssKiB": 349376 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 370524160, + "heapTotal": 233275392, + "heapUsed": 94968480, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 361968 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 370933760, + "heapTotal": 233275392, + "heapUsed": 94964032, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 362368 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383516672, + "heapTotal": 235634688, + "heapUsed": 97697000, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 374656 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 384925696, + "heapTotal": 235372544, + "heapUsed": 97690352, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 376032 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 396279808, + "heapTotal": 237993984, + "heapUsed": 100283088, + "external": 6035625, + "arrayBuffers": 2183442, + "maxRssKiB": 387120 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 393543680, + "heapTotal": 238256128, + "heapUsed": 100283144, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 384448 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 398065664, + "heapTotal": 239566848, + "heapUsed": 101894968, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 388864 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 402391040, + "heapTotal": 240877568, + "heapUsed": 102922728, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 393088 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 399982592, + "heapTotal": 241139712, + "heapUsed": 103462304, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 390736 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 412106752, + "heapTotal": 243499008, + "heapUsed": 105529304, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 402576 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 401899520, + "heapTotal": 242974720, + "heapUsed": 104935776, + "external": 6023723, + "arrayBuffers": 2183442, + "maxRssKiB": 392608 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 403750912, + "heapTotal": 244285440, + "heapUsed": 106505720, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 394416 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 423608320, + "heapTotal": 246120448, + "heapUsed": 108036896, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 413808 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 405864448, + "heapTotal": 246382592, + "heapUsed": 108270688, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 396480 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 432111616, + "heapTotal": 248741888, + "heapUsed": 110648976, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 422112 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 407699456, + "heapTotal": 247955456, + "heapUsed": 109919032, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 398272 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 434208768, + "heapTotal": 250576896, + "heapUsed": 112420032, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 424160 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 436224000, + "heapTotal": 252411904, + "heapUsed": 114073656, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 426128 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 421773312, + "heapTotal": 251150336, + "heapUsed": 106719656, + "external": 8083756, + "arrayBuffers": 2191634, + "maxRssKiB": 412016 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 430080000, + "heapTotal": 247218176, + "heapUsed": 109276016, + "external": 6043861, + "arrayBuffers": 2199826, + "maxRssKiB": 420128 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 436928512, + "heapTotal": 249577472, + "heapUsed": 111830208, + "external": 6043801, + "arrayBuffers": 2199826, + "maxRssKiB": 426816 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 443875328, + "heapTotal": 252198912, + "heapUsed": 114353936, + "external": 6052051, + "arrayBuffers": 2199826, + "maxRssKiB": 433600 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 445464576, + "heapTotal": 253509632, + "heapUsed": 115859120, + "external": 6040430, + "arrayBuffers": 2199826, + "maxRssKiB": 435152 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 447234048, + "heapTotal": 255344640, + "heapUsed": 117389016, + "external": 6048622, + "arrayBuffers": 2199826, + "maxRssKiB": 436880 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 448970752, + "heapTotal": 256917504, + "heapUsed": 118910032, + "external": 6040119, + "arrayBuffers": 2199826, + "maxRssKiB": 438576 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 450691072, + "heapTotal": 258490368, + "heapUsed": 120399584, + "external": 6048311, + "arrayBuffers": 2199826, + "maxRssKiB": 440256 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 452591616, + "heapTotal": 260325376, + "heapUsed": 121946992, + "external": 6040118, + "arrayBuffers": 2199826, + "maxRssKiB": 442112 + }, + { + "pid": 40508, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 454443008, + "heapTotal": 261898240, + "heapUsed": 123472160, + "external": 6048310, + "arrayBuffers": 2199826, + "maxRssKiB": 443920 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 440025088, + "heapTotal": 255082496, + "heapUsed": 109329312, + "external": 8082215, + "arrayBuffers": 2199826, + "maxRssKiB": 429840 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 440631296, + "heapTotal": 249315328, + "heapUsed": 110314600, + "external": 6042260, + "arrayBuffers": 2208018, + "maxRssKiB": 430304 + }, + { + "pid": 40509, + "worker": "2", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 440713216, + "heapTotal": 250101760, + "heapUsed": 111361232, + "external": 6042865, + "arrayBuffers": 2208018, + "maxRssKiB": 430384 + } + ], + "files": [ + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.208 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.292 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.486 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.908 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (117 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (106 MB heap size)" + ] + }, + { + "id": "9-warmup-8-cli", + "phase": "warmup", + "mode": "cli", + "workers": 8, + "wallSeconds": 7.281006332999998, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 40605, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116490240, + "heapTotal": 60964864, + "heapUsed": 24955328, + "external": 5717870, + "arrayBuffers": 2056770, + "maxRssKiB": 113888 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115425280, + "heapTotal": 60702720, + "heapUsed": 24826928, + "external": 5717582, + "arrayBuffers": 2056770, + "maxRssKiB": 112848 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 117030912, + "heapTotal": 60702720, + "heapUsed": 24831088, + "external": 5717593, + "arrayBuffers": 2056770, + "maxRssKiB": 114464 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116686848, + "heapTotal": 60964864, + "heapUsed": 24830688, + "external": 5717621, + "arrayBuffers": 2056770, + "maxRssKiB": 114144 + }, + { + "pid": 40605, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 117260288, + "heapTotal": 62013440, + "heapUsed": 25982504, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 114512 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116178944, + "heapTotal": 61227008, + "heapUsed": 25861624, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 113456 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117735424, + "heapTotal": 61227008, + "heapUsed": 25862800, + "external": 5698829, + "arrayBuffers": 2064962, + "maxRssKiB": 114976 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117260288, + "heapTotal": 61227008, + "heapUsed": 25861304, + "external": 5698855, + "arrayBuffers": 2064962, + "maxRssKiB": 114512 + }, + { + "pid": 40605, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117882880, + "heapTotal": 63062016, + "heapUsed": 27053456, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 115120 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118292480, + "heapTotal": 62275584, + "heapUsed": 26925536, + "external": 5706990, + "arrayBuffers": 2064962, + "maxRssKiB": 115520 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117784576, + "heapTotal": 62275584, + "heapUsed": 26924392, + "external": 5707016, + "arrayBuffers": 2064962, + "maxRssKiB": 115024 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116948992, + "heapTotal": 62275584, + "heapUsed": 26924296, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 114208 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118865920, + "heapTotal": 63586304, + "heapUsed": 28001840, + "external": 5698827, + "arrayBuffers": 2064962, + "maxRssKiB": 116080 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118439936, + "heapTotal": 63324160, + "heapUsed": 28001256, + "external": 5698853, + "arrayBuffers": 2064962, + "maxRssKiB": 115664 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117538816, + "heapTotal": 63324160, + "heapUsed": 28001920, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 114784 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119226368, + "heapTotal": 64634880, + "heapUsed": 29070360, + "external": 5705892, + "arrayBuffers": 2064962, + "maxRssKiB": 116432 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119586816, + "heapTotal": 64634880, + "heapUsed": 29071152, + "external": 5705884, + "arrayBuffers": 2064962, + "maxRssKiB": 116784 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118243328, + "heapTotal": 64897024, + "heapUsed": 29061824, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 115472 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 120160256, + "heapTotal": 66207744, + "heapUsed": 30101776, + "external": 5697692, + "arrayBuffers": 2064962, + "maxRssKiB": 117344 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119799808, + "heapTotal": 65421312, + "heapUsed": 30101336, + "external": 5697700, + "arrayBuffers": 2064962, + "maxRssKiB": 116992 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 118849536, + "heapTotal": 65945600, + "heapUsed": 30091384, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 116064 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120520704, + "heapTotal": 66732032, + "heapUsed": 31124120, + "external": 5705736, + "arrayBuffers": 2083712, + "maxRssKiB": 117696 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120897536, + "heapTotal": 67256320, + "heapUsed": 31126480, + "external": 5705728, + "arrayBuffers": 2064962, + "maxRssKiB": 118064 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119619584, + "heapTotal": 66994176, + "heapUsed": 31114760, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 116816 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121536512, + "heapTotal": 68042752, + "heapUsed": 32146696, + "external": 5697536, + "arrayBuffers": 2064962, + "maxRssKiB": 118704 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121307136, + "heapTotal": 68042752, + "heapUsed": 32148112, + "external": 5697544, + "arrayBuffers": 2064962, + "maxRssKiB": 118464 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120406016, + "heapTotal": 68304896, + "heapUsed": 32138968, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 117584 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122109952, + "heapTotal": 69091328, + "heapUsed": 33211856, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 119248 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122290176, + "heapTotal": 69353472, + "heapUsed": 33213592, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 119424 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121176064, + "heapTotal": 69615616, + "heapUsed": 33216440, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 118336 + }, + { + "pid": 40608, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122667008, + "heapTotal": 70139904, + "heapUsed": 34246144, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 119792 + }, + { + "pid": 40607, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123060224, + "heapTotal": 70664192, + "heapUsed": 34247912, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 120176 + }, + { + "pid": 40606, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121896960, + "heapTotal": 70664192, + "heapUsed": 34246992, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 119040 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.087 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.786 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.804 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.863 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.786 s, 32 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.804 s, 32 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.863 s, 32 MB heap size)" + ] + }, + { + "id": "10-warmup-8-in-process", + "phase": "warmup", + "mode": "in-process", + "workers": 8, + "wallSeconds": 2.600871750000006, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 40792, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116015104, + "heapTotal": 60702720, + "heapUsed": 24930392, + "external": 5717878, + "arrayBuffers": 2056770, + "maxRssKiB": 113424 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 359546880, + "heapTotal": 231702528, + "heapUsed": 92237624, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 351248 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 355565568, + "heapTotal": 231702528, + "heapUsed": 92236704, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 347360 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357761024, + "heapTotal": 231440384, + "heapUsed": 92240200, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 349504 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 372228096, + "heapTotal": 233537536, + "heapUsed": 94970112, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 363632 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 368476160, + "heapTotal": 233799680, + "heapUsed": 94966624, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 359968 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 373424128, + "heapTotal": 233275392, + "heapUsed": 94965472, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 364800 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 384335872, + "heapTotal": 235896832, + "heapUsed": 97693088, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 375456 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 384253952, + "heapTotal": 235372544, + "heapUsed": 97689760, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 375376 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 378159104, + "heapTotal": 236158976, + "heapUsed": 97692848, + "external": 6027417, + "arrayBuffers": 2183442, + "maxRssKiB": 369424 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 394592256, + "heapTotal": 238518272, + "heapUsed": 100286344, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 385472 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 394952704, + "heapTotal": 238518272, + "heapUsed": 100283656, + "external": 6035625, + "arrayBuffers": 2183442, + "maxRssKiB": 385824 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 390316032, + "heapTotal": 238518272, + "heapUsed": 100285552, + "external": 6035667, + "arrayBuffers": 2183442, + "maxRssKiB": 381296 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 392167424, + "heapTotal": 240091136, + "heapUsed": 101895744, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 383104 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 396836864, + "heapTotal": 240091136, + "heapUsed": 101894072, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 387664 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 404160512, + "heapTotal": 241139712, + "heapUsed": 102924800, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 394816 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 398622720, + "heapTotal": 241664000, + "heapUsed": 103461312, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 389408 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 393936896, + "heapTotal": 241664000, + "heapUsed": 103462664, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 384832 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 396017664, + "heapTotal": 243236864, + "heapUsed": 104951296, + "external": 6023735, + "arrayBuffers": 2183442, + "maxRssKiB": 386864 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 411975680, + "heapTotal": 243761152, + "heapUsed": 105530616, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 402448 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 400687104, + "heapTotal": 243499008, + "heapUsed": 104937584, + "external": 6023723, + "arrayBuffers": 2183442, + "maxRssKiB": 391424 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 397770752, + "heapTotal": 245071872, + "heapUsed": 106507728, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 388576 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 402505728, + "heapTotal": 245071872, + "heapUsed": 106504784, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 393200 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 422395904, + "heapTotal": 246120448, + "heapUsed": 108043656, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 412624 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 399753216, + "heapTotal": 246644736, + "heapUsed": 108271360, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 390512 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 404586496, + "heapTotal": 247169024, + "heapUsed": 108268208, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 395232 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 431865856, + "heapTotal": 248741888, + "heapUsed": 110650096, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 421872 + }, + { + "pid": 40791, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 401604608, + "heapTotal": 248479744, + "heapUsed": 109918888, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 392320 + }, + { + "pid": 40790, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 406552576, + "heapTotal": 248741888, + "heapUsed": 109917112, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 397152 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 433848320, + "heapTotal": 250576896, + "heapUsed": 112421176, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 423808 + }, + { + "pid": 40789, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 435814400, + "heapTotal": 252411904, + "heapUsed": 114074872, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 425728 + }, + { + "pid": 40792, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116785152, + "heapTotal": 61751296, + "heapUsed": 25962472, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 114048 + }, + { + "pid": 40792, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117358592, + "heapTotal": 62537728, + "heapUsed": 27033456, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114608 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.262 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.269 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.346 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.069 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)" + ] + }, + { + "id": "11-measured-8-cli", + "phase": "measured", + "mode": "cli", + "workers": 8, + "wallSeconds": 7.272388999999996, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 40839, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115392512, + "heapTotal": 60964864, + "heapUsed": 24929496, + "external": 5717874, + "arrayBuffers": 2056770, + "maxRssKiB": 112816 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 114884608, + "heapTotal": 60440576, + "heapUsed": 24825728, + "external": 5717625, + "arrayBuffers": 2056770, + "maxRssKiB": 112416 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116359168, + "heapTotal": 60440576, + "heapUsed": 24830264, + "external": 5717597, + "arrayBuffers": 2056770, + "maxRssKiB": 113856 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116047872, + "heapTotal": 60702720, + "heapUsed": 24827744, + "external": 5717586, + "arrayBuffers": 2056770, + "maxRssKiB": 113456 + }, + { + "pid": 40839, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116047872, + "heapTotal": 62013440, + "heapUsed": 25961816, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113328 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 115638272, + "heapTotal": 61489152, + "heapUsed": 25859544, + "external": 5698855, + "arrayBuffers": 2064962, + "maxRssKiB": 112944 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117080064, + "heapTotal": 61489152, + "heapUsed": 25861256, + "external": 5698829, + "arrayBuffers": 2064962, + "maxRssKiB": 114336 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116572160, + "heapTotal": 61227008, + "heapUsed": 25861352, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 113840 + }, + { + "pid": 40839, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 116686848, + "heapTotal": 62799872, + "heapUsed": 27032672, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 113952 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117014528, + "heapTotal": 62275584, + "heapUsed": 26924360, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 114272 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117604352, + "heapTotal": 62275584, + "heapUsed": 26924328, + "external": 5706990, + "arrayBuffers": 2064962, + "maxRssKiB": 114848 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116064256, + "heapTotal": 62013440, + "heapUsed": 26924072, + "external": 5707016, + "arrayBuffers": 2064962, + "maxRssKiB": 113344 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117768192, + "heapTotal": 63586304, + "heapUsed": 28000976, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 115008 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118308864, + "heapTotal": 63586304, + "heapUsed": 28000736, + "external": 5698827, + "arrayBuffers": 2064962, + "maxRssKiB": 115536 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 116768768, + "heapTotal": 63324160, + "heapUsed": 28000936, + "external": 5698853, + "arrayBuffers": 2064962, + "maxRssKiB": 114032 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 117571584, + "heapTotal": 64372736, + "heapUsed": 29071608, + "external": 5705892, + "arrayBuffers": 2064962, + "maxRssKiB": 114816 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119029760, + "heapTotal": 64634880, + "heapUsed": 29070048, + "external": 5705884, + "arrayBuffers": 2064962, + "maxRssKiB": 116240 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118358016, + "heapTotal": 64897024, + "heapUsed": 29059456, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 115584 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119717888, + "heapTotal": 65683456, + "heapUsed": 30099024, + "external": 5697692, + "arrayBuffers": 2064962, + "maxRssKiB": 116912 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118243328, + "heapTotal": 65683456, + "heapUsed": 30101624, + "external": 5697700, + "arrayBuffers": 2064962, + "maxRssKiB": 115472 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119111680, + "heapTotal": 65945600, + "heapUsed": 30089256, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 116320 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 118996992, + "heapTotal": 66994176, + "heapUsed": 31124792, + "external": 5705736, + "arrayBuffers": 2064962, + "maxRssKiB": 116208 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120373248, + "heapTotal": 66469888, + "heapUsed": 31123024, + "external": 5705728, + "arrayBuffers": 2064962, + "maxRssKiB": 117552 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119767040, + "heapTotal": 67256320, + "heapUsed": 31114344, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 116960 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 119652352, + "heapTotal": 68042752, + "heapUsed": 32147424, + "external": 5697544, + "arrayBuffers": 2064962, + "maxRssKiB": 116848 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121094144, + "heapTotal": 67780608, + "heapUsed": 32148440, + "external": 5697536, + "arrayBuffers": 2064962, + "maxRssKiB": 118256 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120389632, + "heapTotal": 68304896, + "heapUsed": 32138488, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 117568 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121880576, + "heapTotal": 69091328, + "heapUsed": 33211720, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 119024 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120455168, + "heapTotal": 69091328, + "heapUsed": 33211936, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 117632 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121208832, + "heapTotal": 69353472, + "heapUsed": 33217624, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 118368 + }, + { + "pid": 40841, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122585088, + "heapTotal": 70402048, + "heapUsed": 34246280, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 119712 + }, + { + "pid": 40842, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121176064, + "heapTotal": 70402048, + "heapUsed": 34246016, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 118336 + }, + { + "pid": 40840, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121929728, + "heapTotal": 70664192, + "heapUsed": 34247792, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 119072 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.192 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.815 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.815 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.892 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.815 s, 32 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.815 s, 32 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.892 s, 32 MB heap size)" + ] + }, + { + "id": "12-measured-8-in-process", + "phase": "measured", + "mode": "in-process", + "workers": 8, + "wallSeconds": 2.561761750000005, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 41056, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115720192, + "heapTotal": 61489152, + "heapUsed": 24950064, + "external": 5717880, + "arrayBuffers": 2056770, + "maxRssKiB": 113136 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 361103360, + "heapTotal": 230653952, + "heapUsed": 92238072, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 352768 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358793216, + "heapTotal": 230653952, + "heapUsed": 92236968, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 350512 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 355434496, + "heapTotal": 232488960, + "heapUsed": 92237736, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 347232 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 374177792, + "heapTotal": 232751104, + "heapUsed": 94964680, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 365536 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 368836608, + "heapTotal": 233799680, + "heapUsed": 94968808, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 360320 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371064832, + "heapTotal": 232751104, + "heapUsed": 94969136, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 362496 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 387022848, + "heapTotal": 234848256, + "heapUsed": 97690784, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 378080 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 380059648, + "heapTotal": 235634688, + "heapUsed": 97694448, + "external": 6027417, + "arrayBuffers": 2183442, + "maxRssKiB": 371280 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 382894080, + "heapTotal": 235634688, + "heapUsed": 97691184, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 374048 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 397737984, + "heapTotal": 237731840, + "heapUsed": 100283352, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 388544 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 392019968, + "heapTotal": 238256128, + "heapUsed": 100284560, + "external": 6035667, + "arrayBuffers": 2183442, + "maxRssKiB": 382960 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 394838016, + "heapTotal": 238256128, + "heapUsed": 100283048, + "external": 6035625, + "arrayBuffers": 2183442, + "maxRssKiB": 385712 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 396722176, + "heapTotal": 239828992, + "heapUsed": 101895576, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 387552 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 393986048, + "heapTotal": 240091136, + "heapUsed": 101895768, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 384880 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 407633920, + "heapTotal": 240615424, + "heapUsed": 102922552, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 398208 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 398524416, + "heapTotal": 241664000, + "heapUsed": 103462368, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 389312 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 395788288, + "heapTotal": 241926144, + "heapUsed": 103461920, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 386640 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 400490496, + "heapTotal": 242974720, + "heapUsed": 104940152, + "external": 6023723, + "arrayBuffers": 2183442, + "maxRssKiB": 391232 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 417120256, + "heapTotal": 242974720, + "heapUsed": 105529584, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 407472 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 397885440, + "heapTotal": 243236864, + "heapUsed": 104951160, + "external": 6023735, + "arrayBuffers": 2196071, + "maxRssKiB": 388688 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 402309120, + "heapTotal": 244285440, + "heapUsed": 106505672, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 393008 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 399638528, + "heapTotal": 244547584, + "heapUsed": 106502768, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 390400 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 427130880, + "heapTotal": 245596160, + "heapUsed": 108042136, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 417248 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 404619264, + "heapTotal": 246382592, + "heapUsed": 108269240, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 395264 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 401833984, + "heapTotal": 246644736, + "heapUsed": 108267160, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 392544 + }, + { + "pid": 41055, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 403701760, + "heapTotal": 248479744, + "heapUsed": 109916208, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 394368 + }, + { + "pid": 41054, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 406421504, + "heapTotal": 248479744, + "heapUsed": 109918608, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 397024 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 438140928, + "heapTotal": 247955456, + "heapUsed": 110649128, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 428000 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 440156160, + "heapTotal": 249790464, + "heapUsed": 112419664, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 429968 + }, + { + "pid": 41053, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 442335232, + "heapTotal": 251887616, + "heapUsed": 114073504, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 432096 + }, + { + "pid": 41056, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116342784, + "heapTotal": 61751296, + "heapUsed": 25981424, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113616 + }, + { + "pid": 41056, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 116867072, + "heapTotal": 63062016, + "heapUsed": 27052640, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114128 + } + ], + "files": [ + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.292 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.289 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.387 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.096 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)" + ] + }, + { + "id": "13-measured-8-in-process", + "phase": "measured", + "mode": "in-process", + "workers": 8, + "wallSeconds": 2.809627999999997, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 41103, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115212288, + "heapTotal": 60964864, + "heapUsed": 24929440, + "external": 5717880, + "arrayBuffers": 2056770, + "maxRssKiB": 112640 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358989824, + "heapTotal": 231440384, + "heapUsed": 92239000, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 350704 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 352567296, + "heapTotal": 230916096, + "heapUsed": 92239000, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 344432 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 359612416, + "heapTotal": 231964672, + "heapUsed": 92224664, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 351312 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 372293632, + "heapTotal": 233275392, + "heapUsed": 94950792, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 363696 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 365346816, + "heapTotal": 233275392, + "heapUsed": 94968528, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 356912 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 370999296, + "heapTotal": 233537536, + "heapUsed": 94974024, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 362432 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 375226368, + "heapTotal": 235896832, + "heapUsed": 97692232, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 366560 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383270912, + "heapTotal": 235634688, + "heapUsed": 97682904, + "external": 6027417, + "arrayBuffers": 2183442, + "maxRssKiB": 374416 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383025152, + "heapTotal": 235896832, + "heapUsed": 97692472, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 374176 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 389677056, + "heapTotal": 237993984, + "heapUsed": 100284872, + "external": 6035625, + "arrayBuffers": 2210406, + "maxRssKiB": 380672 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 395886592, + "heapTotal": 237993984, + "heapUsed": 100284120, + "external": 6035667, + "arrayBuffers": 2210426, + "maxRssKiB": 386736 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 393478144, + "heapTotal": 238256128, + "heapUsed": 100285080, + "external": 6035612, + "arrayBuffers": 2210399, + "maxRssKiB": 384384 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 397705216, + "heapTotal": 240091136, + "heapUsed": 101896008, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 388512 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 391528448, + "heapTotal": 239828992, + "heapUsed": 101897032, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 382480 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 404471808, + "heapTotal": 240615424, + "heapUsed": 102924544, + "external": 6035867, + "arrayBuffers": 2210528, + "maxRssKiB": 395120 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 399425536, + "heapTotal": 241664000, + "heapUsed": 103462320, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 390192 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 393117696, + "heapTotal": 241139712, + "heapUsed": 103459552, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 384032 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 401326080, + "heapTotal": 242974720, + "heapUsed": 104950808, + "external": 6023735, + "arrayBuffers": 2201415, + "maxRssKiB": 392048 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 394985472, + "heapTotal": 242712576, + "heapUsed": 104941688, + "external": 6023723, + "arrayBuffers": 2183442, + "maxRssKiB": 385856 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 414154752, + "heapTotal": 243499008, + "heapUsed": 105531288, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 404576 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 403013632, + "heapTotal": 244547584, + "heapUsed": 106502192, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 393696 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 396754944, + "heapTotal": 244285440, + "heapUsed": 106507208, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 387584 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 398819328, + "heapTotal": 246120448, + "heapUsed": 108271272, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 389600 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 404996096, + "heapTotal": 246382592, + "heapUsed": 108269256, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 395632 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 423198720, + "heapTotal": 245596160, + "heapUsed": 108034504, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 413408 + }, + { + "pid": 41106, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 406978560, + "heapTotal": 248217600, + "heapUsed": 109917960, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 397568 + }, + { + "pid": 41105, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 400703488, + "heapTotal": 247955456, + "heapUsed": 109920032, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 391440 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 433307648, + "heapTotal": 248217600, + "heapUsed": 110643792, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 423280 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 435535872, + "heapTotal": 250052608, + "heapUsed": 112421216, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 425456 + }, + { + "pid": 41104, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 437485568, + "heapTotal": 251887616, + "heapUsed": 114073608, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 427360 + }, + { + "pid": 41103, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 115851264, + "heapTotal": 61489152, + "heapUsed": 25961960, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113136 + }, + { + "pid": 41103, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 116391936, + "heapTotal": 62537728, + "heapUsed": 27031984, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 113664 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.354 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.358 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.483 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.346 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)" + ] + }, + { + "id": "14-measured-8-cli", + "phase": "measured", + "mode": "cli", + "workers": 8, + "wallSeconds": 7.269705541000003, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 41233, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115556352, + "heapTotal": 60964864, + "heapUsed": 24951224, + "external": 5717873, + "arrayBuffers": 2056770, + "maxRssKiB": 112976 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116146176, + "heapTotal": 60178432, + "heapUsed": 24827568, + "external": 5717596, + "arrayBuffers": 2056770, + "maxRssKiB": 113552 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115294208, + "heapTotal": 60440576, + "heapUsed": 24826408, + "external": 5717624, + "arrayBuffers": 2056770, + "maxRssKiB": 112720 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116473856, + "heapTotal": 60440576, + "heapUsed": 24827328, + "external": 5717585, + "arrayBuffers": 2056770, + "maxRssKiB": 113968 + }, + { + "pid": 41233, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116195328, + "heapTotal": 61751296, + "heapUsed": 25981032, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113472 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117309440, + "heapTotal": 61489152, + "heapUsed": 25863848, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 114560 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 115965952, + "heapTotal": 61227008, + "heapUsed": 25861744, + "external": 5698855, + "arrayBuffers": 2064962, + "maxRssKiB": 113248 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116883456, + "heapTotal": 61227008, + "heapUsed": 25861472, + "external": 5698829, + "arrayBuffers": 2064962, + "maxRssKiB": 114144 + }, + { + "pid": 41233, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 116817920, + "heapTotal": 62799872, + "heapUsed": 27053560, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114080 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117833728, + "heapTotal": 62799872, + "heapUsed": 26927096, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 115072 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116408320, + "heapTotal": 62275584, + "heapUsed": 26924096, + "external": 5707016, + "arrayBuffers": 2064962, + "maxRssKiB": 113680 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117309440, + "heapTotal": 62275584, + "heapUsed": 26923960, + "external": 5706990, + "arrayBuffers": 2064962, + "maxRssKiB": 114560 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118472704, + "heapTotal": 63586304, + "heapUsed": 28001152, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 115696 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118063104, + "heapTotal": 63848448, + "heapUsed": 28000984, + "external": 5698827, + "arrayBuffers": 2064962, + "maxRssKiB": 115296 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117178368, + "heapTotal": 63586304, + "heapUsed": 28000888, + "external": 5698853, + "arrayBuffers": 2064962, + "maxRssKiB": 114432 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118767616, + "heapTotal": 64897024, + "heapUsed": 29069752, + "external": 5705884, + "arrayBuffers": 2064962, + "maxRssKiB": 115984 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 117817344, + "heapTotal": 64897024, + "heapUsed": 29070904, + "external": 5705892, + "arrayBuffers": 2064962, + "maxRssKiB": 115056 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119078912, + "heapTotal": 65159168, + "heapUsed": 29063440, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 116288 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119472128, + "heapTotal": 65945600, + "heapUsed": 30100216, + "external": 5697692, + "arrayBuffers": 2064962, + "maxRssKiB": 116672 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118489088, + "heapTotal": 65945600, + "heapUsed": 30374320, + "external": 5697700, + "arrayBuffers": 2064962, + "maxRssKiB": 115712 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119799808, + "heapTotal": 66469888, + "heapUsed": 30091816, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 116992 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120242176, + "heapTotal": 66994176, + "heapUsed": 31125144, + "external": 5705728, + "arrayBuffers": 2064962, + "maxRssKiB": 117424 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119209984, + "heapTotal": 66732032, + "heapUsed": 31122616, + "external": 5705736, + "arrayBuffers": 2064962, + "maxRssKiB": 116416 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120569856, + "heapTotal": 67518464, + "heapUsed": 31115048, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 117744 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120979456, + "heapTotal": 68304896, + "heapUsed": 32147024, + "external": 5697536, + "arrayBuffers": 2064962, + "maxRssKiB": 118144 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 119980032, + "heapTotal": 68042752, + "heapUsed": 32147264, + "external": 5697544, + "arrayBuffers": 2064962, + "maxRssKiB": 117168 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121323520, + "heapTotal": 68829184, + "heapUsed": 32138904, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 118480 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121765888, + "heapTotal": 69353472, + "heapUsed": 33211712, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 118912 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120750080, + "heapTotal": 69091328, + "heapUsed": 33211488, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 117920 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121978880, + "heapTotal": 69877760, + "heapUsed": 33216776, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 119120 + }, + { + "pid": 41236, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121339904, + "heapTotal": 70402048, + "heapUsed": 34244448, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 118496 + }, + { + "pid": 41235, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122454016, + "heapTotal": 70664192, + "heapUsed": 34245904, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 119584 + }, + { + "pid": 41234, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122748928, + "heapTotal": 71188480, + "heapUsed": 34247776, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 119872 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.18 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.782 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.78 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.847 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.782 s, 32 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.78 s, 32 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.847 s, 32 MB heap size)" + ] + }, + { + "id": "15-measured-8-cli", + "phase": "measured", + "mode": "cli", + "workers": 8, + "wallSeconds": 7.12136099999999, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 41371, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115769344, + "heapTotal": 61227008, + "heapUsed": 24951048, + "external": 5717874, + "arrayBuffers": 2056770, + "maxRssKiB": 113184 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115441664, + "heapTotal": 60964864, + "heapUsed": 24827488, + "external": 5717597, + "arrayBuffers": 2056770, + "maxRssKiB": 112944 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116375552, + "heapTotal": 60702720, + "heapUsed": 24827640, + "external": 5717625, + "arrayBuffers": 2056770, + "maxRssKiB": 113888 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115458048, + "heapTotal": 60702720, + "heapUsed": 24825944, + "external": 5717586, + "arrayBuffers": 2056770, + "maxRssKiB": 112880 + }, + { + "pid": 41371, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116670464, + "heapTotal": 61751296, + "heapUsed": 25981408, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113936 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116097024, + "heapTotal": 60964864, + "heapUsed": 25859544, + "external": 5698819, + "arrayBuffers": 2064962, + "maxRssKiB": 113376 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117211136, + "heapTotal": 61489152, + "heapUsed": 25860312, + "external": 5698855, + "arrayBuffers": 2064962, + "maxRssKiB": 114464 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116326400, + "heapTotal": 61227008, + "heapUsed": 25860488, + "external": 5698829, + "arrayBuffers": 2064962, + "maxRssKiB": 113600 + }, + { + "pid": 41371, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117211136, + "heapTotal": 62799872, + "heapUsed": 27052640, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114464 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117112832, + "heapTotal": 62537728, + "heapUsed": 26925960, + "external": 5706990, + "arrayBuffers": 2064962, + "maxRssKiB": 114368 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117850112, + "heapTotal": 62537728, + "heapUsed": 26926456, + "external": 5707016, + "arrayBuffers": 2064962, + "maxRssKiB": 115088 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116604928, + "heapTotal": 62013440, + "heapUsed": 26924088, + "external": 5706980, + "arrayBuffers": 2064962, + "maxRssKiB": 113872 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117784576, + "heapTotal": 63324160, + "heapUsed": 28003008, + "external": 5698827, + "arrayBuffers": 2064962, + "maxRssKiB": 115024 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118554624, + "heapTotal": 63586304, + "heapUsed": 28003344, + "external": 5698853, + "arrayBuffers": 2064962, + "maxRssKiB": 115776 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117293056, + "heapTotal": 63324160, + "heapUsed": 28001496, + "external": 5698817, + "arrayBuffers": 2064962, + "maxRssKiB": 114544 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119308288, + "heapTotal": 64634880, + "heapUsed": 29072432, + "external": 5705892, + "arrayBuffers": 2064962, + "maxRssKiB": 116512 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118374400, + "heapTotal": 64634880, + "heapUsed": 29073840, + "external": 5705884, + "arrayBuffers": 2064962, + "maxRssKiB": 115600 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118013952, + "heapTotal": 64897024, + "heapUsed": 29061448, + "external": 5707138, + "arrayBuffers": 2064962, + "maxRssKiB": 115248 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119996416, + "heapTotal": 65945600, + "heapUsed": 30102416, + "external": 5697700, + "arrayBuffers": 2064962, + "maxRssKiB": 117184 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119078912, + "heapTotal": 65945600, + "heapUsed": 30098688, + "external": 5697692, + "arrayBuffers": 2064962, + "maxRssKiB": 116288 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 118652928, + "heapTotal": 65945600, + "heapUsed": 30089232, + "external": 5707167, + "arrayBuffers": 2064962, + "maxRssKiB": 115872 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120750080, + "heapTotal": 66994176, + "heapUsed": 31125232, + "external": 5705736, + "arrayBuffers": 2064962, + "maxRssKiB": 117920 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119750656, + "heapTotal": 67256320, + "heapUsed": 31124432, + "external": 5705728, + "arrayBuffers": 2064962, + "maxRssKiB": 116944 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119275520, + "heapTotal": 66732032, + "heapUsed": 31112832, + "external": 5698790, + "arrayBuffers": 2064962, + "maxRssKiB": 116480 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121487360, + "heapTotal": 68304896, + "heapUsed": 32148024, + "external": 5697544, + "arrayBuffers": 2064962, + "maxRssKiB": 118640 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120406016, + "heapTotal": 68304896, + "heapUsed": 32145192, + "external": 5697536, + "arrayBuffers": 2064962, + "maxRssKiB": 117584 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 119980032, + "heapTotal": 68042752, + "heapUsed": 32139488, + "external": 5707011, + "arrayBuffers": 2064962, + "maxRssKiB": 117168 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122224640, + "heapTotal": 69615616, + "heapUsed": 33212824, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 119360 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121225216, + "heapTotal": 69353472, + "heapUsed": 33212056, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 118384 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120782848, + "heapTotal": 69353472, + "heapUsed": 33216880, + "external": 5697533, + "arrayBuffers": 2064962, + "maxRssKiB": 117952 + }, + { + "pid": 41369, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122929152, + "heapTotal": 70664192, + "heapUsed": 34246280, + "external": 5705735, + "arrayBuffers": 2064962, + "maxRssKiB": 120048 + }, + { + "pid": 41370, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121978880, + "heapTotal": 70664192, + "heapUsed": 34246680, + "external": 5705727, + "arrayBuffers": 2064962, + "maxRssKiB": 119120 + }, + { + "pid": 41368, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121389056, + "heapTotal": 70402048, + "heapUsed": 34246888, + "external": 5705725, + "arrayBuffers": 2064962, + "maxRssKiB": 118544 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.062 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.648 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.655 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.735 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.648 s, 32 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.655 s, 32 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.735 s, 32 MB heap size)" + ] + }, + { + "id": "16-measured-8-in-process", + "phase": "measured", + "mode": "in-process", + "workers": 8, + "wallSeconds": 3.6221990829999995, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 41549, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115982336, + "heapTotal": 60964864, + "heapUsed": 24951672, + "external": 5717879, + "arrayBuffers": 2056770, + "maxRssKiB": 113392 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 356696064, + "heapTotal": 230653952, + "heapUsed": 92233616, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 348464 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358383616, + "heapTotal": 230916096, + "heapUsed": 92236872, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 350112 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 356483072, + "heapTotal": 232226816, + "heapUsed": 92238264, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 348256 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 370491392, + "heapTotal": 232488960, + "heapUsed": 94966608, + "external": 6027435, + "arrayBuffers": 2202216, + "maxRssKiB": 361936 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 374636544, + "heapTotal": 232751104, + "heapUsed": 94967000, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 365984 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 370229248, + "heapTotal": 233799680, + "heapUsed": 94967024, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 361680 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 381779968, + "heapTotal": 235372544, + "heapUsed": 97692664, + "external": 6027375, + "arrayBuffers": 2202185, + "maxRssKiB": 372960 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 385597440, + "heapTotal": 235634688, + "heapUsed": 97693376, + "external": 6027417, + "arrayBuffers": 2202205, + "maxRssKiB": 376688 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 381517824, + "heapTotal": 236158976, + "heapUsed": 97694584, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 372704 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 390692864, + "heapTotal": 237469696, + "heapUsed": 100282176, + "external": 6035625, + "arrayBuffers": 2210406, + "maxRssKiB": 381664 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 396263424, + "heapTotal": 237469696, + "heapUsed": 100282904, + "external": 6035667, + "arrayBuffers": 2183442, + "maxRssKiB": 387104 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 391725056, + "heapTotal": 237993984, + "heapUsed": 100284360, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 382672 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 392691712, + "heapTotal": 239304704, + "heapUsed": 101894648, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 383616 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 398082048, + "heapTotal": 239304704, + "heapUsed": 101897632, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 388880 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 402472960, + "heapTotal": 240877568, + "heapUsed": 102924152, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 393168 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 399949824, + "heapTotal": 240877568, + "heapUsed": 103455800, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 390704 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 394461184, + "heapTotal": 240877568, + "heapUsed": 103460672, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 385344 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 401866752, + "heapTotal": 242450432, + "heapUsed": 104949912, + "external": 6023735, + "arrayBuffers": 2183442, + "maxRssKiB": 392576 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 396460032, + "heapTotal": 242450432, + "heapUsed": 104934752, + "external": 6023723, + "arrayBuffers": 2201407, + "maxRssKiB": 387296 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 411992064, + "heapTotal": 243236864, + "heapUsed": 105529888, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 402464 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 403570688, + "heapTotal": 244285440, + "heapUsed": 106500312, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 394240 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 398163968, + "heapTotal": 244285440, + "heapUsed": 106493888, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 388960 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 421478400, + "heapTotal": 245596160, + "heapUsed": 108037696, + "external": 6035556, + "arrayBuffers": 2210372, + "maxRssKiB": 411728 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 405618688, + "heapTotal": 245858304, + "heapUsed": 108263392, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 396224 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 400261120, + "heapTotal": 246120448, + "heapUsed": 108258016, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 391008 + }, + { + "pid": 41547, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 402243584, + "heapTotal": 247693312, + "heapUsed": 109906992, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 392944 + }, + { + "pid": 41548, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 407535616, + "heapTotal": 248217600, + "heapUsed": 109911960, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 398112 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 432340992, + "heapTotal": 248217600, + "heapUsed": 110644272, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 422336 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 434356224, + "heapTotal": 250052608, + "heapUsed": 112414904, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 424304 + }, + { + "pid": 41546, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 436387840, + "heapTotal": 251887616, + "heapUsed": 114066488, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 426288 + }, + { + "pid": 41549, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116604928, + "heapTotal": 61489152, + "heapUsed": 25981696, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113984 + }, + { + "pid": 41549, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117161984, + "heapTotal": 62799872, + "heapUsed": 27052488, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114416 + } + ], + "files": [ + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.849 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.853 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.972 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 3.074 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)" + ] + }, + { + "id": "17-memory-1-cli", + "phase": "memory", + "mode": "cli", + "workers": 1, + "wallSeconds": 23.44392174999998, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 161693696, + "heapTotal": 106381312, + "heapUsed": 35468448, + "external": 11885796, + "arrayBuffers": 2056767, + "maxRssKiB": 158032 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 161972224, + "heapTotal": 107167744, + "heapUsed": 36457672, + "external": 11894026, + "arrayBuffers": 2064959, + "maxRssKiB": 158176 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 162152448, + "heapTotal": 107429888, + "heapUsed": 37539816, + "external": 11894631, + "arrayBuffers": 2064959, + "maxRssKiB": 158352 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 171180032, + "heapTotal": 110886912, + "heapUsed": 39776392, + "external": 13939335, + "arrayBuffers": 2073151, + "maxRssKiB": 167296 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 171245568, + "heapTotal": 111411200, + "heapUsed": 40808144, + "external": 11910156, + "arrayBuffers": 2081343, + "maxRssKiB": 167296 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 171458560, + "heapTotal": 112197632, + "heapUsed": 41850944, + "external": 11918317, + "arrayBuffers": 2081343, + "maxRssKiB": 167440 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 171868160, + "heapTotal": 113508352, + "heapUsed": 42912480, + "external": 11910154, + "arrayBuffers": 2081343, + "maxRssKiB": 167840 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 172376064, + "heapTotal": 114556928, + "heapUsed": 43951640, + "external": 11918475, + "arrayBuffers": 2081343, + "maxRssKiB": 168336 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 172818432, + "heapTotal": 115605504, + "heapUsed": 44981584, + "external": 11918504, + "arrayBuffers": 2081343, + "maxRssKiB": 168768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 173129728, + "heapTotal": 116916224, + "heapUsed": 46007376, + "external": 11910127, + "arrayBuffers": 2081343, + "maxRssKiB": 169072 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 173604864, + "heapTotal": 117964800, + "heapUsed": 47027848, + "external": 11918348, + "arrayBuffers": 2081343, + "maxRssKiB": 169536 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 174096384, + "heapTotal": 119013376, + "heapUsed": 48090616, + "external": 11908870, + "arrayBuffers": 2081343, + "maxRssKiB": 170016 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 174653440, + "heapTotal": 120324096, + "heapUsed": 49119544, + "external": 11917062, + "arrayBuffers": 2081343, + "maxRssKiB": 170560 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 176324608, + "heapTotal": 117702656, + "heapUsed": 39983384, + "external": 13947563, + "arrayBuffers": 2073151, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 176586752, + "heapTotal": 112459776, + "heapUsed": 40994824, + "external": 11910192, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 176603136, + "heapTotal": 112984064, + "heapUsed": 42046504, + "external": 11918353, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 176619520, + "heapTotal": 114294784, + "heapUsed": 43086376, + "external": 11910190, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 176619520, + "heapTotal": 115081216, + "heapUsed": 44120968, + "external": 11917229, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 176619520, + "heapTotal": 116129792, + "heapUsed": 45148688, + "external": 11909036, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 176619520, + "heapTotal": 117178368, + "heapUsed": 46175664, + "external": 11917073, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 176619520, + "heapTotal": 118489088, + "heapUsed": 47211872, + "external": 11908881, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 176619520, + "heapTotal": 119799808, + "heapUsed": 48264368, + "external": 11917072, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 176619520, + "heapTotal": 120848384, + "heapUsed": 49293136, + "external": 11917072, + "arrayBuffers": 2081343, + "maxRssKiB": 172768 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 188104704, + "heapTotal": 185860096, + "heapUsed": 40087088, + "external": 13947537, + "arrayBuffers": 2073151, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 188317696, + "heapTotal": 179306496, + "heapUsed": 41097656, + "external": 11910166, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 188481536, + "heapTotal": 179568640, + "heapUsed": 42138328, + "external": 11918327, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 188497920, + "heapTotal": 180617216, + "heapUsed": 43193800, + "external": 11910164, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 188514304, + "heapTotal": 182190080, + "heapUsed": 44228192, + "external": 11917221, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 188514304, + "heapTotal": 183238656, + "heapUsed": 45256576, + "external": 11909029, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 188514304, + "heapTotal": 184549376, + "heapUsed": 46277584, + "external": 11917065, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 188514304, + "heapTotal": 185335808, + "heapUsed": 47307440, + "external": 11908873, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 188563456, + "heapTotal": 186908672, + "heapUsed": 48358632, + "external": 11917064, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + }, + { + "pid": 41586, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 188628992, + "heapTotal": 187695104, + "heapUsed": 49385200, + "external": 11917064, + "arrayBuffers": 2081343, + "maxRssKiB": 184272 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.085 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.9 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 7.597 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.191 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (35 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.9 s, 46 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (7.597 s, 47 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.191 s, 47 MB heap size)" + ] + }, + { + "id": "18-handles-1-cli", + "phase": "handles", + "mode": "cli", + "workers": 1, + "wallSeconds": 21.761076791, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 165871616, + "heapTotal": 106381312, + "heapUsed": 36076568, + "external": 11882347, + "arrayBuffers": 2056767, + "maxRssKiB": 162128 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 166477824, + "heapTotal": 107364352, + "heapUsed": 37330328, + "external": 11893808, + "arrayBuffers": 2064959, + "maxRssKiB": 162576 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 166723584, + "heapTotal": 108412928, + "heapUsed": 38415880, + "external": 11901969, + "arrayBuffers": 2064959, + "maxRssKiB": 162816 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 167018496, + "heapTotal": 109985792, + "heapUsed": 39458824, + "external": 11893806, + "arrayBuffers": 2064959, + "maxRssKiB": 163104 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 167788544, + "heapTotal": 111296512, + "heapUsed": 40539688, + "external": 11900845, + "arrayBuffers": 2064959, + "maxRssKiB": 163856 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 168296448, + "heapTotal": 112345088, + "heapUsed": 41569360, + "external": 11892653, + "arrayBuffers": 2064959, + "maxRssKiB": 164352 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 168673280, + "heapTotal": 113393664, + "heapUsed": 42592136, + "external": 11900689, + "arrayBuffers": 2064959, + "maxRssKiB": 164720 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 169476096, + "heapTotal": 114704384, + "heapUsed": 43620704, + "external": 11892497, + "arrayBuffers": 2064959, + "maxRssKiB": 165504 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 170098688, + "heapTotal": 116015104, + "heapUsed": 44687744, + "external": 11900688, + "arrayBuffers": 2064959, + "maxRssKiB": 166112 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 171327488, + "heapTotal": 116801536, + "heapUsed": 45723136, + "external": 11900688, + "arrayBuffers": 2064959, + "maxRssKiB": 167312 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 189710336, + "heapTotal": 120012800, + "heapUsed": 41392752, + "external": 13931143, + "arrayBuffers": 2073151, + "maxRssKiB": 185392 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 190267392, + "heapTotal": 112934912, + "heapUsed": 42384032, + "external": 11910156, + "arrayBuffers": 2081343, + "maxRssKiB": 185808 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 190545920, + "heapTotal": 114245632, + "heapUsed": 43431728, + "external": 11918317, + "arrayBuffers": 2081343, + "maxRssKiB": 186080 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 191070208, + "heapTotal": 115294208, + "heapUsed": 44472032, + "external": 11910154, + "arrayBuffers": 2081343, + "maxRssKiB": 186592 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 191119360, + "heapTotal": 116342784, + "heapUsed": 45521480, + "external": 11918475, + "arrayBuffers": 2081343, + "maxRssKiB": 186640 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 191807488, + "heapTotal": 117391360, + "heapUsed": 46559984, + "external": 11918504, + "arrayBuffers": 2081343, + "maxRssKiB": 187312 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 191807488, + "heapTotal": 118702080, + "heapUsed": 47567760, + "external": 11910127, + "arrayBuffers": 2081343, + "maxRssKiB": 187312 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 192479232, + "heapTotal": 119750656, + "heapUsed": 48591656, + "external": 11918348, + "arrayBuffers": 2081343, + "maxRssKiB": 187968 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 192528384, + "heapTotal": 121061376, + "heapUsed": 49643632, + "external": 11908870, + "arrayBuffers": 2081343, + "maxRssKiB": 188016 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 193118208, + "heapTotal": 122109952, + "heapUsed": 50663688, + "external": 11917062, + "arrayBuffers": 2081343, + "maxRssKiB": 188592 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 206749696, + "heapTotal": 123158528, + "heapUsed": 41537024, + "external": 13947537, + "arrayBuffers": 2073151, + "maxRssKiB": 202480 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 211894272, + "heapTotal": 181354496, + "heapUsed": 42545440, + "external": 11910166, + "arrayBuffers": 2081343, + "maxRssKiB": 206928 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 212353024, + "heapTotal": 182140928, + "heapUsed": 43581616, + "external": 11918327, + "arrayBuffers": 2081343, + "maxRssKiB": 207376 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 212910080, + "heapTotal": 183189504, + "heapUsed": 44632432, + "external": 11910164, + "arrayBuffers": 2081343, + "maxRssKiB": 207920 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 213270528, + "heapTotal": 184238080, + "heapUsed": 45666024, + "external": 11917221, + "arrayBuffers": 2081343, + "maxRssKiB": 208272 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 213794816, + "heapTotal": 185548800, + "heapUsed": 46690312, + "external": 11909029, + "arrayBuffers": 2081343, + "maxRssKiB": 208784 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 214286336, + "heapTotal": 186859520, + "heapUsed": 47731544, + "external": 11917065, + "arrayBuffers": 2081343, + "maxRssKiB": 209264 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 214564864, + "heapTotal": 187645952, + "heapUsed": 48747304, + "external": 11908873, + "arrayBuffers": 2081343, + "maxRssKiB": 209536 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 214859776, + "heapTotal": 188694528, + "heapUsed": 49801536, + "external": 11917064, + "arrayBuffers": 2081343, + "maxRssKiB": 209824 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 215449600, + "heapTotal": 189743104, + "heapUsed": 50834960, + "external": 11917064, + "arrayBuffers": 2081343, + "maxRssKiB": 210400 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 220905472, + "heapTotal": 189480960, + "heapUsed": 41751608, + "external": 13947812, + "arrayBuffers": 2073151, + "maxRssKiB": 216384 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 220905472, + "heapTotal": 180568064, + "heapUsed": 42750744, + "external": 11910410, + "arrayBuffers": 2081343, + "maxRssKiB": 216384 + }, + { + "pid": 41970, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 220905472, + "heapTotal": 181616640, + "heapUsed": 43787024, + "external": 11911015, + "arrayBuffers": 2081343, + "maxRssKiB": 216384 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 6.519 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 6.524 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 6.242 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.889 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (6.519 s, 43 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (6.524 s, 48 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (6.242 s, 48 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (41 MB heap size)" + ] + }, + { + "id": "19-memory-1-in-process", + "phase": "memory", + "mode": "in-process", + "workers": 1, + "wallSeconds": 4.619498250000004, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 382287872, + "heapTotal": 243826688, + "heapUsed": 102512544, + "external": 12612904, + "arrayBuffers": 2646518, + "maxRssKiB": 373456 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 395968512, + "heapTotal": 244613120, + "heapUsed": 105092528, + "external": 12168661, + "arrayBuffers": 2183439, + "maxRssKiB": 386816 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 404586496, + "heapTotal": 246710272, + "heapUsed": 107786544, + "external": 12168601, + "arrayBuffers": 2183439, + "maxRssKiB": 395232 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 414040064, + "heapTotal": 248807424, + "heapUsed": 110398928, + "external": 12176851, + "arrayBuffers": 2183439, + "maxRssKiB": 404464 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 425115648, + "heapTotal": 251953152, + "heapUsed": 113039992, + "external": 12177106, + "arrayBuffers": 2183439, + "maxRssKiB": 415280 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 435077120, + "heapTotal": 253526016, + "heapUsed": 115651024, + "external": 12168972, + "arrayBuffers": 2183439, + "maxRssKiB": 425008 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 445759488, + "heapTotal": 256671744, + "heapUsed": 118162560, + "external": 12176795, + "arrayBuffers": 2183439, + "maxRssKiB": 435440 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 454836224, + "heapTotal": 259293184, + "heapUsed": 120761616, + "external": 12168661, + "arrayBuffers": 2183439, + "maxRssKiB": 444304 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 456802304, + "heapTotal": 261390336, + "heapUsed": 122538040, + "external": 12173151, + "arrayBuffers": 2183439, + "maxRssKiB": 446224 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 458801152, + "heapTotal": 262438912, + "heapUsed": 124176056, + "external": 12164959, + "arrayBuffers": 2183439, + "maxRssKiB": 448176 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 470155264, + "heapTotal": 266420224, + "heapUsed": 120976144, + "external": 14214250, + "arrayBuffers": 2199823, + "maxRssKiB": 459264 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 478117888, + "heapTotal": 261701632, + "heapUsed": 123518624, + "external": 12193292, + "arrayBuffers": 2208015, + "maxRssKiB": 467040 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 484982784, + "heapTotal": 264323072, + "heapUsed": 126064360, + "external": 12193232, + "arrayBuffers": 2208015, + "maxRssKiB": 473744 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 492748800, + "heapTotal": 266682368, + "heapUsed": 128592264, + "external": 12201482, + "arrayBuffers": 2208015, + "maxRssKiB": 481328 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 493895680, + "heapTotal": 268255232, + "heapUsed": 130105368, + "external": 12189861, + "arrayBuffers": 2208015, + "maxRssKiB": 482448 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 495599616, + "heapTotal": 269828096, + "heapUsed": 131632168, + "external": 12198053, + "arrayBuffers": 2208015, + "maxRssKiB": 484112 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 497369088, + "heapTotal": 271925248, + "heapUsed": 133153368, + "external": 12189550, + "arrayBuffers": 2208015, + "maxRssKiB": 485840 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 499253248, + "heapTotal": 273235968, + "heapUsed": 134644752, + "external": 12197742, + "arrayBuffers": 2208015, + "maxRssKiB": 487680 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 501088256, + "heapTotal": 274808832, + "heapUsed": 136194320, + "external": 12189549, + "arrayBuffers": 2208015, + "maxRssKiB": 489472 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 502906880, + "heapTotal": 276643840, + "heapUsed": 137714552, + "external": 12197741, + "arrayBuffers": 2208015, + "maxRssKiB": 491248 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 511229952, + "heapTotal": 278216704, + "heapUsed": 130042088, + "external": 14230592, + "arrayBuffers": 2191631, + "maxRssKiB": 499824 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 519421952, + "heapTotal": 270876672, + "heapUsed": 132566904, + "external": 12185058, + "arrayBuffers": 2199823, + "maxRssKiB": 507376 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 527040512, + "heapTotal": 273235968, + "heapUsed": 135114960, + "external": 12184998, + "arrayBuffers": 2199823, + "maxRssKiB": 514816 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 534069248, + "heapTotal": 275857408, + "heapUsed": 137640872, + "external": 12193248, + "arrayBuffers": 2199823, + "maxRssKiB": 521680 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 534839296, + "heapTotal": 277430272, + "heapUsed": 139141648, + "external": 12181657, + "arrayBuffers": 2199823, + "maxRssKiB": 522432 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 535543808, + "heapTotal": 279003136, + "heapUsed": 140642920, + "external": 12189849, + "arrayBuffers": 2199823, + "maxRssKiB": 523120 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 536150016, + "heapTotal": 280576000, + "heapUsed": 142127480, + "external": 12181346, + "arrayBuffers": 2199823, + "maxRssKiB": 523712 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 537346048, + "heapTotal": 282148864, + "heapUsed": 143603256, + "external": 12189538, + "arrayBuffers": 2199823, + "maxRssKiB": 524880 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 539148288, + "heapTotal": 283721728, + "heapUsed": 145115144, + "external": 12181345, + "arrayBuffers": 2199823, + "maxRssKiB": 526640 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 540901376, + "heapTotal": 285294592, + "heapUsed": 146610264, + "external": 12189537, + "arrayBuffers": 2199823, + "maxRssKiB": 528352 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 542130176, + "heapTotal": 285294592, + "heapUsed": 137442704, + "external": 14212709, + "arrayBuffers": 2191631, + "maxRssKiB": 530000 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 542162944, + "heapTotal": 276643840, + "heapUsed": 138469728, + "external": 12175307, + "arrayBuffers": 2199823, + "maxRssKiB": 530000 + }, + { + "pid": 42319, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 542425088, + "heapTotal": 277692416, + "heapUsed": 139511656, + "external": 12175912, + "arrayBuffers": 2199823, + "maxRssKiB": 530000 + } + ], + "files": [ + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.23 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.477 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 0.494 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.858 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/newtab.integration.test.ts (118 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (131 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (139 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (133 MB heap size)" + ] + }, + { + "id": "20-handles-1-in-process", + "phase": "handles", + "mode": "in-process", + "workers": 1, + "wallSeconds": 4.875271166999999, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 168148992, + "heapTotal": 107167744, + "heapUsed": 36108896, + "external": 11877396, + "arrayBuffers": 2056767, + "maxRssKiB": 164336 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 168607744, + "heapTotal": 108150784, + "heapUsed": 37365448, + "external": 11894026, + "arrayBuffers": 2064959, + "maxRssKiB": 164656 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 168886272, + "heapTotal": 108937216, + "heapUsed": 38512304, + "external": 11894631, + "arrayBuffers": 2064959, + "maxRssKiB": 164928 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 454098944, + "heapTotal": 262356992, + "heapUsed": 109691472, + "external": 12214802, + "arrayBuffers": 2183439, + "maxRssKiB": 443584 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 466239488, + "heapTotal": 255279104, + "heapUsed": 112288008, + "external": 12176853, + "arrayBuffers": 2191631, + "maxRssKiB": 455440 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 476725248, + "heapTotal": 256065536, + "heapUsed": 114982544, + "external": 12176793, + "arrayBuffers": 2191631, + "maxRssKiB": 465680 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 485670912, + "heapTotal": 257900544, + "heapUsed": 117612120, + "external": 12185043, + "arrayBuffers": 2191631, + "maxRssKiB": 474416 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 494108672, + "heapTotal": 259735552, + "heapUsed": 120253496, + "external": 12185298, + "arrayBuffers": 2191631, + "maxRssKiB": 482656 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 502595584, + "heapTotal": 262094848, + "heapUsed": 122849880, + "external": 12177164, + "arrayBuffers": 2191631, + "maxRssKiB": 490944 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 511033344, + "heapTotal": 264716288, + "heapUsed": 125351400, + "external": 12184987, + "arrayBuffers": 2191631, + "maxRssKiB": 499184 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 518963200, + "heapTotal": 267075584, + "heapUsed": 127957744, + "external": 12176853, + "arrayBuffers": 2191631, + "maxRssKiB": 506928 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 520945664, + "heapTotal": 268910592, + "heapUsed": 129720816, + "external": 12181343, + "arrayBuffers": 2191631, + "maxRssKiB": 508864 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 523075584, + "heapTotal": 270483456, + "heapUsed": 131335456, + "external": 12173151, + "arrayBuffers": 2191631, + "maxRssKiB": 510944 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 534921216, + "heapTotal": 274153472, + "heapUsed": 123791088, + "external": 14222400, + "arrayBuffers": 2199823, + "maxRssKiB": 522960 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 543014912, + "heapTotal": 267862016, + "heapUsed": 126373192, + "external": 12193250, + "arrayBuffers": 2208015, + "maxRssKiB": 530496 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 550354944, + "heapTotal": 269172736, + "heapUsed": 128939904, + "external": 12193190, + "arrayBuffers": 2208015, + "maxRssKiB": 537584 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 557727744, + "heapTotal": 271007744, + "heapUsed": 131452696, + "external": 12201440, + "arrayBuffers": 2208015, + "maxRssKiB": 544784 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 558317568, + "heapTotal": 272318464, + "heapUsed": 132963016, + "external": 12189849, + "arrayBuffers": 2208015, + "maxRssKiB": 545360 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 559005696, + "heapTotal": 273891328, + "heapUsed": 134488872, + "external": 12198041, + "arrayBuffers": 2208015, + "maxRssKiB": 546032 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 559726592, + "heapTotal": 275726336, + "heapUsed": 136037496, + "external": 12189538, + "arrayBuffers": 2208015, + "maxRssKiB": 546736 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 561086464, + "heapTotal": 277561344, + "heapUsed": 137516320, + "external": 12197730, + "arrayBuffers": 2208015, + "maxRssKiB": 548064 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 562741248, + "heapTotal": 278872064, + "heapUsed": 139067456, + "external": 12189537, + "arrayBuffers": 2208015, + "maxRssKiB": 549680 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 564494336, + "heapTotal": 280444928, + "heapUsed": 140580480, + "external": 12197729, + "arrayBuffers": 2208015, + "maxRssKiB": 551392 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 573669376, + "heapTotal": 283066368, + "heapUsed": 132856128, + "external": 14230634, + "arrayBuffers": 2191631, + "maxRssKiB": 560800 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 581861376, + "heapTotal": 274153472, + "heapUsed": 135362088, + "external": 12185100, + "arrayBuffers": 2199823, + "maxRssKiB": 568352 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 588005376, + "heapTotal": 277037056, + "heapUsed": 137930976, + "external": 12185040, + "arrayBuffers": 2199823, + "maxRssKiB": 574352 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 594477056, + "heapTotal": 279396352, + "heapUsed": 140445320, + "external": 12193290, + "arrayBuffers": 2199823, + "maxRssKiB": 580672 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 595230720, + "heapTotal": 280444928, + "heapUsed": 141972784, + "external": 12181669, + "arrayBuffers": 2199823, + "maxRssKiB": 581408 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 595836928, + "heapTotal": 282017792, + "heapUsed": 143459392, + "external": 12189861, + "arrayBuffers": 2199823, + "maxRssKiB": 582000 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 596541440, + "heapTotal": 283590656, + "heapUsed": 144942256, + "external": 12181358, + "arrayBuffers": 2199823, + "maxRssKiB": 582688 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 597966848, + "heapTotal": 285425664, + "heapUsed": 146420272, + "external": 12189550, + "arrayBuffers": 2199823, + "maxRssKiB": 584080 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 599883776, + "heapTotal": 286998528, + "heapUsed": 147930736, + "external": 12181357, + "arrayBuffers": 2199823, + "maxRssKiB": 585952 + }, + { + "pid": 42393, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 601522176, + "heapTotal": 288571392, + "heapUsed": 149438656, + "external": 12189549, + "arrayBuffers": 2199823, + "maxRssKiB": 587552 + } + ], + "files": [ + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.88 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.215 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 0.638 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.638 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/competing.integration.test.ts (36 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (125 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (133 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (142 MB heap size)" + ] + }, + { + "id": "21-limited-2-in-process", + "phase": "limited", + "mode": "in-process", + "workers": 2, + "wallSeconds": 3.032205500000011, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42543, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115720192, + "heapTotal": 61227008, + "heapUsed": 24929016, + "external": 5717914, + "arrayBuffers": 2056770, + "maxRssKiB": 113136 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357695488, + "heapTotal": 231702528, + "heapUsed": 92238096, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 349440 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371785728, + "heapTotal": 233275392, + "heapUsed": 94973512, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 363200 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383205376, + "heapTotal": 235634688, + "heapUsed": 97696136, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 374352 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 393265152, + "heapTotal": 237993984, + "heapUsed": 100285432, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 384176 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 404668416, + "heapTotal": 240353280, + "heapUsed": 102925056, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 395312 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 413958144, + "heapTotal": 242974720, + "heapUsed": 105530648, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 404384 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 424443904, + "heapTotal": 245596160, + "heapUsed": 108038160, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 414624 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 435109888, + "heapTotal": 248217600, + "heapUsed": 110650088, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 425040 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 437174272, + "heapTotal": 250052608, + "heapUsed": 112421432, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 427056 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 439189504, + "heapTotal": 251363328, + "heapUsed": 114075568, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 429024 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 450297856, + "heapTotal": 255344640, + "heapUsed": 110864120, + "external": 8091942, + "arrayBuffers": 2199826, + "maxRssKiB": 439872 + }, + { + "pid": 42543, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116506624, + "heapTotal": 61751296, + "heapUsed": 25988176, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113888 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 457998336, + "heapTotal": 254296064, + "heapUsed": 113408552, + "external": 6052011, + "arrayBuffers": 2208018, + "maxRssKiB": 447392 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 464617472, + "heapTotal": 255344640, + "heapUsed": 115960744, + "external": 6051951, + "arrayBuffers": 2208018, + "maxRssKiB": 453856 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 471072768, + "heapTotal": 256917504, + "heapUsed": 118496192, + "external": 6060201, + "arrayBuffers": 2208018, + "maxRssKiB": 460160 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 472711168, + "heapTotal": 258752512, + "heapUsed": 119992272, + "external": 6048610, + "arrayBuffers": 2208018, + "maxRssKiB": 461760 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 474447872, + "heapTotal": 260063232, + "heapUsed": 121520112, + "external": 6056802, + "arrayBuffers": 2208018, + "maxRssKiB": 463456 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 476135424, + "heapTotal": 261636096, + "heapUsed": 123013808, + "external": 6048299, + "arrayBuffers": 2208018, + "maxRssKiB": 465104 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 477741056, + "heapTotal": 262946816, + "heapUsed": 124504456, + "external": 6056491, + "arrayBuffers": 2208018, + "maxRssKiB": 466672 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 479248384, + "heapTotal": 264257536, + "heapUsed": 126057688, + "external": 6048298, + "arrayBuffers": 2208018, + "maxRssKiB": 468144 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 480772096, + "heapTotal": 265830400, + "heapUsed": 127577712, + "external": 6056490, + "arrayBuffers": 2208018, + "maxRssKiB": 469632 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 490455040, + "heapTotal": 268451840, + "heapUsed": 119890320, + "external": 8108370, + "arrayBuffers": 2191634, + "maxRssKiB": 479536 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 497238016, + "heapTotal": 260587520, + "heapUsed": 122403584, + "external": 6043861, + "arrayBuffers": 2199826, + "maxRssKiB": 485712 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 505020416, + "heapTotal": 262684672, + "heapUsed": 124961608, + "external": 6043801, + "arrayBuffers": 2199826, + "maxRssKiB": 493312 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 512000000, + "heapTotal": 265043968, + "heapUsed": 127482160, + "external": 6052051, + "arrayBuffers": 2199826, + "maxRssKiB": 500128 + }, + { + "pid": 42543, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117161984, + "heapTotal": 63062016, + "heapUsed": 27055200, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114416 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 512622592, + "heapTotal": 266616832, + "heapUsed": 128987200, + "external": 6040430, + "arrayBuffers": 2199826, + "maxRssKiB": 500736 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 513228800, + "heapTotal": 268451840, + "heapUsed": 130487920, + "external": 6048622, + "arrayBuffers": 2199826, + "maxRssKiB": 501328 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 514031616, + "heapTotal": 269762560, + "heapUsed": 131968744, + "external": 6040119, + "arrayBuffers": 2199826, + "maxRssKiB": 502112 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 515620864, + "heapTotal": 271597568, + "heapUsed": 133446680, + "external": 6048311, + "arrayBuffers": 2199826, + "maxRssKiB": 503664 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 517308416, + "heapTotal": 272908288, + "heapUsed": 134953840, + "external": 6040118, + "arrayBuffers": 2199826, + "maxRssKiB": 505312 + }, + { + "pid": 42544, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 518897664, + "heapTotal": 274743296, + "heapUsed": 136450920, + "external": 6048310, + "arrayBuffers": 2199826, + "maxRssKiB": 506864 + } + ], + "files": [ + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.187 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 0.47 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.957 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.487 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (121 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (130 MB heap size)" + ] + }, + { + "id": "22-limited-2-in-process", + "phase": "limited", + "mode": "in-process", + "workers": 2, + "wallSeconds": 3.0276612920000043, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42635, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116391936, + "heapTotal": 60702720, + "heapUsed": 24924272, + "external": 5717914, + "arrayBuffers": 2056770, + "maxRssKiB": 113792 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358645760, + "heapTotal": 230916096, + "heapUsed": 92238840, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 350368 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371736576, + "heapTotal": 233013248, + "heapUsed": 94966872, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 363152 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383713280, + "heapTotal": 235372544, + "heapUsed": 97691120, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 374848 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 394821632, + "heapTotal": 237731840, + "heapUsed": 100284408, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 385696 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 404750336, + "heapTotal": 240615424, + "heapUsed": 102923600, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 395392 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 415596544, + "heapTotal": 243236864, + "heapUsed": 105530160, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 405984 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 425443328, + "heapTotal": 245858304, + "heapUsed": 108042976, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 415600 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 435568640, + "heapTotal": 248217600, + "heapUsed": 110650168, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 425488 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 437518336, + "heapTotal": 250052608, + "heapUsed": 112420784, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 427392 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 439549952, + "heapTotal": 252149760, + "heapUsed": 114072224, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 429376 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 452444160, + "heapTotal": 256655360, + "heapUsed": 110869776, + "external": 8091986, + "arrayBuffers": 2199826, + "maxRssKiB": 441968 + }, + { + "pid": 42635, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 117063680, + "heapTotal": 61489152, + "heapUsed": 25955880, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 114320 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 459227136, + "heapTotal": 252198912, + "heapUsed": 113414960, + "external": 6052053, + "arrayBuffers": 2208018, + "maxRssKiB": 448592 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 466878464, + "heapTotal": 254033920, + "heapUsed": 115975944, + "external": 6051993, + "arrayBuffers": 2208018, + "maxRssKiB": 456064 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 474234880, + "heapTotal": 256131072, + "heapUsed": 118499256, + "external": 6060243, + "arrayBuffers": 2208018, + "maxRssKiB": 463248 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 475054080, + "heapTotal": 257966080, + "heapUsed": 120010008, + "external": 6048622, + "arrayBuffers": 2208018, + "maxRssKiB": 464048 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 476495872, + "heapTotal": 259538944, + "heapUsed": 121535832, + "external": 6056814, + "arrayBuffers": 2208018, + "maxRssKiB": 465456 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 478183424, + "heapTotal": 261373952, + "heapUsed": 123056024, + "external": 6048311, + "arrayBuffers": 2208018, + "maxRssKiB": 467104 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 479952896, + "heapTotal": 262684672, + "heapUsed": 124546856, + "external": 6056503, + "arrayBuffers": 2208018, + "maxRssKiB": 468832 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 481722368, + "heapTotal": 264257536, + "heapUsed": 126099496, + "external": 6048310, + "arrayBuffers": 2208018, + "maxRssKiB": 470560 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 483491840, + "heapTotal": 265830400, + "heapUsed": 127617792, + "external": 6056502, + "arrayBuffers": 2208018, + "maxRssKiB": 472288 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 492191744, + "heapTotal": 267141120, + "heapUsed": 119929568, + "external": 8108326, + "arrayBuffers": 2191634, + "maxRssKiB": 481232 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 500006912, + "heapTotal": 260325376, + "heapUsed": 122443000, + "external": 6043819, + "arrayBuffers": 2199826, + "maxRssKiB": 488416 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 506216448, + "heapTotal": 262422528, + "heapUsed": 125000152, + "external": 6043759, + "arrayBuffers": 2199826, + "maxRssKiB": 494480 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 514326528, + "heapTotal": 265568256, + "heapUsed": 127523064, + "external": 6052009, + "arrayBuffers": 2199826, + "maxRssKiB": 502400 + }, + { + "pid": 42635, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117587968, + "heapTotal": 63062016, + "heapUsed": 27029664, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114832 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 515178496, + "heapTotal": 266878976, + "heapUsed": 129026488, + "external": 6040418, + "arrayBuffers": 2199826, + "maxRssKiB": 503232 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 515817472, + "heapTotal": 268451840, + "heapUsed": 130527112, + "external": 6048610, + "arrayBuffers": 2199826, + "maxRssKiB": 503856 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 516505600, + "heapTotal": 270024704, + "heapUsed": 132007776, + "external": 6040107, + "arrayBuffers": 2199826, + "maxRssKiB": 504528 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 517505024, + "heapTotal": 271597568, + "heapUsed": 133485648, + "external": 6048299, + "arrayBuffers": 2199826, + "maxRssKiB": 505504 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 519421952, + "heapTotal": 273432576, + "heapUsed": 134993992, + "external": 6040106, + "arrayBuffers": 2199826, + "maxRssKiB": 507376 + }, + { + "pid": 42636, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 521027584, + "heapTotal": 274743296, + "heapUsed": 136490808, + "external": 6048298, + "arrayBuffers": 2199826, + "maxRssKiB": 508944 + } + ], + "files": [ + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.192 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.455 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.952 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 0.48 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (121 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (130 MB heap size)" + ] + }, + { + "id": "23-limited-2-in-process", + "phase": "limited", + "mode": "in-process", + "workers": 2, + "wallSeconds": 3.1493347920000088, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42705, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 117456896, + "heapTotal": 60440576, + "heapUsed": 24954360, + "external": 5717914, + "arrayBuffers": 2056770, + "maxRssKiB": 114944 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357875712, + "heapTotal": 231440384, + "heapUsed": 92233416, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 349616 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 372162560, + "heapTotal": 232751104, + "heapUsed": 94970840, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 363568 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 384401408, + "heapTotal": 235110400, + "heapUsed": 97692840, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 375520 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 395509760, + "heapTotal": 237993984, + "heapUsed": 100283696, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 386368 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 405635072, + "heapTotal": 240353280, + "heapUsed": 102923200, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 396256 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 414105600, + "heapTotal": 242974720, + "heapUsed": 105530040, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 404528 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 424263680, + "heapTotal": 245596160, + "heapUsed": 108032936, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 414448 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 433684480, + "heapTotal": 248217600, + "heapUsed": 110649368, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 423648 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 435683328, + "heapTotal": 250052608, + "heapUsed": 112420104, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 425600 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 437403648, + "heapTotal": 251363328, + "heapUsed": 114071720, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 427280 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 449806336, + "heapTotal": 255868928, + "heapUsed": 110870624, + "external": 8091942, + "arrayBuffers": 2199826, + "maxRssKiB": 439392 + }, + { + "pid": 42705, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 118013952, + "heapTotal": 61227008, + "heapUsed": 25980752, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 115248 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 456884224, + "heapTotal": 252723200, + "heapUsed": 113419128, + "external": 6052011, + "arrayBuffers": 2208018, + "maxRssKiB": 446304 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 465125376, + "heapTotal": 254558208, + "heapUsed": 115974304, + "external": 6051951, + "arrayBuffers": 2208018, + "maxRssKiB": 454352 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 471941120, + "heapTotal": 255868928, + "heapUsed": 118495800, + "external": 6060201, + "arrayBuffers": 2208018, + "maxRssKiB": 461008 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 473137152, + "heapTotal": 257703936, + "heapUsed": 120008408, + "external": 6048610, + "arrayBuffers": 2208018, + "maxRssKiB": 462176 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 474923008, + "heapTotal": 259538944, + "heapUsed": 121533960, + "external": 6056802, + "arrayBuffers": 2208018, + "maxRssKiB": 463920 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 476676096, + "heapTotal": 261111808, + "heapUsed": 123054152, + "external": 6048299, + "arrayBuffers": 2208018, + "maxRssKiB": 465632 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 478543872, + "heapTotal": 262684672, + "heapUsed": 124544936, + "external": 6056491, + "arrayBuffers": 2208018, + "maxRssKiB": 467456 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 480247808, + "heapTotal": 264257536, + "heapUsed": 126097560, + "external": 6048298, + "arrayBuffers": 2208018, + "maxRssKiB": 469120 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 482099200, + "heapTotal": 266092544, + "heapUsed": 127616592, + "external": 6056490, + "arrayBuffers": 2208018, + "maxRssKiB": 470928 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 491634688, + "heapTotal": 267927552, + "heapUsed": 119927784, + "external": 8108370, + "arrayBuffers": 2191634, + "maxRssKiB": 480688 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 498843648, + "heapTotal": 259801088, + "heapUsed": 122440888, + "external": 6043861, + "arrayBuffers": 2199826, + "maxRssKiB": 487280 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 504299520, + "heapTotal": 262422528, + "heapUsed": 125001280, + "external": 6043801, + "arrayBuffers": 2199826, + "maxRssKiB": 492608 + }, + { + "pid": 42705, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 118587392, + "heapTotal": 62799872, + "heapUsed": 27053880, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 115808 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 511000576, + "heapTotal": 265043968, + "heapUsed": 127519608, + "external": 6052051, + "arrayBuffers": 2199826, + "maxRssKiB": 499152 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 511787008, + "heapTotal": 266878976, + "heapUsed": 129024616, + "external": 6040430, + "arrayBuffers": 2199826, + "maxRssKiB": 499920 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 512409600, + "heapTotal": 268713984, + "heapUsed": 130525256, + "external": 6048622, + "arrayBuffers": 2199826, + "maxRssKiB": 500528 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 513753088, + "heapTotal": 270024704, + "heapUsed": 132006336, + "external": 6040119, + "arrayBuffers": 2199826, + "maxRssKiB": 501840 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 515555328, + "heapTotal": 271859712, + "heapUsed": 133483968, + "external": 6048311, + "arrayBuffers": 2199826, + "maxRssKiB": 503600 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 517275648, + "heapTotal": 273694720, + "heapUsed": 134991104, + "external": 6040118, + "arrayBuffers": 2199826, + "maxRssKiB": 505280 + }, + { + "pid": 42706, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 518864896, + "heapTotal": 274743296, + "heapUsed": 136488264, + "external": 6048310, + "arrayBuffers": 2199826, + "maxRssKiB": 506832 + } + ], + "files": [ + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.214 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 0.49 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.985 + }, + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 0.5 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (121 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)", + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (130 MB heap size)" + ] + }, + { + "id": "24-limited-8-in-process", + "phase": "limited", + "mode": "in-process", + "workers": 8, + "wallSeconds": 2.467169709000009, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42791, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116031488, + "heapTotal": 60440576, + "heapUsed": 24933440, + "external": 5717913, + "arrayBuffers": 2056770, + "maxRssKiB": 113472 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357367808, + "heapTotal": 230653952, + "heapUsed": 92231832, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 349120 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358514688, + "heapTotal": 231702528, + "heapUsed": 92238728, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 350240 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 359399424, + "heapTotal": 231702528, + "heapUsed": 92237848, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 351104 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 369213440, + "heapTotal": 233013248, + "heapUsed": 94966576, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 360688 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 370655232, + "heapTotal": 233275392, + "heapUsed": 94969464, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 362096 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371081216, + "heapTotal": 233537536, + "heapUsed": 94968496, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 362512 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 379043840, + "heapTotal": 235634688, + "heapUsed": 97689768, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 370288 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 382189568, + "heapTotal": 235372544, + "heapUsed": 97696800, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 373360 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 384942080, + "heapTotal": 236158976, + "heapUsed": 97692832, + "external": 6027417, + "arrayBuffers": 2183442, + "maxRssKiB": 376048 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 394297344, + "heapTotal": 238256128, + "heapUsed": 100284024, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 385184 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 389726208, + "heapTotal": 237993984, + "heapUsed": 100282576, + "external": 6035625, + "arrayBuffers": 2183442, + "maxRssKiB": 380720 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 397754368, + "heapTotal": 238518272, + "heapUsed": 100285904, + "external": 6035667, + "arrayBuffers": 2183442, + "maxRssKiB": 388560 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 391626752, + "heapTotal": 239828992, + "heapUsed": 101894704, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 382576 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 399720448, + "heapTotal": 240353280, + "heapUsed": 101895856, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 390480 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 405716992, + "heapTotal": 240877568, + "heapUsed": 102923488, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 396336 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 393461760, + "heapTotal": 241664000, + "heapUsed": 103461528, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 384368 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 401489920, + "heapTotal": 242188288, + "heapUsed": 103462664, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 392208 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 414711808, + "heapTotal": 243236864, + "heapUsed": 105530120, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 405120 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 395427840, + "heapTotal": 242712576, + "heapUsed": 104939336, + "external": 6023723, + "arrayBuffers": 2183442, + "maxRssKiB": 386288 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 403603456, + "heapTotal": 243499008, + "heapUsed": 104951240, + "external": 6023735, + "arrayBuffers": 2183442, + "maxRssKiB": 394272 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 405291008, + "heapTotal": 245071872, + "heapUsed": 106506568, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 395920 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 397213696, + "heapTotal": 244285440, + "heapUsed": 106504944, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 388032 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 425033728, + "heapTotal": 245858304, + "heapUsed": 108037472, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 415200 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 399261696, + "heapTotal": 246120448, + "heapUsed": 108268784, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 390032 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 407552000, + "heapTotal": 246906880, + "heapUsed": 108270040, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 398128 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 435339264, + "heapTotal": 247693312, + "heapUsed": 110649240, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 425264 + }, + { + "pid": 42794, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 401227776, + "heapTotal": 248217600, + "heapUsed": 109917224, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 391952 + }, + { + "pid": 42793, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 409583616, + "heapTotal": 248741888, + "heapUsed": 109918944, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 400112 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 437485568, + "heapTotal": 250052608, + "heapUsed": 112422920, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 427360 + }, + { + "pid": 42792, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 439500800, + "heapTotal": 252149760, + "heapUsed": 114076016, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 429328 + }, + { + "pid": 42791, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116539392, + "heapTotal": 61227008, + "heapUsed": 25961392, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113808 + }, + { + "pid": 42791, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117161984, + "heapTotal": 62799872, + "heapUsed": 27032760, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114416 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.225 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.224 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.297 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 1.995 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)" + ] + }, + { + "id": "25-limited-8-in-process", + "phase": "limited", + "mode": "in-process", + "workers": 8, + "wallSeconds": 2.462256625000009, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42872, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115818496, + "heapTotal": 61227008, + "heapUsed": 24951512, + "external": 5717914, + "arrayBuffers": 2056770, + "maxRssKiB": 113232 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 358940672, + "heapTotal": 231440384, + "heapUsed": 92237608, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 350656 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357941248, + "heapTotal": 231702528, + "heapUsed": 92239712, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 349680 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 357744640, + "heapTotal": 230916096, + "heapUsed": 92224752, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 349488 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371539968, + "heapTotal": 233275392, + "heapUsed": 94969976, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 362960 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 369917952, + "heapTotal": 233537536, + "heapUsed": 94971280, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 361376 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 371916800, + "heapTotal": 232488960, + "heapUsed": 94951912, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 363328 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 381042688, + "heapTotal": 235372544, + "heapUsed": 97692208, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 372240 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 381468672, + "heapTotal": 235896832, + "heapUsed": 97693704, + "external": 6027417, + "arrayBuffers": 2183442, + "maxRssKiB": 372656 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383451136, + "heapTotal": 235110400, + "heapUsed": 97678656, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 374592 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 393986048, + "heapTotal": 237993984, + "heapUsed": 100284440, + "external": 6035667, + "arrayBuffers": 2183442, + "maxRssKiB": 384880 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 393183232, + "heapTotal": 238518272, + "heapUsed": 100284152, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 384096 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 395280384, + "heapTotal": 237731840, + "heapUsed": 100283664, + "external": 6035625, + "arrayBuffers": 2183442, + "maxRssKiB": 386144 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 395755520, + "heapTotal": 239828992, + "heapUsed": 101897432, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 386608 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 397099008, + "heapTotal": 239566848, + "heapUsed": 101896296, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 387920 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 403406848, + "heapTotal": 240877568, + "heapUsed": 102921688, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 394080 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 397557760, + "heapTotal": 241664000, + "heapUsed": 103463664, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 388368 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 398934016, + "heapTotal": 241139712, + "heapUsed": 103463344, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 389712 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 399441920, + "heapTotal": 242974720, + "heapUsed": 104952152, + "external": 6023735, + "arrayBuffers": 2183442, + "maxRssKiB": 390208 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 412794880, + "heapTotal": 243499008, + "heapUsed": 105528672, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 403248 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 400916480, + "heapTotal": 242712576, + "heapUsed": 104940896, + "external": 6023723, + "arrayBuffers": 2183442, + "maxRssKiB": 391648 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 401145856, + "heapTotal": 244809728, + "heapUsed": 106502128, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 391872 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 402669568, + "heapTotal": 244285440, + "heapUsed": 106506384, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 393360 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 423673856, + "heapTotal": 245858304, + "heapUsed": 108035992, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 413872 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 403324928, + "heapTotal": 246644736, + "heapUsed": 108270784, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 394000 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 404668416, + "heapTotal": 246120448, + "heapUsed": 108269320, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 395312 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 432144384, + "heapTotal": 248479744, + "heapUsed": 110642784, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 422144 + }, + { + "pid": 42874, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 405372928, + "heapTotal": 248741888, + "heapUsed": 109920144, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 396000 + }, + { + "pid": 42875, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 406700032, + "heapTotal": 247955456, + "heapUsed": 109918688, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 397296 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 434421760, + "heapTotal": 250052608, + "heapUsed": 112418704, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 424368 + }, + { + "pid": 42873, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 436256768, + "heapTotal": 252411904, + "heapUsed": 114070456, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 426160 + }, + { + "pid": 42872, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116506624, + "heapTotal": 61751296, + "heapUsed": 25982544, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 113776 + }, + { + "pid": 42872, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117211136, + "heapTotal": 63062016, + "heapUsed": 27052744, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 114464 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.204 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.214 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.285 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.001 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)" + ] + }, + { + "id": "26-limited-8-in-process", + "phase": "limited", + "mode": "in-process", + "workers": 8, + "wallSeconds": 2.5396672910000198, + "tests": 33, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "leftovers": 0, + "openHandles": 0, + "samples": [ + { + "pid": 42943, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 117522432, + "heapTotal": 60964864, + "heapUsed": 24925896, + "external": 5717914, + "arrayBuffers": 2056770, + "maxRssKiB": 114896 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 359006208, + "heapTotal": 230916096, + "heapUsed": 92235656, + "external": 6002413, + "arrayBuffers": 2175250, + "maxRssKiB": 350720 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 359907328, + "heapTotal": 231702528, + "heapUsed": 92238832, + "external": 6002361, + "arrayBuffers": 2175250, + "maxRssKiB": 351600 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 354861056, + "heapTotal": 231440384, + "heapUsed": 92237304, + "external": 6002373, + "arrayBuffers": 2175250, + "maxRssKiB": 346672 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 367411200, + "heapTotal": 233799680, + "heapUsed": 94969976, + "external": 6027435, + "arrayBuffers": 2183442, + "maxRssKiB": 358928 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 372981760, + "heapTotal": 234061824, + "heapUsed": 94969512, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 364368 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 370966528, + "heapTotal": 233013248, + "heapUsed": 94965192, + "external": 6027477, + "arrayBuffers": 2183442, + "maxRssKiB": 362400 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 379551744, + "heapTotal": 235896832, + "heapUsed": 97690720, + "external": 6027375, + "arrayBuffers": 2183442, + "maxRssKiB": 370784 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383762432, + "heapTotal": 236158976, + "heapUsed": 97691960, + "external": 6027362, + "arrayBuffers": 2183442, + "maxRssKiB": 374896 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 383352832, + "heapTotal": 235634688, + "heapUsed": 97692136, + "external": 6027417, + "arrayBuffers": 2183442, + "maxRssKiB": 374496 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 389545984, + "heapTotal": 238256128, + "heapUsed": 100284152, + "external": 6035625, + "arrayBuffers": 2183442, + "maxRssKiB": 380544 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 392822784, + "heapTotal": 237993984, + "heapUsed": 100283512, + "external": 6035612, + "arrayBuffers": 2183442, + "maxRssKiB": 383744 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 395542528, + "heapTotal": 237731840, + "heapUsed": 100282488, + "external": 6035667, + "arrayBuffers": 2183442, + "maxRssKiB": 386400 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 397312000, + "heapTotal": 239566848, + "heapUsed": 101895288, + "external": 6024046, + "arrayBuffers": 2183442, + "maxRssKiB": 388128 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 391249920, + "heapTotal": 239828992, + "heapUsed": 101895736, + "external": 6024034, + "arrayBuffers": 2183442, + "maxRssKiB": 382208 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 404504576, + "heapTotal": 240615424, + "heapUsed": 102922768, + "external": 6035867, + "arrayBuffers": 2183442, + "maxRssKiB": 395152 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 399147008, + "heapTotal": 241401856, + "heapUsed": 103461648, + "external": 6032238, + "arrayBuffers": 2183442, + "maxRssKiB": 389920 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 392888320, + "heapTotal": 241664000, + "heapUsed": 103462464, + "external": 6032226, + "arrayBuffers": 2183442, + "maxRssKiB": 383808 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 401145856, + "heapTotal": 242974720, + "heapUsed": 104951216, + "external": 6023735, + "arrayBuffers": 2183442, + "maxRssKiB": 391872 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 394887168, + "heapTotal": 243236864, + "heapUsed": 104945576, + "external": 6023723, + "arrayBuffers": 2201407, + "maxRssKiB": 385760 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 415088640, + "heapTotal": 243236864, + "heapUsed": 105529464, + "external": 6027733, + "arrayBuffers": 2183442, + "maxRssKiB": 405488 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 396738560, + "heapTotal": 245071872, + "heapUsed": 106505912, + "external": 6031915, + "arrayBuffers": 2183442, + "maxRssKiB": 387568 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 402964480, + "heapTotal": 244285440, + "heapUsed": 106505536, + "external": 6031927, + "arrayBuffers": 2183442, + "maxRssKiB": 393648 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 426049536, + "heapTotal": 245858304, + "heapUsed": 108042264, + "external": 6035556, + "arrayBuffers": 2183442, + "maxRssKiB": 416192 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 398770176, + "heapTotal": 246382592, + "heapUsed": 108269232, + "external": 6023722, + "arrayBuffers": 2183442, + "maxRssKiB": 389552 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 404996096, + "heapTotal": 246120448, + "heapUsed": 108268264, + "external": 6023734, + "arrayBuffers": 2183442, + "maxRssKiB": 395632 + }, + { + "pid": 42946, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 406765568, + "heapTotal": 248479744, + "heapUsed": 109917336, + "external": 6031926, + "arrayBuffers": 2183442, + "maxRssKiB": 397360 + }, + { + "pid": 42945, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 400523264, + "heapTotal": 248479744, + "heapUsed": 109919280, + "external": 6031914, + "arrayBuffers": 2183442, + "maxRssKiB": 391264 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 434831360, + "heapTotal": 247955456, + "heapUsed": 110649152, + "external": 6027422, + "arrayBuffers": 2183442, + "maxRssKiB": 424768 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 437010432, + "heapTotal": 250314752, + "heapUsed": 112421080, + "external": 6031912, + "arrayBuffers": 2183442, + "maxRssKiB": 426896 + }, + { + "pid": 42944, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 438861824, + "heapTotal": 251887616, + "heapUsed": 114073944, + "external": 6023720, + "arrayBuffers": 2183442, + "maxRssKiB": 428704 + }, + { + "pid": 42943, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 118259712, + "heapTotal": 61751296, + "heapUsed": 25956520, + "external": 5699073, + "arrayBuffers": 2064962, + "maxRssKiB": 115648 + }, + { + "pid": 42943, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 118833152, + "heapTotal": 62799872, + "heapUsed": 27026792, + "external": 5699678, + "arrayBuffers": 2064962, + "maxRssKiB": 116048 + } + ], + "files": [ + { + "path": "tests/integration/build/override/bookmarks.integration.test.ts", + "seconds": 1.261 + }, + { + "path": "tests/integration/build/override/history.integration.test.ts", + "seconds": 1.263 + }, + { + "path": "tests/integration/build/override/newtab.integration.test.ts", + "seconds": 1.363 + }, + { + "path": "tests/integration/build/override/competing.integration.test.ts", + "seconds": 2.079 + } + ], + "heapOutput": [ + "PASS build tests/integration/build/override/bookmarks.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/history.integration.test.ts (104 MB heap size)", + "PASS build tests/integration/build/override/newtab.integration.test.ts (108 MB heap size)", + "PASS build tests/integration/build/override/competing.integration.test.ts (25 MB heap size)" + ] + } + ], + "stressFixtureCopiesRemaining": 0, + "stress": [ + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 389103616, + "heapTotal": 242778112, + "heapUsed": 102882896, + "external": 21570785, + "arrayBuffers": 2175247, + "maxRssKiB": 380112 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 401883136, + "heapTotal": 244613120, + "heapUsed": 105476648, + "external": 12168661, + "arrayBuffers": 2183439, + "maxRssKiB": 392592 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 413384704, + "heapTotal": 246710272, + "heapUsed": 108164104, + "external": 12168601, + "arrayBuffers": 2183439, + "maxRssKiB": 403824 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 423149568, + "heapTotal": 249069568, + "heapUsed": 110779360, + "external": 12176851, + "arrayBuffers": 2183439, + "maxRssKiB": 413360 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 432275456, + "heapTotal": 251691008, + "heapUsed": 113414456, + "external": 12177106, + "arrayBuffers": 2183439, + "maxRssKiB": 422272 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 441352192, + "heapTotal": 254574592, + "heapUsed": 116031032, + "external": 12168972, + "arrayBuffers": 2183439, + "maxRssKiB": 431136 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 450412544, + "heapTotal": 256933888, + "heapUsed": 118536448, + "external": 12176795, + "arrayBuffers": 2183439, + "maxRssKiB": 439984 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 459718656, + "heapTotal": 259293184, + "heapUsed": 121140704, + "external": 12168661, + "arrayBuffers": 2183439, + "maxRssKiB": 449072 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 461799424, + "heapTotal": 261652480, + "heapUsed": 122913024, + "external": 12173151, + "arrayBuffers": 2183439, + "maxRssKiB": 451104 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 463798272, + "heapTotal": 262438912, + "heapUsed": 124554944, + "external": 12164959, + "arrayBuffers": 2183439, + "maxRssKiB": 453056 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 473972736, + "heapTotal": 265584640, + "heapUsed": 127228776, + "external": 12176808, + "arrayBuffers": 2183439, + "maxRssKiB": 462992 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 483000320, + "heapTotal": 267943936, + "heapUsed": 129772584, + "external": 12168674, + "arrayBuffers": 2183439, + "maxRssKiB": 471808 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 491356160, + "heapTotal": 270565376, + "heapUsed": 132298880, + "external": 12176806, + "arrayBuffers": 2183439, + "maxRssKiB": 479968 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 500547584, + "heapTotal": 272924672, + "heapUsed": 134813520, + "external": 12168672, + "arrayBuffers": 2183439, + "maxRssKiB": 488944 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 502267904, + "heapTotal": 274759680, + "heapUsed": 136310328, + "external": 12173465, + "arrayBuffers": 2183439, + "maxRssKiB": 490624 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 504004608, + "heapTotal": 276070400, + "heapUsed": 137829288, + "external": 12165273, + "arrayBuffers": 2183439, + "maxRssKiB": 492320 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 505774080, + "heapTotal": 277905408, + "heapUsed": 139344872, + "external": 12173154, + "arrayBuffers": 2183439, + "maxRssKiB": 494048 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 507641856, + "heapTotal": 279216128, + "heapUsed": 140834440, + "external": 12164962, + "arrayBuffers": 2183439, + "maxRssKiB": 495872 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 509345792, + "heapTotal": 281051136, + "heapUsed": 142353624, + "external": 12173153, + "arrayBuffers": 2183439, + "maxRssKiB": 497536 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 511246336, + "heapTotal": 281837568, + "heapUsed": 143854368, + "external": 12164961, + "arrayBuffers": 2183439, + "maxRssKiB": 499392 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 519847936, + "heapTotal": 284983296, + "heapUsed": 146347232, + "external": 12176850, + "arrayBuffers": 2183439, + "maxRssKiB": 507792 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 528105472, + "heapTotal": 287342592, + "heapUsed": 148863704, + "external": 12168716, + "arrayBuffers": 2183439, + "maxRssKiB": 515856 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 535838720, + "heapTotal": 289701888, + "heapUsed": 151417192, + "external": 12176848, + "arrayBuffers": 2183439, + "maxRssKiB": 523408 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 544751616, + "heapTotal": 292323328, + "heapUsed": 153916288, + "external": 12168714, + "arrayBuffers": 2183439, + "maxRssKiB": 532112 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 546537472, + "heapTotal": 293634048, + "heapUsed": 155402616, + "external": 12173477, + "arrayBuffers": 2183439, + "maxRssKiB": 533856 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 548192256, + "heapTotal": 295469056, + "heapUsed": 156894944, + "external": 12165285, + "arrayBuffers": 2183439, + "maxRssKiB": 535472 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 550027264, + "heapTotal": 297304064, + "heapUsed": 158372272, + "external": 12173166, + "arrayBuffers": 2183439, + "maxRssKiB": 537264 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 551649280, + "heapTotal": 298614784, + "heapUsed": 159856832, + "external": 12164974, + "arrayBuffers": 2183439, + "maxRssKiB": 538848 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 553385984, + "heapTotal": 300449792, + "heapUsed": 161337992, + "external": 12164973, + "arrayBuffers": 2183439, + "maxRssKiB": 540544 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 1 bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 554926080, + "heapTotal": 301760512, + "heapUsed": 162817024, + "external": 12173165, + "arrayBuffers": 2183439, + "maxRssKiB": 542048 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 562724864, + "heapTotal": 304381952, + "heapUsed": 165321168, + "external": 12168603, + "arrayBuffers": 2183439, + "maxRssKiB": 549664 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 570523648, + "heapTotal": 307003392, + "heapUsed": 167821104, + "external": 12176853, + "arrayBuffers": 2183439, + "maxRssKiB": 557280 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 578600960, + "heapTotal": 309624832, + "heapUsed": 170324280, + "external": 12176793, + "arrayBuffers": 2183439, + "maxRssKiB": 565168 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 586465280, + "heapTotal": 311984128, + "heapUsed": 172830000, + "external": 12168659, + "arrayBuffers": 2183439, + "maxRssKiB": 572848 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 594690048, + "heapTotal": 314605568, + "heapUsed": 175340544, + "external": 12177106, + "arrayBuffers": 2183439, + "maxRssKiB": 580880 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 601669632, + "heapTotal": 316964864, + "heapUsed": 177862992, + "external": 12168972, + "arrayBuffers": 2183439, + "maxRssKiB": 587696 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 610320384, + "heapTotal": 319586304, + "heapUsed": 180373680, + "external": 12176795, + "arrayBuffers": 2183439, + "maxRssKiB": 596144 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 618905600, + "heapTotal": 321945600, + "heapUsed": 182875864, + "external": 12168661, + "arrayBuffers": 2183439, + "maxRssKiB": 604528 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 620412928, + "heapTotal": 324042752, + "heapUsed": 184367912, + "external": 12173151, + "arrayBuffers": 2183439, + "maxRssKiB": 606000 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 622067712, + "heapTotal": 325353472, + "heapUsed": 185844424, + "external": 12164959, + "arrayBuffers": 2183439, + "maxRssKiB": 607616 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 629768192, + "heapTotal": 327712768, + "heapUsed": 188353320, + "external": 12176808, + "arrayBuffers": 2183439, + "maxRssKiB": 615136 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 637976576, + "heapTotal": 330072064, + "heapUsed": 190891360, + "external": 12168674, + "arrayBuffers": 2183439, + "maxRssKiB": 623152 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 645283840, + "heapTotal": 332955648, + "heapUsed": 193437376, + "external": 12176806, + "arrayBuffers": 2183439, + "maxRssKiB": 630288 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 653590528, + "heapTotal": 335577088, + "heapUsed": 195965664, + "external": 12168672, + "arrayBuffers": 2183439, + "maxRssKiB": 638400 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 655327232, + "heapTotal": 337412096, + "heapUsed": 197473672, + "external": 12173465, + "arrayBuffers": 2183439, + "maxRssKiB": 640096 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 657129472, + "heapTotal": 338198528, + "heapUsed": 198992520, + "external": 12165273, + "arrayBuffers": 2183439, + "maxRssKiB": 641856 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 658849792, + "heapTotal": 340295680, + "heapUsed": 200463856, + "external": 12173154, + "arrayBuffers": 2183439, + "maxRssKiB": 643536 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 660537344, + "heapTotal": 341606400, + "heapUsed": 201971456, + "external": 12164962, + "arrayBuffers": 2183439, + "maxRssKiB": 645184 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 662306816, + "heapTotal": 343179264, + "heapUsed": 203445704, + "external": 12173153, + "arrayBuffers": 2183439, + "maxRssKiB": 646912 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 664092672, + "heapTotal": 344752128, + "heapUsed": 204929600, + "external": 12164961, + "arrayBuffers": 2183439, + "maxRssKiB": 648656 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 672776192, + "heapTotal": 347111424, + "heapUsed": 207430448, + "external": 12176850, + "arrayBuffers": 2183439, + "maxRssKiB": 657136 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 681771008, + "heapTotal": 349732864, + "heapUsed": 209950712, + "external": 12168716, + "arrayBuffers": 2183439, + "maxRssKiB": 665920 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 690143232, + "heapTotal": 352354304, + "heapUsed": 212439960, + "external": 12176848, + "arrayBuffers": 2183439, + "maxRssKiB": 674096 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 698220544, + "heapTotal": 354713600, + "heapUsed": 214950832, + "external": 12168714, + "arrayBuffers": 2183439, + "maxRssKiB": 681984 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 700071936, + "heapTotal": 356548608, + "heapUsed": 216432456, + "external": 12173477, + "arrayBuffers": 2183439, + "maxRssKiB": 683792 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 701808640, + "heapTotal": 358383616, + "heapUsed": 217903960, + "external": 12165285, + "arrayBuffers": 2183439, + "maxRssKiB": 685488 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 703479808, + "heapTotal": 360218624, + "heapUsed": 219410232, + "external": 12173166, + "arrayBuffers": 2183439, + "maxRssKiB": 687120 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 705200128, + "heapTotal": 361529344, + "heapUsed": 220890328, + "external": 12164974, + "arrayBuffers": 2183439, + "maxRssKiB": 688800 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 706838528, + "heapTotal": 363364352, + "heapUsed": 222366392, + "external": 12173165, + "arrayBuffers": 2183439, + "maxRssKiB": 690400 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 2 bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 708673536, + "heapTotal": 364675072, + "heapUsed": 223834400, + "external": 12164973, + "arrayBuffers": 2183439, + "maxRssKiB": 692192 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 716750848, + "heapTotal": 367034368, + "heapUsed": 226329752, + "external": 12176795, + "arrayBuffers": 2183439, + "maxRssKiB": 700080 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 725630976, + "heapTotal": 369393664, + "heapUsed": 228823736, + "external": 12168661, + "arrayBuffers": 2183439, + "maxRssKiB": 708752 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 732626944, + "heapTotal": 372015104, + "heapUsed": 231319728, + "external": 12176793, + "arrayBuffers": 2183439, + "maxRssKiB": 715584 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 741801984, + "heapTotal": 374374400, + "heapUsed": 233852136, + "external": 12168659, + "arrayBuffers": 2183439, + "maxRssKiB": 724544 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 749780992, + "heapTotal": 376995840, + "heapUsed": 236343968, + "external": 12177106, + "arrayBuffers": 2183439, + "maxRssKiB": 732336 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 758398976, + "heapTotal": 379617280, + "heapUsed": 238833648, + "external": 12168972, + "arrayBuffers": 2183439, + "maxRssKiB": 740752 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 765902848, + "heapTotal": 382238720, + "heapUsed": 241336344, + "external": 12176795, + "arrayBuffers": 2183439, + "maxRssKiB": 748080 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 775553024, + "heapTotal": 384598016, + "heapUsed": 243828536, + "external": 12168661, + "arrayBuffers": 2183439, + "maxRssKiB": 757504 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 777125888, + "heapTotal": 386170880, + "heapUsed": 245310808, + "external": 12173151, + "arrayBuffers": 2183439, + "maxRssKiB": 759040 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 778797056, + "heapTotal": 387481600, + "heapUsed": 246808864, + "external": 12164959, + "arrayBuffers": 2183439, + "maxRssKiB": 760672 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 786661376, + "heapTotal": 390103040, + "heapUsed": 249303312, + "external": 12176808, + "arrayBuffers": 2183439, + "maxRssKiB": 768352 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 796016640, + "heapTotal": 392724480, + "heapUsed": 251801520, + "external": 12168674, + "arrayBuffers": 2183439, + "maxRssKiB": 777488 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 803356672, + "heapTotal": 395608064, + "heapUsed": 254310528, + "external": 12176806, + "arrayBuffers": 2183439, + "maxRssKiB": 784656 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 812580864, + "heapTotal": 397967360, + "heapUsed": 256821304, + "external": 12168672, + "arrayBuffers": 2183439, + "maxRssKiB": 793664 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 814333952, + "heapTotal": 399802368, + "heapUsed": 258285600, + "external": 12173465, + "arrayBuffers": 2183439, + "maxRssKiB": 795376 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 815939584, + "heapTotal": 400588800, + "heapUsed": 259754456, + "external": 12165273, + "arrayBuffers": 2183439, + "maxRssKiB": 796944 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 817790976, + "heapTotal": 402685952, + "heapUsed": 261214592, + "external": 12173154, + "arrayBuffers": 2183439, + "maxRssKiB": 798752 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 819675136, + "heapTotal": 403996672, + "heapUsed": 262692392, + "external": 12164962, + "arrayBuffers": 2183439, + "maxRssKiB": 800736 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 821608448, + "heapTotal": 405569536, + "heapUsed": 264161568, + "external": 12173153, + "arrayBuffers": 2183439, + "maxRssKiB": 802480 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 823885824, + "heapTotal": 407404544, + "heapUsed": 265642544, + "external": 12164961, + "arrayBuffers": 2183439, + "maxRssKiB": 804704 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 833257472, + "heapTotal": 409763840, + "heapUsed": 268132128, + "external": 12176850, + "arrayBuffers": 2183439, + "maxRssKiB": 813856 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 842727424, + "heapTotal": 412385280, + "heapUsed": 270641808, + "external": 12168716, + "arrayBuffers": 2183439, + "maxRssKiB": 823104 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 851034112, + "heapTotal": 414744576, + "heapUsed": 273133352, + "external": 12176848, + "arrayBuffers": 2183439, + "maxRssKiB": 831216 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 859619328, + "heapTotal": 417103872, + "heapUsed": 275675832, + "external": 12168714, + "arrayBuffers": 2183439, + "maxRssKiB": 839600 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 861241344, + "heapTotal": 418938880, + "heapUsed": 277158176, + "external": 12173477, + "arrayBuffers": 2183439, + "maxRssKiB": 841184 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 863141888, + "heapTotal": 420511744, + "heapUsed": 278681968, + "external": 12165285, + "arrayBuffers": 2183439, + "maxRssKiB": 843040 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 865189888, + "heapTotal": 422084608, + "heapUsed": 280144560, + "external": 12173166, + "arrayBuffers": 2183439, + "maxRssKiB": 845040 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 867221504, + "heapTotal": 423657472, + "heapUsed": 281616064, + "external": 12164974, + "arrayBuffers": 2183439, + "maxRssKiB": 847024 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 869564416, + "heapTotal": 425230336, + "heapUsed": 283098720, + "external": 12173165, + "arrayBuffers": 2183439, + "maxRssKiB": 849312 + }, + { + "pid": 42961, + "worker": "1", + "file": "tests/integration/build/override/memory-pilot.integration.test.ts", + "test": "memory round 3 bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 871759872, + "heapTotal": 426803200, + "heapUsed": 284561984, + "external": 12164973, + "arrayBuffers": 2183439, + "maxRssKiB": 851456 + } + ] +} diff --git a/tests/benchmarks/2026-09-28-override-pilot.md b/tests/benchmarks/2026-09-28-override-pilot.md new file mode 100644 index 00000000..412d7b59 --- /dev/null +++ b/tests/benchmarks/2026-09-28-override-pilot.md @@ -0,0 +1,155 @@ +# Override in-process pilot — 2026-09-28 + +## Decision + +Keep CLI builds as the default. The opt-in in-process path is faster at both worker limits, but repeated +builds retain memory after forced GC. Do not extend it to other integration groups yet. No global +`workerIdleMemoryLimit` or worker-count changes are part of this pilot. The separate absolute-root fix is +required by the helper (`67fd974f`) and is included in the final measurement series below. + +The prerequisite is `005e8457`: awaited, environment-restoring `buildApp`, with package tests under +`tests/integration/build/cli`. The experimental helper is selected by `ADNBN_OVERRIDE_BUILD_MODE=in-process`. + +## Identical work + +All 33 unique full scenario names match in every comparison run. The same temporary application copies and +assertions are used. Thirty ordinary page builds switch backend; all three competing-entrypoint cases remain +on CLI. Both backends use production mode and the same browsers/manifest versions. CLI runs from the fixture; +in-process builds receive its absolute root while keeping the worker's working directory unchanged. +There is no shared compilation cache or concurrent test within a file. + +Jest substitutes `createRequire` and `process.env`. The adapter uses Node's public `getBuiltinModule` API to +load the built package in the same worker, temporarily sharing the harness environment and restoring it in +`finally`. The diagnostics assert environment values, object identity and unchanged cwd after every test. +Production `buildApp` is unchanged by the pilot. + +## Time + +Apple M3 Max, 16 logical CPUs, macOS arm64, Node 24.5.0. One package build before the series; no production +changes during measurements. Each worker limit has a warm-up for each backend followed by three alternating +measured pairs. Time includes the complete Jest command and reporting, excludes the package build. Every +run uses `--expose-gc`, the existing macOS `--no-sparkplug`, `--logHeapUsage`, JSON reporting and the same +per-test diagnostic hook (including GC). These are instrumented comparisons, not uninstrumented CI timings. +With four files, the eight-worker setting can use at most four workers. + +| Worker limit | CLI median (range) | In-process median (range) | Ratio | +| ------------ | --------------------- | ------------------------- | ----- | +| 2 | 13.36 s (13.30–13.37) | 3.63 s (2.66–3.68) | 3.68× | +| 8 | 7.27 s (7.12–7.27) | 2.81 s (2.56–3.62) | 2.59× | + +The ranges are reported rather than promising the best run; three measured pairs per worker limit are a small sample. +These are local measurements, not evidence of Linux/Windows CI performance. + +## Memory and handles + +The sequential four-file run reports CLI worker heap of 35/46/47/47 MiB, versus approximately +118/131/139/133 MiB with the pilot. Worker RSS reaches about 180 MiB for CLI and 517 MiB for in-process. +CLI child-process memory is **not** included in those worker numbers; they are not total machine-memory comparisons. + +A separate diagnostic repeats the existing thirty page cases three times in one file and one worker. All +90 builds pass, but live heap and RSS keep growing despite an explicit GC after each test: + +| Builds completed | Heap after GC | Worker RSS | +| ---------------- | ------------- | ---------- | +| 1 | 98.1 MiB | 371.1 MiB | +| 30 | 155.3 MiB | 529.2 MiB | +| 60 | 213.5 MiB | 675.8 MiB | +| 90 | 271.4 MiB | 831.4 MiB | + +The earlier series also retained memory after GC (271.3 MiB heap / 830.7 MiB RSS at build 90). A standalone +Rspack reproduction now isolates compiler retention without the framework, config loader or Jest; see below. +This does not exclude additional retention in the complete framework build. + +Three additional runs per worker limit with `--workerIdleMemoryLimit=128MB` passed. However, this four-file +workload did not demonstrate mid-run worker replacement: recorded PID counts remain two/four. At two workers +one worker can finish all three in-process files before crossing the threshold at the end. The setting therefore +does not establish that accumulation is controlled, and it cannot recycle a worker midway through the 90-build +file. No memory limit was added to the regular Jest configuration. + +Separate `--detectOpenHandles` runs pass all 33 cases for both backends and report zero open handles. +Successful compiler shutdown and a clean handle report do not prove absence of retained JavaScript/native memory. + +Every series run leaves zero `fixture-*` copies in `.cache/integration`. Before measurement, 57 pre-existing +copies dated September 14–24 were preserved in `.cache/integration-before-pilot-2026-09-28`, not deleted. + +## Other findings and scope + +- The separate root-directory regression covers real entrypoints through CLI and `buildApp`, with two + projects built A → B → A from an unrelated cwd. The fix preserves absolute import roots, resolves source + aliases and compiler context from `rootDir`, and provides project package lookup for virtual modules. + Absolute filenames use portable virtual names without embedded Windows drive roots. Parser-relative + imports and its existing project `tsconfig` injection remain unchanged. The helper no longer changes cwd. +- The first series without cwd switching exposed shared temporary-module directories: closing one worker's + compiler deleted another project's entry modules. That series failed and is excluded from the final timings. + Entrypoint and offscreen temporary directories are now keyed by project root. An IPC-controlled regression + reproduced deletion before the fix and verifies that a pending compiler can run after the other closes. + Node 22 also exposed the external plugin's check-then-create race on `cwd/node_modules`; the adapter now + prepares directories recursively. The root regression covers Chrome and Firefox, including Offscreen's + generated background module resolved through its direct virtual alias. +- Moving internal CLI config fixtures under integrations exposed overly broad application discovery. + Preparation/typecheck now requires both `adnbn.config.ts` and `package.json`; two tests cover exclusion of + internal configs and inclusion of the three real override applications. +- All 33 pilot cases and three root-directory regressions also passed with local Node 22.18.0. + This does not replace the Linux/Windows CI matrix. + +## Standalone Rspack retention + +`rspack-retention.mjs` uses only Node and the installed `@rspack/core`. Each variant runs in a fresh process, +creates twelve compilers, awaits `run` and `close`, and counts `WeakRef` survivors after GC across event-loop +turns. Temporary sources are generated under the OS temporary directory and removed in `finally`. +It does not retain `Stats`, inspect private bindings or load framework code. + +On Rspack 1.7.11, Node 24.5.0, macOS arm64: + +| Configuration | Compilers remaining / 12 | +| ---------------------------------------------- | ------------------------ | +| Baseline, RegExp rule test, function include | 0 | +| Function rule test, use, resourceQuery | 12 | +| RegExp cache-group test | 0 | +| Function cache-group test | 12 | +| HTML file template or string templateContent | 0 | +| Function templateContent or templateParameters | 12 | + +The framework uses a functional asset rule and graph-dependent cache-group tests. Its `View.html()` passes +`template`, not `templateContent`. Replacing the asset rule alone would not remove the other triggers. +The installed Rspack adapter wraps functional resource conditions and passes them to the native binding; +the exact native ownership/release defect still needs upstream confirmation. Stable `external` and +`arrayBuffers` do not by themselves attribute all RSS growth to a native leak. + +```bash +node tests/benchmarks/rspack-retention.mjs +node --expose-gc tests/benchmarks/rspack-retention.mjs rule-test-function +``` + +[Raw retention results](2026-09-28-rspack-retention.json) record versions, counts and memory measurements. +The script is a diagnostic, not a GC-sensitive test in the regular suite; rerun it when evaluating a Rspack update. + +## Reproduction + +Run exclusively, without another integration run or a concurrent package build: + +```bash +npm run build +node tests/benchmarks/override-pilot.mjs +``` + +The runner writes logs, per-test worker metrics and Jest JSON under `.cache/benchmarks/override-pilot`. +It checks scenario identity and fixture cleanup. The 90-build diagnostic temporarily creates a test beside +existing override tests, reusing their matrices, and removes it in `finally`. `--stress-only` repeats that probe; +`--limits-only` appends memory-limit comparisons to an existing results file. Existing unrelated fixture copies +must be accounted for separately before a run: the runner never deletes them to satisfy its cleanup assertion. + +[Raw results](2026-09-28-override-pilot.json) include all timings, full scenario names, worker PID/heap/RSS samples, +Jest heap output, handle counts and the 90-build diagnostic. + +## Validation + +- Final `npm run check` with the default CLI backend: 183 suites / 2002 tests, package build, framework typecheck, + and fixture builds/typechecks passed. +- Chrome + Firefox on eight workers: 30 suites / 41 tests passed; one Chrome startup timed out at 30 seconds + with ECONNREFUSED before the scenario ran. The timeout was not increased. The isolated retry passed + in 4.3 s; all 31 suites / 42 tests passed on two workers; the final repeat took 63.2 s. This does not prove absence of rare + startup failures under heavier concurrency. +- Inventory: 214 files, each in exactly one project; zero local module mocks. +- Unit coverage: 139 suites / 1685 tests passed with localhost access. +- CI for these unpushed changes remains unverified; no push or workflow dispatch was performed. diff --git a/tests/benchmarks/2026-09-28-rspack-retention.json b/tests/benchmarks/2026-09-28-rspack-retention.json new file mode 100644 index 00000000..5129a525 --- /dev/null +++ b/tests/benchmarks/2026-09-28-rspack-retention.json @@ -0,0 +1,105 @@ +{ + "node": "v24.5.0", + "platform": "darwin", + "arch": "arm64", + "rspack": "1.7.11", + "iterations": 12, + "results": [ + { + "variant": "baseline", + "alive": 0, + "heapUsed": 8744624, + "rss": 147259392, + "external": 2006085, + "arrayBuffers": 11656 + }, + { + "variant": "rule-test-regexp", + "alive": 0, + "heapUsed": 8922544, + "rss": 164577280, + "external": 2014277, + "arrayBuffers": 19848 + }, + { + "variant": "rule-test-function", + "alive": 12, + "heapUsed": 14572240, + "rss": 163151872, + "external": 2014277, + "arrayBuffers": 19848 + }, + { + "variant": "rule-include-function", + "alive": 0, + "heapUsed": 8763512, + "rss": 148520960, + "external": 2006085, + "arrayBuffers": 11656 + }, + { + "variant": "rule-use-function", + "alive": 12, + "heapUsed": 14569976, + "rss": 157761536, + "external": 2014277, + "arrayBuffers": 19848 + }, + { + "variant": "rule-query-function", + "alive": 12, + "heapUsed": 14571880, + "rss": 164724736, + "external": 2014277, + "arrayBuffers": 19848 + }, + { + "variant": "cache-group-regexp", + "alive": 0, + "heapUsed": 8746896, + "rss": 150061056, + "external": 2007212, + "arrayBuffers": 12783 + }, + { + "variant": "cache-group-function", + "alive": 12, + "heapUsed": 14248744, + "rss": 153731072, + "external": 2007213, + "arrayBuffers": 12784 + }, + { + "variant": "html-file", + "alive": 0, + "heapUsed": 8770376, + "rss": 152862720, + "external": 2006085, + "arrayBuffers": 11656 + }, + { + "variant": "html-string", + "alive": 0, + "heapUsed": 8768248, + "rss": 149749760, + "external": 2006085, + "arrayBuffers": 11656 + }, + { + "variant": "html-content-function", + "alive": 12, + "heapUsed": 11720416, + "rss": 151650304, + "external": 2006085, + "arrayBuffers": 11656 + }, + { + "variant": "html-parameters-function", + "alive": 12, + "heapUsed": 11718440, + "rss": 154468352, + "external": 2006085, + "arrayBuffers": 11656 + } + ] +} diff --git a/tests/benchmarks/override-pilot-metrics.cjs b/tests/benchmarks/override-pilot-metrics.cjs new file mode 100644 index 00000000..61eb134f --- /dev/null +++ b/tests/benchmarks/override-pilot-metrics.cjs @@ -0,0 +1,32 @@ +const fs = require("node:fs"); +const assert = require("node:assert/strict"); +const nodeProcess = process.getBuiltinModule("process"); +const environment = nodeProcess.env; +const values = {...environment}; +const testValues = {...process.env}; +const cwd = nodeProcess.cwd(); + +afterEach(() => { + // Registered after the harness cleanup: both environment layers must be back to their initial state. + assert.equal(nodeProcess.env, environment); + assert.deepEqual({...nodeProcess.env}, values); + assert.deepEqual({...process.env}, testValues); + assert.equal(nodeProcess.cwd(), cwd); + + if (global.gc) { + global.gc(); + } + + fs.appendFileSync( + process.env.ADNBN_PILOT_METRICS, + JSON.stringify({ + pid: process.pid, + worker: process.env.JEST_WORKER_ID, + file: expect.getState().testPath, + test: expect.getState().currentTestName, + gc: typeof global.gc === "function", + ...process.memoryUsage(), + maxRssKiB: process.resourceUsage().maxRSS, + }) + "\n" + ); +}); diff --git a/tests/benchmarks/override-pilot.mjs b/tests/benchmarks/override-pilot.mjs new file mode 100644 index 00000000..2105d325 --- /dev/null +++ b/tests/benchmarks/override-pilot.mjs @@ -0,0 +1,214 @@ +import {spawnSync} from "node:child_process"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import {performance} from "node:perf_hooks"; + +const root = path.resolve(import.meta.dirname, "../.."); +const output = path.join(root, ".cache/benchmarks/override-pilot"); +fs.mkdirSync(output, {recursive: true}); +const flags = ["--expose-gc", ...(process.platform === "darwin" && process.arch === "arm64" ? ["--no-sparkplug"] : [])]; +const stressOnly = process.argv.includes("--stress-only"); +const resume = process.argv.includes("--limits-only") || stressOnly; +const report = resume + ? JSON.parse(fs.readFileSync(path.join(output, "results.json"), "utf8")) + : { + node: process.version, + platform: process.platform, + arch: process.arch, + cpus: os.cpus().length, + flags, + runs: [], + }; +let sequence = report.runs.length; +let expectedNames = report.runs[0]?.names; + +const run = (mode, workers, phase, extra = []) => { + const id = `${++sequence}-${phase}-${workers}-${mode}`; + const metrics = path.join(output, `${id}.jsonl`); + const resultPath = path.join(output, `${id}.json`); + const log = path.join(output, `${id}.log`); + fs.writeFileSync(metrics, ""); + const args = [ + ...flags, + "node_modules/jest/bin/jest.js", + "--selectProjects", + "build", + "--testPathPatterns=tests/integration/build/override", + `--maxWorkers=${workers}`, + "--logHeapUsage", + "--json", + `--outputFile=${resultPath}`, + "--setupFilesAfterEnv", + path.join(root, "tests/jest.setup.ts"), + path.join(import.meta.dirname, "override-pilot-metrics.cjs"), + ...extra, + ]; + const started = performance.now(); + const result = spawnSync(process.execPath, args, { + cwd: root, + env: {...process.env, ADNBN_OVERRIDE_BUILD_MODE: mode, ADNBN_PILOT_METRICS: metrics}, + encoding: "utf8", + timeout: 180_000, + maxBuffer: 8 * 1024 * 1024, + }); + const wallSeconds = (performance.now() - started) / 1000; + fs.writeFileSync(log, result.stdout + result.stderr); + + if (result.error || result.status !== 0) { + throw new Error(`${id} failed: ${result.error ?? result.status}; see ${log}`); + } + + const data = JSON.parse(fs.readFileSync(resultPath, "utf8")); + const names = data.testResults.flatMap(file => file.assertionResults.map(test => test.fullName)).sort(); + expectedNames ??= names; + + if (names.length !== 33 || JSON.stringify(names) !== JSON.stringify(expectedNames)) { + throw new Error(`${id}: scenario inventory changed`); + } + + const leftovers = fs.readdirSync(path.join(root, ".cache/integration")).filter(name => name.startsWith("fixture-")); + + if (leftovers.length) { + throw new Error(`${id}: ${leftovers.length} fixture copies remain`); + } + + const samples = fs + .readFileSync(metrics, "utf8") + .trim() + .split("\n") + .map(JSON.parse) + .map(sample => ({...sample, file: path.relative(root, sample.file)})); + const entry = { + id, + phase, + mode, + workers, + wallSeconds, + tests: data.numPassedTests, + names, + leftovers: leftovers.length, + openHandles: data.openHandles?.length ?? null, + samples, + files: data.testResults.map(file => ({ + path: path.relative(root, file.name), + seconds: (file.endTime - file.startTime) / 1000, + })), + heapOutput: result.stderr.split("\n").filter(line => line.includes("heap size")), + }; + report.runs.push(entry); + fs.writeFileSync(path.join(output, "results.json"), JSON.stringify(report, null, 2) + "\n"); + console.log(`${id}: ${wallSeconds.toFixed(2)} s, 33 tests, 0 leftover copies`); +}; + +if (!resume) { + for (const workers of [2, 8]) { + for (const mode of ["cli", "in-process"]) { + run(mode, workers, "warmup"); + } + + for (let round = 0; round < 3; round++) { + for (const mode of round % 2 ? ["in-process", "cli"] : ["cli", "in-process"]) { + run(mode, workers, "measured"); + } + } + } + + for (const mode of ["cli", "in-process"]) { + run(mode, 1, "memory"); + run(mode, 1, "handles", ["--detectOpenHandles"]); + } +} + +if (!stressOnly) { + for (const workers of [2, 8]) { + for (let round = 0; round < 3; round++) { + run("in-process", workers, "limited", ["--workerIdleMemoryLimit=128MB"]); + } + } +} + +if (!resume || stressOnly) { + const temporary = path.join(root, "tests/integration/build/override/memory-pilot.integration.test.ts"); + + if (fs.existsSync(temporary)) { + throw new Error(`Refusing to replace ${temporary}`); + } + + const cases = ["newtab", "history", "bookmarks"] + .map( + page => + fs + .readFileSync(path.join(path.dirname(temporary), `${page}.integration.test.ts`), "utf8") + .split("jest.setTimeout(90_000);")[1] + ) + .join("\n"); + const metrics = path.join(output, "stress.jsonl"); + const resultPath = path.join(output, "stress.json"); + fs.writeFileSync(metrics, ""); + + try { + fs.writeFileSync( + temporary, + 'import {testOverridePage} from "./override-utils";\njest.setTimeout(90_000);\ndescribe.each([1, 2, 3])("memory round %s", () => {\n' + + cases + + "\n});\n" + ); + const result = spawnSync( + process.execPath, + [ + ...flags, + "node_modules/jest/bin/jest.js", + "--selectProjects", + "build", + "--runTestsByPath", + temporary, + "--runInBand", + "--logHeapUsage", + "--json", + `--outputFile=${resultPath}`, + "--setupFilesAfterEnv", + path.join(root, "tests/jest.setup.ts"), + path.join(import.meta.dirname, "override-pilot-metrics.cjs"), + ], + { + cwd: root, + env: {...process.env, ADNBN_OVERRIDE_BUILD_MODE: "in-process", ADNBN_PILOT_METRICS: metrics}, + encoding: "utf8", + timeout: 180_000, + maxBuffer: 8 * 1024 * 1024, + } + ); + fs.writeFileSync(path.join(output, "stress.log"), result.stdout + result.stderr); + + if (result.error || result.status !== 0) { + throw new Error("Memory stress probe failed; see stress.log"); + } + + const data = JSON.parse(fs.readFileSync(resultPath, "utf8")); + + if (data.numPassedTests !== 90) { + throw new Error("Expected 90 successful builds in one worker"); + } + + const leftovers = fs + .readdirSync(path.join(root, ".cache/integration")) + .filter(name => name.startsWith("fixture-")); + + if (leftovers.length) { + throw new Error(`Memory stress probe left ${leftovers.length} fixture copies`); + } + + report.stressFixtureCopiesRemaining = leftovers.length; + report.stress = fs + .readFileSync(metrics, "utf8") + .trim() + .split("\n") + .map(JSON.parse) + .map(sample => ({...sample, file: path.relative(root, sample.file)})); + fs.writeFileSync(path.join(output, "results.json"), JSON.stringify(report, null, 2) + "\n"); + console.log("Memory stress probe: 90 tests passed"); + } finally { + fs.rmSync(temporary); + } +} diff --git a/tests/benchmarks/rspack-retention.mjs b/tests/benchmarks/rspack-retention.mjs new file mode 100644 index 00000000..eeca0163 --- /dev/null +++ b/tests/benchmarks/rspack-retention.mjs @@ -0,0 +1,183 @@ +import assert from "node:assert/strict"; +import {spawnSync} from "node:child_process"; +import {mkdtemp, readFile, rm, writeFile} from "node:fs/promises"; +import {createRequire} from "node:module"; +import os from "node:os"; +import path from "node:path"; +import {setImmediate} from "node:timers/promises"; +import {fileURLToPath} from "node:url"; +import {HtmlRspackPlugin, rspack} from "@rspack/core"; + +// Each variant runs in a fresh process. No framework code, Jest, or retained Stats. +const variants = [ + "baseline", + "rule-test-regexp", + "rule-test-function", + "rule-include-function", + "rule-use-function", + "rule-query-function", + "cache-group-regexp", + "cache-group-function", + "html-file", + "html-string", + "html-content-function", + "html-parameters-function", +]; +const [variant] = process.argv.slice(2); +const iterations = 12; + +if (!variant) { + const results = variants.map(name => { + const child = spawnSync(process.execPath, ["--expose-gc", fileURLToPath(import.meta.url), name], { + encoding: "utf8", + timeout: 60_000, + }); + assert.equal(child.error, undefined); + assert.equal(child.status, 0, child.stderr); + + return JSON.parse(child.stdout); + }); + const require = createRequire(import.meta.url); + console.log( + JSON.stringify( + { + node: process.version, + platform: process.platform, + arch: process.arch, + rspack: require("@rspack/core/package.json").version, + iterations, + results, + }, + null, + 2 + ) + ); +} else { + assert.ok(variants.includes(variant), `Unknown variant: ${variant}`); + assert.equal(typeof global.gc, "function", "Run a single variant with node --expose-gc"); + const directory = await mkdtemp(path.join(os.tmpdir(), "rspack-retention-")); + const references = []; + + try { + const imports = []; + + for (let index = 0; index < 24; index++) { + await writeFile(path.join(directory, `module-${index}.js`), `export default ${index};\n`); + imports.push(`import value${index} from "./module-${index}.js"; console.log(value${index});`); + } + + await writeFile(path.join(directory, "entry.js"), imports.join("\n")); + await writeFile(path.join(directory, "loader.cjs"), "module.exports = source => source;\n"); + await writeFile(path.join(directory, "template.html"), "Retention probe"); + + const configure = () => { + const config = { + mode: "production", + context: directory, + entry: path.join(directory, "entry.js"), + devtool: false, + output: {path: path.join(directory, "dist"), filename: "[name].js"}, + module: {rules: []}, + optimization: {}, + plugins: [], + }; + const use = [path.join(directory, "loader.cjs")]; + + if (variant === "rule-test-regexp") { + config.module.rules.push({test: /\.js$/, use}); + } else if (variant === "rule-test-function") { + config.module.rules.push({test: resource => resource.endsWith(".js"), use}); + } else if (variant === "rule-include-function") { + config.module.rules.push({include: resource => resource.startsWith(directory), use}); + } else if (variant === "rule-use-function") { + config.module.rules.push({test: /\.js$/, use: () => use}); + } else if (variant === "rule-query-function") { + config.module.rules.push({resourceQuery: query => query === "", use}); + } else if (variant.startsWith("cache-group-")) { + config.optimization.splitChunks = { + chunks: "all", + cacheGroups: { + probe: { + test: + variant === "cache-group-regexp" + ? /module-\d+\.js$/ + : module => /module-\d+\.js$/.test(module.resource ?? ""), + name: "probe", + minSize: 0, + }, + }, + }; + } else if (variant === "html-file") { + config.plugins.push(new HtmlRspackPlugin({template: path.join(directory, "template.html")})); + } else if (variant === "html-string") { + config.plugins.push( + new HtmlRspackPlugin({templateContent: "Retention probe"}) + ); + } else if (variant === "html-content-function") { + config.plugins.push( + new HtmlRspackPlugin({templateContent: () => "Retention probe"}) + ); + } else if (variant === "html-parameters-function") { + config.plugins.push( + new HtmlRspackPlugin({ + template: path.join(directory, "template.html"), + templateParameters: params => params, + }) + ); + } + + return config; + }; + const build = async () => { + const compiler = rspack(configure()); + references.push(new WeakRef(compiler)); + + try { + await new Promise((resolve, reject) => { + compiler.run((error, stats) => { + if (error || !stats || stats.hasErrors()) { + reject(error ?? new Error(stats?.toString({all: false, errors: true}) ?? "No stats")); + } else { + resolve(); + } + }); + }); + } finally { + await new Promise((resolve, reject) => { + compiler.close(error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + } + }; + + for (let index = 0; index < iterations; index++) { + await build(); + } + + // Cross event-loop turns before collecting; do not dereference between collections. + for (let index = 0; index < 4; index++) { + await setImmediate(); + global.gc(); + } + + await readFile(path.join(directory, "dist/main.js")); + const memory = process.memoryUsage(); + console.log( + JSON.stringify({ + variant, + alive: references.filter(reference => reference.deref()).length, + heapUsed: memory.heapUsed, + rss: memory.rss, + external: memory.external, + arrayBuffers: memory.arrayBuffers, + }) + ); + } finally { + await rm(directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); + } +} From cf3a311da88d3029f97e177645cfbdd6839b2654 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:37:57 +0300 Subject: [PATCH 29/36] docs: define production boundaries for test work --- AGENTS.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 1a1cc1aa..4d60b31e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,6 +4,14 @@ - `CHANGELOG.md` is generated by the release pipeline during publication. Do not edit it manually; document behavior and migration guidance in the relevant documentation instead. +## Production code changes during test work + +- Authorization to add, migrate, refactor, or accelerate tests does not authorize changes to production code, including CLI, build configuration, resolvers, plugins, public contracts, or resource lifecycles. Do not change working production behavior merely to accommodate a test runner, fixture layout, parallel execution, or an experimental test backend. +- Distinguish a product defect from a limitation of the test setup. Before proposing a production fix, demonstrate how supported consumer usage violates the intended contract, independently of test-only assumptions. A failing experiment or a faster test run alone is not evidence of a product defect. Keep a confirmed fix within an already authorized scope; otherwise present the evidence and obtain agreement before changing production code. +- First use the existing public boundaries and realistic execution model in tests. Prefer properly isolated fixtures, processes, and resources owned by the test session. Preserve scenario coverage and observable behavior; do not hide failures with private implementation access, hidden global state, arbitrary retries, or special production branches for tests. +- If a sound test-layer solution is unavailable, stop the affected implementation and propose alternatives before editing production code. Explain the ownership boundary, consumer impact, lifecycle and cleanup, concurrency and platform constraints, tradeoffs, and validation. Choose established mechanisms that fit this codebase; neither novelty nor passing tests justifies an architectural workaround. +- Treat newly discovered production changes as a separate scope decision, even inside an approved test optimization pilot. Agreement on a product fix does not authorize unrelated infrastructure redesign. Keep product fixes and test-only changes separately reviewable. + ## Cross-platform tests - The full CI matrix in `.github/workflows/ci.yml` runs on Linux and Windows with Node.js 22 and 24. New and changed non-browser tests must work on both operating systems. Chrome and Firefox browser integrations currently run on Linux; consult the workflow for the current matrix. From 30174892476e5020dc776c40d612568e9856e558 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:48:27 +0300 Subject: [PATCH 30/36] refactor(cli): remove pilot virtual-directory workaround Keep absolute-root resolution and portable entrypoint names. Restore the external plugin's directory names. Preserve the shared-cwd conflict as a standalone upstream diagnostic. --- .../plugins/entrypoint/EntrypointPlugin.ts | 12 +-- src/cli/bundler/utils/index.ts | 1 - src/cli/bundler/utils/virtual-module.ts | 15 ---- src/cli/plugins/offscreen/index.ts | 6 +- tests/benchmarks/virtual-module-conflict.mjs | 74 +++++++++++++++++++ tests/integration/README.md | 13 ++-- .../build/cli/root-dir.integration.test.ts | 67 +---------------- 7 files changed, 87 insertions(+), 101 deletions(-) delete mode 100644 src/cli/bundler/utils/virtual-module.ts create mode 100644 tests/benchmarks/virtual-module-conflict.mjs diff --git a/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts b/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts index d930a712..92a9fcb1 100644 --- a/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts +++ b/src/cli/bundler/plugins/entrypoint/EntrypointPlugin.ts @@ -4,7 +4,6 @@ import {createHash} from "node:crypto"; import {Compiler, DynamicEntryPlugin, type EntryDescription, EntryNormalized} from "@rspack/core"; import {RspackVirtualModulePlugin as VirtualModulesPlugin} from "rspack-plugin-virtual-module"; -import {prepareVirtualModuleDirectory} from "../../utils/virtual-module"; import {EntrypointEntries, EntrypointFile} from "@typing/entrypoint"; @@ -37,7 +36,6 @@ export default class EntrypointPlugin { private _plugin?: VirtualModulesPlugin; private _modules?: EntrypointPluginEntryModules; - private context?: string; private readonly _entryOptions: EntrypointPluginEntryOptionsResolver[] = []; protected template?: EntrypointPluginTemplate; @@ -66,14 +64,7 @@ export default class EntrypointPlugin { const modules = Object.fromEntries(this.getModuleContents(this.modules)); - if (!this.context) { - throw new Error("EntrypointPlugin must be applied before writing virtual modules"); - } - - return (this._plugin = new VirtualModulesPlugin( - modules, - prepareVirtualModuleDirectory(this.context, "entrypoint") - )); + return (this._plugin = new VirtualModulesPlugin(modules, "entrypoint")); } protected get modules(): EntrypointPluginEntryModules { @@ -114,7 +105,6 @@ export default class EntrypointPlugin { } public apply(compiler: Compiler): void { - this.context = compiler.context; this.plugin.apply(compiler); compiler.hooks.entryOption.tap(this.pluginName, (_, entry) => { diff --git a/src/cli/bundler/utils/index.ts b/src/cli/bundler/utils/index.ts index bd4de9b9..bae2efef 100644 --- a/src/cli/bundler/utils/index.ts +++ b/src/cli/bundler/utils/index.ts @@ -1,4 +1,3 @@ export * from "./optimization"; export * from "./app-filename"; export * from "./assets"; -export {prepareVirtualModuleDirectory} from "./virtual-module"; diff --git a/src/cli/bundler/utils/virtual-module.ts b/src/cli/bundler/utils/virtual-module.ts deleted file mode 100644 index 86ac2298..00000000 --- a/src/cli/bundler/utils/virtual-module.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {createHash} from "node:crypto"; -import {mkdirSync} from "node:fs"; -import path from "node:path"; - -// The external virtual-module plugin stores files under cwd/node_modules and removes -// the entire directory on close. Independent project builds must not share it. -export const prepareVirtualModuleDirectory = (context: string, namespace: string): string => { - const key = createHash("sha256").update(path.resolve(context)).digest("hex"); - const directory = `${namespace}-${key}`; - - // Its existsSync + mkdirSync sequence races when workers share a fresh cwd. - mkdirSync(path.resolve("node_modules", directory), {recursive: true}); - - return directory; -}; diff --git a/src/cli/plugins/offscreen/index.ts b/src/cli/plugins/offscreen/index.ts index a581f0db..3d2ab435 100644 --- a/src/cli/plugins/offscreen/index.ts +++ b/src/cli/plugins/offscreen/index.ts @@ -4,7 +4,7 @@ import HtmlRspackTagsPlugin from "html-rspack-tags-plugin"; import {definePlugin} from "@main/plugin"; -import {EntrypointPlugin, GenerateModulePlugin, prepareVirtualModuleDirectory} from "@cli/bundler"; +import {EntrypointPlugin, GenerateModulePlugin} from "@cli/bundler"; import {virtualOffscreenBackgroundModule} from "@cli/virtual"; import Offscreen from "./Offscreen"; @@ -68,14 +68,12 @@ export default definePlugin(() => { plugins.push(plugin, ...htmlPlugins, ...tagsPlugins); if (config.manifestVersion === 2 || config.browser === Browser.Firefox) { - const directory = prepareVirtualModuleDirectory(config.rootDir, "offscreen"); - plugins.push( new RspackVirtualModulePlugin( { [OffscreenBackgroundModule]: virtualOffscreenBackgroundModule(), }, - directory + "virtual" ) ); diff --git a/tests/benchmarks/virtual-module-conflict.mjs b/tests/benchmarks/virtual-module-conflict.mjs new file mode 100644 index 00000000..a456dbde --- /dev/null +++ b/tests/benchmarks/virtual-module-conflict.mjs @@ -0,0 +1,74 @@ +import assert from "node:assert/strict"; +import {fork} from "node:child_process"; +import {mkdirSync, mkdtempSync, rmSync} from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const reproduce = async () => { + const directory = mkdtempSync(path.join(os.tmpdir(), "adnbn-root-dir-parallel-")); + const projectRoot = path.resolve(import.meta.dirname, "../.."); + const sharedCwd = path.join(directory, "caller"); + mkdirSync(path.join(sharedCwd, "node_modules", "entrypoint"), {recursive: true}); + mkdirSync(path.join(sharedCwd, "node_modules", "virtual")); + + const start = name => { + const root = path.join(directory, name); + mkdirSync(root); + const child = fork( + path.join(projectRoot, "tests/integration/build/cli/scripts/verify-root-dir.mjs"), + [projectRoot, root, "parallel", sharedCwd], + { + execArgv: [], + stdio: ["ignore", "pipe", "pipe", "ipc"], + timeout: 60_000, + killSignal: "SIGKILL", + } + ); + let stderr = ""; + child.stdout.resume(); + child.stderr.on("data", data => { + stderr += data; + }); + const completed = new Promise(resolve => + child.once("close", (code, signal) => resolve({code, signal, stderr})) + ); + const ready = new Promise((resolve, reject) => { + child.once("message", message => { + if (message === "ready") { + resolve(); + } else { + reject(new Error(`Unexpected build message: ${message}`)); + } + }); + child.once("error", reject); + child.once("close", () => reject(new Error(`Build stopped before ready: ${stderr}`))); + }); + + return {child, ready, completed}; + }; + const builds = [start("first"), start("second")]; + + try { + await Promise.all(builds.map(build => build.ready)); + // Both compilers have installed their virtual modules. Finish and close one + // before the other's compilation starts, so shared-directory deletion is observable. + builds[0].child.send("build"); + assert.deepEqual(await builds[0].completed, {code: 0, signal: null, stderr: ""}); + builds[1].child.send("build"); + const failed = await builds[1].completed; + assert.notEqual(failed.code, 0); + assert.match(failed.stderr, /Cannot find module|Module not found/); + console.log("Confirmed external-plugin limitation: closing A removes B virtual modules"); + } finally { + for (const {child} of builds) { + if (child.exitCode === null && child.signalCode === null) { + child.kill("SIGKILL"); + } + } + + await Promise.all(builds.map(build => build.completed)); + rmSync(directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); + } +}; + +await reproduce(); diff --git a/tests/integration/README.md b/tests/integration/README.md index ad6b8f3e..7e26d2a8 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -213,11 +213,14 @@ and `buildApp`, from an unrelated working directory. Absolute roots, paths conta imports and the generated `@/` alias are covered. Compiler context, source aliases and fallback package resolution use the project root; the caller's working directory is unchanged. The external virtual-module plugin still creates temporary files under the caller's `node_modules` and removes its module directory on -compiler shutdown; these builds therefore require a writable working directory. Entrypoint and offscreen -module directories are keyed by project root so independent workers do not remove each other's files. -An IPC-controlled regression pauses two compilers after module creation, closes one, then runs the other. -The sequence includes Chrome and Firefox with Offscreen, including its generated background module. -Temporary directory creation is recursive, avoiding the external plugin's check-then-create race in a fresh cwd. +compiler shutdown; these builds therefore require a writable working directory. The sequence includes +Chrome and Firefox with Offscreen, including its generated background module. + +The external plugin does not isolate overlapping builds sharing a cwd: closing one compiler can remove +another compiler's virtual modules. This remains an upstream limitation, not a supported concurrency contract. +After `npm run build`, run `node tests/benchmarks/virtual-module-conflict.mjs` to reproduce the controlled +creation/close race. The diagnostic expects the second build to fail and is deliberately outside Jest. +Parallel test sessions use distinct working directories; production directory naming is unchanged. - `types/content`: shared Content and adapter render types through the public source and built package APIs, callback props inference, and iframe-navigation restrictions for both define functions. - `types/view`: shared View and adapter render types through the public source and built package APIs, render and container props inference, the render contract adopted by Offscreen and Sandbox, and the rejection of Promise and plain-object render values. diff --git a/tests/integration/build/cli/root-dir.integration.test.ts b/tests/integration/build/cli/root-dir.integration.test.ts index c3f150f0..67d6f981 100644 --- a/tests/integration/build/cli/root-dir.integration.test.ts +++ b/tests/integration/build/cli/root-dir.integration.test.ts @@ -1,5 +1,5 @@ -import {fork, spawnSync} from "node:child_process"; -import {mkdirSync, mkdtempSync, rmSync} from "node:fs"; +import {spawnSync} from "node:child_process"; +import {mkdtempSync, rmSync} from "node:fs"; import os from "node:os"; import path from "node:path"; @@ -23,66 +23,3 @@ test.each(["api", "cli"])("%s builds absolute roots from an unrelated working di rmSync(directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); } }); - -test("closing one project does not remove another project's pending virtual entrypoints", async () => { - const directory = mkdtempSync(path.join(os.tmpdir(), "adnbn-root-dir-parallel-")); - const projectRoot = path.resolve(__dirname, "../../../.."); - const sharedCwd = path.join(directory, "caller"); - mkdirSync(sharedCwd); - - const start = (name: string) => { - const root = path.join(directory, name); - mkdirSync(root); - const child = fork( - path.join(__dirname, "scripts/verify-root-dir.mjs"), - [projectRoot, root, "parallel", sharedCwd], - { - execArgv: [], - stdio: ["ignore", "pipe", "pipe", "ipc"], - timeout: 60_000, - killSignal: "SIGKILL", - } - ); - let stderr = ""; - child.stdout!.resume(); - child.stderr!.on("data", data => { - stderr += data; - }); - const completed = new Promise(resolve => - child.once("close", (code, signal) => resolve({code, signal, stderr})) - ); - const ready = new Promise((resolve, reject) => { - child.once("message", message => { - if (message === "ready") { - resolve(); - } else { - reject(new Error(`Unexpected build message: ${message}`)); - } - }); - child.once("error", reject); - child.once("close", () => reject(new Error(`Build stopped before ready: ${stderr}`))); - }); - - return {child, ready, completed}; - }; - const builds = [start("first"), start("second")]; - - try { - await Promise.all(builds.map(build => build.ready)); - // Both compilers have installed their virtual modules. Finish and close one - // before the other's compilation starts, so shared-directory deletion is observable. - for (const build of builds) { - build.child.send("build"); - expect(await build.completed).toEqual({code: 0, signal: null, stderr: ""}); - } - } finally { - for (const {child} of builds) { - if (child.exitCode === null && child.signalCode === null) { - child.kill("SIGKILL"); - } - } - - await Promise.all(builds.map(build => build.completed)); - rmSync(directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); - } -}); From fa6fd32de9d310d714392b9b159a917642d4fbc6 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:56:52 +0300 Subject: [PATCH 31/36] test: isolate override builds in file-owned child sessions Replace the Jest-worker backend with a sequential IPC child per page file. Keep absolute fixture roots, fixed session cwd and the three CLI error cases. Preserve error causes and compiler diagnostics; terminate on timeout or IPC loss. Cover recovery, overlap rejection, shutdown, cleanup and A-B-A builds. --- tests/integration/README.md | 38 ++-- .../cli/BuildSession.integration.test.ts | 159 ++++++++++++++ .../fixtures/build-session/adnbn.config.ts | 25 +++ .../fixtures/build-session/invalid-newtab.ts | 4 + .../build/override/override-utils.ts | 19 +- tests/integration/utils/BuildSession.ts | 197 ++++++++++++++++++ .../integration/utils/build-session-child.mjs | 78 +++++++ .../utils/build-session-protocol.ts | 30 +++ tests/integration/utils/fixture.ts | 39 +--- 9 files changed, 541 insertions(+), 48 deletions(-) create mode 100644 tests/integration/build/cli/BuildSession.integration.test.ts create mode 100644 tests/integration/build/cli/fixtures/build-session/adnbn.config.ts create mode 100644 tests/integration/build/cli/fixtures/build-session/invalid-newtab.ts create mode 100644 tests/integration/utils/BuildSession.ts create mode 100644 tests/integration/utils/build-session-child.mjs create mode 100644 tests/integration/utils/build-session-protocol.ts diff --git a/tests/integration/README.md b/tests/integration/README.md index 7e26d2a8..72916ea5 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -13,8 +13,10 @@ tests/integration/ │ │ ├── cli.integration.test.ts │ │ ├── build-api.integration.test.ts │ │ ├── dotenv.integration.test.ts +│ │ ├── BuildSession.integration.test.ts │ │ ├── root-dir.integration.test.ts │ │ ├── fixtures/ +│ │ │ ├── build-session/ │ │ │ ├── exit-code/ │ │ │ ├── environment/ │ │ │ ├── dotenv/ @@ -267,24 +269,34 @@ The extra dotenv pass in `c12` is disabled. **Behavior change:** running the CLI project no longer reads that directory's `.env`, and its interpolated values no longer reach user config or plugins. Put build variables in the app/project dotenv files or export them explicitly in the calling process. -## Override in-process pilot +## Override build-session pilot -Override page tests can select `ADNBN_OVERRIDE_BUILD_MODE=in-process`; the default is `cli`. -The fixture helper owns the alternative build path, while application copies, scenario names and assertions -are identical. Thirty page builds use `buildApp`; the three competing-entrypoint scenarios always use the CLI. +Override page tests can select `ADNBN_OVERRIDE_BUILD_MODE=session`; the default is `cli`. +Each page test file owns one child Node process through `BuildSession`. The process loads the built package +once and handles its ten builds sequentially. Application copies, scenario names and assertions are unchanged; +the three competing-entrypoint scenarios always use the CLI. -The in-process helper uses Node's native loader inside the Jest worker. Jest substitutes its own loader and -`process.env`, so the helper temporarily lends the native process the test environment and restores it in -`finally`. It passes an absolute fixture root without changing the worker's cwd. No concurrent tests -or background builds are allowed. This is a test-only execution adapter, not a change to ordinary CLI behavior. +The session has a private, fixed cwd and receives absolute fixture roots. Its environment is copied for each +request and restored inside the child. Jest's environment, cwd and loader are never replaced. Neither concurrent +requests nor automatic retries are supported. Errors preserve their message, cause chain and compiler diagnostics. +The parent owns the deadline, kills a timed-out process, and waits for `close` before rejecting the request. +Normal disposal asks the child to exit, waits for `close`, and removes the session directory. If the owner dies, +the child's IPC `disconnect` handler exits immediately, even during a pending build. An abrupt owner death may +leave a temporary directory, but must not leave a running build process. ```bash -npx cross-env ADNBN_OVERRIDE_BUILD_MODE=in-process npm run test:run -- --selectProjects build --testPathPatterns=tests/integration/build/override --maxWorkers=2 npm run build +npx cross-env ADNBN_OVERRIDE_BUILD_MODE=session npm run test:run -- --selectProjects build --testPathPatterns=tests/integration/build/override --maxWorkers=2 node tests/benchmarks/override-pilot.mjs ``` -The pilot is opt-in: it is faster locally, but repeated builds retain memory even after GC. The benchmark runner -records worker heap/RSS, verifies environment/cwd restoration and cleanup, and compares both worker counts. -Its 90-build diagnostic creates a temporary test file and removes it in `finally`; run benchmarks exclusively. -See [the pilot report](../benchmarks/2026-09-28-override-pilot.md) for measurements, limitations and the decision. +The child exits after each file, bounding compiler retention to that file's builds. This does not fix Rspack's +retention or establish a peak-memory bound. The benchmark compares two/eight workers, records child PID, +heap/RSS and peak RSS after each build, checks the same 33 scenario names, and verifies process and directory +cleanup. With three session-backed files there are at most three build children, even with eight Jest workers. +Run benchmarks exclusively, without another build or test run. + +See the [session measurements](../benchmarks/2026-09-28-override-session.md) for timings and child memory. +The [earlier in-process report](../benchmarks/2026-09-28-override-pilot.md) describes the superseded experiment; +its timings do not apply to the session backend. The session pilot remains opt-in pending fresh measurements +and the Linux/Windows Node 22/24 CI matrix. diff --git a/tests/integration/build/cli/BuildSession.integration.test.ts b/tests/integration/build/cli/BuildSession.integration.test.ts new file mode 100644 index 00000000..bb9e343e --- /dev/null +++ b/tests/integration/build/cli/BuildSession.integration.test.ts @@ -0,0 +1,159 @@ +import {fork} from "node:child_process"; +import {cp, mkdir, mkdtemp, readFile, rm, symlink, access} from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + +import BuildSession, {BuildSessionError} from "../../utils/BuildSession"; + +const projectRoot = path.resolve(__dirname, "../../../.."); +const fixtures = path.join(__dirname, "fixtures"); +let directory: string; +let root: string; +let session: BuildSession; + +jest.setTimeout(60_000); + +beforeEach(async () => { + directory = await mkdtemp(path.join(os.tmpdir(), "adnbn-session-test-")); + root = path.join(directory, "project a"); + await cp(path.join(fixtures, "root-dir"), root, {recursive: true}); + await cp(path.join(fixtures, "build-session/adnbn.config.ts"), path.join(root, "adnbn.config.ts")); + await mkdir(path.join(root, "node_modules")); + await symlink( + projectRoot, + path.join(root, "node_modules/adnbn"), + process.platform === "win32" ? "junction" : "dir" + ); + + session = await BuildSession.create(projectRoot); +}); + +afterEach(async () => { + delete process.env.ADNBN_SESSION_CASE; + await session?.dispose(); + await rm(directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); +}); + +test("reuses one child with fixed cwd across A-B-A, rejects overlap and restores the parent", async () => { + const cwd = process.cwd(); + const environment = {...process.env}; + const rootB = path.join(directory, "project b"); + await cp(root, rootB, {recursive: true, filter: source => path.basename(source) !== "node_modules"}); + await mkdir(path.join(rootB, "node_modules")); + await symlink( + projectRoot, + path.join(rootB, "node_modules/adnbn"), + process.platform === "win32" ? "junction" : "dir" + ); + + await cp(path.join(fixtures, "root-dir-b"), path.join(rootB, "src"), {recursive: true}); + const first = session.build(root); + await expect(session.build(rootB)).rejects.toThrow("pending request"); + const samples = [await first, await session.build(rootB, "firefox"), await session.build(root)]; + + expect(samples.map(sample => sample.pid)).toEqual([session.pid, session.pid, session.pid]); + expect(samples.map(sample => sample.build)).toEqual([1, 2, 3]); + expect(samples.map(sample => sample.cwd)).toEqual(Array(3).fill(session.directory)); + expect(samples.every(sample => sample.rss > 0 && sample.heapUsed > 0)).toBe(true); + expect(await readFile(path.join(root, "dist/myapp-chrome-mv3/newtab.html"), "utf8")).toContain("Absolute root A"); + expect(await readFile(path.join(rootB, "dist/myapp-firefox-mv3/newtab.html"), "utf8")).toContain("Absolute root B"); + expect(process.cwd()).toBe(cwd); + expect({...process.env}).toEqual(environment); + await session.dispose(); + await expect(access(session.directory)).rejects.toMatchObject({code: "ENOENT"}); + await expect(session.build(root)).rejects.toThrow("closed"); +}); + +test("preserves the original error cause and can build after a config failure", async () => { + process.env.ADNBN_SESSION_CASE = "error"; + await expect(session.build(root)).rejects.toMatchObject({ + message: expect.stringContaining("Session config failed"), + cause: {message: "Original config cause"}, + }); + + delete process.env.ADNBN_SESSION_CASE; + await expect(session.build(root)).resolves.toMatchObject({pid: session.pid, build: 2}); +}); + +test("serializes compiler diagnostics and recovers after a failed compilation", async () => { + await cp(path.join(fixtures, "build-session/invalid-newtab.ts"), path.join(root, "src/newtab.ts")); + const error = await session.build(root).catch(error => error); + expect(error).toBeInstanceOf(BuildSessionError); + expect(error.stats).toContain("missing-runtime"); + await cp(path.join(fixtures, "root-dir/src/newtab.ts"), path.join(root, "src/newtab.ts")); + await expect(session.build(root)).resolves.toMatchObject({build: 2}); +}); + +test("kills a timed-out child, waits for close and never retries", async () => { + process.env.ADNBN_SESSION_CASE = "hang"; + await expect(session.build(root, "chrome", 3, 3_000)).rejects.toThrow("timed out"); + expect(() => process.kill(session.pid!, 0)).toThrow(); + await expect(session.build(root)).rejects.toThrow("closed"); +}); + +test("rejects an unexpected child exit without leaving a pending request", async () => { + process.env.ADNBN_SESSION_CASE = "exit"; + await expect(session.build(root)).rejects.toThrow("exited with 9"); + await expect(session.build(root)).rejects.toThrow("closed"); +}); + +test("disposing a pending build rejects it and removes the session after close", async () => { + process.env.ADNBN_SESSION_CASE = "hang"; + const pending = session.build(root); + const rejected = expect(pending).rejects.toThrow("disposed during a pending request"); + await session.dispose(); + await rejected; + expect(() => process.kill(session.pid!, 0)).toThrow(); + await expect(access(session.directory)).rejects.toMatchObject({code: "ENOENT"}); +}); + +test.each([false, true])("child exits on IPC disconnect (active build: %s)", async active => { + const child = fork(path.join(projectRoot, "tests/integration/utils/build-session-child.mjs"), [projectRoot], { + cwd: session.directory, + execArgv: [], + stdio: ["ignore", "ignore", "pipe", "ipc"], + }); + + let stderr = ""; + child.stderr!.on("data", chunk => { + stderr += chunk; + }); + + const exited = new Promise(resolve => child.once("exit", (code, signal) => resolve({code, signal}))); + const ready = new Promise((resolve, reject) => { + const timer = setTimeout(() => reject(new Error(`Child did not become ready: ${stderr}`)), 15_000); + child.on("message", message => { + if ((message as {ready?: boolean; started?: boolean})[active ? "started" : "ready"]) { + clearTimeout(timer); + resolve(); + } + }); + + child.once("error", error => { + clearTimeout(timer); + reject(error); + }); + }); + + const deadline = setTimeout(() => child.kill("SIGKILL"), 20_000); + + try { + if (active) { + child.send({ + id: 1, + rootDir: root, + browser: "chrome", + manifestVersion: 3, + environment: {...process.env, ADNBN_SESSION_CASE: "hang"}, + }); + } + + await ready; + child.disconnect(); + await expect(exited).resolves.toEqual({code: 0, signal: null}); + } finally { + clearTimeout(deadline); + child.kill("SIGKILL"); + await exited; + } +}); diff --git a/tests/integration/build/cli/fixtures/build-session/adnbn.config.ts b/tests/integration/build/cli/fixtures/build-session/adnbn.config.ts new file mode 100644 index 00000000..50fc5a11 --- /dev/null +++ b/tests/integration/build/cli/fixtures/build-session/adnbn.config.ts @@ -0,0 +1,25 @@ +import {defineConfig} from "adnbn"; + +export default defineConfig({ + name: "Build session lifecycle", + version: "1.0.0", + plugins: [ + { + name: "session-lifecycle", + async startup() { + if (process.env.ADNBN_SESSION_CASE === "error") { + throw new Error("Session config failed", {cause: new Error("Original config cause")}); + } + + if (process.env.ADNBN_SESSION_CASE === "hang") { + process.send?.({started: true}); + await new Promise(() => {}); + } + + if (process.env.ADNBN_SESSION_CASE === "exit") { + process.exit(9); + } + }, + }, + ], +}); diff --git a/tests/integration/build/cli/fixtures/build-session/invalid-newtab.ts b/tests/integration/build/cli/fixtures/build-session/invalid-newtab.ts new file mode 100644 index 00000000..93bcfb8c --- /dev/null +++ b/tests/integration/build/cli/fixtures/build-session/invalid-newtab.ts @@ -0,0 +1,4 @@ +import "./missing-runtime"; +import {defineNewtab} from "adnbn"; + +export default defineNewtab({render: "Invalid runtime import"}); diff --git a/tests/integration/build/override/override-utils.ts b/tests/integration/build/override/override-utils.ts index 28a3ef02..a604bc7b 100644 --- a/tests/integration/build/override/override-utils.ts +++ b/tests/integration/build/override/override-utils.ts @@ -1,6 +1,8 @@ import {readdir, readFile} from "fs/promises"; import path from "path"; +import BuildSession from "../../utils/BuildSession"; + import {createIntegrationFixture} from "../../utils/fixture"; interface OverridePageScenario { @@ -12,7 +14,7 @@ interface OverridePageScenario { const buildMode = process.env.ADNBN_OVERRIDE_BUILD_MODE ?? "cli"; -if (buildMode !== "cli" && buildMode !== "in-process") { +if (buildMode !== "cli" && buildMode !== "session") { throw new Error(`Unknown override build mode: ${buildMode}`); } @@ -26,10 +28,21 @@ export const testOverridePage = ({page, permission, supported, unsupported}: Ove describe(`${page} override`, () => { const fixtureDir = path.join(__dirname, page); const source = `https://${page}.example.com`; + let session: BuildSession | undefined; + + beforeAll(async () => { + if (buildMode === "session") { + session = await BuildSession.create(rootDir); + } + }); + + afterAll(async () => { + await session?.dispose(); + }); describe.each(supported)("%s", browser => { test.each([2, 3] as const)("MV%s build replaces the browser page", async manifestVersion => { - const fixture = await createIntegrationFixture(rootDir, fixtureDir, buildMode); + const fixture = await createIntegrationFixture(rootDir, fixtureDir, session); try { const extensionDir = await fixture.build({browser, manifestVersion}); @@ -50,7 +63,7 @@ export const testOverridePage = ({page, permission, supported, unsupported}: Ove describe.each(unsupported)("%s", browser => { test.each([2, 3] as const)("MV%s build skips the unsupported page", async manifestVersion => { - const fixture = await createIntegrationFixture(rootDir, fixtureDir, buildMode); + const fixture = await createIntegrationFixture(rootDir, fixtureDir, session); try { const extensionDir = await fixture.build({browser, manifestVersion}); diff --git a/tests/integration/utils/BuildSession.ts b/tests/integration/utils/BuildSession.ts new file mode 100644 index 00000000..846f8870 --- /dev/null +++ b/tests/integration/utils/BuildSession.ts @@ -0,0 +1,197 @@ +import {fork, type ChildProcess} from "node:child_process"; +import {appendFileSync} from "node:fs"; +import {mkdir, mkdtemp, rm} from "node:fs/promises"; +import path from "node:path"; + +import type { + BuildSessionFailure, + BuildSessionRequest, + BuildSessionResponse, + BuildSessionSample, +} from "./build-session-protocol"; + +interface BuildSessionPending { + id: number; + resolve(sample: BuildSessionSample): void; + reject(error: Error): void; + timer: NodeJS.Timeout; +} + +export class BuildSessionError extends Error { + readonly stats?: string; + + constructor(failure: BuildSessionFailure) { + super(failure.message, failure.cause ? {cause: new BuildSessionError(failure.cause)} : undefined); + this.name = failure.name; + this.stats = failure.stats; + } +} + +/** One file owns one process; builds are sequential and cwd never changes. */ +export default class BuildSession { + private readonly child: ChildProcess; + private readonly closed: Promise; + private pending?: BuildSessionPending; + private sequence = 0; + private stopped = false; + private failure?: Error; + private output = ""; + private disposal?: Promise; + + private constructor( + readonly directory: string, + projectRoot: string + ) { + this.child = fork(path.join(projectRoot, "tests/integration/utils/build-session-child.mjs"), [projectRoot], { + cwd: directory, + // Keep Node flags, including the macOS JIT workaround and optional benchmark GC. + execArgv: process.execArgv.filter(flag => flag === "--no-sparkplug" || flag === "--expose-gc"), + stdio: ["ignore", "pipe", "pipe", "ipc"], + }); + + const capture = (chunk: Buffer) => { + this.output = (this.output + chunk.toString()).slice(-64 * 1024); + }; + + this.child.stdout!.on("data", capture); + this.child.stderr!.on("data", capture); + this.child.on("message", message => this.receive(message as BuildSessionResponse)); + this.child.once("error", error => this.terminate(error)); + this.closed = new Promise(resolve => { + this.child.once("close", (code, signal) => { + this.stopped = true; + const pending = this.pending; + this.pending = undefined; + + if (pending) { + clearTimeout(pending.timer); + pending.reject( + this.failure ?? new Error(`Build session exited with ${signal ?? code}\n${this.output}`) + ); + } + + resolve(); + }); + }); + } + + static async create(projectRoot: string): Promise { + const parent = path.join(projectRoot, ".cache/integration"); + await mkdir(parent, {recursive: true}); + const directory = await mkdtemp(path.join(parent, "session-")); + + try { + // The external plugin's parent-directory creation is not atomic. This directory is session-owned. + await mkdir(path.join(directory, "node_modules"), {recursive: true}); + + return new BuildSession(directory, projectRoot); + } catch (error) { + await rm(directory, {recursive: true, force: true}); + throw error; + } + } + + get pid(): number | undefined { + return this.child.pid; + } + + async build( + rootDir: string, + browser = "chrome", + manifestVersion: 2 | 3 = 3, + timeout = 30_000 + ): Promise { + if (this.stopped) { + throw new Error("Build session is closed"); + } + + if (this.pending) { + throw new Error("Build session already has a pending request; await it before starting another"); + } + + this.output = ""; + const id = ++this.sequence; + const request: BuildSessionRequest = { + id, + rootDir: path.resolve(rootDir), + browser, + manifestVersion, + environment: {...process.env}, + }; + + return new Promise((resolve, reject) => { + const timer = setTimeout( + () => this.terminate(new Error(`Build session timed out after ${timeout} ms\n${this.output}`)), + timeout + ); + + this.pending = {id, resolve, reject, timer}; + this.child.send(request, error => { + if (error) { + this.terminate(error); + } + }); + }); + } + + private receive(response: BuildSessionResponse): void { + const pending = this.pending; + + if (this.stopped || !pending || response.id !== pending.id) { + return; + } + + clearTimeout(pending.timer); + this.pending = undefined; + + try { + // Diagnostics are opt-in and report the child, never Jest worker memory. + if (process.env.ADNBN_SESSION_METRICS) { + appendFileSync(process.env.ADNBN_SESSION_METRICS, JSON.stringify(response.sample) + "\n"); + } + + if (response.error) { + pending.reject(new BuildSessionError(response.error)); + } else { + pending.resolve(response.sample); + } + } catch (error) { + pending.reject(error as Error); + } + } + + private terminate(error: Error): void { + this.failure ??= error; + this.stopped = true; + this.child.kill("SIGKILL"); + } + + dispose(): Promise { + this.disposal ??= this.close(); + + return this.disposal; + } + + private async close(): Promise { + this.stopped = true; + + if (this.pending) { + this.terminate(new Error("Build session disposed during a pending request")); + } else if (this.child.connected) { + this.child.send({close: true}, error => { + if (error) { + this.terminate(error); + } + }); + } + + const timer = setTimeout(() => this.child.kill("SIGKILL"), 5_000); + + try { + await this.closed; + } finally { + clearTimeout(timer); + await rm(this.directory, {recursive: true, force: true, maxRetries: 5, retryDelay: 200}); + } + } +} diff --git a/tests/integration/utils/build-session-child.mjs b/tests/integration/utils/build-session-child.mjs new file mode 100644 index 00000000..dbeab35c --- /dev/null +++ b/tests/integration/utils/build-session-child.mjs @@ -0,0 +1,78 @@ +import path from "node:path"; +import {pathToFileURL} from "node:url"; + +// Install before loading the framework: the parent may disappear during startup or a build. +process.on("disconnect", () => process.exit(0)); + +const [projectRoot] = process.argv.slice(2); +const app = import(pathToFileURL(path.join(projectRoot, "dist/cli/builders/app/index.js")).href); +// The request handler reports load failures through the same error protocol as build failures. +app.catch(() => {}); +let active = false; +let builds = 0; + +const serializeError = (error, depth = 0) => ({ + name: error?.name ?? "Error", + message: error?.message ?? String(error), + ...(error?.cause !== undefined && depth < 5 ? {cause: serializeError(error.cause, depth + 1)} : {}), + ...(error?.stats + ? {stats: error.stats.toString({all: false, errors: true, errorDetails: true, colors: false})} + : {}), +}); + +process.on("message", async request => { + if (request.close) { + process.exit(0); + } + + if (active) { + // A parent protocol violation cannot leave overlapping builds running. + process.exit(1); + } + + active = true; + const environment = process.env; + let failure; + + try { + process.env = {...request.environment}; + const {buildApp} = await app; + await buildApp({ + rootDir: request.rootDir, + browser: request.browser, + manifestVersion: request.manifestVersion, + mode: "production", + }); + } catch (error) { + failure = serializeError(error); + } finally { + process.env = environment; + active = false; + } + + builds++; + + if (global.gc) { + global.gc(); + } + + const {rss, heapUsed} = process.memoryUsage(); + + if (process.connected) { + process.send({ + id: request.id, + ...(failure ? {error: failure} : {}), + sample: { + pid: process.pid, + build: builds, + rootDir: request.rootDir, + cwd: process.cwd(), + rss, + heapUsed, + maxRssKiB: process.resourceUsage().maxRSS, + }, + }); + } +}); + +process.send?.({ready: true}); diff --git a/tests/integration/utils/build-session-protocol.ts b/tests/integration/utils/build-session-protocol.ts new file mode 100644 index 00000000..8d606e69 --- /dev/null +++ b/tests/integration/utils/build-session-protocol.ts @@ -0,0 +1,30 @@ +export interface BuildSessionRequest { + id: number; + rootDir: string; + browser: string; + manifestVersion: 2 | 3; + environment: NodeJS.ProcessEnv; +} + +export interface BuildSessionFailure { + name: string; + message: string; + cause?: BuildSessionFailure; + stats?: string; +} + +export interface BuildSessionSample { + pid: number; + build: number; + rootDir: string; + cwd: string; + rss: number; + heapUsed: number; + maxRssKiB: number; +} + +export interface BuildSessionResponse { + id: number; + error?: BuildSessionFailure; + sample: BuildSessionSample; +} diff --git a/tests/integration/utils/fixture.ts b/tests/integration/utils/fixture.ts index ead7fae8..276f9612 100644 --- a/tests/integration/utils/fixture.ts +++ b/tests/integration/utils/fixture.ts @@ -1,19 +1,11 @@ import {cp, lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, symlink} from "fs/promises"; import path from "path"; -import type {buildApp} from "@cli/builders/app"; -import {Browser} from "@typing/browser"; -import {Mode} from "@typing/app"; +import type BuildSession from "./BuildSession"; import {run} from "./process"; -interface AppBuildModule { - buildApp: typeof buildApp; -} - -export type IntegrationFixtureBuildMode = "cli" | "in-process"; - export interface IntegrationFixtureBuildOptions { - buildMode?: IntegrationFixtureBuildMode; + session?: BuildSession; browser?: string; manifestVersion?: 2 | 3; } @@ -51,7 +43,7 @@ const linkDependency = async (source: string, destination: string): Promise => { const manifest = JSON.parse(await readFile(path.join(directory, "package.json"), "utf8")) as { @@ -65,25 +57,8 @@ export const prepareIntegrationFixture = async ( ); } - if (buildMode === "in-process") { - // Jest replaces createRequire and process.env. Use Node's loader in this same worker, - // lending it the test environment only for the awaited build. - const nodeProcess = process.getBuiltinModule("process"); - const require = process.getBuiltinModule("module").createRequire(path.join(projectRoot, "package.json")); - const environment = nodeProcess.env; - nodeProcess.env = process.env; - - try { - const app = require(path.join(projectRoot, "dist/cli/builders/app/index.js")) as AppBuildModule; - await app.buildApp({ - rootDir: path.resolve(directory), - browser: browser as Browser, - manifestVersion, - mode: Mode.Production, - }); - } finally { - nodeProcess.env = environment; - } + if (session) { + await session.build(directory, browser, manifestVersion, timeout); } else { await run( process.execPath, @@ -106,7 +81,7 @@ export const prepareIntegrationFixture = async ( export const createIntegrationFixture = async ( projectRoot: string, sourceDirectory: string, - buildMode: IntegrationFixtureBuildMode = "cli" + session?: BuildSession ): Promise => { const cacheDirectory = path.join(projectRoot, ".cache", "integration"); @@ -127,7 +102,7 @@ export const createIntegrationFixture = async ( return { directory, - build: options => prepareIntegrationFixture(projectRoot, directory, {buildMode, ...options}), + build: options => prepareIntegrationFixture(projectRoot, directory, {session, ...options}), dispose, }; }; From 324404064e33efd123d8b0defc636c5b716ec6d7 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 16:57:29 +0300 Subject: [PATCH 32/36] docs: record isolated build-session benchmarks Compare identical override scenarios with two and eight workers. Record child memory, exited PIDs, directory cleanup and open-handle checks. Mark the previous in-process report historical and update the reproduction runner. --- tests/benchmarks/2026-09-28-override-pilot.md | 4 + .../2026-09-28-override-session.json | 11037 ++++++++++++++++ .../benchmarks/2026-09-28-override-session.md | 83 + tests/benchmarks/override-pilot.mjs | 255 +- 4 files changed, 11211 insertions(+), 168 deletions(-) create mode 100644 tests/benchmarks/2026-09-28-override-session.json create mode 100644 tests/benchmarks/2026-09-28-override-session.md diff --git a/tests/benchmarks/2026-09-28-override-pilot.md b/tests/benchmarks/2026-09-28-override-pilot.md index 412d7b59..18084405 100644 --- a/tests/benchmarks/2026-09-28-override-pilot.md +++ b/tests/benchmarks/2026-09-28-override-pilot.md @@ -1,5 +1,9 @@ # Override in-process pilot — 2026-09-28 +This is a historical report for the superseded in-process backend. The current helper and benchmark runner +use [file-owned child sessions](2026-09-28-override-session.md). The project-hashed virtual-directory workaround +described here has been removed; the independent absolute-root fixes remain. + ## Decision Keep CLI builds as the default. The opt-in in-process path is faster at both worker limits, but repeated diff --git a/tests/benchmarks/2026-09-28-override-session.json b/tests/benchmarks/2026-09-28-override-session.json new file mode 100644 index 00000000..18fba4a3 --- /dev/null +++ b/tests/benchmarks/2026-09-28-override-session.json @@ -0,0 +1,11037 @@ +{ + "node": "v24.5.0", + "platform": "darwin", + "arch": "arm64", + "cpus": 16, + "flags": [ + "--expose-gc", + "--no-sparkplug" + ], + "runs": [ + { + "id": "1-warmup-2-cli", + "phase": "warmup", + "mode": "cli", + "workers": 2, + "wallSeconds": 13.201181042, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 123076608, + "heapTotal": 60702720, + "heapUsed": 24951432, + "external": 5722819, + "arrayBuffers": 2062059, + "maxRssKiB": 120448 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 122798080, + "heapTotal": 60964864, + "heapUsed": 24948512, + "external": 5722808, + "arrayBuffers": 2062059, + "maxRssKiB": 120192 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 123994112, + "heapTotal": 61489152, + "heapUsed": 25995120, + "external": 5704053, + "arrayBuffers": 2070251, + "maxRssKiB": 121088 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 123387904, + "heapTotal": 61489152, + "heapUsed": 25994256, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 120496 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 123928576, + "heapTotal": 62537728, + "heapUsed": 27056000, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 121024 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 124534784, + "heapTotal": 62537728, + "heapUsed": 27058920, + "external": 5712214, + "arrayBuffers": 2070251, + "maxRssKiB": 121616 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 124665856, + "heapTotal": 63586304, + "heapUsed": 28133320, + "external": 5704041, + "arrayBuffers": 2070251, + "maxRssKiB": 121744 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 125108224, + "heapTotal": 63848448, + "heapUsed": 28134208, + "external": 5704051, + "arrayBuffers": 2070251, + "maxRssKiB": 122192 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 125829120, + "heapTotal": 64897024, + "heapUsed": 29201728, + "external": 5711173, + "arrayBuffers": 2070251, + "maxRssKiB": 122880 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 125419520, + "heapTotal": 65159168, + "heapUsed": 29194040, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 122480 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 126517248, + "heapTotal": 65945600, + "heapUsed": 30231008, + "external": 5702981, + "arrayBuffers": 2080957, + "maxRssKiB": 123568 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 126140416, + "heapTotal": 66207744, + "heapUsed": 30222736, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 123184 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 127221760, + "heapTotal": 66994176, + "heapUsed": 31255184, + "external": 5711017, + "arrayBuffers": 2070251, + "maxRssKiB": 124240 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 126910464, + "heapTotal": 67518464, + "heapUsed": 31246912, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 123936 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 127926272, + "heapTotal": 68042752, + "heapUsed": 32277200, + "external": 5702825, + "arrayBuffers": 2070251, + "maxRssKiB": 124928 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 127680512, + "heapTotal": 68567040, + "heapUsed": 32269640, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 124688 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 128614400, + "heapTotal": 68829184, + "heapUsed": 33344368, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 125600 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 128417792, + "heapTotal": 69877760, + "heapUsed": 33348888, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 125408 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 129286144, + "heapTotal": 70139904, + "heapUsed": 34379760, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 126256 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 129105920, + "heapTotal": 70926336, + "heapUsed": 34379040, + "external": 5711014, + "arrayBuffers": 2070251, + "maxRssKiB": 126080 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 146604032, + "heapTotal": 105054208, + "heapUsed": 29614360, + "external": 7765668, + "arrayBuffers": 2078443, + "maxRssKiB": 143296 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 146423808, + "heapTotal": 105840640, + "heapUsed": 29750544, + "external": 7765982, + "arrayBuffers": 2078443, + "maxRssKiB": 143120 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 147275776, + "heapTotal": 101122048, + "heapUsed": 30732960, + "external": 5720746, + "arrayBuffers": 2086635, + "maxRssKiB": 143824 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 148029440, + "heapTotal": 101384192, + "heapUsed": 30602696, + "external": 5720463, + "arrayBuffers": 2086635, + "maxRssKiB": 144560 + }, + { + "pid": 96503, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 147570688, + "heapTotal": 102170624, + "heapUsed": 31766656, + "external": 5721286, + "arrayBuffers": 2086635, + "maxRssKiB": 144112 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 148242432, + "heapTotal": 102170624, + "heapUsed": 31640904, + "external": 5728624, + "arrayBuffers": 2086635, + "maxRssKiB": 144768 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 148258816, + "heapTotal": 103219200, + "heapUsed": 32679896, + "external": 5720461, + "arrayBuffers": 2086635, + "maxRssKiB": 144784 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 148275200, + "heapTotal": 104529920, + "heapUsed": 33729872, + "external": 5727565, + "arrayBuffers": 2086635, + "maxRssKiB": 144800 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 148307968, + "heapTotal": 105578496, + "heapUsed": 34754768, + "external": 5719373, + "arrayBuffers": 2086635, + "maxRssKiB": 144832 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 148307968, + "heapTotal": 106364928, + "heapUsed": 35775912, + "external": 5727409, + "arrayBuffers": 2086635, + "maxRssKiB": 144832 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 148455424, + "heapTotal": 107675648, + "heapUsed": 36797736, + "external": 5719217, + "arrayBuffers": 2086635, + "maxRssKiB": 144976 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 149143552, + "heapTotal": 108986368, + "heapUsed": 37852776, + "external": 5727408, + "arrayBuffers": 2086635, + "maxRssKiB": 145648 + }, + { + "pid": 96504, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 149684224, + "heapTotal": 110297088, + "heapUsed": 38895960, + "external": 5727408, + "arrayBuffers": 2086635, + "maxRssKiB": 146176 + } + ] + }, + { + "id": "2-warmup-2-session", + "phase": "warmup", + "mode": "session", + "workers": 2, + "wallSeconds": 3.541855040999999, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 96821, + "build": 1, + "rootDir": ".cache/integration/fixture-FmnjsN", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 338231296, + "heapUsed": 71203192, + "maxRssKiB": 330432 + }, + { + "pid": 96822, + "build": 1, + "rootDir": ".cache/integration/fixture-PaQlKf", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 343883776, + "heapUsed": 71208264, + "maxRssKiB": 335952 + }, + { + "pid": 96821, + "build": 2, + "rootDir": ".cache/integration/fixture-R34AsH", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 350961664, + "heapUsed": 72813176, + "maxRssKiB": 342864 + }, + { + "pid": 96822, + "build": 2, + "rootDir": ".cache/integration/fixture-HSmPZL", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 356040704, + "heapUsed": 72816680, + "maxRssKiB": 347824 + }, + { + "pid": 96821, + "build": 3, + "rootDir": ".cache/integration/fixture-1CXC53", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 359677952, + "heapUsed": 74448720, + "maxRssKiB": 351376 + }, + { + "pid": 96822, + "build": 3, + "rootDir": ".cache/integration/fixture-pzz9Tx", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 367460352, + "heapUsed": 74456936, + "maxRssKiB": 358976 + }, + { + "pid": 96821, + "build": 4, + "rootDir": ".cache/integration/fixture-f20gKb", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 369623040, + "heapUsed": 75995392, + "maxRssKiB": 361088 + }, + { + "pid": 96822, + "build": 4, + "rootDir": ".cache/integration/fixture-caxDRQ", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 376193024, + "heapUsed": 76006456, + "maxRssKiB": 367504 + }, + { + "pid": 96821, + "build": 5, + "rootDir": ".cache/integration/fixture-MhISh4", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 370327552, + "heapUsed": 76563168, + "maxRssKiB": 361776 + }, + { + "pid": 96822, + "build": 5, + "rootDir": ".cache/integration/fixture-ZAjtWK", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 384253952, + "heapUsed": 77604032, + "maxRssKiB": 375376 + }, + { + "pid": 96821, + "build": 6, + "rootDir": ".cache/integration/fixture-30NlPT", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 370868224, + "heapUsed": 77100416, + "maxRssKiB": 362304 + }, + { + "pid": 96822, + "build": 6, + "rootDir": ".cache/integration/fixture-Zssut1", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 392724480, + "heapUsed": 79179744, + "maxRssKiB": 383648 + }, + { + "pid": 96821, + "build": 7, + "rootDir": ".cache/integration/fixture-sOG3Qc", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 371523584, + "heapUsed": 77549520, + "maxRssKiB": 362944 + }, + { + "pid": 96821, + "build": 8, + "rootDir": ".cache/integration/fixture-1c6Nvo", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 372097024, + "heapUsed": 78090696, + "maxRssKiB": 363504 + }, + { + "pid": 96822, + "build": 7, + "rootDir": ".cache/integration/fixture-eTE8Zb", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 401260544, + "heapUsed": 80669736, + "maxRssKiB": 391984 + }, + { + "pid": 96821, + "build": 9, + "rootDir": ".cache/integration/fixture-GNRICB", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 372752384, + "heapUsed": 78794440, + "maxRssKiB": 364144 + }, + { + "pid": 96822, + "build": 8, + "rootDir": ".cache/integration/fixture-kIoxlz", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 409583616, + "heapUsed": 82248016, + "maxRssKiB": 400112 + }, + { + "pid": 96821, + "build": 10, + "rootDir": ".cache/integration/fixture-wav3Bk", + "cwd": ".cache/integration/session-ryC8xE", + "rss": 373407744, + "heapUsed": 79395936, + "maxRssKiB": 364784 + }, + { + "pid": 96822, + "build": 9, + "rootDir": ".cache/integration/fixture-AFo5aY", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 410976256, + "heapUsed": 82956832, + "maxRssKiB": 401472 + }, + { + "pid": 96822, + "build": 10, + "rootDir": ".cache/integration/fixture-F8crQg", + "cwd": ".cache/integration/session-UqKC6r", + "rss": 412385280, + "heapUsed": 83548032, + "maxRssKiB": 402848 + }, + { + "pid": 96823, + "build": 1, + "rootDir": ".cache/integration/fixture-DbcV6R", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 313458688, + "heapUsed": 71143776, + "maxRssKiB": 306240 + }, + { + "pid": 96823, + "build": 2, + "rootDir": ".cache/integration/fixture-cbGZyO", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 326172672, + "heapUsed": 72841032, + "maxRssKiB": 318656 + }, + { + "pid": 96823, + "build": 3, + "rootDir": ".cache/integration/fixture-UKNDEU", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 336838656, + "heapUsed": 74467680, + "maxRssKiB": 329072 + }, + { + "pid": 96823, + "build": 4, + "rootDir": ".cache/integration/fixture-lEt0Ex", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 348536832, + "heapUsed": 76021032, + "maxRssKiB": 340496 + }, + { + "pid": 96823, + "build": 5, + "rootDir": ".cache/integration/fixture-uz6de7", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 349913088, + "heapUsed": 76588136, + "maxRssKiB": 341840 + }, + { + "pid": 96823, + "build": 6, + "rootDir": ".cache/integration/fixture-Q0dVrD", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 351010816, + "heapUsed": 77127288, + "maxRssKiB": 342912 + }, + { + "pid": 96823, + "build": 7, + "rootDir": ".cache/integration/fixture-sMAeKc", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 352256000, + "heapUsed": 77587000, + "maxRssKiB": 344128 + }, + { + "pid": 96823, + "build": 8, + "rootDir": ".cache/integration/fixture-9P4V4U", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 358973440, + "heapUsed": 78117096, + "maxRssKiB": 350688 + }, + { + "pid": 96823, + "build": 9, + "rootDir": ".cache/integration/fixture-3pFdIY", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 360431616, + "heapUsed": 78818784, + "maxRssKiB": 352112 + }, + { + "pid": 96823, + "build": 10, + "rootDir": ".cache/integration/fixture-xQiSnU", + "cwd": ".cache/integration/session-KB3RvB", + "rss": 361840640, + "heapUsed": 79422616, + "maxRssKiB": 353488 + } + ], + "parentSamples": [ + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115752960, + "heapTotal": 60702720, + "heapUsed": 24852728, + "external": 5723121, + "arrayBuffers": 2062066, + "maxRssKiB": 113248 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115441664, + "heapTotal": 60964864, + "heapUsed": 24852920, + "external": 5723129, + "arrayBuffers": 2062066, + "maxRssKiB": 112944 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116162560, + "heapTotal": 61751296, + "heapUsed": 25882608, + "external": 5703744, + "arrayBuffers": 2070258, + "maxRssKiB": 113440 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116457472, + "heapTotal": 61751296, + "heapUsed": 25883184, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 113728 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117030912, + "heapTotal": 63062016, + "heapUsed": 26940920, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 114288 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116736000, + "heapTotal": 62799872, + "heapUsed": 26940608, + "external": 5703713, + "arrayBuffers": 2070258, + "maxRssKiB": 114000 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117374976, + "heapTotal": 63848448, + "heapUsed": 28000584, + "external": 5711934, + "arrayBuffers": 2070258, + "maxRssKiB": 114624 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117522432, + "heapTotal": 63848448, + "heapUsed": 28001088, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 114768 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118145024, + "heapTotal": 65159168, + "heapUsed": 29059176, + "external": 5703091, + "arrayBuffers": 2070258, + "maxRssKiB": 115376 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118226944, + "heapTotal": 65159168, + "heapUsed": 29046800, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 115456 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118734848, + "heapTotal": 66207744, + "heapUsed": 30088032, + "external": 5711283, + "arrayBuffers": 2070258, + "maxRssKiB": 115968 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 118865920, + "heapTotal": 66207744, + "heapUsed": 30078560, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 116096 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119504896, + "heapTotal": 67256320, + "heapUsed": 31109968, + "external": 5702935, + "arrayBuffers": 2070258, + "maxRssKiB": 116704 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120209408, + "heapTotal": 68567040, + "heapUsed": 32133272, + "external": 5711127, + "arrayBuffers": 2070258, + "maxRssKiB": 117392 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119619584, + "heapTotal": 67256320, + "heapUsed": 31102536, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 116816 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120864768, + "heapTotal": 69615616, + "heapUsed": 33188856, + "external": 5702934, + "arrayBuffers": 2070258, + "maxRssKiB": 118032 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120356864, + "heapTotal": 68567040, + "heapUsed": 32125952, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 117536 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121634816, + "heapTotal": 70664192, + "heapUsed": 34221520, + "external": 5711128, + "arrayBuffers": 2070258, + "maxRssKiB": 118784 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121110528, + "heapTotal": 69877760, + "heapUsed": 33192768, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 118272 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121683968, + "heapTotal": 70926336, + "heapUsed": 34224808, + "external": 5711126, + "arrayBuffers": 2070258, + "maxRssKiB": 118848 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 138313728, + "heapTotal": 103743488, + "heapUsed": 29537216, + "external": 7765979, + "arrayBuffers": 2078457, + "maxRssKiB": 135200 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 138641408, + "heapTotal": 104529920, + "heapUsed": 29656840, + "external": 7774185, + "arrayBuffers": 2086642, + "maxRssKiB": 135520 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 138756096, + "heapTotal": 100859904, + "heapUsed": 30527400, + "external": 5720155, + "arrayBuffers": 2086649, + "maxRssKiB": 135504 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 139051008, + "heapTotal": 101646336, + "heapUsed": 31561664, + "external": 5720124, + "arrayBuffers": 2086649, + "maxRssKiB": 135792 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 139067392, + "heapTotal": 103219200, + "heapUsed": 32600224, + "external": 5728345, + "arrayBuffers": 2086649, + "maxRssKiB": 135808 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 139067392, + "heapTotal": 104005632, + "heapUsed": 33639712, + "external": 5719490, + "arrayBuffers": 2086649, + "maxRssKiB": 135808 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 139427840, + "heapTotal": 105316352, + "heapUsed": 34677920, + "external": 5727682, + "arrayBuffers": 2086649, + "maxRssKiB": 136160 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 139952128, + "heapTotal": 106364928, + "heapUsed": 35703336, + "external": 5719334, + "arrayBuffers": 2086649, + "maxRssKiB": 136688 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 140705792, + "heapTotal": 107675648, + "heapUsed": 36723400, + "external": 5727526, + "arrayBuffers": 2086649, + "maxRssKiB": 137408 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141361152, + "heapTotal": 108724224, + "heapUsed": 37762328, + "external": 5719333, + "arrayBuffers": 2086649, + "maxRssKiB": 138048 + }, + { + "pid": 96806, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 141983744, + "heapTotal": 109772800, + "heapUsed": 38790752, + "external": 5727527, + "arrayBuffers": 2086649, + "maxRssKiB": 138656 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 139296768, + "heapTotal": 101384192, + "heapUsed": 30643904, + "external": 5728945, + "arrayBuffers": 2094834, + "maxRssKiB": 136032 + }, + { + "pid": 96805, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 139378688, + "heapTotal": 101908480, + "heapUsed": 31689616, + "external": 5729485, + "arrayBuffers": 2094834, + "maxRssKiB": 136112 + } + ] + }, + { + "id": "3-measured-2-cli", + "phase": "measured", + "mode": "cli", + "workers": 2, + "wallSeconds": 13.095825540999998, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115998720, + "heapTotal": 60964864, + "heapUsed": 24957576, + "external": 5723163, + "arrayBuffers": 2062059, + "maxRssKiB": 113408 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116719616, + "heapTotal": 60440576, + "heapUsed": 24862504, + "external": 5722810, + "arrayBuffers": 2062059, + "maxRssKiB": 114224 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116801536, + "heapTotal": 61489152, + "heapUsed": 25989744, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 114064 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117522432, + "heapTotal": 61489152, + "heapUsed": 25892256, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 114768 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117243904, + "heapTotal": 62537728, + "heapUsed": 27060600, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114496 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118112256, + "heapTotal": 62537728, + "heapUsed": 26956384, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 115344 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 125140992, + "heapTotal": 66256896, + "heapUsed": 29326664, + "external": 7773834, + "arrayBuffers": 2078443, + "maxRssKiB": 122336 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118898688, + "heapTotal": 63586304, + "heapUsed": 28033240, + "external": 5704041, + "arrayBuffers": 2070251, + "maxRssKiB": 116112 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 125403136, + "heapTotal": 66781184, + "heapUsed": 30363960, + "external": 5720437, + "arrayBuffers": 2086635, + "maxRssKiB": 122464 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119619584, + "heapTotal": 64897024, + "heapUsed": 29091832, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 116816 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 125829120, + "heapTotal": 67829760, + "heapUsed": 31411776, + "external": 5728598, + "arrayBuffers": 2086635, + "maxRssKiB": 122880 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 120291328, + "heapTotal": 65683456, + "heapUsed": 30120592, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 117472 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 128811008, + "heapTotal": 102432768, + "heapUsed": 32470688, + "external": 5720435, + "arrayBuffers": 2086635, + "maxRssKiB": 125792 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 121044992, + "heapTotal": 66994176, + "heapUsed": 31145048, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 118208 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 129433600, + "heapTotal": 103743488, + "heapUsed": 33518192, + "external": 5727557, + "arrayBuffers": 2086635, + "maxRssKiB": 126400 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121798656, + "heapTotal": 68042752, + "heapUsed": 32170776, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 118944 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 129974272, + "heapTotal": 104792064, + "heapUsed": 34564520, + "external": 5719365, + "arrayBuffers": 2086635, + "maxRssKiB": 126928 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122552320, + "heapTotal": 69353472, + "heapUsed": 33249120, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 119680 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 130629632, + "heapTotal": 105840640, + "heapUsed": 35590760, + "external": 5727401, + "arrayBuffers": 2086635, + "maxRssKiB": 127568 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123256832, + "heapTotal": 70402048, + "heapUsed": 34279288, + "external": 5711014, + "arrayBuffers": 2070251, + "maxRssKiB": 120368 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 131268608, + "heapTotal": 106889216, + "heapUsed": 36611848, + "external": 5719209, + "arrayBuffers": 2086635, + "maxRssKiB": 128208 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 140230656, + "heapTotal": 106102784, + "heapUsed": 29550376, + "external": 7765670, + "arrayBuffers": 2078443, + "maxRssKiB": 137072 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 131940352, + "heapTotal": 107675648, + "heapUsed": 37664920, + "external": 5727400, + "arrayBuffers": 2086635, + "maxRssKiB": 128848 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 140525568, + "heapTotal": 101122048, + "heapUsed": 30539176, + "external": 5720463, + "arrayBuffers": 2086635, + "maxRssKiB": 137232 + }, + { + "pid": 96877, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 132595712, + "heapTotal": 108986368, + "heapUsed": 38696336, + "external": 5727400, + "arrayBuffers": 2086635, + "maxRssKiB": 129488 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 140541952, + "heapTotal": 101646336, + "heapUsed": 31572832, + "external": 5728624, + "arrayBuffers": 2086635, + "maxRssKiB": 137248 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 140673024, + "heapTotal": 102694912, + "heapUsed": 32615456, + "external": 5720461, + "arrayBuffers": 2086635, + "maxRssKiB": 137376 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 140673024, + "heapTotal": 103743488, + "heapUsed": 33662240, + "external": 5727565, + "arrayBuffers": 2086635, + "maxRssKiB": 137376 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 140705792, + "heapTotal": 104792064, + "heapUsed": 34701368, + "external": 5719373, + "arrayBuffers": 2086635, + "maxRssKiB": 137408 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140705792, + "heapTotal": 106102784, + "heapUsed": 35721976, + "external": 5727409, + "arrayBuffers": 2086635, + "maxRssKiB": 137408 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 140722176, + "heapTotal": 107151360, + "heapUsed": 36747296, + "external": 5719217, + "arrayBuffers": 2086635, + "maxRssKiB": 137424 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141213696, + "heapTotal": 108199936, + "heapUsed": 37787568, + "external": 5727408, + "arrayBuffers": 2086635, + "maxRssKiB": 137904 + }, + { + "pid": 96878, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 141869056, + "heapTotal": 109510656, + "heapUsed": 38817096, + "external": 5727408, + "arrayBuffers": 2086635, + "maxRssKiB": 138544 + } + ] + }, + { + "id": "4-measured-2-session", + "phase": "measured", + "mode": "session", + "workers": 2, + "wallSeconds": 3.6496417919999984, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 97237, + "build": 1, + "rootDir": ".cache/integration/fixture-8vou0G", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 316702720, + "heapUsed": 71158840, + "maxRssKiB": 309408 + }, + { + "pid": 97236, + "build": 1, + "rootDir": ".cache/integration/fixture-in7Fhi", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 341245952, + "heapUsed": 71199424, + "maxRssKiB": 333376 + }, + { + "pid": 97237, + "build": 2, + "rootDir": ".cache/integration/fixture-Y0mCMA", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 328744960, + "heapUsed": 72851856, + "maxRssKiB": 321168 + }, + { + "pid": 97236, + "build": 2, + "rootDir": ".cache/integration/fixture-6SJuMK", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 353697792, + "heapUsed": 72805168, + "maxRssKiB": 345536 + }, + { + "pid": 97236, + "build": 3, + "rootDir": ".cache/integration/fixture-JvGs4o", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 363446272, + "heapUsed": 74443216, + "maxRssKiB": 355056 + }, + { + "pid": 97237, + "build": 3, + "rootDir": ".cache/integration/fixture-lhHxVP", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 340410368, + "heapUsed": 74485528, + "maxRssKiB": 332560 + }, + { + "pid": 97237, + "build": 4, + "rootDir": ".cache/integration/fixture-SpzkVx", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 351862784, + "heapUsed": 76036840, + "maxRssKiB": 343744 + }, + { + "pid": 97236, + "build": 4, + "rootDir": ".cache/integration/fixture-L3Sw3B", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 372555776, + "heapUsed": 75997696, + "maxRssKiB": 363952 + }, + { + "pid": 97237, + "build": 5, + "rootDir": ".cache/integration/fixture-yhBO7a", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 353435648, + "heapUsed": 76604888, + "maxRssKiB": 345280 + }, + { + "pid": 97236, + "build": 5, + "rootDir": ".cache/integration/fixture-3aOQx8", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 381583360, + "heapUsed": 77596040, + "maxRssKiB": 372768 + }, + { + "pid": 97237, + "build": 6, + "rootDir": ".cache/integration/fixture-SLKwOk", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 354451456, + "heapUsed": 77142576, + "maxRssKiB": 346272 + }, + { + "pid": 97237, + "build": 7, + "rootDir": ".cache/integration/fixture-NifYog", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 355696640, + "heapUsed": 77588440, + "maxRssKiB": 347488 + }, + { + "pid": 97236, + "build": 6, + "rootDir": ".cache/integration/fixture-TSzAi2", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 389758976, + "heapUsed": 79171928, + "maxRssKiB": 380752 + }, + { + "pid": 97237, + "build": 8, + "rootDir": ".cache/integration/fixture-Vp7uTb", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 356827136, + "heapUsed": 78132936, + "maxRssKiB": 348592 + }, + { + "pid": 97236, + "build": 7, + "rootDir": ".cache/integration/fixture-qDwzRA", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 397705216, + "heapUsed": 80663664, + "maxRssKiB": 388512 + }, + { + "pid": 97237, + "build": 9, + "rootDir": ".cache/integration/fixture-LIm5ai", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 358219776, + "heapUsed": 78833800, + "maxRssKiB": 349952 + }, + { + "pid": 97237, + "build": 10, + "rootDir": ".cache/integration/fixture-qJ1PR7", + "cwd": ".cache/integration/session-bYzhbx", + "rss": 359759872, + "heapUsed": 79437600, + "maxRssKiB": 351456 + }, + { + "pid": 97236, + "build": 8, + "rootDir": ".cache/integration/fixture-b2R8By", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 407339008, + "heapUsed": 82242552, + "maxRssKiB": 397920 + }, + { + "pid": 97236, + "build": 9, + "rootDir": ".cache/integration/fixture-rcYCcw", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 408748032, + "heapUsed": 82948704, + "maxRssKiB": 399296 + }, + { + "pid": 97236, + "build": 10, + "rootDir": ".cache/integration/fixture-EacgpA", + "cwd": ".cache/integration/session-Pa9e5n", + "rss": 410107904, + "heapUsed": 83538944, + "maxRssKiB": 400624 + }, + { + "pid": 97238, + "build": 1, + "rootDir": ".cache/integration/fixture-kD5TCC", + "cwd": ".cache/integration/session-v3McW2", + "rss": 339214336, + "heapUsed": 71199416, + "maxRssKiB": 331392 + }, + { + "pid": 97238, + "build": 2, + "rootDir": ".cache/integration/fixture-0vapv0", + "cwd": ".cache/integration/session-v3McW2", + "rss": 351567872, + "heapUsed": 72808672, + "maxRssKiB": 343456 + }, + { + "pid": 97238, + "build": 3, + "rootDir": ".cache/integration/fixture-YmjQfY", + "cwd": ".cache/integration/session-v3McW2", + "rss": 362528768, + "heapUsed": 74449392, + "maxRssKiB": 354160 + }, + { + "pid": 97238, + "build": 4, + "rootDir": ".cache/integration/fixture-PVyG6w", + "cwd": ".cache/integration/session-v3McW2", + "rss": 372375552, + "heapUsed": 75999024, + "maxRssKiB": 363776 + }, + { + "pid": 97238, + "build": 5, + "rootDir": ".cache/integration/fixture-pBGuht", + "cwd": ".cache/integration/session-v3McW2", + "rss": 373161984, + "heapUsed": 76559624, + "maxRssKiB": 364544 + }, + { + "pid": 97238, + "build": 6, + "rootDir": ".cache/integration/fixture-COqo9f", + "cwd": ".cache/integration/session-v3McW2", + "rss": 373751808, + "heapUsed": 77097456, + "maxRssKiB": 365120 + }, + { + "pid": 97238, + "build": 7, + "rootDir": ".cache/integration/fixture-fIUU2f", + "cwd": ".cache/integration/session-v3McW2", + "rss": 374489088, + "heapUsed": 77561512, + "maxRssKiB": 365840 + }, + { + "pid": 97238, + "build": 8, + "rootDir": ".cache/integration/fixture-p20i2f", + "cwd": ".cache/integration/session-v3McW2", + "rss": 375242752, + "heapUsed": 78092976, + "maxRssKiB": 366576 + }, + { + "pid": 97238, + "build": 9, + "rootDir": ".cache/integration/fixture-ObWwOD", + "cwd": ".cache/integration/session-v3McW2", + "rss": 376111104, + "heapUsed": 78790840, + "maxRssKiB": 367424 + }, + { + "pid": 97238, + "build": 10, + "rootDir": ".cache/integration/fixture-8mROAt", + "cwd": ".cache/integration/session-v3McW2", + "rss": 376782848, + "heapUsed": 79395736, + "maxRssKiB": 368080 + } + ], + "parentSamples": [ + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116228096, + "heapTotal": 60964864, + "heapUsed": 24852400, + "external": 5723131, + "arrayBuffers": 2062066, + "maxRssKiB": 113728 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116359168, + "heapTotal": 60440576, + "heapUsed": 24853272, + "external": 5723123, + "arrayBuffers": 2062066, + "maxRssKiB": 113808 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117014528, + "heapTotal": 61489152, + "heapUsed": 25879136, + "external": 5703744, + "arrayBuffers": 2070258, + "maxRssKiB": 114272 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117096448, + "heapTotal": 61489152, + "heapUsed": 25882640, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 114352 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117538816, + "heapTotal": 62799872, + "heapUsed": 26938352, + "external": 5703713, + "arrayBuffers": 2070258, + "maxRssKiB": 114784 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117555200, + "heapTotal": 62537728, + "heapUsed": 26938440, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 114800 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118308864, + "heapTotal": 63848448, + "heapUsed": 28001016, + "external": 5711934, + "arrayBuffers": 2070258, + "maxRssKiB": 115536 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118161408, + "heapTotal": 63586304, + "heapUsed": 28001296, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 115392 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119013376, + "heapTotal": 64634880, + "heapUsed": 29060488, + "external": 5703091, + "arrayBuffers": 2070258, + "maxRssKiB": 116240 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118865920, + "heapTotal": 64897024, + "heapUsed": 29048320, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 116080 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119668736, + "heapTotal": 65945600, + "heapUsed": 30086800, + "external": 5711283, + "arrayBuffers": 2070258, + "maxRssKiB": 116864 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120356864, + "heapTotal": 66994176, + "heapUsed": 31110384, + "external": 5702935, + "arrayBuffers": 2070258, + "maxRssKiB": 117536 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119603200, + "heapTotal": 65945600, + "heapUsed": 30078704, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 116800 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121044992, + "heapTotal": 68042752, + "heapUsed": 32133832, + "external": 5711127, + "arrayBuffers": 2070258, + "maxRssKiB": 118224 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120340480, + "heapTotal": 67256320, + "heapUsed": 31101368, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 117520 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121683968, + "heapTotal": 69091328, + "heapUsed": 33188984, + "external": 5702934, + "arrayBuffers": 2070258, + "maxRssKiB": 118832 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122388480, + "heapTotal": 70139904, + "heapUsed": 34219616, + "external": 5711128, + "arrayBuffers": 2070258, + "maxRssKiB": 119520 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121012224, + "heapTotal": 68567040, + "heapUsed": 32125008, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 118176 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121749504, + "heapTotal": 69353472, + "heapUsed": 33193248, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 118896 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122486784, + "heapTotal": 70664192, + "heapUsed": 34225928, + "external": 5711126, + "arrayBuffers": 2070258, + "maxRssKiB": 119616 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 138805248, + "heapTotal": 104005632, + "heapUsed": 29537312, + "external": 7765981, + "arrayBuffers": 2078457, + "maxRssKiB": 135680 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 139182080, + "heapTotal": 106364928, + "heapUsed": 29678632, + "external": 7774187, + "arrayBuffers": 2086642, + "maxRssKiB": 136048 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 139362304, + "heapTotal": 101384192, + "heapUsed": 30526280, + "external": 5720155, + "arrayBuffers": 2086649, + "maxRssKiB": 136096 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 139378688, + "heapTotal": 101908480, + "heapUsed": 31563024, + "external": 5720124, + "arrayBuffers": 2086649, + "maxRssKiB": 136112 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 139395072, + "heapTotal": 102957056, + "heapUsed": 32598448, + "external": 5728345, + "arrayBuffers": 2086649, + "maxRssKiB": 136128 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 139558912, + "heapTotal": 104005632, + "heapUsed": 33640224, + "external": 5719490, + "arrayBuffers": 2086649, + "maxRssKiB": 136288 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 139689984, + "heapTotal": 105316352, + "heapUsed": 34677088, + "external": 5727682, + "arrayBuffers": 2086649, + "maxRssKiB": 136416 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140034048, + "heapTotal": 106627072, + "heapUsed": 35703896, + "external": 5719334, + "arrayBuffers": 2086649, + "maxRssKiB": 136784 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 140738560, + "heapTotal": 107675648, + "heapUsed": 36722456, + "external": 5727526, + "arrayBuffers": 2086649, + "maxRssKiB": 137440 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141361152, + "heapTotal": 108462080, + "heapUsed": 37762216, + "external": 5719333, + "arrayBuffers": 2086649, + "maxRssKiB": 138048 + }, + { + "pid": 97234, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 142065664, + "heapTotal": 109772800, + "heapUsed": 38792768, + "external": 5727527, + "arrayBuffers": 2086649, + "maxRssKiB": 138736 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 139952128, + "heapTotal": 101122048, + "heapUsed": 30663328, + "external": 5728945, + "arrayBuffers": 2094834, + "maxRssKiB": 136672 + }, + { + "pid": 97233, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 140345344, + "heapTotal": 101908480, + "heapUsed": 31710464, + "external": 5729485, + "arrayBuffers": 2094834, + "maxRssKiB": 137056 + } + ] + }, + { + "id": "5-measured-2-session", + "phase": "measured", + "mode": "session", + "workers": 2, + "wallSeconds": 3.5277742089999955, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 97286, + "build": 1, + "rootDir": ".cache/integration/fixture-aqbrs0", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 312524800, + "heapUsed": 71152440, + "maxRssKiB": 305328 + }, + { + "pid": 97286, + "build": 2, + "rootDir": ".cache/integration/fixture-0GEhER", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 327221248, + "heapUsed": 72839216, + "maxRssKiB": 319680 + }, + { + "pid": 97286, + "build": 3, + "rootDir": ".cache/integration/fixture-8YDC2Y", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 339410944, + "heapUsed": 74473704, + "maxRssKiB": 331584 + }, + { + "pid": 97286, + "build": 4, + "rootDir": ".cache/integration/fixture-5gz7PU", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 350044160, + "heapUsed": 76023080, + "maxRssKiB": 341968 + }, + { + "pid": 97286, + "build": 5, + "rootDir": ".cache/integration/fixture-LqMShT", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 360857600, + "heapUsed": 77627952, + "maxRssKiB": 352528 + }, + { + "pid": 97286, + "build": 6, + "rootDir": ".cache/integration/fixture-exR2EK", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 376029184, + "heapUsed": 79205064, + "maxRssKiB": 367344 + }, + { + "pid": 97286, + "build": 7, + "rootDir": ".cache/integration/fixture-JxYV70", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 385187840, + "heapUsed": 80682880, + "maxRssKiB": 376288 + }, + { + "pid": 97286, + "build": 8, + "rootDir": ".cache/integration/fixture-hmTMFA", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 394903552, + "heapUsed": 82261256, + "maxRssKiB": 385776 + }, + { + "pid": 97286, + "build": 9, + "rootDir": ".cache/integration/fixture-IeZBHy", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 396378112, + "heapUsed": 82970128, + "maxRssKiB": 387216 + }, + { + "pid": 97286, + "build": 10, + "rootDir": ".cache/integration/fixture-UGayAN", + "cwd": ".cache/integration/session-G8kYZd", + "rss": 397656064, + "heapUsed": 83573864, + "maxRssKiB": 388464 + }, + { + "pid": 97348, + "build": 1, + "rootDir": ".cache/integration/fixture-nmJjWd", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 312410112, + "heapUsed": 71142424, + "maxRssKiB": 305216 + }, + { + "pid": 97348, + "build": 2, + "rootDir": ".cache/integration/fixture-NkjxGa", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 325664768, + "heapUsed": 72838216, + "maxRssKiB": 318160 + }, + { + "pid": 97348, + "build": 3, + "rootDir": ".cache/integration/fixture-U90ZFC", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 337428480, + "heapUsed": 74467928, + "maxRssKiB": 329648 + }, + { + "pid": 97348, + "build": 4, + "rootDir": ".cache/integration/fixture-a08n7n", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 348504064, + "heapUsed": 76017264, + "maxRssKiB": 340464 + }, + { + "pid": 97348, + "build": 5, + "rootDir": ".cache/integration/fixture-zjOWdy", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 349863936, + "heapUsed": 76586784, + "maxRssKiB": 341792 + }, + { + "pid": 97348, + "build": 6, + "rootDir": ".cache/integration/fixture-nGjQ3h", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 350928896, + "heapUsed": 77126312, + "maxRssKiB": 342832 + }, + { + "pid": 97348, + "build": 7, + "rootDir": ".cache/integration/fixture-CpFPP7", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 352321536, + "heapUsed": 77573152, + "maxRssKiB": 344192 + }, + { + "pid": 97348, + "build": 8, + "rootDir": ".cache/integration/fixture-a0KWKp", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 359006208, + "heapUsed": 78119544, + "maxRssKiB": 350720 + }, + { + "pid": 97348, + "build": 9, + "rootDir": ".cache/integration/fixture-p5jwyO", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 360398848, + "heapUsed": 78817224, + "maxRssKiB": 352080 + }, + { + "pid": 97348, + "build": 10, + "rootDir": ".cache/integration/fixture-SKVfvg", + "cwd": ".cache/integration/session-i2kDU7", + "rss": 361889792, + "heapUsed": 79422040, + "maxRssKiB": 353536 + }, + { + "pid": 97349, + "build": 1, + "rootDir": ".cache/integration/fixture-Qo6BJV", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 311312384, + "heapUsed": 71140672, + "maxRssKiB": 304144 + }, + { + "pid": 97349, + "build": 2, + "rootDir": ".cache/integration/fixture-coANBP", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 323764224, + "heapUsed": 72832608, + "maxRssKiB": 316304 + }, + { + "pid": 97349, + "build": 3, + "rootDir": ".cache/integration/fixture-Y5JGTy", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 335052800, + "heapUsed": 74466128, + "maxRssKiB": 327328 + }, + { + "pid": 97349, + "build": 4, + "rootDir": ".cache/integration/fixture-iHbAUR", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 345538560, + "heapUsed": 76017456, + "maxRssKiB": 337568 + }, + { + "pid": 97349, + "build": 5, + "rootDir": ".cache/integration/fixture-rLLIMt", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 346963968, + "heapUsed": 76584528, + "maxRssKiB": 338960 + }, + { + "pid": 97349, + "build": 6, + "rootDir": ".cache/integration/fixture-OnWwkW", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 348078080, + "heapUsed": 77122720, + "maxRssKiB": 340048 + }, + { + "pid": 97349, + "build": 7, + "rootDir": ".cache/integration/fixture-hTiOiG", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 349339648, + "heapUsed": 77583344, + "maxRssKiB": 341280 + }, + { + "pid": 97349, + "build": 8, + "rootDir": ".cache/integration/fixture-0E5r7m", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 356040704, + "heapUsed": 78117584, + "maxRssKiB": 347824 + }, + { + "pid": 97349, + "build": 9, + "rootDir": ".cache/integration/fixture-Or831i", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 357548032, + "heapUsed": 78814584, + "maxRssKiB": 349296 + }, + { + "pid": 97349, + "build": 10, + "rootDir": ".cache/integration/fixture-mNlBOl", + "cwd": ".cache/integration/session-LDsTHa", + "rss": 358842368, + "heapUsed": 79421776, + "maxRssKiB": 350560 + } + ], + "parentSamples": [ + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116047872, + "heapTotal": 60964864, + "heapUsed": 24957464, + "external": 5723167, + "arrayBuffers": 2101183, + "maxRssKiB": 113456 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116211712, + "heapTotal": 60178432, + "heapUsed": 24854072, + "external": 5723123, + "arrayBuffers": 2101139, + "maxRssKiB": 113616 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116768768, + "heapTotal": 61227008, + "heapUsed": 25882120, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 114032 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117194752, + "heapTotal": 62537728, + "heapUsed": 26938992, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 114448 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117850112, + "heapTotal": 63586304, + "heapUsed": 28001840, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 115088 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118587392, + "heapTotal": 64634880, + "heapUsed": 29049032, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 115808 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119291904, + "heapTotal": 65945600, + "heapUsed": 30079544, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 116496 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119996416, + "heapTotal": 66994176, + "heapUsed": 31101760, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 117184 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120750080, + "heapTotal": 68304896, + "heapUsed": 32125032, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 117920 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121503744, + "heapTotal": 69353472, + "heapUsed": 33193048, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 118656 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122241024, + "heapTotal": 70664192, + "heapUsed": 34225952, + "external": 5711126, + "arrayBuffers": 2070258, + "maxRssKiB": 119376 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116604928, + "heapTotal": 61489152, + "heapUsed": 25987064, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 113872 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117112832, + "heapTotal": 62537728, + "heapUsed": 27059736, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114368 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 139280384, + "heapTotal": 106627072, + "heapUsed": 29539168, + "external": 7774151, + "arrayBuffers": 2086649, + "maxRssKiB": 136144 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 140017664, + "heapTotal": 100597760, + "heapUsed": 30527368, + "external": 5728327, + "arrayBuffers": 2094841, + "maxRssKiB": 136736 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 140017664, + "heapTotal": 101122048, + "heapUsed": 31562864, + "external": 5728296, + "arrayBuffers": 2094841, + "maxRssKiB": 136736 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 140017664, + "heapTotal": 102170624, + "heapUsed": 32601888, + "external": 5736517, + "arrayBuffers": 2094841, + "maxRssKiB": 136736 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 140034048, + "heapTotal": 103481344, + "heapUsed": 33640272, + "external": 5727674, + "arrayBuffers": 2094841, + "maxRssKiB": 136752 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 140083200, + "heapTotal": 104529920, + "heapUsed": 34679480, + "external": 5735866, + "arrayBuffers": 2094841, + "maxRssKiB": 136800 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140165120, + "heapTotal": 105578496, + "heapUsed": 35703944, + "external": 5727518, + "arrayBuffers": 2094841, + "maxRssKiB": 136880 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 140165120, + "heapTotal": 106889216, + "heapUsed": 36724136, + "external": 5735710, + "arrayBuffers": 2094841, + "maxRssKiB": 136880 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 140197888, + "heapTotal": 107937792, + "heapUsed": 37763912, + "external": 5727517, + "arrayBuffers": 2094841, + "maxRssKiB": 136912 + }, + { + "pid": 97280, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 140673024, + "heapTotal": 109248512, + "heapUsed": 38797456, + "external": 5735711, + "arrayBuffers": 2094841, + "maxRssKiB": 137376 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 125059072, + "heapTotal": 66519040, + "heapUsed": 29346784, + "external": 7774166, + "arrayBuffers": 2078450, + "maxRssKiB": 122256 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 125517824, + "heapTotal": 67305472, + "heapUsed": 30370432, + "external": 5720148, + "arrayBuffers": 2086642, + "maxRssKiB": 122576 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 126140416, + "heapTotal": 68354048, + "heapUsed": 31414224, + "external": 5720117, + "arrayBuffers": 2086642, + "maxRssKiB": 123184 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 129286144, + "heapTotal": 102957056, + "heapUsed": 32474920, + "external": 5728338, + "arrayBuffers": 2086642, + "maxRssKiB": 126256 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 129613824, + "heapTotal": 103743488, + "heapUsed": 33522992, + "external": 5719483, + "arrayBuffers": 2086642, + "maxRssKiB": 126576 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 130105344, + "heapTotal": 105054208, + "heapUsed": 34551264, + "external": 5727675, + "arrayBuffers": 2086642, + "maxRssKiB": 127056 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 130826240, + "heapTotal": 106102784, + "heapUsed": 35591384, + "external": 5719327, + "arrayBuffers": 2086642, + "maxRssKiB": 127760 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 131399680, + "heapTotal": 107151360, + "heapUsed": 36609944, + "external": 5727519, + "arrayBuffers": 2086642, + "maxRssKiB": 128320 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 132136960, + "heapTotal": 108199936, + "heapUsed": 37666096, + "external": 5719326, + "arrayBuffers": 2086642, + "maxRssKiB": 129040 + }, + { + "pid": 97279, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 132694016, + "heapTotal": 109248512, + "heapUsed": 38695728, + "external": 5727520, + "arrayBuffers": 2086642, + "maxRssKiB": 129584 + } + ] + }, + { + "id": "6-measured-2-cli", + "phase": "measured", + "mode": "cli", + "workers": 2, + "wallSeconds": 12.941111665999998, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115556352, + "heapTotal": 61227008, + "heapUsed": 24958392, + "external": 5723162, + "arrayBuffers": 2062059, + "maxRssKiB": 112976 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116097024, + "heapTotal": 60702720, + "heapUsed": 24861472, + "external": 5722809, + "arrayBuffers": 2062059, + "maxRssKiB": 113600 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116490240, + "heapTotal": 62013440, + "heapUsed": 25989096, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 113760 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116850688, + "heapTotal": 61227008, + "heapUsed": 25894104, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 114112 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117112832, + "heapTotal": 63062016, + "heapUsed": 27060024, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114368 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117358592, + "heapTotal": 62275584, + "heapUsed": 26955992, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 114608 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 125468672, + "heapTotal": 66781184, + "heapUsed": 29326288, + "external": 7773861, + "arrayBuffers": 2078443, + "maxRssKiB": 122656 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118112256, + "heapTotal": 63586304, + "heapUsed": 28035104, + "external": 5704041, + "arrayBuffers": 2070251, + "maxRssKiB": 115344 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 125730816, + "heapTotal": 67567616, + "heapUsed": 30365768, + "external": 5720463, + "arrayBuffers": 2086635, + "maxRssKiB": 122784 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118882304, + "heapTotal": 64897024, + "heapUsed": 29093424, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 116096 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 125976576, + "heapTotal": 68091904, + "heapUsed": 31409352, + "external": 5728624, + "arrayBuffers": 2086635, + "maxRssKiB": 123024 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119537664, + "heapTotal": 66207744, + "heapUsed": 30123112, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 116736 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120291328, + "heapTotal": 67256320, + "heapUsed": 31147552, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 117472 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 129024000, + "heapTotal": 102694912, + "heapUsed": 32471696, + "external": 5720461, + "arrayBuffers": 2086635, + "maxRssKiB": 126000 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 129499136, + "heapTotal": 103743488, + "heapUsed": 33517664, + "external": 5727565, + "arrayBuffers": 2086635, + "maxRssKiB": 126464 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121012224, + "heapTotal": 68567040, + "heapUsed": 32170288, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 118176 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 130203648, + "heapTotal": 104792064, + "heapUsed": 34564744, + "external": 5719373, + "arrayBuffers": 2086635, + "maxRssKiB": 127152 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121815040, + "heapTotal": 69615616, + "heapUsed": 33248760, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 118960 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 130891776, + "heapTotal": 106102784, + "heapUsed": 35592168, + "external": 5727409, + "arrayBuffers": 2086635, + "maxRssKiB": 127824 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122535936, + "heapTotal": 70926336, + "heapUsed": 34278896, + "external": 5711014, + "arrayBuffers": 2070251, + "maxRssKiB": 119664 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 131497984, + "heapTotal": 107151360, + "heapUsed": 36613848, + "external": 5719217, + "arrayBuffers": 2086635, + "maxRssKiB": 128416 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 138985472, + "heapTotal": 104005632, + "heapUsed": 29549008, + "external": 7765641, + "arrayBuffers": 2078443, + "maxRssKiB": 135856 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 132169728, + "heapTotal": 108199936, + "heapUsed": 37665080, + "external": 5727408, + "arrayBuffers": 2086635, + "maxRssKiB": 129072 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 139280384, + "heapTotal": 100859904, + "heapUsed": 30538256, + "external": 5720437, + "arrayBuffers": 2086635, + "maxRssKiB": 136016 + }, + { + "pid": 97389, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 132907008, + "heapTotal": 109510656, + "heapUsed": 38695792, + "external": 5727408, + "arrayBuffers": 2086635, + "maxRssKiB": 129792 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 139542528, + "heapTotal": 101646336, + "heapUsed": 31571920, + "external": 5728598, + "arrayBuffers": 2086635, + "maxRssKiB": 136272 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 139624448, + "heapTotal": 102694912, + "heapUsed": 32617024, + "external": 5720435, + "arrayBuffers": 2086635, + "maxRssKiB": 136352 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 139640832, + "heapTotal": 104005632, + "heapUsed": 33661256, + "external": 5727557, + "arrayBuffers": 2086635, + "maxRssKiB": 136368 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 139788288, + "heapTotal": 105054208, + "heapUsed": 34699936, + "external": 5719364, + "arrayBuffers": 2086635, + "maxRssKiB": 136512 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140197888, + "heapTotal": 106102784, + "heapUsed": 35719520, + "external": 5727401, + "arrayBuffers": 2086635, + "maxRssKiB": 136912 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 140935168, + "heapTotal": 107151360, + "heapUsed": 36747064, + "external": 5719209, + "arrayBuffers": 2086635, + "maxRssKiB": 137632 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141705216, + "heapTotal": 108462080, + "heapUsed": 37786440, + "external": 5727400, + "arrayBuffers": 2086635, + "maxRssKiB": 138384 + }, + { + "pid": 97390, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 142442496, + "heapTotal": 109772800, + "heapUsed": 38816624, + "external": 5727400, + "arrayBuffers": 2086635, + "maxRssKiB": 139104 + } + ] + }, + { + "id": "7-measured-2-cli", + "phase": "measured", + "mode": "cli", + "workers": 2, + "wallSeconds": 12.859592665999998, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116113408, + "heapTotal": 60702720, + "heapUsed": 24860536, + "external": 5722810, + "arrayBuffers": 2062059, + "maxRssKiB": 113648 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116441088, + "heapTotal": 60440576, + "heapUsed": 24864168, + "external": 5722849, + "arrayBuffers": 2062059, + "maxRssKiB": 113936 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117260288, + "heapTotal": 61227008, + "heapUsed": 25894792, + "external": 5704079, + "arrayBuffers": 2070251, + "maxRssKiB": 114512 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116899840, + "heapTotal": 60964864, + "heapUsed": 25892064, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 114160 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117784576, + "heapTotal": 62537728, + "heapUsed": 26958488, + "external": 5712240, + "arrayBuffers": 2070251, + "maxRssKiB": 115024 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117686272, + "heapTotal": 62537728, + "heapUsed": 26957032, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 114928 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118390784, + "heapTotal": 63324160, + "heapUsed": 28034000, + "external": 5704041, + "arrayBuffers": 2070251, + "maxRssKiB": 115616 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118292480, + "heapTotal": 63586304, + "heapUsed": 28033664, + "external": 5704077, + "arrayBuffers": 2070251, + "maxRssKiB": 115520 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118980608, + "heapTotal": 64372736, + "heapUsed": 29102704, + "external": 5711181, + "arrayBuffers": 2070251, + "maxRssKiB": 116192 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119259136, + "heapTotal": 64897024, + "heapUsed": 29095216, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 116464 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119635968, + "heapTotal": 65683456, + "heapUsed": 30134768, + "external": 5702988, + "arrayBuffers": 2070251, + "maxRssKiB": 116832 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 120012800, + "heapTotal": 66207744, + "heapUsed": 30123336, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 117200 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120471552, + "heapTotal": 66994176, + "heapUsed": 31157560, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 117648 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120569856, + "heapTotal": 67256320, + "heapUsed": 31146592, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 117744 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121159680, + "heapTotal": 68042752, + "heapUsed": 32179728, + "external": 5702833, + "arrayBuffers": 2070251, + "maxRssKiB": 118320 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121372672, + "heapTotal": 68304896, + "heapUsed": 32168736, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 118528 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121946112, + "heapTotal": 69353472, + "heapUsed": 33244792, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 119088 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122060800, + "heapTotal": 69615616, + "heapUsed": 33248264, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 119200 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122650624, + "heapTotal": 70402048, + "heapUsed": 34279384, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 119776 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122732544, + "heapTotal": 70664192, + "heapUsed": 34279632, + "external": 5711014, + "arrayBuffers": 2070251, + "maxRssKiB": 119856 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 139198464, + "heapTotal": 104529920, + "heapUsed": 29548360, + "external": 7765642, + "arrayBuffers": 2078443, + "maxRssKiB": 136064 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 139395072, + "heapTotal": 104005632, + "heapUsed": 29669008, + "external": 7765984, + "arrayBuffers": 2078443, + "maxRssKiB": 136256 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 139624448, + "heapTotal": 101122048, + "heapUsed": 30676424, + "external": 5720746, + "arrayBuffers": 2086635, + "maxRssKiB": 136352 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 139673600, + "heapTotal": 100859904, + "heapUsed": 30536528, + "external": 5720437, + "arrayBuffers": 2086635, + "maxRssKiB": 136400 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 139804672, + "heapTotal": 101908480, + "heapUsed": 31572360, + "external": 5728598, + "arrayBuffers": 2086635, + "maxRssKiB": 136528 + }, + { + "pid": 97734, + "worker": "2", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 139722752, + "heapTotal": 101908480, + "heapUsed": 31703304, + "external": 5721286, + "arrayBuffers": 2086635, + "maxRssKiB": 136448 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 139886592, + "heapTotal": 103219200, + "heapUsed": 32612144, + "external": 5720435, + "arrayBuffers": 2086635, + "maxRssKiB": 136608 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 139935744, + "heapTotal": 104267776, + "heapUsed": 33659192, + "external": 5727557, + "arrayBuffers": 2086635, + "maxRssKiB": 136656 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 139968512, + "heapTotal": 105316352, + "heapUsed": 34698456, + "external": 5719365, + "arrayBuffers": 2086635, + "maxRssKiB": 136688 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 140492800, + "heapTotal": 106627072, + "heapUsed": 35721552, + "external": 5727401, + "arrayBuffers": 2086635, + "maxRssKiB": 137200 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 141017088, + "heapTotal": 107675648, + "heapUsed": 36740808, + "external": 5719209, + "arrayBuffers": 2086635, + "maxRssKiB": 137712 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 141721600, + "heapTotal": 108724224, + "heapUsed": 37786120, + "external": 5727399, + "arrayBuffers": 2086635, + "maxRssKiB": 138400 + }, + { + "pid": 97733, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 142475264, + "heapTotal": 109772800, + "heapUsed": 38816528, + "external": 5727400, + "arrayBuffers": 2086635, + "maxRssKiB": 139136 + } + ] + }, + { + "id": "8-measured-2-session", + "phase": "measured", + "mode": "session", + "workers": 2, + "wallSeconds": 3.506650125, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 97970, + "build": 1, + "rootDir": ".cache/integration/fixture-YwUPnu", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 313098240, + "heapUsed": 71149648, + "maxRssKiB": 305888 + }, + { + "pid": 97971, + "build": 1, + "rootDir": ".cache/integration/fixture-JCTRe5", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 342687744, + "heapUsed": 71198856, + "maxRssKiB": 334784 + }, + { + "pid": 97970, + "build": 2, + "rootDir": ".cache/integration/fixture-UHniEX", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 325451776, + "heapUsed": 72839784, + "maxRssKiB": 317952 + }, + { + "pid": 97971, + "build": 2, + "rootDir": ".cache/integration/fixture-r1OqVU", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 353927168, + "heapUsed": 72810400, + "maxRssKiB": 345760 + }, + { + "pid": 97971, + "build": 3, + "rootDir": ".cache/integration/fixture-8aPRzc", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 363921408, + "heapUsed": 74441392, + "maxRssKiB": 355520 + }, + { + "pid": 97970, + "build": 3, + "rootDir": ".cache/integration/fixture-vysHyD", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 336248832, + "heapUsed": 74474872, + "maxRssKiB": 328496 + }, + { + "pid": 97971, + "build": 4, + "rootDir": ".cache/integration/fixture-I9L9PG", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 373571584, + "heapUsed": 75990584, + "maxRssKiB": 364944 + }, + { + "pid": 97970, + "build": 4, + "rootDir": ".cache/integration/fixture-lB2KOv", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 345554944, + "heapUsed": 76025192, + "maxRssKiB": 337584 + }, + { + "pid": 97970, + "build": 5, + "rootDir": ".cache/integration/fixture-NKHBnP", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 346963968, + "heapUsed": 76594672, + "maxRssKiB": 338960 + }, + { + "pid": 97971, + "build": 5, + "rootDir": ".cache/integration/fixture-YThxIf", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 382550016, + "heapUsed": 77593896, + "maxRssKiB": 373712 + }, + { + "pid": 97970, + "build": 6, + "rootDir": ".cache/integration/fixture-fOSpNo", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 348110848, + "heapUsed": 77131888, + "maxRssKiB": 340080 + }, + { + "pid": 97971, + "build": 6, + "rootDir": ".cache/integration/fixture-JVUFAh", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 389726208, + "heapUsed": 79170776, + "maxRssKiB": 380720 + }, + { + "pid": 97970, + "build": 7, + "rootDir": ".cache/integration/fixture-4OQMAw", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 349372416, + "heapUsed": 77591488, + "maxRssKiB": 341312 + }, + { + "pid": 97970, + "build": 8, + "rootDir": ".cache/integration/fixture-A63oKi", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 356122624, + "heapUsed": 78124384, + "maxRssKiB": 347904 + }, + { + "pid": 97971, + "build": 7, + "rootDir": ".cache/integration/fixture-g6nHWW", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 398934016, + "heapUsed": 80649896, + "maxRssKiB": 389712 + }, + { + "pid": 97970, + "build": 9, + "rootDir": ".cache/integration/fixture-nqlkEb", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 357564416, + "heapUsed": 78826504, + "maxRssKiB": 349312 + }, + { + "pid": 97971, + "build": 8, + "rootDir": ".cache/integration/fixture-9QhupS", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 407617536, + "heapUsed": 82228248, + "maxRssKiB": 398192 + }, + { + "pid": 97970, + "build": 10, + "rootDir": ".cache/integration/fixture-9zFHwZ", + "cwd": ".cache/integration/session-1ptfYz", + "rss": 358891520, + "heapUsed": 79428880, + "maxRssKiB": 350608 + }, + { + "pid": 97971, + "build": 9, + "rootDir": ".cache/integration/fixture-lJGL6k", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 409026560, + "heapUsed": 82936472, + "maxRssKiB": 399568 + }, + { + "pid": 97971, + "build": 10, + "rootDir": ".cache/integration/fixture-F4KNtQ", + "cwd": ".cache/integration/session-kMHOQa", + "rss": 410238976, + "heapUsed": 83538832, + "maxRssKiB": 400752 + }, + { + "pid": 97974, + "build": 1, + "rootDir": ".cache/integration/fixture-zozeUv", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 312852480, + "heapUsed": 71148600, + "maxRssKiB": 305648 + }, + { + "pid": 97974, + "build": 2, + "rootDir": ".cache/integration/fixture-DfoKnA", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 325795840, + "heapUsed": 72843440, + "maxRssKiB": 318288 + }, + { + "pid": 97974, + "build": 3, + "rootDir": ".cache/integration/fixture-MIVOOz", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 337428480, + "heapUsed": 74472792, + "maxRssKiB": 329648 + }, + { + "pid": 97974, + "build": 4, + "rootDir": ".cache/integration/fixture-4dyGvw", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 347799552, + "heapUsed": 76026192, + "maxRssKiB": 339776 + }, + { + "pid": 97974, + "build": 5, + "rootDir": ".cache/integration/fixture-6e4iEO", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 349290496, + "heapUsed": 76593312, + "maxRssKiB": 341232 + }, + { + "pid": 97974, + "build": 6, + "rootDir": ".cache/integration/fixture-8lDm1m", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 350273536, + "heapUsed": 77131456, + "maxRssKiB": 342192 + }, + { + "pid": 97974, + "build": 7, + "rootDir": ".cache/integration/fixture-nIan0d", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 351535104, + "heapUsed": 77580560, + "maxRssKiB": 343424 + }, + { + "pid": 97974, + "build": 8, + "rootDir": ".cache/integration/fixture-pllKdv", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 358268928, + "heapUsed": 78123272, + "maxRssKiB": 350000 + }, + { + "pid": 97974, + "build": 9, + "rootDir": ".cache/integration/fixture-oHLRQ5", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 359677952, + "heapUsed": 78822560, + "maxRssKiB": 351376 + }, + { + "pid": 97974, + "build": 10, + "rootDir": ".cache/integration/fixture-qeYu1x", + "cwd": ".cache/integration/session-LdxoM8", + "rss": 361086976, + "heapUsed": 79427080, + "maxRssKiB": 352752 + } + ], + "parentSamples": [ + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115818496, + "heapTotal": 60440576, + "heapUsed": 24852768, + "external": 5723154, + "arrayBuffers": 2062066, + "maxRssKiB": 113344 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115834880, + "heapTotal": 60702720, + "heapUsed": 24851896, + "external": 5723124, + "arrayBuffers": 2062066, + "maxRssKiB": 113344 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116441088, + "heapTotal": 61227008, + "heapUsed": 25881344, + "external": 5703764, + "arrayBuffers": 2070258, + "maxRssKiB": 113712 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116375552, + "heapTotal": 61227008, + "heapUsed": 25879520, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 113648 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116948992, + "heapTotal": 62275584, + "heapUsed": 26938728, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 114208 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117063680, + "heapTotal": 62537728, + "heapUsed": 26940760, + "external": 5703733, + "arrayBuffers": 2070258, + "maxRssKiB": 114320 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117702656, + "heapTotal": 63586304, + "heapUsed": 28001216, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 114944 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117768192, + "heapTotal": 63848448, + "heapUsed": 28001216, + "external": 5711954, + "arrayBuffers": 2070258, + "maxRssKiB": 115008 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118374400, + "heapTotal": 64634880, + "heapUsed": 29057232, + "external": 5703099, + "arrayBuffers": 2070258, + "maxRssKiB": 115600 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118390784, + "heapTotal": 64634880, + "heapUsed": 29048864, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 115616 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119062528, + "heapTotal": 65945600, + "heapUsed": 30087176, + "external": 5711291, + "arrayBuffers": 2070258, + "maxRssKiB": 116272 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119701504, + "heapTotal": 67256320, + "heapUsed": 31110584, + "external": 5702943, + "arrayBuffers": 2070258, + "maxRssKiB": 116896 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119128064, + "heapTotal": 65945600, + "heapUsed": 30078592, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 116336 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120340480, + "heapTotal": 68042752, + "heapUsed": 32132160, + "external": 5711135, + "arrayBuffers": 2070258, + "maxRssKiB": 117520 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119898112, + "heapTotal": 66994176, + "heapUsed": 31102984, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 117088 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121012224, + "heapTotal": 69353472, + "heapUsed": 33187664, + "external": 5702942, + "arrayBuffers": 2070258, + "maxRssKiB": 118176 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120487936, + "heapTotal": 68042752, + "heapUsed": 32124832, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 117664 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121716736, + "heapTotal": 70139904, + "heapUsed": 34221576, + "external": 5711136, + "arrayBuffers": 2070258, + "maxRssKiB": 118864 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121257984, + "heapTotal": 69353472, + "heapUsed": 33193072, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 118416 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121847808, + "heapTotal": 70664192, + "heapUsed": 34224936, + "external": 5711126, + "arrayBuffers": 2070258, + "maxRssKiB": 118992 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 138608640, + "heapTotal": 104529920, + "heapUsed": 29537776, + "external": 7765960, + "arrayBuffers": 2078457, + "maxRssKiB": 135488 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 139788288, + "heapTotal": 106889216, + "heapUsed": 29679264, + "external": 7774188, + "arrayBuffers": 2086642, + "maxRssKiB": 136640 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 138870784, + "heapTotal": 100859904, + "heapUsed": 30526816, + "external": 5720135, + "arrayBuffers": 2086649, + "maxRssKiB": 135616 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 139067392, + "heapTotal": 101908480, + "heapUsed": 31563208, + "external": 5720104, + "arrayBuffers": 2086649, + "maxRssKiB": 135808 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 139116544, + "heapTotal": 102957056, + "heapUsed": 32598776, + "external": 5728325, + "arrayBuffers": 2086649, + "maxRssKiB": 135856 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 139116544, + "heapTotal": 104005632, + "heapUsed": 33639928, + "external": 5719482, + "arrayBuffers": 2086649, + "maxRssKiB": 135856 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 139149312, + "heapTotal": 105054208, + "heapUsed": 34677176, + "external": 5727674, + "arrayBuffers": 2086649, + "maxRssKiB": 135888 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 139706368, + "heapTotal": 106364928, + "heapUsed": 35703008, + "external": 5719326, + "arrayBuffers": 2086649, + "maxRssKiB": 136432 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 140296192, + "heapTotal": 107151360, + "heapUsed": 36723200, + "external": 5727518, + "arrayBuffers": 2086649, + "maxRssKiB": 137008 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 140853248, + "heapTotal": 108462080, + "heapUsed": 37764896, + "external": 5719325, + "arrayBuffers": 2086649, + "maxRssKiB": 137552 + }, + { + "pid": 97969, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 141459456, + "heapTotal": 109510656, + "heapUsed": 38791272, + "external": 5727519, + "arrayBuffers": 2086649, + "maxRssKiB": 138144 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 140460032, + "heapTotal": 100859904, + "heapUsed": 30663928, + "external": 5728945, + "arrayBuffers": 2094834, + "maxRssKiB": 137168 + }, + { + "pid": 97968, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 140722176, + "heapTotal": 101908480, + "heapUsed": 31712272, + "external": 5729485, + "arrayBuffers": 2094834, + "maxRssKiB": 137424 + } + ] + }, + { + "id": "9-warmup-8-cli", + "phase": "warmup", + "mode": "cli", + "workers": 8, + "wallSeconds": 6.832721541999999, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 98078, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115884032, + "heapTotal": 61751296, + "heapUsed": 24977848, + "external": 5723160, + "arrayBuffers": 2062059, + "maxRssKiB": 113296 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115736576, + "heapTotal": 60702720, + "heapUsed": 24860704, + "external": 5722846, + "arrayBuffers": 2062059, + "maxRssKiB": 113152 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115572736, + "heapTotal": 60702720, + "heapUsed": 24859560, + "external": 5722818, + "arrayBuffers": 2062059, + "maxRssKiB": 113088 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115441664, + "heapTotal": 60440576, + "heapUsed": 24859248, + "external": 5722807, + "arrayBuffers": 2062059, + "maxRssKiB": 112960 + }, + { + "pid": 98078, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116670464, + "heapTotal": 62013440, + "heapUsed": 26007512, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 113936 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116473856, + "heapTotal": 61227008, + "heapUsed": 25891360, + "external": 5704053, + "arrayBuffers": 2070251, + "maxRssKiB": 113744 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116080640, + "heapTotal": 61227008, + "heapUsed": 25892648, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 113360 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116359168, + "heapTotal": 61227008, + "heapUsed": 25894040, + "external": 5704079, + "arrayBuffers": 2082055, + "maxRssKiB": 113632 + }, + { + "pid": 98078, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117047296, + "heapTotal": 62799872, + "heapUsed": 27082048, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114304 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116441088, + "heapTotal": 62537728, + "heapUsed": 26956168, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 113712 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117129216, + "heapTotal": 62275584, + "heapUsed": 26956536, + "external": 5712240, + "arrayBuffers": 2070251, + "maxRssKiB": 114384 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117096448, + "heapTotal": 62275584, + "heapUsed": 26957744, + "external": 5712214, + "arrayBuffers": 2070251, + "maxRssKiB": 114352 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117161984, + "heapTotal": 63586304, + "heapUsed": 28031536, + "external": 5704041, + "arrayBuffers": 2070251, + "maxRssKiB": 114416 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117817344, + "heapTotal": 63586304, + "heapUsed": 28035272, + "external": 5704051, + "arrayBuffers": 2070251, + "maxRssKiB": 115056 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117751808, + "heapTotal": 63324160, + "heapUsed": 28033592, + "external": 5704077, + "arrayBuffers": 2070251, + "maxRssKiB": 114992 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118521856, + "heapTotal": 64634880, + "heapUsed": 29104488, + "external": 5711181, + "arrayBuffers": 2070251, + "maxRssKiB": 115744 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118587392, + "heapTotal": 64897024, + "heapUsed": 29103064, + "external": 5711173, + "arrayBuffers": 2070251, + "maxRssKiB": 115808 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 117964800, + "heapTotal": 65159168, + "heapUsed": 29089952, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 115200 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119177216, + "heapTotal": 65945600, + "heapUsed": 30132200, + "external": 5702981, + "arrayBuffers": 2070251, + "maxRssKiB": 116400 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119226368, + "heapTotal": 65945600, + "heapUsed": 30133640, + "external": 5702989, + "arrayBuffers": 2070251, + "maxRssKiB": 116432 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 118472704, + "heapTotal": 66207744, + "heapUsed": 30121432, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 115696 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119816192, + "heapTotal": 66732032, + "heapUsed": 31156352, + "external": 5711017, + "arrayBuffers": 2070251, + "maxRssKiB": 117008 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119947264, + "heapTotal": 66994176, + "heapUsed": 31157392, + "external": 5711025, + "arrayBuffers": 2070251, + "maxRssKiB": 117152 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119193600, + "heapTotal": 67256320, + "heapUsed": 31145312, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 116400 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120487936, + "heapTotal": 68042752, + "heapUsed": 32179688, + "external": 5702825, + "arrayBuffers": 2070251, + "maxRssKiB": 117664 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120766464, + "heapTotal": 68304896, + "heapUsed": 32177768, + "external": 5702833, + "arrayBuffers": 2070251, + "maxRssKiB": 117936 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 119996416, + "heapTotal": 68304896, + "heapUsed": 32170128, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 117184 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121323520, + "heapTotal": 69353472, + "heapUsed": 33243184, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 118480 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121487360, + "heapTotal": 69615616, + "heapUsed": 33243984, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 118640 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120750080, + "heapTotal": 69615616, + "heapUsed": 33247800, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 117920 + }, + { + "pid": 98081, + "worker": "4", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122028032, + "heapTotal": 70402048, + "heapUsed": 34277680, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 119168 + }, + { + "pid": 98080, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122093568, + "heapTotal": 70664192, + "heapUsed": 34276928, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 119232 + }, + { + "pid": 98079, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121503744, + "heapTotal": 70926336, + "heapUsed": 34277616, + "external": 5711014, + "arrayBuffers": 2070251, + "maxRssKiB": 118656 + } + ] + }, + { + "id": "10-warmup-8-session", + "phase": "warmup", + "mode": "session", + "workers": 8, + "wallSeconds": 2.545903166999997, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 98251, + "build": 1, + "rootDir": ".cache/integration/fixture-9ebwYg", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 342638592, + "heapUsed": 71203504, + "maxRssKiB": 334736 + }, + { + "pid": 98249, + "build": 1, + "rootDir": ".cache/integration/fixture-FW02WI", + "cwd": ".cache/integration/session-5B72nd", + "rss": 342310912, + "heapUsed": 71207032, + "maxRssKiB": 334416 + }, + { + "pid": 98250, + "build": 1, + "rootDir": ".cache/integration/fixture-HDpoYJ", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 342261760, + "heapUsed": 71205000, + "maxRssKiB": 334368 + }, + { + "pid": 98251, + "build": 2, + "rootDir": ".cache/integration/fixture-UKL2vA", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 355860480, + "heapUsed": 72815144, + "maxRssKiB": 347648 + }, + { + "pid": 98249, + "build": 2, + "rootDir": ".cache/integration/fixture-9ZkJET", + "cwd": ".cache/integration/session-5B72nd", + "rss": 354893824, + "heapUsed": 72815096, + "maxRssKiB": 346704 + }, + { + "pid": 98250, + "build": 2, + "rootDir": ".cache/integration/fixture-ClPzte", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 354713600, + "heapUsed": 72813824, + "maxRssKiB": 346528 + }, + { + "pid": 98251, + "build": 3, + "rootDir": ".cache/integration/fixture-YVx4KR", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 366526464, + "heapUsed": 74453800, + "maxRssKiB": 358064 + }, + { + "pid": 98249, + "build": 3, + "rootDir": ".cache/integration/fixture-bNITr0", + "cwd": ".cache/integration/session-5B72nd", + "rss": 365101056, + "heapUsed": 74456600, + "maxRssKiB": 356672 + }, + { + "pid": 98250, + "build": 3, + "rootDir": ".cache/integration/fixture-8pG5VK", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 365707264, + "heapUsed": 74451920, + "maxRssKiB": 357264 + }, + { + "pid": 98249, + "build": 4, + "rootDir": ".cache/integration/fixture-KcMucS", + "cwd": ".cache/integration/session-5B72nd", + "rss": 374669312, + "heapUsed": 76006296, + "maxRssKiB": 366016 + }, + { + "pid": 98251, + "build": 4, + "rootDir": ".cache/integration/fixture-u1VjET", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 374652928, + "heapUsed": 76002120, + "maxRssKiB": 366000 + }, + { + "pid": 98250, + "build": 4, + "rootDir": ".cache/integration/fixture-5FighA", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 375668736, + "heapUsed": 75998400, + "maxRssKiB": 366992 + }, + { + "pid": 98249, + "build": 5, + "rootDir": ".cache/integration/fixture-6FStM4", + "cwd": ".cache/integration/session-5B72nd", + "rss": 375422976, + "heapUsed": 76568632, + "maxRssKiB": 366752 + }, + { + "pid": 98250, + "build": 5, + "rootDir": ".cache/integration/fixture-Wc3MAe", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 376455168, + "heapUsed": 76563888, + "maxRssKiB": 367760 + }, + { + "pid": 98251, + "build": 5, + "rootDir": ".cache/integration/fixture-fYou8p", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 383778816, + "heapUsed": 77601872, + "maxRssKiB": 374912 + }, + { + "pid": 98249, + "build": 6, + "rootDir": ".cache/integration/fixture-1QOOkv", + "cwd": ".cache/integration/session-5B72nd", + "rss": 376078336, + "heapUsed": 77105992, + "maxRssKiB": 367392 + }, + { + "pid": 98250, + "build": 6, + "rootDir": ".cache/integration/fixture-nPHmOw", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 377110528, + "heapUsed": 77105152, + "maxRssKiB": 368400 + }, + { + "pid": 98249, + "build": 7, + "rootDir": ".cache/integration/fixture-7nUUHz", + "cwd": ".cache/integration/session-5B72nd", + "rss": 377028608, + "heapUsed": 77567064, + "maxRssKiB": 368320 + }, + { + "pid": 98251, + "build": 6, + "rootDir": ".cache/integration/fixture-B4zzLw", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 392740864, + "heapUsed": 79176464, + "maxRssKiB": 383664 + }, + { + "pid": 98250, + "build": 7, + "rootDir": ".cache/integration/fixture-xXCkN0", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 377847808, + "heapUsed": 77562256, + "maxRssKiB": 369120 + }, + { + "pid": 98249, + "build": 8, + "rootDir": ".cache/integration/fixture-zD5g8j", + "cwd": ".cache/integration/session-5B72nd", + "rss": 377749504, + "heapUsed": 78099080, + "maxRssKiB": 369024 + }, + { + "pid": 98250, + "build": 8, + "rootDir": ".cache/integration/fixture-wUVn9A", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 378372096, + "heapUsed": 78092344, + "maxRssKiB": 369632 + }, + { + "pid": 98251, + "build": 7, + "rootDir": ".cache/integration/fixture-F9SpbT", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 399409152, + "heapUsed": 80666328, + "maxRssKiB": 390176 + }, + { + "pid": 98249, + "build": 9, + "rootDir": ".cache/integration/fixture-nuHDDE", + "cwd": ".cache/integration/session-5B72nd", + "rss": 378519552, + "heapUsed": 78799096, + "maxRssKiB": 369776 + }, + { + "pid": 98250, + "build": 9, + "rootDir": ".cache/integration/fixture-H65LVO", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 379109376, + "heapUsed": 78792176, + "maxRssKiB": 370352 + }, + { + "pid": 98249, + "build": 10, + "rootDir": ".cache/integration/fixture-xKuNSs", + "cwd": ".cache/integration/session-5B72nd", + "rss": 379273216, + "heapUsed": 79402064, + "maxRssKiB": 370512 + }, + { + "pid": 98251, + "build": 8, + "rootDir": ".cache/integration/fixture-U3S1ek", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 407797760, + "heapUsed": 82242368, + "maxRssKiB": 398368 + }, + { + "pid": 98250, + "build": 10, + "rootDir": ".cache/integration/fixture-IwDYhk", + "cwd": ".cache/integration/session-KVwWk7", + "rss": 379879424, + "heapUsed": 79397576, + "maxRssKiB": 371104 + }, + { + "pid": 98251, + "build": 9, + "rootDir": ".cache/integration/fixture-WTZyy4", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 409370624, + "heapUsed": 82941264, + "maxRssKiB": 399904 + }, + { + "pid": 98251, + "build": 10, + "rootDir": ".cache/integration/fixture-OOSpsO", + "cwd": ".cache/integration/session-lKWiWQ", + "rss": 410583040, + "heapUsed": 83545096, + "maxRssKiB": 401088 + } + ], + "parentSamples": [ + { + "pid": 98244, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115572736, + "heapTotal": 60964864, + "heapUsed": 24978160, + "external": 5723166, + "arrayBuffers": 2101182, + "maxRssKiB": 112992 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115490816, + "heapTotal": 60964864, + "heapUsed": 24853152, + "external": 5723130, + "arrayBuffers": 2062066, + "maxRssKiB": 113008 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116080640, + "heapTotal": 60964864, + "heapUsed": 24853008, + "external": 5723152, + "arrayBuffers": 2062066, + "maxRssKiB": 113584 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116965376, + "heapTotal": 60702720, + "heapUsed": 24856264, + "external": 5723122, + "arrayBuffers": 2062066, + "maxRssKiB": 114400 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117719040, + "heapTotal": 61489152, + "heapUsed": 25881360, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 114960 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116834304, + "heapTotal": 61489152, + "heapUsed": 25881176, + "external": 5703764, + "arrayBuffers": 2070258, + "maxRssKiB": 114096 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 115965952, + "heapTotal": 61489152, + "heapUsed": 25881600, + "external": 5703744, + "arrayBuffers": 2070258, + "maxRssKiB": 113248 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118243328, + "heapTotal": 62275584, + "heapUsed": 26938712, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 115472 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117424128, + "heapTotal": 62799872, + "heapUsed": 26938384, + "external": 5703733, + "arrayBuffers": 2070258, + "maxRssKiB": 114672 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116457472, + "heapTotal": 62799872, + "heapUsed": 26941952, + "external": 5703713, + "arrayBuffers": 2070258, + "maxRssKiB": 113728 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118095872, + "heapTotal": 63848448, + "heapUsed": 28001048, + "external": 5711954, + "arrayBuffers": 2070258, + "maxRssKiB": 115328 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118947840, + "heapTotal": 63586304, + "heapUsed": 28003840, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 116160 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117161984, + "heapTotal": 63848448, + "heapUsed": 28001448, + "external": 5711934, + "arrayBuffers": 2070258, + "maxRssKiB": 114416 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118800384, + "heapTotal": 65159168, + "heapUsed": 29059176, + "external": 5703099, + "arrayBuffers": 2070258, + "maxRssKiB": 116016 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 117833728, + "heapTotal": 64897024, + "heapUsed": 29058936, + "external": 5703091, + "arrayBuffers": 2070258, + "maxRssKiB": 115072 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119717888, + "heapTotal": 64634880, + "heapUsed": 29048752, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 116912 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119406592, + "heapTotal": 66207744, + "heapUsed": 30088608, + "external": 5711291, + "arrayBuffers": 2070258, + "maxRssKiB": 116608 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118489088, + "heapTotal": 66207744, + "heapUsed": 30088480, + "external": 5711283, + "arrayBuffers": 2089259, + "maxRssKiB": 115712 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120127488, + "heapTotal": 67256320, + "heapUsed": 31110592, + "external": 5702943, + "arrayBuffers": 2070258, + "maxRssKiB": 117312 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 120340480, + "heapTotal": 65945600, + "heapUsed": 30079648, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 117520 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119078912, + "heapTotal": 67256320, + "heapUsed": 31109392, + "external": 5702935, + "arrayBuffers": 2070258, + "maxRssKiB": 116288 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120848384, + "heapTotal": 68567040, + "heapUsed": 32133608, + "external": 5711135, + "arrayBuffers": 2070258, + "maxRssKiB": 118016 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 119750656, + "heapTotal": 68304896, + "heapUsed": 32132216, + "external": 5711127, + "arrayBuffers": 2070258, + "maxRssKiB": 116944 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120979456, + "heapTotal": 66994176, + "heapUsed": 31101768, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 118144 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121602048, + "heapTotal": 69615616, + "heapUsed": 33189064, + "external": 5702942, + "arrayBuffers": 2070258, + "maxRssKiB": 118752 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120504320, + "heapTotal": 69615616, + "heapUsed": 33187720, + "external": 5702934, + "arrayBuffers": 2070258, + "maxRssKiB": 117680 + }, + { + "pid": 98242, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122306560, + "heapTotal": 70926336, + "heapUsed": 34221936, + "external": 5711136, + "arrayBuffers": 2070258, + "maxRssKiB": 119440 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121749504, + "heapTotal": 68042752, + "heapUsed": 32125456, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 118896 + }, + { + "pid": 98243, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121290752, + "heapTotal": 70664192, + "heapUsed": 34221200, + "external": 5711128, + "arrayBuffers": 2070258, + "maxRssKiB": 118448 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122290176, + "heapTotal": 69091328, + "heapUsed": 33194000, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 119424 + }, + { + "pid": 98244, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116342784, + "heapTotal": 62013440, + "heapUsed": 26008400, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 113616 + }, + { + "pid": 98241, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123027456, + "heapTotal": 70402048, + "heapUsed": 34226232, + "external": 5711126, + "arrayBuffers": 2089102, + "maxRssKiB": 120144 + }, + { + "pid": 98244, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 116883456, + "heapTotal": 62799872, + "heapUsed": 27080000, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114144 + } + ] + }, + { + "id": "11-measured-8-cli", + "phase": "measured", + "mode": "cli", + "workers": 8, + "wallSeconds": 6.86661320800001, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 98360, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 117161984, + "heapTotal": 61227008, + "heapUsed": 24981912, + "external": 5723163, + "arrayBuffers": 2062059, + "maxRssKiB": 114640 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116031488, + "heapTotal": 60440576, + "heapUsed": 24860584, + "external": 5722821, + "arrayBuffers": 2062059, + "maxRssKiB": 113552 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115605504, + "heapTotal": 60440576, + "heapUsed": 24860448, + "external": 5722849, + "arrayBuffers": 2062059, + "maxRssKiB": 113024 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 117047296, + "heapTotal": 59916288, + "heapUsed": 24862360, + "external": 5722810, + "arrayBuffers": 2062059, + "maxRssKiB": 114512 + }, + { + "pid": 98360, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 117702656, + "heapTotal": 61751296, + "heapUsed": 26011024, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 114944 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116228096, + "heapTotal": 61227008, + "heapUsed": 25892080, + "external": 5704079, + "arrayBuffers": 2070251, + "maxRssKiB": 113504 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117702656, + "heapTotal": 60964864, + "heapUsed": 25893760, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 114944 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116768768, + "heapTotal": 61227008, + "heapUsed": 25894016, + "external": 5704053, + "arrayBuffers": 2070251, + "maxRssKiB": 114032 + }, + { + "pid": 98360, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 118112256, + "heapTotal": 62537728, + "heapUsed": 27079896, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 115344 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116752384, + "heapTotal": 62537728, + "heapUsed": 26957760, + "external": 5712240, + "arrayBuffers": 2070251, + "maxRssKiB": 114016 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118095872, + "heapTotal": 62013440, + "heapUsed": 26956288, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 115328 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117473280, + "heapTotal": 62275584, + "heapUsed": 26956064, + "external": 5712214, + "arrayBuffers": 2070251, + "maxRssKiB": 114720 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117964800, + "heapTotal": 63324160, + "heapUsed": 28032920, + "external": 5704051, + "arrayBuffers": 2070251, + "maxRssKiB": 115200 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117260288, + "heapTotal": 63586304, + "heapUsed": 28032568, + "external": 5704077, + "arrayBuffers": 2070251, + "maxRssKiB": 114512 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118800384, + "heapTotal": 63324160, + "heapUsed": 28033144, + "external": 5704041, + "arrayBuffers": 2070251, + "maxRssKiB": 116016 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 117948416, + "heapTotal": 64897024, + "heapUsed": 29101816, + "external": 5711181, + "arrayBuffers": 2070251, + "maxRssKiB": 115184 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118718464, + "heapTotal": 64634880, + "heapUsed": 29103976, + "external": 5711173, + "arrayBuffers": 2070251, + "maxRssKiB": 115936 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 119570432, + "heapTotal": 64634880, + "heapUsed": 29093208, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 116768 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118603776, + "heapTotal": 65683456, + "heapUsed": 30130792, + "external": 5702989, + "arrayBuffers": 2070251, + "maxRssKiB": 115824 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119357440, + "heapTotal": 65945600, + "heapUsed": 30132872, + "external": 5702981, + "arrayBuffers": 2070251, + "maxRssKiB": 116560 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 120274944, + "heapTotal": 65945600, + "heapUsed": 30123408, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 117456 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120176640, + "heapTotal": 66994176, + "heapUsed": 31157920, + "external": 5711017, + "arrayBuffers": 2070251, + "maxRssKiB": 117360 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119390208, + "heapTotal": 66732032, + "heapUsed": 31156848, + "external": 5711025, + "arrayBuffers": 2070251, + "maxRssKiB": 116592 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 121012224, + "heapTotal": 66994176, + "heapUsed": 31147032, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 118176 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120733696, + "heapTotal": 68042752, + "heapUsed": 32179736, + "external": 5702825, + "arrayBuffers": 2070251, + "maxRssKiB": 117904 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120111104, + "heapTotal": 68042752, + "heapUsed": 32179080, + "external": 5702833, + "arrayBuffers": 2070251, + "maxRssKiB": 117296 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 121716736, + "heapTotal": 68304896, + "heapUsed": 32170520, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 118864 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120864768, + "heapTotal": 69091328, + "heapUsed": 33243936, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 118032 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121487360, + "heapTotal": 69353472, + "heapUsed": 33244768, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 118640 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122535936, + "heapTotal": 69353472, + "heapUsed": 33248992, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 119664 + }, + { + "pid": 98363, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121569280, + "heapTotal": 70402048, + "heapUsed": 34279040, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 118720 + }, + { + "pid": 98362, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122208256, + "heapTotal": 70664192, + "heapUsed": 34278288, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 119344 + }, + { + "pid": 98361, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123224064, + "heapTotal": 70664192, + "heapUsed": 34279128, + "external": 5711014, + "arrayBuffers": 2070251, + "maxRssKiB": 120336 + } + ] + }, + { + "id": "12-measured-8-session", + "phase": "measured", + "mode": "session", + "workers": 8, + "wallSeconds": 2.3549656250000117, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 98536, + "build": 1, + "rootDir": ".cache/integration/fixture-8i0Bxp", + "cwd": ".cache/integration/session-19bcmp", + "rss": 315310080, + "heapUsed": 71150280, + "maxRssKiB": 308048 + }, + { + "pid": 98537, + "build": 1, + "rootDir": ".cache/integration/fixture-NrAPo9", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 341082112, + "heapUsed": 71201136, + "maxRssKiB": 333216 + }, + { + "pid": 98538, + "build": 1, + "rootDir": ".cache/integration/fixture-M9cJtZ", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 309329920, + "heapUsed": 71146056, + "maxRssKiB": 302208 + }, + { + "pid": 98538, + "build": 2, + "rootDir": ".cache/integration/fixture-X009X3", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 322224128, + "heapUsed": 72834496, + "maxRssKiB": 314800 + }, + { + "pid": 98536, + "build": 2, + "rootDir": ".cache/integration/fixture-g9M1vD", + "cwd": ".cache/integration/session-19bcmp", + "rss": 328220672, + "heapUsed": 72841920, + "maxRssKiB": 320656 + }, + { + "pid": 98537, + "build": 2, + "rootDir": ".cache/integration/fixture-Ma93Mn", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 354598912, + "heapUsed": 72808160, + "maxRssKiB": 346416 + }, + { + "pid": 98537, + "build": 3, + "rootDir": ".cache/integration/fixture-CT9Tyh", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 363790336, + "heapUsed": 74451856, + "maxRssKiB": 355392 + }, + { + "pid": 98538, + "build": 3, + "rootDir": ".cache/integration/fixture-RCVRKR", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 333889536, + "heapUsed": 74466888, + "maxRssKiB": 326192 + }, + { + "pid": 98536, + "build": 3, + "rootDir": ".cache/integration/fixture-r6JvSX", + "cwd": ".cache/integration/session-19bcmp", + "rss": 340295680, + "heapUsed": 74476000, + "maxRssKiB": 332448 + }, + { + "pid": 98536, + "build": 4, + "rootDir": ".cache/integration/fixture-R9YTqH", + "cwd": ".cache/integration/session-19bcmp", + "rss": 349569024, + "heapUsed": 76026136, + "maxRssKiB": 341504 + }, + { + "pid": 98538, + "build": 4, + "rootDir": ".cache/integration/fixture-Jo9bEm", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 344506368, + "heapUsed": 76020496, + "maxRssKiB": 336560 + }, + { + "pid": 98537, + "build": 4, + "rootDir": ".cache/integration/fixture-QyE4ag", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 372244480, + "heapUsed": 76002200, + "maxRssKiB": 363648 + }, + { + "pid": 98536, + "build": 5, + "rootDir": ".cache/integration/fixture-DnKXky", + "cwd": ".cache/integration/session-19bcmp", + "rss": 350961664, + "heapUsed": 76594232, + "maxRssKiB": 342864 + }, + { + "pid": 98538, + "build": 5, + "rootDir": ".cache/integration/fixture-yISub7", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 345899008, + "heapUsed": 76588304, + "maxRssKiB": 337920 + }, + { + "pid": 98537, + "build": 5, + "rootDir": ".cache/integration/fixture-Za58tJ", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 382255104, + "heapUsed": 77600544, + "maxRssKiB": 373424 + }, + { + "pid": 98538, + "build": 6, + "rootDir": ".cache/integration/fixture-C7QomN", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 347144192, + "heapUsed": 77125584, + "maxRssKiB": 339136 + }, + { + "pid": 98536, + "build": 6, + "rootDir": ".cache/integration/fixture-pkfQmi", + "cwd": ".cache/integration/session-19bcmp", + "rss": 352043008, + "heapUsed": 77131528, + "maxRssKiB": 343920 + }, + { + "pid": 98537, + "build": 6, + "rootDir": ".cache/integration/fixture-98vSUF", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 389480448, + "heapUsed": 79174760, + "maxRssKiB": 380480 + }, + { + "pid": 98536, + "build": 7, + "rootDir": ".cache/integration/fixture-v4gbLX", + "cwd": ".cache/integration/session-19bcmp", + "rss": 353222656, + "heapUsed": 77588472, + "maxRssKiB": 345072 + }, + { + "pid": 98538, + "build": 7, + "rootDir": ".cache/integration/fixture-RfwnmA", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 348356608, + "heapUsed": 77574656, + "maxRssKiB": 340320 + }, + { + "pid": 98536, + "build": 8, + "rootDir": ".cache/integration/fixture-gnvBNu", + "cwd": ".cache/integration/session-19bcmp", + "rss": 360022016, + "heapUsed": 78123616, + "maxRssKiB": 351712 + }, + { + "pid": 98538, + "build": 8, + "rootDir": ".cache/integration/fixture-NIcMDT", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 355139584, + "heapUsed": 78115896, + "maxRssKiB": 346944 + }, + { + "pid": 98537, + "build": 7, + "rootDir": ".cache/integration/fixture-TThHEf", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 399687680, + "heapUsed": 80664640, + "maxRssKiB": 390448 + }, + { + "pid": 98538, + "build": 9, + "rootDir": ".cache/integration/fixture-B3Z495", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 356548608, + "heapUsed": 78816048, + "maxRssKiB": 348320 + }, + { + "pid": 98536, + "build": 9, + "rootDir": ".cache/integration/fixture-1mk5Cq", + "cwd": ".cache/integration/session-19bcmp", + "rss": 361463808, + "heapUsed": 78822504, + "maxRssKiB": 353120 + }, + { + "pid": 98537, + "build": 8, + "rootDir": ".cache/integration/fixture-vZmIAz", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 408633344, + "heapUsed": 82243064, + "maxRssKiB": 399184 + }, + { + "pid": 98538, + "build": 10, + "rootDir": ".cache/integration/fixture-CapMOC", + "cwd": ".cache/integration/session-kNMPGt", + "rss": 357941248, + "heapUsed": 79422120, + "maxRssKiB": 349680 + }, + { + "pid": 98536, + "build": 10, + "rootDir": ".cache/integration/fixture-QFwP1g", + "cwd": ".cache/integration/session-19bcmp", + "rss": 362807296, + "heapUsed": 79429120, + "maxRssKiB": 354432 + }, + { + "pid": 98537, + "build": 9, + "rootDir": ".cache/integration/fixture-jbjzeX", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 410025984, + "heapUsed": 82951752, + "maxRssKiB": 400544 + }, + { + "pid": 98537, + "build": 10, + "rootDir": ".cache/integration/fixture-E3kp1e", + "cwd": ".cache/integration/session-Qmag3W", + "rss": 411353088, + "heapUsed": 83543336, + "maxRssKiB": 401840 + } + ], + "parentSamples": [ + { + "pid": 98531, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115621888, + "heapTotal": 60964864, + "heapUsed": 24958928, + "external": 5723168, + "arrayBuffers": 2062059, + "maxRssKiB": 113040 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 117014528, + "heapTotal": 60440576, + "heapUsed": 24856152, + "external": 5723132, + "arrayBuffers": 2062066, + "maxRssKiB": 114464 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116015104, + "heapTotal": 60964864, + "heapUsed": 24852728, + "external": 5723124, + "arrayBuffers": 2062066, + "maxRssKiB": 113504 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115818496, + "heapTotal": 60702720, + "heapUsed": 24852360, + "external": 5723154, + "arrayBuffers": 2062066, + "maxRssKiB": 113312 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117669888, + "heapTotal": 61227008, + "heapUsed": 25879528, + "external": 5703744, + "arrayBuffers": 2070258, + "maxRssKiB": 114912 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116801536, + "heapTotal": 61751296, + "heapUsed": 25880784, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 114064 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116244480, + "heapTotal": 61227008, + "heapUsed": 25880216, + "external": 5703764, + "arrayBuffers": 2081740, + "maxRssKiB": 113520 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118259712, + "heapTotal": 62275584, + "heapUsed": 26938776, + "external": 5703713, + "arrayBuffers": 2070258, + "maxRssKiB": 115488 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117456896, + "heapTotal": 63324160, + "heapUsed": 26941480, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 114704 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116867072, + "heapTotal": 62275584, + "heapUsed": 26938576, + "external": 5703733, + "arrayBuffers": 2070258, + "maxRssKiB": 114128 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 119029760, + "heapTotal": 63586304, + "heapUsed": 28001304, + "external": 5711934, + "arrayBuffers": 2070258, + "maxRssKiB": 116240 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117473280, + "heapTotal": 63324160, + "heapUsed": 28002408, + "external": 5711954, + "arrayBuffers": 2070258, + "maxRssKiB": 114720 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117981184, + "heapTotal": 63848448, + "heapUsed": 28001744, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 115216 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118226944, + "heapTotal": 64634880, + "heapUsed": 29060168, + "external": 5703099, + "arrayBuffers": 2070258, + "maxRssKiB": 115456 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119652352, + "heapTotal": 64634880, + "heapUsed": 29058792, + "external": 5703091, + "arrayBuffers": 2070258, + "maxRssKiB": 116848 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118636544, + "heapTotal": 65159168, + "heapUsed": 29050896, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 115856 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 120258560, + "heapTotal": 65421312, + "heapUsed": 30088752, + "external": 5711283, + "arrayBuffers": 2070258, + "maxRssKiB": 117440 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118931456, + "heapTotal": 65945600, + "heapUsed": 30088160, + "external": 5711291, + "arrayBuffers": 2070258, + "maxRssKiB": 116144 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119373824, + "heapTotal": 66207744, + "heapUsed": 30079040, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 116576 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119570432, + "heapTotal": 66994176, + "heapUsed": 31110688, + "external": 5702943, + "arrayBuffers": 2070258, + "maxRssKiB": 116768 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120930304, + "heapTotal": 66732032, + "heapUsed": 31112368, + "external": 5702935, + "arrayBuffers": 2070258, + "maxRssKiB": 118096 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120373248, + "heapTotal": 68042752, + "heapUsed": 32134056, + "external": 5711135, + "arrayBuffers": 2070258, + "maxRssKiB": 117552 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121569280, + "heapTotal": 67780608, + "heapUsed": 32132456, + "external": 5711127, + "arrayBuffers": 2070258, + "maxRssKiB": 118720 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 120160256, + "heapTotal": 67518464, + "heapUsed": 31103000, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 117344 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120946688, + "heapTotal": 69091328, + "heapUsed": 33189960, + "external": 5702942, + "arrayBuffers": 2075118, + "maxRssKiB": 118112 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122306560, + "heapTotal": 69091328, + "heapUsed": 33189304, + "external": 5702934, + "arrayBuffers": 2080910, + "maxRssKiB": 119440 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120766464, + "heapTotal": 68567040, + "heapUsed": 32125040, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 117936 + }, + { + "pid": 98530, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121651200, + "heapTotal": 70402048, + "heapUsed": 34221072, + "external": 5711136, + "arrayBuffers": 2070258, + "maxRssKiB": 118800 + }, + { + "pid": 98529, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123027456, + "heapTotal": 70139904, + "heapUsed": 34221856, + "external": 5711128, + "arrayBuffers": 2070258, + "maxRssKiB": 120144 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121454592, + "heapTotal": 69615616, + "heapUsed": 33193104, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 118608 + }, + { + "pid": 98528, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122126336, + "heapTotal": 70926336, + "heapUsed": 34224920, + "external": 5711126, + "arrayBuffers": 2070258, + "maxRssKiB": 119264 + }, + { + "pid": 98531, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116326400, + "heapTotal": 61489152, + "heapUsed": 25989088, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 113600 + }, + { + "pid": 98531, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 116899840, + "heapTotal": 62799872, + "heapUsed": 27062456, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114160 + } + ] + }, + { + "id": "13-measured-8-session", + "phase": "measured", + "mode": "session", + "workers": 8, + "wallSeconds": 2.3692148329999907, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 98592, + "build": 1, + "rootDir": ".cache/integration/fixture-eb9Joj", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 341000192, + "heapUsed": 71202368, + "maxRssKiB": 333136 + }, + { + "pid": 98594, + "build": 1, + "rootDir": ".cache/integration/fixture-Qhu0kq", + "cwd": ".cache/integration/session-jP19hT", + "rss": 344358912, + "heapUsed": 71202080, + "maxRssKiB": 336416 + }, + { + "pid": 98593, + "build": 1, + "rootDir": ".cache/integration/fixture-w8GJlf", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 344850432, + "heapUsed": 71203320, + "maxRssKiB": 336896 + }, + { + "pid": 98592, + "build": 2, + "rootDir": ".cache/integration/fixture-kFaG9c", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 352911360, + "heapUsed": 72812248, + "maxRssKiB": 344768 + }, + { + "pid": 98594, + "build": 2, + "rootDir": ".cache/integration/fixture-ZRpSQK", + "cwd": ".cache/integration/session-jP19hT", + "rss": 356139008, + "heapUsed": 72813408, + "maxRssKiB": 347920 + }, + { + "pid": 98593, + "build": 2, + "rootDir": ".cache/integration/fixture-tw5XPc", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 357400576, + "heapUsed": 72815592, + "maxRssKiB": 349152 + }, + { + "pid": 98593, + "build": 3, + "rootDir": ".cache/integration/fixture-KeLud8", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 368082944, + "heapUsed": 74454072, + "maxRssKiB": 359584 + }, + { + "pid": 98594, + "build": 3, + "rootDir": ".cache/integration/fixture-DSb7lQ", + "cwd": ".cache/integration/session-jP19hT", + "rss": 366264320, + "heapUsed": 74448560, + "maxRssKiB": 357808 + }, + { + "pid": 98592, + "build": 3, + "rootDir": ".cache/integration/fixture-14OHZt", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 361938944, + "heapUsed": 74450992, + "maxRssKiB": 353584 + }, + { + "pid": 98592, + "build": 4, + "rootDir": ".cache/integration/fixture-wAGxml", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 371507200, + "heapUsed": 76000456, + "maxRssKiB": 362928 + }, + { + "pid": 98593, + "build": 4, + "rootDir": ".cache/integration/fixture-kEJqUW", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 376635392, + "heapUsed": 76003304, + "maxRssKiB": 367936 + }, + { + "pid": 98594, + "build": 4, + "rootDir": ".cache/integration/fixture-EDnQdg", + "cwd": ".cache/integration/session-jP19hT", + "rss": 373964800, + "heapUsed": 76000800, + "maxRssKiB": 365328 + }, + { + "pid": 98592, + "build": 5, + "rootDir": ".cache/integration/fixture-Kz6Cs0", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 372342784, + "heapUsed": 76563136, + "maxRssKiB": 363744 + }, + { + "pid": 98594, + "build": 5, + "rootDir": ".cache/integration/fixture-4uc3Id", + "cwd": ".cache/integration/session-jP19hT", + "rss": 374767616, + "heapUsed": 76562896, + "maxRssKiB": 366112 + }, + { + "pid": 98593, + "build": 5, + "rootDir": ".cache/integration/fixture-ktPuEe", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 384827392, + "heapUsed": 77602808, + "maxRssKiB": 375936 + }, + { + "pid": 98592, + "build": 6, + "rootDir": ".cache/integration/fixture-nzMEc3", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 373112832, + "heapUsed": 77102232, + "maxRssKiB": 364496 + }, + { + "pid": 98594, + "build": 6, + "rootDir": ".cache/integration/fixture-q0MWhB", + "cwd": ".cache/integration/session-jP19hT", + "rss": 375341056, + "heapUsed": 77098720, + "maxRssKiB": 366672 + }, + { + "pid": 98593, + "build": 6, + "rootDir": ".cache/integration/fixture-eTJ4S8", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 392445952, + "heapUsed": 79177992, + "maxRssKiB": 383376 + }, + { + "pid": 98594, + "build": 7, + "rootDir": ".cache/integration/fixture-FZWvCW", + "cwd": ".cache/integration/session-jP19hT", + "rss": 376045568, + "heapUsed": 77551344, + "maxRssKiB": 367360 + }, + { + "pid": 98592, + "build": 7, + "rootDir": ".cache/integration/fixture-Bro9UR", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 373915648, + "heapUsed": 77560768, + "maxRssKiB": 365280 + }, + { + "pid": 98594, + "build": 8, + "rootDir": ".cache/integration/fixture-QVKPEg", + "cwd": ".cache/integration/session-jP19hT", + "rss": 376668160, + "heapUsed": 78089184, + "maxRssKiB": 367968 + }, + { + "pid": 98592, + "build": 8, + "rootDir": ".cache/integration/fixture-P7yyjb", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 374554624, + "heapUsed": 78092072, + "maxRssKiB": 365904 + }, + { + "pid": 98593, + "build": 7, + "rootDir": ".cache/integration/fixture-8L41iG", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 400588800, + "heapUsed": 80667728, + "maxRssKiB": 391328 + }, + { + "pid": 98594, + "build": 9, + "rootDir": ".cache/integration/fixture-3V3Pz9", + "cwd": ".cache/integration/session-jP19hT", + "rss": 377274368, + "heapUsed": 78789576, + "maxRssKiB": 368560 + }, + { + "pid": 98592, + "build": 9, + "rootDir": ".cache/integration/fixture-TvCMjJ", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 375357440, + "heapUsed": 78791344, + "maxRssKiB": 366688 + }, + { + "pid": 98593, + "build": 8, + "rootDir": ".cache/integration/fixture-kHFCHu", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 409780224, + "heapUsed": 82246192, + "maxRssKiB": 400304 + }, + { + "pid": 98594, + "build": 10, + "rootDir": ".cache/integration/fixture-Oh4Q4j", + "cwd": ".cache/integration/session-jP19hT", + "rss": 378011648, + "heapUsed": 79395728, + "maxRssKiB": 369280 + }, + { + "pid": 98592, + "build": 10, + "rootDir": ".cache/integration/fixture-SOKtM2", + "cwd": ".cache/integration/session-cW6NQU", + "rss": 376209408, + "heapUsed": 79396512, + "maxRssKiB": 367520 + }, + { + "pid": 98593, + "build": 9, + "rootDir": ".cache/integration/fixture-WrATOi", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 411271168, + "heapUsed": 82956024, + "maxRssKiB": 401760 + }, + { + "pid": 98593, + "build": 10, + "rootDir": ".cache/integration/fixture-AW2ZtN", + "cwd": ".cache/integration/session-jq1sxt", + "rss": 412499968, + "heapUsed": 83557800, + "maxRssKiB": 402960 + } + ], + "parentSamples": [ + { + "pid": 98582, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115343360, + "heapTotal": 61227008, + "heapUsed": 24957072, + "external": 5723169, + "arrayBuffers": 2062059, + "maxRssKiB": 112768 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115539968, + "heapTotal": 60964864, + "heapUsed": 24852056, + "external": 5723155, + "arrayBuffers": 2062066, + "maxRssKiB": 112960 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115621888, + "heapTotal": 60964864, + "heapUsed": 24852696, + "external": 5723133, + "arrayBuffers": 2062066, + "maxRssKiB": 113040 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115572736, + "heapTotal": 60702720, + "heapUsed": 24851896, + "external": 5723125, + "arrayBuffers": 2062066, + "maxRssKiB": 113088 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116457472, + "heapTotal": 61489152, + "heapUsed": 25882880, + "external": 5703764, + "arrayBuffers": 2070258, + "maxRssKiB": 113728 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116146176, + "heapTotal": 61227008, + "heapUsed": 25880992, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 113424 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116441088, + "heapTotal": 61489152, + "heapUsed": 25879016, + "external": 5703744, + "arrayBuffers": 2070258, + "maxRssKiB": 113712 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116965376, + "heapTotal": 62275584, + "heapUsed": 26938496, + "external": 5703713, + "arrayBuffers": 2070258, + "maxRssKiB": 114240 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117276672, + "heapTotal": 63062016, + "heapUsed": 26938536, + "external": 5703733, + "arrayBuffers": 2070258, + "maxRssKiB": 114528 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116621312, + "heapTotal": 62537728, + "heapUsed": 26938616, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 113888 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117276672, + "heapTotal": 63586304, + "heapUsed": 28000752, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 114528 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117915648, + "heapTotal": 63848448, + "heapUsed": 28001200, + "external": 5711954, + "arrayBuffers": 2070258, + "maxRssKiB": 115152 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117620736, + "heapTotal": 63586304, + "heapUsed": 28002200, + "external": 5711934, + "arrayBuffers": 2070258, + "maxRssKiB": 114864 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118669312, + "heapTotal": 64897024, + "heapUsed": 29057392, + "external": 5703099, + "arrayBuffers": 2070258, + "maxRssKiB": 115888 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118292480, + "heapTotal": 64897024, + "heapUsed": 29057032, + "external": 5703091, + "arrayBuffers": 2070258, + "maxRssKiB": 115520 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118112256, + "heapTotal": 64634880, + "heapUsed": 29051024, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 115344 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119324672, + "heapTotal": 66207744, + "heapUsed": 30089080, + "external": 5711291, + "arrayBuffers": 2070258, + "maxRssKiB": 116528 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118980608, + "heapTotal": 65945600, + "heapUsed": 30086800, + "external": 5711283, + "arrayBuffers": 2089259, + "maxRssKiB": 116192 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 118784000, + "heapTotal": 65945600, + "heapUsed": 30079408, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 116000 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119734272, + "heapTotal": 66732032, + "heapUsed": 31109136, + "external": 5702935, + "arrayBuffers": 2070258, + "maxRssKiB": 116928 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120094720, + "heapTotal": 67256320, + "heapUsed": 31110744, + "external": 5702943, + "arrayBuffers": 2070258, + "maxRssKiB": 117280 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120455168, + "heapTotal": 67780608, + "heapUsed": 32133496, + "external": 5711127, + "arrayBuffers": 2070258, + "maxRssKiB": 117632 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120864768, + "heapTotal": 68567040, + "heapUsed": 32134432, + "external": 5711135, + "arrayBuffers": 2070258, + "maxRssKiB": 118032 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119537664, + "heapTotal": 67256320, + "heapUsed": 31102040, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 116736 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121208832, + "heapTotal": 69091328, + "heapUsed": 33189464, + "external": 5702934, + "arrayBuffers": 2070258, + "maxRssKiB": 118368 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121683968, + "heapTotal": 69877760, + "heapUsed": 33188992, + "external": 5702942, + "arrayBuffers": 2070258, + "maxRssKiB": 118832 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120160256, + "heapTotal": 68304896, + "heapUsed": 32124848, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 117360 + }, + { + "pid": 98584, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121946112, + "heapTotal": 70402048, + "heapUsed": 34221040, + "external": 5711128, + "arrayBuffers": 2070258, + "maxRssKiB": 119088 + }, + { + "pid": 98585, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122355712, + "heapTotal": 70926336, + "heapUsed": 34221128, + "external": 5711136, + "arrayBuffers": 2070258, + "maxRssKiB": 119488 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120963072, + "heapTotal": 69353472, + "heapUsed": 33194064, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 118128 + }, + { + "pid": 98582, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 115998720, + "heapTotal": 61751296, + "heapUsed": 26013536, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 113280 + }, + { + "pid": 98583, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121733120, + "heapTotal": 70664192, + "heapUsed": 34224856, + "external": 5711126, + "arrayBuffers": 2070258, + "maxRssKiB": 118880 + }, + { + "pid": 98582, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 116654080, + "heapTotal": 63062016, + "heapUsed": 27080008, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 113920 + } + ] + }, + { + "id": "14-measured-8-cli", + "phase": "measured", + "mode": "cli", + "workers": 8, + "wallSeconds": 7.139863834000003, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 98699, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116752384, + "heapTotal": 60440576, + "heapUsed": 24960784, + "external": 5723165, + "arrayBuffers": 2062059, + "maxRssKiB": 114288 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 117342208, + "heapTotal": 60178432, + "heapUsed": 24862648, + "external": 5722823, + "arrayBuffers": 2062059, + "maxRssKiB": 114832 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115720192, + "heapTotal": 60440576, + "heapUsed": 24860664, + "external": 5722812, + "arrayBuffers": 2062059, + "maxRssKiB": 113136 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116195328, + "heapTotal": 60702720, + "heapUsed": 24859960, + "external": 5722851, + "arrayBuffers": 2062059, + "maxRssKiB": 113664 + }, + { + "pid": 98699, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 117309440, + "heapTotal": 60964864, + "heapUsed": 25989584, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 114560 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116391936, + "heapTotal": 61227008, + "heapUsed": 25894120, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 113664 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 118079488, + "heapTotal": 61227008, + "heapUsed": 25894080, + "external": 5704053, + "arrayBuffers": 2070251, + "maxRssKiB": 115312 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116801536, + "heapTotal": 61751296, + "heapUsed": 25893680, + "external": 5704079, + "arrayBuffers": 2070251, + "maxRssKiB": 114064 + }, + { + "pid": 98699, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117882880, + "heapTotal": 62537728, + "heapUsed": 27061560, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 115120 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117424128, + "heapTotal": 62275584, + "heapUsed": 26955792, + "external": 5712240, + "arrayBuffers": 2070251, + "maxRssKiB": 114672 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118439936, + "heapTotal": 62013440, + "heapUsed": 26956160, + "external": 5712214, + "arrayBuffers": 2070251, + "maxRssKiB": 115664 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117129216, + "heapTotal": 62537728, + "heapUsed": 26957400, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 114384 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117784576, + "heapTotal": 63586304, + "heapUsed": 28033064, + "external": 5704041, + "arrayBuffers": 2082017, + "maxRssKiB": 115024 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 119128064, + "heapTotal": 63324160, + "heapUsed": 28033704, + "external": 5704051, + "arrayBuffers": 2082027, + "maxRssKiB": 116336 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118194176, + "heapTotal": 63586304, + "heapUsed": 28033264, + "external": 5704077, + "arrayBuffers": 2070251, + "maxRssKiB": 115424 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118964224, + "heapTotal": 64634880, + "heapUsed": 29103216, + "external": 5711181, + "arrayBuffers": 2070251, + "maxRssKiB": 116176 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118439936, + "heapTotal": 64897024, + "heapUsed": 29093280, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 115664 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119947264, + "heapTotal": 64634880, + "heapUsed": 29104040, + "external": 5711173, + "arrayBuffers": 2070251, + "maxRssKiB": 117136 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119668736, + "heapTotal": 65945600, + "heapUsed": 30133168, + "external": 5702989, + "arrayBuffers": 2070251, + "maxRssKiB": 116864 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 120586240, + "heapTotal": 65683456, + "heapUsed": 30132824, + "external": 5702981, + "arrayBuffers": 2070251, + "maxRssKiB": 117760 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119160832, + "heapTotal": 66207744, + "heapUsed": 30123352, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 116368 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 120438784, + "heapTotal": 66994176, + "heapUsed": 31157368, + "external": 5711025, + "arrayBuffers": 2070251, + "maxRssKiB": 117616 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 121339904, + "heapTotal": 66994176, + "heapUsed": 31156736, + "external": 5711017, + "arrayBuffers": 2070251, + "maxRssKiB": 118496 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119996416, + "heapTotal": 67256320, + "heapUsed": 31147792, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 117184 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 122093568, + "heapTotal": 68042752, + "heapUsed": 32179144, + "external": 5702825, + "arrayBuffers": 2070251, + "maxRssKiB": 119232 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121176064, + "heapTotal": 68304896, + "heapUsed": 32178672, + "external": 5702833, + "arrayBuffers": 2070251, + "maxRssKiB": 118336 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120537088, + "heapTotal": 68567040, + "heapUsed": 32170944, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 117712 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122781696, + "heapTotal": 69353472, + "heapUsed": 33243344, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 119904 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121962496, + "heapTotal": 69353472, + "heapUsed": 33243592, + "external": 5711023, + "arrayBuffers": 2070251, + "maxRssKiB": 119104 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121290752, + "heapTotal": 69615616, + "heapUsed": 33248584, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 118448 + }, + { + "pid": 98702, + "worker": "4", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122699776, + "heapTotal": 70664192, + "heapUsed": 34278632, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 119824 + }, + { + "pid": 98701, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123404288, + "heapTotal": 70139904, + "heapUsed": 34278304, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 120512 + }, + { + "pid": 98700, + "worker": "2", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122011648, + "heapTotal": 70664192, + "heapUsed": 34279456, + "external": 5711014, + "arrayBuffers": 2088990, + "maxRssKiB": 119152 + } + ] + }, + { + "id": "15-measured-8-cli", + "phase": "measured", + "mode": "cli", + "workers": 8, + "wallSeconds": 6.823447874999998, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 98879, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 115638272, + "heapTotal": 61489152, + "heapUsed": 24955776, + "external": 5723164, + "arrayBuffers": 2062059, + "maxRssKiB": 113056 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115900416, + "heapTotal": 60702720, + "heapUsed": 24860032, + "external": 5722822, + "arrayBuffers": 2062059, + "maxRssKiB": 113312 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115589120, + "heapTotal": 60178432, + "heapUsed": 24860592, + "external": 5722850, + "arrayBuffers": 2062059, + "maxRssKiB": 113104 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115802112, + "heapTotal": 60440576, + "heapUsed": 24859200, + "external": 5722811, + "arrayBuffers": 2062059, + "maxRssKiB": 113344 + }, + { + "pid": 98879, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 116604928, + "heapTotal": 62013440, + "heapUsed": 25986152, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 113872 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116260864, + "heapTotal": 61227008, + "heapUsed": 25894240, + "external": 5704079, + "arrayBuffers": 2070251, + "maxRssKiB": 113536 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116523008, + "heapTotal": 61489152, + "heapUsed": 25894208, + "external": 5704043, + "arrayBuffers": 2070251, + "maxRssKiB": 113792 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116555776, + "heapTotal": 61227008, + "heapUsed": 25894216, + "external": 5704053, + "arrayBuffers": 2070251, + "maxRssKiB": 113824 + }, + { + "pid": 98879, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117194752, + "heapTotal": 62799872, + "heapUsed": 27061128, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114448 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117080064, + "heapTotal": 62537728, + "heapUsed": 26956320, + "external": 5712240, + "arrayBuffers": 2070251, + "maxRssKiB": 114336 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117112832, + "heapTotal": 62537728, + "heapUsed": 26956416, + "external": 5712204, + "arrayBuffers": 2070251, + "maxRssKiB": 114368 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117145600, + "heapTotal": 62275584, + "heapUsed": 26956184, + "external": 5712214, + "arrayBuffers": 2070251, + "maxRssKiB": 114400 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117719040, + "heapTotal": 63848448, + "heapUsed": 28034728, + "external": 5704077, + "arrayBuffers": 2070251, + "maxRssKiB": 114960 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117833728, + "heapTotal": 63586304, + "heapUsed": 28035344, + "external": 5704051, + "arrayBuffers": 2070251, + "maxRssKiB": 115072 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117751808, + "heapTotal": 63324160, + "heapUsed": 28033096, + "external": 5704041, + "arrayBuffers": 2070251, + "maxRssKiB": 114992 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118571008, + "heapTotal": 64634880, + "heapUsed": 29103616, + "external": 5711173, + "arrayBuffers": 2070251, + "maxRssKiB": 115792 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118276096, + "heapTotal": 64634880, + "heapUsed": 29101832, + "external": 5711181, + "arrayBuffers": 2070251, + "maxRssKiB": 115504 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118505472, + "heapTotal": 64897024, + "heapUsed": 29095608, + "external": 5712362, + "arrayBuffers": 2070251, + "maxRssKiB": 115728 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119144448, + "heapTotal": 65945600, + "heapUsed": 30132496, + "external": 5702981, + "arrayBuffers": 2070251, + "maxRssKiB": 116352 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 118996992, + "heapTotal": 65945600, + "heapUsed": 30130904, + "external": 5702989, + "arrayBuffers": 2070251, + "maxRssKiB": 116208 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 119226368, + "heapTotal": 66207744, + "heapUsed": 30123360, + "external": 5712391, + "arrayBuffers": 2070251, + "maxRssKiB": 116432 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119619584, + "heapTotal": 66994176, + "heapUsed": 31156752, + "external": 5711025, + "arrayBuffers": 2070251, + "maxRssKiB": 116816 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119865344, + "heapTotal": 66994176, + "heapUsed": 31156968, + "external": 5711017, + "arrayBuffers": 2070251, + "maxRssKiB": 117056 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119898112, + "heapTotal": 67256320, + "heapUsed": 31146696, + "external": 5704014, + "arrayBuffers": 2070251, + "maxRssKiB": 117088 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120504320, + "heapTotal": 67780608, + "heapUsed": 32179408, + "external": 5702825, + "arrayBuffers": 2070251, + "maxRssKiB": 117696 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120438784, + "heapTotal": 68304896, + "heapUsed": 32179608, + "external": 5702833, + "arrayBuffers": 2070251, + "maxRssKiB": 117616 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120635392, + "heapTotal": 68304896, + "heapUsed": 32170616, + "external": 5712235, + "arrayBuffers": 2070251, + "maxRssKiB": 117808 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121176064, + "heapTotal": 69353472, + "heapUsed": 33244616, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 118336 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121061376, + "heapTotal": 69091328, + "heapUsed": 33243664, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 118224 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121389056, + "heapTotal": 69615616, + "heapUsed": 33248592, + "external": 5702822, + "arrayBuffers": 2070251, + "maxRssKiB": 118544 + }, + { + "pid": 98877, + "worker": "2", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121929728, + "heapTotal": 70402048, + "heapUsed": 34278376, + "external": 5711016, + "arrayBuffers": 2070251, + "maxRssKiB": 119072 + }, + { + "pid": 98878, + "worker": "3", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121765888, + "heapTotal": 70402048, + "heapUsed": 34279168, + "external": 5711024, + "arrayBuffers": 2070251, + "maxRssKiB": 118912 + }, + { + "pid": 98876, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122208256, + "heapTotal": 70664192, + "heapUsed": 34279832, + "external": 5711014, + "arrayBuffers": 2070251, + "maxRssKiB": 119344 + } + ] + }, + { + "id": "16-measured-8-session", + "phase": "measured", + "mode": "session", + "workers": 8, + "wallSeconds": 2.3346659999999972, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 99116, + "build": 1, + "rootDir": ".cache/integration/fixture-yntHPt", + "cwd": ".cache/integration/session-24oUfy", + "rss": 315097088, + "heapUsed": 71158960, + "maxRssKiB": 307840 + }, + { + "pid": 99117, + "build": 1, + "rootDir": ".cache/integration/fixture-y83Ywn", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 341360640, + "heapUsed": 71199736, + "maxRssKiB": 333488 + }, + { + "pid": 99118, + "build": 1, + "rootDir": ".cache/integration/fixture-daCsG3", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 344014848, + "heapUsed": 71207208, + "maxRssKiB": 336080 + }, + { + "pid": 99117, + "build": 2, + "rootDir": ".cache/integration/fixture-I9It0z", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 353370112, + "heapUsed": 72809992, + "maxRssKiB": 345216 + }, + { + "pid": 99118, + "build": 2, + "rootDir": ".cache/integration/fixture-W0Fir8", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 356679680, + "heapUsed": 72816928, + "maxRssKiB": 348448 + }, + { + "pid": 99116, + "build": 2, + "rootDir": ".cache/integration/fixture-HcWoEQ", + "cwd": ".cache/integration/session-24oUfy", + "rss": 328974336, + "heapUsed": 72854648, + "maxRssKiB": 321392 + }, + { + "pid": 99118, + "build": 3, + "rootDir": ".cache/integration/fixture-XIkOTT", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 366313472, + "heapUsed": 74456008, + "maxRssKiB": 357856 + }, + { + "pid": 99116, + "build": 3, + "rootDir": ".cache/integration/fixture-3pUXMp", + "cwd": ".cache/integration/session-24oUfy", + "rss": 340197376, + "heapUsed": 74485800, + "maxRssKiB": 332352 + }, + { + "pid": 99117, + "build": 3, + "rootDir": ".cache/integration/fixture-ZBz8RB", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 364052480, + "heapUsed": 74448168, + "maxRssKiB": 355648 + }, + { + "pid": 99118, + "build": 4, + "rootDir": ".cache/integration/fixture-in2MoK", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 376635392, + "heapUsed": 76005208, + "maxRssKiB": 367936 + }, + { + "pid": 99116, + "build": 4, + "rootDir": ".cache/integration/fixture-TFeo9n", + "cwd": ".cache/integration/session-24oUfy", + "rss": 350552064, + "heapUsed": 76037288, + "maxRssKiB": 342464 + }, + { + "pid": 99117, + "build": 4, + "rootDir": ".cache/integration/fixture-hGbdyu", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 374112256, + "heapUsed": 76002280, + "maxRssKiB": 365472 + }, + { + "pid": 99118, + "build": 5, + "rootDir": ".cache/integration/fixture-Sf5uNT", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 377323520, + "heapUsed": 76568088, + "maxRssKiB": 368608 + }, + { + "pid": 99116, + "build": 5, + "rootDir": ".cache/integration/fixture-wHyhGp", + "cwd": ".cache/integration/session-24oUfy", + "rss": 351977472, + "heapUsed": 76605496, + "maxRssKiB": 343856 + }, + { + "pid": 99117, + "build": 5, + "rootDir": ".cache/integration/fixture-cX5YQp", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 383614976, + "heapUsed": 77599336, + "maxRssKiB": 374752 + }, + { + "pid": 99118, + "build": 6, + "rootDir": ".cache/integration/fixture-8DIxal", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 378060800, + "heapUsed": 77105816, + "maxRssKiB": 369328 + }, + { + "pid": 99116, + "build": 6, + "rootDir": ".cache/integration/fixture-TQBaZS", + "cwd": ".cache/integration/session-24oUfy", + "rss": 353075200, + "heapUsed": 77143704, + "maxRssKiB": 344928 + }, + { + "pid": 99118, + "build": 7, + "rootDir": ".cache/integration/fixture-lQHqeJ", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 378912768, + "heapUsed": 77554008, + "maxRssKiB": 370160 + }, + { + "pid": 99117, + "build": 6, + "rootDir": ".cache/integration/fixture-c70DV9", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 392200192, + "heapUsed": 79175384, + "maxRssKiB": 383136 + }, + { + "pid": 99116, + "build": 7, + "rootDir": ".cache/integration/fixture-jIRxjf", + "cwd": ".cache/integration/session-24oUfy", + "rss": 354287616, + "heapUsed": 77603216, + "maxRssKiB": 346112 + }, + { + "pid": 99118, + "build": 8, + "rootDir": ".cache/integration/fixture-739fX1", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 379600896, + "heapUsed": 78095984, + "maxRssKiB": 370832 + }, + { + "pid": 99116, + "build": 8, + "rootDir": ".cache/integration/fixture-j0AxHG", + "cwd": ".cache/integration/session-24oUfy", + "rss": 355434496, + "heapUsed": 78147888, + "maxRssKiB": 347232 + }, + { + "pid": 99117, + "build": 7, + "rootDir": ".cache/integration/fixture-IQACgq", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 400801792, + "heapUsed": 80665384, + "maxRssKiB": 391536 + }, + { + "pid": 99118, + "build": 9, + "rootDir": ".cache/integration/fixture-R6txJT", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 380321792, + "heapUsed": 78796720, + "maxRssKiB": 371536 + }, + { + "pid": 99116, + "build": 9, + "rootDir": ".cache/integration/fixture-CuzvGp", + "cwd": ".cache/integration/session-24oUfy", + "rss": 356892672, + "heapUsed": 78846056, + "maxRssKiB": 348656 + }, + { + "pid": 99117, + "build": 8, + "rootDir": ".cache/integration/fixture-1De76i", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 410894336, + "heapUsed": 82243536, + "maxRssKiB": 401392 + }, + { + "pid": 99118, + "build": 10, + "rootDir": ".cache/integration/fixture-MTlXFV", + "cwd": ".cache/integration/session-3oHbpU", + "rss": 380944384, + "heapUsed": 79401816, + "maxRssKiB": 372144 + }, + { + "pid": 99116, + "build": 10, + "rootDir": ".cache/integration/fixture-N1aej8", + "cwd": ".cache/integration/session-24oUfy", + "rss": 358301696, + "heapUsed": 79440184, + "maxRssKiB": 350032 + }, + { + "pid": 99117, + "build": 9, + "rootDir": ".cache/integration/fixture-yLY2Dt", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 412221440, + "heapUsed": 82952736, + "maxRssKiB": 402688 + }, + { + "pid": 99117, + "build": 10, + "rootDir": ".cache/integration/fixture-eCREBE", + "cwd": ".cache/integration/session-DatcZ3", + "rss": 413384704, + "heapUsed": 83543504, + "maxRssKiB": 403824 + } + ], + "parentSamples": [ + { + "pid": 99111, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 116326400, + "heapTotal": 61227008, + "heapUsed": 24979664, + "external": 5723167, + "arrayBuffers": 2062059, + "maxRssKiB": 113728 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115539968, + "heapTotal": 60964864, + "heapUsed": 24853912, + "external": 5723123, + "arrayBuffers": 2062066, + "maxRssKiB": 113056 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 115752960, + "heapTotal": 60702720, + "heapUsed": 24853024, + "external": 5723153, + "arrayBuffers": 2062066, + "maxRssKiB": 113216 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 116850688, + "heapTotal": 60440576, + "heapUsed": 24856424, + "external": 5723131, + "arrayBuffers": 2062066, + "maxRssKiB": 114336 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116097024, + "heapTotal": 61751296, + "heapUsed": 25881472, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 113376 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 117489664, + "heapTotal": 61227008, + "heapUsed": 25881408, + "external": 5703744, + "arrayBuffers": 2070258, + "maxRssKiB": 114736 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 116310016, + "heapTotal": 61227008, + "heapUsed": 25879136, + "external": 5703764, + "arrayBuffers": 2070258, + "maxRssKiB": 113584 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 117129216, + "heapTotal": 62537728, + "heapUsed": 26942456, + "external": 5703733, + "arrayBuffers": 2070258, + "maxRssKiB": 114384 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 118095872, + "heapTotal": 62799872, + "heapUsed": 26941464, + "external": 5703713, + "arrayBuffers": 2070258, + "maxRssKiB": 115328 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 116719616, + "heapTotal": 62537728, + "heapUsed": 26939400, + "external": 5703706, + "arrayBuffers": 2070258, + "maxRssKiB": 113984 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 118816768, + "heapTotal": 63586304, + "heapUsed": 28000928, + "external": 5711934, + "arrayBuffers": 2070258, + "maxRssKiB": 116032 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117866496, + "heapTotal": 63586304, + "heapUsed": 28004320, + "external": 5711954, + "arrayBuffers": 2070258, + "maxRssKiB": 115104 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 117424128, + "heapTotal": 63848448, + "heapUsed": 28006800, + "external": 5711927, + "arrayBuffers": 2070258, + "maxRssKiB": 114672 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 118571008, + "heapTotal": 64897024, + "heapUsed": 29061072, + "external": 5703099, + "arrayBuffers": 2070258, + "maxRssKiB": 115792 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 119586816, + "heapTotal": 64897024, + "heapUsed": 29059520, + "external": 5703091, + "arrayBuffers": 2070258, + "maxRssKiB": 116784 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 118161408, + "heapTotal": 64897024, + "heapUsed": 29048736, + "external": 5703864, + "arrayBuffers": 2070258, + "maxRssKiB": 115392 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 120291328, + "heapTotal": 65945600, + "heapUsed": 30088472, + "external": 5711283, + "arrayBuffers": 2070258, + "maxRssKiB": 117472 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 119291904, + "heapTotal": 66207744, + "heapUsed": 30088280, + "external": 5711291, + "arrayBuffers": 2070258, + "maxRssKiB": 116496 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 121028608, + "heapTotal": 67256320, + "heapUsed": 31111368, + "external": 5702935, + "arrayBuffers": 2070258, + "maxRssKiB": 118192 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 118849536, + "heapTotal": 66207744, + "heapUsed": 30080064, + "external": 5712085, + "arrayBuffers": 2070258, + "maxRssKiB": 116064 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 119930880, + "heapTotal": 67256320, + "heapUsed": 31110376, + "external": 5702943, + "arrayBuffers": 2070258, + "maxRssKiB": 117120 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 121815040, + "heapTotal": 68304896, + "heapUsed": 32134480, + "external": 5711127, + "arrayBuffers": 2070258, + "maxRssKiB": 118960 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 120717312, + "heapTotal": 68304896, + "heapUsed": 32133488, + "external": 5711135, + "arrayBuffers": 2070258, + "maxRssKiB": 117888 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 119619584, + "heapTotal": 67518464, + "heapUsed": 31101784, + "external": 5711900, + "arrayBuffers": 2070258, + "maxRssKiB": 116816 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 122535936, + "heapTotal": 69615616, + "heapUsed": 33189456, + "external": 5702934, + "arrayBuffers": 2070258, + "maxRssKiB": 119664 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 121421824, + "heapTotal": 69615616, + "heapUsed": 33188800, + "external": 5702942, + "arrayBuffers": 2070258, + "maxRssKiB": 118576 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 120242176, + "heapTotal": 68567040, + "heapUsed": 32125136, + "external": 5703737, + "arrayBuffers": 2070258, + "maxRssKiB": 117424 + }, + { + "pid": 99109, + "worker": "2", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 122126336, + "heapTotal": 70926336, + "heapUsed": 34220936, + "external": 5711136, + "arrayBuffers": 2070258, + "maxRssKiB": 119264 + }, + { + "pid": 99110, + "worker": "3", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 123273216, + "heapTotal": 70664192, + "heapUsed": 34221240, + "external": 5711128, + "arrayBuffers": 2070258, + "maxRssKiB": 120384 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 120897536, + "heapTotal": 69615616, + "heapUsed": 33193568, + "external": 5702932, + "arrayBuffers": 2070258, + "maxRssKiB": 118064 + }, + { + "pid": 99108, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 121683968, + "heapTotal": 70926336, + "heapUsed": 34225864, + "external": 5711126, + "arrayBuffers": 2070258, + "maxRssKiB": 118832 + }, + { + "pid": 99111, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 117161984, + "heapTotal": 61489152, + "heapUsed": 26008272, + "external": 5704362, + "arrayBuffers": 2070251, + "maxRssKiB": 114416 + }, + { + "pid": 99111, + "worker": "4", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 117719040, + "heapTotal": 62799872, + "heapUsed": 27082976, + "external": 5704902, + "arrayBuffers": 2070251, + "maxRssKiB": 114960 + } + ] + }, + { + "id": "17-handles-1-cli", + "phase": "handles", + "mode": "cli", + "workers": 1, + "wallSeconds": 21.403079625000014, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [], + "parentSamples": [ + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 169082880, + "heapTotal": 107429888, + "heapUsed": 36126856, + "external": 11884685, + "arrayBuffers": 2062056, + "maxRssKiB": 165248 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 169558016, + "heapTotal": 108412928, + "heapUsed": 37381440, + "external": 11899315, + "arrayBuffers": 2070248, + "maxRssKiB": 165584 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 169885696, + "heapTotal": 109461504, + "heapUsed": 38533648, + "external": 11899855, + "arrayBuffers": 2070248, + "maxRssKiB": 165904 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 183336960, + "heapTotal": 113180672, + "heapUsed": 40911136, + "external": 13949848, + "arrayBuffers": 2078440, + "maxRssKiB": 179168 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 183910400, + "heapTotal": 113704960, + "heapUsed": 41948200, + "external": 11915380, + "arrayBuffers": 2086632, + "maxRssKiB": 179600 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 184385536, + "heapTotal": 115015680, + "heapUsed": 42990312, + "external": 11923541, + "arrayBuffers": 2086632, + "maxRssKiB": 180064 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 184926208, + "heapTotal": 116064256, + "heapUsed": 44052464, + "external": 11915378, + "arrayBuffers": 2086632, + "maxRssKiB": 180592 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 185155584, + "heapTotal": 117112832, + "heapUsed": 45089920, + "external": 11923699, + "arrayBuffers": 2086632, + "maxRssKiB": 180816 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 185892864, + "heapTotal": 118161408, + "heapUsed": 46124040, + "external": 11923728, + "arrayBuffers": 2086632, + "maxRssKiB": 181536 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 186073088, + "heapTotal": 118947840, + "heapUsed": 47155176, + "external": 11915351, + "arrayBuffers": 2086632, + "maxRssKiB": 181712 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 187236352, + "heapTotal": 120520704, + "heapUsed": 48176600, + "external": 11923572, + "arrayBuffers": 2086632, + "maxRssKiB": 182848 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 187809792, + "heapTotal": 121307136, + "heapUsed": 49246960, + "external": 11914159, + "arrayBuffers": 2086632, + "maxRssKiB": 183408 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 188350464, + "heapTotal": 122355712, + "heapUsed": 50269168, + "external": 11922351, + "arrayBuffers": 2086632, + "maxRssKiB": 183936 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 201768960, + "heapTotal": 122634240, + "heapUsed": 41464328, + "external": 13958050, + "arrayBuffers": 2078440, + "maxRssKiB": 197616 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 201981952, + "heapTotal": 114245632, + "heapUsed": 42506568, + "external": 11915390, + "arrayBuffers": 2086632, + "maxRssKiB": 197616 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 201981952, + "heapTotal": 115294208, + "heapUsed": 43511736, + "external": 11923551, + "arrayBuffers": 2086632, + "maxRssKiB": 197616 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 202063872, + "heapTotal": 116342784, + "heapUsed": 44564328, + "external": 11915388, + "arrayBuffers": 2086632, + "maxRssKiB": 197616 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 202096640, + "heapTotal": 117653504, + "heapUsed": 45594536, + "external": 11922510, + "arrayBuffers": 2086632, + "maxRssKiB": 197616 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 202113024, + "heapTotal": 118702080, + "heapUsed": 46615472, + "external": 11914318, + "arrayBuffers": 2086632, + "maxRssKiB": 197616 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 207339520, + "heapTotal": 186859520, + "heapUsed": 47639192, + "external": 11922354, + "arrayBuffers": 2086632, + "maxRssKiB": 202480 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 207470592, + "heapTotal": 187908096, + "heapUsed": 48685344, + "external": 11914162, + "arrayBuffers": 2086632, + "maxRssKiB": 202608 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 207929344, + "heapTotal": 188956672, + "heapUsed": 49736912, + "external": 11922353, + "arrayBuffers": 2086632, + "maxRssKiB": 203056 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 208289792, + "heapTotal": 189743104, + "heapUsed": 50765352, + "external": 11922353, + "arrayBuffers": 2086632, + "maxRssKiB": 203408 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 215728128, + "heapTotal": 189743104, + "heapUsed": 41558560, + "external": 13958076, + "arrayBuffers": 2078440, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 215990272, + "heapTotal": 181616640, + "heapUsed": 42555592, + "external": 11915416, + "arrayBuffers": 2086940, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 216023040, + "heapTotal": 182665216, + "heapUsed": 43596064, + "external": 11923577, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 216072192, + "heapTotal": 183451648, + "heapUsed": 44656152, + "external": 11915414, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 216072192, + "heapTotal": 184762368, + "heapUsed": 45686632, + "external": 11922518, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 216072192, + "heapTotal": 186073088, + "heapUsed": 46711936, + "external": 11914326, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 216072192, + "heapTotal": 187383808, + "heapUsed": 47739784, + "external": 11922362, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 216104960, + "heapTotal": 188694528, + "heapUsed": 48769968, + "external": 11914170, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 216104960, + "heapTotal": 189743104, + "heapUsed": 49818208, + "external": 11922361, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + }, + { + "pid": 99123, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 216104960, + "heapTotal": 190267392, + "heapUsed": 50846936, + "external": 11922361, + "arrayBuffers": 2086632, + "maxRssKiB": 211312 + } + ] + }, + { + "id": "18-handles-1-session", + "phase": "handles", + "mode": "session", + "workers": 1, + "wallSeconds": 6.103320749999984, + "names": [ + "bookmarks override chrome MV2 build replaces the browser page", + "bookmarks override chrome MV3 build replaces the browser page", + "bookmarks override edge MV2 build replaces the browser page", + "bookmarks override edge MV3 build replaces the browser page", + "bookmarks override firefox MV2 build skips the unsupported page", + "bookmarks override firefox MV3 build skips the unsupported page", + "bookmarks override opera MV2 build skips the unsupported page", + "bookmarks override opera MV3 build skips the unsupported page", + "bookmarks override safari MV2 build skips the unsupported page", + "bookmarks override safari MV3 build skips the unsupported page", + "competing overrides chrome build fails and names both entrypoints", + "competing overrides edge build fails and names both entrypoints", + "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "history override chrome MV2 build replaces the browser page", + "history override chrome MV3 build replaces the browser page", + "history override edge MV2 build replaces the browser page", + "history override edge MV3 build replaces the browser page", + "history override firefox MV2 build skips the unsupported page", + "history override firefox MV3 build skips the unsupported page", + "history override opera MV2 build skips the unsupported page", + "history override opera MV3 build skips the unsupported page", + "history override safari MV2 build skips the unsupported page", + "history override safari MV3 build skips the unsupported page", + "newtab override chrome MV2 build replaces the browser page", + "newtab override chrome MV3 build replaces the browser page", + "newtab override edge MV2 build replaces the browser page", + "newtab override edge MV3 build replaces the browser page", + "newtab override firefox MV2 build replaces the browser page", + "newtab override firefox MV3 build replaces the browser page", + "newtab override opera MV2 build skips the unsupported page", + "newtab override opera MV3 build skips the unsupported page", + "newtab override safari MV2 build replaces the browser page", + "newtab override safari MV3 build replaces the browser page" + ], + "tests": 33, + "leftovers": 0, + "openHandles": 0, + "children": [ + { + "pid": 99666, + "build": 1, + "rootDir": ".cache/integration/fixture-BDVvw4", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 315637760, + "heapUsed": 71151088, + "maxRssKiB": 308368 + }, + { + "pid": 99666, + "build": 2, + "rootDir": ".cache/integration/fixture-WUFKTQ", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 328613888, + "heapUsed": 72847552, + "maxRssKiB": 321040 + }, + { + "pid": 99666, + "build": 3, + "rootDir": ".cache/integration/fixture-FhbYvw", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 339574784, + "heapUsed": 74477600, + "maxRssKiB": 331744 + }, + { + "pid": 99666, + "build": 4, + "rootDir": ".cache/integration/fixture-dmGMpY", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 350371840, + "heapUsed": 76029288, + "maxRssKiB": 342288 + }, + { + "pid": 99666, + "build": 5, + "rootDir": ".cache/integration/fixture-NOvCHb", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 351682560, + "heapUsed": 76596352, + "maxRssKiB": 343568 + }, + { + "pid": 99666, + "build": 6, + "rootDir": ".cache/integration/fixture-f99dmX", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 352911360, + "heapUsed": 77134600, + "maxRssKiB": 344768 + }, + { + "pid": 99666, + "build": 7, + "rootDir": ".cache/integration/fixture-NNdshK", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 354172928, + "heapUsed": 77594416, + "maxRssKiB": 346000 + }, + { + "pid": 99666, + "build": 8, + "rootDir": ".cache/integration/fixture-9rhr4S", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 360792064, + "heapUsed": 78130488, + "maxRssKiB": 352464 + }, + { + "pid": 99666, + "build": 9, + "rootDir": ".cache/integration/fixture-oETlSs", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 362250240, + "heapUsed": 78826488, + "maxRssKiB": 353888 + }, + { + "pid": 99666, + "build": 10, + "rootDir": ".cache/integration/fixture-jIarv7", + "cwd": ".cache/integration/session-6qxS3L", + "rss": 363626496, + "heapUsed": 79431960, + "maxRssKiB": 355232 + }, + { + "pid": 99667, + "build": 1, + "rootDir": ".cache/integration/fixture-kPzp6Z", + "cwd": ".cache/integration/session-we72mE", + "rss": 315408384, + "heapUsed": 71144112, + "maxRssKiB": 308144 + }, + { + "pid": 99667, + "build": 2, + "rootDir": ".cache/integration/fixture-OppZaq", + "cwd": ".cache/integration/session-we72mE", + "rss": 328646656, + "heapUsed": 72836608, + "maxRssKiB": 321072 + }, + { + "pid": 99667, + "build": 3, + "rootDir": ".cache/integration/fixture-xURRtJ", + "cwd": ".cache/integration/session-we72mE", + "rss": 339525632, + "heapUsed": 74472200, + "maxRssKiB": 331696 + }, + { + "pid": 99667, + "build": 4, + "rootDir": ".cache/integration/fixture-veYyM0", + "cwd": ".cache/integration/session-we72mE", + "rss": 350420992, + "heapUsed": 76019760, + "maxRssKiB": 342336 + }, + { + "pid": 99667, + "build": 5, + "rootDir": ".cache/integration/fixture-KPB37P", + "cwd": ".cache/integration/session-we72mE", + "rss": 360595456, + "heapUsed": 77624944, + "maxRssKiB": 352272 + }, + { + "pid": 99667, + "build": 6, + "rootDir": ".cache/integration/fixture-S8uXU9", + "cwd": ".cache/integration/session-we72mE", + "rss": 375439360, + "heapUsed": 79201744, + "maxRssKiB": 366768 + }, + { + "pid": 99667, + "build": 7, + "rootDir": ".cache/integration/fixture-MwFnMi", + "cwd": ".cache/integration/session-we72mE", + "rss": 384548864, + "heapUsed": 80676912, + "maxRssKiB": 375664 + }, + { + "pid": 99667, + "build": 8, + "rootDir": ".cache/integration/fixture-qUsqBh", + "cwd": ".cache/integration/session-we72mE", + "rss": 393723904, + "heapUsed": 82258104, + "maxRssKiB": 384624 + }, + { + "pid": 99667, + "build": 9, + "rootDir": ".cache/integration/fixture-zSQpMj", + "cwd": ".cache/integration/session-we72mE", + "rss": 395198464, + "heapUsed": 82967880, + "maxRssKiB": 386064 + }, + { + "pid": 99667, + "build": 10, + "rootDir": ".cache/integration/fixture-G6aQlU", + "cwd": ".cache/integration/session-we72mE", + "rss": 396541952, + "heapUsed": 83570720, + "maxRssKiB": 387376 + }, + { + "pid": 99737, + "build": 1, + "rootDir": ".cache/integration/fixture-1UtWxy", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 313180160, + "heapUsed": 71158648, + "maxRssKiB": 305968 + }, + { + "pid": 99737, + "build": 2, + "rootDir": ".cache/integration/fixture-oCUVA3", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 325582848, + "heapUsed": 72851696, + "maxRssKiB": 318080 + }, + { + "pid": 99737, + "build": 3, + "rootDir": ".cache/integration/fixture-f6mHj7", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 336494592, + "heapUsed": 74485800, + "maxRssKiB": 328736 + }, + { + "pid": 99737, + "build": 4, + "rootDir": ".cache/integration/fixture-Di9AjT", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 346996736, + "heapUsed": 76037176, + "maxRssKiB": 338992 + }, + { + "pid": 99737, + "build": 5, + "rootDir": ".cache/integration/fixture-yBfHCO", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 348323840, + "heapUsed": 76604504, + "maxRssKiB": 340288 + }, + { + "pid": 99737, + "build": 6, + "rootDir": ".cache/integration/fixture-ieDtLa", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 349487104, + "heapUsed": 77142472, + "maxRssKiB": 341424 + }, + { + "pid": 99737, + "build": 7, + "rootDir": ".cache/integration/fixture-7bQq0S", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 350683136, + "heapUsed": 77591512, + "maxRssKiB": 342592 + }, + { + "pid": 99737, + "build": 8, + "rootDir": ".cache/integration/fixture-WofWO2", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 351682560, + "heapUsed": 78133648, + "maxRssKiB": 343568 + }, + { + "pid": 99737, + "build": 9, + "rootDir": ".cache/integration/fixture-USrgyf", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 353206272, + "heapUsed": 78833048, + "maxRssKiB": 345056 + }, + { + "pid": 99737, + "build": 10, + "rootDir": ".cache/integration/fixture-QsyXTR", + "cwd": ".cache/integration/session-sdxa7T", + "rss": 354598912, + "heapUsed": 79439008, + "maxRssKiB": 346416 + } + ], + "parentSamples": [ + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 168493056, + "heapTotal": 106905600, + "heapUsed": 36132304, + "external": 11888695, + "arrayBuffers": 2062063, + "maxRssKiB": 164672 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 168656896, + "heapTotal": 107429888, + "heapUsed": 37188896, + "external": 11904341, + "arrayBuffers": 2070255, + "maxRssKiB": 164704 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV2 build replaces the browser page", + "gc": true, + "rss": 168787968, + "heapTotal": 108216320, + "heapUsed": 38250968, + "external": 11904310, + "arrayBuffers": 2070255, + "maxRssKiB": 164832 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override edge MV3 build replaces the browser page", + "gc": true, + "rss": 169017344, + "heapTotal": 109264896, + "heapUsed": 39298824, + "external": 11912531, + "arrayBuffers": 2070255, + "maxRssKiB": 165056 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 169738240, + "heapTotal": 110772224, + "heapUsed": 40536656, + "external": 11903676, + "arrayBuffers": 2070255, + "maxRssKiB": 165760 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 170344448, + "heapTotal": 112345088, + "heapUsed": 41572728, + "external": 11911868, + "arrayBuffers": 2070255, + "maxRssKiB": 166352 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 170917888, + "heapTotal": 113393664, + "heapUsed": 42597720, + "external": 11903520, + "arrayBuffers": 2070255, + "maxRssKiB": 166912 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 171491328, + "heapTotal": 114442240, + "heapUsed": 43635040, + "external": 11911712, + "arrayBuffers": 2070255, + "maxRssKiB": 167472 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 171868160, + "heapTotal": 115490816, + "heapUsed": 44695944, + "external": 11903519, + "arrayBuffers": 2070255, + "maxRssKiB": 167840 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/bookmarks.integration.test.ts", + "test": "bookmarks override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 172359680, + "heapTotal": 117063680, + "heapUsed": 45729120, + "external": 11911713, + "arrayBuffers": 2070255, + "maxRssKiB": 168320 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 187908096, + "heapTotal": 119750656, + "heapUsed": 41462632, + "external": 13947596, + "arrayBuffers": 2078454, + "maxRssKiB": 183632 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 188694528, + "heapTotal": 114507776, + "heapUsed": 42464168, + "external": 11920705, + "arrayBuffers": 2086646, + "maxRssKiB": 184272 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV2 build replaces the browser page", + "gc": true, + "rss": 188874752, + "heapTotal": 115294208, + "heapUsed": 43502088, + "external": 11920674, + "arrayBuffers": 2086646, + "maxRssKiB": 184448 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override edge MV3 build replaces the browser page", + "gc": true, + "rss": 188973056, + "heapTotal": 116342784, + "heapUsed": 44552216, + "external": 11928895, + "arrayBuffers": 2086646, + "maxRssKiB": 184544 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV2 build replaces the browser page", + "gc": true, + "rss": 189005824, + "heapTotal": 117391360, + "heapUsed": 45599664, + "external": 11920832, + "arrayBuffers": 2086646, + "maxRssKiB": 184576 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override firefox MV3 build replaces the browser page", + "gc": true, + "rss": 189005824, + "heapTotal": 118439936, + "heapUsed": 46626192, + "external": 11929053, + "arrayBuffers": 2086646, + "maxRssKiB": 184576 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV2 build replaces the browser page", + "gc": true, + "rss": 189005824, + "heapTotal": 119226368, + "heapUsed": 47643752, + "external": 11928868, + "arrayBuffers": 2086646, + "maxRssKiB": 184576 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override safari MV3 build replaces the browser page", + "gc": true, + "rss": 189087744, + "heapTotal": 120537088, + "heapUsed": 48667080, + "external": 11920705, + "arrayBuffers": 2086646, + "maxRssKiB": 184656 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 189136896, + "heapTotal": 121585664, + "heapUsed": 49704736, + "external": 11919900, + "arrayBuffers": 2086646, + "maxRssKiB": 184704 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/newtab.integration.test.ts", + "test": "newtab override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 189317120, + "heapTotal": 122634240, + "heapUsed": 50732408, + "external": 11928094, + "arrayBuffers": 2086646, + "maxRssKiB": 184880 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV2 build replaces the browser page", + "gc": true, + "rss": 203587584, + "heapTotal": 122109952, + "heapUsed": 41591992, + "external": 13972186, + "arrayBuffers": 2086646, + "maxRssKiB": 199392 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override chrome MV3 build replaces the browser page", + "gc": true, + "rss": 208355328, + "heapTotal": 181354496, + "heapUsed": 42616960, + "external": 11928904, + "arrayBuffers": 2094838, + "maxRssKiB": 203472 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV2 build replaces the browser page", + "gc": true, + "rss": 208453632, + "heapTotal": 181878784, + "heapUsed": 43657728, + "external": 11928873, + "arrayBuffers": 2094838, + "maxRssKiB": 203600 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override edge MV3 build replaces the browser page", + "gc": true, + "rss": 208699392, + "heapTotal": 182927360, + "heapUsed": 44705576, + "external": 11937094, + "arrayBuffers": 2094838, + "maxRssKiB": 203808 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV2 build skips the unsupported page", + "gc": true, + "rss": 208863232, + "heapTotal": 183975936, + "heapUsed": 45743168, + "external": 11928251, + "arrayBuffers": 2094838, + "maxRssKiB": 203968 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override firefox MV3 build skips the unsupported page", + "gc": true, + "rss": 209272832, + "heapTotal": 185286656, + "heapUsed": 46778336, + "external": 11936443, + "arrayBuffers": 2094838, + "maxRssKiB": 204368 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV2 build skips the unsupported page", + "gc": true, + "rss": 209633280, + "heapTotal": 186335232, + "heapUsed": 47795272, + "external": 11928095, + "arrayBuffers": 2094838, + "maxRssKiB": 204720 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override safari MV3 build skips the unsupported page", + "gc": true, + "rss": 210190336, + "heapTotal": 187121664, + "heapUsed": 48829448, + "external": 11936287, + "arrayBuffers": 2094838, + "maxRssKiB": 205264 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV2 build skips the unsupported page", + "gc": true, + "rss": 210567168, + "heapTotal": 188432384, + "heapUsed": 49877992, + "external": 11928094, + "arrayBuffers": 2094838, + "maxRssKiB": 205632 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/history.integration.test.ts", + "test": "history override opera MV3 build skips the unsupported page", + "gc": true, + "rss": 211025920, + "heapTotal": 189218816, + "heapUsed": 50906872, + "external": 11936288, + "arrayBuffers": 2094838, + "maxRssKiB": 206080 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides chrome build fails and names both entrypoints", + "gc": true, + "rss": 217792512, + "heapTotal": 189480960, + "heapUsed": 41897952, + "external": 13972220, + "arrayBuffers": 2078447, + "maxRssKiB": 213264 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides edge build fails and names both entrypoints", + "gc": true, + "rss": 217808896, + "heapTotal": 181354496, + "heapUsed": 42905240, + "external": 11921330, + "arrayBuffers": 2086639, + "maxRssKiB": 213264 + }, + { + "pid": 99625, + "worker": "1", + "file": "tests/integration/build/override/competing.integration.test.ts", + "test": "competing overrides firefox build keeps the new tab and its permission without the unsupported history page", + "gc": true, + "rss": 217808896, + "heapTotal": 182403072, + "heapUsed": 43935984, + "external": 11921870, + "arrayBuffers": 2086639, + "maxRssKiB": 213264 + } + ] + } + ] +} diff --git a/tests/benchmarks/2026-09-28-override-session.md b/tests/benchmarks/2026-09-28-override-session.md new file mode 100644 index 00000000..1e1bccc2 --- /dev/null +++ b/tests/benchmarks/2026-09-28-override-session.md @@ -0,0 +1,83 @@ +# Override build-session pilot — 2026-09-28 + +## Decision and scope + +Keep CLI as the default and expose the new pilot through `ADNBN_OVERRIDE_BUILD_MODE=session`. +The previous Jest-worker in-process backend is removed. Production `rootDir` fixes remain, but the +project-hashed virtual-directory helper and its entrypoint/offscreen connections are removed. +No native virtual-module migration, new production concurrency contract, or global worker tuning is included. + +Each of the three page files owns one child process with a private fixed cwd and ten sequential builds. +The child imports the built package once and receives absolute fixture roots. The parent never changes its +cwd, native environment, or module loader. All three competing-entrypoint scenarios still invoke the CLI. +Each request has a parent-owned deadline; a timeout kills the child and rejects only after `close`, without +retrying. Errors preserve their message, cause chain and compiler diagnostics. Normal disposal sends a close +request and waits for process/stdio closure before removing the session directory. IPC disconnect exits the +child even if a build is pending. A hard owner crash can leave files for later cleanup, but not a cooperative +build process waiting indefinitely for its owner. + +The shared-cwd conflict in the external virtual-module plugin remains a documented limitation. Reproduce it +with `node tests/benchmarks/virtual-module-conflict.mjs` after building the package. The diagnostic controls +both compilers with IPC and expects the second to fail after the first removes its shared virtual files. + +## Measurements + +Apple M3 Max, 16 logical CPUs, macOS arm64, Node 24.5.0, Rspack 1.7.11. Fresh package build before the series; +no other build or test run during timing. Each worker limit has one warm-up per backend and three alternating +measured pairs. Both backends use the same GC-enabled instrumentation and macOS `--no-sparkplug` setting. +Times cover the full Jest process, including session startup/disposal, and exclude building the framework. +These are instrumented local observations, not CI performance evidence. + +| Worker limit | CLI median (range) | Session median (range) | Ratio | +| ------------ | --------------------- | ---------------------- | ----- | +| 2 | 12.94 s (12.86–13.10) | 3.53 s (3.51–3.65) | 3.67× | +| 8 | 6.87 s (6.82–7.14) | 2.35 s (2.33–2.37) | 2.92× | + +All 18 runs pass the same 33 unique full scenario names. Those names also match the raw results of the previous +pilot. Each session run records exactly three child PIDs and ten responses per child. With three session-backed +files the eight-worker setting starts at most three session children; it does not model eight concurrent sessions. + +## Child memory and cleanup + +The figures below are measured in the build child after each build, with forced GC, not in the Jest worker. +Across the measured series, after build ten: + +- RSS: 341.36–394.23 MiB per child. +- JavaScript heap: 75.72–79.70 MiB per child. +- Highest reported process peak RSS across the measured samples: 394.36 MiB. + +The process exits at the file boundary, so retained compiler memory does not follow a Jest worker into its next +file. This bounds the number of builds per child, not peak bytes, and does not repair Rspack retention. More +simultaneous files would increase aggregate memory. CLI child memory is not instrumented, so these values must +not be read as a total-memory comparison between backends. + +Every run verifies zero leftover fixture/session directories and that all reported session PIDs have exited. +Separate `--detectOpenHandles` runs for both backends pass all 33 cases and report zero open handles. Directories +left by deliberately interrupted development diagnostics were removed before the benchmark; they are not part +of the measured cleanup results. + +## Validation + +- Full `npm run check`: package build, framework typecheck, fixture builds/typechecks, 184 suites / 2008 tests. + This preceded one additional pending-disposal regression; the final session file passes all eight tests. +- Final Node 24.5.0 session lifecycle tests: 8/8. +- Local Node 22.18.0: all 33 override cases, two absolute-root cases and eight session lifecycle cases pass (43/43). +- Chrome/Firefox: 31 suites / 42 tests pass on two workers. +- Inventory: 215 files, each in exactly one project; no local module mocks. +- The lifecycle cases cover fixed-cwd A → B → A, overlap rejection, config causes, compiler diagnostics, + recovery, timeout, unexpected exit, disposal while pending, and IPC disconnect both idle and during a build. +- Linux/Windows CI remains unverified; local Node 22/24 checks do not replace that matrix. + +## Reproduction + +```bash +npm run build +node tests/benchmarks/override-pilot.mjs +``` + +The runner writes logs, parent/child JSONL and Jest output under `.cache/benchmarks/override-session`. It refuses +to start with unaccounted fixture/session directories. Parent instrumentation also checks that environment values, +object identity and cwd are unchanged after each test. No global memory limit or worker-count change is applied. + +[Raw results](2026-09-28-override-session.json) include scenario names and all child samples. +The [previous report](2026-09-28-override-pilot.md) records the superseded backend, not current behavior. diff --git a/tests/benchmarks/override-pilot.mjs b/tests/benchmarks/override-pilot.mjs index 2105d325..9d86c6e0 100644 --- a/tests/benchmarks/override-pilot.mjs +++ b/tests/benchmarks/override-pilot.mjs @@ -1,3 +1,4 @@ +import assert from "node:assert/strict"; import {spawnSync} from "node:child_process"; import fs from "node:fs"; import os from "node:os"; @@ -5,53 +6,63 @@ import path from "node:path"; import {performance} from "node:perf_hooks"; const root = path.resolve(import.meta.dirname, "../.."); -const output = path.join(root, ".cache/benchmarks/override-pilot"); +const output = path.join(root, ".cache/benchmarks/override-session"); fs.mkdirSync(output, {recursive: true}); const flags = ["--expose-gc", ...(process.platform === "darwin" && process.arch === "arm64" ? ["--no-sparkplug"] : [])]; -const stressOnly = process.argv.includes("--stress-only"); -const resume = process.argv.includes("--limits-only") || stressOnly; -const report = resume - ? JSON.parse(fs.readFileSync(path.join(output, "results.json"), "utf8")) - : { - node: process.version, - platform: process.platform, - arch: process.arch, - cpus: os.cpus().length, - flags, - runs: [], - }; -let sequence = report.runs.length; -let expectedNames = report.runs[0]?.names; +const report = { + node: process.version, + platform: process.platform, + arch: process.arch, + cpus: os.cpus().length, + flags, + runs: [], +}; +let expectedNames; + +const leftovers = () => + fs.readdirSync(path.join(root, ".cache/integration")).filter(name => /^(fixture|session)-/.test(name)); +assert.deepEqual(leftovers(), [], "Account for existing test directories before running the benchmark"); +const readSamples = filename => fs.readFileSync(filename, "utf8").trim().split("\n").filter(Boolean).map(JSON.parse); const run = (mode, workers, phase, extra = []) => { - const id = `${++sequence}-${phase}-${workers}-${mode}`; + const id = `${report.runs.length + 1}-${phase}-${workers}-${mode}`; const metrics = path.join(output, `${id}.jsonl`); + const childMetrics = path.join(output, `${id}-children.jsonl`); const resultPath = path.join(output, `${id}.json`); const log = path.join(output, `${id}.log`); fs.writeFileSync(metrics, ""); - const args = [ - ...flags, - "node_modules/jest/bin/jest.js", - "--selectProjects", - "build", - "--testPathPatterns=tests/integration/build/override", - `--maxWorkers=${workers}`, - "--logHeapUsage", - "--json", - `--outputFile=${resultPath}`, - "--setupFilesAfterEnv", - path.join(root, "tests/jest.setup.ts"), - path.join(import.meta.dirname, "override-pilot-metrics.cjs"), - ...extra, - ]; + fs.writeFileSync(childMetrics, ""); const started = performance.now(); - const result = spawnSync(process.execPath, args, { - cwd: root, - env: {...process.env, ADNBN_OVERRIDE_BUILD_MODE: mode, ADNBN_PILOT_METRICS: metrics}, - encoding: "utf8", - timeout: 180_000, - maxBuffer: 8 * 1024 * 1024, - }); + const result = spawnSync( + process.execPath, + [ + ...flags, + "node_modules/jest/bin/jest.js", + "--selectProjects", + "build", + "--testPathPatterns=tests/integration/build/override", + `--maxWorkers=${workers}`, + "--logHeapUsage", + "--json", + `--outputFile=${resultPath}`, + "--setupFilesAfterEnv", + path.join(root, "tests/jest.setup.ts"), + path.join(import.meta.dirname, "override-pilot-metrics.cjs"), + ...extra, + ], + { + cwd: root, + env: { + ...process.env, + ADNBN_OVERRIDE_BUILD_MODE: mode, + ADNBN_PILOT_METRICS: metrics, + ADNBN_SESSION_METRICS: childMetrics, + }, + encoding: "utf8", + timeout: 180_000, + maxBuffer: 8 * 1024 * 1024, + } + ); const wallSeconds = (performance.now() - started) / 1000; fs.writeFileSync(log, result.stdout + result.stderr); @@ -62,153 +73,61 @@ const run = (mode, workers, phase, extra = []) => { const data = JSON.parse(fs.readFileSync(resultPath, "utf8")); const names = data.testResults.flatMap(file => file.assertionResults.map(test => test.fullName)).sort(); expectedNames ??= names; - - if (names.length !== 33 || JSON.stringify(names) !== JSON.stringify(expectedNames)) { - throw new Error(`${id}: scenario inventory changed`); - } - - const leftovers = fs.readdirSync(path.join(root, ".cache/integration")).filter(name => name.startsWith("fixture-")); - - if (leftovers.length) { - throw new Error(`${id}: ${leftovers.length} fixture copies remain`); + assert.equal(names.length, 33); + assert.equal(new Set(names).size, 33); + assert.deepEqual(names, expectedNames); + assert.deepEqual(leftovers(), []); + const children = readSamples(childMetrics); + const pids = [...new Set(children.map(sample => sample.pid))]; + + if (mode === "session") { + assert.equal(children.length, 30); + assert.equal(pids.length, 3); + + for (const pid of pids) { + assert.deepEqual( + children.filter(sample => sample.pid === pid).map(sample => sample.build), + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + ); + assert.throws(() => process.kill(pid, 0), {code: "ESRCH"}); + } } - const samples = fs - .readFileSync(metrics, "utf8") - .trim() - .split("\n") - .map(JSON.parse) - .map(sample => ({...sample, file: path.relative(root, sample.file)})); - const entry = { + report.runs.push({ id, phase, mode, workers, wallSeconds, - tests: data.numPassedTests, names, - leftovers: leftovers.length, + tests: data.numPassedTests, + leftovers: leftovers().length, openHandles: data.openHandles?.length ?? null, - samples, - files: data.testResults.map(file => ({ - path: path.relative(root, file.name), - seconds: (file.endTime - file.startTime) / 1000, + children: children.map(sample => ({ + ...sample, + rootDir: path.relative(root, sample.rootDir), + cwd: path.relative(root, sample.cwd), })), - heapOutput: result.stderr.split("\n").filter(line => line.includes("heap size")), - }; - report.runs.push(entry); + parentSamples: readSamples(metrics).map(sample => ({...sample, file: path.relative(root, sample.file)})), + }); fs.writeFileSync(path.join(output, "results.json"), JSON.stringify(report, null, 2) + "\n"); - console.log(`${id}: ${wallSeconds.toFixed(2)} s, 33 tests, 0 leftover copies`); + console.log( + `${id}: ${wallSeconds.toFixed(2)} s; 33 tests, ${pids.length} session children, no leftover processes/directories` + ); }; -if (!resume) { - for (const workers of [2, 8]) { - for (const mode of ["cli", "in-process"]) { - run(mode, workers, "warmup"); - } - - for (let round = 0; round < 3; round++) { - for (const mode of round % 2 ? ["in-process", "cli"] : ["cli", "in-process"]) { - run(mode, workers, "measured"); - } - } +for (const workers of [2, 8]) { + for (const mode of ["cli", "session"]) { + run(mode, workers, "warmup"); } - for (const mode of ["cli", "in-process"]) { - run(mode, 1, "memory"); - run(mode, 1, "handles", ["--detectOpenHandles"]); - } -} - -if (!stressOnly) { - for (const workers of [2, 8]) { - for (let round = 0; round < 3; round++) { - run("in-process", workers, "limited", ["--workerIdleMemoryLimit=128MB"]); + for (let round = 0; round < 3; round++) { + for (const mode of round % 2 ? ["session", "cli"] : ["cli", "session"]) { + run(mode, workers, "measured"); } } } -if (!resume || stressOnly) { - const temporary = path.join(root, "tests/integration/build/override/memory-pilot.integration.test.ts"); - - if (fs.existsSync(temporary)) { - throw new Error(`Refusing to replace ${temporary}`); - } - - const cases = ["newtab", "history", "bookmarks"] - .map( - page => - fs - .readFileSync(path.join(path.dirname(temporary), `${page}.integration.test.ts`), "utf8") - .split("jest.setTimeout(90_000);")[1] - ) - .join("\n"); - const metrics = path.join(output, "stress.jsonl"); - const resultPath = path.join(output, "stress.json"); - fs.writeFileSync(metrics, ""); - - try { - fs.writeFileSync( - temporary, - 'import {testOverridePage} from "./override-utils";\njest.setTimeout(90_000);\ndescribe.each([1, 2, 3])("memory round %s", () => {\n' + - cases + - "\n});\n" - ); - const result = spawnSync( - process.execPath, - [ - ...flags, - "node_modules/jest/bin/jest.js", - "--selectProjects", - "build", - "--runTestsByPath", - temporary, - "--runInBand", - "--logHeapUsage", - "--json", - `--outputFile=${resultPath}`, - "--setupFilesAfterEnv", - path.join(root, "tests/jest.setup.ts"), - path.join(import.meta.dirname, "override-pilot-metrics.cjs"), - ], - { - cwd: root, - env: {...process.env, ADNBN_OVERRIDE_BUILD_MODE: "in-process", ADNBN_PILOT_METRICS: metrics}, - encoding: "utf8", - timeout: 180_000, - maxBuffer: 8 * 1024 * 1024, - } - ); - fs.writeFileSync(path.join(output, "stress.log"), result.stdout + result.stderr); - - if (result.error || result.status !== 0) { - throw new Error("Memory stress probe failed; see stress.log"); - } - - const data = JSON.parse(fs.readFileSync(resultPath, "utf8")); - - if (data.numPassedTests !== 90) { - throw new Error("Expected 90 successful builds in one worker"); - } - - const leftovers = fs - .readdirSync(path.join(root, ".cache/integration")) - .filter(name => name.startsWith("fixture-")); - - if (leftovers.length) { - throw new Error(`Memory stress probe left ${leftovers.length} fixture copies`); - } - - report.stressFixtureCopiesRemaining = leftovers.length; - report.stress = fs - .readFileSync(metrics, "utf8") - .trim() - .split("\n") - .map(JSON.parse) - .map(sample => ({...sample, file: path.relative(root, sample.file)})); - fs.writeFileSync(path.join(output, "results.json"), JSON.stringify(report, null, 2) + "\n"); - console.log("Memory stress probe: 90 tests passed"); - } finally { - fs.rmSync(temporary); - } +for (const mode of ["cli", "session"]) { + run(mode, 1, "handles", ["--detectOpenHandles"]); } From eb784b03dec62fa0e5099e721abef7f169611429 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:19:29 +0300 Subject: [PATCH 33/36] chore(release): narrow package contents and make preview read-only Publish only the CLI launcher, dist, types and the Node preinstall check. Use release-version and changelog modes to stop preview before npm version; the installed release-it still updates package files in plain dry-run mode. Validate the tarball through a clean consumer install and CLI build. Verify preview leaves tracked files, HEAD and tags unchanged. --- CONTRIBUTING.md | 7 +++++++ package.json | 6 +++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 127d8c4e..1beaed45 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -73,6 +73,13 @@ Releases and versioning: - We use SemVer. Commit types help determine version bumps (`feat` → minor, `fix` → patch, `BREAKING CHANGE` → major). - Package version is updated by maintainers within release/hotfix branches. +Run `npm run release:preview` to inspect the proposed version and changelog from a feature branch, +even with a dirty working tree. This is a dry run: it does not update files, create commits or tags, push, +or publish to GitHub/npm. Only committed changes contribute to the proposed release notes and version. +The preview uses the read-only `--release-version` and `--changelog` modes, which exit before the version +update stage. `--dry-run` alone is insufficient with the installed release-it version: its npm plugin still +executes `npm version`. The preview's relaxed checks do not change the actual release restrictions. + ## How to contribute 1. Fork the repository and create a branch from `develop` (or `main` for `hotfix`): diff --git a/package.json b/package.json index 7f01ec14..527863db 100644 --- a/package.json +++ b/package.json @@ -31,9 +31,9 @@ "module": "./dist/index.js", "types": "./dist/index.d.ts", "files": [ - "bin", + "bin/adnbn.js", "dist", - "scripts", + "scripts/check-node-version.js", "types" ], "engines": { @@ -130,7 +130,7 @@ "test:inventory": "node scripts/test-inventory.mjs", "test:run": "node scripts/test-run.mjs", "release": "release-it", - "release:preview": "release-it --no-github.release --no-npm.publish --no-git.tag --ci" + "release:preview": "release-it --release-version --dry-run --no-git.requireBranch --no-git.requireCleanWorkingDir --no-git.push --no-github.release --no-npm.publish --ci && release-it --changelog --dry-run --no-git.requireBranch --no-git.requireCleanWorkingDir --no-git.push --no-github.release --no-npm.publish --ci" }, "dependencies": { "@addon-core/browser": "^0.9.0", From da92605864b4de2a24eeb3c7184b4988f6096ff7 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 17:19:40 +0300 Subject: [PATCH 34/36] ci(test): exercise override build sessions across the matrix Run the opt-in session backend after CLI build tests on Linux and Windows with Node 22 and 24. Reuse the package build and retain CLI coverage. Validate all 33 scenarios locally on Node 22 and 24 with two workers. --- .github/workflows/ci.yml | 4 ++++ tests/integration/README.md | 6 ++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ecc7fdca..7460c5f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,6 +99,10 @@ jobs: - name: Compiler, CLI and declaration integrations if: matrix.os != 'ubuntu-latest' || matrix.node != 22 run: npm run test:run -- --selectProjects build types --ci --maxWorkers=2 + - name: Override builds in isolated sessions + run: npm run test:run -- --selectProjects build --testPathPatterns=tests/integration/build/override --ci --maxWorkers=2 + env: + ADNBN_OVERRIDE_BUILD_MODE: session - name: Upload build coverage if: matrix.os == 'ubuntu-latest' && matrix.node == 22 uses: actions/upload-artifact@v4 diff --git a/tests/integration/README.md b/tests/integration/README.md index 72916ea5..d00f7080 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -298,5 +298,7 @@ Run benchmarks exclusively, without another build or test run. See the [session measurements](../benchmarks/2026-09-28-override-session.md) for timings and child memory. The [earlier in-process report](../benchmarks/2026-09-28-override-pilot.md) describes the superseded experiment; -its timings do not apply to the session backend. The session pilot remains opt-in pending fresh measurements -and the Linux/Windows Node 22/24 CI matrix. +its timings do not apply to the session backend. CI explicitly runs the session override suite on Linux and +Windows with Node 22 and 24, after the ordinary CLI build integrations, using two workers. This reuses the +existing package build and does not replace the CLI coverage run. The local default remains CLI; switching +it requires successful CI results and fresh measurements on the target machines. From 72abf34c89da43f17d89ebab21052d526f67c103 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Mon, 28 Sep 2026 23:13:02 +0300 Subject: [PATCH 35/36] fix(style)!: resolve relative Sass requests from their source files Rebase explicit ./ and ../ requests in @use and @forward before merging shared and app styles, preserving as, show, hide and with. Rebase literal app url(...) values, including module configuration, and preserve query strings and fragments. Keep shared and app styles in one CSS module with the existing class identifiers, shared-to-app order and stylesheet routing. Use sass-loader additionalData to register existing and missing app overrides as watch dependencies. Emit preparation errors through the loader context. Separate request rewriting from merge ordering and deduplication. Cover rewriting, merging, two-app builds and watch updates with tests. Document supported cases, limitations and migration. BREAKING CHANGE: relative @use and @forward requests in app stylesheets now resolve from the app file instead of the shared file. To reuse a shared module intentionally, use an explicit path or configured alias to the shared directory. --- .prettierignore | 2 + package-lock.json | 7 - package.json | 1 - src/cli/plugins/style/README.md | 81 ++++++ src/cli/plugins/style/index.ts | 87 +++--- src/cli/plugins/style/rebase.test.ts | 101 +++++++ src/cli/plugins/style/rebase.ts | 98 +++++++ .../relative-conflict/app/content.scss | 5 + .../relative-conflict/app/theme/_tokens.scss | 1 + .../relative-conflict/shared/content.scss | 5 + .../shared/theme/_tokens.scss | 1 + .../relative-modules/app/content.scss | 10 + .../relative-modules/app/theme/_hidden.scss | 1 + .../relative-modules/app/theme/_metrics.scss | 1 + .../relative-modules/app/theme/_tokens.scss | 1 + .../app/theme/fonts/_index.scss | 9 + .../relative-modules/shared/content.scss | 5 + .../shared/theme/_tokens.scss | 1 + .../fixtures/same-module/app/content.scss | 5 + .../fixtures/same-module/common/_tokens.scss | 1 + .../fixtures/same-module/shared/content.scss | 5 + .../fixtures/url-values/app/content.scss | 13 + .../fixtures/url-values/shared/content.scss | 5 + src/cli/plugins/style/utils.test.ts | 76 ++++- src/cli/plugins/style/utils.ts | 50 +++- src/types/config.ts | 5 +- tests/integration/README.md | 7 + tests/integration/build/style/compiler.ts | 109 ++++++++ .../multi-app-updates/Inter-Latin.woff2 | 1 + .../fixtures/multi-app-updates/conflict.scss | 5 + .../fixtures/multi-app-updates/content.scss | 10 + .../fixtures/multi-app-updates/fonts.scss | 9 + .../fixtures/multi-app-updates/invalid.scss | 1 + .../fixtures/multi-app-updates/metrics.scss | 1 + .../fixtures/multi-app-updates/missing.scss | 1 + .../build/style/fixtures/multi-app/asis.js | 1 + .../style/fixtures/multi-app/document.js | 3 + .../build/style/fixtures/multi-app/normal.js | 3 + .../src/apps/alpha/content/content.scss | 10 + .../src/apps/alpha/theme/_metrics.scss | 1 + .../src/apps/alpha/theme/_tokens.scss | 1 + .../apps/alpha/theme/fonts/Inter-Latin.woff2 | 1 + .../src/apps/alpha/theme/fonts/_index.scss | 9 + .../src/apps/beta/content/content.scss | 10 + .../src/apps/beta/theme/_metrics.scss | 1 + .../src/apps/beta/theme/_tokens.scss | 1 + .../apps/beta/theme/fonts/Inter-Latin.woff2 | 1 + .../src/apps/beta/theme/fonts/_index.scss | 9 + .../multi-app/src/shared/content/content.scss | 12 + .../multi-app/src/shared/theme/_alias.scss | 1 + .../multi-app/src/shared/theme/_tokens.scss | 1 + .../vendor/fixture-theme/_index.scss | 1 + .../style/style-merge.integration.test.ts | 259 ++++++++++++++++++ 53 files changed, 979 insertions(+), 67 deletions(-) create mode 100644 src/cli/plugins/style/README.md create mode 100644 src/cli/plugins/style/rebase.test.ts create mode 100644 src/cli/plugins/style/rebase.ts create mode 100644 src/cli/plugins/style/tests/fixtures/relative-conflict/app/content.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-conflict/app/theme/_tokens.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-conflict/shared/content.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-conflict/shared/theme/_tokens.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-modules/app/content.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_hidden.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_metrics.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_tokens.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/fonts/_index.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-modules/shared/content.scss create mode 100644 src/cli/plugins/style/tests/fixtures/relative-modules/shared/theme/_tokens.scss create mode 100644 src/cli/plugins/style/tests/fixtures/same-module/app/content.scss create mode 100644 src/cli/plugins/style/tests/fixtures/same-module/common/_tokens.scss create mode 100644 src/cli/plugins/style/tests/fixtures/same-module/shared/content.scss create mode 100644 src/cli/plugins/style/tests/fixtures/url-values/app/content.scss create mode 100644 src/cli/plugins/style/tests/fixtures/url-values/shared/content.scss create mode 100644 tests/integration/build/style/compiler.ts create mode 100644 tests/integration/build/style/fixtures/multi-app-updates/Inter-Latin.woff2 create mode 100644 tests/integration/build/style/fixtures/multi-app-updates/conflict.scss create mode 100644 tests/integration/build/style/fixtures/multi-app-updates/content.scss create mode 100644 tests/integration/build/style/fixtures/multi-app-updates/fonts.scss create mode 100644 tests/integration/build/style/fixtures/multi-app-updates/invalid.scss create mode 100644 tests/integration/build/style/fixtures/multi-app-updates/metrics.scss create mode 100644 tests/integration/build/style/fixtures/multi-app-updates/missing.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/asis.js create mode 100644 tests/integration/build/style/fixtures/multi-app/document.js create mode 100644 tests/integration/build/style/fixtures/multi-app/normal.js create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/alpha/content/content.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_metrics.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_tokens.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/Inter-Latin.woff2 create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/_index.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/beta/content/content.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_metrics.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_tokens.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/Inter-Latin.woff2 create mode 100644 tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/_index.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/shared/content/content.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/shared/theme/_alias.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/src/shared/theme/_tokens.scss create mode 100644 tests/integration/build/style/fixtures/multi-app/vendor/fixture-theme/_index.scss create mode 100644 tests/integration/build/style/style-merge.integration.test.ts diff --git a/.prettierignore b/.prettierignore index fb92500f..418e7b6e 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,4 +4,6 @@ dist/ /build/ addon/ src/cli/entrypoint/file/tests/fixtures/ +src/cli/plugins/style/tests/fixtures/ +tests/integration/build/style/fixtures/ CHANGELOG.md diff --git a/package-lock.json b/package-lock.json index a1809a66..76ca3b62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,6 @@ "sass": "^1.83.4", "sass-loader": "^16.0.4", "semver": "^7.7.3", - "source-modifier-loader": "^1.0.2", "tsconfig-paths": "^4.2.0", "type-fest": "^4.37.0", "typescript": "^5.7.3", @@ -18080,12 +18079,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-modifier-loader": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-modifier-loader/-/source-modifier-loader-1.0.2.tgz", - "integrity": "sha512-XrNpNvB8+gACt1k4bIX5MFuwWZkPesnhoS+vsVhn4XoMD2luvvJMiMkHPkF2/qmCAbWw5vjKqo0pq0GoYXhPcA==", - "license": "MIT" - }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", diff --git a/package.json b/package.json index 527863db..cc43deab 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,6 @@ "sass": "^1.83.4", "sass-loader": "^16.0.4", "semver": "^7.7.3", - "source-modifier-loader": "^1.0.2", "tsconfig-paths": "^4.2.0", "type-fest": "^4.37.0", "typescript": "^5.7.3", diff --git a/src/cli/plugins/style/README.md b/src/cli/plugins/style/README.md new file mode 100644 index 00000000..684ecb37 --- /dev/null +++ b/src/cli/plugins/style/README.md @@ -0,0 +1,81 @@ +# Shared and app styles + +With `mergeStyles` enabled, importing a shared stylesheet also includes the app +stylesheet at the same relative path. Both sources compile as one CSS module, +using the shared resource path and the existing app-specific class-name salt. +Shared body rules precede app body rules. Ordinary, `?unisolated`, and `?asis` +imports retain their existing routing and CSS module behavior. + +Before merging, the plugin rebases explicit `./` and `../` requests in `@use` +and `@forward` from each original file to the shared resource. Module parameters +remain intact; Sass resolves partials and directory indexes. Package requests, +aliases, built-in modules, and requests without an explicit relative prefix keep +their normal resolution. `rebase.ts` owns the request and URL rewriting; +`utils.ts` owns which nodes receive it, the prelude order, and deduplication. + +Only identical prepared prelude statements are deduplicated. Shared requests pass +through the same normalization, so equivalent spellings of one module match. Two +identical relative requests from different source directories can refer to +different modules. Use distinct `as` namespaces when both modules need to +coexist; Sass still reports namespace and configuration conflicts. + +## Resources configured through `with` + +The plugin also rebases literal app `url(...)` values, including values supplied +inside module configuration. Query strings and fragments are preserved for the +normal CSS and asset loaders. + +```scss +// src/apps/example/content/content.scss +@use "../theme/fonts" with ( + $inter-latin: url("../theme/fonts/Inter-Latin.woff2?browser") +); +``` + +```scss +// src/apps/example/theme/fonts/_index.scss +$inter-latin: url("./Inter-Latin.woff2?browser") !default; + +@font-face { + font-family: Inter; + src: $inter-latin format("woff2"); +} +``` + +Pass the complete `url(...)` from the app source. + +## Limitations + +- Only `@use` and `@forward` requests are rebased. Sass `@import` and + `meta.load-css()` requests keep resolving from the shared resource. +- Resources written inside imported partials are not rebased. Pass the complete + `url(...)` from the app source through `with`. +- URL aliases are not recognized. A `url(...)` without a scheme, root, `~`, `#`, + or `?` prefix is treated as a relative resource, so an aliased or package URL + written in an app source becomes a relative path and fails to resolve. Write a + full relative `url(...)` from the app source; alias-aware URL rewriting would + have to follow the bundler resolve settings. +- Dynamic Sass URL expressions and interpolated requests are left for Sass to + evaluate. +- Sass reports errors in the merged app body against the shared resource with + positions in the merged text. Errors inside imported modules keep their own + file positions. + +## Migration + +Relative `@use` and `@forward` requests in app sources used to resolve from the +shared file. They now resolve from the app file, as written. An app stylesheet +that intentionally reuses a shared module through such a request must address +it by a path or a configured alias to the shared directory. + +## Watch dependencies and errors + +Preparation runs through `sass-loader.additionalData`. Existing overrides are +registered with `addDependency`; absent overrides use `addMissingDependency` so +creation, removal, and recreation trigger compilation. Sass and the asset loader +register imported modules and resources. + +Preparation errors are emitted as compilation errors and produce no stylesheet, +because sass-loader 16 does not forward errors thrown by `additionalData` through +its loader callback. Sass compilation errors retain Sass's own diagnostics. A +failed override never falls back to a successful shared-only build. diff --git a/src/cli/plugins/style/index.ts b/src/cli/plugins/style/index.ts index a330f1eb..66ed0354 100644 --- a/src/cli/plugins/style/index.ts +++ b/src/cli/plugins/style/index.ts @@ -1,7 +1,13 @@ import _ from "lodash"; import path from "path"; import fs from "fs"; -import {Configuration as RspackConfig, CssExtractRspackPlugin, RuleSetUse, RuleSetUseItem} from "@rspack/core"; +import { + Configuration as RspackConfig, + CssExtractRspackPlugin, + LoaderContext, + RuleSetUse, + RuleSetUseItem, +} from "@rspack/core"; import {mergeStyleSources} from "./utils"; import {DocumentStylesLayer, DefaultStylesLayer} from "@cli/bundler/layers"; @@ -10,7 +16,6 @@ import {definePlugin} from "@main/plugin"; import {appFilenameResolver} from "@cli/bundler"; import {getAppSourcePath, getResolvePath, getSharedPath} from "@cli/resolvers/path"; -import {toPosix} from "@cli/utils/path"; import {ReadonlyConfig} from "@typing/config"; @@ -19,44 +24,42 @@ import {ReadonlyConfig} from "@typing/config"; const DefaultAsIsLoaderIdent = "adnbn-default-asis"; const DefaultModulesLoaderIdent = "adnbn-default-modules"; -// prettier-ignore -const styleMergerLoader = - (config: ReadonlyConfig) => - (sharedStyle: string, sharedPath: string): string | void => { - const sharedDir = getResolvePath(getSharedPath(config)); +const createStyleMerger = (config: ReadonlyConfig) => { + const sharedDir = getResolvePath(getSharedPath(config)); + const appDir = getResolvePath(getAppSourcePath(config)); - if (sharedPath.startsWith(sharedDir)) { - const relativePath = path.relative(sharedDir, sharedPath); + return (content: string, context: LoaderContext): string => { + const filename = context.resourcePath; + const relative = path.relative(sharedDir, filename); - const appDir = getResolvePath(getAppSourcePath(config)); - const appPath = getResolvePath(path.join(appDir, relativePath)); + if (relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) { + return content; + } - if (fs.existsSync(appPath)) { - let appStyle = fs.readFileSync(appPath, "utf8"); + const appPath = path.join(appDir, relative); - appStyle = appStyle.replace(/url\((['"]?)(.*?)\1\)/g, (match, quote, filePath) => { - if ( - filePath.startsWith("/") || - filePath.startsWith("http") || - filePath.startsWith("data:") - ) { - return match; - } + if (!fs.existsSync(appPath)) { + context.addMissingDependency(appPath); - const cssDir = path.dirname(appPath); - const assetAbs = path.resolve(cssDir, filePath); + return content; + } - const sharedFileDir = path.dirname(sharedPath); + context.addDependency(appPath); - const relativeToSharedFile = path.relative(sharedFileDir, assetAbs); + try { + return mergeStyleSources( + {filename, content}, + {filename: appPath, content: fs.readFileSync(appPath, "utf8")} + ); + } catch (error) { + // sass-loader prepares additionalData outside its error handler. Fail the compilation + // explicitly and supply no stylesheet instead of compiling an incomplete shared fallback. + context.emitError(error instanceof Error ? error : new Error(String(error))); - return `url("${toPosix(relativeToSharedFile)}")`; - }); - - return mergeStyleSources(sharedStyle, appStyle); - } - } - }; + return ""; + } + }; +}; export default definePlugin(() => { return { @@ -68,18 +71,14 @@ export default definePlugin(() => { const kebabApp = _.kebabCase(app); const createSassRuleSet = (rule: RuleSetUseItem): RuleSetUse => { - const rules: RuleSetUse = [CssExtractRspackPlugin.loader, rule, "sass-loader"]; - - if (mergeStyles) { - rules.push({ - loader: "source-modifier-loader", - options: { - modify: styleMergerLoader(config), - }, - }); - } - - return rules; + return [ + CssExtractRspackPlugin.loader, + rule, + { + loader: "sass-loader", + options: mergeStyles ? {additionalData: createStyleMerger(config)} : {}, + }, + ]; }; const createStyleRules = (defaultStyles = false) => [ diff --git a/src/cli/plugins/style/rebase.test.ts b/src/cli/plugins/style/rebase.test.ts new file mode 100644 index 00000000..471236c8 --- /dev/null +++ b/src/cli/plugins/style/rebase.test.ts @@ -0,0 +1,101 @@ +import path from "path"; + +import {rebaseModuleRequest, rebaseUrlFunctions} from "./rebase"; + +const source = path.resolve("src"); +const from = path.join(source, "apps", "alpha", "content", "content.scss"); +const to = path.join(source, "shared", "content", "content.scss"); + +describe("rebaseModuleRequest", () => { + test.each([ + ['"./theme/tokens" as appTokens', '"../../apps/alpha/content/theme/tokens" as appTokens'], + ["'../theme/fonts'", "'../../apps/alpha/theme/fonts'"], + [ + '"./theme/tokens" as theme-* show $theme-accent with ($accent: blue)', + '"../../apps/alpha/content/theme/tokens" as theme-* show $theme-accent with ($accent: blue)', + ], + [ + '"../theme/fonts" with (\n $font: url("../theme/font.woff2?browser")\n)', + '"../../apps/alpha/theme/fonts" with (\n $font: url("../theme/font.woff2?browser")\n)', + ], + ['/* app */ "./theme/tokens"', '/* app */ "../../apps/alpha/content/theme/tokens"'], + ])("rebases the relative request in %j", (params, expected) => { + expect(rebaseModuleRequest(params, from, to)).toBe(expected); + }); + + test.each([ + '"sass:math"', + '"some-package/theme"', + '"@theme/tokens"', + '"~theme/tokens"', + '"theme/tokens"', + '"pkg:some-package"', + '"./#{$theme}"', + '"./theme/tokens', + "", + ])("keeps %j as written", params => { + expect(rebaseModuleRequest(params, from, to)).toBe(params); + }); + + test.each([ + ['"./../common/tokens"', '"../common/tokens"'], + ['"./theme/./tokens" as tokens', '"./theme/tokens" as tokens'], + ['"../common/tokens"', '"../common/tokens"'], + ['"theme/tokens"', '"theme/tokens"'], + ])("normalizes %j for the same stylesheet", (params, expected) => { + expect(rebaseModuleRequest(params, to, to)).toBe(expected); + }); +}); + +describe("rebaseUrlFunctions", () => { + test.each([ + ["url(theme/plain.svg)", 'url("../../apps/alpha/content/theme/plain.svg")'], + [ + 'url( "./theme/spaced font.woff2?browser#face" )', + 'url( "../../apps/alpha/content/theme/spaced font.woff2?browser#face" )', + ], + ["url('./theme/icon.svg?next=a/../b#face')", "url('../../apps/alpha/content/theme/icon.svg?next=a/../b#face')"], + ['url("./theme/escaped\\ name.svg")', 'url("../../apps/alpha/content/theme/escaped\\ name.svg")'], + ['URL("../theme/upper.svg")', 'URL("../../apps/alpha/theme/upper.svg")'], + [ + 'url("./theme/font.woff2") format("woff2")', + 'url("../../apps/alpha/content/theme/font.woff2") format("woff2")', + ], + [ + 'url("./a.svg"), url("./b.svg")', + 'url("../../apps/alpha/content/a.svg"), url("../../apps/alpha/content/b.svg")', + ], + [ + 'image-set(url("./a.png") 1x, url("./a@2x.png") 2x)', + 'image-set(url("../../apps/alpha/content/a.png") 1x, url("../../apps/alpha/content/a@2x.png") 2x)', + ], + [ + '(\n $font: url("../theme/font.woff2?browser")\n)', + '(\n $font: url("../../apps/alpha/theme/font.woff2?browser")\n)', + ], + ])("rebases the literal URL in %j", (value, expected) => { + expect(rebaseUrlFunctions(value, from, to)).toBe(expected); + }); + + test.each([ + 'url("https://example.com/font.woff2")', + 'url("//example.com/font.woff2")', + 'url("data:image/svg+xml;base64,AAAA")', + 'url("chrome-extension://id/font.woff2")', + 'url("/font.woff2")', + 'url("#icon")', + 'url("?version=1")', + 'url("~package/font.woff2")', + "url($font)", + 'url("#{$directory}/font.woff2")', + "url(./#{$theme}.svg)", + "url(font-path())", + 'url("./a.svg" "b")', + 'url("./unclosed', + "url()", + '"url(./text.svg)"', + '$font format("woff2")', + ])("keeps %j as written", value => { + expect(rebaseUrlFunctions(value, from, to)).toBe(value); + }); +}); diff --git a/src/cli/plugins/style/rebase.ts b/src/cli/plugins/style/rebase.ts new file mode 100644 index 00000000..1e869ecb --- /dev/null +++ b/src/cli/plugins/style/rebase.ts @@ -0,0 +1,98 @@ +import path from "path"; +import valueParser from "postcss-value-parser"; + +import {toPosix} from "@cli/utils/path"; + +const RelativeRequestPattern = /^\.{1,2}\//; +// Schemes, root-relative paths, fragments, queries and ~-prefixed requests are not rebased. +const ExternalUrlPattern = /^(?:[a-z][a-z\d+.-]*:|[/#?~])/i; + +/** + * Rebases the explicit `./` or `../` module request of `@use` or `@forward` parameters written in the + * stylesheet `from` so it resolves from the stylesheet `to`. Namespaces, `show`, `hide` and `with` + * configuration stay as written; package, alias, built-in and interpolated requests keep their normal + * resolution. When both stylesheets are the same file, the request is only normalized. + */ +export const rebaseModuleRequest = (params: string, from: string, to: string): string => { + const parsed = valueParser(params); + const request = parsed.nodes.find(node => node.type !== "space" && node.type !== "comment"); + + if ( + request?.type !== "string" || + request.unclosed || + !RelativeRequestPattern.test(request.value) || + request.value.includes("#{") + ) { + return params; + } + + request.value = rebaseStylePath(request.value, from, to); + + return parsed.toString(); +}; + +/** + * Rebases literal relative `url(...)` values written in the stylesheet `from` so they resolve from the + * stylesheet `to`, including values nested in lists, functions and module configuration. Absolute URLs, + * schemes, root-relative paths, fragments, queries, ~-prefixed requests and Sass expressions stay as written. + * Rebased URLs are quoted and keep their query and fragment. + */ +export const rebaseUrlFunctions = (value: string, from: string, to: string): string => { + const parsed = valueParser(value); + let rebased = false; + + parsed.walk(node => { + if (node.type !== "function" || node.value.toLowerCase() !== "url") { + return; + } + + const tokens = node.nodes.filter(token => token.type !== "space" && token.type !== "comment"); + const url = tokens[0]; + + if (node.unclosed || tokens.length !== 1 || !isLiteralUrl(url)) { + return false; + } + + // Quote rebased URLs so spaces in the app directory remain part of the path. + const quoted: valueParser.StringNode = { + ...url, + type: "string", + quote: url.type === "string" ? url.quote : '"', + value: rebaseStylePath(url.value, from, to), + }; + + node.nodes = node.nodes.map(token => (token === url ? quoted : token)); + rebased = true; + + return false; + }); + + return rebased ? parsed.toString() : value; +}; + +const isLiteralUrl = (token: valueParser.Node): token is valueParser.StringNode | valueParser.WordNode => { + if (token.type === "string") { + return !token.unclosed && isLiteralPath(token.value); + } + + if (token.type === "word") { + return isLiteralPath(token.value) && !/[$()]/.test(token.value); + } + + return false; +}; + +const isLiteralPath = (value: string): boolean => { + return Boolean(value) && !ExternalUrlPattern.test(value) && !value.includes("#{"); +}; + +const rebaseStylePath = (request: string, from: string, to: string): string => { + const suffixIndex = request.search(/[?#]/); + const pathname = suffixIndex === -1 ? request : request.slice(0, suffixIndex); + const suffix = suffixIndex === -1 ? "" : request.slice(suffixIndex); + const directory = toPosix(path.relative(path.dirname(to), path.dirname(from))); + // Stylesheet requests are URLs, including on Windows. Keep query/fragment data out of path normalization. + const relative = path.posix.join(directory, pathname); + + return `${relative.startsWith("../") ? relative : `./${relative}`}${suffix}`; +}; diff --git a/src/cli/plugins/style/tests/fixtures/relative-conflict/app/content.scss b/src/cli/plugins/style/tests/fixtures/relative-conflict/app/content.scss new file mode 100644 index 00000000..c90b5443 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-conflict/app/content.scss @@ -0,0 +1,5 @@ +@use "./theme/tokens"; + +.badge { + background: tokens.$accent; +} diff --git a/src/cli/plugins/style/tests/fixtures/relative-conflict/app/theme/_tokens.scss b/src/cli/plugins/style/tests/fixtures/relative-conflict/app/theme/_tokens.scss new file mode 100644 index 00000000..2e174f9b --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-conflict/app/theme/_tokens.scss @@ -0,0 +1 @@ +$accent: blue; diff --git a/src/cli/plugins/style/tests/fixtures/relative-conflict/shared/content.scss b/src/cli/plugins/style/tests/fixtures/relative-conflict/shared/content.scss new file mode 100644 index 00000000..e4811819 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-conflict/shared/content.scss @@ -0,0 +1,5 @@ +@use "./theme/tokens"; + +.badge { + color: tokens.$accent; +} diff --git a/src/cli/plugins/style/tests/fixtures/relative-conflict/shared/theme/_tokens.scss b/src/cli/plugins/style/tests/fixtures/relative-conflict/shared/theme/_tokens.scss new file mode 100644 index 00000000..a96ed09a --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-conflict/shared/theme/_tokens.scss @@ -0,0 +1 @@ +$accent: red; diff --git a/src/cli/plugins/style/tests/fixtures/relative-modules/app/content.scss b/src/cli/plugins/style/tests/fixtures/relative-modules/app/content.scss new file mode 100644 index 00000000..0361da1d --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-modules/app/content.scss @@ -0,0 +1,10 @@ +@forward "./theme/tokens" as theme-* show $theme-accent with ($accent: blue); +@forward "./theme/hidden" hide $private; +@use "./theme/tokens" as appTokens; +@use "./theme/fonts" with ( + $font: url("./theme/Inter.woff2?browser&v=a/../b#face") +); + +.badge { + background: appTokens.$accent; +} diff --git a/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_hidden.scss b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_hidden.scss new file mode 100644 index 00000000..4f9ad094 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_hidden.scss @@ -0,0 +1 @@ +$private: red; diff --git a/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_metrics.scss b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_metrics.scss new file mode 100644 index 00000000..abc0c236 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_metrics.scss @@ -0,0 +1 @@ +$weight: 400; diff --git a/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_tokens.scss b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_tokens.scss new file mode 100644 index 00000000..3f610320 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/_tokens.scss @@ -0,0 +1 @@ +$accent: orange !default; diff --git a/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/fonts/_index.scss b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/fonts/_index.scss new file mode 100644 index 00000000..5e03864e --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-modules/app/theme/fonts/_index.scss @@ -0,0 +1,9 @@ +@use "../metrics"; + +$font: url("./default.woff2") !default; + +@font-face { + font-family: Inter; + font-weight: metrics.$weight; + src: $font; +} diff --git a/src/cli/plugins/style/tests/fixtures/relative-modules/shared/content.scss b/src/cli/plugins/style/tests/fixtures/relative-modules/shared/content.scss new file mode 100644 index 00000000..11116869 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-modules/shared/content.scss @@ -0,0 +1,5 @@ +@use "./theme/tokens" as sharedTokens; + +.badge { + color: sharedTokens.$accent; +} diff --git a/src/cli/plugins/style/tests/fixtures/relative-modules/shared/theme/_tokens.scss b/src/cli/plugins/style/tests/fixtures/relative-modules/shared/theme/_tokens.scss new file mode 100644 index 00000000..b511b753 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/relative-modules/shared/theme/_tokens.scss @@ -0,0 +1 @@ +$accent: red !default; diff --git a/src/cli/plugins/style/tests/fixtures/same-module/app/content.scss b/src/cli/plugins/style/tests/fixtures/same-module/app/content.scss new file mode 100644 index 00000000..d8333753 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/same-module/app/content.scss @@ -0,0 +1,5 @@ +@use "../shared/../common/tokens"; + +.badge { + color: tokens.$accent; +} diff --git a/src/cli/plugins/style/tests/fixtures/same-module/common/_tokens.scss b/src/cli/plugins/style/tests/fixtures/same-module/common/_tokens.scss new file mode 100644 index 00000000..a96ed09a --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/same-module/common/_tokens.scss @@ -0,0 +1 @@ +$accent: red; diff --git a/src/cli/plugins/style/tests/fixtures/same-module/shared/content.scss b/src/cli/plugins/style/tests/fixtures/same-module/shared/content.scss new file mode 100644 index 00000000..8b796477 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/same-module/shared/content.scss @@ -0,0 +1,5 @@ +@use "./../common/tokens"; + +.badge { + color: tokens.$accent; +} diff --git a/src/cli/plugins/style/tests/fixtures/url-values/app/content.scss b/src/cli/plugins/style/tests/fixtures/url-values/app/content.scss new file mode 100644 index 00000000..81b17045 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/url-values/app/content.scss @@ -0,0 +1,13 @@ +/* url("./comment.svg") */ +$icon: url("./theme/icon.svg"); + +.badge { + background: url(theme/plain.svg); + content: "url(./text.svg)"; +} + +@supports (background: url("./theme/support.svg")) { + .badge { + color: blue; + } +} diff --git a/src/cli/plugins/style/tests/fixtures/url-values/shared/content.scss b/src/cli/plugins/style/tests/fixtures/url-values/shared/content.scss new file mode 100644 index 00000000..37bd50e7 --- /dev/null +++ b/src/cli/plugins/style/tests/fixtures/url-values/shared/content.scss @@ -0,0 +1,5 @@ +$logo: url(./logo.svg); + +.badge { + background: url("./shared.svg"); +} diff --git a/src/cli/plugins/style/utils.test.ts b/src/cli/plugins/style/utils.test.ts index e91ccb93..3e9c355a 100644 --- a/src/cli/plugins/style/utils.test.ts +++ b/src/cli/plugins/style/utils.test.ts @@ -1,17 +1,29 @@ import fs from "fs"; import path from "path"; import sass from "sass"; +import {pathToFileURL} from "url"; -import {mergeStyleSources} from "./utils"; +import {mergeStyleSources, type StyleSource} from "./utils"; const fixtures = path.resolve(__dirname, "tests", "fixtures"); -const mergeFixture = (name: string): string => { - const fixture = (...parts: string[]): string => fs.readFileSync(path.join(fixtures, name, ...parts), "utf8"); +const readSource = (...parts: string[]): StyleSource => { + const filename = path.join(fixtures, ...parts); - return mergeStyleSources(fixture("shared.scss"), fixture("app.scss")); + return {filename, content: fs.readFileSync(filename, "utf8")}; }; +const mergeFixture = (name: string): string => + mergeStyleSources(readSource(name, "shared.scss"), readSource(name, "app.scss")); + +const mergeLocatedFixture = (name: string): string => + mergeStyleSources(readSource(name, "shared", "content.scss"), readSource(name, "app", "content.scss")); + +const compileLocatedFixture = (name: string): string => + sass.compileString(mergeLocatedFixture(name), { + url: pathToFileURL(path.join(fixtures, name, "shared", "content.scss")), + }).css; + const normalizeStyle = (style: string): string => style.replace(/\s+/g, " ").trim(); describe("mergeStyleSources", () => { @@ -134,6 +146,62 @@ describe("mergeStyleSources", () => { expect(normalizeStyle(merged).match(/\.badge \{ color: red; \}/g)).toHaveLength(2); }); + + test("rebases module requests before merging and preserves Sass configuration and forwarding", () => { + const merged = mergeLocatedFixture("relative-modules"); + + expect(merged).toContain('@use "./theme/tokens" as sharedTokens;'); + expect(merged).toContain('@use "../app/theme/tokens" as appTokens;'); + expect(merged).toContain('@forward "../app/theme/tokens" as theme-* show $theme-accent with ($accent: blue);'); + expect(merged).toContain('@forward "../app/theme/hidden" hide $private;'); + expect(normalizeStyle(merged)).toContain( + '@use "../app/theme/fonts" with ( $font: url("../app/theme/Inter.woff2?browser&v=a/../b#face") );' + ); + + const css = compileLocatedFixture("relative-modules"); + + expect(css).toContain("color: red"); + expect(css).toContain("background: blue"); + expect(css).toContain("font-weight: 400"); + expect(css).toContain('url("../app/theme/Inter.woff2?browser&v=a/../b#face")'); + }); + + test("does not deduplicate identical requests to different files or hide their namespace conflict", () => { + const merged = mergeLocatedFixture("relative-conflict"); + + expect(merged).toContain('@use "./theme/tokens";'); + expect(merged).toContain('@use "../app/theme/tokens";'); + expect(() => compileLocatedFixture("relative-conflict")).toThrow(/already a module with namespace "tokens"/); + }); + + test("normalizes shared requests so equivalent relative requests to the same module deduplicate", () => { + const merged = mergeLocatedFixture("same-module"); + + expect(merged).not.toContain("./../common/tokens"); + expect(merged.match(/@use "\.\.\/common\/tokens";/g)).toHaveLength(1); + expect(compileLocatedFixture("same-module").match(/color: red/g)).toHaveLength(2); + }); + + test("rebases app URLs in declarations and at-rule parameters and leaves shared values and comments as written", () => { + const merged = normalizeStyle(mergeLocatedFixture("url-values")); + + for (const rebased of [ + '$icon: url("../app/theme/icon.svg");', + 'background: url("../app/theme/plain.svg");', + '@supports (background: url("../app/theme/support.svg"))', + ]) { + expect(merged).toContain(rebased); + } + + for (const unchanged of [ + "$logo: url(./logo.svg);", + 'background: url("./shared.svg");', + 'content: "url(./text.svg)";', + '/* url("./comment.svg") */', + ]) { + expect(merged).toContain(unchanged); + } + }); }); declare global { diff --git a/src/cli/plugins/style/utils.ts b/src/cli/plugins/style/utils.ts index ee227633..695d51a7 100644 --- a/src/cli/plugins/style/utils.ts +++ b/src/cli/plugins/style/utils.ts @@ -1,6 +1,13 @@ import {ChildNode} from "postcss"; import * as scss from "postcss-scss"; +import {rebaseModuleRequest, rebaseUrlFunctions} from "./rebase"; + +export type StyleSource = { + filename: string; + content: string; +}; + type StyleSourceParts = { charsets: string[]; sassPrelude: string[]; @@ -8,9 +15,9 @@ type StyleSourceParts = { body: string[]; }; -export const mergeStyleSources = (sharedStyle: string, appStyle: string): string => { - const shared = splitStyleSource(sharedStyle); - const app = splitStyleSource(appStyle); +export const mergeStyleSources = (sharedStyle: StyleSource, appStyle: StyleSource): string => { + const shared = splitStyleSource(sharedStyle, sharedStyle.filename); + const app = splitStyleSource(appStyle, sharedStyle.filename); return [ ...dedupePrelude([...shared.charsets, ...app.charsets]), @@ -41,8 +48,29 @@ const dedupePrelude = (parts: string[]): string[] => { }); }; -const splitStyleSource = (source: string): StyleSourceParts => { - const root = scss.parse(source); +const splitStyleSource = (source: StyleSource, target: string): StyleSourceParts => { + const root = scss.parse(source.content, {from: source.filename}); + // Only a source compiled under another filename needs its resources rebased. + const relocated = source.filename !== target; + + root.walkAtRules(node => { + if (isModuleRuleNode(node)) { + // Shared requests pass through the same normalization, so equivalent prepared + // imports from both sources match when the prelude is deduplicated. + node.params = rebaseModuleRequest(node.params, source.filename, target); + } + + if (relocated) { + // Module configuration such as `with ($font: url(...))` carries app resources as well. + node.params = rebaseUrlFunctions(node.params, source.filename, target); + } + }); + + if (relocated) { + root.walkDecls(node => { + node.value = rebaseUrlFunctions(node.value, source.filename, target); + }); + } const parts: StyleSourceParts = { charsets: [], @@ -57,22 +85,26 @@ const splitStyleSource = (source: string): StyleSourceParts => { root.nodes?.forEach(node => { if (!bodyStarted && isCharsetNode(node)) { parts.charsets.push(stringifyNode(node)); + return; } if (!bodyStarted && node.type === "comment") { parts[preludeType === "sass" ? "sassPrelude" : "cssPrelude"].push(stringifyNode(node)); + return; } if (!bodyStarted && isSassPreludeNode(node)) { parts.sassPrelude.push(stringifyNode(node)); + return; } if (!bodyStarted && isCssPreludeNode(node)) { preludeType = "css"; parts.cssPrelude.push(stringifyNode(node)); + return; } @@ -95,16 +127,14 @@ const stringifyNode = (node: ChildNode): string => { const isCharsetNode = (node: ChildNode): boolean => node.type === "atrule" && node.name === "charset"; +const isModuleRuleNode = (node: ChildNode): boolean => node.type === "atrule" && ["forward", "use"].includes(node.name); + const isSassPreludeNode = (node: ChildNode): boolean => { if (node.type === "decl") { return node.prop.startsWith("$"); } - if (node.type !== "atrule") { - return false; - } - - return ["forward", "use"].includes(node.name); + return isModuleRuleNode(node); }; const isCssPreludeNode = (node: ChildNode): boolean => { diff --git a/src/types/config.ts b/src/types/config.ts index 58b57653..f49c45af 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -449,7 +449,10 @@ export interface Config { /** * Flag indicating whether to merge styles from App and Shared directories. - * When `true`, styles from both directories will be combined. + * When `true`, importing a shared stylesheet also compiles the app stylesheet at the same + * relative path into the same CSS module, with shared rules before app rules. Relative `@use` + * and `@forward` requests and literal `url(...)` values in the merged sources resolve from the + * file that declares them. * * @default true */ diff --git a/tests/integration/README.md b/tests/integration/README.md index d00f7080..4a0dcffb 100644 --- a/tests/integration/README.md +++ b/tests/integration/README.md @@ -48,6 +48,12 @@ tests/integration/ │ ├── permissions/ │ │ ├── permissions.integration.test.ts │ │ └── views/ +│ ├── style/ +│ │ ├── style-merge.integration.test.ts +│ │ ├── compiler.ts +│ │ └── fixtures/ +│ │ ├── multi-app/ +│ │ └── multi-app-updates/ │ └── … ├── browser/ │ ├── relay/ @@ -232,6 +238,7 @@ Parallel test sessions use distinct working directories; production directory na - `browser/options`: two Chrome MV3 cases covering Vanilla and React rendering, CSS, state/events, opening Options from background, and a View chunk shared with a Page. - `build/override`: separate New Tab, Bookmarks, History and competing-entrypoint files let Jest schedule the scenarios across workers. The files share assertions, while each case keeps its own application copy and build. Thirty manifest checks cover the New Tab, Bookmarks, and History applications across Chrome, Edge, Opera, Safari, and Firefox in MV2 and MV3. Supporting browsers receive `chrome_url_overrides`, the page HTML, its CSP, and the permission declared by the entrypoint; the others receive none of them. Three more cases add the History entrypoint to a copy of the New Tab application: Chrome and Edge builds fail naming both entrypoints, while Firefox keeps the New Tab with its own permission and without the History one. No browser is launched. - `build/permissions/views`: four manifest checks of one application with two popups, two sidebars, and an options page that each declare permissions. Every built view contributes, including a popup and a sidebar that are not applied by default; Chrome MV2 has no sidebar, so its permissions are not requested; Firefox builds the sidebar action without `sidePanel`; MV2 declares hosts as permissions. No browser is launched. +- `build/style`: shared and app stylesheet merging with `mergeStyles` through the production style and asset rules. Two applications with the same layout and different fonts each receive one CSS module per destination, the same class identifier in ordinary and `?unisolated` output, and their own font. App overrides with a namespace conflict, a missing module, or invalid SCSS fail the build. Watch mode tracks override creation, edits, imported partials, resources, removal, and recreation. No browser is launched. - `browser/override`: three Chrome MV3 cases opening `chrome://newtab`, `chrome://bookmarks`, and `chrome://history`. They verify that Chrome serves the extension page, React and Vanilla rendering, CSS, state/events, custom `as` and `htmlDir` naming, and a View chunk shared with a Page. - `browser/offscreen/service`: one Chrome MV3 round trip from background through Offscreen to a registered background service. - `browser/content/entrypoint-assets`: one Chrome MV3 case and two Firefox cases (MV2 and MV3) using the same application and probe assertions. They cover current asset getters, rejecting the full-map getter outside background, common chunks, dynamic imports, CSS/SVG resource URLs, world separation, and top/child frames. The Chrome case also reads the full-map readiness flag in background. diff --git a/tests/integration/build/style/compiler.ts b/tests/integration/build/style/compiler.ts new file mode 100644 index 00000000..b4f1e3f2 --- /dev/null +++ b/tests/integration/build/style/compiler.ts @@ -0,0 +1,109 @@ +import {cp, mkdtemp, realpath, rm} from "fs/promises"; +import os from "os"; +import path from "path"; +import {rspack, type Compilation, type Compiler, type Configuration, type Stats} from "@rspack/core"; + +import stylePlugin from "@cli/plugins/style"; +import assetPlugin from "@cli/plugins/asset"; +import {Browser} from "@typing/browser"; +import type {ReadonlyConfig} from "@typing/config"; + +const project = path.resolve(__dirname, "../../../.."); +export const fixtures = path.join(__dirname, "fixtures"); +export const entries = ["normal", "document", "asis"]; + +export const createFixture = async (): Promise => { + // Rspack resolves symlinks, including the macOS temporary-directory alias. + const root = await realpath(await mkdtemp(path.join(os.tmpdir(), "adnbn-style-merge-"))); + + try { + await cp(path.join(fixtures, "multi-app"), root, {recursive: true}); + } catch (error) { + await rm(root, {recursive: true, force: true}); + + throw error; + } + + return root; +}; + +export const createCompiler = async (root: string, app: string, mergeStyles = true): Promise => { + const config = { + rootDir: root, + srcDir: "src", + sharedDir: "shared", + appsDir: "apps", + appSrcDir: "", + app, + browser: Browser.Chrome, + mergeStyles, + cssDir: "css", + cssFilename: "[name].css", + cssIdentName: "[app]-[local]__[hash:base64:5]", + assetsDir: "assets", + assetsFilename: "[name].[contenthash:8][ext]", + } as ReadonlyConfig; + + const settings: Configuration[] = []; + + for (const plugin of [stylePlugin(), assetPlugin()]) { + const handler = plugin.bundler!; + + settings.push((typeof handler === "function" ? await handler({config, rspack: {}}) : handler) as Configuration); + } + + const [styles, assets] = settings; + + return rspack({ + context: root, + mode: "production", + devtool: false, + entry: Object.fromEntries(entries.map(entry => [entry, `./${entry}.js`])), + output: { + ...assets.output, + path: path.join(root, "dist", app), + filename: "[name].js", + publicPath: "", + library: {type: "commonjs2"}, + }, + resolve: { + alias: {"@tokens": path.join(root, "src/shared/theme/_alias.scss")}, + modules: [path.join(root, "vendor"), path.join(project, "node_modules")], + }, + resolveLoader: {modules: [path.join(project, "node_modules")]}, + module: {rules: [...styles.module!.rules!, ...assets.module!.rules!]}, + plugins: [...styles.plugins!, ...assets.plugins!], + optimization: {minimize: false}, + }); +}; + +export const runCompiler = (compiler: Compiler): Promise => + new Promise((resolve, reject) => { + compiler.run((error, stats) => { + if (error || !stats) { + reject(error ?? new Error("Style compilation did not return stats")); + } else { + resolve(stats); + } + }); + }); + +export const closeCompiler = (compiler: Compiler): Promise => + new Promise((resolve, reject) => { + compiler.close(error => { + if (error) { + reject(error); + } else { + resolve(); + } + }); + }); + +export const getCss = (compilation: Compilation, entry: string): string => { + return compilation.entrypoints + .get(entry)! + .getFiles() + .filter(file => file.endsWith(".css")) + .map(file => compilation.getAsset(file)!.source.source().toString()) + .join("\n"); +}; diff --git a/tests/integration/build/style/fixtures/multi-app-updates/Inter-Latin.woff2 b/tests/integration/build/style/fixtures/multi-app-updates/Inter-Latin.woff2 new file mode 100644 index 00000000..be70fdab --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app-updates/Inter-Latin.woff2 @@ -0,0 +1 @@ +updated-font-fixture diff --git a/tests/integration/build/style/fixtures/multi-app-updates/conflict.scss b/tests/integration/build/style/fixtures/multi-app-updates/conflict.scss new file mode 100644 index 00000000..8427cbc2 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app-updates/conflict.scss @@ -0,0 +1,5 @@ +@use "../theme/tokens" as sharedTokens; + +.badge { + color: sharedTokens.$accent; +} diff --git a/tests/integration/build/style/fixtures/multi-app-updates/content.scss b/tests/integration/build/style/fixtures/multi-app-updates/content.scss new file mode 100644 index 00000000..b8f35c9d --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app-updates/content.scss @@ -0,0 +1,10 @@ +@use "sass:math"; +@use "../theme/tokens" as appTokens; +@use "../theme/fonts" with ( + $inter-latin: url("../theme/fonts/Inter-Latin.woff2?browser&v=1#face") +); + +.badge { + color: appTokens.$accent; + --order: app-updated; +} diff --git a/tests/integration/build/style/fixtures/multi-app-updates/fonts.scss b/tests/integration/build/style/fixtures/multi-app-updates/fonts.scss new file mode 100644 index 00000000..4ae076d6 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app-updates/fonts.scss @@ -0,0 +1,9 @@ +@use "../metrics"; + +$inter-latin: url("./Inter-Latin.woff2?browser") !default; + +@font-face { + font-family: ExampleUpdated; + font-weight: metrics.$weight; + src: $inter-latin format("woff2"); +} diff --git a/tests/integration/build/style/fixtures/multi-app-updates/invalid.scss b/tests/integration/build/style/fixtures/multi-app-updates/invalid.scss new file mode 100644 index 00000000..9f908838 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app-updates/invalid.scss @@ -0,0 +1 @@ +.badge { diff --git a/tests/integration/build/style/fixtures/multi-app-updates/metrics.scss b/tests/integration/build/style/fixtures/multi-app-updates/metrics.scss new file mode 100644 index 00000000..05f1dc4b --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app-updates/metrics.scss @@ -0,0 +1 @@ +$weight: 700; diff --git a/tests/integration/build/style/fixtures/multi-app-updates/missing.scss b/tests/integration/build/style/fixtures/multi-app-updates/missing.scss new file mode 100644 index 00000000..ebbdf928 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app-updates/missing.scss @@ -0,0 +1 @@ +@use "../missing/theme"; diff --git a/tests/integration/build/style/fixtures/multi-app/asis.js b/tests/integration/build/style/fixtures/multi-app/asis.js new file mode 100644 index 00000000..c4c4a14f --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/asis.js @@ -0,0 +1 @@ +import "./src/shared/content/content.scss?asis"; diff --git a/tests/integration/build/style/fixtures/multi-app/document.js b/tests/integration/build/style/fixtures/multi-app/document.js new file mode 100644 index 00000000..b6beb654 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/document.js @@ -0,0 +1,3 @@ +import styles from "./src/shared/content/content.scss?unisolated"; + +export default styles; diff --git a/tests/integration/build/style/fixtures/multi-app/normal.js b/tests/integration/build/style/fixtures/multi-app/normal.js new file mode 100644 index 00000000..54953851 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/normal.js @@ -0,0 +1,3 @@ +import styles from "./src/shared/content/content.scss"; + +export default styles; diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/content/content.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/content/content.scss new file mode 100644 index 00000000..de9cc26e --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/content/content.scss @@ -0,0 +1,10 @@ +@use "sass:math"; +@use "../theme/tokens" as appTokens; +@use "../theme/fonts" with ( + $inter-latin: url("../theme/fonts/Inter-Latin.woff2?browser&v=1#face") +); + +.badge { + color: appTokens.$accent; + --order: app; +} diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_metrics.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_metrics.scss new file mode 100644 index 00000000..abc0c236 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_metrics.scss @@ -0,0 +1 @@ +$weight: 400; diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_tokens.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_tokens.scss new file mode 100644 index 00000000..2e174f9b --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/_tokens.scss @@ -0,0 +1 @@ +$accent: blue; diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/Inter-Latin.woff2 b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/Inter-Latin.woff2 new file mode 100644 index 00000000..626ce765 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/Inter-Latin.woff2 @@ -0,0 +1 @@ +alpha-font-fixture diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/_index.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/_index.scss new file mode 100644 index 00000000..07fdd6df --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/alpha/theme/fonts/_index.scss @@ -0,0 +1,9 @@ +@use "../metrics"; + +$inter-latin: url("./Inter-Latin.woff2?browser") !default; + +@font-face { + font-family: Example; + font-weight: metrics.$weight; + src: $inter-latin format("woff2"); +} diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/beta/content/content.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/content/content.scss new file mode 100644 index 00000000..de9cc26e --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/content/content.scss @@ -0,0 +1,10 @@ +@use "sass:math"; +@use "../theme/tokens" as appTokens; +@use "../theme/fonts" with ( + $inter-latin: url("../theme/fonts/Inter-Latin.woff2?browser&v=1#face") +); + +.badge { + color: appTokens.$accent; + --order: app; +} diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_metrics.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_metrics.scss new file mode 100644 index 00000000..abc0c236 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_metrics.scss @@ -0,0 +1 @@ +$weight: 400; diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_tokens.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_tokens.scss new file mode 100644 index 00000000..0a9308d8 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/_tokens.scss @@ -0,0 +1 @@ +$accent: green; diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/Inter-Latin.woff2 b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/Inter-Latin.woff2 new file mode 100644 index 00000000..966d9c69 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/Inter-Latin.woff2 @@ -0,0 +1 @@ +beta-font-fixture diff --git a/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/_index.scss b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/_index.scss new file mode 100644 index 00000000..07fdd6df --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/apps/beta/theme/fonts/_index.scss @@ -0,0 +1,9 @@ +@use "../metrics"; + +$inter-latin: url("./Inter-Latin.woff2?browser") !default; + +@font-face { + font-family: Example; + font-weight: metrics.$weight; + src: $inter-latin format("woff2"); +} diff --git a/tests/integration/build/style/fixtures/multi-app/src/shared/content/content.scss b/tests/integration/build/style/fixtures/multi-app/src/shared/content/content.scss new file mode 100644 index 00000000..ad588f9f --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/shared/content/content.scss @@ -0,0 +1,12 @@ +@use "sass:math"; +@use "@tokens" as aliasTokens; +@use "fixture-theme" as packageTokens; +@use "../theme/tokens" as sharedTokens; + +.badge { + color: sharedTokens.$accent; + width: math.div(4px, 2); + --alias: #{aliasTokens.$value}; + --package: #{packageTokens.$value}; + --order: shared; +} diff --git a/tests/integration/build/style/fixtures/multi-app/src/shared/theme/_alias.scss b/tests/integration/build/style/fixtures/multi-app/src/shared/theme/_alias.scss new file mode 100644 index 00000000..f8f07da0 --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/shared/theme/_alias.scss @@ -0,0 +1 @@ +$value: alias; diff --git a/tests/integration/build/style/fixtures/multi-app/src/shared/theme/_tokens.scss b/tests/integration/build/style/fixtures/multi-app/src/shared/theme/_tokens.scss new file mode 100644 index 00000000..a96ed09a --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/src/shared/theme/_tokens.scss @@ -0,0 +1 @@ +$accent: red; diff --git a/tests/integration/build/style/fixtures/multi-app/vendor/fixture-theme/_index.scss b/tests/integration/build/style/fixtures/multi-app/vendor/fixture-theme/_index.scss new file mode 100644 index 00000000..623ff60a --- /dev/null +++ b/tests/integration/build/style/fixtures/multi-app/vendor/fixture-theme/_index.scss @@ -0,0 +1 @@ +$value: package; diff --git a/tests/integration/build/style/style-merge.integration.test.ts b/tests/integration/build/style/style-merge.integration.test.ts new file mode 100644 index 00000000..d83eaf4b --- /dev/null +++ b/tests/integration/build/style/style-merge.integration.test.ts @@ -0,0 +1,259 @@ +import {readFile, rm, unlink, writeFile} from "fs/promises"; +import path from "path"; +import vm from "vm"; +import postcss from "postcss"; +import type {Compilation, Compiler, NormalModule, Stats} from "@rspack/core"; + +import {DefaultStylesLayer, DocumentStylesLayer} from "@cli/bundler/layers"; + +import {closeCompiler, createCompiler, createFixture, entries, fixtures, getCss, runCompiler} from "./compiler"; + +let root: string; +let compiler: Compiler | undefined; + +beforeEach(async () => { + root = await createFixture(); +}); + +afterEach(async () => { + try { + if (compiler) { + await closeCompiler(compiler); + } + } finally { + compiler = undefined; + await rm(root, {recursive: true, force: true}); + } +}); + +const getBadgeClass = (compilation: Compilation, entry: string): string => { + const script = compilation.getAsset(`${entry}.js`)!.source.source().toString(); + + return vm.runInNewContext(`${script}\nmodule.exports.default.badge`, {module: {exports: {}}}); +}; + +const getFont = (compilation: Compilation): string => { + return compilation + .getAssets() + .filter(asset => asset.name.endsWith(".woff2")) + .map(asset => asset.source.source().toString()) + .join(""); +}; + +const assertSuccess = (stats: Stats): void => { + expect(stats.toJson({all: false, errors: true}).errors).toEqual([]); +}; + +const hasDeclaration = (css: string, prop: string, value: string): boolean => { + let found = false; + + postcss.parse(css).walkDecls(prop, declaration => { + found ||= declaration.value === value; + }); + + return found; +}; + +test.each([ + ["alpha", "blue"], + ["beta", "green"], +])("merges %s styles into one CSS module per destination with its own resources", async (app, color) => { + compiler = await createCompiler(root, app); + const stats = await runCompiler(compiler); + assertSuccess(stats); + const {compilation} = stats; + const badge = getBadgeClass(compilation, "normal"); + + expect(badge).toMatch(new RegExp(`^${app}-badge__`)); + expect(getBadgeClass(compilation, "document")).toBe(badge); + + for (const entry of entries) { + const css = getCss(compilation, entry); + const selector = entry === "asis" ? ".badge" : `.${badge}`; + const rules = postcss.parse(css).nodes.filter(node => node.type === "rule"); + + expect(rules.map(rule => rule.selector)).toEqual([selector, selector]); + expect(hasDeclaration(rules[0].toString(), "color", "red")).toBe(true); + expect(hasDeclaration(rules[1].toString(), "color", color)).toBe(true); + expect(hasDeclaration(css, "width", "2px")).toBe(true); + expect(hasDeclaration(css, "--alias", "alias")).toBe(true); + expect(hasDeclaration(css, "--package", "package")).toBe(true); + expect(hasDeclaration(css, "font-weight", "400")).toBe(true); + expect(css).toMatch( + /url\(chrome-extension:\/\/__MSG_@@extension_id__\/assets\/Inter-Latin\.[a-f0-9]+\.woff2#face\)/ + ); + } + + const font = path.join(root, "src/apps", app, "theme/fonts/Inter-Latin.woff2"); + expect(Buffer.from(getFont(compilation))).toEqual(await readFile(font)); + + const sharedPath = path.join(root, "src/shared/content/content.scss"); + const resources = [...compilation.modules] + .filter((module): module is NormalModule => "resource" in module) + .map(module => ({resource: module.resource, layer: module.layer})); + + expect(resources).toEqual( + expect.arrayContaining([ + {resource: sharedPath, layer: DefaultStylesLayer}, + {resource: `${sharedPath}?unisolated`, layer: DocumentStylesLayer}, + {resource: `${sharedPath}?asis`, layer: DefaultStylesLayer}, + ]) + ); + + for (const filename of ["content/content.scss", "theme/fonts/_index.scss", "theme/_metrics.scss"]) { + expect(compilation.fileDependencies.has(path.join(root, "src/apps", app, filename))).toBe(true); + } +}); + +test("mergeStyles=false compiles the shared source without loading the app override", async () => { + compiler = await createCompiler(root, "alpha", false); + const stats = await runCompiler(compiler); + assertSuccess(stats); + + for (const entry of entries) { + const css = getCss(stats.compilation, entry); + + expect(hasDeclaration(css, "--order", "shared")).toBe(true); + expect(hasDeclaration(css, "--order", "app")).toBe(false); + expect(css).not.toContain("@font-face"); + } + + expect(stats.compilation.fileDependencies.has(path.join(root, "src/apps/alpha/content/content.scss"))).toBe(false); +}); + +test.each([ + ["conflict", /already a module with namespace "sharedTokens"/], + ["missing", /Can't find stylesheet to import/], + ["invalid", /Unclosed block/], +])("fails the build for a %s app override", async (name, message) => { + const appPath = path.join(root, "src/apps/alpha/content/content.scss"); + await writeFile(appPath, await readFile(path.join(fixtures, "multi-app-updates", `${name}.scss`))); + compiler = await createCompiler(root, "alpha"); + const stats = await runCompiler(compiler); + + expect(stats.hasErrors()).toBe(true); + expect(stats.toString({all: false, errors: true})).toMatch(message); +}); + +test.each(["alpha", "beta"])( + "watch tracks %s override creation, edits, dependencies and removal", + async app => { + const appPath = path.join(root, "src/apps", app, "content/content.scss"); + const initialSource = path.join(fixtures, "multi-app/src/apps", app, "content/content.scss"); + await unlink(appPath); + compiler = await createCompiler(root, app); + let complete: ((error: Error | null, stats?: Stats) => void) | undefined; + + const next = ( + description: string, + matches: (compilation: Compilation) => boolean, + update?: () => Promise, + expectErrors = false + ) => + new Promise((resolve, reject) => { + const timer = setTimeout(() => { + complete = undefined; + reject(new Error(`Style watch did not observe ${description} for ${app}`)); + }, 10_000); + + complete = (error, stats) => { + if (!error && stats && !stats.hasErrors() && (expectErrors || !matches(stats.compilation))) { + return; + } + + clearTimeout(timer); + complete = undefined; + + if (error || !stats || (!expectErrors && stats.hasErrors())) { + reject(error ?? new Error(stats?.toString({all: false, errors: true}))); + } else { + // Rspack reconnects the watcher on nextTick after delivering its compilation callback. + setImmediate(() => resolve(stats)); + } + }; + + void update?.().catch(error => complete?.(error)); + }); + + const contains = (prop: string, value: string) => (compilation: Compilation) => + entries.every(entry => hasDeclaration(getCss(compilation, entry), prop, value)); + const updateFrom = async (destination: string, filename: string): Promise => { + await writeFile(destination, await readFile(path.join(fixtures, "multi-app-updates", filename))); + }; + + const initial = next("shared styles without an override", contains("--order", "shared")); + const watcher = compiler.watch({poll: 50}, (error, stats) => complete?.(error, stats)); + + try { + let stats = await initial; + const badge = getBadgeClass(stats.compilation, "normal"); + + expect(stats.compilation.missingDependencies.has(appPath)).toBe(true); + + stats = await next("override creation", contains("--order", "app"), async () => { + await writeFile(appPath, await readFile(initialSource)); + }); + + expect(stats.compilation.fileDependencies.has(appPath)).toBe(true); + expect(getBadgeClass(stats.compilation, "normal")).toBe(badge); + + stats = await next("override edit", contains("--order", "app-updated"), () => + updateFrom(appPath, "content.scss") + ); + expect(getBadgeClass(stats.compilation, "normal")).toBe(badge); + + stats = await next( + "invalid override error", + () => true, + () => updateFrom(appPath, "invalid.scss"), + true + ); + + expect(stats.toString({all: false, errors: true})).toContain("Unclosed block"); + expect(stats.compilation.fileDependencies.has(appPath)).toBe(true); + + stats = await next("override correction", contains("--order", "app-updated"), () => + updateFrom(appPath, "content.scss") + ); + + expect(getBadgeClass(stats.compilation, "normal")).toBe(badge); + + await next("imported index edit", contains("font-family", "ExampleUpdated"), () => + updateFrom(path.join(root, "src/apps", app, "theme/fonts/_index.scss"), "fonts.scss") + ); + + await next("nested partial edit", contains("font-weight", "700"), () => + updateFrom(path.join(root, "src/apps", app, "theme/_metrics.scss"), "metrics.scss") + ); + + const expectedFont = await readFile(path.join(fixtures, "multi-app-updates/Inter-Latin.woff2"), "utf8"); + + await next( + "font resource edit", + compilation => getFont(compilation) === expectedFont, + () => updateFrom(path.join(root, "src/apps", app, "theme/fonts/Inter-Latin.woff2"), "Inter-Latin.woff2") + ); + + stats = await next( + "override removal", + compilation => + entries.every(entry => !hasDeclaration(getCss(compilation, entry), "--order", "app-updated")), + () => unlink(appPath) + ); + + expect(stats.compilation.missingDependencies.has(appPath)).toBe(true); + expect(getFont(stats.compilation)).toBe(""); + expect(getBadgeClass(stats.compilation, "normal")).toBe(badge); + + stats = await next("override recreation", contains("--order", "app-updated"), () => + updateFrom(appPath, "content.scss") + ); + + expect(getBadgeClass(stats.compilation, "normal")).toBe(badge); + expect(getFont(stats.compilation)).toBe(expectedFont); + } finally { + await new Promise(resolve => watcher.close(resolve)); + } + }, + 30_000 +); From 2a49fa4f1e1162a9f7dfc6af389c9779777a2597 Mon Sep 17 00:00:00 2001 From: Anjey Tsibylskij <130153594+atldays@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:22:40 +0300 Subject: [PATCH 36/36] feat(action): configure toolbar icon and title defaults --- src/cli/builders/manifest/Manifest.test.ts | 137 +++++++++++++++++- src/cli/builders/manifest/ManifestBase.ts | 21 +++ src/cli/builders/manifest/ManifestV2.ts | 20 +-- src/cli/builders/manifest/ManifestV3.ts | 20 +-- src/cli/plugins/action.ts | 11 ++ src/cli/plugins/index.ts | 1 + src/cli/resolvers/config.test.ts | 22 +++ src/cli/resolvers/config.ts | 4 + src/main/config.ts | 1 + src/types/action.ts | 10 ++ src/types/config.ts | 14 ++ src/types/manifest.ts | 6 + src/types/popup.ts | 3 +- src/types/sidebar.ts | 3 +- .../build/action/action.integration.test.ts | 92 ++++++++++++ .../build/action/fixture/adnbn.config.ts | 13 ++ .../build/action/fixture/package.json | 9 ++ .../build/action/fixture/src/icons/16.png | Bin 0 -> 82 bytes .../action/fixture/src/icons/active16.png | Bin 0 -> 82 bytes .../action/fixture/src/icons/popup16.png | Bin 0 -> 82 bytes .../build/action/fixture/src/locales/en.json | 8 + .../build/action/fixture/tsconfig.json | 3 + .../action/fixture/variants/action.command.ts | 8 + .../action/fixture/variants/full.popup.ts | 7 + .../action/fixture/variants/title.popup.ts | 6 + .../fixture/variants/unapplied.popup.ts | 8 + .../types/fixtures/registries/fallback.ts | 4 + .../types/fixtures/registries/generated.ts | 11 ++ 28 files changed, 407 insertions(+), 35 deletions(-) create mode 100644 src/cli/plugins/action.ts create mode 100644 src/types/action.ts create mode 100644 tests/integration/build/action/action.integration.test.ts create mode 100644 tests/integration/build/action/fixture/adnbn.config.ts create mode 100644 tests/integration/build/action/fixture/package.json create mode 100644 tests/integration/build/action/fixture/src/icons/16.png create mode 100644 tests/integration/build/action/fixture/src/icons/active16.png create mode 100644 tests/integration/build/action/fixture/src/icons/popup16.png create mode 100644 tests/integration/build/action/fixture/src/locales/en.json create mode 100644 tests/integration/build/action/fixture/tsconfig.json create mode 100644 tests/integration/build/action/fixture/variants/action.command.ts create mode 100644 tests/integration/build/action/fixture/variants/full.popup.ts create mode 100644 tests/integration/build/action/fixture/variants/title.popup.ts create mode 100644 tests/integration/build/action/fixture/variants/unapplied.popup.ts diff --git a/src/cli/builders/manifest/Manifest.test.ts b/src/cli/builders/manifest/Manifest.test.ts index c26e4a92..86940a60 100644 --- a/src/cli/builders/manifest/Manifest.test.ts +++ b/src/cli/builders/manifest/Manifest.test.ts @@ -1,8 +1,10 @@ import ManifestV2 from "./ManifestV2"; import ManifestV3 from "./ManifestV3"; import {Browser, DataCollectionPermission} from "@typing/browser"; +import {CommandExecuteActionName} from "@typing/command"; +import type {ActionOptions} from "@typing/action"; import {Language} from "@typing/locale"; -import {ManifestIncognito, type OptionalManifest} from "@typing/manifest"; +import {ManifestIncognito, type ManifestPopup, type OptionalManifest} from "@typing/manifest"; describe("Manifest primitive properties", () => { it("name", () => { @@ -265,6 +267,139 @@ describe("Manifest common builder methods", () => { }); }); +describe.each([ + {version: 2, Builder: ManifestV2, field: "browser_action"}, + {version: 3, Builder: ManifestV3, field: "action"}, +] as const)("MV$version toolbar action", ({Builder, field}) => { + const createBuilder = () => + new Builder(Browser.Chrome) + .setName("Extension") + .setIcon("extension") + .setIcons( + new Map([ + ["default", new Map([[16, "icons/default.png"]])], + ["extension", new Map([[16, "icons/extension.png"]])], + ["action", new Map([[16, "icons/action.png"]])], + ["popup", new Map([[16, "icons/popup.png"]])], + ]) + ); + + it("does not declare a button for extension metadata or ordinary commands alone", () => { + const manifest = createBuilder() + .setCommands(new Set([{name: "open-settings", description: "Open settings"}])) + .build(); + + expect(manifest).not.toHaveProperty(field); + expect(manifest.icons).toEqual({16: "icons/extension.png"}); + }); + + it("declares configured appearance without a popup, command or background", () => { + const manifest = createBuilder().setAction({icon: "action", title: "Action"}).build(); + + expect(manifest).toHaveProperty(field, { + default_icon: {16: "icons/action.png"}, + default_title: "Action", + }); + expect(manifest).not.toHaveProperty(`${field}.default_popup`); + expect(manifest).not.toHaveProperty("commands"); + expect(manifest).not.toHaveProperty("background"); + expect(manifest.icons).toEqual({16: "icons/extension.png"}); + }); + + it("uses extension defaults for an explicit empty action", () => { + expect(createBuilder().setAction({}).build()).toHaveProperty(field, { + default_icon: {16: "icons/extension.png"}, + default_title: "Extension", + }); + }); + + it("uses extension defaults for an execute-action command", () => { + const manifest = createBuilder() + .setCommands(new Set([{name: CommandExecuteActionName}])) + .build(); + + expect(manifest).toHaveProperty(field, { + default_icon: {16: "icons/extension.png"}, + default_title: "Extension", + }); + }); + + it("uses configured appearance with an execute-action command", () => { + const manifest = createBuilder() + .setCommands(new Set([{name: CommandExecuteActionName}])) + .setAction({icon: "action", title: "Action"}) + .build(); + + expect(manifest).toHaveProperty(field, { + default_icon: {16: "icons/action.png"}, + default_title: "Action", + }); + }); + + it.each([ + {options: {}, title: "Action", icon: "action"}, + {options: {title: "Popup"}, title: "Popup", icon: "action"}, + {options: {icon: "popup"}, title: "Action", icon: "popup"}, + {options: {title: "Popup", icon: "popup"}, title: "Popup", icon: "popup"}, + ])("overrides popup fields independently: $options", ({options, title, icon}) => { + const action: ActionOptions = {icon: "action", title: "Action"}; + const popup: ManifestPopup = {path: "popup.html", ...options}; + + const before = createBuilder().setAction(action).setPopup(popup).build(); + const after = createBuilder().setPopup(popup).setAction(action).build(); + + expect(before).toHaveProperty(field, { + default_icon: {16: `icons/${icon}.png`}, + default_title: title, + default_popup: "popup.html", + }); + expect(after).toEqual(before); + }); + + it("uses extension defaults when the popup has no appearance overrides", () => { + expect(createBuilder().setPopup({path: "popup.html"}).build()).toHaveProperty(field, { + default_icon: {16: "icons/extension.png"}, + default_title: "Extension", + default_popup: "popup.html", + }); + }); + + it("preserves an explicitly empty tooltip", () => { + expect(createBuilder().setAction({title: ""}).build()).toHaveProperty(`${field}.default_title`, ""); + }); + + it("can declare a button without available icons", () => { + const manifest = new Builder(Browser.Chrome).setName("Extension").setAction({}).build(); + + expect(manifest).toHaveProperty(field, {default_title: "Extension"}); + }); + + it("keeps the existing default-group fallback for unknown icon groups", () => { + const manifest = createBuilder().setAction({icon: "missing"}).build(); + + expect(manifest).toHaveProperty(`${field}.default_icon`, {16: "icons/default.png"}); + }); + + it("clears popup overrides and explicit action settings without retaining stale data", () => { + const builder = createBuilder() + .setAction({icon: "action", title: "Action"}) + .setPopup({path: "popup.html", icon: "popup", title: "Popup"}); + + expect(builder.build()).toHaveProperty(`${field}.default_title`, "Popup"); + + builder.setPopup(undefined); + + expect(builder.build()).toHaveProperty(field, { + default_icon: {16: "icons/action.png"}, + default_title: "Action", + }); + + builder.setAction(undefined); + + expect(builder.build()).not.toHaveProperty(field); + }); +}); + describe.each([ ["ManifestV2", ManifestV2], ["ManifestV3", ManifestV3], diff --git a/src/cli/builders/manifest/ManifestBase.ts b/src/cli/builders/manifest/ManifestBase.ts index 4a28e719..8ee999c1 100644 --- a/src/cli/builders/manifest/ManifestBase.ts +++ b/src/cli/builders/manifest/ManifestBase.ts @@ -7,6 +7,7 @@ import {mergeWebAccessibleResources, normalizeDataCollectionPermissions} from ". import { CoreManifest, Manifest, + ManifestAction, ManifestAccessibleResource, ManifestAccessibleResources, ManifestBackground, @@ -36,6 +37,7 @@ import {CommandExecuteActionName} from "@typing/command"; import {DefaultIconGroupName} from "@typing/icon"; import {SidebarAlternativeBrowsers} from "@typing/sidebar"; import {ContentScriptMatches} from "@typing/content"; +import {ActionOptions} from "@typing/action"; type ManifestV3 = chrome.runtime.ManifestV3; type ManifestPermission = chrome.runtime.ManifestPermission; @@ -57,6 +59,7 @@ export default abstract class implements ManifestBuilder protected minimumVersion?: string; protected version?: string; protected icon?: string; + protected action?: ActionOptions; protected incognito?: ManifestIncognito; protected specific?: BrowserSpecific; protected locale?: Language; @@ -263,6 +266,12 @@ export default abstract class implements ManifestBuilder return this; } + public setAction(action?: ActionOptions): this { + this.action = action; + + return this; + } + public setSidebar(sidebar?: ManifestSidebar): this { this.sidebar = sidebar; @@ -816,6 +825,18 @@ export default abstract class implements ManifestBuilder return inInternalCommands || inOptionalCommands; } + protected getAction(): ManifestAction | undefined { + if (!this.action && !this.popup && !this.hasExecuteActionCommand()) { + return; + } + + return { + default_title: this.popup?.title ?? this.action?.title ?? this.name, + default_icon: this.getIconsByName(this.popup?.icon ?? this.action?.icon ?? this.icon), + ...(this.popup ? {default_popup: this.popup.path} : {}), + }; + } + protected getIconsByName(name?: string): CoreManifestIcons | undefined { if (this.icons.size === 0) { return; diff --git a/src/cli/builders/manifest/ManifestV2.ts b/src/cli/builders/manifest/ManifestV2.ts index 9bab70c9..09c49190 100644 --- a/src/cli/builders/manifest/ManifestV2.ts +++ b/src/cli/builders/manifest/ManifestV2.ts @@ -19,22 +19,10 @@ export default class extends ManifestBase { } protected buildAction(): Partial | undefined { - if (this.popup) { - const {icon, title, path} = this.popup; - - return { - browser_action: { - default_title: title || this.name, - default_popup: path, - default_icon: this.getIconsByName(icon), - }, - }; - } else if (this.hasExecuteActionCommand()) { - return { - browser_action: { - default_title: this.name, - }, - }; + const action = this.getAction(); + + if (action) { + return {browser_action: action}; } } diff --git a/src/cli/builders/manifest/ManifestV3.ts b/src/cli/builders/manifest/ManifestV3.ts index 0482a80f..bd3ca173 100644 --- a/src/cli/builders/manifest/ManifestV3.ts +++ b/src/cli/builders/manifest/ManifestV3.ts @@ -53,22 +53,10 @@ export default class extends ManifestBase { } protected buildAction(): Partial | undefined { - if (this.popup) { - const {path, icon, title} = this.popup; - - return { - action: { - default_title: title || this.name, - default_popup: path, - default_icon: this.getIconsByName(icon), - }, - }; - } else if (this.hasExecuteActionCommand()) { - return { - action: { - default_title: this.name, - }, - }; + const action = this.getAction(); + + if (action) { + return {action}; } } diff --git a/src/cli/plugins/action.ts b/src/cli/plugins/action.ts new file mode 100644 index 00000000..50140fab --- /dev/null +++ b/src/cli/plugins/action.ts @@ -0,0 +1,11 @@ +import {definePlugin} from "@main/plugin"; +import {modifyLocaleMessageKey} from "@shared/locale"; + +export default definePlugin(() => ({ + name: "adnbn:action", + manifest: ({config, manifest}) => { + const {action} = config; + + manifest.setAction(action ? {icon: action.icon, title: modifyLocaleMessageKey(action.title)} : undefined); + }, +})); diff --git a/src/cli/plugins/index.ts b/src/cli/plugins/index.ts index 4ac57cb3..f5ab67d5 100644 --- a/src/cli/plugins/index.ts +++ b/src/cli/plugins/index.ts @@ -1,3 +1,4 @@ +export {default as pluginAction} from "./action"; export {default as pluginAsset} from "./asset"; export {default as pluginBundler} from "./bundler"; export {default as pluginBackground} from "./background"; diff --git a/src/cli/resolvers/config.test.ts b/src/cli/resolvers/config.test.ts index 410e8c50..a9bf835e 100644 --- a/src/cli/resolvers/config.test.ts +++ b/src/cli/resolvers/config.test.ts @@ -2,6 +2,7 @@ jest.mock("../plugins", () => { const plugin = (name: string) => () => ({name}); return { + pluginAction: plugin("action"), pluginAsset: plugin("asset"), pluginBackground: plugin("background"), pluginBundler: plugin("bundler"), @@ -61,6 +62,27 @@ describe("config resolver", () => { expect(config.lang).toBe(Language.English); }); + test("does not enable an action by default", async () => { + const config = await resolveConfig({configFile: "package.json"}); + + expect(config.action).toBeUndefined(); + }); + + test("preserves explicit action options from build config", async () => { + const action = {icon: "active", title: "@action.title"}; + const config = await resolveConfig({configFile: "package.json", action}); + + expect(config.action).toEqual(action); + }); + + test("preserves an explicit empty action from user config", async () => { + mockedLoadConfig.mockResolvedValue({config: {action: {}}}); + + const config = await resolveConfig({configFile: "package.json"}); + + expect(config.action).toEqual({}); + }); + test("normalizes language from user config", async () => { mockedLoadConfig.mockResolvedValue({ config: { diff --git a/src/cli/resolvers/config.ts b/src/cli/resolvers/config.ts index fa7a296e..3191a733 100644 --- a/src/cli/resolvers/config.ts +++ b/src/cli/resolvers/config.ts @@ -4,6 +4,7 @@ import {loadConfig} from "c12"; import _ from "lodash"; import { + pluginAction, pluginAsset, pluginBackground, pluginBundler, @@ -205,6 +206,7 @@ export default async (config: OptionalConfig): Promise => { author = undefined, homepage = "HOMEPAGE", icon = DefaultIconGroupName, + action, lang = Language.English, incognito, specific, @@ -279,6 +281,7 @@ export default async (config: OptionalConfig): Promise => { homepage, lang: resolveLanguage(lang), icon, + action, incognito, specific, manifest, @@ -367,6 +370,7 @@ export default async (config: OptionalConfig): Promise => { pluginStyle(), pluginLocale(), pluginMeta(), + pluginAction(), pluginContent(), pluginBackground(), pluginOptions(), diff --git a/src/main/config.ts b/src/main/config.ts index 24f06f88..27e518a7 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -1,5 +1,6 @@ import type {Config, ConfigDefinition, ReadonlyConfig, UserConfig} from "@typing/config"; +export type {ActionOptions} from "@typing/action"; export type {Config, ReadonlyConfig, ConfigDefinition, UserConfig}; export const defineConfig = (config: ConfigDefinition): ConfigDefinition => { diff --git a/src/types/action.ts b/src/types/action.ts new file mode 100644 index 00000000..8f9c3f8b --- /dev/null +++ b/src/types/action.ts @@ -0,0 +1,10 @@ +import type {IconName} from "@typing/icon"; + +/** Default appearance of the extension's toolbar button. */ +export interface ActionOptions { + /** Icon group name. Falls back to `config.icon` when omitted. */ + icon?: IconName; + + /** Tooltip text or locale key, such as `@action.title`. Defaults to the extension name. */ + title?: string; +} diff --git a/src/types/config.ts b/src/types/config.ts index f49c45af..8dd9c958 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -8,6 +8,7 @@ import {Plugin} from "@typing/plugin"; import {Language} from "@typing/locale"; import {Awaiter} from "@typing/helpers"; import {EnvFilterOptions, EnvFilterVariant} from "@typing/env"; +import {ActionOptions} from "@typing/action"; /** * Configuration options for building a browser extension. This interface defines @@ -151,6 +152,19 @@ export interface Config { */ icon: string; + /** + * Default appearance of the toolbar button (`action` in MV3, `browser_action` in MV2). + * An explicit object, including `{}`, declares the button without requiring a popup or command. + * It does not register a click handler. + * + * The applied popup overrides each explicitly specified icon/title independently. + * Missing values fall back to these settings, then to `icon` and the extension name. + * When omitted, only an applied popup or execute-action command declares the button. + * + * @example {icon: "active", title: "@action.title"} + */ + action?: ActionOptions; + /** * Browser-specific settings (populate manifest.browser_specific_settings). * diff --git a/src/types/manifest.ts b/src/types/manifest.ts index bcbb1a66..1913a1ee 100644 --- a/src/types/manifest.ts +++ b/src/types/manifest.ts @@ -5,6 +5,7 @@ import {Language} from "@typing/locale"; import {BrowserSpecific, DataCollectionPermission} from "@typing/browser"; import {CspConfig} from "@typing/csp"; import {SandboxCspConfig} from "@typing/sandbox"; +import {ActionOptions} from "@typing/action"; export const ManifestMatchSchemes: ReadonlySet = new Set(["http", "https", "file", "ftp", "ws", "wss"]); @@ -33,6 +34,8 @@ export type ManifestPermission = chrome.runtime.ManifestPermission; export type ManifestOptionalPermission = chrome.runtime.ManifestOptionalPermission; +export type ManifestAction = NonNullable; + type ManifestFixed = Omit & { manifest_version: ManifestVersion; }; @@ -120,6 +123,9 @@ export interface ManifestBuilder { setIcon(icon?: string): this; // name of an icon set for manifest.icons + // Toolbar button + setAction(action?: ActionOptions): this; + // Entry setBackground(background?: ManifestBackground): this; diff --git a/src/types/popup.ts b/src/types/popup.ts index 1d11b310..22d44fc3 100644 --- a/src/types/popup.ts +++ b/src/types/popup.ts @@ -2,6 +2,7 @@ import {ViewDefinition, ViewOptions} from "@typing/view"; import {CspOptions} from "@typing/csp"; import {PermissionsOptions} from "@typing/permissions"; import type {ManifestPopup} from "@typing/manifest"; +import type {IconName} from "@typing/icon"; /** * Empty because popup aliases depend on the consuming application's entrypoints. @@ -14,7 +15,7 @@ export type PopupAlias = keyof PopupAliasRegistry extends never ? string : Extra export type PopupMap = Map; export interface PopupConfig { - icon?: string; + icon?: IconName; apply?: boolean; } diff --git a/src/types/sidebar.ts b/src/types/sidebar.ts index 2a7bf5e4..73b76995 100644 --- a/src/types/sidebar.ts +++ b/src/types/sidebar.ts @@ -3,6 +3,7 @@ import {Browser} from "@typing/browser"; import {CspOptions} from "@typing/csp"; import {PermissionsOptions} from "@typing/permissions"; import type {ManifestSidebar} from "@typing/manifest"; +import type {IconName} from "@typing/icon"; export const SidebarAlternativeBrowsers: ReadonlySet = new Set([Browser.Opera, Browser.Firefox]); @@ -19,7 +20,7 @@ export type SidebarAlias = keyof SidebarAliasRegistry extends never export type SidebarMap = Map; export interface SidebarConfig { - icon?: string; + icon?: IconName; apply?: boolean; } diff --git a/tests/integration/build/action/action.integration.test.ts b/tests/integration/build/action/action.integration.test.ts new file mode 100644 index 00000000..15ab1d55 --- /dev/null +++ b/tests/integration/build/action/action.integration.test.ts @@ -0,0 +1,92 @@ +import {copyFile, readFile, stat} from "fs/promises"; +import path from "path"; + +import {createIntegrationFixture} from "../../utils/fixture"; + +const projectRoot = path.resolve(__dirname, "..", "..", "..", ".."); +const fixtureDir = path.join(__dirname, "fixture"); + +jest.setTimeout(90_000); + +describe.each([ + {browser: "chrome", manifestVersion: 3, actionField: "action"}, + {browser: "chrome", manifestVersion: 2, actionField: "browser_action"}, + {browser: "firefox", manifestVersion: 3, actionField: "action"}, + {browser: "firefox", manifestVersion: 2, actionField: "browser_action"}, +] as const)("$browser MV$manifestVersion action", ({browser, manifestVersion, actionField}) => { + test.each([ + {scenario: "config only", entry: undefined, popup: false, command: false, icon: "active", title: "action"}, + { + scenario: "execute action", + entry: "action.command.ts", + popup: false, + command: true, + icon: "active", + title: "action", + }, + { + scenario: "popup overrides", + entry: "full.popup.ts", + popup: true, + command: false, + icon: "popup", + title: "popup", + }, + { + scenario: "popup title only", + entry: "title.popup.ts", + popup: true, + command: false, + icon: "active", + title: "popup", + }, + { + scenario: "unapplied popup", + entry: "unapplied.popup.ts", + popup: false, + command: false, + icon: "active", + title: "action", + }, + ])("$scenario", async ({entry, popup, command, icon, title}) => { + const fixture = await createIntegrationFixture(projectRoot, fixtureDir); + + try { + if (entry) { + const filename = command ? "action.command.ts" : "popup.ts"; + + await copyFile( + path.join(fixture.directory, "variants", entry), + path.join(fixture.directory, "src", filename) + ); + } + + const extensionDir = await fixture.build({browser, manifestVersion}); + const manifest = JSON.parse(await readFile(path.join(extensionDir, "manifest.json"), "utf8")); + const action = manifest[actionField]; + const messages = JSON.parse( + await readFile(path.join(extensionDir, "_locales", "en", "messages.json"), "utf8") + ); + + expect(action).toEqual({ + default_icon: {16: `/icons/${icon}-16.png`}, + default_title: `__MSG_${title}_title__`, + ...(popup ? {default_popup: "popup.html"} : {}), + }); + expect(manifest.icons).toEqual({16: "/icons/16.png"}); + expect(messages.action_title.message).toBe("Run the action"); + expect(messages.popup_title.message).toBe("Open the popup"); + expect((await stat(path.join(extensionDir, "icons", `${icon}-16.png`))).isFile()).toBe(true); + + if (command) { + expect(manifest.commands).toBeDefined(); + expect(manifest.background).toBeDefined(); + } else { + expect(manifest.commands).toBeUndefined(); + expect(manifest.background).toBeUndefined(); + } + } finally { + await fixture.dispose(); + } + }); +}); diff --git a/tests/integration/build/action/fixture/adnbn.config.ts b/tests/integration/build/action/fixture/adnbn.config.ts new file mode 100644 index 00000000..be601078 --- /dev/null +++ b/tests/integration/build/action/fixture/adnbn.config.ts @@ -0,0 +1,13 @@ +import {defineConfig, type ActionOptions} from "adnbn"; + +const action: ActionOptions = { + icon: "active", + title: "@action.title", +}; + +export default defineConfig({ + name: "Action Integration", + version: "1.0.0", + icon: "default", + action, +}); diff --git a/tests/integration/build/action/fixture/package.json b/tests/integration/build/action/fixture/package.json new file mode 100644 index 00000000..bd4063df --- /dev/null +++ b/tests/integration/build/action/fixture/package.json @@ -0,0 +1,9 @@ +{ + "name": "adnbn-test-action", + "private": true, + "type": "module", + "version": "1.0.0", + "dependencies": { + "adnbn": "file:../../../../../.." + } +} diff --git a/tests/integration/build/action/fixture/src/icons/16.png b/tests/integration/build/action/fixture/src/icons/16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3e580fb1504f3e4f6f312fbb18065955fca4f6 GIT binary patch literal 82 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`lAbP(Ar*6y4g6;RoG*07AnU*i f#>H$r;>--qc82ozUlbh#Dq`?-^>bP0l+XkK=Is@Z literal 0 HcmV?d00001 diff --git a/tests/integration/build/action/fixture/src/icons/active16.png b/tests/integration/build/action/fixture/src/icons/active16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3e580fb1504f3e4f6f312fbb18065955fca4f6 GIT binary patch literal 82 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`lAbP(Ar*6y4g6;RoG*07AnU*i f#>H$r;>--qc82ozUlbh#Dq`?-^>bP0l+XkK=Is@Z literal 0 HcmV?d00001 diff --git a/tests/integration/build/action/fixture/src/icons/popup16.png b/tests/integration/build/action/fixture/src/icons/popup16.png new file mode 100644 index 0000000000000000000000000000000000000000..0a3e580fb1504f3e4f6f312fbb18065955fca4f6 GIT binary patch literal 82 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`lAbP(Ar*6y4g6;RoG*07AnU*i f#>H$r;>--qc82ozUlbh#Dq`?-^>bP0l+XkK=Is@Z literal 0 HcmV?d00001 diff --git a/tests/integration/build/action/fixture/src/locales/en.json b/tests/integration/build/action/fixture/src/locales/en.json new file mode 100644 index 00000000..cb6e3b46 --- /dev/null +++ b/tests/integration/build/action/fixture/src/locales/en.json @@ -0,0 +1,8 @@ +{ + "action": { + "title": "Run the action" + }, + "popup": { + "title": "Open the popup" + } +} diff --git a/tests/integration/build/action/fixture/tsconfig.json b/tests/integration/build/action/fixture/tsconfig.json new file mode 100644 index 00000000..eadf1be8 --- /dev/null +++ b/tests/integration/build/action/fixture/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "./.adnbn/tsconfig.json" +} diff --git a/tests/integration/build/action/fixture/variants/action.command.ts b/tests/integration/build/action/fixture/variants/action.command.ts new file mode 100644 index 00000000..75758875 --- /dev/null +++ b/tests/integration/build/action/fixture/variants/action.command.ts @@ -0,0 +1,8 @@ +import {defineExecuteActionCommand} from "adnbn"; + +export default defineExecuteActionCommand({ + defaultKey: "Ctrl+Shift+Y", + execute() { + console.info("Action clicked"); + }, +}); diff --git a/tests/integration/build/action/fixture/variants/full.popup.ts b/tests/integration/build/action/fixture/variants/full.popup.ts new file mode 100644 index 00000000..c00c4870 --- /dev/null +++ b/tests/integration/build/action/fixture/variants/full.popup.ts @@ -0,0 +1,7 @@ +import {definePopup} from "adnbn"; + +export default definePopup({ + icon: "popup", + title: "@popup.title", + render: "Popup", +}); diff --git a/tests/integration/build/action/fixture/variants/title.popup.ts b/tests/integration/build/action/fixture/variants/title.popup.ts new file mode 100644 index 00000000..9cd28351 --- /dev/null +++ b/tests/integration/build/action/fixture/variants/title.popup.ts @@ -0,0 +1,6 @@ +import {definePopup} from "adnbn"; + +export default definePopup({ + title: "@popup.title", + render: "Popup", +}); diff --git a/tests/integration/build/action/fixture/variants/unapplied.popup.ts b/tests/integration/build/action/fixture/variants/unapplied.popup.ts new file mode 100644 index 00000000..f2148b90 --- /dev/null +++ b/tests/integration/build/action/fixture/variants/unapplied.popup.ts @@ -0,0 +1,8 @@ +import {definePopup} from "adnbn"; + +export default definePopup({ + apply: false, + icon: "popup", + title: "@popup.title", + render: "Popup", +}); diff --git a/tests/integration/types/fixtures/registries/fallback.ts b/tests/integration/types/fixtures/registries/fallback.ts index 98e7ebfb..bea4557c 100644 --- a/tests/integration/types/fixtures/registries/fallback.ts +++ b/tests/integration/types/fixtures/registries/fallback.ts @@ -16,6 +16,10 @@ api.changeSidebar("custom"); api.changeActionIcon("custom"); api.changeSidebarIcon(); +api.defineConfig({action: {icon: "custom"}}); +api.definePopup({icon: "custom", render: "Popup"}); +api.defineSidebar({icon: "custom", render: "Sidebar"}); + // @ts-expect-error: Empty transport registries remain strict. api.getService("unknown"); // @ts-expect-error: Empty transport registries remain strict. diff --git a/tests/integration/types/fixtures/registries/generated.ts b/tests/integration/types/fixtures/registries/generated.ts index a3122cff..7e0e40a4 100644 --- a/tests/integration/types/fixtures/registries/generated.ts +++ b/tests/integration/types/fixtures/registries/generated.ts @@ -27,6 +27,17 @@ api.getPopups().get("popup"); api.getSidebars().get("sidebar"); api.getIcons().get("brand"); +api.defineConfig({action: {icon: "brand"}}); +api.definePopup({icon: "brand", render: "Popup"}); +api.defineSidebar({icon: "brand", render: "Sidebar"}); + +// @ts-expect-error: Action defaults use the generated icon group names. +api.defineConfig({action: {icon: "unknown"}}); +// @ts-expect-error: Popup icons use the generated icon group names. +api.definePopup({icon: "unknown", render: "Popup"}); +// @ts-expect-error: Sidebar icons use the generated icon group names. +api.defineSidebar({icon: "unknown", render: "Sidebar"}); + const worker = api.getService("worker"); const document = api.getOffscreen("document"); const frame = api.getSandbox("frame");