Verify release canvas pixels

This commit is contained in:
2026-07-05 17:26:37 +09:00
parent 0289c28ea8
commit ceeffbeb7f

View File

@@ -38,6 +38,7 @@ try {
await page.reload({ waitUntil: 'domcontentloaded' });
await waitForTitle(page);
await page.screenshot({ path: `${screenshotDir}/rc-title-first-run.png`, fullPage: true });
await assertCanvasPainted(page, 'title first run');
const emptySave = await readCampaignSave(page);
assert(!emptySave.current && !emptySave.slot1, `Expected first run without campaign save: ${JSON.stringify(emptySave)}`);
@@ -45,8 +46,10 @@ try {
await page.mouse.click(962, 240);
await waitForStoryReady(page);
await page.screenshot({ path: `${screenshotDir}/rc-new-game-story.png`, fullPage: true });
await assertCanvasPainted(page, 'new game story');
await advanceUntilBattle(page, 'first-battle-zhuo-commandery');
await page.screenshot({ path: `${screenshotDir}/rc-first-battle.png`, fullPage: true });
await assertCanvasPainted(page, 'first battle');
const firstBattleProbe = await page.evaluate(() => {
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
@@ -76,6 +79,7 @@ try {
await page.evaluate(() => window.__HEROS_DEBUG__?.forceBattleOutcome('victory'));
await waitForBattleOutcome(page, 'victory');
await page.screenshot({ path: `${screenshotDir}/rc-first-battle-result.png`, fullPage: true });
await assertCanvasPainted(page, 'first battle result');
const resultProbe = await page.evaluate(() => {
const scene = window.__HEROS_GAME__?.scene.getScene('BattleScene');
@@ -97,6 +101,7 @@ try {
await page.mouse.click(738, 642);
await waitForCampAfterBattleResult(page);
await page.screenshot({ path: `${screenshotDir}/rc-first-camp.png`, fullPage: true });
await assertCanvasPainted(page, 'first camp');
const firstCampProbe = await page.evaluate(() => {
const scene = window.__HEROS_GAME__?.scene.getScene('CampScene');
@@ -128,6 +133,7 @@ try {
await page.mouse.click(962, 310);
await waitForCamp(page);
await page.screenshot({ path: `${screenshotDir}/rc-mid-campaign-continue.png`, fullPage: true });
await assertCanvasPainted(page, 'mid campaign camp');
const midCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
assert(midCampState?.campaign?.step === 'fifty-eighth-camp', `Expected mid-campaign save to continue into camp: ${JSON.stringify(midCampState)}`);
@@ -150,6 +156,7 @@ try {
const finalCampState = await page.evaluate(() => window.__HEROS_DEBUG__?.camp());
assert(finalCampState?.campaign?.step === 'sixty-sixth-camp', `Expected final camp save to continue: ${JSON.stringify(finalCampState)}`);
await page.screenshot({ path: `${screenshotDir}/rc-final-camp.png`, fullPage: true });
await assertCanvasPainted(page, 'final camp');
await page.mouse.click(1160, 38);
const finalTransitionScenes = await waitForStoryOrEnding(page);
@@ -159,6 +166,7 @@ try {
}
await waitForEnding(page);
await page.screenshot({ path: `${screenshotDir}/rc-ending.png`, fullPage: true });
await assertCanvasPainted(page, 'ending');
const endingState = await page.evaluate(() => window.__HEROS_DEBUG__?.scene('EndingScene')?.getDebugState?.());
assert(endingState?.campaignStep === 'ending-complete', `Expected ending-complete state: ${JSON.stringify(endingState)}`);
@@ -169,6 +177,7 @@ try {
await page.mouse.click(962, 310);
await waitForEnding(page);
await page.screenshot({ path: `${screenshotDir}/rc-ending-continue.png`, fullPage: true });
await assertCanvasPainted(page, 'ending continue');
const endingContinueState = await page.evaluate(() => ({
activeScenes: window.__HEROS_DEBUG__?.activeScenes() ?? [],
@@ -337,6 +346,50 @@ async function waitForEnding(page) {
}, undefined, { timeout: 90000 });
}
async function assertCanvasPainted(page, context) {
const probe = await page.evaluate(() => {
const canvas = document.querySelector('canvas');
if (!canvas) {
return { readable: false, missing: true, sampleCount: 0, nonTransparentRatio: 0, distinctColorBuckets: 0 };
}
const canvasContext = canvas.getContext('2d', { willReadFrequently: true });
if (!canvasContext) {
return { readable: false, missing: false, sampleCount: 0, nonTransparentRatio: 0, distinctColorBuckets: 0 };
}
const stepX = Math.max(1, Math.floor(canvas.width / 64));
const stepY = Math.max(1, Math.floor(canvas.height / 64));
const colorBuckets = new Set();
let sampleCount = 0;
let nonTransparentCount = 0;
for (let y = Math.floor(stepY / 2); y < canvas.height; y += stepY) {
for (let x = Math.floor(stepX / 2); x < canvas.width; x += stepX) {
const [r, g, b, a] = canvasContext.getImageData(x, y, 1, 1).data;
sampleCount += 1;
if (a > 8) {
nonTransparentCount += 1;
}
colorBuckets.add(`${r >> 4},${g >> 4},${b >> 4},${a >> 5}`);
}
}
return {
readable: true,
missing: false,
sampleCount,
nonTransparentRatio: sampleCount > 0 ? nonTransparentCount / sampleCount : 0,
distinctColorBuckets: colorBuckets.size
};
});
assert(probe.readable === true, `Expected readable canvas pixels during ${context}: ${JSON.stringify(probe)}`);
assert(probe.sampleCount >= 500, `Expected enough canvas pixel samples during ${context}: ${JSON.stringify(probe)}`);
assert(probe.nonTransparentRatio > 0.5, `Expected non-empty canvas pixels during ${context}: ${JSON.stringify(probe)}`);
assert(probe.distinctColorBuckets >= 12, `Expected visually varied canvas pixels during ${context}: ${JSON.stringify(probe)}`);
}
async function readCampaignSave(page) {
return page.evaluate(() => {
const read = (key) => {