Files
KoalaSync/tests/e2e/extension.spec.mjs
T
KoalaDev 36e1291d2c feat: rank player candidates by ordered signals, add browser E2E suite
The weighted score summed incomparable units, so size could outvote traits
that disqualify an element outright. Measured on a real page: a display:none
preload reports its full 1080p intrinsic size and scored 2073600, beating a
visible unmuted player at 509920.

Selection now compares an ordered list of signals, highest priority first:
has a source, is rendered, is not a silent background loop, rendered size
bucket, is playing, has controls, duration. Rendered size replaces intrinsic
resolution, and mute state is gone from the ranking entirely: it is a viewer
preference, not evidence about which element is the player.

It stays a ranking rather than a filter, so a page of only bad candidates
still yields one and findVideo never returns null where a video exists.

The new tests/e2e suite runs the shipped finder against real fixture pages
and drives the packed extension for injection, reinjection and remote
play/pause/seek into a first-party frame. All five scoring scenarios fail
against the previous implementation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 07:23:12 +02:00

144 lines
6.1 KiB
JavaScript

import path from 'node:path';
import fs from 'node:fs';
import os from 'node:os';
import { fileURLToPath } from 'node:url';
import { test as base, expect, chromium } from '@playwright/test';
/**
* Drives the packed extension itself: real background service worker, real
* chrome.scripting injection, real runtime messaging. The detection specs cover
* which element gets picked; this file covers whether the extension ever gets
* far enough to pick one.
*/
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const extensionPath = path.join(repoRoot, 'dist/chrome');
const test = base.extend({
context: async ({}, use) => {
if (!fs.existsSync(path.join(extensionPath, 'manifest.json'))) {
throw new Error('dist/chrome is missing. Run: npm run build:extension');
}
const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'koalasync-e2e-'));
const context = await chromium.launchPersistentContext(userDataDir, {
// The headless shell does not run MV3 service workers; the full
// Chromium build in new headless mode does.
channel: 'chromium',
headless: true,
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
'--autoplay-policy=no-user-gesture-required'
]
});
await use(context);
await context.close();
fs.rmSync(userDataDir, { recursive: true, force: true });
},
extensionId: async ({ context }, use) => {
let [worker] = context.serviceWorkers();
if (!worker) worker = await context.waitForEvent('serviceworker');
await use(worker.url().split('/')[2]);
}
});
/** Runs code in an extension page, where the privileged chrome.* APIs exist. */
async function withExtensionPage(context, extensionId, fn) {
const page = await context.newPage();
await page.goto(`chrome-extension://${extensionId}/popup.html`);
const result = await fn(page);
await page.close();
return result;
}
async function selectTargetTab(context, extensionId, pageUrl) {
return withExtensionPage(context, extensionId, page => page.evaluate(async (url) => {
const [tab] = await chrome.tabs.query({ url });
if (!tab) throw new Error(`no tab matched ${url}`);
const response = await chrome.runtime.sendMessage({ type: 'SET_TARGET_TAB', tabId: tab.id });
return { tabId: tab.id, response };
}, pageUrl));
}
async function sendServerCommand(context, extensionId, tabId, action, payload) {
return withExtensionPage(context, extensionId, page => page.evaluate(async ({ tabId, action, payload }) => {
return chrome.tabs.sendMessage(tabId, {
type: 'SERVER_COMMAND',
action,
payload,
actionTimestamp: Date.now(),
commandSenderId: 'e2e'
});
}, { tabId, action, payload }));
}
test('injects into the target tab and attaches to a same-origin frame player', async ({ context, extensionId, baseURL }) => {
const url = `${baseURL}/pages/iframe-player.html`;
const page = await context.newPage();
await page.goto(url);
await page.waitForFunction(() => window.__fixtureReady === true);
const { response } = await selectTargetTab(context, extensionId, url);
expect(response?.status, 'SET_TARGET_TAB should not report a failure').not.toBe('error');
await expect.poll(
() => page.evaluate(() => {
const video = document.querySelector('iframe').contentDocument.querySelector('video');
return video ? video.dataset.koalaAttached : null;
}),
{ message: 'content script should attach to the video inside the frame' }
).toBe('true');
});
test('applies remote play, pause and seek to the framed player', async ({ context, extensionId, baseURL }) => {
const url = `${baseURL}/pages/iframe-player.html`;
const page = await context.newPage();
await page.goto(url);
await page.waitForFunction(() => window.__fixtureReady === true);
const { tabId } = await selectTargetTab(context, extensionId, url);
await expect.poll(() => page.evaluate(() => {
const video = document.querySelector('iframe').contentDocument.querySelector('video');
return video ? video.dataset.koalaAttached : null;
})).toBe('true');
await sendServerCommand(context, extensionId, tabId, 'play');
await expect.poll(() => page.evaluate(FRAMED_VIDEO_PAUSED), { message: 'remote play should start playback' }).toBe(false);
await sendServerCommand(context, extensionId, tabId, 'pause');
await expect.poll(() => page.evaluate(FRAMED_VIDEO_PAUSED), { message: 'remote pause should stop playback' }).toBe(true);
await sendServerCommand(context, extensionId, tabId, 'seek', { targetTime: 6 });
await expect.poll(
() => page.evaluate(() => document.querySelector('iframe').contentDocument.querySelector('video').currentTime),
{ message: 'remote seek should move the framed player' }
).toBeGreaterThan(5);
});
test('reinjects after the target tab navigates', async ({ context, extensionId, baseURL }) => {
const first = `${baseURL}/pages/iframe-player.html`;
const page = await context.newPage();
await page.goto(first);
await page.waitForFunction(() => window.__fixtureReady === true);
await selectTargetTab(context, extensionId, first);
await expect.poll(() => page.evaluate(() => {
const video = document.querySelector('iframe').contentDocument.querySelector('video');
return video ? video.dataset.koalaAttached : null;
})).toBe('true');
await page.goto(`${baseURL}/pages/simple-player.html`);
await page.waitForFunction(() => window.__fixtureReady === true);
await expect.poll(
() => page.evaluate(() => {
const video = document.getElementById('player');
return video ? video.dataset.koalaAttached : null;
}),
{ message: 'the content script should come back after a navigation' }
).toBe('true');
});
function FRAMED_VIDEO_PAUSED() {
return document.querySelector('iframe').contentDocument.querySelector('video').paused;
}