623 lines
20 KiB
JavaScript
623 lines
20 KiB
JavaScript
import assert from 'node:assert/strict';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { chromium } from 'playwright';
|
|
import {
|
|
desktopBrowserContextOptions,
|
|
desktopBrowserDeviceScaleFactor,
|
|
desktopBrowserViewport
|
|
} from './desktop-browser-viewport.mjs';
|
|
import {
|
|
startVerificationPreview,
|
|
stopVerificationPreview
|
|
} from './verification-preview-server.mjs';
|
|
|
|
const renderers = ['canvas', 'webgl'];
|
|
const renderer = process.env.VERIFY_BATTLE_SAVE_GENERATION_RENDERER;
|
|
|
|
if (!renderer) {
|
|
for (const requestedRenderer of renderers) {
|
|
const result = spawnSync(process.execPath, [fileURLToPath(import.meta.url)], {
|
|
cwd: process.cwd(),
|
|
env: {
|
|
...process.env,
|
|
VERIFY_BATTLE_SAVE_GENERATION_RENDERER: requestedRenderer
|
|
},
|
|
stdio: 'inherit'
|
|
});
|
|
if (result.status !== 0) {
|
|
process.exit(result.status ?? 1);
|
|
}
|
|
}
|
|
process.exit(0);
|
|
}
|
|
|
|
assert(
|
|
renderers.includes(renderer),
|
|
`Unsupported battle-save generation renderer "${renderer}".`
|
|
);
|
|
|
|
const battleId = 'first-battle-zhuo-commandery';
|
|
const campaignStep = 'first-battle';
|
|
const campaignGeneration = 4;
|
|
const staleGeneration = 3;
|
|
const campaignKey = 'heros-web:campaign-state';
|
|
const campaignSlotKey = `${campaignKey}:slot-1`;
|
|
const battleBaseKey = `heros-web:battle:${battleId}`;
|
|
const battleSlotKey = `${battleBaseKey}:slot-1`;
|
|
const legacyBattleKey = 'heros-web:first-battle-state';
|
|
const battleStorageKeys = [battleSlotKey, battleBaseKey, legacyBattleKey];
|
|
const baseUrl =
|
|
process.env.VERIFY_BATTLE_SAVE_GENERATION_URL ??
|
|
`http://127.0.0.1:${renderer === 'canvas' ? 41825 : 41826}/heros_web/`;
|
|
const targetUrl = withDebugOptions(baseUrl, renderer);
|
|
|
|
let browser;
|
|
let serverProcess;
|
|
|
|
try {
|
|
serverProcess = await startVerificationPreview({
|
|
targetUrl,
|
|
explicitUrl: Boolean(
|
|
process.env.VERIFY_BATTLE_SAVE_GENERATION_URL
|
|
),
|
|
label:
|
|
`${renderer} battle save generation verification`
|
|
});
|
|
browser = await chromium.launch({
|
|
headless: process.env.VERIFY_BATTLE_SAVE_GENERATION_HEADLESS !== '0',
|
|
args:
|
|
renderer === 'webgl'
|
|
? ['--use-angle=swiftshader', '--enable-unsafe-swiftshader']
|
|
: []
|
|
});
|
|
const context = await browser.newContext(desktopBrowserContextOptions);
|
|
const page = await context.newPage();
|
|
page.setDefaultTimeout(30000);
|
|
|
|
const pageErrors = [];
|
|
const consoleErrors = [];
|
|
page.on('pageerror', (error) => pageErrors.push(error.stack ?? error.message));
|
|
page.on('console', (message) => {
|
|
if (message.type() === 'error') {
|
|
consoleErrors.push(message.text());
|
|
}
|
|
});
|
|
|
|
await page.goto(targetUrl, {
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 90000
|
|
});
|
|
await waitForDebugApi(page);
|
|
await page.evaluate(() => window.localStorage.clear());
|
|
await page.reload({
|
|
waitUntil: 'domcontentloaded',
|
|
timeout: 90000
|
|
});
|
|
await waitForTitle(page);
|
|
await assertDesktopRuntime(page, renderer);
|
|
|
|
await page.evaluate(async (requestedBattleId) => {
|
|
await window.__HEROS_DEBUG__?.goToBattle(requestedBattleId);
|
|
}, battleId);
|
|
await waitForBattleReady(page);
|
|
|
|
const fixture = await page.evaluate(
|
|
({
|
|
requestedBattleId,
|
|
requestedCampaignStep,
|
|
requestedCampaignGeneration,
|
|
requestedStaleGeneration,
|
|
requestedCampaignKey,
|
|
requestedCampaignSlotKey,
|
|
requestedBattleBaseKey,
|
|
requestedBattleSlotKey,
|
|
requestedLegacyBattleKey,
|
|
requestedBattleStorageKeys
|
|
}) => {
|
|
const scene = window.__HEROS_DEBUG__?.scene('BattleScene');
|
|
if (
|
|
!scene ||
|
|
typeof scene.createBattleSaveState !== 'function' ||
|
|
typeof scene.saveBattleState !== 'function'
|
|
) {
|
|
throw new Error('BattleScene save internals are unavailable.');
|
|
}
|
|
|
|
// Persist a normalized campaign fixture and a fully valid battle-save
|
|
// shape through the production writer before applying generation markers.
|
|
scene.saveBattleState(1);
|
|
const campaignRaw =
|
|
window.localStorage.getItem(requestedCampaignSlotKey) ??
|
|
window.localStorage.getItem(requestedCampaignKey);
|
|
const battleRaw =
|
|
window.localStorage.getItem(requestedBattleSlotKey) ??
|
|
window.localStorage.getItem(requestedBattleBaseKey);
|
|
if (!campaignRaw || !battleRaw) {
|
|
throw new Error('The production save writer did not create the browser fixture.');
|
|
}
|
|
|
|
const campaign = JSON.parse(campaignRaw);
|
|
campaign.step = requestedCampaignStep;
|
|
campaign.activeSaveSlot = 1;
|
|
campaign.battleResumeGeneration = requestedCampaignGeneration;
|
|
campaign.storyCheckpoint = undefined;
|
|
campaign.explorationCheckpoint = undefined;
|
|
campaign.updatedAt = new Date().toISOString();
|
|
const serializedCampaign = JSON.stringify(campaign);
|
|
window.localStorage.setItem(requestedCampaignSlotKey, serializedCampaign);
|
|
window.localStorage.setItem(requestedCampaignKey, serializedCampaign);
|
|
|
|
const baseline = JSON.parse(battleRaw);
|
|
const markerIndex = baseline.units.findIndex((unit) => unit.id === 'liu-bei');
|
|
if (markerIndex < 0) {
|
|
throw new Error('The first-battle Liu Bei save fixture is unavailable.');
|
|
}
|
|
const baselineMarkerHp = baseline.units[markerIndex].hp;
|
|
const markerMaxHp = baseline.units[markerIndex].maxHp;
|
|
const staleMarkerHp = Math.max(1, Math.min(markerMaxHp, baselineMarkerHp - 7));
|
|
const validMarkerHp = Math.max(1, Math.min(markerMaxHp, baselineMarkerHp - 3));
|
|
if (
|
|
staleMarkerHp === baselineMarkerHp ||
|
|
validMarkerHp === baselineMarkerHp ||
|
|
staleMarkerHp === validMarkerHp
|
|
) {
|
|
throw new Error(
|
|
`Battle marker HP values are not distinguishable: ${JSON.stringify({
|
|
baselineMarkerHp,
|
|
staleMarkerHp,
|
|
validMarkerHp
|
|
})}`
|
|
);
|
|
}
|
|
|
|
const stale = structuredClone(baseline);
|
|
stale.battleId = requestedBattleId;
|
|
stale.campaignStep = requestedCampaignStep;
|
|
stale.battleResumeGeneration = requestedStaleGeneration;
|
|
stale.turnNumber = 8;
|
|
stale.activeFaction = 'ally';
|
|
stale.savedAt = new Date(Date.now() - 60_000).toISOString();
|
|
stale.units[markerIndex].hp = staleMarkerHp;
|
|
|
|
const valid = structuredClone(baseline);
|
|
valid.battleId = requestedBattleId;
|
|
valid.campaignStep = requestedCampaignStep;
|
|
valid.battleResumeGeneration = requestedCampaignGeneration;
|
|
valid.turnNumber = 6;
|
|
valid.activeFaction = 'ally';
|
|
valid.savedAt = new Date().toISOString();
|
|
valid.units[markerIndex].hp = validMarkerHp;
|
|
|
|
window.localStorage.removeItem(requestedBattleBaseKey);
|
|
window.localStorage.removeItem(requestedLegacyBattleKey);
|
|
window.localStorage.setItem(requestedBattleSlotKey, JSON.stringify(stale));
|
|
|
|
const storagePrototype = Object.getPrototypeOf(window.localStorage);
|
|
const originalRemoveItem = storagePrototype.removeItem;
|
|
const blockedKeys = new Set(requestedBattleStorageKeys);
|
|
const removalStats = {
|
|
attempts: 0,
|
|
blocked: 0,
|
|
keys: []
|
|
};
|
|
storagePrototype.removeItem = function removeItem(key) {
|
|
const normalizedKey = String(key);
|
|
if (this === window.localStorage && blockedKeys.has(normalizedKey)) {
|
|
removalStats.attempts += 1;
|
|
removalStats.blocked += 1;
|
|
removalStats.keys.push(normalizedKey);
|
|
throw new DOMException(
|
|
'Injected battle-save removal refusal',
|
|
'InvalidStateError'
|
|
);
|
|
}
|
|
return originalRemoveItem.call(this, key);
|
|
};
|
|
|
|
Object.defineProperty(window, '__HEROS_BATTLE_GENERATION_FIXTURE__', {
|
|
configurable: true,
|
|
value: {
|
|
baselineMarkerHp,
|
|
staleMarkerHp,
|
|
validMarkerHp,
|
|
valid,
|
|
removalStats
|
|
}
|
|
});
|
|
|
|
scene.scene.restart({
|
|
battleId: requestedBattleId,
|
|
resumeBattleSaveSlot: 1
|
|
});
|
|
|
|
return {
|
|
baselineMarkerHp,
|
|
staleMarkerHp,
|
|
validMarkerHp,
|
|
staleTurnNumber: stale.turnNumber,
|
|
validTurnNumber: valid.turnNumber
|
|
};
|
|
},
|
|
{
|
|
requestedBattleId: battleId,
|
|
requestedCampaignStep: campaignStep,
|
|
requestedCampaignGeneration: campaignGeneration,
|
|
requestedStaleGeneration: staleGeneration,
|
|
requestedCampaignKey: campaignKey,
|
|
requestedCampaignSlotKey: campaignSlotKey,
|
|
requestedBattleBaseKey: battleBaseKey,
|
|
requestedBattleSlotKey: battleSlotKey,
|
|
requestedLegacyBattleKey: legacyBattleKey,
|
|
requestedBattleStorageKeys: battleStorageKeys
|
|
}
|
|
);
|
|
|
|
await page.waitForFunction(
|
|
({ expectedBattleId, expectedBaselineHp }) => {
|
|
const battle = window.__HEROS_DEBUG__?.battle?.();
|
|
const fixtureState = window.__HEROS_BATTLE_GENERATION_FIXTURE__;
|
|
return (
|
|
battle?.battleId === expectedBattleId &&
|
|
battle?.mapBackgroundReady === true &&
|
|
['deployment', 'idle'].includes(battle?.phase) &&
|
|
battle?.turnNumber === 1 &&
|
|
battle?.units?.find((unit) => unit.id === 'liu-bei')?.hp ===
|
|
expectedBaselineHp &&
|
|
fixtureState?.removalStats?.blocked >= 1
|
|
);
|
|
},
|
|
{
|
|
expectedBattleId: battleId,
|
|
expectedBaselineHp: fixture.baselineMarkerHp
|
|
},
|
|
{ timeout: 90000 }
|
|
);
|
|
|
|
const staleProbe = await page.evaluate(
|
|
({
|
|
requestedBattleSlotKey,
|
|
requestedStaleGeneration
|
|
}) => {
|
|
const scene = window.__HEROS_DEBUG__?.scene('BattleScene');
|
|
if (!scene) {
|
|
throw new Error('BattleScene is unavailable after stale resume rejection.');
|
|
}
|
|
const rejected =
|
|
scene.readBattleSaveState?.(1, true) === undefined;
|
|
const loadDisabled =
|
|
scene.isMapMenuActionDisabled?.('load') === true;
|
|
scene.showSaveSlotPanel?.('load');
|
|
const panelBeforeSelection =
|
|
window.__HEROS_DEBUG__?.battle?.();
|
|
scene.activateSaveSlotPanelSlot?.(1);
|
|
const panelAfterSelection =
|
|
window.__HEROS_DEBUG__?.battle?.();
|
|
scene.hideSaveSlotPanel?.();
|
|
|
|
const persisted = JSON.parse(
|
|
window.localStorage.getItem(requestedBattleSlotKey) ?? 'null'
|
|
);
|
|
const fixtureState =
|
|
window.__HEROS_BATTLE_GENERATION_FIXTURE__;
|
|
return {
|
|
rejected,
|
|
loadDisabled,
|
|
panelMode:
|
|
panelBeforeSelection?.saveSlotPanelMode ?? null,
|
|
panelStayedOpen:
|
|
panelAfterSelection?.saveSlotPanelVisible === true,
|
|
promptMode:
|
|
panelAfterSelection?.turnPromptMode ?? null,
|
|
persistedGeneration:
|
|
persisted?.battleResumeGeneration ?? null,
|
|
removalStats: {
|
|
attempts:
|
|
fixtureState?.removalStats?.attempts ?? 0,
|
|
blocked:
|
|
fixtureState?.removalStats?.blocked ?? 0,
|
|
keys: [
|
|
...(fixtureState?.removalStats?.keys ?? [])
|
|
]
|
|
}
|
|
};
|
|
},
|
|
{
|
|
requestedBattleSlotKey: battleSlotKey,
|
|
requestedStaleGeneration: staleGeneration
|
|
}
|
|
);
|
|
|
|
assert.equal(
|
|
staleProbe.rejected,
|
|
true,
|
|
`${renderer}: BattleScene accepted a stale generation.`
|
|
);
|
|
assert.equal(
|
|
staleProbe.loadDisabled,
|
|
true,
|
|
`${renderer}: manual Load remained enabled for a stale save.`
|
|
);
|
|
assert.equal(staleProbe.panelMode, 'load');
|
|
assert.equal(
|
|
staleProbe.panelStayedOpen,
|
|
true,
|
|
`${renderer}: selecting a stale Load row should be a no-op.`
|
|
);
|
|
assert.equal(
|
|
staleProbe.promptMode,
|
|
null,
|
|
`${renderer}: a stale save reached the Load confirmation prompt.`
|
|
);
|
|
assert.equal(
|
|
staleProbe.persistedGeneration,
|
|
staleGeneration,
|
|
`${renderer}: the removal-refusal fixture was not preserved.`
|
|
);
|
|
assert(
|
|
staleProbe.removalStats.blocked >= 1,
|
|
`${renderer}: no stale-save cleanup refusal was exercised.`
|
|
);
|
|
assert(
|
|
staleProbe.removalStats.keys.every((key) =>
|
|
battleStorageKeys.includes(key)
|
|
),
|
|
`${renderer}: removal refusal escaped the intended battle keys.`
|
|
);
|
|
|
|
const beforeControl = await page.evaluate(
|
|
({
|
|
requestedBattleSlotKey,
|
|
requestedCampaignGeneration
|
|
}) => {
|
|
const scene = window.__HEROS_DEBUG__?.scene('BattleScene');
|
|
const fixtureState =
|
|
window.__HEROS_BATTLE_GENERATION_FIXTURE__;
|
|
if (!scene || !fixtureState?.valid) {
|
|
throw new Error('The current-generation control fixture is unavailable.');
|
|
}
|
|
|
|
window.localStorage.setItem(
|
|
requestedBattleSlotKey,
|
|
JSON.stringify(fixtureState.valid)
|
|
);
|
|
const accepted = scene.readBattleSaveState?.(1, true);
|
|
const loadDisabled =
|
|
scene.isMapMenuActionDisabled?.('load') === true;
|
|
scene.showSaveSlotPanel?.('load');
|
|
const panel =
|
|
window.__HEROS_DEBUG__?.battle?.();
|
|
scene.activateSaveSlotPanelSlot?.(1);
|
|
const confirmation =
|
|
window.__HEROS_DEBUG__?.battle?.();
|
|
|
|
return {
|
|
acceptedGeneration:
|
|
accepted?.battleResumeGeneration ?? null,
|
|
acceptedTurnNumber:
|
|
accepted?.turnNumber ?? null,
|
|
loadDisabled,
|
|
panelMode: panel?.saveSlotPanelMode ?? null,
|
|
confirmationMode:
|
|
confirmation?.turnPromptMode ?? null,
|
|
confirmationVisible:
|
|
confirmation?.turnPromptVisible === true,
|
|
expectedGeneration: requestedCampaignGeneration
|
|
};
|
|
},
|
|
{
|
|
requestedBattleSlotKey: battleSlotKey,
|
|
requestedCampaignGeneration: campaignGeneration
|
|
}
|
|
);
|
|
|
|
assert.equal(
|
|
beforeControl.acceptedGeneration,
|
|
campaignGeneration,
|
|
`${renderer}: the current-generation control was not accepted.`
|
|
);
|
|
assert.equal(
|
|
beforeControl.acceptedTurnNumber,
|
|
fixture.validTurnNumber
|
|
);
|
|
assert.equal(beforeControl.loadDisabled, false);
|
|
assert.equal(beforeControl.panelMode, 'load');
|
|
assert.equal(beforeControl.confirmationMode, 'load-confirm');
|
|
assert.equal(beforeControl.confirmationVisible, true);
|
|
|
|
await page.evaluate(() => {
|
|
const scene = window.__HEROS_DEBUG__?.scene('BattleScene');
|
|
scene?.activateTurnPromptPrimaryAction?.();
|
|
});
|
|
await page.waitForFunction(
|
|
({
|
|
expectedBattleId,
|
|
expectedTurnNumber,
|
|
expectedMarkerHp
|
|
}) => {
|
|
const battle = window.__HEROS_DEBUG__?.battle?.();
|
|
return (
|
|
battle?.battleId === expectedBattleId &&
|
|
battle?.turnNumber === expectedTurnNumber &&
|
|
battle?.activeFaction === 'ally' &&
|
|
battle?.phase === 'idle' &&
|
|
battle?.units?.find((unit) => unit.id === 'liu-bei')?.hp ===
|
|
expectedMarkerHp
|
|
);
|
|
},
|
|
{
|
|
expectedBattleId: battleId,
|
|
expectedTurnNumber: fixture.validTurnNumber,
|
|
expectedMarkerHp: fixture.validMarkerHp
|
|
},
|
|
{ timeout: 90000 }
|
|
);
|
|
|
|
const restored = await page.evaluate(
|
|
({
|
|
requestedCampaignKey,
|
|
requestedCampaignSlotKey
|
|
}) => {
|
|
const battle = window.__HEROS_DEBUG__?.battle?.();
|
|
const current = JSON.parse(
|
|
window.localStorage.getItem(requestedCampaignKey) ?? 'null'
|
|
);
|
|
const slot = JSON.parse(
|
|
window.localStorage.getItem(requestedCampaignSlotKey) ?? 'null'
|
|
);
|
|
return {
|
|
turnNumber: battle?.turnNumber ?? null,
|
|
activeFaction: battle?.activeFaction ?? null,
|
|
markerHp:
|
|
battle?.units?.find((unit) => unit.id === 'liu-bei')?.hp ?? null,
|
|
campaignGeneration:
|
|
current?.battleResumeGeneration ?? null,
|
|
slotGeneration:
|
|
slot?.battleResumeGeneration ?? null,
|
|
campaignStep: current?.step ?? null,
|
|
slotStep: slot?.step ?? null
|
|
};
|
|
},
|
|
{
|
|
requestedCampaignKey: campaignKey,
|
|
requestedCampaignSlotKey: campaignSlotKey
|
|
}
|
|
);
|
|
|
|
assert.equal(restored.turnNumber, fixture.validTurnNumber);
|
|
assert.equal(restored.activeFaction, 'ally');
|
|
assert.equal(restored.markerHp, fixture.validMarkerHp);
|
|
assert.equal(restored.campaignGeneration, campaignGeneration);
|
|
assert.equal(restored.slotGeneration, campaignGeneration);
|
|
assert.equal(restored.campaignStep, campaignStep);
|
|
assert.equal(restored.slotStep, campaignStep);
|
|
assert.equal(pageErrors.length, 0, pageErrors.join('\n'));
|
|
assert.equal(consoleErrors.length, 0, consoleErrors.join('\n'));
|
|
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
renderer,
|
|
viewport: desktopBrowserViewport,
|
|
deviceScaleFactor: desktopBrowserDeviceScaleFactor,
|
|
cssZoom: 1,
|
|
stale: {
|
|
campaignGeneration,
|
|
saveGeneration: staleGeneration,
|
|
requestedTurnNumber: fixture.staleTurnNumber,
|
|
liveTurnNumber: 1,
|
|
loadDisabled: staleProbe.loadDisabled,
|
|
removalRefusal: staleProbe.removalStats
|
|
},
|
|
control: {
|
|
campaignGeneration,
|
|
saveGeneration: beforeControl.acceptedGeneration,
|
|
restoredTurnNumber: restored.turnNumber,
|
|
restoredMarkerHp: restored.markerHp
|
|
},
|
|
pageErrors: pageErrors.length,
|
|
consoleErrors: consoleErrors.length
|
|
},
|
|
null,
|
|
2
|
|
)
|
|
);
|
|
} finally {
|
|
await browser?.close();
|
|
await stopVerificationPreview(serverProcess);
|
|
}
|
|
|
|
function withDebugOptions(url, requestedRenderer) {
|
|
const parsed = new URL(url);
|
|
parsed.searchParams.set('debug', '1');
|
|
parsed.searchParams.set('renderer', requestedRenderer);
|
|
parsed.searchParams.set('motion', 'reduced');
|
|
return parsed.toString();
|
|
}
|
|
|
|
async function waitForDebugApi(page) {
|
|
await page.waitForFunction(
|
|
() =>
|
|
Boolean(
|
|
document.querySelector('canvas') &&
|
|
window.__HEROS_GAME__ &&
|
|
window.__HEROS_DEBUG__
|
|
),
|
|
undefined,
|
|
{ timeout: 90000 }
|
|
);
|
|
}
|
|
|
|
async function waitForTitle(page) {
|
|
await page.waitForFunction(
|
|
() =>
|
|
window.__HEROS_DEBUG__?.activeScenes?.().includes('TitleScene'),
|
|
undefined,
|
|
{ timeout: 90000 }
|
|
);
|
|
}
|
|
|
|
async function waitForBattleReady(page) {
|
|
await page.waitForFunction(
|
|
(expectedBattleId) => {
|
|
const battle = window.__HEROS_DEBUG__?.battle?.();
|
|
return (
|
|
window.__HEROS_DEBUG__?.activeScenes?.().includes('BattleScene') &&
|
|
battle?.scene === 'BattleScene' &&
|
|
battle?.battleId === expectedBattleId &&
|
|
battle?.battleOutcome === null &&
|
|
['deployment', 'idle'].includes(battle?.phase) &&
|
|
battle?.mapBackgroundReady === true &&
|
|
battle?.resultVisible === false
|
|
);
|
|
},
|
|
battleId,
|
|
{ timeout: 90000 }
|
|
);
|
|
}
|
|
|
|
async function assertDesktopRuntime(page, expectedRenderer) {
|
|
const runtime = await page.evaluate(() => {
|
|
const canvas = document.querySelector('canvas');
|
|
const bounds = canvas?.getBoundingClientRect();
|
|
return {
|
|
innerWidth: window.innerWidth,
|
|
innerHeight: window.innerHeight,
|
|
devicePixelRatio: window.devicePixelRatio,
|
|
visualViewportScale: window.visualViewport?.scale ?? 1,
|
|
rootCssZoom: getComputedStyle(document.documentElement).zoom,
|
|
rendererType: window.__HEROS_GAME__?.renderer.type,
|
|
canvas: canvas
|
|
? {
|
|
width: canvas.width,
|
|
height: canvas.height,
|
|
clientWidth: canvas.clientWidth,
|
|
clientHeight: canvas.clientHeight,
|
|
bounds: bounds
|
|
? {
|
|
width: bounds.width,
|
|
height: bounds.height
|
|
}
|
|
: null
|
|
}
|
|
: null
|
|
};
|
|
});
|
|
|
|
assert.equal(runtime.innerWidth, desktopBrowserViewport.width);
|
|
assert.equal(runtime.innerHeight, desktopBrowserViewport.height);
|
|
assert.equal(runtime.devicePixelRatio, desktopBrowserDeviceScaleFactor);
|
|
assert.equal(runtime.visualViewportScale, 1);
|
|
assert(
|
|
runtime.rootCssZoom === '1' || runtime.rootCssZoom === 'normal',
|
|
`Unexpected CSS zoom ${runtime.rootCssZoom}.`
|
|
);
|
|
assert.equal(runtime.rendererType, expectedRenderer === 'canvas' ? 1 : 2);
|
|
assert.equal(runtime.canvas?.width, desktopBrowserViewport.width);
|
|
assert.equal(runtime.canvas?.height, desktopBrowserViewport.height);
|
|
assert.equal(runtime.canvas?.clientWidth, desktopBrowserViewport.width);
|
|
assert.equal(runtime.canvas?.clientHeight, desktopBrowserViewport.height);
|
|
assert.equal(runtime.canvas?.bounds?.width, desktopBrowserViewport.width);
|
|
assert.equal(runtime.canvas?.bounds?.height, desktopBrowserViewport.height);
|
|
}
|