@skyramp/mcp 0.2.1-rc.1 → 0.2.1
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/build/playwright/registerPlaywrightTools.js +1 -0
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +98 -87
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +92 -60
- package/build/prompts/test-maintenance/driftAnalysisSections.js +139 -197
- package/build/prompts/testbot/testbot-prompts.js +4 -7
- package/build/prompts/testbot/testbot-prompts.test.js +17 -22
- package/build/services/TestDiscoveryService.js +39 -9
- package/build/tools/test-management/actionsTool.js +166 -148
- package/build/tools/test-management/analyzeChangesTool.js +2 -10
- package/build/tools/test-management/analyzeTestHealthTool.js +10 -22
- package/build/utils/docker.test.js +1 -1
- package/build/utils/versions.js +1 -1
- package/node_modules/playwright/lib/mcp/skyramp/assertApiRequestTool.js +46 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +298 -51
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +5 -0
- package/package.json +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +0 -261
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.3.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.4.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.5.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz +0 -0
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
var domAnalyzer_exports = {};
|
|
30
|
-
__export(domAnalyzer_exports, {
|
|
31
|
-
default: () => domAnalyzer_default
|
|
32
|
-
});
|
|
33
|
-
module.exports = __toCommonJS(domAnalyzer_exports);
|
|
34
|
-
var crypto = __toESM(require("crypto"));
|
|
35
|
-
var fs = __toESM(require("fs"));
|
|
36
|
-
var import_mcpBundle = require("playwright-core/lib/mcpBundle");
|
|
37
|
-
var import_tool = require("./tool");
|
|
38
|
-
var import_crawler = require("../../../dom-analyzer/crawler");
|
|
39
|
-
var import_blueprint = require("../../../dom-analyzer/blueprint");
|
|
40
|
-
var import_serialization = require("../../../dom-analyzer/serialization");
|
|
41
|
-
function hashStorageState(path) {
|
|
42
|
-
if (!path)
|
|
43
|
-
return null;
|
|
44
|
-
try {
|
|
45
|
-
const contents = fs.readFileSync(path, "utf-8");
|
|
46
|
-
return crypto.createHash("sha256").update(contents).digest("hex").slice(0, 16);
|
|
47
|
-
} catch {
|
|
48
|
-
const pathHash = crypto.createHash("sha256").update(path).digest("hex").slice(0, 16);
|
|
49
|
-
return `unreadable:${pathHash}`;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function cacheKey(entryUrl, storageStateHash, probeButtons) {
|
|
53
|
-
return `${entryUrl}::${storageStateHash ?? "no-auth"}::${probeButtons}`;
|
|
54
|
-
}
|
|
55
|
-
function humanDuration(ms) {
|
|
56
|
-
if (ms < 6e4)
|
|
57
|
-
return `${Math.round(ms / 1e3)}s`;
|
|
58
|
-
return `${Math.round(ms / 6e4)}m`;
|
|
59
|
-
}
|
|
60
|
-
const sitemapBuild = (0, import_tool.defineTool)({
|
|
61
|
-
capability: "core",
|
|
62
|
-
schema: {
|
|
63
|
-
name: "browser_sitemap_build",
|
|
64
|
-
title: "Build Sitemap (graph of PageBlueprints)",
|
|
65
|
-
description: [
|
|
66
|
-
"Crawl an application starting from a URL and build a Sitemap \u2014 a graph of PageBlueprints (one per URL)",
|
|
67
|
-
"connected by navigation edges. The Sitemap is cached in the session.",
|
|
68
|
-
"",
|
|
69
|
-
"Call once at the start of a session. Subsequent calls within the TTL (~30 minutes) reuse the cached",
|
|
70
|
-
"Sitemap unless `refresh: true` is passed. Use `browser_sitemap_query` to read already-crawled pages",
|
|
71
|
-
"rather than re-calling this tool.",
|
|
72
|
-
"",
|
|
73
|
-
"Depth defaults to 5, maxPages to 50 (the real bound in practice).",
|
|
74
|
-
"",
|
|
75
|
-
'\u26A0\uFE0F SAFETY: By default, `probeButtons: "immutable-only"` skips destructive-looking buttons',
|
|
76
|
-
"(Delete / Submit / Place Order / etc.) and buttons inside forms to prevent side effects during",
|
|
77
|
-
'crawling. Use `probeButtons: "all"` only against dev / staging environments \u2014 it will click every',
|
|
78
|
-
"unique button, which can submit forms, create records, send notifications, or mutate server state."
|
|
79
|
-
].join("\n"),
|
|
80
|
-
inputSchema: import_mcpBundle.z.object({
|
|
81
|
-
url: import_mcpBundle.z.string().describe("Entry URL to start crawling from"),
|
|
82
|
-
depth: import_mcpBundle.z.number().optional().default(5).describe("Max crawl depth (default: 5)"),
|
|
83
|
-
maxPages: import_mcpBundle.z.number().optional().default(50).describe("Max pages to visit (default: 50)"),
|
|
84
|
-
sameOriginOnly: import_mcpBundle.z.boolean().optional().default(true).describe("Only follow same-origin links (default: true)"),
|
|
85
|
-
probeButtons: import_mcpBundle.z.enum(["immutable-only", "all", "none"]).optional().default("immutable-only").describe('Button-probing safety mode. Default: immutable-only (safe; skips destructive-verb buttons and buttons inside forms). Use "all" only against dev/staging \u2014 probes every button. "none" disables button probing entirely.'),
|
|
86
|
-
playwrightStoragePath: import_mcpBundle.z.string().optional().describe("Path to a Playwright storageState.json file \u2014 cookies, localStorage, sessionStorage per origin. Use this to crawl apps behind a login."),
|
|
87
|
-
refresh: import_mcpBundle.z.boolean().optional().default(false).describe("Force a full re-crawl, bypassing the cache")
|
|
88
|
-
}),
|
|
89
|
-
type: "readOnly"
|
|
90
|
-
},
|
|
91
|
-
handle: async (context, params, response) => {
|
|
92
|
-
const { url, depth, maxPages, sameOriginOnly, probeButtons, playwrightStoragePath, refresh } = params;
|
|
93
|
-
const normalizedUrl = (0, import_serialization.normalizeUrl)(url);
|
|
94
|
-
const storageStateHash = hashStorageState(playwrightStoragePath);
|
|
95
|
-
const probeMode = probeButtons;
|
|
96
|
-
const key = cacheKey(normalizedUrl, storageStateHash, probeMode);
|
|
97
|
-
const cached = context.sitemapCache.get(key);
|
|
98
|
-
if (cached && !refresh) {
|
|
99
|
-
const age = Date.now() - new Date(cached.sitemap.cachedAt).getTime();
|
|
100
|
-
if (age < import_serialization.CRAWL_TTL_MS) {
|
|
101
|
-
const pageCount2 = Object.keys(cached.sitemap.pages).length;
|
|
102
|
-
response.addTextResult(
|
|
103
|
-
`Reusing cached Sitemap from ${humanDuration(age)} ago.
|
|
104
|
-
Entry: ${cached.sitemap.entryUrl}
|
|
105
|
-
Pages: ${pageCount2}
|
|
106
|
-
Edges: ${cached.sitemap.edges.length}
|
|
107
|
-
Storage state: ${storageStateHash ? `auth hash ${storageStateHash}` : "none"}
|
|
108
|
-
Probe mode: ${probeMode}
|
|
109
|
-
|
|
110
|
-
Use browser_sitemap_query to read any page. Pass refresh=true to recrawl.`
|
|
111
|
-
);
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
let sitemap;
|
|
116
|
-
if (depth > 0) {
|
|
117
|
-
sitemap = await (0, import_crawler.crawl)(url, {
|
|
118
|
-
depth,
|
|
119
|
-
maxPages,
|
|
120
|
-
sameOriginOnly,
|
|
121
|
-
probeButtons: probeMode,
|
|
122
|
-
playwrightStoragePath
|
|
123
|
-
});
|
|
124
|
-
} else {
|
|
125
|
-
sitemap = await (0, import_crawler.crawlSinglePage)(url, { playwrightStoragePath });
|
|
126
|
-
}
|
|
127
|
-
context.sitemapCache.set(key, { sitemap, storageStateHash, probeButtonsMode: probeMode });
|
|
128
|
-
const pageCount = Object.keys(sitemap.pages).length;
|
|
129
|
-
const pageList = Object.keys(sitemap.pages).map((u) => {
|
|
130
|
-
const bp = sitemap.pages[u];
|
|
131
|
-
const elCount = bp.sections.reduce((sum, s) => sum + s.elements.length + s.repeatingElements.length, 0);
|
|
132
|
-
return ` - ${u} (${elCount} elements across ${bp.sections.length} sections)`;
|
|
133
|
-
}).join("\n");
|
|
134
|
-
response.addTextResult(
|
|
135
|
-
`Sitemap built for ${sitemap.entryUrl}
|
|
136
|
-
Pages: ${pageCount}
|
|
137
|
-
Edges: ${sitemap.edges.length}
|
|
138
|
-
Storage state: ${storageStateHash ? `auth hash ${storageStateHash}` : "none"}
|
|
139
|
-
Probe mode: ${probeMode}
|
|
140
|
-
|
|
141
|
-
Pages:
|
|
142
|
-
${pageList}
|
|
143
|
-
|
|
144
|
-
Use browser_sitemap_query to read page blueprints, edges, or derived views (mapJson, outline).`
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
const sitemapQuery = (0, import_tool.defineTool)({
|
|
149
|
-
capability: "core",
|
|
150
|
-
schema: {
|
|
151
|
-
name: "browser_sitemap_query",
|
|
152
|
-
title: "Query the cached Sitemap",
|
|
153
|
-
description: [
|
|
154
|
-
"Query the Sitemap cached by browser_sitemap_build. Use this instead of re-calling sitemap_build.",
|
|
155
|
-
"",
|
|
156
|
-
"Modes:",
|
|
157
|
-
" page \u2014 returns the full canonical PageBlueprint for the given URL",
|
|
158
|
-
" (sections with enrichment, logical names, XPaths)",
|
|
159
|
-
" edges \u2014 returns navigation edges originating from the given URL",
|
|
160
|
-
" mapJson \u2014 returns the derived flat logicalName \u2192 xpath map for the URL",
|
|
161
|
-
" outline \u2014 returns the derived textual section-to-element hierarchy for the URL",
|
|
162
|
-
" overview \u2014 (when url is omitted) returns page list + edge list summary"
|
|
163
|
-
].join("\n"),
|
|
164
|
-
inputSchema: import_mcpBundle.z.object({
|
|
165
|
-
type: import_mcpBundle.z.enum(["page", "edges", "mapJson", "outline"]).optional().describe("Query mode. Omit with url to get overview."),
|
|
166
|
-
url: import_mcpBundle.z.string().optional().describe("Page URL to query. Omit to get overview across all pages.")
|
|
167
|
-
}),
|
|
168
|
-
type: "readOnly"
|
|
169
|
-
},
|
|
170
|
-
handle: async (context, params, response) => {
|
|
171
|
-
if (context.sitemapCache.size === 0) {
|
|
172
|
-
response.addError("No Sitemap available. Run browser_sitemap_build first.");
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
const latest = [...context.sitemapCache.values()].sort(
|
|
176
|
-
(a, b) => new Date(b.sitemap.cachedAt).getTime() - new Date(a.sitemap.cachedAt).getTime()
|
|
177
|
-
)[0];
|
|
178
|
-
const sitemap = latest.sitemap;
|
|
179
|
-
const { type, url } = params;
|
|
180
|
-
if (!url || !type) {
|
|
181
|
-
const overview = {
|
|
182
|
-
schemaVersion: sitemap.schemaVersion,
|
|
183
|
-
entryUrl: sitemap.entryUrl,
|
|
184
|
-
cachedAt: sitemap.cachedAt,
|
|
185
|
-
pages: Object.keys(sitemap.pages).map((u) => ({
|
|
186
|
-
url: u,
|
|
187
|
-
sections: sitemap.pages[u].sections.length,
|
|
188
|
-
elements: sitemap.pages[u].sections.reduce(
|
|
189
|
-
(s, sec) => s + sec.elements.length + sec.repeatingElements.length,
|
|
190
|
-
0
|
|
191
|
-
),
|
|
192
|
-
pageHash: sitemap.pages[u].pageHash
|
|
193
|
-
})),
|
|
194
|
-
edges: sitemap.edges
|
|
195
|
-
};
|
|
196
|
-
response.addTextResult(JSON.stringify(overview, null, 2));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
const normalizedQueryUrl = (0, import_serialization.normalizeUrl)(url);
|
|
200
|
-
const blueprint2 = sitemap.pages[normalizedQueryUrl];
|
|
201
|
-
if (!blueprint2) {
|
|
202
|
-
response.addError(
|
|
203
|
-
`Page not found in Sitemap: ${normalizedQueryUrl}. Available pages: ${Object.keys(sitemap.pages).join(", ")}`
|
|
204
|
-
);
|
|
205
|
-
return;
|
|
206
|
-
}
|
|
207
|
-
if (type === "page") {
|
|
208
|
-
response.addTextResult(JSON.stringify(blueprint2, null, 2));
|
|
209
|
-
return;
|
|
210
|
-
}
|
|
211
|
-
if (type === "edges") {
|
|
212
|
-
const edges = sitemap.edges.filter((e) => e.from === normalizedQueryUrl);
|
|
213
|
-
response.addTextResult(JSON.stringify({ url: normalizedQueryUrl, edges }, null, 2));
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
if (type === "mapJson") {
|
|
217
|
-
response.addTextResult(JSON.stringify((0, import_blueprint.buildMap)(blueprint2), null, 2));
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (type === "outline") {
|
|
221
|
-
response.addTextResult((0, import_blueprint.buildOutline)(blueprint2));
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
});
|
|
226
|
-
const blueprint = (0, import_tool.defineTabTool)({
|
|
227
|
-
capability: "core",
|
|
228
|
-
schema: {
|
|
229
|
-
name: "browser_blueprint",
|
|
230
|
-
title: "Build PageBlueprint for the current page",
|
|
231
|
-
description: [
|
|
232
|
-
"Build a PageBlueprint for the currently loaded page. Returns the canonical `sections` tree",
|
|
233
|
-
"containing singular elements and repeating-element shapes, each carrying enrichment fields",
|
|
234
|
-
"(mutability, widgetType, framePath, shadowRoot, stableId, testId).",
|
|
235
|
-
"",
|
|
236
|
-
"Call this only when the DOM has changed since the last known-good blueprint \u2014 e.g. after a",
|
|
237
|
-
"modal opens, a form submits, a filter changes a list, or any mutable action. For pure navigation",
|
|
238
|
-
"to an already-crawled URL, reuse the Sitemap cache via browser_sitemap_query.",
|
|
239
|
-
"",
|
|
240
|
-
"Logical names are stable \u2014 use them in generated test code. Refs (from browser_snapshot) are",
|
|
241
|
-
"ephemeral \u2014 use them only for dispatching the next interaction, never in generated code."
|
|
242
|
-
].join("\n"),
|
|
243
|
-
inputSchema: import_mcpBundle.z.object({}),
|
|
244
|
-
type: "readOnly"
|
|
245
|
-
},
|
|
246
|
-
handle: async (tab, _params, response) => {
|
|
247
|
-
try {
|
|
248
|
-
const bp = await (0, import_blueprint.buildPageBlueprint)(tab.page);
|
|
249
|
-
response.addTextResult(JSON.stringify(bp, null, 2));
|
|
250
|
-
} catch (err) {
|
|
251
|
-
if (err instanceof import_blueprint.BlueprintInvariantError) {
|
|
252
|
-
response.addError(
|
|
253
|
-
`Blueprint invariant violated: ${err.message}. This is a builder bug; please file an issue with the URL of the page that triggered it.`
|
|
254
|
-
);
|
|
255
|
-
return;
|
|
256
|
-
}
|
|
257
|
-
throw err;
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
});
|
|
261
|
-
var domAnalyzer_default = [sitemapBuild, sitemapQuery, blueprint];
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|