fix: recheck attached worker policy after final artifacts
This commit is contained in:
1 parent
98c3d2ff93
commit
0729aba6d7
3 files changed
+105
-2
No files matched your search
@@ -17,7 +17,11 @@ final sanitized URL capture are supplied by default. Set
|
||||
`environment.service_workers` to `allow` when the application requires them.
|
||||
For attached browsers, `block` rejects captures when the selected page or its
|
||||
frames are controlled by a service worker or already have a registration,
|
||||
including after navigation. Existing caller registrations are never removed.
|
||||
including after navigation. Policy checks bracket each artifact capture, including
|
||||
the final step. If an external worker takes control while artifacts are being
|
||||
captured, REA rejects the capture and retains the completed artifacts in
|
||||
`details.diagnostics.collected_artifacts`; they do not establish policy-compliant
|
||||
completion. Existing caller registrations are never removed.
|
||||
Navigation can run destination code before a newly discovered worker is rejected;
|
||||
that rejection does not roll back browser effects. REA removes its owned
|
||||
future-document scripts, restores live frame methods, then disconnects.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { AnalysisCleanupLeaseGroup } from "../artifacts/AnalysisCleanupLease.js";
|
||||
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
|
||||
import { ProviderCleanupError } from "../domain/providerCleanupError.js";
|
||||
import { PlaywrightBrowserScenarioProvider } from "./PlaywrightBrowserScenarioProvider.js";
|
||||
import { AnalysisCleanupLease } from "../artifacts/AnalysisCleanupLease.js";
|
||||
@@ -1484,3 +1485,77 @@ it.each(["launch", "connect"] as const)(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.each([false, true])(
|
||||
"checks attached worker policy after final artifacts, worker takeover=%s",
|
||||
async (takeover) => {
|
||||
const { page, cleanup } = fixture();
|
||||
let controlled = false;
|
||||
let captures = 0;
|
||||
Object.assign(page, {
|
||||
waitForTimeout: vi.fn(async () => undefined),
|
||||
content: vi.fn(async () => {
|
||||
captures += 1;
|
||||
controlled = takeover && captures === 2;
|
||||
return controlled ? "<worker-response/>" : "<uncontrolled-response/>";
|
||||
}),
|
||||
});
|
||||
page.evaluate.mockImplementation(async (script: unknown) =>
|
||||
controlled &&
|
||||
typeof script === "string" &&
|
||||
script.includes("serviceWorker.controller")
|
||||
? "service_worker_blocked"
|
||||
: null,
|
||||
);
|
||||
const input = browserScenarioSchema.parse({
|
||||
...connectedScenario("block"),
|
||||
capture: { after_each_step: ["dom"], at_end: ["dom"], events: [] },
|
||||
});
|
||||
const provider = new PlaywrightBrowserScenarioProvider(
|
||||
(scenario, options) =>
|
||||
PlaywrightScenarioSession.open(scenario, {}, options),
|
||||
);
|
||||
try {
|
||||
const result = await provider.captureScenario(input, {});
|
||||
expect(captures).toBe(2);
|
||||
expect(cleanup.browserClosed).toBe(true);
|
||||
if (takeover) {
|
||||
if (result.ok)
|
||||
throw new Error("Expected final worker policy rejection");
|
||||
expect(projectAnalysisError(result.error)).toMatchObject({
|
||||
details: {
|
||||
diagnostics: {
|
||||
collected_artifacts: {
|
||||
dom: { value: { text: "<worker-response/>" } },
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(result.error).toMatchObject({
|
||||
reason: "service_worker_blocked",
|
||||
cleanupIncomplete: false,
|
||||
diagnostics: {
|
||||
collected_artifacts: {
|
||||
dom: { state: "captured", value: { text: "<worker-response/>" } },
|
||||
},
|
||||
},
|
||||
partialObservation: {
|
||||
capture: {
|
||||
browser: { cleanup: "disconnected-external" },
|
||||
steps: [{ action: "goto_start" }],
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (!result.ok) throw result.error;
|
||||
expect(result.value.steps).toHaveLength(2);
|
||||
expect(result.value.steps[1]).toMatchObject({
|
||||
status: "completed",
|
||||
completeness: { status: "complete", equality_eligible: true },
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
await provider.close();
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -6,6 +6,9 @@ import type { BrowserContext } from "playwright-core";
|
||||
|
||||
import type { BrowserScenario } from "../domain/browserScenario.js";
|
||||
import type { BrowserScenarioAction } from "../domain/browserScenarioValues.js";
|
||||
import { jsonValueSchema } from "../domain/jsonValue.js";
|
||||
import { PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY } from "./providerIdentities.js";
|
||||
import { ProviderAdapterError } from "../domain/providerAdapterError.js";
|
||||
import { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import { BrowserObservationError } from "../domain/browserObservationError.js";
|
||||
import type { BrowserScenarioSessionPort } from "./BrowserScenarioSessionPort.js";
|
||||
@@ -459,7 +462,7 @@ export class PlaywrightScenarioSession implements BrowserScenarioSessionPort {
|
||||
undefined,
|
||||
signal,
|
||||
);
|
||||
return withPlaywrightExecutionBoundary(
|
||||
const artifacts = await withPlaywrightExecutionBoundary(
|
||||
() =>
|
||||
capturePlaywrightStepArtifacts({
|
||||
context: this.opened.context,
|
||||
@@ -470,6 +473,27 @@ export class PlaywrightScenarioSession implements BrowserScenarioSessionPort {
|
||||
undefined,
|
||||
signal,
|
||||
);
|
||||
try {
|
||||
await withPlaywrightExecutionBoundary(
|
||||
() => this.serviceWorkers?.assertBlocked() ?? Promise.resolve(),
|
||||
undefined,
|
||||
signal,
|
||||
);
|
||||
} catch (cause: unknown) {
|
||||
const failure =
|
||||
cause instanceof AnalysisError
|
||||
? cause
|
||||
: new ProviderAdapterError(
|
||||
PLAYWRIGHT_BROWSER_SCENARIO_PROVIDER_IDENTITY.id,
|
||||
OPERATION,
|
||||
{ cause },
|
||||
);
|
||||
failure.retainDiagnostics({
|
||||
collected_artifacts: jsonValueSchema.parse(artifacts),
|
||||
});
|
||||
throw failure;
|
||||
}
|
||||
return artifacts;
|
||||
}
|
||||
|
||||
async close(stopLoading = this.signal?.aborted === true) {
|
||||
|
||||
Reference in new issue
Block a user