@skyramp/mcp 0.1.8 → 0.2.0-rc.2
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 +4 -2
- package/build/playwright/registerPlaywrightTools.js +12 -0
- package/build/playwright/traceRecordingPrompt.js +15 -0
- package/build/prompts/code-reuse.js +106 -7
- package/build/prompts/pom-aware-code-reuse.js +106 -7
- package/build/prompts/startTraceCollectionPrompts.js +37 -15
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +26 -31
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +40 -1
- package/build/prompts/test-maintenance/driftAnalysisSections.js +90 -86
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +286 -163
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +154 -45
- package/build/prompts/test-recommendation/diffExecutionPlan.js +246 -117
- package/build/prompts/test-recommendation/promptPlan.js +290 -0
- package/build/prompts/test-recommendation/promptPlan.test.js +336 -0
- package/build/prompts/test-recommendation/recommendationSections.js +4 -3
- package/build/prompts/test-recommendation/recommendationShared.js +23 -1
- package/build/prompts/test-recommendation/scopeAssessment.js +65 -14
- package/build/prompts/test-recommendation/scopeAssessment.test.js +93 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +36 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +316 -1
- package/build/prompts/testbot/testbot-prompts.js +73 -13
- package/build/prompts/testbot/testbot-prompts.test.js +114 -1
- package/build/resources/testbotResource.js +1 -1
- package/build/services/ScenarioGenerationService.integration.test.js +158 -0
- package/build/services/ScenarioGenerationService.js +47 -4
- package/build/services/ScenarioGenerationService.test.js +158 -22
- package/build/services/TestExecutionService.js +73 -15
- package/build/services/TestExecutionService.test.js +105 -0
- package/build/services/TestGenerationService.js +11 -1
- package/build/tools/executeSkyrampTestTool.js +1 -10
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +16 -4
- package/build/tools/generate-tests/generateIntegrationRestTool.js +2 -0
- package/build/tools/generate-tests/generateUIRestTool.js +2 -0
- package/build/tools/test-management/actionsTool.js +152 -63
- package/build/tools/test-management/analyzeChangesTool.js +178 -64
- package/build/tools/test-management/analyzeChangesTool.test.js +103 -16
- package/build/tools/test-management/analyzeTestHealthTool.js +30 -81
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.js +149 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +100 -0
- package/build/tools/trace/resolveSaveStoragePath.js +16 -0
- package/build/tools/trace/resolveSaveStoragePath.test.js +17 -0
- package/build/tools/trace/resolveSessionPaths.js +39 -0
- package/build/tools/trace/resolveSessionPaths.test.js +103 -0
- package/build/tools/trace/sessionState.js +14 -0
- package/build/tools/trace/sessionState.test.js +17 -0
- package/build/tools/trace/startTraceCollectionTool.js +84 -14
- package/build/tools/trace/stopTraceCollectionTool.js +9 -2
- package/build/types/TestAnalysis.js +50 -0
- package/build/types/TestRecommendation.js +6 -58
- package/build/types/TestTypes.js +1 -1
- package/build/utils/AnalysisStateManager.js +22 -11
- package/build/utils/branchDiff.js +11 -2
- package/build/utils/docker.test.js +1 -1
- package/build/utils/gitStaging.js +52 -3
- package/build/utils/gitStaging.test.js +19 -1
- package/build/utils/repoScanner.js +18 -10
- package/build/utils/repoScanner.test.js +92 -0
- package/build/utils/routeParsers.js +180 -25
- package/build/utils/routeParsers.test.js +180 -1
- package/build/utils/scenarioDrafting.js +220 -17
- package/build/utils/scenarioDrafting.test.js +182 -9
- package/build/utils/sourceRouteExtractor.js +806 -0
- package/build/utils/sourceRouteExtractor.test.js +565 -0
- package/build/utils/uiPageEnumerator.js +319 -0
- package/build/utils/uiPageEnumerator.test.js +422 -0
- package/build/utils/utils.js +27 -0
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.js +33 -4
- package/node_modules/playwright/ThirdPartyNotices.txt +6 -6
- package/node_modules/playwright/lib/dom-analyzer/analyze.js +111 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1210 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.test.js +396 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.js +254 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.test.js +304 -0
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +384 -0
- package/node_modules/playwright/lib/dom-analyzer/curatedWidgets.js +73 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +85 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.js +90 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.test.js +231 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.fixtures.js +145 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.test.js +41 -0
- package/node_modules/playwright/lib/dom-analyzer/graph.js +36 -0
- package/node_modules/playwright/lib/dom-analyzer/liveFingerprints.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.js +72 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.test.js +182 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.js +150 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.test.js +470 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.js +169 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.test.js +269 -0
- package/node_modules/playwright/lib/dom-analyzer/serialization.js +75 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.js +30 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.test.js +84 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.js +127 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.test.js +212 -0
- package/node_modules/playwright/lib/mcp/browser/browserContextFactory.js +3 -1
- package/node_modules/playwright/lib/mcp/browser/config.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/context.js +17 -1
- package/node_modules/playwright/lib/mcp/browser/tab.js +38 -0
- package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +261 -0
- package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +3 -3
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +146 -0
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.test.js +140 -0
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +226 -0
- package/node_modules/playwright/lib/mcp/browser/tools/snapshot.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/widgetContract.js +168 -0
- package/node_modules/playwright/lib/mcp/browser/tools.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +52 -12
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +64 -13
- package/node_modules/playwright/package.json +1 -1
- 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
- package/package.json +3 -3
- package/build/services/TestHealthService.js +0 -694
- package/build/services/TestHealthService.test.js +0 -241
- package/build/types/TestDriftAnalysis.js +0 -1
- package/build/types/TestHealth.js +0 -4
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var import_logicalNameResolver = require("./logicalNameResolver");
|
|
3
|
+
const cases = [];
|
|
4
|
+
function test(name, run) {
|
|
5
|
+
cases.push({ name, run });
|
|
6
|
+
}
|
|
7
|
+
function assertEqual(actual, expected, msg) {
|
|
8
|
+
const a = JSON.stringify(actual);
|
|
9
|
+
const e = JSON.stringify(expected);
|
|
10
|
+
if (a !== e) throw new Error(`${msg ?? "assertEqual"} \u2014 expected ${e}, got ${a}`);
|
|
11
|
+
}
|
|
12
|
+
function input(overrides = {}) {
|
|
13
|
+
return {
|
|
14
|
+
initialName: "save_btn",
|
|
15
|
+
sectionName: "main",
|
|
16
|
+
testId: null,
|
|
17
|
+
stableId: null,
|
|
18
|
+
nearestAncestorId: null,
|
|
19
|
+
...overrides
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
test("resolveCollisions: single element no collision, pass through unchanged", () => {
|
|
23
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([input({ initialName: "save_btn" })]);
|
|
24
|
+
assertEqual(out, ["save_btn"]);
|
|
25
|
+
});
|
|
26
|
+
test("resolveCollisions: testId wins when element has one (slugged)", () => {
|
|
27
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
28
|
+
input({ initialName: "save_btn", testId: "save-action" }),
|
|
29
|
+
input({ initialName: "save_btn", testId: "confirm-action" })
|
|
30
|
+
]);
|
|
31
|
+
assertEqual(out, ["save_action_save_btn", "confirm_action_save_btn"]);
|
|
32
|
+
});
|
|
33
|
+
test("resolveCollisions: stableId used when testId absent (slugged)", () => {
|
|
34
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
35
|
+
input({ initialName: "save_btn", stableId: "top-save" }),
|
|
36
|
+
input({ initialName: "save_btn", stableId: "bottom-save" })
|
|
37
|
+
]);
|
|
38
|
+
assertEqual(out, ["top_save_save_btn", "bottom_save_save_btn"]);
|
|
39
|
+
});
|
|
40
|
+
test("resolveCollisions: nearestAncestorId used when self has no id (slugged)", () => {
|
|
41
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
42
|
+
input({ initialName: "save_btn", nearestAncestorId: "form-a" }),
|
|
43
|
+
input({ initialName: "save_btn", nearestAncestorId: "form-b" })
|
|
44
|
+
]);
|
|
45
|
+
assertEqual(out, ["form_a_save_btn", "form_b_save_btn"]);
|
|
46
|
+
});
|
|
47
|
+
test("resolveCollisions: position-hinted fallback when nothing disambiguates", () => {
|
|
48
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
49
|
+
input({ initialName: "save_btn", sectionName: "main" }),
|
|
50
|
+
input({ initialName: "save_btn", sectionName: "main" }),
|
|
51
|
+
input({ initialName: "save_btn", sectionName: "main" })
|
|
52
|
+
]);
|
|
53
|
+
assertEqual(out, ["main_save_btn_1", "main_save_btn_2", "main_save_btn_3"]);
|
|
54
|
+
});
|
|
55
|
+
test("resolveCollisions: duplicate testIds fall through to position-hinted fallback", () => {
|
|
56
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
57
|
+
input({ initialName: "save_btn", testId: "save", sectionName: "main" }),
|
|
58
|
+
input({ initialName: "save_btn", testId: "save", sectionName: "main" })
|
|
59
|
+
]);
|
|
60
|
+
assertEqual(out, ["main_save_btn_1", "main_save_btn_2"]);
|
|
61
|
+
});
|
|
62
|
+
test("resolveCollisions: ancestor id with spaces and caps is slugged", () => {
|
|
63
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
64
|
+
input({
|
|
65
|
+
initialName: "view_details_btn",
|
|
66
|
+
sectionName: "page",
|
|
67
|
+
nearestAncestorId: "order-detail-product-Bose QuietComfort 35 II"
|
|
68
|
+
}),
|
|
69
|
+
input({
|
|
70
|
+
initialName: "view_details_btn",
|
|
71
|
+
sectionName: "page",
|
|
72
|
+
nearestAncestorId: "order-detail-product-Apple iWatch"
|
|
73
|
+
})
|
|
74
|
+
]);
|
|
75
|
+
assertEqual(out, [
|
|
76
|
+
"order_detail_product_bose_quietcomfort_35_ii_view_details_btn",
|
|
77
|
+
"order_detail_product_apple_iwatch_view_details_btn"
|
|
78
|
+
]);
|
|
79
|
+
});
|
|
80
|
+
test("resolveCollisions: testId with capitals is slugged", () => {
|
|
81
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
82
|
+
input({ initialName: "save_btn", testId: "MainSaveBtn" }),
|
|
83
|
+
input({ initialName: "save_btn", testId: "FooterSaveBtn" })
|
|
84
|
+
]);
|
|
85
|
+
assertEqual(out, ["mainsavebtn_save_btn", "footersavebtn_save_btn"]);
|
|
86
|
+
});
|
|
87
|
+
test("resolveCollisions: prefix that slugs to empty falls through tier", () => {
|
|
88
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
89
|
+
input({ initialName: "save_btn", sectionName: "main", nearestAncestorId: "!!!" }),
|
|
90
|
+
input({ initialName: "save_btn", sectionName: "main", nearestAncestorId: "---" })
|
|
91
|
+
]);
|
|
92
|
+
assertEqual(out, ["main_save_btn_1", "main_save_btn_2"]);
|
|
93
|
+
});
|
|
94
|
+
test("resolveCollisions: ladder falls through when testId slugs to empty", () => {
|
|
95
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
96
|
+
input({ initialName: "save_btn", testId: "!!!", stableId: "top-save" }),
|
|
97
|
+
input({ initialName: "save_btn", testId: "???", stableId: "bottom-save" })
|
|
98
|
+
]);
|
|
99
|
+
assertEqual(out, ["top_save_save_btn", "bottom_save_save_btn"]);
|
|
100
|
+
});
|
|
101
|
+
test("resolveCollisions: CJK ancestor id preserved after slugging", () => {
|
|
102
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
103
|
+
input({ initialName: "edit_btn", nearestAncestorId: "\u8CC7\u7523\u7BA1\u7406_\u30D1\u30CD\u30EB" }),
|
|
104
|
+
input({ initialName: "edit_btn", nearestAncestorId: "\u5C65\u6B74\u60C5\u5831_\u30D1\u30CD\u30EB" })
|
|
105
|
+
]);
|
|
106
|
+
assertEqual(out, ["\u8CC7\u7523\u7BA1\u7406_\u30D1\u30CD\u30EB_edit_btn", "\u5C65\u6B74\u60C5\u5831_\u30D1\u30CD\u30EB_edit_btn"]);
|
|
107
|
+
});
|
|
108
|
+
test("resolveCollisions: no cross-section collision \u2014 same initialName in different sections", () => {
|
|
109
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
110
|
+
input({ initialName: "save_btn", sectionName: "header" }),
|
|
111
|
+
input({ initialName: "save_btn", sectionName: "footer" })
|
|
112
|
+
]);
|
|
113
|
+
assertEqual(out, ["save_btn", "save_btn"]);
|
|
114
|
+
});
|
|
115
|
+
test("resolveCollisions: mixed group \u2014 one with testId, one without", () => {
|
|
116
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
117
|
+
input({ initialName: "save_btn", testId: "save-top" }),
|
|
118
|
+
input({ initialName: "save_btn" })
|
|
119
|
+
]);
|
|
120
|
+
assertEqual(out, ["main_save_btn_1", "main_save_btn_2"]);
|
|
121
|
+
});
|
|
122
|
+
test("resolveCollisions: preserves document order in position hints", () => {
|
|
123
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
124
|
+
input({ initialName: "item", sectionName: "list" }),
|
|
125
|
+
input({ initialName: "item", sectionName: "list" }),
|
|
126
|
+
input({ initialName: "item", sectionName: "list" })
|
|
127
|
+
]);
|
|
128
|
+
assertEqual(out, ["list_item_1", "list_item_2", "list_item_3"]);
|
|
129
|
+
});
|
|
130
|
+
test("resolveCollisions: multi-signal precedence \u2014 testId beats stableId beats nearestAncestorId", () => {
|
|
131
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
132
|
+
input({ initialName: "save_btn", testId: "save-all", stableId: "id-a", nearestAncestorId: "anc-a" }),
|
|
133
|
+
input({ initialName: "save_btn", testId: null, stableId: "id-b", nearestAncestorId: "anc-b" })
|
|
134
|
+
]);
|
|
135
|
+
assertEqual(out, ["save_all_save_btn", "id_b_save_btn"]);
|
|
136
|
+
});
|
|
137
|
+
test("resolveCollisions: multiple simultaneous collision groups in one input", () => {
|
|
138
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
139
|
+
input({ initialName: "save_btn", testId: "save-a", sectionName: "main" }),
|
|
140
|
+
input({ initialName: "cancel_btn", testId: "cancel-a", sectionName: "main" }),
|
|
141
|
+
input({ initialName: "save_btn", testId: "save-b", sectionName: "main" }),
|
|
142
|
+
input({ initialName: "cancel_btn", testId: "cancel-b", sectionName: "main" })
|
|
143
|
+
]);
|
|
144
|
+
assertEqual(out, [
|
|
145
|
+
"save_a_save_btn",
|
|
146
|
+
"cancel_a_cancel_btn",
|
|
147
|
+
"save_b_save_btn",
|
|
148
|
+
"cancel_b_cancel_btn"
|
|
149
|
+
]);
|
|
150
|
+
});
|
|
151
|
+
test("resolveCollisions: mixed collision and non-collision elements in one input", () => {
|
|
152
|
+
const out = (0, import_logicalNameResolver.resolveCollisions)([
|
|
153
|
+
input({ initialName: "unique_btn", testId: "u", sectionName: "main" }),
|
|
154
|
+
input({ initialName: "save_btn", testId: "a", sectionName: "main" }),
|
|
155
|
+
input({ initialName: "save_btn", testId: "b", sectionName: "main" }),
|
|
156
|
+
input({ initialName: "another_btn", testId: "x", sectionName: "main" })
|
|
157
|
+
]);
|
|
158
|
+
assertEqual(out, [
|
|
159
|
+
"unique_btn",
|
|
160
|
+
"a_save_btn",
|
|
161
|
+
"b_save_btn",
|
|
162
|
+
"another_btn"
|
|
163
|
+
]);
|
|
164
|
+
});
|
|
165
|
+
let failed = 0;
|
|
166
|
+
for (const { name, run } of cases) {
|
|
167
|
+
try {
|
|
168
|
+
run();
|
|
169
|
+
console.log(" \u2713", name);
|
|
170
|
+
} catch (e) {
|
|
171
|
+
failed++;
|
|
172
|
+
console.log(" \u2717", name);
|
|
173
|
+
console.log(" ", e.message);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (failed > 0) {
|
|
177
|
+
console.log(`
|
|
178
|
+
${failed}/${cases.length} failed`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
console.log(`
|
|
182
|
+
${cases.length} passed`);
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
var possibleAssertions_exports = {};
|
|
20
|
+
__export(possibleAssertions_exports, {
|
|
21
|
+
buildFullCaptureAssertions: () => buildFullCaptureAssertions,
|
|
22
|
+
buildPossibleAssertions: () => buildPossibleAssertions
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(possibleAssertions_exports);
|
|
25
|
+
const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
26
|
+
"button",
|
|
27
|
+
"link",
|
|
28
|
+
"textbox",
|
|
29
|
+
"combobox",
|
|
30
|
+
"tab",
|
|
31
|
+
"menuitem",
|
|
32
|
+
"switch",
|
|
33
|
+
"checkbox",
|
|
34
|
+
"radio",
|
|
35
|
+
"option",
|
|
36
|
+
"searchbox",
|
|
37
|
+
"dialog",
|
|
38
|
+
"alertdialog"
|
|
39
|
+
]);
|
|
40
|
+
function escapeForSingleQuote(s) {
|
|
41
|
+
let cleaned = s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/[\r\n]+/g, " ");
|
|
42
|
+
if (cleaned.length > 80)
|
|
43
|
+
cleaned = cleaned.slice(0, 79) + "\u2026";
|
|
44
|
+
return cleaned;
|
|
45
|
+
}
|
|
46
|
+
function buildPossibleAssertions(delta) {
|
|
47
|
+
const results = [];
|
|
48
|
+
for (const el of delta.elementsAdded) {
|
|
49
|
+
if (!el.accessibleName.trim()) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const escapedName = escapeForSingleQuote(el.accessibleName);
|
|
53
|
+
const code = `await expect(page.getByRole('${el.role}', { name: '${escapedName}' })).toBeVisible();`;
|
|
54
|
+
let rationale;
|
|
55
|
+
let tier;
|
|
56
|
+
if (INTERACTIVE_ROLES.has(el.role)) {
|
|
57
|
+
rationale = `Element added to DOM after action: ${el.role} "${el.accessibleName}"`;
|
|
58
|
+
tier = "MEDIUM";
|
|
59
|
+
} else if (el.role === "heading") {
|
|
60
|
+
rationale = `Heading appeared after action: "${el.accessibleName}"`;
|
|
61
|
+
tier = "MEDIUM";
|
|
62
|
+
} else {
|
|
63
|
+
rationale = `${el.role} live region appeared: "${el.accessibleName}"`;
|
|
64
|
+
tier = "LOW";
|
|
65
|
+
}
|
|
66
|
+
results.push({ code, rationale, tier });
|
|
67
|
+
}
|
|
68
|
+
for (const el of delta.elementsRemoved) {
|
|
69
|
+
if (!el.accessibleName.trim()) continue;
|
|
70
|
+
const escapedName = escapeForSingleQuote(el.accessibleName);
|
|
71
|
+
const code = `await expect(page.getByRole('${el.role}', { name: '${escapedName}' })).not.toBeVisible();`;
|
|
72
|
+
let tier;
|
|
73
|
+
if (INTERACTIVE_ROLES.has(el.role)) {
|
|
74
|
+
tier = "MEDIUM";
|
|
75
|
+
} else {
|
|
76
|
+
tier = "LOW";
|
|
77
|
+
}
|
|
78
|
+
results.push({
|
|
79
|
+
code,
|
|
80
|
+
rationale: `Element removed from DOM after action: ${el.role} "${el.accessibleName}"`,
|
|
81
|
+
tier
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
for (const tc of delta.textChanges) {
|
|
85
|
+
if (!tc.before.trim() && !tc.after.trim()) continue;
|
|
86
|
+
if (!tc.accessibleName.trim()) continue;
|
|
87
|
+
const escapedAccessibleName = escapeForSingleQuote(tc.accessibleName);
|
|
88
|
+
const escapedAfter = escapeForSingleQuote(tc.after);
|
|
89
|
+
const code = `await expect(page.getByRole('${tc.role}', { name: '${escapedAccessibleName}' })).toHaveText('${escapedAfter}');`;
|
|
90
|
+
results.push({
|
|
91
|
+
code,
|
|
92
|
+
rationale: `Text changed: "${tc.before}" \u2192 "${tc.after}"`,
|
|
93
|
+
tier: "HIGH"
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
for (const rc of delta.repeatingCountChanges) {
|
|
97
|
+
if (rc.before === rc.after) continue;
|
|
98
|
+
if (!rc.accessibleNameTemplate.trim()) continue;
|
|
99
|
+
const regexPattern = templateToRegex(rc.accessibleNameTemplate);
|
|
100
|
+
const code = `await expect(page.getByRole('${rc.role}', { name: ${regexPattern} })).toHaveCount(${rc.after});`;
|
|
101
|
+
results.push({
|
|
102
|
+
code,
|
|
103
|
+
rationale: `Repeating element count changed: ${rc.before} \u2192 ${rc.after}`,
|
|
104
|
+
tier: "HIGH"
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const tierOrder = { HIGH: 0, MEDIUM: 1, LOW: 2 };
|
|
108
|
+
results.sort((a, b) => tierOrder[a.tier] - tierOrder[b.tier]);
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
function buildFullCaptureAssertions(blueprint) {
|
|
112
|
+
const out = [];
|
|
113
|
+
if (blueprint.url && blueprint.url.trim()) {
|
|
114
|
+
const escapedUrl = escapeForSingleQuote(blueprint.url);
|
|
115
|
+
out.push({
|
|
116
|
+
code: `await expect(page).toHaveURL('${escapedUrl}');`,
|
|
117
|
+
rationale: `Page URL after action: ${blueprint.url}`,
|
|
118
|
+
tier: "HIGH"
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const heading = findFirstHeading(blueprint);
|
|
122
|
+
if (heading) {
|
|
123
|
+
const escapedName = escapeForSingleQuote(heading);
|
|
124
|
+
out.push({
|
|
125
|
+
code: `await expect(page.getByRole('heading', { name: '${escapedName}' })).toBeVisible();`,
|
|
126
|
+
rationale: `Heading visible on destination page: "${heading}"`,
|
|
127
|
+
tier: "LOW"
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
function findFirstHeading(blueprint) {
|
|
133
|
+
for (const section of blueprint.sections) {
|
|
134
|
+
for (const el of section.elements) {
|
|
135
|
+
if (el.role === "heading" && el.accessibleName.trim())
|
|
136
|
+
return el.accessibleName;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
function templateToRegex(template) {
|
|
142
|
+
let pattern = template.replace(/[.+*?^$()|[\]\\]/g, "\\$&");
|
|
143
|
+
pattern = pattern.replace(/\{[a-zA-Z0-9_]+\}/g, ".+");
|
|
144
|
+
return `/^${pattern}$/i`;
|
|
145
|
+
}
|
|
146
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
147
|
+
0 && (module.exports = {
|
|
148
|
+
buildFullCaptureAssertions,
|
|
149
|
+
buildPossibleAssertions
|
|
150
|
+
});
|