Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
48315a4
fix(chrome-extension): restore the in-page recorder panel and camera …
richiemcilroy Oct 29, 2026
1ca846b
fix(chrome-extension): show the camera preview again when the recorde…
richiemcilroy Oct 29, 2026
669354e
fix(chrome-extension): keep the pre-roll countdown visible in the rec…
richiemcilroy Oct 29, 2026
33e4935
fix(chrome-extension): collapse the recording dock when the pointer l…
richiemcilroy Oct 29, 2026
9873a4d
test(chrome-extension): cover the recording badge and dock in the ove…
richiemcilroy Oct 29, 2026
1f7760c
fix(chrome-extension): mix recording audio without an output device
richiemcilroy Oct 29, 2026
48d98c2
test(chrome-extension): check uploaded audio length, tail and tab A/V…
richiemcilroy Oct 29, 2026
91ef5d2
fix(chrome-extension): mute only the live recording microphone from t…
richiemcilroy Oct 29, 2026
b4edb50
fix(chrome-extension): re-check a cached free plan before starting a …
richiemcilroy Oct 29, 2026
957a6cf
fix(chrome-extension): check the raw microphone signal before warning…
richiemcilroy Oct 29, 2026
ec777e6
test(chrome-extension): cover the microphone check and stale plan in …
richiemcilroy Oct 29, 2026
e356e98
test(chrome-extension): run the e2e suites in installed Chrome, Edge …
richiemcilroy Oct 29, 2026
3fed61b
fix(chrome-extension): pick a camera when switching to Camera mode in…
richiemcilroy Oct 29, 2026
fe8a61d
test(chrome-extension): cover Camera mode start availability in the p…
richiemcilroy Oct 29, 2026
d2e43ba
test(chrome-extension): check the panel and camera preview on a stric…
richiemcilroy Oct 29, 2026
12369c0
fix(chrome-extension): retry offscreen requests that arrive before th…
richiemcilroy Oct 29, 2026
6b70b06
test(chrome-extension): let the quiet-room recording capture a chunk …
richiemcilroy Oct 29, 2026
31d6a19
fix(chrome-extension): fall back to the default mic and retry busy de…
richiemcilroy Oct 29, 2026
770d95a
fix(chrome-extension): keep the camera preview in the recorded tab wh…
richiemcilroy Oct 29, 2026
d7a11ed
test(chrome-extension): stress webcam and mic starts on synthetic dev…
richiemcilroy Oct 29, 2026
4766bc1
test(chrome-extension): derive the unpacked extension id the way Chro…
richiemcilroy Oct 29, 2026
a21b1ac
test(chrome-extension): abandon the stale start by stopping the worke…
richiemcilroy Oct 29, 2026
7159c11
test(chrome-extension): skip the OS-device tab loop in installed brow…
richiemcilroy Oct 29, 2026
ed45b2e
test(chrome-extension): run the background tab check in Camera mode o…
richiemcilroy Oct 29, 2026
9d7e991
fix(chrome-extension): register overlay frame tokens each time a fram…
richiemcilroy Oct 29, 2026
227135c
fix(chrome-extension): show the real mic state when a dock mute is no…
richiemcilroy Oct 29, 2026
2e4086c
test(chrome-extension): check the OS camera size only when the rig de…
richiemcilroy Oct 29, 2026
7dc45d8
test(chrome-extension): reopen the panel after its token is pushed ou…
richiemcilroy Oct 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions apps/chrome-extension/e2e/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { createHash } from "node:crypto";
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { chromium } from "@playwright/test";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

export const extensionPath = path.resolve(__dirname, "../dist");

// Chrome derives an unpacked extension's id from its absolute path: on
// Windows from the UTF-16 path with an upper-case drive letter.
export const unpackedExtensionId = createHash("sha256")
.update(
process.platform === "win32"
? Buffer.from(
extensionPath.replace(/^[a-z]:/, (drive) => drive.toUpperCase()),
"utf16le",
)
: extensionPath,
)
.digest("hex")
.slice(0, 32)
.replace(/[0-9a-f]/g, (digit) =>
String.fromCharCode(97 + Number.parseInt(digit, 16)),
);

// CAP_E2E_BROWSER points the suites at an installed browser (Chrome, Edge,
// Brave) instead of Playwright's Chromium. Branded Chrome ignores
// --load-extension, so installed browsers load the extension over CDP.
const installedBrowser = process.env.CAP_E2E_BROWSER;

// Allows the extension's camera and mic in a new profile, as if the user had
// accepted the permission prompt. Chrome refuses CDP permission grants for
// extension origins, so the setting is written before the browser starts.
const allowExtensionMediaInProfile = async (userDataDir: string) => {
const allow = { setting: 1 };
const exceptions = {
[`chrome-extension://${unpackedExtensionId}/,*`]: allow,
[`chrome-extension://${unpackedExtensionId},*`]: allow,
};
await mkdir(path.join(userDataDir, "Default"), { recursive: true });
await writeFile(
path.join(userDataDir, "Default", "Preferences"),
JSON.stringify({
profile: {
content_settings: {
exceptions: {
media_stream_camera: exceptions,
media_stream_mic: exceptions,
},
},
},
}),
);
};

export const launchExtensionBrowser = async ({
profilePrefix,
headless,
args,
allowExtensionMedia = false,
}: {
profilePrefix: string;
headless: boolean;
args: string[];
allowExtensionMedia?: boolean;
}) => {
const userDataDir = await mkdtemp(path.join(tmpdir(), profilePrefix));
if (allowExtensionMedia) await allowExtensionMediaInProfile(userDataDir);
const context = await chromium.launchPersistentContext(
userDataDir,
installedBrowser
? {
executablePath: installedBrowser,
headless,
ignoreDefaultArgs: ["--disable-extensions"],
args: ["--enable-unsafe-extension-debugging", ...args],
}
: {
channel: "chromium",
headless,
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
...args,
],
},
);
if (installedBrowser) {
const browser = context.browser();
if (!browser) throw new Error("Installed browser did not expose CDP");
const session = await browser.newBrowserCDPSession();
await session.send(
"Extensions.loadUnpacked" as never,
{
path: extensionPath,
} as never,
);
await session.detach();
}
const cleanup = async () => {
await context.close();
await rm(userDataDir, { recursive: true, force: true });
};
return { context, cleanup };
};
Loading
Loading