




















@@ -112,6 +112,44 @@ async function withSymlinkPathEscape<T>(params: {
112112}
113113}
114114115+type MockWithCalls = { mock: { calls: unknown[][] } };
116+117+function isRecord(value: unknown): value is Record<string, unknown> {
118+return typeof value === "object" && value !== null && !Array.isArray(value);
119+}
120+121+function requireRecord(value: unknown, label: string): Record<string, unknown> {
122+if (!isRecord(value)) {
123+throw new Error(`expected ${label} to be an object`);
124+}
125+return value;
126+}
127+128+function expectRecordFields(value: unknown, label: string, expected: Record<string, unknown>) {
129+const record = requireRecord(value, label);
130+for (const [key, expectedValue] of Object.entries(expected)) {
131+expect(record[key]).toEqual(expectedValue);
132+}
133+}
134+135+function requireMockArg(mock: MockWithCalls, callIndex = 0, argIndex = 0) {
136+return requireRecord(mock.mock.calls[callIndex]?.[argIndex], "mock call argument");
137+}
138+139+function expectBrowserCallFields(
140+mock: MockWithCalls,
141+expected: Record<string, unknown>,
142+callIndex = 0,
143+) {
144+const arg = requireMockArg(mock, callIndex);
145+expect(typeof arg.cdpUrl).toBe("string");
146+expectRecordFields(arg, "browser call argument", expected);
147+}
148+149+function expectOkResult(result: unknown) {
150+expect(requireRecord(result, "response").ok).toBe(true);
151+}
152+115153describe("browser control server", () => {
116154installAgentContractHooks();
117155@@ -128,14 +166,11 @@ describe("browser control server", () => {
128166values: ["a", "b"],
129167});
130168expect(select.ok).toBe(true);
131-expect(pwMocks.selectOptionViaPlaywright).toHaveBeenCalledWith(
132-expect.objectContaining({
133-cdpUrl: expect.any(String),
134-targetId: "abcd1234",
135-ref: "5",
136-values: ["a", "b"],
137-}),
138-);
169+expectBrowserCallFields(pwMocks.selectOptionViaPlaywright, {
170+targetId: "abcd1234",
171+ref: "5",
172+values: ["a", "b"],
173+});
139174140175const fillCases: Array<{
141176input: Record<string, unknown>;
@@ -160,12 +195,13 @@ describe("browser control server", () => {
160195fields: [input],
161196});
162197expect(fill.ok).toBe(true);
163-expect(pwMocks.fillFormViaPlaywright).toHaveBeenCalledWith(
164-expect.objectContaining({
165- cdpUrl: expect.any(String),
198+expectBrowserCallFields(
199+pwMocks.fillFormViaPlaywright,
200+{
166201targetId: "abcd1234",
167202fields: [expected],
168-}),
203+},
204+pwMocks.fillFormViaPlaywright.mock.calls.length - 1,
169205);
170206}
171207@@ -175,14 +211,11 @@ describe("browser control server", () => {
175211height: 600,
176212});
177213expect(resize.ok).toBe(true);
178-expect(pwMocks.resizeViewportViaPlaywright).toHaveBeenCalledWith(
179-expect.objectContaining({
180-cdpUrl: expect.any(String),
181-targetId: "abcd1234",
182-width: 800,
183-height: 600,
184-}),
185-);
214+expectBrowserCallFields(pwMocks.resizeViewportViaPlaywright, {
215+targetId: "abcd1234",
216+width: 800,
217+height: 600,
218+});
186219187220const resizeZero = await postJson<{ error?: string; code?: string }>(`${base}/act`, {
188221kind: "resize",
@@ -207,29 +240,26 @@ describe("browser control server", () => {
207240timeMs: 5,
208241});
209242expect(wait.ok).toBe(true);
210-expect(pwMocks.waitForViaPlaywright).toHaveBeenCalledWith(
211-expect.objectContaining({
212-cdpUrl: state.cdpBaseUrl,
213-targetId: "abcd1234",
214-timeMs: 5,
215-}),
216-);
243+expectBrowserCallFields(pwMocks.waitForViaPlaywright, {
244+cdpUrl: state.cdpBaseUrl,
245+targetId: "abcd1234",
246+timeMs: 5,
247+});
217248218249const evalRes = await postJson<{ ok: boolean; result?: string }>(`${base}/act`, {
219250kind: "evaluate",
220251fn: "() => 1",
221252});
222253expect(evalRes.ok).toBe(true);
223254expect(evalRes.result).toBe("ok");
224-expect(pwMocks.evaluateViaPlaywright).toHaveBeenCalledWith(
225-expect.objectContaining({
226-cdpUrl: state.cdpBaseUrl,
227-targetId: "abcd1234",
228-fn: "() => 1",
229-ref: undefined,
230-signal: expect.any(AbortSignal),
231-}),
232-);
255+const evalCall = requireMockArg(pwMocks.evaluateViaPlaywright);
256+expectRecordFields(evalCall, "evaluate call", {
257+cdpUrl: state.cdpBaseUrl,
258+targetId: "abcd1234",
259+fn: "() => 1",
260+ref: undefined,
261+});
262+expect(evalCall.signal).toBeInstanceOf(AbortSignal);
233263},
234264slowTimeoutMs,
235265);
@@ -252,26 +282,23 @@ describe("browser control server", () => {
252282);
253283254284expect(batchRes.ok).toBe(true);
255-expect(pwMocks.batchViaPlaywright).toHaveBeenCalledWith(
256-expect.objectContaining({
257-cdpUrl: expect.any(String),
258-targetId: "abcd1234",
259-stopOnError: false,
260-evaluateEnabled: true,
261-actions: [
262-{
263-kind: "click",
264-selector: "button.save",
265-doubleClick: true,
266-delayMs: 25,
267-},
268-{
269-kind: "wait",
270-fn: "() => window.ready === true",
271-},
272-],
273-}),
274-);
285+expectBrowserCallFields(pwMocks.batchViaPlaywright, {
286+targetId: "abcd1234",
287+stopOnError: false,
288+evaluateEnabled: true,
289+actions: [
290+{
291+kind: "click",
292+selector: "button.save",
293+doubleClick: true,
294+delayMs: 25,
295+},
296+{
297+kind: "wait",
298+fn: "() => window.ready === true",
299+},
300+],
301+});
275302},
276303slowTimeoutMs,
277304);
@@ -290,22 +317,20 @@ describe("browser control server", () => {
290317});
291318292319expect(batchRes.ok).toBe(true);
293-expect(pwMocks.batchViaPlaywright).toHaveBeenCalledWith(
294-expect.objectContaining({
295-actions: [
296-{
297-kind: "type",
298-selector: "input.name",
299-text: " padded ",
300-},
301-{
302-kind: "type",
303-selector: "input.clearable",
304-text: "",
305-},
306-],
307-}),
308-);
320+expectRecordFields(requireMockArg(pwMocks.batchViaPlaywright), "batch call", {
321+actions: [
322+{
323+kind: "type",
324+selector: "input.name",
325+text: " padded ",
326+},
327+{
328+kind: "type",
329+selector: "input.clearable",
330+text: "",
331+},
332+],
333+});
309334},
310335slowTimeoutMs,
311336);
@@ -383,59 +408,56 @@ describe("browser control server", () => {
383408paths: ["a.txt"],
384409timeoutMs: 1234,
385410});
386-expect(upload).toMatchObject({ ok: true });
387-expect(pwMocks.armFileUploadViaPlaywright).toHaveBeenCalledWith(
388-expect.objectContaining({
389-cdpUrl: expect.any(String),
390-targetId: "abcd1234",
391-// The server resolves paths (which adds a drive letter on Windows for `\\tmp\\...` style roots).
392-paths: [path.resolve(DEFAULT_UPLOAD_DIR, "a.txt")],
393-timeoutMs: 1234,
394-}),
395-);
411+expectOkResult(upload);
412+expectBrowserCallFields(pwMocks.armFileUploadViaPlaywright, {
413+targetId: "abcd1234",
414+// The server resolves paths (which adds a drive letter on Windows for `\\tmp\\...` style roots).
415+paths: [path.resolve(DEFAULT_UPLOAD_DIR, "a.txt")],
416+timeoutMs: 1234,
417+});
396418397419const uploadWithRef = await postJson(`${base}/hooks/file-chooser`, {
398420paths: ["b.txt"],
399421ref: "e12",
400422});
401-expect(uploadWithRef).toMatchObject({ ok: true });
423+expectOkResult(uploadWithRef);
402424403425const uploadWithInputRef = await postJson(`${base}/hooks/file-chooser`, {
404426paths: ["c.txt"],
405427inputRef: "e99",
406428});
407-expect(uploadWithInputRef).toMatchObject({ ok: true });
429+expectOkResult(uploadWithInputRef);
408430409431const uploadWithElement = await postJson(`${base}/hooks/file-chooser`, {
410432paths: ["d.txt"],
411433element: "input[type=file]",
412434});
413-expect(uploadWithElement).toMatchObject({ ok: true });
435+expectOkResult(uploadWithElement);
414436415437const dialog = await postJson(`${base}/hooks/dialog`, {
416438accept: true,
417439timeoutMs: 5678,
418440});
419-expect(dialog).toMatchObject({ ok: true });
441+expectOkResult(dialog);
420442421443const waitDownload = await postJson(`${base}/wait/download`, {
422444path: "report.pdf",
423445timeoutMs: 1111,
424446});
425-expect(waitDownload).toMatchObject({ ok: true });
447+expectOkResult(waitDownload);
426448427449const download = await postJson(`${base}/download`, {
428450ref: "e12",
429451path: "report.pdf",
430452});
431-expect(download).toMatchObject({ ok: true });
453+expectOkResult(download);
432454433455const responseBody = await postJson(`${base}/response/body`, {
434456url: "**/api/data",
435457timeoutMs: 2222,
436458maxChars: 10,
437459});
438-expect(responseBody).toMatchObject({ ok: true });
460+expectOkResult(responseBody);
439461440462const consoleRes = (await realFetch(`${base}/console?level=error`).then((r) => r.json())) as {
441463ok: boolean;
@@ -455,13 +477,11 @@ describe("browser control server", () => {
455477});
456478expect(shot.ok).toBe(true);
457479expect(typeof shot.path).toBe("string");
458-expect(pwMocks.takeScreenshotViaPlaywright).toHaveBeenCalledWith(
459-expect.objectContaining({
460-element: "body",
461-type: "jpeg",
462-timeoutMs: 3333,
463-}),
464-);
480+expectRecordFields(requireMockArg(pwMocks.takeScreenshotViaPlaywright), "screenshot call", {
481+element: "body",
482+type: "jpeg",
483+timeoutMs: 3333,
484+});
465485});
466486467487it("blocks file chooser traversal / absolute paths outside uploads dir", async () => {
@@ -507,13 +527,12 @@ describe("browser control server", () => {
507527});
508528expect(res.ok).toBe(true);
509529expect(res.path).toContain("safe-trace.zip");
510-expect(pwMocks.traceStopViaPlaywright).toHaveBeenCalledWith(
511-expect.objectContaining({
512-cdpUrl: expect.any(String),
513-targetId: "abcd1234",
514-path: expect.stringContaining("safe-trace.zip"),
515-}),
516-);
530+const traceCall = requireMockArg(pwMocks.traceStopViaPlaywright);
531+expect(typeof traceCall.cdpUrl).toBe("string");
532+expectRecordFields(traceCall, "trace stop call", {
533+targetId: "abcd1234",
534+});
535+expect(String(traceCall.path)).toContain("safe-trace.zip");
517536});
518537519538it.each(guardedCurrentTabRouteCases)(
@@ -615,13 +634,12 @@ describe("browser control server", () => {
615634},
616635);
617636expect(res.ok).toBe(true);
618-expect(pwMocks.waitForDownloadViaPlaywright).toHaveBeenCalledWith(
619-expect.objectContaining({
620-cdpUrl: expect.any(String),
621-targetId: "abcd1234",
622-path: expect.stringContaining("safe-wait.pdf"),
623-}),
624-);
637+const waitCall = requireMockArg(pwMocks.waitForDownloadViaPlaywright);
638+expect(typeof waitCall.cdpUrl).toBe("string");
639+expectRecordFields(waitCall, "wait download call", {
640+targetId: "abcd1234",
641+});
642+expect(String(waitCall.path)).toContain("safe-wait.pdf");
625643});
626644627645it("download accepts in-root relative output path", async () => {
@@ -631,13 +649,12 @@ describe("browser control server", () => {
631649path: "safe-download.pdf",
632650});
633651expect(res.ok).toBe(true);
634-expect(pwMocks.downloadViaPlaywright).toHaveBeenCalledWith(
635-expect.objectContaining({
636-cdpUrl: expect.any(String),
637-targetId: "abcd1234",
638-ref: "e12",
639-path: expect.stringContaining("safe-download.pdf"),
640-}),
641-);
652+const downloadCall = requireMockArg(pwMocks.downloadViaPlaywright);
653+expect(typeof downloadCall.cdpUrl).toBe("string");
654+expectRecordFields(downloadCall, "download call", {
655+targetId: "abcd1234",
656+ref: "e12",
657+});
658+expect(String(downloadCall.path)).toContain("safe-download.pdf");
642659});
643660});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。