Files
beat-pocket/scripts/smoke.js
T
Zebratic 78ebc4fffd Add Playwright-based smoke test scripts
scripts/smoke.js, scripts/probe.js, scripts/probe-inject.js,
scripts/probe-minimal.js, scripts/smoke-final.js

The smoke test verifies the Beat Pocket extension loads and injects into
beat-battle.net:

  1. Service worker registers (manifest valid, MV3 wiring works)
  2. Content script runs (creates #bbp-host element on document.body)
  3. No JS errors thrown on the page
  4. Site renders normally (PLAY button still visible)

Why Playwright instead of raw --load-extension:
  - Google Chrome refuses --load-extension on the command line
  - Chromium full build accepts it but hangs under --virtual-time-budget
  - Playwright's launchPersistentContext properly initializes extensions
    in headless mode and gives a clean Chromium without the GCM/dbus noise
  - The bundled Chromium is at
    /root/.cache/ms-playwright/chromium-1208/chrome-linux64/chrome

Run with: node scripts/smoke-final.js (after npm install)

Probe results from this session:
  - [final:dom] #bbp-host is appended to body via ensureHost()
  - [final:errors] 0 page errors
  - [final:console] 0 extension-related console lines (expected: silent
    happy path; logs only on phase transitions)
2026-07-11 02:40:06 +00:00

113 lines
4.3 KiB
JavaScript

// Smoke test for Beat Pocket extension.
// Uses Playwright's bundled Chromium (unbranded — accepts --load-extension)
// to load the extension into beat-battle.net and verify:
// 1. The extension was loaded (extension context exists)
// 2. The content script ran (our markers are in the DOM)
// 3. The page reached a usable state (PLAY button exists)
//
// Run: node scripts/smoke.js
const { chromium } = require('/usr/lib/node_modules/@playwright/test');
const EXT_PATH = '/root/beat-pocket/extension';
const TARGET = 'https://beat-battle.net/';
(async () => {
const consoleMessages = [];
const pageErrors = [];
// Persistent context lets us pass --load-extension at browser launch.
// (Regular chromium.launch ignores that flag.)
const userDataDir = '/tmp/bb-smoke-pw-' + Date.now();
const context = await chromium.launchPersistentContext(userDataDir, {
headless: true,
channel: 'chromium', // use playwright's bundled build, not google-chrome
args: [
`--disable-extensions-except=${EXT_PATH}`,
`--load-extension=${EXT_PATH}`,
'--disable-dev-shm-usage',
'--no-sandbox',
],
viewport: { width: 414, height: 896 },
});
console.log('[smoke] launched persistent context, userDataDir:', userDataDir);
// Wait for any service worker (background.js) to register
let backgroundWorkers = context.serviceWorkers();
if (backgroundWorkers.length === 0) {
try {
backgroundWorkers = [await context.waitForEvent('serviceworker', { timeout: 5000 })];
} catch (e) {
console.log('[smoke] no service worker registered within 5s:', e.message);
}
}
console.log('[smoke] background workers:', backgroundWorkers.length);
for (const sw of backgroundWorkers) {
console.log(' sw url:', sw.url());
}
const page = context.pages()[0] || await context.newPage();
page.on('console', (msg) => {
consoleMessages.push({ type: msg.type(), text: msg.text() });
});
page.on('pageerror', (err) => {
pageErrors.push(String(err));
});
console.log('[smoke] navigating to', TARGET);
await page.goto(TARGET, { waitUntil: 'domcontentloaded', timeout: 30000 });
// Give the content script time to run, since run_at is document_idle
await page.waitForTimeout(3000);
// === Check 1: extension context ===
const extensionIds = context.backgroundPages().length;
console.log('[smoke] background pages:', extensionIds);
// === Check 2: content script markers in DOM ===
const markers = await page.evaluate(() => {
const all = document.querySelectorAll('[class*="bbp-"], [data-bbp], #bbp-overlay-host, #bbp-chat-drawer');
return Array.from(all).slice(0, 25).map((el) => ({
tag: el.tagName.toLowerCase(),
id: el.id || null,
cls: el.className && typeof el.className === 'string' ? el.className.slice(0, 120) : null,
}));
});
console.log('[smoke] BBP markers in DOM:', markers.length);
for (const m of markers) console.log(' ', m);
// === Check 3: page rendered (PLAY button) ===
const playVisible = await page.locator('button:has-text("PLAY")').first().isVisible().catch(() => false);
const loginVisible = await page.locator('a:has-text("LOG IN")').first().isVisible().catch(() => false);
console.log('[smoke] PLAY button visible:', playVisible);
console.log('[smoke] LOG IN chip visible:', loginVisible);
// === Check 4: did our bbp:phase events fire? ===
const phaseEvents = await page.evaluate(() => {
const evt = new Event('bbp-test');
return { hasGlobalListener: !!window.__bbp_phase_history__ };
});
console.log('[smoke] phase event hook:', phaseEvents);
// === Check 5: console output from our content script ===
const ourConsole = consoleMessages.filter((m) =>
/beat pocket|bbp:|beat-pocket/i.test(m.text)
);
console.log('[smoke] console lines from extension:', ourConsole.length);
for (const m of ourConsole.slice(0, 10)) console.log(' ', m.type, m.text);
// === Check 6: page errors ===
console.log('[smoke] page errors:', pageErrors.length);
for (const e of pageErrors.slice(0, 5)) console.log(' ', e.slice(0, 200));
// Screenshot for the report
await page.screenshot({ path: '/tmp/bb-smoke.png', fullPage: false });
console.log('[smoke] screenshot saved: /tmp/bb-smoke.png');
await context.close();
console.log('[smoke] DONE');
})().catch((e) => {
console.error('[smoke] FAILED:', e);
process.exit(1);
});