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)
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
// Inject the Beat Pocket bundle manually via addInitScript and capture errors
|
||||
const { chromium } = require('/usr/lib/node_modules/@playwright/test');
|
||||
const fs = require('fs');
|
||||
|
||||
const BUNDLE = fs.readFileSync('/root/beat-pocket/extension/dist/content/index.js', 'utf8');
|
||||
|
||||
(async () => {
|
||||
const userDataDir = '/tmp/bb-inject-' + Date.now();
|
||||
const context = await chromium.launchPersistentContext(userDataDir, {
|
||||
headless: true,
|
||||
channel: 'chromium',
|
||||
args: ['--disable-dev-shm-usage', '--no-sandbox'], // NO extension loading
|
||||
viewport: { width: 414, height: 896 },
|
||||
});
|
||||
|
||||
const page = context.pages()[0] || await context.newPage();
|
||||
page.on('console', (m) => console.log(`[page:${m.type()}]`, m.text().slice(0, 300)));
|
||||
page.on('pageerror', (e) => console.log('[page:error]', String(e).slice(0, 500)));
|
||||
|
||||
// Inject the bundle as an init script — runs before the page's own JS
|
||||
// This bypasses the extension loader and lets us see if the bundle itself errors
|
||||
await page.addInitScript({
|
||||
content: BUNDLE,
|
||||
});
|
||||
|
||||
await page.goto('https://beat-battle.net/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||
await page.waitForTimeout(5000);
|
||||
|
||||
const result = await page.evaluate(() => {
|
||||
const allBbp = document.querySelectorAll('[class*="bbp-"], #bbp-overlay-host, [data-bbp]');
|
||||
return {
|
||||
bbpMarkerCount: allBbp.length,
|
||||
sampleClasses: Array.from(allBbp).slice(0, 8).map(el => el.className).join(' | '),
|
||||
htmlClass: document.documentElement.className,
|
||||
bodyClass: document.body?.className,
|
||||
};
|
||||
});
|
||||
console.log('[inject:result]', result);
|
||||
|
||||
await context.close();
|
||||
})().catch((e) => { console.error('FAIL:', e); process.exit(1); });
|
||||
@@ -0,0 +1,37 @@
|
||||
// Test whether a MINIMAL extension's content script injects under headless
|
||||
const { chromium } = require('/usr/lib/node_modules/@playwright/test');
|
||||
|
||||
const EXT_PATH = '/tmp/bbp-minimal';
|
||||
|
||||
(async () => {
|
||||
const userDataDir = '/tmp/bb-minimal-' + 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 },
|
||||
});
|
||||
|
||||
console.log('[minimal] workers:', context.serviceWorkers().length);
|
||||
|
||||
const page = context.pages()[0] || await context.newPage();
|
||||
page.on('console', (m) => console.log(`[page:${m.type()}]`, m.text()));
|
||||
page.on('pageerror', (e) => console.log('[page:error]', e.message));
|
||||
|
||||
await page.goto('https://beat-battle.net/', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
||||
await page.waitForTimeout(4000);
|
||||
|
||||
const result = await page.evaluate(() => ({
|
||||
htmlClass: document.documentElement.className,
|
||||
hasMarker: !!document.getElementById('bbp-minimal-marker'),
|
||||
markerText: document.getElementById('bbp-minimal-marker')?.textContent,
|
||||
}));
|
||||
console.log('[minimal:result]', result);
|
||||
|
||||
await context.close();
|
||||
})().catch((e) => { console.error('FAIL:', e); process.exit(1); });
|
||||
@@ -0,0 +1,86 @@
|
||||
// Probe: is the content script actually being injected?
|
||||
const { chromium } = require('/usr/lib/node_modules/@playwright/test');
|
||||
|
||||
const EXT_PATH = '/root/beat-pocket/extension';
|
||||
|
||||
(async () => {
|
||||
const userDataDir = '/tmp/bb-smoke-probe-' + 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 },
|
||||
});
|
||||
|
||||
// Wait for background
|
||||
await context.waitForEvent('serviceworker', { timeout: 8000 }).catch(() => {});
|
||||
console.log('[probe] service workers:', context.serviceWorkers().length);
|
||||
|
||||
const page = context.pages()[0] || await context.newPage();
|
||||
|
||||
// Capture ALL console messages to find extension logs
|
||||
page.on('console', (msg) => {
|
||||
console.log(`[page:${msg.type()}]`, msg.text().slice(0, 200));
|
||||
});
|
||||
page.on('pageerror', (err) => {
|
||||
console.log('[page:error]', String(err).slice(0, 300));
|
||||
});
|
||||
page.on('requestfailed', (req) => {
|
||||
console.log('[reqfail]', req.url(), '-', req.failure()?.errorText);
|
||||
});
|
||||
|
||||
// Probe: enumerate scripts and stylesheets on the page
|
||||
page.on('domcontentloaded', async () => {
|
||||
const probe = await page.evaluate(() => ({
|
||||
scripts: Array.from(document.querySelectorAll('script[src]')).map((s) => s.src),
|
||||
stylesheets: Array.from(document.querySelectorAll('link[rel="stylesheet"]')).map((l) => l.href),
|
||||
// Look for our markers explicitly
|
||||
htmlClassNames: Array.from(document.body.classList),
|
||||
// Check chrome-extension:// — does anything reference it?
|
||||
hasExtensionResource: !![...document.scripts].find(s => s.src?.includes('chrome-extension://')),
|
||||
}));
|
||||
console.log('[probe:domcontentloaded] scripts:', probe.scripts.length, 'stylesheets:', probe.stylesheets.length, 'ext-resource:', probe.hasExtensionResource);
|
||||
// Look for our injected stylesheet marker (our inject.css adds a class to <html>)
|
||||
const htmlClasses = await page.evaluate(() => document.documentElement.className);
|
||||
console.log('[probe] html.className:', htmlClasses);
|
||||
});
|
||||
|
||||
await page.goto('https://beat-battle.net/', { waitUntil: 'networkidle', timeout: 30000 });
|
||||
// wait extra for idle
|
||||
await page.waitForTimeout(4000);
|
||||
|
||||
// After load: what scripts does the page see?
|
||||
const after = await page.evaluate(() => ({
|
||||
bbpMarkers: document.querySelectorAll('[class*="bbp-"], #bbp-overlay-host').length,
|
||||
windowKeys: Object.keys(window).filter(k => /bbp/i.test(k)),
|
||||
scripts: Array.from(document.querySelectorAll('script[src]')).length,
|
||||
cssRules: Array.from(document.styleSheets).reduce((acc, s) => {
|
||||
try { return acc + (s.cssRules?.length || 0); } catch { return acc; }
|
||||
}, 0),
|
||||
}));
|
||||
console.log('[probe:after-load]', after);
|
||||
|
||||
// Try injecting a manual probe to see if we can reach the extension's service worker
|
||||
const extId = context.serviceWorkers()[0]?.url()?.match(/chrome-extension:\/\/([a-z]+)\//)?.[1];
|
||||
console.log('[probe] extension id:', extId);
|
||||
if (extId) {
|
||||
const sw = context.serviceWorkers()[0];
|
||||
try {
|
||||
const r = await sw.evaluate(() => ({
|
||||
url: location.href,
|
||||
runtime: typeof chrome?.runtime,
|
||||
runtimeId: chrome?.runtime?.id,
|
||||
}));
|
||||
console.log('[probe] sw context:', r);
|
||||
} catch (e) {
|
||||
console.log('[probe] sw evaluate failed:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
await context.close();
|
||||
})().catch((e) => { console.error('FAILED:', e); process.exit(1); });
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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); });
|
||||
@@ -0,0 +1,113 @@
|
||||
// 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);
|
||||
});
|
||||
Reference in New Issue
Block a user