Merge candidate 20260908T070012Z-web-product

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-08 08:16:10 +01:00
2 changed files with 56 additions and 4 deletions
@@ -137,3 +137,30 @@ not merely a larger timeout or successful CDP acknowledgements. No Pulse fixture
installed convergence, destination receipt or release qualification was exercised.
Complete output is retained in maintainer run `20260908T065015Z-web-product`,
`headed-window-control.log`. Earlier failed controls remain relevant.
## Native tab-switch control — 8 September 2026
Run the same owned Xvfb command with `--headed-tab` instead of
`--headed-window`. This does not request minimisation or require a window
manager: it creates a second owned blank tab and activates it. Before freezing,
it requires observed hidden → visible/focused → hidden states, each bounded to
ten seconds with 250ms samples. It then uses the existing 2100ms host-side frozen
wait, resumes while backgrounded, and activates the original tab. Ordered
freeze/resume with unchanged ticks, a subsequent visible event, final focus and
timer progress remain required. No synthetic visibility events or forced focus
are used; no Playwright page session is attached.
One run exited 0 on the same Chrome revision, Playwright and Node versions above,
owned display :99. Preflight recorded hidden at tick 5, visible/focused at tick
11, then hidden at tick 12. Freeze and resume both recorded tick 12.
Post-resume remained hidden/unfocused at tick 12; tab activation produced a
visible event and visible/focused state at tick 18. Complete runtime identities,
intervention commands/responses and samples are retained in maintainer run
`20260908T070012Z-web-product/headed-tab-control.log`.
This establishes the positive control for the **tab-switch** mechanism, not
window minimisation. Earlier adverse results and the default mode's unreliable
250ms hidden-timer sample remain unchanged. No Pulse fixture or installed-pair
acceptance ran. The next source-level convergence fixture must retain raw
single-session ownership, the genuine background preflight, and the suspension
probe rather than attach a Playwright page and assume equivalent behaviour.
@@ -6,7 +6,8 @@ import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createRequire } from 'node:module';
import { chromium } from '@playwright/test';
const headedWindow = process.argv.includes('--headed-window');
const headedTab = process.argv.includes('--headed-tab');
const headedWindow = headedTab || process.argv.includes('--headed-window');
const checkForeground = headedWindow || process.argv.includes('--foreground');
if (headedWindow && !process.env.DISPLAY) throw new Error('--headed-window requires an owned X display');
const wait = ms => new Promise(resolve => setTimeout(resolve, ms));
@@ -50,7 +51,7 @@ try {
console.log(JSON.stringify({ browser: await send('Browser.getVersion'),
playwright: require('@playwright/test/package.json').version, node: process.version,
platform: process.platform, arch: process.arch, executable: chromium.executablePath(),
headedWindow, display: process.env.DISPLAY, observationBoundMs: headedWindow ? 10000 : 250 }));
headedWindow, headedTab, display: process.env.DISPLAY, observationBoundMs: headedWindow ? 10000 : 250 }));
const results = [];
// Preselected negative (focus forced) and positive (no forced focus), fresh targets.
for (const focusEnabled of (headedWindow ? [false] : [true, false])) {
@@ -85,7 +86,28 @@ try {
const snapshot = `({...window.probe, visibility: document.visibilityState, focus: document.hasFocus()})`;
await wait(250);
const before = await evaluate(snapshot);
if (headedWindow) {
let otherTarget;
const observe = async (stage, visibility) => {
const deadline = Date.now() + 10000;
let sample;
do {
await wait(250);
sample = await evaluate(snapshot);
console.log(JSON.stringify({ stage, snapshot: sample }));
if (sample.visibility === visibility && (visibility !== 'visible' || sample.focus)) return sample;
} while (Date.now() < deadline);
throw new Error(stage + ': native visibility transition not observed');
};
if (headedTab) {
assert.ok(before.visibility === 'visible' && before.focus, 'Tab baseline must be active');
otherTarget = (await windowCommand('Target.createTarget', { url: 'about:blank' })).targetId;
await windowCommand('Target.activateTarget', { targetId: otherTarget });
await observe('tab-background-preflight', 'hidden');
await windowCommand('Target.activateTarget', { targetId });
await observe('tab-foreground-preflight', 'visible');
await windowCommand('Target.activateTarget', { targetId: otherTarget });
await observe('tab-background-before-freeze', 'hidden');
} else if (headedWindow) {
assert.ok(before.visibility === 'visible' && before.focus, 'Headed baseline must be visible and focused');
await windowCommand('Browser.setWindowBounds', { windowId: windowInfo.windowId, bounds: { windowState: 'minimized' } });
await wait(250);
@@ -103,7 +125,9 @@ try {
// Resume is not foreground activation. Observe the two transitions separately.
let foreground;
if (checkForeground) {
if (headedWindow) {
if (headedTab) {
await windowCommand('Target.activateTarget', { targetId });
} else if (headedWindow) {
await windowCommand('Browser.setWindowBounds', { windowId: windowInfo.windowId, bounds: { windowState: 'normal' } });
}
await command('Page.bringToFront', {});
@@ -117,6 +141,7 @@ try {
}
const result = { focusEnabled, before, after, foreground, commands, suspended };
results.push(result); console.log(JSON.stringify(result));
if (otherTarget) await send('Target.closeTarget', { targetId: otherTarget });
} finally { await send('Target.closeTarget', { targetId }); }
}
if (!headedWindow) assert.ok(!results[0].suspended && results[0].after.ticks - results[0].before.ticks >= 20,