diff --git a/package.json b/package.json index 3b6de9d..b2920de 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "verify:flow": "node scripts/verify-flow.mjs", "verify:save-flow": "node scripts/verify-save-retry-flow.mjs", "verify:release": "node scripts/verify-release-candidate.mjs", + "verify:public-deploy": "node scripts/verify-public-deploy.mjs", "qa:representative": "node scripts/qa-representative-battles.mjs", "deploy:nas": "powershell -NoProfile -ExecutionPolicy Bypass -File scripts/deploy-nas.ps1 -Build" }, diff --git a/scripts/verify-public-deploy.mjs b/scripts/verify-public-deploy.mjs new file mode 100644 index 0000000..10b04ed --- /dev/null +++ b/scripts/verify-public-deploy.mjs @@ -0,0 +1,75 @@ +import { mkdirSync } from 'node:fs'; +import { dirname } from 'node:path'; +import { chromium } from 'playwright'; + +const targetUrl = withQaParams(process.env.PUBLIC_QA_URL ?? 'https://comtropy.synology.me/heros_web/'); +const screenshotPath = process.env.PUBLIC_QA_SCREENSHOT ?? 'dist/public-deploy-qa.png'; +const relevantLogPattern = /asset|audio|cannot|failed|map|missing|portrait|sprite|story|texture|unit/i; + +mkdirSync(dirname(screenshotPath), { recursive: true }); + +const browser = await chromium.launch({ headless: true }); + +try { + const page = await browser.newPage({ viewport: { width: 1280, height: 720 } }); + const consoleMessages = []; + page.on('console', (message) => { + consoleMessages.push({ type: message.type(), text: message.text() }); + }); + + await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 90000 }); + await page.waitForFunction(() => document.querySelector('canvas') !== null, undefined, { timeout: 90000 }); + await page.waitForFunction(() => { + const canvas = document.querySelector('canvas'); + return Boolean(canvas && canvas.width >= 960 && canvas.height >= 540); + }, undefined, { timeout: 90000 }); + await page.waitForTimeout(3000); + await page.screenshot({ path: screenshotPath, fullPage: true }); + + const state = await page.evaluate(() => { + const canvas = document.querySelector('canvas'); + return { + title: document.title, + canvasCount: document.querySelectorAll('canvas').length, + canvas: canvas + ? { + width: canvas.width, + height: canvas.height, + clientWidth: canvas.clientWidth, + clientHeight: canvas.clientHeight + } + : undefined + }; + }); + + assert(state.title?.trim().length > 0, `Expected deployed page title: ${JSON.stringify(state)}`); + assert(state.canvasCount === 1, `Expected exactly one game canvas: ${JSON.stringify(state)}`); + assert(state.canvas?.width >= 960 && state.canvas?.height >= 540, `Expected desktop-size game canvas: ${JSON.stringify(state)}`); + + const relevantLogs = consoleMessages.filter( + (message) => message.type === 'error' || (isWarning(message.type) && relevantLogPattern.test(message.text)) + ); + assert(relevantLogs.length === 0, `Unexpected public deploy console issues: ${JSON.stringify(relevantLogs, null, 2)}`); + + console.log(`Verified public deploy at ${targetUrl}`); + console.log(`Captured ${screenshotPath}`); +} finally { + await browser.close(); +} + +function withQaParams(url) { + const parsed = new URL(url); + parsed.searchParams.set('renderer', 'canvas'); + parsed.searchParams.set('v', process.env.PUBLIC_QA_VERSION ?? String(Date.now())); + return parsed.toString(); +} + +function isWarning(type) { + return type === 'warning' || type === 'warn'; +} + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +}