mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
test: prove hidden-tab polling in a real browser
This commit is contained in:
1 parent
2c8b3593d1
commit
5b9c726f33
5 files changed
+134
-1
No files matched your search
@@ -2,3 +2,5 @@
|
|||||||
- Report and guard browser visibility during Transit QA so hidden-page polling pauses cannot masquerade as playback regressions (asturksever, #1282).
|
- Report and guard browser visibility during Transit QA so hidden-page polling pauses cannot masquerade as playback regressions (asturksever, #1282).
|
||||||
- Require each live city to supply its own feed vehicles in Transit QA; retained vehicles cannot qualify another city (asturksever, #1282).
|
- Require each live city to supply its own feed vehicles in Transit QA; retained vehicles cannot qualify another city (asturksever, #1282).
|
||||||
- Bound the Transit card-anchor probe by observed poll coverage instead of a fixed 20-second window (asturksever, #1282).
|
- Bound the Transit card-anchor probe by observed poll coverage instead of a fixed 20-second window (asturksever, #1282).
|
||||||
|
- Keep visible-page Transit probes focused through the browser testing API (asturksever, #1282).
|
||||||
|
- Add a real-browser regression gate for hidden-tab periodic work, explicit refresh, resume and teardown (asturksever, #1282).
|
||||||
@@ -4002,6 +4002,8 @@ animation. Polls, surface completions and trail fades request individual renders
|
|||||||
Disable clears vehicles, detection references, requests, timers and holds;
|
Disable clears vehicles, detection references, requests, timers and holds;
|
||||||
destruction also releases collections and registrations.
|
destruction also releases collections and registrations.
|
||||||
|
|
||||||
|
The `scripts/qa-layer-visibility.mjs` gate uses the development-only registration seam and real browser tab visibility to prove periodic work pauses while hidden, explicit refresh still works, resume performs one overdue refresh, and disabled layers stay stopped.
|
||||||
|
|
||||||
**Acceptance harness.** Run against an already running development server:
|
**Acceptance harness.** Run against an already running development server:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|||||||
@@ -1070,5 +1070,6 @@
|
|||||||
"scripts/assemble-changelog.mjs",
|
"scripts/assemble-changelog.mjs",
|
||||||
"src/tooling/changelogFragments.test.mjs",
|
"src/tooling/changelogFragments.test.mjs",
|
||||||
"src/data/gbfsProxyRateLimit.test.mjs",
|
"src/data/gbfsProxyRateLimit.test.mjs",
|
||||||
"src/terrainHeightsBounds.test.mjs"
|
"src/terrainHeightsBounds.test.mjs",
|
||||||
|
"scripts/qa-layer-visibility.mjs"
|
||||||
]
|
]
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* Real-browser proof for manager-owned periodic work and document visibility.
|
||||||
|
* Uses the app's development-only layer registration seam; no provider keys.
|
||||||
|
* Run: node scripts/qa-layer-visibility.mjs --url http://localhost:4173
|
||||||
|
* Hiding uses a real background tab, never a patched document.hidden getter.
|
||||||
|
*/
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
import { chromeExecutablePath } from './qa-chrome.mjs';
|
||||||
|
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const urlIndex = args.indexOf('--url');
|
||||||
|
const url = new URL(
|
||||||
|
(urlIndex >= 0 ? args[urlIndex + 1] : undefined) ||
|
||||||
|
process.env.QA_BASE_URL ||
|
||||||
|
'http://localhost:4173',
|
||||||
|
);
|
||||||
|
url.searchParams.set('welcome', '0');
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
executablePath: chromeExecutablePath(),
|
||||||
|
headless: true,
|
||||||
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-background-timer-throttling',
|
||||||
|
'--disable-renderer-backgrounding',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
const check = (name, actual, expected) => {
|
||||||
|
assert.equal(actual, expected, name);
|
||||||
|
console.log(`PASS ${name}: ${actual}`);
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
const focus = await page.createCDPSession();
|
||||||
|
await focus.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
||||||
|
await page.goto(url.href, { waitUntil: 'domcontentloaded' });
|
||||||
|
await page.waitForFunction(
|
||||||
|
() => window.__gevQaRegisterLayer && window.__godsEyeView?.dataManager,
|
||||||
|
);
|
||||||
|
await page.evaluate(async () => {
|
||||||
|
const manager = window.__godsEyeView.dataManager;
|
||||||
|
window.__qaVisibilityUpdates = 0;
|
||||||
|
window.__gevQaRegisterLayer(manager, {
|
||||||
|
id: 'qa-document-visibility',
|
||||||
|
name: 'Visibility QA',
|
||||||
|
updateInterval: 750,
|
||||||
|
init() {},
|
||||||
|
enable() {},
|
||||||
|
disable() {},
|
||||||
|
destroy() {},
|
||||||
|
async update() {
|
||||||
|
window.__qaVisibilityUpdates++;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
getStats() {
|
||||||
|
return { count: window.__qaVisibilityUpdates };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await manager.setEnabled('qa-document-visibility', true, { origin: 'qa' });
|
||||||
|
});
|
||||||
|
const count = () => page.evaluate(() => window.__qaVisibilityUpdates);
|
||||||
|
await page.waitForFunction(() => window.__qaVisibilityUpdates >= 3);
|
||||||
|
check('periodic updates run while visible', (await count()) >= 3, true);
|
||||||
|
await focus.send('Emulation.setFocusEmulationEnabled', { enabled: false });
|
||||||
|
const cover = await browser.newPage();
|
||||||
|
await cover.bringToFront();
|
||||||
|
await page.waitForFunction(() => document.visibilityState === 'hidden');
|
||||||
|
const hiddenCount = await count();
|
||||||
|
await pause(2200);
|
||||||
|
check('hidden tab makes no periodic updates', await count(), hiddenCount);
|
||||||
|
check(
|
||||||
|
'explicit refresh remains available while hidden',
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.__godsEyeView.dataManager.refreshLayer('qa-document-visibility'),
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
check('explicit hidden refresh runs once', await count(), hiddenCount + 1);
|
||||||
|
await focus.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
||||||
|
await page.waitForFunction(
|
||||||
|
() =>
|
||||||
|
document.visibilityState === 'visible' &&
|
||||||
|
window.__qaVisibilityUpdates > 0,
|
||||||
|
);
|
||||||
|
await pause(200);
|
||||||
|
check(
|
||||||
|
'resume performs one overdue refresh without a catch-up burst',
|
||||||
|
await count(),
|
||||||
|
hiddenCount + 2,
|
||||||
|
);
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.__godsEyeView.dataManager.setEnabled(
|
||||||
|
'qa-document-visibility',
|
||||||
|
false,
|
||||||
|
{ origin: 'qa' },
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const stopped = await count();
|
||||||
|
await focus.send('Emulation.setFocusEmulationEnabled', { enabled: false });
|
||||||
|
await cover.bringToFront();
|
||||||
|
await page.waitForFunction(() => document.visibilityState === 'hidden');
|
||||||
|
await focus.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
||||||
|
await pause(1100);
|
||||||
|
check(
|
||||||
|
'disabled layer does not resume on visibility change',
|
||||||
|
await count(),
|
||||||
|
stopped,
|
||||||
|
);
|
||||||
|
check(
|
||||||
|
'QA layer destroys cleanly',
|
||||||
|
await page.evaluate(() =>
|
||||||
|
window.__gevQaUnregisterLayer(
|
||||||
|
window.__godsEyeView.dataManager,
|
||||||
|
'qa-document-visibility',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
console.log('RESULT: 7 passed');
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
@@ -275,6 +275,10 @@ const browser = await puppeteer.launch({
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
const page = await browser.newPage();
|
const page = await browser.newPage();
|
||||||
|
// This gate measures visible-page motion. OS minimization must not silently
|
||||||
|
// switch it into the app's intentional hidden-tab polling pause.
|
||||||
|
const focus = await page.createCDPSession();
|
||||||
|
await focus.send('Emulation.setFocusEmulationEnabled', { enabled: true });
|
||||||
boundPageEvaluations(page);
|
boundPageEvaluations(page);
|
||||||
await page.setViewport({ width: 1920, height: 1080 });
|
await page.setViewport({ width: 1920, height: 1080 });
|
||||||
const pageErrors = [];
|
const pageErrors = [];
|
||||||
|
|||||||
Reference in new issue
Block a user