test(web): verify native return through owning CDP session

The application preflight stayed visible because Playwright enabled focus emulation on another CDP session. Disable that override in an opt-in loopback transport without fabricating visibility or weakening assertions. Preserve the ordinary fixtures and prior adverse evidence.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-09-08 09:24:03 +01:00
parent 7443465c28
commit a39b08e55d
5 changed files with 143 additions and 11 deletions
+19 -5
View File
@@ -56,12 +56,26 @@ an exact release-pair, live WebSocket convergence, real incident-store, or deliv
notification check. Keep any failed application run as evidence, not a component
fixture success in its place.
Known harness limitation (8 September 2026): the first local run reached the real
Retained adverse run (8 September 2026, c98c0565a7): the first local run reached the real
history row and settled initial incident details, then failed the native-hidden
preflight: the original page stayed `visible` for ten seconds after another
Playwright page was activated. The source-built frontend/backend succeeded and
the backend was stopped by cleanup. Do not remove the visibility assertion or
replace it with a synthetic DOM event to claim foreground coverage. The native
browser-control prerequisite remains unresolved; the Refresh portion of this
application scenario has **not** been executed successfully. The raw single-CDP
component checks above do not clear this application failure.
replace it with a synthetic DOM event to claim foreground coverage. That revision did not successfully execute Refresh after return. The raw
single-CDP component checks above do not clear this application failure.
The additive harness uses `tests/integration/tests/native-visibility.ts` only
when the opt-in is set. Playwright 1.56.1 enables focus emulation on its owning
CDP session (`crPage.js`); disabling it on a secondary session leaves the first
override active. A loopback-only CDP proxy forwards commands and replies, changing
only `Emulation.setFocusEmulationEnabled({enabled:true})` to `false` on that
owning session. It checks that this command was encountered. It does not inject
DOM visibility, fabricate events, freeze the page or change assertion results.
An owned Chromium/profile and proxy are cleaned up afterwards. Native tab
activation and visibility polling still have to pass before Refresh is clicked.
This version-sensitive harness must fail rather than infer backgrounding if
Chromium or Playwright changes. Ordinary runs use the unmodified base fixtures.
For the ordinary-journey control, repeat the command above with
`PULSE_E2E_INCIDENT_FOREGROUND` unset. Retain both exact-revision results;
a prior success is not evidence for a later untested revision.
+35 -1
View File
@@ -12,7 +12,9 @@
"@axe-core/playwright": "^4.13.0",
"@playwright/test": "1.56.1",
"@types/node": "^20.10.0",
"typescript": "^5.3.0"
"@types/ws": "^8.18.1",
"typescript": "^5.3.0",
"ws": "^8.21.3"
}
},
"node_modules/@axe-core/playwright": {
@@ -54,6 +56,16 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/axe-core": {
"version": "4.13.0",
"resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.13.0.tgz",
@@ -131,6 +143,28 @@
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/ws": {
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}
+3 -1
View File
@@ -32,6 +32,8 @@
"@axe-core/playwright": "^4.13.0",
"@playwright/test": "1.56.1",
"@types/node": "^20.10.0",
"typescript": "^5.3.0"
"@types/ws": "^8.18.1",
"typescript": "^5.3.0",
"ws": "^8.21.3"
}
}
@@ -1,4 +1,5 @@
import { expect, test } from "@playwright/test";
import { expect } from "@playwright/test";
import { test } from "./native-visibility";
import { ensureAuthenticated } from "./helpers";
const SCREENSHOT_PATH = "/tmp/vmware-alert-history-resource-incidents.png";
@@ -236,8 +237,6 @@ test.describe("VMware alert history resource incidents", () => {
console.log(JSON.stringify({ stage: "application-incident-initial-read-settled", incidentReads,
tokenAuthenticationPresent: await page.evaluate(() => Boolean(sessionStorage.getItem("pulse_auth"))),
liveWebSocket: false, incidentHTTP: "mocked" }));
const cdp = await context.newCDPSession(page);
await cdp.send("Emulation.setFocusEmulationEnabled", { enabled: false });
const other = await context.newPage();
try {
await other.goto("about:blank");
@@ -259,7 +258,6 @@ test.describe("VMware alert history resource incidents", () => {
visibility: "native hidden then visible", freeze: false }));
} finally {
await other.close();
await cdp.detach();
}
}
await page.screenshot({ path: SCREENSHOT_PATH, fullPage: true });
@@ -0,0 +1,84 @@
import { test as base, expect } from '@playwright/test';
import { spawn } from 'node:child_process';
import { mkdtemp, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import WebSocket, { WebSocketServer } from 'ws';
// Playwright 1.56 enables focus emulation on its own CDP session. Disabling
// it on a second session does not clear that first session's override. This
// opt-in transport changes only that emulation command, on the owning session;
// visibility observations, input, HTTP routing and assertions remain untouched.
const nativeVisibilityTest = base.extend({
browser: async ({ playwright, browserName, headless }, use) => {
if (browserName !== 'chromium' || headless || !process.env.DISPLAY) {
throw new Error('Native visibility requires headed Chromium on an owned display');
}
const profile = await mkdtemp(join(tmpdir(), 'pulse-native-visibility-'));
const child = spawn(playwright.chromium.executablePath(), [
'--no-sandbox', '--remote-debugging-port=0', `--user-data-dir=${profile}`, 'about:blank',
], { stdio: ['ignore', 'ignore', 'pipe'] });
const proxy = new WebSocketServer({ host: '127.0.0.1', port: 0 });
const sockets = new Set<WebSocket>();
let overrides = 0;
try {
const endpoint = await new Promise<string>((resolve, reject) => {
const timer = setTimeout(() => reject(new Error('Chromium endpoint timeout')), 15000);
let stderr = '';
child.once('error', error => { clearTimeout(timer); reject(error); });
child.once('exit', () => { clearTimeout(timer); reject(new Error('Chromium exited before connection')); });
child.stderr!.on('data', data => {
stderr += data;
const match = stderr.match(/DevTools listening on (ws:\/\/\S+)/);
if (match) { clearTimeout(timer); resolve(match[1]); }
});
});
proxy.on('connection', downstream => {
const upstream = new WebSocket(endpoint);
sockets.add(downstream); sockets.add(upstream);
const queued: string[] = [];
downstream.on('message', data => {
const message = JSON.parse(data.toString());
if (message.method === 'Emulation.setFocusEmulationEnabled' && message.params?.enabled === true) {
message.params.enabled = false;
overrides++;
}
const wire = JSON.stringify(message);
if (upstream.readyState === WebSocket.OPEN) upstream.send(wire);
else queued.push(wire);
});
upstream.on('open', () => { for (const wire of queued) upstream.send(wire); queued.length = 0; });
upstream.on('message', data => {
if (downstream.readyState === WebSocket.OPEN) downstream.send(data.toString());
});
upstream.on('error', () => downstream.close());
downstream.on('error', () => upstream.close());
downstream.on('close', () => upstream.close());
upstream.on('close', () => downstream.close());
});
if (!proxy.address()) await new Promise<void>(resolve => proxy.once('listening', resolve));
const address = proxy.address();
if (!address || typeof address === 'string') throw new Error('Missing proxy address');
const browser = await playwright.chromium.connectOverCDP(`ws://127.0.0.1:${address.port}`);
try {
console.log(JSON.stringify({ nativeVisibilityTransport: 'owning-session focus emulation disabled', browser: browser.version() }));
await use(browser);
expect(overrides, 'Expected Playwright owning-session focus command').toBeGreaterThan(0);
} finally { await browser.close(); }
} finally {
for (const socket of sockets) socket.terminate();
await new Promise<void>(resolve => proxy.close(() => resolve()));
if (child.exitCode === null && child.signalCode === null) {
const stopped = new Promise<void>(resolve => child.once('exit', () => resolve()));
child.kill('SIGTERM');
const timer = setTimeout(() => child.kill('SIGKILL'), 5000);
try { await stopped; } finally { clearTimeout(timer); }
}
await rm(profile, { recursive: true, force: true });
}
},
});
// Keep the ordinary journey on Playwright's unmodified default fixtures.
export const test = process.env.PULSE_E2E_INCIDENT_FOREGROUND === '1'
? nativeVisibilityTest : base;