@wordbricks/playwright-mcp 0.1.20 → 0.1.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli-wrapper.js +15 -14
- package/cli.js +1 -1
- package/config.d.ts +11 -6
- package/index.d.ts +7 -5
- package/index.js +1 -1
- package/lib/browserContextFactory.js +131 -58
- package/lib/browserServerBackend.js +14 -12
- package/lib/config.js +60 -46
- package/lib/context.js +41 -39
- package/lib/extension/cdpRelay.js +67 -61
- package/lib/extension/extensionContextFactory.js +10 -10
- package/lib/frameworkPatterns.js +21 -21
- package/lib/hooks/antiBotDetectionHook.js +59 -52
- package/lib/hooks/core.js +11 -10
- package/lib/hooks/eventConsumer.js +21 -21
- package/lib/hooks/events.js +3 -3
- package/lib/hooks/formatToolCallEvent.js +3 -7
- package/lib/hooks/frameworkStateHook.js +40 -40
- package/lib/hooks/grouping.js +3 -3
- package/lib/hooks/jsonLdDetectionHook.js +44 -37
- package/lib/hooks/networkFilters.js +17 -17
- package/lib/hooks/networkSetup.js +9 -7
- package/lib/hooks/networkTrackingHook.js +21 -21
- package/lib/hooks/pageHeightHook.js +9 -9
- package/lib/hooks/registry.js +15 -16
- package/lib/hooks/requireTabHook.js +3 -3
- package/lib/hooks/schema.js +38 -38
- package/lib/hooks/waitHook.js +7 -7
- package/lib/index.js +12 -10
- package/lib/mcp/inProcessTransport.js +3 -4
- package/lib/mcp/proxyBackend.js +43 -28
- package/lib/mcp/server.js +24 -19
- package/lib/mcp/tool.js +14 -8
- package/lib/mcp/transport.js +60 -53
- package/lib/playwrightTransformer.js +129 -106
- package/lib/program.js +54 -52
- package/lib/response.js +36 -30
- package/lib/sessionLog.js +19 -17
- package/lib/tab.js +41 -39
- package/lib/tools/common.js +19 -19
- package/lib/tools/console.js +11 -11
- package/lib/tools/dialogs.js +18 -15
- package/lib/tools/evaluate.js +26 -17
- package/lib/tools/extractFrameworkState.js +48 -37
- package/lib/tools/files.js +17 -14
- package/lib/tools/form.js +32 -23
- package/lib/tools/getSnapshot.js +14 -15
- package/lib/tools/getVisibleHtml.js +33 -17
- package/lib/tools/install.js +20 -20
- package/lib/tools/keyboard.js +29 -24
- package/lib/tools/mouse.js +29 -31
- package/lib/tools/navigate.js +19 -23
- package/lib/tools/network.js +12 -14
- package/lib/tools/networkDetail.js +58 -49
- package/lib/tools/networkSearch/bodySearch.js +46 -32
- package/lib/tools/networkSearch/grouping.js +15 -6
- package/lib/tools/networkSearch/helpers.js +4 -4
- package/lib/tools/networkSearch/searchHtml.js +25 -16
- package/lib/tools/networkSearch/urlSearch.js +56 -14
- package/lib/tools/networkSearch.js +46 -36
- package/lib/tools/pdf.js +13 -12
- package/lib/tools/repl.js +66 -54
- package/lib/tools/screenshot.js +57 -33
- package/lib/tools/scroll.js +29 -24
- package/lib/tools/snapshot.js +66 -49
- package/lib/tools/tabs.js +22 -19
- package/lib/tools/tool.js +5 -3
- package/lib/tools/utils.js +17 -13
- package/lib/tools/wait.js +24 -19
- package/lib/tools.js +21 -20
- package/lib/utils/adBlockFilter.js +29 -26
- package/lib/utils/codegen.js +20 -16
- package/lib/utils/extensionPath.js +4 -4
- package/lib/utils/fileUtils.js +17 -13
- package/lib/utils/graphql.js +69 -58
- package/lib/utils/guid.js +3 -3
- package/lib/utils/httpServer.js +9 -9
- package/lib/utils/log.js +3 -3
- package/lib/utils/manualPromise.js +7 -7
- package/lib/utils/networkFormat.js +7 -5
- package/lib/utils/package.js +4 -4
- package/lib/utils/sanitizeHtml.js +66 -34
- package/lib/utils/truncate.js +25 -25
- package/lib/utils/withTimeout.js +1 -1
- package/package.json +34 -57
- package/src/index.ts +27 -17
- package/LICENSE +0 -202
package/lib/tools/keyboard.js
CHANGED
|
@@ -13,21 +13,23 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { z } from
|
|
17
|
-
import
|
|
18
|
-
import { elementSchema } from
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import * as javascript from "../utils/codegen.js";
|
|
18
|
+
import { elementSchema } from "./snapshot.js";
|
|
19
|
+
import { defineTabTool } from "./tool.js";
|
|
20
|
+
import { generateLocator } from "./utils.js";
|
|
21
21
|
const pressKey = defineTabTool({
|
|
22
|
-
capability:
|
|
22
|
+
capability: "core",
|
|
23
23
|
schema: {
|
|
24
|
-
name:
|
|
25
|
-
title:
|
|
26
|
-
description:
|
|
24
|
+
name: "browser_press_key",
|
|
25
|
+
title: "Press a key",
|
|
26
|
+
description: "Press a key on the keyboard",
|
|
27
27
|
inputSchema: z.object({
|
|
28
|
-
key: z
|
|
28
|
+
key: z
|
|
29
|
+
.string()
|
|
30
|
+
.describe("Name of the key to press or a character to generate, such as `ArrowLeft` or `a`"),
|
|
29
31
|
}),
|
|
30
|
-
type:
|
|
32
|
+
type: "input",
|
|
31
33
|
},
|
|
32
34
|
handle: async (tab, params, response) => {
|
|
33
35
|
response.setIncludeSnapshot();
|
|
@@ -39,18 +41,24 @@ const pressKey = defineTabTool({
|
|
|
39
41
|
},
|
|
40
42
|
});
|
|
41
43
|
const typeSchema = elementSchema.extend({
|
|
42
|
-
text: z.string().describe(
|
|
43
|
-
submit: z
|
|
44
|
-
|
|
44
|
+
text: z.string().describe("Text to type into the element"),
|
|
45
|
+
submit: z
|
|
46
|
+
.boolean()
|
|
47
|
+
.optional()
|
|
48
|
+
.describe("Whether to submit entered text (press Enter after)"),
|
|
49
|
+
slowly: z
|
|
50
|
+
.boolean()
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Whether to type one character at a time. Useful for triggering key handlers in the page. By default entire text is filled in at once."),
|
|
45
53
|
});
|
|
46
54
|
const type = defineTabTool({
|
|
47
|
-
capability:
|
|
55
|
+
capability: "core",
|
|
48
56
|
schema: {
|
|
49
|
-
name:
|
|
50
|
-
title:
|
|
51
|
-
description:
|
|
57
|
+
name: "browser_type",
|
|
58
|
+
title: "Type text",
|
|
59
|
+
description: "Type text into editable element",
|
|
52
60
|
inputSchema: typeSchema,
|
|
53
|
-
type:
|
|
61
|
+
type: "input",
|
|
54
62
|
},
|
|
55
63
|
handle: async (tab, params, response) => {
|
|
56
64
|
const locator = await tab.refLocator(params);
|
|
@@ -67,12 +75,9 @@ const type = defineTabTool({
|
|
|
67
75
|
if (params.submit) {
|
|
68
76
|
response.setIncludeSnapshot();
|
|
69
77
|
response.addCode(`await page.${await generateLocator(locator)}.press('Enter');`);
|
|
70
|
-
await locator.press(
|
|
78
|
+
await locator.press("Enter");
|
|
71
79
|
}
|
|
72
80
|
});
|
|
73
81
|
},
|
|
74
82
|
});
|
|
75
|
-
export default [
|
|
76
|
-
pressKey,
|
|
77
|
-
type,
|
|
78
|
-
];
|
|
83
|
+
export default [pressKey, type];
|
package/lib/tools/mouse.js
CHANGED
|
@@ -13,22 +13,24 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { z } from
|
|
17
|
-
import { defineTabTool } from
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { defineTabTool } from "./tool.js";
|
|
18
18
|
const elementSchema = z.object({
|
|
19
|
-
element: z
|
|
19
|
+
element: z
|
|
20
|
+
.string()
|
|
21
|
+
.describe("Human-readable element description used to obtain permission to interact with the element"),
|
|
20
22
|
});
|
|
21
23
|
const mouseMove = defineTabTool({
|
|
22
|
-
capability:
|
|
24
|
+
capability: "vision",
|
|
23
25
|
schema: {
|
|
24
|
-
name:
|
|
25
|
-
title:
|
|
26
|
-
description:
|
|
26
|
+
name: "browser_mouse_move_xy",
|
|
27
|
+
title: "Move mouse",
|
|
28
|
+
description: "Move mouse to a given position",
|
|
27
29
|
inputSchema: elementSchema.extend({
|
|
28
|
-
x: z.number().describe(
|
|
29
|
-
y: z.number().describe(
|
|
30
|
+
x: z.number().describe("X coordinate"),
|
|
31
|
+
y: z.number().describe("Y coordinate"),
|
|
30
32
|
}),
|
|
31
|
-
type:
|
|
33
|
+
type: "readOnly",
|
|
32
34
|
},
|
|
33
35
|
handle: async (tab, params, response) => {
|
|
34
36
|
response.addCode(`// Move mouse to (${params.x}, ${params.y})`);
|
|
@@ -39,16 +41,16 @@ const mouseMove = defineTabTool({
|
|
|
39
41
|
},
|
|
40
42
|
});
|
|
41
43
|
const mouseClick = defineTabTool({
|
|
42
|
-
capability:
|
|
44
|
+
capability: "vision",
|
|
43
45
|
schema: {
|
|
44
|
-
name:
|
|
45
|
-
title:
|
|
46
|
-
description:
|
|
46
|
+
name: "browser_mouse_click_xy",
|
|
47
|
+
title: "Click",
|
|
48
|
+
description: "Click left mouse button at a given position",
|
|
47
49
|
inputSchema: elementSchema.extend({
|
|
48
|
-
x: z.number().describe(
|
|
49
|
-
y: z.number().describe(
|
|
50
|
+
x: z.number().describe("X coordinate"),
|
|
51
|
+
y: z.number().describe("Y coordinate"),
|
|
50
52
|
}),
|
|
51
|
-
type:
|
|
53
|
+
type: "input",
|
|
52
54
|
},
|
|
53
55
|
handle: async (tab, params, response) => {
|
|
54
56
|
response.setIncludeSnapshot();
|
|
@@ -64,18 +66,18 @@ const mouseClick = defineTabTool({
|
|
|
64
66
|
},
|
|
65
67
|
});
|
|
66
68
|
const mouseDrag = defineTabTool({
|
|
67
|
-
capability:
|
|
69
|
+
capability: "vision",
|
|
68
70
|
schema: {
|
|
69
|
-
name:
|
|
70
|
-
title:
|
|
71
|
-
description:
|
|
71
|
+
name: "browser_mouse_drag_xy",
|
|
72
|
+
title: "Drag mouse",
|
|
73
|
+
description: "Drag left mouse button to a given position",
|
|
72
74
|
inputSchema: elementSchema.extend({
|
|
73
|
-
startX: z.number().describe(
|
|
74
|
-
startY: z.number().describe(
|
|
75
|
-
endX: z.number().describe(
|
|
76
|
-
endY: z.number().describe(
|
|
75
|
+
startX: z.number().describe("Start X coordinate"),
|
|
76
|
+
startY: z.number().describe("Start Y coordinate"),
|
|
77
|
+
endX: z.number().describe("End X coordinate"),
|
|
78
|
+
endY: z.number().describe("End Y coordinate"),
|
|
77
79
|
}),
|
|
78
|
-
type:
|
|
80
|
+
type: "input",
|
|
79
81
|
},
|
|
80
82
|
handle: async (tab, params, response) => {
|
|
81
83
|
response.setIncludeSnapshot();
|
|
@@ -92,8 +94,4 @@ const mouseDrag = defineTabTool({
|
|
|
92
94
|
});
|
|
93
95
|
},
|
|
94
96
|
});
|
|
95
|
-
export default [
|
|
96
|
-
mouseMove,
|
|
97
|
-
mouseClick,
|
|
98
|
-
mouseDrag,
|
|
99
|
-
];
|
|
97
|
+
export default [mouseMove, mouseClick, mouseDrag];
|
package/lib/tools/navigate.js
CHANGED
|
@@ -13,18 +13,18 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { z } from
|
|
17
|
-
import {
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { defineTabTool, defineTool } from "./tool.js";
|
|
18
18
|
const navigate = defineTool({
|
|
19
|
-
capability:
|
|
19
|
+
capability: "core",
|
|
20
20
|
schema: {
|
|
21
|
-
name:
|
|
22
|
-
title:
|
|
23
|
-
description:
|
|
21
|
+
name: "browser_navigate",
|
|
22
|
+
title: "Navigate to a URL",
|
|
23
|
+
description: "Navigate to a URL",
|
|
24
24
|
inputSchema: z.object({
|
|
25
|
-
url: z.string().describe(
|
|
25
|
+
url: z.string().describe("The URL to navigate to"),
|
|
26
26
|
}),
|
|
27
|
-
type:
|
|
27
|
+
type: "action",
|
|
28
28
|
},
|
|
29
29
|
handle: async (context, params, response) => {
|
|
30
30
|
const tab = await context.ensureTab();
|
|
@@ -34,13 +34,13 @@ const navigate = defineTool({
|
|
|
34
34
|
},
|
|
35
35
|
});
|
|
36
36
|
const goBack = defineTabTool({
|
|
37
|
-
capability:
|
|
37
|
+
capability: "core",
|
|
38
38
|
schema: {
|
|
39
|
-
name:
|
|
40
|
-
title:
|
|
41
|
-
description:
|
|
39
|
+
name: "browser_navigate_back",
|
|
40
|
+
title: "Go back",
|
|
41
|
+
description: "Go back to the previous page",
|
|
42
42
|
inputSchema: z.object({}),
|
|
43
|
-
type:
|
|
43
|
+
type: "readOnly",
|
|
44
44
|
},
|
|
45
45
|
handle: async (tab, params, response) => {
|
|
46
46
|
await tab.page.goBack();
|
|
@@ -49,13 +49,13 @@ const goBack = defineTabTool({
|
|
|
49
49
|
},
|
|
50
50
|
});
|
|
51
51
|
const goForward = defineTabTool({
|
|
52
|
-
capability:
|
|
52
|
+
capability: "core",
|
|
53
53
|
schema: {
|
|
54
|
-
name:
|
|
55
|
-
title:
|
|
56
|
-
description:
|
|
54
|
+
name: "browser_navigate_forward",
|
|
55
|
+
title: "Go forward",
|
|
56
|
+
description: "Go forward to the next page",
|
|
57
57
|
inputSchema: z.object({}),
|
|
58
|
-
type:
|
|
58
|
+
type: "readOnly",
|
|
59
59
|
},
|
|
60
60
|
handle: async (tab, params, response) => {
|
|
61
61
|
await tab.page.goForward();
|
|
@@ -63,8 +63,4 @@ const goForward = defineTabTool({
|
|
|
63
63
|
response.addCode(`await page.goForward();`);
|
|
64
64
|
},
|
|
65
65
|
});
|
|
66
|
-
export default [
|
|
67
|
-
navigate,
|
|
68
|
-
goBack,
|
|
69
|
-
goForward,
|
|
70
|
-
];
|
|
66
|
+
export default [navigate, goBack, goForward];
|
package/lib/tools/network.js
CHANGED
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
* See the License for the specific language governing permissions and
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
|
-
import { z } from
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { formatUrlWithTrimmedParams } from "../hooks/networkFilters.js";
|
|
18
|
+
import { defineTabTool } from "./tool.js";
|
|
19
19
|
// const requests = defineTool({
|
|
20
20
|
// capability: 'core',
|
|
21
21
|
// schema: {
|
|
@@ -61,13 +61,13 @@ import { formatUrlWithTrimmedParams } from '../hooks/networkFilters.js';
|
|
|
61
61
|
// },
|
|
62
62
|
// });
|
|
63
63
|
const requests = defineTabTool({
|
|
64
|
-
capability:
|
|
64
|
+
capability: "core",
|
|
65
65
|
schema: {
|
|
66
|
-
name:
|
|
67
|
-
title:
|
|
68
|
-
description:
|
|
66
|
+
name: "browser_network_requests",
|
|
67
|
+
title: "List network requests",
|
|
68
|
+
description: "Returns all network requests since loading the page",
|
|
69
69
|
inputSchema: z.object({}),
|
|
70
|
-
type:
|
|
70
|
+
type: "readOnly",
|
|
71
71
|
},
|
|
72
72
|
handle: async (tab, params, response) => {
|
|
73
73
|
const requests = tab.requests();
|
|
@@ -82,8 +82,8 @@ export async function renderRequest(request, response) {
|
|
|
82
82
|
result.push(`[${request.method().toUpperCase()}] ${trimmedUrl}`);
|
|
83
83
|
// Append response status if available with compact content-type
|
|
84
84
|
if (response) {
|
|
85
|
-
const ct = response.headers()?.[
|
|
86
|
-
result.push(`=> [${response.status()}] ${response.statusText()}${ct ? ` ct=${ct}` :
|
|
85
|
+
const ct = response.headers()?.["content-type"];
|
|
86
|
+
result.push(`=> [${response.status()}] ${response.statusText()}${ct ? ` ct=${ct}` : ""}`);
|
|
87
87
|
}
|
|
88
88
|
// Always include request headers (excluding HTTP/2 pseudo-headers and common headers)
|
|
89
89
|
// const allHeaders = await request.allHeaders();
|
|
@@ -116,8 +116,6 @@ export async function renderRequest(request, response) {
|
|
|
116
116
|
// if (body)
|
|
117
117
|
// result.push(`body=${body}`);
|
|
118
118
|
// }
|
|
119
|
-
return result.join(
|
|
119
|
+
return result.join("\n");
|
|
120
120
|
}
|
|
121
|
-
export default [
|
|
122
|
-
requests,
|
|
123
|
-
];
|
|
121
|
+
export default [requests];
|
|
@@ -1,25 +1,25 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
const TIMEOUT_MS = ms(
|
|
1
|
+
import ms from "ms";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import { getNetworkEventEntry } from "../hooks/networkSetup.js";
|
|
4
|
+
import { extractGraphQLResponseInfo, minifyGraphQLQuery, minifyGraphQLRequestBody, parseGraphQLRequestFromHttp, summarizeGraphQL, } from "../utils/graphql.js";
|
|
5
|
+
import { formatNetworkSummaryLine } from "../utils/networkFormat.js";
|
|
6
|
+
import { sanitizeHtml } from "../utils/sanitizeHtml.js";
|
|
7
|
+
import { formatTruncationLine } from "../utils/truncate.js";
|
|
8
|
+
import { withTimeout } from "../utils/withTimeout.js";
|
|
9
|
+
import { defineTabTool } from "./tool.js";
|
|
10
|
+
const TIMEOUT_MS = ms("5s");
|
|
11
11
|
const CHUNK_BYTES = 8 * 1024; // 8KB per page
|
|
12
12
|
const contextBodyMap = new WeakMap();
|
|
13
13
|
// Skip HTTP/2 pseudo-headers and their non-colon variants in displayed request headers
|
|
14
14
|
const SKIP_REQUEST_HEADER_KEYS = new Set([
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
":authority",
|
|
16
|
+
":method",
|
|
17
|
+
":path",
|
|
18
|
+
":scheme",
|
|
19
|
+
"authority",
|
|
20
|
+
"method",
|
|
21
|
+
"path",
|
|
22
|
+
"scheme",
|
|
23
23
|
]);
|
|
24
24
|
const getStateMap = (ctx) => {
|
|
25
25
|
let m = contextBodyMap.get(ctx);
|
|
@@ -30,7 +30,7 @@ const getStateMap = (ctx) => {
|
|
|
30
30
|
return m;
|
|
31
31
|
};
|
|
32
32
|
const networkDetailSchema = z.object({
|
|
33
|
-
id: z.number().describe(
|
|
33
|
+
id: z.number().describe("The network event id from events log"),
|
|
34
34
|
});
|
|
35
35
|
const normalizeHeaderKeys = (headers) => {
|
|
36
36
|
const out = {};
|
|
@@ -39,7 +39,7 @@ const normalizeHeaderKeys = (headers) => {
|
|
|
39
39
|
return out;
|
|
40
40
|
};
|
|
41
41
|
const formatBytes = (n) => {
|
|
42
|
-
const units = [
|
|
42
|
+
const units = ["B", "KB", "MB", "GB"];
|
|
43
43
|
let i = 0;
|
|
44
44
|
let v = n;
|
|
45
45
|
while (v >= 1024 && i < units.length - 1) {
|
|
@@ -67,16 +67,16 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
|
|
|
67
67
|
if (!Number.isNaN(duration))
|
|
68
68
|
lines.push(`Duration: ${duration}ms`);
|
|
69
69
|
}
|
|
70
|
-
lines.push(`Failure: ${request.failure()?.errorText ||
|
|
70
|
+
lines.push(`Failure: ${request.failure()?.errorText || "None"}`);
|
|
71
71
|
if (!response) {
|
|
72
72
|
lines.push(`\nRequest Headers:`);
|
|
73
73
|
const headerKeysNoResp = Object.keys(reqHeaders)
|
|
74
|
-
.filter(k => !SKIP_REQUEST_HEADER_KEYS.has(k))
|
|
74
|
+
.filter((k) => !SKIP_REQUEST_HEADER_KEYS.has(k))
|
|
75
75
|
.sort();
|
|
76
76
|
for (const key of headerKeysNoResp)
|
|
77
77
|
lines.push(`${key}: ${reqHeaders[key]}`);
|
|
78
78
|
lines.push(`\nNo response received yet`);
|
|
79
|
-
return lines.join(
|
|
79
|
+
return lines.join("\n");
|
|
80
80
|
}
|
|
81
81
|
// Determine if this request is GraphQL (used for both request and response sections)
|
|
82
82
|
const gqlReq = parseGraphQLRequestFromHttp(request.method(), request.url(), reqHeaders, requestBody);
|
|
@@ -96,25 +96,27 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
|
|
|
96
96
|
// Print full request headers on the first call (excluding authority/method/path/scheme)
|
|
97
97
|
lines.push(`\nRequest Headers:`);
|
|
98
98
|
const headerKeys = Object.keys(reqHeaders)
|
|
99
|
-
.filter(k => !SKIP_REQUEST_HEADER_KEYS.has(k))
|
|
99
|
+
.filter((k) => !SKIP_REQUEST_HEADER_KEYS.has(k))
|
|
100
100
|
.sort();
|
|
101
101
|
for (const key of headerKeys)
|
|
102
102
|
lines.push(`${key}: ${reqHeaders[key]}`);
|
|
103
|
-
const reqCt = reqHeaders[
|
|
103
|
+
const reqCt = reqHeaders["content-type"];
|
|
104
104
|
// GraphQL request summary (if applicable) and compact body handling
|
|
105
105
|
if (gqlReq) {
|
|
106
106
|
lines.push(`\nGraphQL Request: ${summarizeGraphQL(gqlReq)}`);
|
|
107
|
-
if (gqlReq.variables &&
|
|
107
|
+
if (gqlReq.variables &&
|
|
108
|
+
typeof gqlReq.variables === "object" &&
|
|
109
|
+
!Array.isArray(gqlReq.variables)) {
|
|
108
110
|
const varKeys = Object.keys(gqlReq.variables);
|
|
109
111
|
if (varKeys.length)
|
|
110
|
-
lines.push(`Variables: { ${varKeys.join(
|
|
112
|
+
lines.push(`Variables: { ${varKeys.join(", ")} }`);
|
|
111
113
|
}
|
|
112
114
|
}
|
|
113
115
|
if (requestBody) {
|
|
114
116
|
if (gqlReq) {
|
|
115
117
|
lines.push(`\nRequest Body (GraphQL):`);
|
|
116
118
|
try {
|
|
117
|
-
if (reqCt && reqCt.includes(
|
|
119
|
+
if (reqCt && reqCt.includes("application/graphql")) {
|
|
118
120
|
lines.push(minifyGraphQLQuery(requestBody));
|
|
119
121
|
}
|
|
120
122
|
else {
|
|
@@ -138,16 +140,18 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
|
|
|
138
140
|
}
|
|
139
141
|
}
|
|
140
142
|
const resHeaders = normalizeHeaderKeys(await response.allHeaders());
|
|
141
|
-
const resCt = resHeaders[
|
|
142
|
-
const resCl = resHeaders[
|
|
143
|
+
const resCt = resHeaders["content-type"];
|
|
144
|
+
const resCl = resHeaders["content-length"];
|
|
143
145
|
const headerParts = [];
|
|
144
146
|
if (resCt)
|
|
145
147
|
headerParts.push(`content-type=${resCt}`);
|
|
146
148
|
if (resCl)
|
|
147
149
|
headerParts.push(`content-length=${resCl}`);
|
|
148
150
|
if (headerParts.length)
|
|
149
|
-
lines.push(`\nResponse Headers: { ${headerParts.join(
|
|
150
|
-
const setCookieHeaders = await response
|
|
151
|
+
lines.push(`\nResponse Headers: { ${headerParts.join(", ")} }`);
|
|
152
|
+
const setCookieHeaders = await response
|
|
153
|
+
.headerValues("set-cookie")
|
|
154
|
+
.catch(() => []);
|
|
151
155
|
if (setCookieHeaders.length) {
|
|
152
156
|
lines.push(`\nSet-Cookie Headers (${setCookieHeaders.length}):`);
|
|
153
157
|
for (const cookie of setCookieHeaders)
|
|
@@ -162,29 +166,32 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
|
|
|
162
166
|
catch (e) {
|
|
163
167
|
const msg = e instanceof Error ? e.message : String(e);
|
|
164
168
|
lines.push(`\nFailed to read response body: ${msg}`);
|
|
165
|
-
return lines.join(
|
|
169
|
+
return lines.join("\n");
|
|
166
170
|
}
|
|
167
171
|
// On first page, summarize GraphQL response if applicable
|
|
168
172
|
if (!state.headersShown && state.isGraphQLRequest) {
|
|
169
173
|
try {
|
|
170
|
-
const fullText = buf.toString(
|
|
174
|
+
const fullText = buf.toString("utf8");
|
|
171
175
|
const gqlInfo = extractGraphQLResponseInfo(fullText);
|
|
172
176
|
if (gqlInfo) {
|
|
173
|
-
lines.push(`\nGraphQL Response: ${gqlInfo.hasErrors ? `${gqlInfo.errorCount} error(s)` :
|
|
177
|
+
lines.push(`\nGraphQL Response: ${gqlInfo.hasErrors ? `${gqlInfo.errorCount} error(s)` : "ok"}`);
|
|
174
178
|
if (gqlInfo.topMessages.length)
|
|
175
|
-
lines.push(`Errors: ${gqlInfo.topMessages.join(
|
|
179
|
+
lines.push(`Errors: ${gqlInfo.topMessages.join(" | ")}`);
|
|
176
180
|
if (gqlInfo.dataKeys.length)
|
|
177
|
-
lines.push(`Data keys: ${gqlInfo.dataKeys.join(
|
|
181
|
+
lines.push(`Data keys: ${gqlInfo.dataKeys.join(", ")}`);
|
|
178
182
|
}
|
|
179
183
|
}
|
|
180
184
|
catch { }
|
|
181
185
|
}
|
|
182
|
-
let bodyText = buf.toString(
|
|
186
|
+
let bodyText = buf.toString("utf8");
|
|
183
187
|
const resHeadersForBody = normalizeHeaderKeys(await response.allHeaders());
|
|
184
|
-
const resCtForBody = resHeadersForBody[
|
|
185
|
-
const isHtml = resCtForBody.includes(
|
|
188
|
+
const resCtForBody = resHeadersForBody["content-type"] || "";
|
|
189
|
+
const isHtml = resCtForBody.includes("html");
|
|
186
190
|
if (isHtml)
|
|
187
|
-
bodyText = sanitizeHtml(bodyText, {
|
|
191
|
+
bodyText = sanitizeHtml(bodyText, {
|
|
192
|
+
shouldRemoveScripts: false,
|
|
193
|
+
shouldRemoveStyles: true,
|
|
194
|
+
});
|
|
188
195
|
const totalLength = bodyText.length;
|
|
189
196
|
if (state.cursor >= totalLength) {
|
|
190
197
|
state.cursor = 0;
|
|
@@ -195,23 +202,25 @@ const formatRequestDetail = async (ctx, { request, response, id, }) => {
|
|
|
195
202
|
const chunk = bodyText.slice(start, end);
|
|
196
203
|
const nextCursor = end;
|
|
197
204
|
const more = nextCursor < totalLength;
|
|
198
|
-
const percent = totalLength
|
|
199
|
-
|
|
205
|
+
const percent = totalLength
|
|
206
|
+
? Math.round((nextCursor / totalLength) * 100)
|
|
207
|
+
: 100;
|
|
208
|
+
lines.push(`\nBody Chunk (text): ${percent}%${more ? " (more)" : " (done)"}:`);
|
|
200
209
|
lines.push(chunk);
|
|
201
210
|
if (more)
|
|
202
211
|
lines.push(formatTruncationLine(nextCursor, totalLength, { formatter: formatBytes }));
|
|
203
212
|
state.cursor = nextCursor;
|
|
204
213
|
state.headersShown = true;
|
|
205
|
-
return lines.join(
|
|
214
|
+
return lines.join("\n");
|
|
206
215
|
};
|
|
207
216
|
const networkDetail = defineTabTool({
|
|
208
|
-
capability:
|
|
217
|
+
capability: "core",
|
|
209
218
|
schema: {
|
|
210
|
-
name:
|
|
211
|
-
title:
|
|
212
|
-
description:
|
|
219
|
+
name: "browser_network_detail",
|
|
220
|
+
title: "Get network request detail",
|
|
221
|
+
description: "Show detailed info for a specific network request by event id. Call repeatedly to page through the body.",
|
|
213
222
|
inputSchema: networkDetailSchema,
|
|
214
|
-
type:
|
|
223
|
+
type: "readOnly",
|
|
215
224
|
},
|
|
216
225
|
handle: async (tab, params, r) => {
|
|
217
226
|
const entry = getNetworkEventEntry(tab.context, params.id);
|