fix(browser): identify scenario timeouts by Playwright error type
This commit is contained in:
1 parent
5d7d6ad9ab
commit
30fef57f75
2 files changed
+91
-3
No files matched your search
@@ -1322,3 +1322,85 @@ it("restores an environment mutation that completes after its deadline", async (
|
||||
);
|
||||
expect(browser.close).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it.each([
|
||||
new Error("Timeout unrelated launch diagnostic"),
|
||||
Object.assign(new Error("unrelated launch failure"), {
|
||||
name: "TimeoutError",
|
||||
}),
|
||||
])(
|
||||
"preserves a launch failure without inferring a Playwright deadline: %s",
|
||||
async (failure) => {
|
||||
const scenario = browserScenarioSchema.parse({
|
||||
browser: {
|
||||
mode: "launch",
|
||||
executable_path: process.execPath,
|
||||
headless: true,
|
||||
},
|
||||
start_url: { url: "https://example.test" },
|
||||
command_timeout_ms: 4_321,
|
||||
actions: scenarioActions,
|
||||
});
|
||||
let profilePath: string | undefined;
|
||||
await expect(
|
||||
openPlaywrightScenarioBrowser(
|
||||
scenario,
|
||||
{},
|
||||
{
|
||||
launcher: {
|
||||
connectOverCDP: async () => {
|
||||
throw new Error("Unexpected connection");
|
||||
},
|
||||
launchPersistentContext: async (profile) => {
|
||||
profilePath = profile;
|
||||
throw failure;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toBe(failure);
|
||||
expect(profilePath).toBeDefined();
|
||||
await expect(access(profilePath!)).rejects.toMatchObject({
|
||||
code: "ENOENT",
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("normalizes a genuine Playwright launch timeout and releases its profile", async () => {
|
||||
const { errors } = await import("playwright-core");
|
||||
const failure = new errors.TimeoutError("native launch deadline");
|
||||
const scenario = browserScenarioSchema.parse({
|
||||
browser: {
|
||||
mode: "launch",
|
||||
executable_path: process.execPath,
|
||||
headless: true,
|
||||
},
|
||||
start_url: { url: "https://example.test" },
|
||||
command_timeout_ms: 4_321,
|
||||
actions: scenarioActions,
|
||||
});
|
||||
let profilePath: string | undefined;
|
||||
await expect(
|
||||
openPlaywrightScenarioBrowser(
|
||||
scenario,
|
||||
{},
|
||||
{
|
||||
launcher: {
|
||||
connectOverCDP: async () => {
|
||||
throw new Error("Unexpected connection");
|
||||
},
|
||||
launchPersistentContext: async (profile) => {
|
||||
profilePath = profile;
|
||||
throw failure;
|
||||
},
|
||||
},
|
||||
},
|
||||
),
|
||||
).rejects.toMatchObject({
|
||||
reason: "timeout",
|
||||
userMessage: expect.stringContaining("4321 ms command_timeout_ms"),
|
||||
cause: failure,
|
||||
});
|
||||
expect(profilePath).toBeDefined();
|
||||
await expect(access(profilePath!)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createRequire } from "node:module";
|
||||
import { BrowserObservationError } from "../domain/browserObservationError.js";
|
||||
import {
|
||||
browserDeadlineRetry,
|
||||
@@ -23,9 +24,14 @@ export const scenarioCommandTimeoutMs = (
|
||||
scenario: Pick<BrowserScenario, "command_timeout_ms">,
|
||||
): number => selectedCdpCommandTimeoutMs(scenario.command_timeout_ms);
|
||||
|
||||
const playwrightTimedOut = (cause: unknown): cause is Error =>
|
||||
cause instanceof Error &&
|
||||
(cause.name === "TimeoutError" || cause.message.startsWith("Timeout "));
|
||||
const playwrightTimedOut = (cause: unknown): cause is Error => {
|
||||
if (!(cause instanceof Error) || cause.name !== "TimeoutError") return false;
|
||||
// Check the public producer type without loading Playwright at MCP startup.
|
||||
const { errors } = createRequire(import.meta.url)(
|
||||
"playwright-core",
|
||||
) as typeof import("playwright-core");
|
||||
return cause instanceof errors.TimeoutError;
|
||||
};
|
||||
|
||||
/**
|
||||
* `capture-browser-scenario` accepts only scenario JSON, so recovery names
|
||||
|
||||
Reference in new issue
Block a user