From d5d7d3361799787b9c570994276a3e0faa5e880d Mon Sep 17 00:00:00 2001 From: prayagv <76861333+prayagv@users.noreply.github.com> Date: Thu, 22 Oct 2026 17:56:27 +0530 Subject: [PATCH] I let the drill read a full Grid slot --- docs/verification/grid-worker-recovery.md | 2 +- scripts/grid-recovery-drill.mjs | 5 +++-- scripts/grid-recovery.test.mjs | 16 +++++++++++++--- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/docs/verification/grid-worker-recovery.md b/docs/verification/grid-worker-recovery.md index cf9fe75..3bf6abc 100644 --- a/docs/verification/grid-worker-recovery.md +++ b/docs/verification/grid-worker-recovery.md @@ -2,7 +2,7 @@ This is an opt-in hosted experiment. Run **Grid Worker Recovery Drill** with GitHub Actions `workflow_dispatch`; it is not part of the regular browser matrix and it does not target a shared or remote Grid. The manual workflow creates a unique Compose project from the pinned Selenium Hub and Chrome image digests, uses one Chrome slot, checks the effective 2 GiB memory and shared-memory limits, and has an eight-minute job timeout with project-scoped teardown. -The drill first validates the Grid `/status` slot inventory and confirms it starts empty. It sends one `quality-drill-unsupported` request with the hub's five-second session queue timeout and one-second retry interval. Only a structured session-creation failure plus a validated inventory without that browser is `CAPABILITY_UNAVAILABLE`; connection trouble is `GRID_UNREACHABLE`. +The drill first validates the Grid `/status` slot inventory and confirms it starts empty. Grid's `ready` flag describes available capacity: a full one-slot Grid reports `ready: false` while its owned session is running. The harness requires readiness during initial startup, then keeps reading the validated inventory even while that slot is occupied. It sends one `quality-drill-unsupported` request with the hub's five-second session queue timeout and one-second retry interval. Only a structured session-creation failure plus a validated inventory without that browser is `CAPABILITY_UNAVAILABLE`; connection trouble is `GRID_UNREACHABLE`. For the interruption, the TestNG worker creates one Chrome session through `WebDriverFactory`, visits a harmless local `data:` page, and atomically writes a private receipt before printing its readiness marker. The receipt directory is mode `0700`; the receipt is mode `0600`, and neither is uploaded. The Node harness independently matches the receipt's exact session in the validated Grid inventory, then sends `SIGKILL` only to the detached process group it spawned. It requires the orphan to remain visible before issuing one W3C DELETE for that receipt-authorized session. It rejects stale, mismatched, foreign, or inventory-unavailable receipts and never uses a blanket session delete. diff --git a/scripts/grid-recovery-drill.mjs b/scripts/grid-recovery-drill.mjs index 73b23de..607ecca 100644 --- a/scripts/grid-recovery-drill.mjs +++ b/scripts/grid-recovery-drill.mjs @@ -54,7 +54,7 @@ export function validatePrivateGridUrl(value) { export function parseGridInventory(payload) { const value = payload?.value; - if (!value || value.ready !== true || !Array.isArray(value.nodes)) { + if (!value || typeof value.ready !== 'boolean' || !Array.isArray(value.nodes)) { throw failure('GRID_INVENTORY_SCHEMA_UNAVAILABLE', 'UNKNOWN', 'inventory'); } const sessions = []; @@ -90,7 +90,7 @@ export function parseGridInventory(payload) { } } return Object.freeze({ - ready: true, + ready: value.ready, nodeCount: value.nodes.length, sessions: Object.freeze(sessions), stereotypes: Object.freeze(stereotypes), @@ -362,6 +362,7 @@ async function waitForGrid(grid, fetchImpl = fetch) { try { const inventory = await readGridInventory(grid, fetchImpl); if ( + inventory.ready && inventory.nodeCount > 0 && inventory.stereotypes.includes('chrome') ) { diff --git a/scripts/grid-recovery.test.mjs b/scripts/grid-recovery.test.mjs index cf9975c..84b0b55 100644 --- a/scripts/grid-recovery.test.mjs +++ b/scripts/grid-recovery.test.mjs @@ -16,10 +16,10 @@ const ownedId = 'owned:session-1'; const origin = 'http://127.0.0.1:4444'; const grid = { origin, endpoint: `${origin}/wd/hub` }; -function statusPayload(sessions = [], browserName = 'chrome') { +function statusPayload(sessions = [], browserName = 'chrome', ready = true) { return { value: { - ready: true, + ready, nodes: [{ id: 'node-test', slots: [{ @@ -84,7 +84,17 @@ test('capability classification follows the validated slot inventory and structu test('missing or changed Grid inventory schemas fail closed', () => { assert.throws(() => parseGridInventory({ value: { ready: true } }), /GRID_INVENTORY_SCHEMA_UNAVAILABLE/); assert.throws(() => parseGridInventory({ value: { ready: true, nodes: [{ slots: [{}] }] } }), /GRID_INVENTORY_SCHEMA_UNAVAILABLE/); - assert.throws(() => parseGridInventory({ value: { ready: false, nodes: [] } }), /GRID_INVENTORY_SCHEMA_UNAVAILABLE/); + assert.throws(() => parseGridInventory({ value: { nodes: [] } }), /GRID_INVENTORY_SCHEMA_UNAVAILABLE/); +}); + +test('a full single-slot Grid can be not ready and still expose its live session', () => { + const inventory = parseGridInventory( + statusPayload([{ sessionId: ownedId }], 'chrome', false), + ); + + assert.equal(inventory.ready, false); + assert.equal(inventory.sessions.length, 1); + assert.equal(inventory.sessions[0].sessionId, ownedId); }); test('private Grid URL rejects remote hosts, credentials, query strings and fragments', () => {