Files
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

61 lines
2.7 KiB
JavaScript

// Final smoke: load the extension, hit the site, check the correct marker IDs
const { chromium } = require('/usr/lib/node_modules/@playwright/test');
const EXT_PATH = '/root/beat-pocket/extension';
(async () => {
const consoleMessages = [];
const pageErrors = [];
const userDataDir = '/tmp/bb-final-' + Date.now();
const context = await chromium.launchPersistentContext(userDataDir, {
headless: true,
channel: 'chromium',
args: [
`--disable-extensions-except=${EXT_PATH}`,
`--load-extension=${EXT_PATH}`,
'--disable-dev-shm-usage',
'--no-sandbox',
],
viewport: { width: 414, height: 896 },
});
await context.waitForEvent('serviceworker', { timeout: 8000 }).catch(() => {});
console.log('[final] sw count:', context.serviceWorkers().length);
const page = context.pages()[0] || await context.newPage();
page.on('console', (m) => consoleMessages.push({ type: m.type(), text: m.text() }));
page.on('pageerror', (e) => pageErrors.push(String(e)));
await page.goto('https://beat-battle.net/', { waitUntil: 'domcontentloaded', timeout: 30000 });
await page.waitForTimeout(5000);
// Comprehensive DOM check — every plausible marker
const result = await page.evaluate(() => {
const find = (sel) => document.querySelector(sel);
return {
bbpHost: find('#bbp-host')?.outerHTML?.slice(0, 200) || null,
bbpDrawer: find('#bbp-chat-drawer')?.outerHTML?.slice(0, 200) || null,
bbpFab: find('#bbp-fab')?.outerHTML?.slice(0, 200) || null,
bbpHeaderBtn: find('#bbp-header-btn')?.outerHTML?.slice(0, 200) || null,
bbpAnyDiv: Array.from(document.querySelectorAll('div')).filter(d => d.id.startsWith('bbp-')).map(d => d.id),
bbpClassCount: document.querySelectorAll('[class*="bbp-"]').length,
// Site state
playButton: !!Array.from(document.querySelectorAll('button')).find(b => /PLAY/i.test(b.textContent || '')),
windowBbp: Object.keys(window).filter(k => /bbp/i.test(k)),
};
});
console.log('[final:dom]', JSON.stringify(result, null, 2));
console.log('[final:errors]', pageErrors.length, 'errors:', pageErrors.slice(0,3));
console.log('[final:console] total', consoleMessages.length);
const ours = consoleMessages.filter(m => /beat pocket|bbp|beat-pocket/i.test(m.text));
console.log('[final:console] ours:', ours.length);
for (const m of ours.slice(0, 8)) console.log(' ', m.type, m.text.slice(0,200));
// Now navigate to a known-in-game URL? No — site is dynamic, just check homepage state.
await page.screenshot({ path: '/tmp/bb-final.png', fullPage: false });
console.log('[final] screenshot: /tmp/bb-final.png');
await context.close();
})().catch((e) => { console.error('FAIL:', e); process.exit(1); });