@tontoko/fast-playwright-mcp 0.0.8 → 0.1.0
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/README.md +277 -117
- package/lib/batch/batch-executor.js +4 -5
- package/lib/browser-context-factory.js +2 -4
- package/lib/browser-server-backend.js +5 -7
- package/lib/config.js +1 -1
- package/lib/context.js +1 -4
- package/lib/diagnostics/common/error-enrichment-utils.js +3 -2
- package/lib/diagnostics/common/index.js +4 -55
- package/lib/diagnostics/element-discovery.js +1 -2
- package/lib/diagnostics/frame-reference-manager.js +5 -6
- package/lib/diagnostics/resource-manager.js +1 -2
- package/lib/diagnostics/smart-config.js +5 -6
- package/lib/diagnostics/smart-handle.js +1 -2
- package/lib/extension/cdp-relay.js +32 -34
- package/lib/extension/extension-context-factory.js +4 -5
- package/lib/in-process-client.js +1 -1
- package/lib/loop/loop.js +5 -5
- package/lib/loopTools/main.js +1 -1
- package/lib/mcp/proxy-backend.js +2 -2
- package/lib/mcp/server.js +4 -6
- package/lib/{log.js → mcp/tool.js} +17 -11
- package/lib/mcp/transport.js +2 -4
- package/lib/program.js +2 -3
- package/lib/response.js +1 -2
- package/lib/session-log.js +1 -1
- package/lib/tab.js +2 -4
- package/lib/tools/diagnose/diagnose-config-handler.js +2 -3
- package/lib/tools/evaluate.js +1 -1
- package/lib/tools/keyboard.js +1 -1
- package/lib/tools/network.js +97 -6
- package/lib/tools/pdf.js +1 -1
- package/lib/tools/screenshot.js +1 -1
- package/lib/tools/snapshot.js +1 -1
- package/lib/tools/utils.js +6 -7
- package/lib/{javascript.js → utils/codegen.js} +1 -1
- package/lib/utils/common-formatters.js +2 -3
- package/lib/utils/error-handler-middleware.js +1 -2
- package/lib/{utils.js → utils/guid.js} +1 -1
- package/lib/utils/index.js +6 -0
- package/lib/utils/log.js +90 -0
- package/lib/utils/network-filter.js +114 -0
- package/lib/{package.js → utils/package.js} +2 -2
- package/lib/utils/request-logger.js +1 -1
- package/package.json +3 -3
package/lib/program.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/program.ts
|
|
2
2
|
import { Option, program } from "commander";
|
|
3
|
-
import debug from "debug";
|
|
4
3
|
import { startTraceViewerServer } from "playwright-core/lib/server";
|
|
5
4
|
import { contextFactory } from "./browser-context-factory.js";
|
|
6
5
|
import {
|
|
@@ -18,9 +17,9 @@ import {
|
|
|
18
17
|
} from "./extension/main.js";
|
|
19
18
|
import { runLoopTools } from "./loopTools/main.js";
|
|
20
19
|
import { start } from "./mcp/transport.js";
|
|
21
|
-
import {
|
|
20
|
+
import { programDebug } from "./utils/log.js";
|
|
21
|
+
import { packageJSON } from "./utils/package.js";
|
|
22
22
|
import { logServerStart } from "./utils/request-logger.js";
|
|
23
|
-
var programDebug = debug("pw:mcp:program");
|
|
24
23
|
program.version(`Version ${packageJSON.version}`).name(packageJSON.name).option("--allowed-origins <origins>", "semicolon-separated list of origins to allow the browser to request. Default is to allow all.", semicolonSeparatedList).option("--blocked-origins <origins>", "semicolon-separated list of origins to block the browser from requesting. Blocklist is evaluated before allowlist. If used without the allowlist, requests not matching the blocklist are still allowed.", semicolonSeparatedList).option("--block-service-workers", "block service workers").option("--browser <browser>", "browser or chrome channel to use, possible values: chrome, firefox, webkit, msedge.").option("--caps <caps>", "comma-separated list of additional capabilities to enable, possible values: vision, pdf.", commaSeparatedList).option("--cdp-endpoint <endpoint>", "CDP endpoint to connect to.").option("--config <path>", "path to the configuration file.").option("--device <device>", 'device to emulate, for example: "iPhone 15"').option("--executable-path <path>", "path to the browser executable.").option("--headless", "run browser in headless mode, headed by default").option("--host <host>", "host to bind server to. Default is localhost. Use 0.0.0.0 to bind to all interfaces.").option("--ignore-https-errors", "ignore https errors").option("--isolated", "keep the browser profile in memory, do not save it to disk.").option("--image-responses <mode>", 'whether to send image responses to the client. Can be "allow" or "omit", Defaults to "allow".').option("--no-sandbox", "disable the sandbox for all process types that are normally sandboxed.").option("--output-dir <path>", "path to the directory for output files.").option("--port <port>", "port to listen on for SSE transport.").option("--proxy-bypass <bypass>", 'comma-separated domains to bypass proxy, for example ".com,chromium.org,.domain.com"').option("--proxy-server <proxy>", 'specify proxy server, for example "http://myproxy:3128" or "socks5://myproxy:8080"').option("--save-session", "Whether to save the Playwright MCP session into the output directory.").option("--save-trace", "Whether to save the Playwright Trace of the session into the output directory.").option("--storage-state <path>", "path to the storage state file for isolated sessions.").option("--user-agent <ua string>", "specify user agent string").option("--user-data-dir <path>", "path to the user data directory. If not specified, a temporary directory will be created.").option("--viewport-size <size>", 'specify browser viewport size in pixels, for example "1280, 720"').addOption(new Option("--extension", 'Connect to a running browser instance (Edge/Chrome only). Requires the "Playwright MCP Bridge" browser extension to be installed.').hideHelp()).addOption(new Option("--connect-tool", "Allow to switch between different browser connection methods.").hideHelp()).addOption(new Option("--loop-tools", "Run loop tools").hideHelp()).addOption(new Option("--vision", "Legacy option, use --caps=vision instead").hideHelp()).action(async (options) => {
|
|
25
24
|
setupExitWatchdog();
|
|
26
25
|
if (options.vision) {
|
package/lib/response.js
CHANGED
|
@@ -18,15 +18,14 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/response.ts
|
|
21
|
-
import debug from "debug";
|
|
22
21
|
import { TIMEOUTS } from "./config/constants.js";
|
|
23
22
|
import { mergeExpectations } from "./schemas/expectation.js";
|
|
24
23
|
import { renderModalStates } from "./tab.js";
|
|
25
24
|
import { filterConsoleMessages } from "./utils/console-filter.js";
|
|
26
25
|
import { processImage } from "./utils/image-processor.js";
|
|
26
|
+
import { responseDebug } from "./utils/log.js";
|
|
27
27
|
import { TextReportBuilder } from "./utils/report-builder.js";
|
|
28
28
|
import { ResponseDiffDetector } from "./utils/response-diff-detector.js";
|
|
29
|
-
var responseDebug = debug("pw:mcp:response");
|
|
30
29
|
|
|
31
30
|
class Response {
|
|
32
31
|
_result = [];
|
package/lib/session-log.js
CHANGED
|
@@ -21,7 +21,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
21
21
|
import fs from "node:fs";
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import { outputFile } from "./config.js";
|
|
24
|
-
import { logUnhandledError } from "./log.js";
|
|
24
|
+
import { logUnhandledError } from "./utils/log.js";
|
|
25
25
|
|
|
26
26
|
class SessionLog {
|
|
27
27
|
_folder;
|
package/lib/tab.js
CHANGED
|
@@ -19,17 +19,15 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/tab.ts
|
|
21
21
|
import { EventEmitter } from "node:events";
|
|
22
|
-
import debug from "debug";
|
|
23
22
|
import { TIMEOUTS } from "./config/constants.js";
|
|
24
|
-
import { logUnhandledError } from "./log.js";
|
|
25
23
|
import { ManualPromise } from "./manual-promise.js";
|
|
26
24
|
import { callOnPageNoTrace, waitForCompletion } from "./tools/utils.js";
|
|
25
|
+
import { logUnhandledError } from "./utils/log.js";
|
|
26
|
+
import { snapshotDebug, tabDebug } from "./utils/log.js";
|
|
27
27
|
var REF_VALUE_REGEX = /\[ref=([^\]]+)\]/;
|
|
28
28
|
var TabEvents = {
|
|
29
29
|
modalState: "modalState"
|
|
30
30
|
};
|
|
31
|
-
var snapshotDebug = debug("pw:mcp:snapshot");
|
|
32
|
-
var tabDebug = debug("pw:mcp:tab");
|
|
33
31
|
|
|
34
32
|
class Tab extends EventEmitter {
|
|
35
33
|
context;
|
|
@@ -18,11 +18,10 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/tools/diagnose/diagnose-config-handler.ts
|
|
21
|
-
import debug from "debug";
|
|
22
21
|
import { getCurrentThresholds } from "../../diagnostics/diagnostic-thresholds.js";
|
|
23
22
|
import { PageAnalyzer } from "../../diagnostics/page-analyzer.js";
|
|
24
23
|
import { UnifiedDiagnosticSystem } from "../../diagnostics/unified-system.js";
|
|
25
|
-
|
|
24
|
+
import { smartConfigDebug } from "../../utils/log.js";
|
|
26
25
|
|
|
27
26
|
class DiagnoseConfigHandler {
|
|
28
27
|
validateConfiguration() {
|
|
@@ -30,7 +29,7 @@ class DiagnoseConfigHandler {
|
|
|
30
29
|
const thresholdsManager = getCurrentThresholds();
|
|
31
30
|
return thresholdsManager.getConfigDiagnostics();
|
|
32
31
|
} catch (error) {
|
|
33
|
-
|
|
32
|
+
smartConfigDebug("Configuration validation failed:", error);
|
|
34
33
|
return {
|
|
35
34
|
status: "failed",
|
|
36
35
|
warnings: [
|
package/lib/tools/evaluate.js
CHANGED
|
@@ -19,8 +19,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/tools/evaluate.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { quote } from "../javascript.js";
|
|
23
22
|
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { quote } from "../utils/codegen.js";
|
|
24
24
|
import { defineTabTool } from "./tool.js";
|
|
25
25
|
import { generateLocator } from "./utils.js";
|
|
26
26
|
var evaluateSchema = z.object({
|
package/lib/tools/keyboard.js
CHANGED
|
@@ -19,8 +19,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/tools/keyboard.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { quote } from "../javascript.js";
|
|
23
22
|
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { quote } from "../utils/codegen.js";
|
|
24
24
|
import { generateKeyPressCode } from "../utils/common-formatters.js";
|
|
25
25
|
import { baseElementSchema as elementSchema } from "./base-tool-handler.js";
|
|
26
26
|
import { defineTabTool } from "./tool.js";
|
package/lib/tools/network.js
CHANGED
|
@@ -19,23 +19,114 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/tools/network.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
|
+
import {
|
|
23
|
+
filterNetworkRequests
|
|
24
|
+
} from "../utils/network-filter.js";
|
|
22
25
|
import { defineTabTool } from "./tool.js";
|
|
26
|
+
var networkFilterSchema = z.object({
|
|
27
|
+
urlPatterns: z.array(z.string()).optional(),
|
|
28
|
+
excludeUrlPatterns: z.array(z.string()).optional(),
|
|
29
|
+
statusRanges: z.array(z.object({
|
|
30
|
+
min: z.number(),
|
|
31
|
+
max: z.number()
|
|
32
|
+
})).optional(),
|
|
33
|
+
methods: z.array(z.string()).optional(),
|
|
34
|
+
maxRequests: z.number().default(20),
|
|
35
|
+
newestFirst: z.boolean().default(true)
|
|
36
|
+
});
|
|
23
37
|
var requests = defineTabTool({
|
|
24
38
|
capability: "core",
|
|
25
39
|
schema: {
|
|
26
40
|
name: "browser_network_requests",
|
|
27
41
|
title: "List network requests",
|
|
28
|
-
description:
|
|
29
|
-
inputSchema:
|
|
42
|
+
description: 'Returns network requests since loading the page with optional filtering. urlPatterns:["api/users"] to filter by URL patterns. excludeUrlPatterns:["analytics"] to exclude specific patterns. statusRanges:[{min:200,max:299}] for success codes only. methods:["GET","POST"] to filter by HTTP method. maxRequests:10 to limit results. newestFirst:false for chronological order. Supports regex patterns for advanced filtering.',
|
|
43
|
+
inputSchema: networkFilterSchema.partial(),
|
|
30
44
|
type: "readOnly"
|
|
31
45
|
},
|
|
32
|
-
handle: async (tab,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
46
|
+
handle: async (tab, params, response) => {
|
|
47
|
+
try {
|
|
48
|
+
const requestList = await Promise.resolve(tab.requests());
|
|
49
|
+
const requestEntries = Array.from(requestList.entries());
|
|
50
|
+
const filterOptions = buildFilterOptions(params);
|
|
51
|
+
const result = processNetworkRequests(requestEntries, filterOptions);
|
|
52
|
+
displayFilterSummary(response, filterOptions, result.filteredCount, result.totalCount);
|
|
53
|
+
displayResults(response, result.filteredRequests, result.totalCount);
|
|
54
|
+
} catch (error) {
|
|
55
|
+
response.addResult(`Error retrieving network requests: ${error instanceof Error ? error.message : String(error)}`);
|
|
36
56
|
}
|
|
37
57
|
}
|
|
38
58
|
});
|
|
59
|
+
function hasFilterOptions(options) {
|
|
60
|
+
return !!(options.urlPatterns?.length || options.excludeUrlPatterns?.length || options.statusRanges?.length || options.methods?.length || options.maxRequests && options.maxRequests !== 20);
|
|
61
|
+
}
|
|
62
|
+
function applyFilters(requestList, options) {
|
|
63
|
+
const networkRequests = requestList.map(([request, response]) => ({
|
|
64
|
+
url: request.url(),
|
|
65
|
+
method: request.method(),
|
|
66
|
+
status: response?.status(),
|
|
67
|
+
statusText: response?.statusText() || "",
|
|
68
|
+
headers: response?.headers() || {},
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
duration: undefined
|
|
71
|
+
}));
|
|
72
|
+
const filtered = filterNetworkRequests(networkRequests, options);
|
|
73
|
+
return requestList.filter(([request]) => filtered.some((f) => f.url === request.url() && f.method === request.method()));
|
|
74
|
+
}
|
|
75
|
+
function buildFilterOptions(params) {
|
|
76
|
+
return {
|
|
77
|
+
urlPatterns: params.urlPatterns,
|
|
78
|
+
excludeUrlPatterns: params.excludeUrlPatterns,
|
|
79
|
+
statusRanges: params.statusRanges,
|
|
80
|
+
methods: params.methods,
|
|
81
|
+
maxRequests: params.maxRequests ?? 20,
|
|
82
|
+
newestFirst: params.newestFirst ?? true
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
function processNetworkRequests(requestEntries, filterOptions) {
|
|
86
|
+
const totalCount = requestEntries.length;
|
|
87
|
+
let filteredRequests = applyFilters(requestEntries, filterOptions);
|
|
88
|
+
if (hasFilterOptions(filterOptions) && filterOptions.newestFirst === false) {
|
|
89
|
+
filteredRequests = filteredRequests.slice().reverse();
|
|
90
|
+
}
|
|
91
|
+
if (filterOptions.maxRequests && filteredRequests.length > filterOptions.maxRequests) {
|
|
92
|
+
filteredRequests = filteredRequests.slice(0, filterOptions.maxRequests);
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
filteredRequests,
|
|
96
|
+
filteredCount: filteredRequests.length,
|
|
97
|
+
totalCount
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function displayFilterSummary(response, filterOptions, filteredCount, totalCount) {
|
|
101
|
+
if (!hasFilterOptions(filterOptions)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
response.addResult(`Filter Summary: ${filteredCount}/${totalCount} requests match criteria`);
|
|
105
|
+
if (filterOptions.urlPatterns?.length) {
|
|
106
|
+
response.addResult(` URL patterns: ${filterOptions.urlPatterns.join(", ")}`);
|
|
107
|
+
}
|
|
108
|
+
if (filterOptions.excludeUrlPatterns?.length) {
|
|
109
|
+
response.addResult(` Exclude URL patterns: ${filterOptions.excludeUrlPatterns.join(", ")}`);
|
|
110
|
+
}
|
|
111
|
+
if (filterOptions.statusRanges?.length) {
|
|
112
|
+
response.addResult(` Status ranges: ${filterOptions.statusRanges.map((r) => `${r.min}-${r.max}`).join(", ")}`);
|
|
113
|
+
}
|
|
114
|
+
if (filterOptions.methods?.length) {
|
|
115
|
+
response.addResult(` Methods: ${filterOptions.methods.join(", ")}`);
|
|
116
|
+
}
|
|
117
|
+
if (filterOptions.maxRequests && filterOptions.maxRequests !== 20) {
|
|
118
|
+
response.addResult(` maxRequests: ${filterOptions.maxRequests}`);
|
|
119
|
+
}
|
|
120
|
+
response.addResult("");
|
|
121
|
+
}
|
|
122
|
+
function displayResults(response, filteredRequests, totalCount) {
|
|
123
|
+
for (const [req, res] of filteredRequests) {
|
|
124
|
+
response.addResult(renderRequest(req, res));
|
|
125
|
+
}
|
|
126
|
+
if (filteredRequests.length === 0 && totalCount > 0) {
|
|
127
|
+
response.addResult("No requests match the specified filter criteria.");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
39
130
|
function renderRequest(request, response) {
|
|
40
131
|
const result = [];
|
|
41
132
|
result.push(`[${request.method().toUpperCase()}] ${request.url()}`);
|
package/lib/tools/pdf.js
CHANGED
|
@@ -19,7 +19,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/tools/pdf.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { formatObject } from "../
|
|
22
|
+
import { formatObject } from "../utils/codegen.js";
|
|
23
23
|
import { defineTabTool } from "./tool.js";
|
|
24
24
|
var pdfSchema = z.object({
|
|
25
25
|
filename: z.string().optional().describe("File name to save the pdf to. Defaults to `page-{timestamp}.pdf` if not specified.")
|
package/lib/tools/screenshot.js
CHANGED
|
@@ -19,8 +19,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/tools/screenshot.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { formatObject } from "../javascript.js";
|
|
23
22
|
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { formatObject } from "../utils/codegen.js";
|
|
24
24
|
import { defineTabTool } from "./tool.js";
|
|
25
25
|
import { generateLocator } from "./utils.js";
|
|
26
26
|
var screenshotSchema = z.object({
|
package/lib/tools/snapshot.js
CHANGED
|
@@ -19,8 +19,8 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
19
19
|
|
|
20
20
|
// src/tools/snapshot.ts
|
|
21
21
|
import { z } from "zod";
|
|
22
|
-
import { formatObject } from "../javascript.js";
|
|
23
22
|
import { expectationSchema } from "../schemas/expectation.js";
|
|
23
|
+
import { formatObject } from "../utils/codegen.js";
|
|
24
24
|
import { defineTabTool, defineTool } from "./tool.js";
|
|
25
25
|
import { generateLocator } from "./utils.js";
|
|
26
26
|
var snapshot = defineTool({
|
package/lib/tools/utils.js
CHANGED
|
@@ -18,10 +18,9 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/tools/utils.ts
|
|
21
|
-
import debug from "debug";
|
|
22
21
|
import { asLocator } from "playwright-core/lib/utils";
|
|
23
22
|
import { TIMEOUTS } from "../config/constants.js";
|
|
24
|
-
|
|
23
|
+
import { toolsUtilsDebug } from "../utils/log.js";
|
|
25
24
|
async function waitForCompletion(tab, callback) {
|
|
26
25
|
const requests = new Set;
|
|
27
26
|
let frameNavigated = false;
|
|
@@ -48,19 +47,19 @@ async function waitForCompletion(tab, callback) {
|
|
|
48
47
|
await tab.waitForLoadState("networkidle", {
|
|
49
48
|
timeout: getNavigationConfig().networkIdleTimeout
|
|
50
49
|
}).catch((error) => {
|
|
51
|
-
|
|
50
|
+
toolsUtilsDebug("Network idle timeout reached:", error);
|
|
52
51
|
});
|
|
53
52
|
navigationCompleted = true;
|
|
54
53
|
if (!requests.size) {
|
|
55
54
|
waitCallback();
|
|
56
55
|
}
|
|
57
56
|
} catch (error) {
|
|
58
|
-
|
|
57
|
+
toolsUtilsDebug("Load state waiting failed, continuing:", error);
|
|
59
58
|
navigationCompleted = true;
|
|
60
59
|
waitCallback();
|
|
61
60
|
}
|
|
62
61
|
})().catch((error) => {
|
|
63
|
-
|
|
62
|
+
toolsUtilsDebug("Navigation handling failed:", error);
|
|
64
63
|
navigationCompleted = true;
|
|
65
64
|
waitCallback();
|
|
66
65
|
});
|
|
@@ -92,7 +91,7 @@ async function waitForCompletion(tab, callback) {
|
|
|
92
91
|
try {
|
|
93
92
|
await tab.page.evaluate(() => document.readyState);
|
|
94
93
|
} catch (error) {
|
|
95
|
-
|
|
94
|
+
toolsUtilsDebug("Page readyState check failed (context may be destroyed):", error);
|
|
96
95
|
}
|
|
97
96
|
} else {
|
|
98
97
|
await tab.waitForTimeout(getNavigationConfig().defaultWait);
|
|
@@ -107,7 +106,7 @@ async function generateLocator(locator) {
|
|
|
107
106
|
const { resolvedSelector } = await locator._resolveSelector();
|
|
108
107
|
return asLocator("javascript", resolvedSelector);
|
|
109
108
|
} catch (error) {
|
|
110
|
-
|
|
109
|
+
toolsUtilsDebug("Locator generation failed:", error);
|
|
111
110
|
throw new Error("Ref not found, likely because element was removed. Use browser_snapshot to see what elements are currently on the page.");
|
|
112
111
|
}
|
|
113
112
|
}
|
|
@@ -17,7 +17,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
-
// src/
|
|
20
|
+
// src/utils/codegen.ts
|
|
21
21
|
function escapeWithQuotes(text, char = "'") {
|
|
22
22
|
const stringified = JSON.stringify(text);
|
|
23
23
|
const escapedText = extractEscapedContent(stringified);
|
|
@@ -18,8 +18,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/utils/common-formatters.ts
|
|
21
|
-
import
|
|
22
|
-
var formattersDebug = debug("pw:mcp:formatters");
|
|
21
|
+
import { commonFormattersDebug } from "./log.js";
|
|
23
22
|
function formatPerformanceMetric(name, value, unit, threshold) {
|
|
24
23
|
const icon = threshold && value > threshold ? "⚠️" : "✅";
|
|
25
24
|
const thresholdText = threshold ? ` (threshold: ${threshold}${unit})` : "";
|
|
@@ -161,7 +160,7 @@ function createDiagnosticErrorInfo(error, operation = "Unknown operation", compo
|
|
|
161
160
|
component
|
|
162
161
|
};
|
|
163
162
|
}
|
|
164
|
-
function handleResourceDisposalError(error, resourceType, logger =
|
|
163
|
+
function handleResourceDisposalError(error, resourceType, logger = commonFormattersDebug) {
|
|
165
164
|
logger(`${resourceType} disposal failed: ${getErrorMessage(error)}`);
|
|
166
165
|
}
|
|
167
166
|
function handleFrameAccessError(error, frameInfo) {
|
|
@@ -18,9 +18,8 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
20
|
// src/utils/error-handler-middleware.ts
|
|
21
|
-
import debug from "debug";
|
|
22
21
|
import { getErrorMessage } from "./common-formatters.js";
|
|
23
|
-
|
|
22
|
+
import { errorHandlerDebug } from "./log.js";
|
|
24
23
|
async function withErrorHandling(operation, context) {
|
|
25
24
|
try {
|
|
26
25
|
const result = await operation();
|
|
@@ -17,7 +17,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
-
// src/utils.ts
|
|
20
|
+
// src/utils/guid.ts
|
|
21
21
|
import { createHash as cryptoCreateHash } from "node:crypto";
|
|
22
22
|
function createHash(data) {
|
|
23
23
|
return cryptoCreateHash("sha256").update(data).digest("hex").slice(0, 7);
|
package/lib/utils/index.js
CHANGED
|
@@ -43,6 +43,10 @@ import {
|
|
|
43
43
|
handleResourceDisposalError
|
|
44
44
|
} from "./common-formatters.js";
|
|
45
45
|
import { createDisposableManager } from "./disposable-manager.js";
|
|
46
|
+
import {
|
|
47
|
+
createStatusCategoryFilter,
|
|
48
|
+
filterNetworkRequests
|
|
49
|
+
} from "./network-filter.js";
|
|
46
50
|
import {
|
|
47
51
|
addMouseOperationComment,
|
|
48
52
|
addNavigationComment,
|
|
@@ -78,9 +82,11 @@ export {
|
|
|
78
82
|
formatDiagnosticKeyValue,
|
|
79
83
|
formatConfidence,
|
|
80
84
|
filterTruthy,
|
|
85
|
+
filterNetworkRequests,
|
|
81
86
|
executeToolOperation,
|
|
82
87
|
deduplicateAndLimit,
|
|
83
88
|
deduplicate,
|
|
89
|
+
createStatusCategoryFilter,
|
|
84
90
|
createDisposableManager,
|
|
85
91
|
applyCommonExpectations,
|
|
86
92
|
addToolErrorContext,
|
package/lib/utils/log.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/utils/log.ts
|
|
21
|
+
import debug from "debug";
|
|
22
|
+
var elementDiscoveryDebug = debug("pw:mcp:diagnostics:element-discovery");
|
|
23
|
+
var frameReferenceDebug = debug("pw:mcp:diagnostics:frame-reference");
|
|
24
|
+
var resourceDebug = debug("pw:mcp:diagnostics:resource");
|
|
25
|
+
var smartConfigDebug = debug("pw:mcp:diagnostics:smart-config");
|
|
26
|
+
var smartHandleDebug = debug("pw:mcp:diagnostics:smart-handle");
|
|
27
|
+
var commonFormattersDebug = debug("pw:mcp:utils:common-formatters");
|
|
28
|
+
var browserContextDebug = debug("pw:mcp:browser-context");
|
|
29
|
+
var browserContextFactoryDebug = debug("pw:mcp:browser-context-factory");
|
|
30
|
+
var browserServerBackendDebug = debug("pw:mcp:browser-server-backend");
|
|
31
|
+
var browserDebug = debug("pw:mcp:browser");
|
|
32
|
+
var testDebug = debug("pw:mcp:test");
|
|
33
|
+
var contextDebug = debug("pw:mcp:context");
|
|
34
|
+
var mcpServerDebug = debug("pw:mcp:server");
|
|
35
|
+
var mcpTransportDebug = debug("pw:mcp:transport");
|
|
36
|
+
var extensionContextDebug = debug("pw:mcp:extension:context");
|
|
37
|
+
var extensionContextFactoryDebug = debug("pw:mcp:extension:context-factory");
|
|
38
|
+
var cdpRelayDebug = debug("pw:mcp:extension:cdp-relay");
|
|
39
|
+
var batchExecutorDebug = debug("pw:mcp:batch:executor");
|
|
40
|
+
var loopDebug = debug("pw:mcp:loop");
|
|
41
|
+
var historyDebug = debug("pw:mcp:loop:history");
|
|
42
|
+
var toolDebug = debug("pw:mcp:loop:tool");
|
|
43
|
+
var toolsUtilsDebug = debug("pw:mcp:tools:utils");
|
|
44
|
+
var diagnoseConfigHandlerDebug = debug("pw:mcp:tools:diagnose:config-handler");
|
|
45
|
+
var tabDebug = debug("pw:mcp:tab");
|
|
46
|
+
var programDebug = debug("pw:mcp:program");
|
|
47
|
+
var responseDebug = debug("pw:mcp:response");
|
|
48
|
+
var errorHandlerDebug = debug("pw:mcp:error-handler");
|
|
49
|
+
var testserverDebug = debug("pw:mcp:testserver");
|
|
50
|
+
var snapshotDebug = debug("pw:mcp:tab:snapshot");
|
|
51
|
+
var errorEnrichmentDebug = debug("pw:mcp:diagnostics:error-enrichment");
|
|
52
|
+
var requestDebug = debug("pw:mcp:request");
|
|
53
|
+
var errorsDebug = debug("pw:mcp:errors");
|
|
54
|
+
function logUnhandledError(error) {
|
|
55
|
+
errorsDebug(error);
|
|
56
|
+
}
|
|
57
|
+
export {
|
|
58
|
+
toolsUtilsDebug,
|
|
59
|
+
toolDebug,
|
|
60
|
+
testserverDebug,
|
|
61
|
+
testDebug,
|
|
62
|
+
tabDebug,
|
|
63
|
+
snapshotDebug,
|
|
64
|
+
smartHandleDebug,
|
|
65
|
+
smartConfigDebug,
|
|
66
|
+
responseDebug,
|
|
67
|
+
resourceDebug,
|
|
68
|
+
requestDebug,
|
|
69
|
+
programDebug,
|
|
70
|
+
mcpTransportDebug,
|
|
71
|
+
mcpServerDebug,
|
|
72
|
+
loopDebug,
|
|
73
|
+
logUnhandledError,
|
|
74
|
+
historyDebug,
|
|
75
|
+
frameReferenceDebug,
|
|
76
|
+
extensionContextFactoryDebug,
|
|
77
|
+
extensionContextDebug,
|
|
78
|
+
errorHandlerDebug,
|
|
79
|
+
errorEnrichmentDebug,
|
|
80
|
+
elementDiscoveryDebug,
|
|
81
|
+
diagnoseConfigHandlerDebug,
|
|
82
|
+
contextDebug,
|
|
83
|
+
commonFormattersDebug,
|
|
84
|
+
cdpRelayDebug,
|
|
85
|
+
browserServerBackendDebug,
|
|
86
|
+
browserDebug,
|
|
87
|
+
browserContextFactoryDebug,
|
|
88
|
+
browserContextDebug,
|
|
89
|
+
batchExecutorDebug
|
|
90
|
+
};
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
8
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
9
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
10
|
+
for (let key of __getOwnPropNames(mod))
|
|
11
|
+
if (!__hasOwnProp.call(to, key))
|
|
12
|
+
__defProp(to, key, {
|
|
13
|
+
get: () => mod[key],
|
|
14
|
+
enumerable: true
|
|
15
|
+
});
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
// src/utils/network-filter.ts
|
|
21
|
+
function filterNetworkRequests(requests, options) {
|
|
22
|
+
if (!requests || requests.length === 0) {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
const opts = {
|
|
26
|
+
maxRequests: 20,
|
|
27
|
+
newestFirst: true,
|
|
28
|
+
...options
|
|
29
|
+
};
|
|
30
|
+
let filtered = [...requests];
|
|
31
|
+
filtered = applyUrlPatternFilters(filtered, opts);
|
|
32
|
+
if (opts.methods && opts.methods.length > 0) {
|
|
33
|
+
filtered = filtered.filter((request) => {
|
|
34
|
+
const method = request.method.toUpperCase();
|
|
35
|
+
return opts.methods?.some((m) => m.toUpperCase() === method) ?? false;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
filtered = applyStatusRangeFilters(filtered, opts);
|
|
39
|
+
if (filtered.length > 1) {
|
|
40
|
+
filtered = sortRequestsByTimestamp(filtered, opts.newestFirst ?? true);
|
|
41
|
+
}
|
|
42
|
+
if (opts.maxRequests && opts.maxRequests > 0) {
|
|
43
|
+
filtered = filtered.slice(0, opts.maxRequests);
|
|
44
|
+
}
|
|
45
|
+
return filtered;
|
|
46
|
+
}
|
|
47
|
+
function applyUrlPatternFilters(requests, options) {
|
|
48
|
+
let filtered = requests;
|
|
49
|
+
if (options.urlPatterns && options.urlPatterns.length > 0) {
|
|
50
|
+
filtered = filtered.filter((request) => {
|
|
51
|
+
return options.urlPatterns?.some((pattern) => matchesUrlPattern(request.url, pattern)) ?? false;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (options.excludeUrlPatterns && options.excludeUrlPatterns.length > 0) {
|
|
55
|
+
filtered = filtered.filter((request) => {
|
|
56
|
+
return !options.excludeUrlPatterns?.some((pattern) => matchesUrlPattern(request.url, pattern));
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return filtered;
|
|
60
|
+
}
|
|
61
|
+
function applyStatusRangeFilters(requests, options) {
|
|
62
|
+
if (!options.statusRanges || options.statusRanges.length === 0) {
|
|
63
|
+
return requests;
|
|
64
|
+
}
|
|
65
|
+
return requests.filter((request) => {
|
|
66
|
+
const status = request.status;
|
|
67
|
+
if (status === undefined) {
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
return options.statusRanges?.some((range) => {
|
|
71
|
+
return status >= range.min && status <= range.max;
|
|
72
|
+
}) ?? false;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function sortRequestsByTimestamp(requests, newestFirst) {
|
|
76
|
+
const sorted = [...requests].sort((a, b) => {
|
|
77
|
+
const timestampA = a.timestamp;
|
|
78
|
+
const timestampB = b.timestamp;
|
|
79
|
+
if (newestFirst) {
|
|
80
|
+
return timestampB - timestampA;
|
|
81
|
+
}
|
|
82
|
+
return timestampA - timestampB;
|
|
83
|
+
});
|
|
84
|
+
return sorted;
|
|
85
|
+
}
|
|
86
|
+
function matchesUrlPattern(url, pattern) {
|
|
87
|
+
if (!(pattern && url)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const regex = new RegExp(pattern, "i");
|
|
92
|
+
return regex.test(url);
|
|
93
|
+
} catch {
|
|
94
|
+
return url.toLowerCase().includes(pattern.toLowerCase());
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function createStatusCategoryFilter(category) {
|
|
98
|
+
switch (category) {
|
|
99
|
+
case "success":
|
|
100
|
+
return { statusRanges: [{ min: 200, max: 299 }] };
|
|
101
|
+
case "redirect":
|
|
102
|
+
return { statusRanges: [{ min: 300, max: 399 }] };
|
|
103
|
+
case "client-error":
|
|
104
|
+
return { statusRanges: [{ min: 400, max: 499 }] };
|
|
105
|
+
case "server-error":
|
|
106
|
+
return { statusRanges: [{ min: 500, max: 599 }] };
|
|
107
|
+
default:
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export {
|
|
112
|
+
filterNetworkRequests,
|
|
113
|
+
createStatusCategoryFilter
|
|
114
|
+
};
|
|
@@ -17,12 +17,12 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
17
17
|
};
|
|
18
18
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
19
19
|
|
|
20
|
-
// src/package.ts
|
|
20
|
+
// src/utils/package.ts
|
|
21
21
|
import fs from "node:fs";
|
|
22
22
|
import path from "node:path";
|
|
23
23
|
import url from "node:url";
|
|
24
24
|
var __filename2 = url.fileURLToPath(import.meta.url);
|
|
25
|
-
var packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename2), "..", "package.json"), "utf8"));
|
|
25
|
+
var packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename2), "..", "..", "package.json"), "utf8"));
|
|
26
26
|
export {
|
|
27
27
|
packageJSON
|
|
28
28
|
};
|
|
@@ -22,7 +22,7 @@ import { randomBytes } from "node:crypto";
|
|
|
22
22
|
import { appendFileSync, existsSync, mkdirSync } from "node:fs";
|
|
23
23
|
import { dirname, join } from "node:path";
|
|
24
24
|
import { fileURLToPath } from "node:url";
|
|
25
|
-
import { requestDebug } from "
|
|
25
|
+
import { requestDebug } from "./log.js";
|
|
26
26
|
var __filename2 = fileURLToPath(import.meta.url);
|
|
27
27
|
var __dirname2 = dirname(__filename2);
|
|
28
28
|
var PROJECT_ROOT = join(__dirname2, "..", "..");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tontoko/fast-playwright-mcp",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -10,12 +10,12 @@
|
|
|
10
10
|
"type": "git",
|
|
11
11
|
"url": "https://github.com/tontoko/fast-playwright-mcp"
|
|
12
12
|
},
|
|
13
|
-
"homepage": "https://playwright
|
|
13
|
+
"homepage": "https://github.com/tontoko/fast-playwright-mcp",
|
|
14
14
|
"engines": {
|
|
15
15
|
"node": ">=18"
|
|
16
16
|
},
|
|
17
17
|
"author": {
|
|
18
|
-
"name": "
|
|
18
|
+
"name": "Tomohiko Hiraki"
|
|
19
19
|
},
|
|
20
20
|
"license": "Apache-2.0",
|
|
21
21
|
"scripts": {
|