@skyramp/mcp 0.3.2 → 0.3.4
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/index.js +47 -2
- package/build/playwright/PlaywrightTraceService.d.ts +8 -0
- package/build/playwright/PlaywrightTraceService.js +1 -0
- package/build/playwright/registerPlaywrightTools.js +42 -1
- package/build/prompts/enhance-assertions/sharedAssertionRules.js +19 -0
- package/build/prompts/pom-aware-code-reuse.js +17 -8
- package/build/prompts/test-maintenance/actionsInstructions.js +2 -2
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +1 -4
- package/build/prompts/test-recommendation/recommendationSections.d.ts +1 -1
- package/build/prompts/test-recommendation/recommendationSections.js +5 -5
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +13 -7
- package/build/prompts/testbot/testbot-prompts.d.ts +2 -12
- package/build/prompts/testbot/testbot-prompts.js +28 -16
- package/build/recommendation/discriminators.d.ts +7 -1
- package/build/recommendation/discriminators.js +16 -3
- package/build/resources/testbotResource.js +20 -4
- package/build/services/ScenarioGenerationService.js +5 -2
- package/build/services/TestExecutionService.d.ts +13 -8
- package/build/services/TestExecutionService.js +73 -26
- package/build/services/TestGenerationService.js +24 -9
- package/build/services/containerEnv.d.ts +12 -1
- package/build/services/containerEnv.js +118 -1
- package/build/tools/executeSkyrampTestTool.d.ts +9 -0
- package/build/tools/executeSkyrampTestTool.js +20 -6
- package/build/tools/execution-video-state.d.ts +21 -0
- package/build/tools/execution-video-state.js +51 -0
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +31 -11
- package/build/tools/generate-tests/planGuard.d.ts +5 -5
- package/build/tools/generate-tests/planGuard.js +5 -17
- package/build/tools/queryProxyMocksTool.js +0 -1
- package/build/tools/submitReportTool.d.ts +83 -10
- package/build/tools/submitReportTool.js +179 -29
- package/build/tools/test-management/actionsTool.js +52 -41
- package/build/tools/test-management/analyzeChangesTool.d.ts +11 -0
- package/build/tools/test-management/analyzeChangesTool.js +37 -33
- package/build/tools/test-management/analyzeTestHealthTool.js +3 -3
- package/build/tools/test-management/registerTestPlanTool.js +113 -31
- package/build/types/TestAnalysis.d.ts +7 -3
- package/build/types/TestExecution.d.ts +14 -0
- package/build/types/TestTypes.js +3 -2
- package/build/types/TestbotPromptOptions.d.ts +34 -0
- package/build/types/TestbotPromptOptions.js +1 -0
- package/build/types/TestbotReport.d.ts +10 -0
- package/build/types/TestbotReport.js +10 -1
- package/build/types/index.d.ts +2 -0
- package/build/types/index.js +1 -0
- package/build/utils/AnalysisStateManager.d.ts +36 -2
- package/build/utils/AnalysisStateManager.js +34 -13
- package/build/utils/frontendSelectors.js +0 -1
- package/build/utils/gitStaging.d.ts +5 -0
- package/build/utils/gitStaging.js +1 -1
- package/build/utils/pom-catalog.d.ts +23 -0
- package/build/utils/pom-catalog.js +30 -0
- package/build/utils/pom-scope/pom-files.d.ts +14 -0
- package/build/utils/pom-scope/pom-files.js +32 -6
- package/build/utils/pom-scope/testIdDiscovery.d.ts +40 -0
- package/build/utils/pom-scope/testIdDiscovery.js +104 -0
- package/build/utils/reportVerification.d.ts +7 -2
- package/build/utils/reportVerification.js +9 -3
- package/build/utils/scenarioDrafting.js +7 -1
- package/build/utils/skyrampMdContent.d.ts +1 -1
- package/build/utils/skyrampMdContent.js +1 -1
- package/build/utils/urlPath.d.ts +37 -0
- package/build/utils/urlPath.js +55 -0
- package/build/utils/utils.d.ts +45 -0
- package/build/utils/utils.js +50 -0
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.d.ts +15 -15
- package/build/utils/workspaceAuth.js +32 -17
- package/build/workspace/queryParamResolution.d.ts +93 -0
- package/build/workspace/queryParamResolution.js +201 -0
- package/build/workspace/workspace.d.ts +104 -0
- package/build/workspace/workspace.js +24 -0
- package/node_modules/playwright/ThirdPartyNotices.txt +319 -266
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +154 -27
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +6 -2
- package/node_modules/playwright/lib/mcp/skyramp/loadTraceTool.js +3 -2
- package/node_modules/playwright/lib/mcp/skyramp/resultCode.js +4 -3
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +15 -3
- package/node_modules/playwright/lib/mcp/skyramp/specImport.js +781 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +14 -3
- package/node_modules/playwright/lib/mcp/test/resultCode.test.js +2 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +1 -1
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +30 -0
- package/node_modules/playwright/lib/transform/babelBundleImpl.js +200 -199
- package/node_modules/playwright/node_modules/playwright-core/ThirdPartyNotices.txt +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/.package-lock.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/aws-lambda/handler.js +16 -25
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/bun/websocket.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/adapter/lambda-edge/handler.js +20 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/aws-lambda/handler.js +16 -25
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/bun/websocket.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/adapter/lambda-edge/handler.js +20 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/client.js +10 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/client/utils.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/common.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/helper/css/index.js +9 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/base.js +8 -14
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/components.js +41 -21
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/context.js +131 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/jsx/streaming.js +9 -7
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/bearer-auth/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cache/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/compress/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/cors/index.js +2 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/etag/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/language/language.js +10 -32
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/method-override/index.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/serve-static/index.js +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/middleware/timing/timing.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/router/trie-router/node.js +9 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/body.js +12 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/buffer.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/utils/ipaddr.js +6 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/cjs/validator/validator.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/client.js +10 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/client/utils.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/common.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/helper/css/index.js +9 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/base.js +15 -15
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/components.js +42 -22
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/context.js +129 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/jsx/streaming.js +10 -8
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/bearer-auth/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cache/index.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/compress/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/cors/index.js +2 -5
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/etag/index.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/language/language.js +10 -32
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/method-override/index.js +5 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/serve-static/index.js +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/middleware/timing/timing.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/router/trie-router/node.js +9 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/aws-lambda/handler.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/adapter/lambda-edge/handler.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/helper/websocket/index.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/base.d.ts +1 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/jsx/context.d.ts +39 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/context-storage/index.d.ts +2 -2
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/middleware/language/language.d.ts +18 -0
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/body.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/types/utils/types.d.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/body.js +12 -4
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/buffer.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/utils/ipaddr.js +6 -1
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/validator/validator.js +3 -3
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/package.json +29 -22
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/package-lock.json +3 -3
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +5 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/specReader.js +781 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderApp.js +25 -6
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/replayEngine.js +3 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/utils.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/htmlReport/index.html +253 -27
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/{codeMirrorModule-D0BjbCb7.js → codeMirrorModule-DtudTj_v.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-BpDwp16L.js +422 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/index.html +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bzd72-bG.js → codeMirrorModule-FNMuBzX1.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-Co9upU5h.js +1035 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.DXNIQ_dx.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.CIKB3XSv.js +5 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +4 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/specReader.ts +1028 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderApp.ts +31 -8
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/replayEngine.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils.ts +1 -0
- package/node_modules/playwright/package.json +1 -1
- package/package.json +10 -6
- package/node_modules/playwright/node_modules/playwright-core/bundles/mcp/node_modules/hono/dist/tsconfig.build.tsbuildinfo +0 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/recorder/assets/index-lvTRGFx-.js +0 -193
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DzxTioTK.js +0 -809
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.BGc30U3S.js +0 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.IaDrb29A.js +0 -5
|
@@ -0,0 +1,781 @@
|
|
|
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 specImport_exports = {};
|
|
30
|
+
__export(specImport_exports, {
|
|
31
|
+
specLanguageForPath: () => specLanguageForPath,
|
|
32
|
+
specToActions: () => specToActions
|
|
33
|
+
});
|
|
34
|
+
module.exports = __toCommonJS(specImport_exports);
|
|
35
|
+
var import_path = __toESM(require("path"));
|
|
36
|
+
var import_utils = require("playwright-core/lib/utils");
|
|
37
|
+
const TEST_ID_ATTRIBUTE = "data-testid";
|
|
38
|
+
function specLanguageForPath(specPath) {
|
|
39
|
+
if (specPath.endsWith(".py"))
|
|
40
|
+
return "python";
|
|
41
|
+
if (specPath.endsWith(".ts") || specPath.endsWith(".js") || specPath.endsWith(".mjs") || specPath.endsWith(".cjs"))
|
|
42
|
+
return "javascript";
|
|
43
|
+
return void 0;
|
|
44
|
+
}
|
|
45
|
+
function specToActions(content, language, specDir) {
|
|
46
|
+
const dir = specDir ?? ".";
|
|
47
|
+
return language === "python" ? new PySpecParser(content, dir).parse() : new JsSpecParser(content, dir).parse();
|
|
48
|
+
}
|
|
49
|
+
function scanStatements(content, commentPrefix) {
|
|
50
|
+
const statements = [];
|
|
51
|
+
let buffer = "";
|
|
52
|
+
let startLine = 0;
|
|
53
|
+
let indent = 0;
|
|
54
|
+
let depth = 0;
|
|
55
|
+
const lines = content.split("\n");
|
|
56
|
+
for (let i = 0; i < lines.length; i++) {
|
|
57
|
+
const raw = lines[i].trim();
|
|
58
|
+
if (!buffer) {
|
|
59
|
+
if (!raw || raw.startsWith(commentPrefix))
|
|
60
|
+
continue;
|
|
61
|
+
startLine = i + 1;
|
|
62
|
+
indent = lines[i].length - lines[i].trimStart().length;
|
|
63
|
+
}
|
|
64
|
+
buffer = buffer ? `${buffer} ${raw}` : raw;
|
|
65
|
+
depth = bracketDepth(buffer);
|
|
66
|
+
if (depth > 0 && !/^test\(/.test(buffer))
|
|
67
|
+
continue;
|
|
68
|
+
statements.push({ text: buffer, line: startLine, indent });
|
|
69
|
+
buffer = "";
|
|
70
|
+
}
|
|
71
|
+
if (buffer)
|
|
72
|
+
statements.push({ text: buffer, line: startLine, indent });
|
|
73
|
+
return statements;
|
|
74
|
+
}
|
|
75
|
+
function bracketDepth(text) {
|
|
76
|
+
let depth = 0;
|
|
77
|
+
let quote;
|
|
78
|
+
for (let i = 0; i < text.length; i++) {
|
|
79
|
+
const c = text[i];
|
|
80
|
+
if (quote) {
|
|
81
|
+
if (c === "\\")
|
|
82
|
+
i++;
|
|
83
|
+
else if (c === quote)
|
|
84
|
+
quote = void 0;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (c === "'" || c === '"' || c === "`")
|
|
88
|
+
quote = c;
|
|
89
|
+
else if (c === "(" || c === "{" || c === "[")
|
|
90
|
+
depth++;
|
|
91
|
+
else if (c === ")" || c === "}" || c === "]")
|
|
92
|
+
depth--;
|
|
93
|
+
}
|
|
94
|
+
return depth;
|
|
95
|
+
}
|
|
96
|
+
const MODIFIER_MASKS = {
|
|
97
|
+
"Alt": 1,
|
|
98
|
+
"Control": 2,
|
|
99
|
+
"Meta": 4,
|
|
100
|
+
"Shift": 8,
|
|
101
|
+
"ControlOrMeta": 6
|
|
102
|
+
};
|
|
103
|
+
class SpecParserBase {
|
|
104
|
+
constructor(statements, specDir) {
|
|
105
|
+
this._actions = [];
|
|
106
|
+
this._diagnostics = [];
|
|
107
|
+
// const playwrightRequestN / expectedPayloadN = {...} — data bindings whose
|
|
108
|
+
// values are inlined where getValue() references them.
|
|
109
|
+
this._bindings = /* @__PURE__ */ new Map();
|
|
110
|
+
// Variables that hold pages (test fixture `page` + pageN/popupPage
|
|
111
|
+
// declarations). Value is the pageAlias used in the emitted actions.
|
|
112
|
+
this._pageAliases = /* @__PURE__ */ new Map([["page", "page"]]);
|
|
113
|
+
// Variables consumed by the waitForResponse / fileChooser patterns.
|
|
114
|
+
this._promiseVars = /* @__PURE__ */ new Set();
|
|
115
|
+
// A pending expect-filechooser: the next click is the chooser trigger and is
|
|
116
|
+
// folded into the fileChooser action (replay performs the trigger click
|
|
117
|
+
// itself — see loadTraceTool performClientAction).
|
|
118
|
+
this._pendingFileChooser = false;
|
|
119
|
+
this._statements = statements;
|
|
120
|
+
this._specDir = specDir;
|
|
121
|
+
}
|
|
122
|
+
parse() {
|
|
123
|
+
for (const stmt of this._statements)
|
|
124
|
+
this._parseStatement(stmt);
|
|
125
|
+
if (this._pendingFileChooser)
|
|
126
|
+
this._diagnostics.push({ line: 0, text: "expect filechooser", reason: "file chooser was opened but no setFiles/set_files followed" });
|
|
127
|
+
return { actions: this._actions, diagnostics: this._diagnostics };
|
|
128
|
+
}
|
|
129
|
+
_diag(stmt, reason) {
|
|
130
|
+
this._diagnostics.push({ line: stmt.line, text: stmt.text.length > 120 ? stmt.text.slice(0, 117) + "..." : stmt.text, reason });
|
|
131
|
+
}
|
|
132
|
+
_push(pageAlias, framePath, action) {
|
|
133
|
+
const index = this._actions.length;
|
|
134
|
+
this._actions.push({
|
|
135
|
+
frame: { pageGuid: "", pageAlias, framePath },
|
|
136
|
+
action: { signals: [], timestamp: String(index + 1), ...action },
|
|
137
|
+
startTime: index + 1
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Split a locator chain into iframe frame hops + leaf, converting each part
|
|
142
|
+
* to a selector via the upstream locator parser. Generated chains pierce
|
|
143
|
+
* frames only via contentFrame()/content_frame:
|
|
144
|
+
* locator("iframe[title=\"x\"]").contentFrame().getByRole(...) →
|
|
145
|
+
* framePath ['iframe[title="x"]'], leaf getByRole(...)
|
|
146
|
+
*/
|
|
147
|
+
_resolveLocatorChain(chain) {
|
|
148
|
+
const segments = chain.split(this._frameHopSplit);
|
|
149
|
+
const framePath = [];
|
|
150
|
+
for (let i = 0; i < segments.length - 1; i++) {
|
|
151
|
+
const hop = (0, import_utils.locatorOrSelectorAsSelector)(this._locatorLanguage, segments[i], TEST_ID_ATTRIBUTE);
|
|
152
|
+
if (!hop)
|
|
153
|
+
return void 0;
|
|
154
|
+
framePath.push(hop);
|
|
155
|
+
}
|
|
156
|
+
const leafSource = segments[segments.length - 1];
|
|
157
|
+
if (!leafSource)
|
|
158
|
+
return void 0;
|
|
159
|
+
const selector = (0, import_utils.locatorOrSelectorAsSelector)(this._locatorLanguage, leafSource, TEST_ID_ATTRIBUTE);
|
|
160
|
+
if (!selector)
|
|
161
|
+
return void 0;
|
|
162
|
+
return { selector, framePath };
|
|
163
|
+
}
|
|
164
|
+
/** Emit the action for a parsed `<locator chain>.<method>(<args>)` statement. */
|
|
165
|
+
_emitLocatorAction(stmt, pageAlias, chain, method, args) {
|
|
166
|
+
const resolved = this._resolveLocatorChain(chain);
|
|
167
|
+
if (!resolved) {
|
|
168
|
+
this._diag(stmt, "could not parse the locator chain \u2014 not replayed");
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const { selector, framePath } = resolved;
|
|
172
|
+
if (method === "click" && this._pendingFileChooser) {
|
|
173
|
+
this._pendingFileChooser = false;
|
|
174
|
+
this._heldChooserTrigger = { selector, framePath, pageAlias };
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
switch (method) {
|
|
178
|
+
case "click":
|
|
179
|
+
case "dblclick": {
|
|
180
|
+
const opts = this._parseCallOptions(args);
|
|
181
|
+
const action = { name: "click", selector, button: opts.button ?? "left", modifiers: opts.modifiers ?? 0, clickCount: method === "dblclick" ? 2 : opts.clickCount ?? 1 };
|
|
182
|
+
if (opts.position)
|
|
183
|
+
action.position = opts.position;
|
|
184
|
+
this._push(pageAlias, framePath, action);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
case "hover": {
|
|
188
|
+
const opts = this._parseCallOptions(args);
|
|
189
|
+
const action = { name: "hover", selector };
|
|
190
|
+
if (opts.position)
|
|
191
|
+
action.position = opts.position;
|
|
192
|
+
this._push(pageAlias, framePath, action);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
case "fill":
|
|
196
|
+
case "pressSequentially": {
|
|
197
|
+
const text = this._resolveStringArg(args);
|
|
198
|
+
if (text === void 0) {
|
|
199
|
+
this._diag(stmt, `could not resolve the ${method}() argument \u2014 not replayed`);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
this._push(pageAlias, framePath, { name: method, selector, text });
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
case "press": {
|
|
206
|
+
const shortcut = this._resolveStringArg(args);
|
|
207
|
+
if (shortcut === void 0) {
|
|
208
|
+
this._diag(stmt, "could not resolve the press() argument \u2014 not replayed");
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const parts = shortcut.split("+");
|
|
212
|
+
const key = parts.pop();
|
|
213
|
+
let modifiers = 0;
|
|
214
|
+
for (const p of parts)
|
|
215
|
+
modifiers |= MODIFIER_MASKS[p] ?? 0;
|
|
216
|
+
this._push(pageAlias, framePath, { name: "press", selector, key, modifiers });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
case "check":
|
|
220
|
+
case "uncheck":
|
|
221
|
+
this._push(pageAlias, framePath, { name: method, selector });
|
|
222
|
+
return;
|
|
223
|
+
case "select": {
|
|
224
|
+
const options = this._parseStringArray(args);
|
|
225
|
+
if (!options) {
|
|
226
|
+
this._diag(stmt, "could not resolve the select option argument \u2014 not replayed");
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
this._push(pageAlias, framePath, { name: "select", selector, options });
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
case "setInputFiles": {
|
|
233
|
+
this._push(pageAlias, framePath, { name: "setInputFiles", selector, files: this._parseFileArgs(args) });
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
case "dragTo": {
|
|
237
|
+
const targetMatch = args.match(/^\s*(\w+)\.([\s\S]+?)\s*(?:,\s*\{[\s\S]*\})?$/);
|
|
238
|
+
const target = targetMatch && this._pageAliases.has(targetMatch[1]) ? this._resolveLocatorChain(targetMatch[2]) : void 0;
|
|
239
|
+
if (!target) {
|
|
240
|
+
this._diag(stmt, "could not parse the drag target locator \u2014 not replayed");
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
this._push(pageAlias, framePath, { name: "dragTo", selector, target: target.selector });
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Emit the bare assertApiRequest MARKER for a checkRequestPayload statement.
|
|
250
|
+
* The payload assertion cannot execute during replay (no primitive), but its
|
|
251
|
+
* trace-side representation is just a marker whose payload/URL live in the
|
|
252
|
+
* HAR — the Go codegen re-correlates them at generate time. Emitting the
|
|
253
|
+
* marker means a combined export + regenerate keeps the assertion instead of
|
|
254
|
+
* silently losing it (SKYR-4083). Attributed to the last action's tab (the
|
|
255
|
+
* statement itself names no page).
|
|
256
|
+
*/
|
|
257
|
+
_emitApiRequestMarker(stmt) {
|
|
258
|
+
const pageAlias = this._actions.length ? this._actions[this._actions.length - 1].frame.pageAlias : "page";
|
|
259
|
+
this._push(pageAlias, [], { name: "assertApiRequest" });
|
|
260
|
+
this._diag(stmt, "payload assertion is not executed during replay; preserved as an assertApiRequest marker for export/regeneration");
|
|
261
|
+
}
|
|
262
|
+
/** Emit the fileChooser action for a setFiles/set_files on a chooser variable. */
|
|
263
|
+
_emitSetFiles(stmt, args) {
|
|
264
|
+
const trigger = this._heldChooserTrigger;
|
|
265
|
+
this._heldChooserTrigger = void 0;
|
|
266
|
+
if (!trigger) {
|
|
267
|
+
this._diag(stmt, "setFiles without a preceding chooser trigger click \u2014 not replayed");
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
this._push(trigger.pageAlias, trigger.framePath, { name: "fileChooser", selector: trigger.selector, files: this._parseFileArgs(args) });
|
|
271
|
+
}
|
|
272
|
+
/** Emit the assert/visualSnapshot action for an expect() statement. */
|
|
273
|
+
_emitExpect(stmt, subject, matcher, args) {
|
|
274
|
+
if (matcher === "screenshot") {
|
|
275
|
+
this._emitScreenshot(stmt, subject, args);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const subjMatch = subject.match(/^(\w+)\.([\s\S]+)$/);
|
|
279
|
+
const resolved = subjMatch && this._pageAliases.has(subjMatch[1]) ? this._resolveLocatorChain(subjMatch[2]) : void 0;
|
|
280
|
+
if (!resolved) {
|
|
281
|
+
this._diag(stmt, "could not parse the expect() subject locator \u2014 not replayed");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const { selector, framePath } = resolved;
|
|
285
|
+
const pageAlias = this._pageAliases.get(subjMatch[1]);
|
|
286
|
+
switch (matcher) {
|
|
287
|
+
case "assertText":
|
|
288
|
+
case "assertTextSub": {
|
|
289
|
+
const text = this._resolveStringArg(args);
|
|
290
|
+
if (text === void 0) {
|
|
291
|
+
this._diag(stmt, "could not resolve the text assertion argument \u2014 not replayed");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
this._push(pageAlias, framePath, { name: "assertText", selector, text, substring: matcher === "assertTextSub" });
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
case "assertValue": {
|
|
298
|
+
const value = this._resolveStringArg(args);
|
|
299
|
+
if (value === void 0) {
|
|
300
|
+
this._diag(stmt, "could not resolve the value assertion argument \u2014 not replayed");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
this._push(pageAlias, framePath, { name: "assertValue", selector, value });
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
case "assertChecked":
|
|
307
|
+
this._push(pageAlias, framePath, { name: "assertChecked", selector, checked: !/checked\s*[:=]\s*(false|False)/.test(args) });
|
|
308
|
+
return;
|
|
309
|
+
case "assertVisible":
|
|
310
|
+
this._push(pageAlias, framePath, { name: "assertVisible", selector });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
_emitScreenshot(stmt, subject, args) {
|
|
315
|
+
const filenameMatch = args.match(/^\s*(["'])((?:[^\\]|\\.)*?)\1/);
|
|
316
|
+
const filename = filenameMatch ? unescapeString(filenameMatch[2]) : "";
|
|
317
|
+
const fullPage = /full_?[pP]age\s*[:=]\s*(true|True)/.test(args);
|
|
318
|
+
const clipMatch = args.match(/clip\s*[:=]\s*(\{[^}]*\})/);
|
|
319
|
+
if (this._pageAliases.has(subject.trim())) {
|
|
320
|
+
const pageAlias = this._pageAliases.get(subject.trim());
|
|
321
|
+
if (clipMatch)
|
|
322
|
+
this._push(pageAlias, [], { name: "visualSnapshot", snapshotType: "region", filename, clip: parseLooseObject(clipMatch[1]) });
|
|
323
|
+
else
|
|
324
|
+
this._push(pageAlias, [], { name: "visualSnapshot", snapshotType: "page", filename, ...fullPage ? { fullPage: true } : {} });
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const subjMatch = subject.match(/^(\w+)\.([\s\S]+)$/);
|
|
328
|
+
const resolved = subjMatch && this._pageAliases.has(subjMatch[1]) ? this._resolveLocatorChain(subjMatch[2]) : void 0;
|
|
329
|
+
if (!resolved) {
|
|
330
|
+
this._diag(stmt, "could not parse the screenshot subject locator \u2014 not replayed");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
this._push(this._pageAliases.get(subjMatch[1]), resolved.framePath, { name: "visualSnapshot", snapshotType: "element", selector: resolved.selector, filename });
|
|
334
|
+
}
|
|
335
|
+
/** Emit page.mouse.* actions (coordinates + kwargs are language-neutral). */
|
|
336
|
+
_emitMouse(stmt, pageAlias, method, args) {
|
|
337
|
+
const nums = [...args.matchAll(/-?\d+(?:\.\d+)?/g)].map((m) => Number(m[0]));
|
|
338
|
+
switch (method) {
|
|
339
|
+
case "move": {
|
|
340
|
+
if (nums.length < 2)
|
|
341
|
+
break;
|
|
342
|
+
const stepsMatch = args.match(/steps\s*[:=]\s*(\d+)/);
|
|
343
|
+
const action = { name: "mouse.move", position: { x: nums[0], y: nums[1] } };
|
|
344
|
+
if (stepsMatch)
|
|
345
|
+
action.steps = Number(stepsMatch[1]);
|
|
346
|
+
this._push(pageAlias, [], action);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
case "down":
|
|
350
|
+
case "up": {
|
|
351
|
+
const buttonMatch = args.match(/button\s*[:=]\s*["'](\w+)["']/);
|
|
352
|
+
const action = { name: `mouse.${method}` };
|
|
353
|
+
if (buttonMatch)
|
|
354
|
+
action.button = buttonMatch[1];
|
|
355
|
+
this._push(pageAlias, [], action);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
case "wheel": {
|
|
359
|
+
if (nums.length < 2)
|
|
360
|
+
break;
|
|
361
|
+
this._push(pageAlias, [], { name: "mouse.wheel", position: { x: 0, y: 0 }, deltaX: nums[0], deltaY: nums[1], modifiers: 0 });
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
case "click": {
|
|
365
|
+
if (nums.length < 2)
|
|
366
|
+
break;
|
|
367
|
+
this._push(pageAlias, [], { name: "click", selector: "body", position: { x: nums[0], y: nums[1] }, button: "left", modifiers: 0, clickCount: 1 });
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
this._diag(stmt, `could not parse mouse.${method}() arguments \u2014 not replayed`);
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Resolve a single string-valued call argument: a literal, or a
|
|
375
|
+
* getValue(playwrightRequestN, "key") reference whose value is inlined from
|
|
376
|
+
* the tracked binding.
|
|
377
|
+
*/
|
|
378
|
+
_resolveStringArg(args) {
|
|
379
|
+
const literal = args.match(/^\s*(["'])((?:[^\\]|\\.|(?!\1).)*?)\1\s*$/s);
|
|
380
|
+
if (literal)
|
|
381
|
+
return unescapeString(literal[2]);
|
|
382
|
+
const getValue = this._matchGetValue(args);
|
|
383
|
+
if (getValue) {
|
|
384
|
+
const binding = this._bindings.get(getValue.binding);
|
|
385
|
+
if (binding === void 0)
|
|
386
|
+
return void 0;
|
|
387
|
+
let value = binding;
|
|
388
|
+
for (const part of getValue.key.split("."))
|
|
389
|
+
value = value?.[part];
|
|
390
|
+
if (value === void 0)
|
|
391
|
+
return void 0;
|
|
392
|
+
return String(value);
|
|
393
|
+
}
|
|
394
|
+
return void 0;
|
|
395
|
+
}
|
|
396
|
+
/** Parse select-option args: a string literal or an array of string literals. */
|
|
397
|
+
_parseStringArray(args) {
|
|
398
|
+
const single = this._resolveStringArg(args);
|
|
399
|
+
if (single !== void 0)
|
|
400
|
+
return [single];
|
|
401
|
+
const arrayMatch = args.match(/^\s*\[([\s\S]*)\]\s*$/);
|
|
402
|
+
if (arrayMatch)
|
|
403
|
+
return [...arrayMatch[1].matchAll(/(["'])((?:[^\\]|\\.|(?!\1).)*?)\1/g)].map((m) => unescapeString(m[2]));
|
|
404
|
+
return void 0;
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Parse setFiles/set_input_files arguments into file paths. Generated specs
|
|
408
|
+
* emit a spec-relative join (`path.join(__dirname, 'fixtures', "data.csv")`
|
|
409
|
+
* / `os.path.join(os.path.dirname(__file__), ...)`) — resolved against the
|
|
410
|
+
* spec file's directory — or plain string literals.
|
|
411
|
+
*/
|
|
412
|
+
_parseFileArgs(args) {
|
|
413
|
+
const files = [];
|
|
414
|
+
const joinRe = /(?:path\.join\(\s*__dirname|os\.path\.join\(\s*os\.path\.dirname\(__file__\))\s*((?:,\s*(["'])(?:[^\\]|\\.)*?\2\s*)+)\)/g;
|
|
415
|
+
let joinMatch;
|
|
416
|
+
let rest = args;
|
|
417
|
+
while (joinMatch = joinRe.exec(args)) {
|
|
418
|
+
const parts = [...joinMatch[1].matchAll(/(["'])((?:[^\\]|\\.)*?)\1/g)].map((m) => unescapeString(m[2]));
|
|
419
|
+
files.push(import_path.default.join(this._specDir, ...parts));
|
|
420
|
+
rest = rest.replace(joinMatch[0], "");
|
|
421
|
+
}
|
|
422
|
+
for (const m of rest.matchAll(/(["'])((?:[^\\]|\\.)*?)\1/g)) {
|
|
423
|
+
const p = unescapeString(m[2]);
|
|
424
|
+
files.push(import_path.default.isAbsolute(p) ? p : import_path.default.join(this._specDir, p));
|
|
425
|
+
}
|
|
426
|
+
return files;
|
|
427
|
+
}
|
|
428
|
+
/** Parse the trailing options of a click/hover call (button, clickCount/click_count, position, modifiers). */
|
|
429
|
+
_parseCallOptions(args) {
|
|
430
|
+
const out = {};
|
|
431
|
+
const buttonMatch = args.match(/button\s*[:=]\s*["'](left|middle|right)["']/);
|
|
432
|
+
if (buttonMatch)
|
|
433
|
+
out.button = buttonMatch[1];
|
|
434
|
+
const clickCountMatch = args.match(/click_?[cC]ount\s*[:=]\s*(\d+)/);
|
|
435
|
+
if (clickCountMatch)
|
|
436
|
+
out.clickCount = Number(clickCountMatch[1]);
|
|
437
|
+
const positionMatch = args.match(/position\s*[:=]\s*(\{[^}]*\})/);
|
|
438
|
+
if (positionMatch) {
|
|
439
|
+
const pos = parseLooseObject(positionMatch[1]);
|
|
440
|
+
if (typeof pos.x === "number" && typeof pos.y === "number")
|
|
441
|
+
out.position = { x: pos.x, y: pos.y };
|
|
442
|
+
}
|
|
443
|
+
const modifiersMatch = args.match(/modifiers\s*[:=]\s*\[([^\]]*)\]/);
|
|
444
|
+
if (modifiersMatch) {
|
|
445
|
+
let mask = 0;
|
|
446
|
+
for (const m of modifiersMatch[1].matchAll(/["'](\w+)["']/g))
|
|
447
|
+
mask |= MODIFIER_MASKS[m[1]] ?? 0;
|
|
448
|
+
out.modifiers = mask;
|
|
449
|
+
}
|
|
450
|
+
return out;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
const JS_SCAFFOLDING = [
|
|
454
|
+
/^import\b/,
|
|
455
|
+
/^const\s+.*=\s*require\(/,
|
|
456
|
+
/^const\s+pageTimeout\b/,
|
|
457
|
+
/^test\(/,
|
|
458
|
+
/^test\.setTimeout\(/,
|
|
459
|
+
/^test\.use\(/,
|
|
460
|
+
/^\}\)?;?$/,
|
|
461
|
+
/^(await\s+)?\w+\.setDefaultTimeout\(/,
|
|
462
|
+
/^page\s*=\s*newSkyrampPlaywrightPage\(/,
|
|
463
|
+
/^'use strict'/
|
|
464
|
+
];
|
|
465
|
+
const JS_LOCATOR_METHODS = "click|dblclick|hover|fill|press|pressSequentially|check|uncheck|selectOption|setInputFiles|dragTo";
|
|
466
|
+
const JS_METHOD_MAP = {
|
|
467
|
+
selectOption: "select",
|
|
468
|
+
dragTo: "dragTo",
|
|
469
|
+
setInputFiles: "setInputFiles"
|
|
470
|
+
};
|
|
471
|
+
class JsSpecParser extends SpecParserBase {
|
|
472
|
+
constructor(content, specDir) {
|
|
473
|
+
super(scanStatements(content, "//"), specDir);
|
|
474
|
+
this._locatorLanguage = "javascript";
|
|
475
|
+
this._frameHopSplit = /\.contentFrame\(\)\.?/;
|
|
476
|
+
}
|
|
477
|
+
_matchGetValue(args) {
|
|
478
|
+
const m = args.match(/^\s*getValue\(\s*(\w+)\s*,\s*(["'])((?:[^\\]|\\.)*?)\2\s*\)(\.toString\(\))?\s*$/);
|
|
479
|
+
return m ? { binding: m[1], key: unescapeString(m[3]) } : void 0;
|
|
480
|
+
}
|
|
481
|
+
_parseStatement(stmt) {
|
|
482
|
+
const text = stmt.text.replace(/;$/, "");
|
|
483
|
+
for (const re of JS_SCAFFOLDING) {
|
|
484
|
+
if (re.test(text))
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
const bindingMatch = text.match(/^const\s+(\w+)\s*(?::[^=]+)?=\s*(\{[\s\S]*\})$/);
|
|
488
|
+
if (bindingMatch) {
|
|
489
|
+
try {
|
|
490
|
+
this._bindings.set(bindingMatch[1], JSON.parse(bindingMatch[2]));
|
|
491
|
+
} catch {
|
|
492
|
+
this._diag(stmt, "object literal is not plain JSON; getValue() references to it cannot be inlined");
|
|
493
|
+
this._bindings.set(bindingMatch[1], void 0);
|
|
494
|
+
}
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
const responsePromiseMatch = text.match(/^const\s+(\w+)\s*=\s*(\w+)\.waitForResponse\(/);
|
|
498
|
+
if (responsePromiseMatch) {
|
|
499
|
+
this._promiseVars.add(responsePromiseMatch[1]);
|
|
500
|
+
this._diag(stmt, "waitForResponse is a codegen enrichment with no replay primitive \u2014 not replayed");
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const chooserPromiseMatch = text.match(/^const\s+(\w+)\s*=\s*(\w+)\.waitForEvent\(\s*["']filechooser["']\s*\)/);
|
|
504
|
+
if (chooserPromiseMatch) {
|
|
505
|
+
this._promiseVars.add(chooserPromiseMatch[1]);
|
|
506
|
+
this._pendingFileChooser = true;
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
const awaitPromiseMatch = text.match(/^const\s+(\w+)\s*=\s*await\s+(\w+)$/);
|
|
510
|
+
if (awaitPromiseMatch && this._promiseVars.has(awaitPromiseMatch[2])) {
|
|
511
|
+
this._promiseVars.add(awaitPromiseMatch[1]);
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
const newPageMatch = text.match(/^const\s+(\w+)\s*=\s*await\s+(\w+)\.context\(\)\.newPage\(\)$/);
|
|
515
|
+
if (newPageMatch) {
|
|
516
|
+
this._pageAliases.set(newPageMatch[1], newPageMatch[1]);
|
|
517
|
+
this._push(newPageMatch[1], [], { name: "openPage", url: "about:blank" });
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
const waitForPageMatch = text.match(/^const\s+(\w+)\s*=\s*await\s+(\w+)\.waitForPage\(/);
|
|
521
|
+
if (waitForPageMatch) {
|
|
522
|
+
this._pageAliases.set(waitForPageMatch[1], waitForPageMatch[1]);
|
|
523
|
+
this._push(waitForPageMatch[1], [], { name: "openPage", url: "about:blank" });
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
const setFilesMatch = text.match(/^await\s+(\w+)\.setFiles\(([\s\S]*)\)$/);
|
|
527
|
+
if (setFilesMatch && this._promiseVars.has(setFilesMatch[1])) {
|
|
528
|
+
this._emitSetFiles(stmt, setFilesMatch[2]);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (/^(await\s+)?checkRequestPayload\(/.test(text)) {
|
|
532
|
+
this._emitApiRequestMarker(stmt);
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
const gotoMatch = text.match(/^await\s+(\w+)\.goto\(\s*(["'])((?:[^\\]|\\.)*?)\2/);
|
|
536
|
+
if (gotoMatch && this._pageAliases.has(gotoMatch[1])) {
|
|
537
|
+
this._push(this._pageAliases.get(gotoMatch[1]), [], { name: "navigate", url: unescapeString(gotoMatch[3]) });
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const waitTimeoutMatch = text.match(/^await\s+(\w+)\.waitForTimeout\(\s*(\d+)\s*\)$/);
|
|
541
|
+
if (waitTimeoutMatch && this._pageAliases.has(waitTimeoutMatch[1])) {
|
|
542
|
+
this._push(this._pageAliases.get(waitTimeoutMatch[1]), [], { name: "waitForTimeout", duration: Number(waitTimeoutMatch[2]) });
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const evaluateMatch = text.match(/^await\s+(\w+)\.evaluate\(([\s\S]*)\)$/);
|
|
546
|
+
if (evaluateMatch && this._pageAliases.has(evaluateMatch[1])) {
|
|
547
|
+
this._push(this._pageAliases.get(evaluateMatch[1]), [], { name: "evaluate", expression: evaluateMatch[2].trim() });
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const closeMatch = text.match(/^await\s+(\w+)\.close\(\)$/);
|
|
551
|
+
if (closeMatch && this._pageAliases.has(closeMatch[1])) {
|
|
552
|
+
this._push(this._pageAliases.get(closeMatch[1]), [], { name: "closePage" });
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
const mouseMatch = text.match(/^await\s+(\w+)\.mouse\.(move|down|up|wheel|click)\(([\s\S]*)\)$/);
|
|
556
|
+
if (mouseMatch && this._pageAliases.has(mouseMatch[1])) {
|
|
557
|
+
this._emitMouse(stmt, this._pageAliases.get(mouseMatch[1]), mouseMatch[2], mouseMatch[3]);
|
|
558
|
+
return;
|
|
559
|
+
}
|
|
560
|
+
const expectMatch = text.match(/^await\s+expect\(\s*([\s\S]+?)\s*\)\.(not\.)?(\w+)\(([\s\S]*)\)$/);
|
|
561
|
+
if (expectMatch) {
|
|
562
|
+
if (expectMatch[2]) {
|
|
563
|
+
this._diag(stmt, `negated expect matcher .not.${expectMatch[3]} has no trace equivalent \u2014 not replayed`);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
const matcher = {
|
|
567
|
+
toHaveScreenshot: "screenshot",
|
|
568
|
+
toHaveText: "assertText",
|
|
569
|
+
toContainText: "assertTextSub",
|
|
570
|
+
toHaveValue: "assertValue",
|
|
571
|
+
toBeChecked: "assertChecked",
|
|
572
|
+
toBeVisible: "assertVisible"
|
|
573
|
+
}[expectMatch[3]];
|
|
574
|
+
if (!matcher) {
|
|
575
|
+
this._diag(stmt, `expect matcher .${expectMatch[3]}() has no trace equivalent \u2014 not replayed`);
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
this._emitExpect(stmt, expectMatch[1], matcher, expectMatch[4]);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const actionMatch = text.match(new RegExp(`^await\\s+(\\w+)\\.([\\s\\S]+)\\.(${JS_LOCATOR_METHODS})\\(([\\s\\S]*)\\)$`));
|
|
582
|
+
if (actionMatch && this._pageAliases.has(actionMatch[1])) {
|
|
583
|
+
this._emitLocatorAction(stmt, this._pageAliases.get(actionMatch[1]), actionMatch[2], JS_METHOD_MAP[actionMatch[3]] ?? actionMatch[3], actionMatch[4]);
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
this._diag(stmt, "unrecognized statement \u2014 not replayed");
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const PY_SCAFFOLDING = [
|
|
590
|
+
/^import\b/,
|
|
591
|
+
/^from\s+[\w.]+\s+import\b/,
|
|
592
|
+
/^@/,
|
|
593
|
+
/^def\s+test_\w+\(/,
|
|
594
|
+
/^\w+\.set_default_timeout\(/,
|
|
595
|
+
/^page\s*=\s*skyramp\.new_skyramp_playwright_page\(/
|
|
596
|
+
];
|
|
597
|
+
const PY_LOCATOR_METHODS = "click|dblclick|hover|fill|press|press_sequentially|pressSequentially|check|uncheck|select_option|set_input_files|drag_to";
|
|
598
|
+
const PY_METHOD_MAP = {
|
|
599
|
+
press_sequentially: "pressSequentially",
|
|
600
|
+
select_option: "select",
|
|
601
|
+
set_input_files: "setInputFiles",
|
|
602
|
+
drag_to: "dragTo"
|
|
603
|
+
};
|
|
604
|
+
const PY_EXPECT_MATCHERS = {
|
|
605
|
+
to_have_screenshot: "screenshot",
|
|
606
|
+
to_have_text: "assertText",
|
|
607
|
+
to_contain_text: "assertTextSub",
|
|
608
|
+
to_have_value: "assertValue",
|
|
609
|
+
to_be_checked: "assertChecked",
|
|
610
|
+
to_be_visible: "assertVisible"
|
|
611
|
+
};
|
|
612
|
+
class PySpecParser extends SpecParserBase {
|
|
613
|
+
constructor(content, specDir) {
|
|
614
|
+
super(scanStatements(content, "#"), specDir);
|
|
615
|
+
this._locatorLanguage = "python";
|
|
616
|
+
this._frameHopSplit = /\.content_frame\./;
|
|
617
|
+
}
|
|
618
|
+
_matchGetValue(args) {
|
|
619
|
+
const m = args.match(/^\s*(?:str\(\s*)?skyramp\.get_value\(\s*(\w+)\s*,\s*(["'])((?:[^\\]|\\.)*?)\2\s*\)\s*\)?\s*$/);
|
|
620
|
+
return m ? { binding: m[1], key: unescapeString(m[3]) } : void 0;
|
|
621
|
+
}
|
|
622
|
+
_parseStatement(stmt) {
|
|
623
|
+
if (this._skipBlockIndent !== void 0) {
|
|
624
|
+
if (stmt.indent > this._skipBlockIndent)
|
|
625
|
+
return;
|
|
626
|
+
this._skipBlockIndent = void 0;
|
|
627
|
+
}
|
|
628
|
+
const defMatch = stmt.text.match(/^def\s+(\w+)\(/);
|
|
629
|
+
if (defMatch && !defMatch[1].startsWith("test_")) {
|
|
630
|
+
this._skipBlockIndent = stmt.indent;
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
const text = stmt.text;
|
|
634
|
+
for (const re of PY_SCAFFOLDING) {
|
|
635
|
+
if (re.test(text))
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
const expectResponseMatch = text.match(/^with\s+(\w+)\.expect_response\([\s\S]*\)\s+as\s+(\w+)\s*:$/);
|
|
639
|
+
if (expectResponseMatch) {
|
|
640
|
+
this._promiseVars.add(expectResponseMatch[2]);
|
|
641
|
+
this._diag(stmt, "waitForResponse is a codegen enrichment with no replay primitive \u2014 not replayed");
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
const expectChooserMatch = text.match(/^with\s+(\w+)\.expect_file_chooser\(\)\s+as\s+(\w+)\s*:$/);
|
|
645
|
+
if (expectChooserMatch) {
|
|
646
|
+
this._promiseVars.add(expectChooserMatch[2]);
|
|
647
|
+
this._pendingFileChooser = true;
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const bindingMatch = text.match(/^(\w+)\s*=\s*(\{[\s\S]*\})$/);
|
|
651
|
+
if (bindingMatch) {
|
|
652
|
+
const json = pyLiteralToJson(bindingMatch[2]);
|
|
653
|
+
try {
|
|
654
|
+
this._bindings.set(bindingMatch[1], JSON.parse(json));
|
|
655
|
+
} catch {
|
|
656
|
+
this._diag(stmt, "dict literal could not be parsed; get_value() references to it cannot be inlined");
|
|
657
|
+
this._bindings.set(bindingMatch[1], void 0);
|
|
658
|
+
}
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const newPageMatch = text.match(/^(\w+)\s*=\s*(\w+)\.context\.new_page\(\)$/);
|
|
662
|
+
if (newPageMatch) {
|
|
663
|
+
this._pageAliases.set(newPageMatch[1], newPageMatch[1]);
|
|
664
|
+
this._push(newPageMatch[1], [], { name: "openPage", url: "about:blank" });
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const waitForPageMatch = text.match(/^(\w+)\s*=\s*(\w+)\.wait_for_page\(/);
|
|
668
|
+
if (waitForPageMatch) {
|
|
669
|
+
this._pageAliases.set(waitForPageMatch[1], waitForPageMatch[1]);
|
|
670
|
+
this._push(waitForPageMatch[1], [], { name: "openPage", url: "about:blank" });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
673
|
+
const valueMatch = text.match(/^(\w+)\s*=\s*(\w+)\.value$/);
|
|
674
|
+
if (valueMatch && this._promiseVars.has(valueMatch[2])) {
|
|
675
|
+
this._promiseVars.add(valueMatch[1]);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
const setFilesMatch = text.match(/^(\w+)\.set_files\(([\s\S]*)\)$/);
|
|
679
|
+
if (setFilesMatch && this._promiseVars.has(setFilesMatch[1])) {
|
|
680
|
+
this._emitSetFiles(stmt, setFilesMatch[2]);
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
if (/^skyramp\.check_request_payload\(/.test(text)) {
|
|
684
|
+
this._emitApiRequestMarker(stmt);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
const gotoMatch = text.match(/^(\w+)\.goto\(\s*(["'])((?:[^\\]|\\.)*?)\2/);
|
|
688
|
+
if (gotoMatch && this._pageAliases.has(gotoMatch[1])) {
|
|
689
|
+
this._push(this._pageAliases.get(gotoMatch[1]), [], { name: "navigate", url: unescapeString(gotoMatch[3]) });
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
const waitTimeoutMatch = text.match(/^(\w+)\.wait_for_timeout\(\s*(\d+)\s*\)$/);
|
|
693
|
+
if (waitTimeoutMatch && this._pageAliases.has(waitTimeoutMatch[1])) {
|
|
694
|
+
this._push(this._pageAliases.get(waitTimeoutMatch[1]), [], { name: "waitForTimeout", duration: Number(waitTimeoutMatch[2]) });
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
const evaluateMatch = text.match(/^(\w+)\.evaluate\(([\s\S]*)\)$/);
|
|
698
|
+
if (evaluateMatch && this._pageAliases.has(evaluateMatch[1])) {
|
|
699
|
+
const expression = this._resolveStringArg(evaluateMatch[2]) ?? evaluateMatch[2].trim();
|
|
700
|
+
this._push(this._pageAliases.get(evaluateMatch[1]), [], { name: "evaluate", expression });
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
const closeMatch = text.match(/^(\w+)\.close\(\)$/);
|
|
704
|
+
if (closeMatch && this._pageAliases.has(closeMatch[1])) {
|
|
705
|
+
this._push(this._pageAliases.get(closeMatch[1]), [], { name: "closePage" });
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
const mouseMatch = text.match(/^(\w+)\.mouse\.(move|down|up|wheel|click)\(([\s\S]*)\)$/);
|
|
709
|
+
if (mouseMatch && this._pageAliases.has(mouseMatch[1])) {
|
|
710
|
+
this._emitMouse(stmt, this._pageAliases.get(mouseMatch[1]), mouseMatch[2], mouseMatch[3]);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
713
|
+
const expectMatch = text.match(/^expect\(\s*([\s\S]+?)\s*\)\.(not_)?(\w+)\(([\s\S]*)\)$/);
|
|
714
|
+
if (expectMatch) {
|
|
715
|
+
if (expectMatch[2]) {
|
|
716
|
+
this._diag(stmt, `negated expect matcher .not_${expectMatch[3]} has no trace equivalent \u2014 not replayed`);
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
719
|
+
const matcher = PY_EXPECT_MATCHERS[expectMatch[3]];
|
|
720
|
+
if (!matcher) {
|
|
721
|
+
this._diag(stmt, `expect matcher .${expectMatch[3]}() has no trace equivalent \u2014 not replayed`);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
this._emitExpect(stmt, expectMatch[1], matcher, expectMatch[4]);
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
if (/\bwait_for_frame\(/.test(text)) {
|
|
728
|
+
this._diag(stmt, "wait_for_frame (extension frame) replay is not supported yet \u2014 not replayed");
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const actionMatch = text.match(new RegExp(`^(\\w+)\\.([\\s\\S]+)\\.(${PY_LOCATOR_METHODS})\\(([\\s\\S]*)\\)$`));
|
|
732
|
+
if (actionMatch && this._pageAliases.has(actionMatch[1])) {
|
|
733
|
+
this._emitLocatorAction(stmt, this._pageAliases.get(actionMatch[1]), actionMatch[2], PY_METHOD_MAP[actionMatch[3]] ?? actionMatch[3], actionMatch[4]);
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
this._diag(stmt, "unrecognized statement \u2014 not replayed");
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
function unescapeString(text) {
|
|
740
|
+
return text.replace(/\\(.)/g, (_, c) => c === "n" ? "\n" : c === "t" ? " " : c === "r" ? "\r" : c);
|
|
741
|
+
}
|
|
742
|
+
function pyLiteralToJson(text) {
|
|
743
|
+
let out = "";
|
|
744
|
+
let quote;
|
|
745
|
+
for (let i = 0; i < text.length; i++) {
|
|
746
|
+
const c = text[i];
|
|
747
|
+
if (quote) {
|
|
748
|
+
out += c;
|
|
749
|
+
if (c === "\\")
|
|
750
|
+
out += text[++i] ?? "";
|
|
751
|
+
else if (c === quote)
|
|
752
|
+
quote = void 0;
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
if (c === '"' || c === "'") {
|
|
756
|
+
quote = c;
|
|
757
|
+
out += c;
|
|
758
|
+
continue;
|
|
759
|
+
}
|
|
760
|
+
if (/\w/.test(c)) {
|
|
761
|
+
let word = c;
|
|
762
|
+
while (i + 1 < text.length && /\w/.test(text[i + 1]))
|
|
763
|
+
word += text[++i];
|
|
764
|
+
out += word === "True" ? "true" : word === "False" ? "false" : word === "None" ? "null" : word;
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
767
|
+
out += c;
|
|
768
|
+
}
|
|
769
|
+
return out;
|
|
770
|
+
}
|
|
771
|
+
function parseLooseObject(text) {
|
|
772
|
+
const out = {};
|
|
773
|
+
for (const m of text.matchAll(/["']?(\w+)["']?\s*[:=]\s*(-?\d+(?:\.\d+)?|true|false|True|False)/g))
|
|
774
|
+
out[m[1]] = /^true$/i.test(m[2]) ? true : /^false$/i.test(m[2]) ? false : Number(m[2]);
|
|
775
|
+
return out;
|
|
776
|
+
}
|
|
777
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
778
|
+
0 && (module.exports = {
|
|
779
|
+
specLanguageForPath,
|
|
780
|
+
specToActions
|
|
781
|
+
});
|