@skyramp/mcp 0.2.0-rc.2 → 0.2.0-rc.3
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/prompts/testbot/testbot-prompts.js +9 -1
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +6 -5
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +19 -2
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +47 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.js +26 -19
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.test.js +125 -2
- package/node_modules/playwright/lib/mcp/browser/tab.js +1 -1
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz +0 -0
- package/package.json +3 -3
|
@@ -78,7 +78,15 @@ ${buildDriftAnalysisPrompt({ existingTests: [], scannedEndpoints: [], repository
|
|
|
78
78
|
- Incorrect arithmetic in business logic (discount calculations, price aggregation)
|
|
79
79
|
Log each finding in \`issuesFound\` with a \`severity\` (critical/high/medium/low). These bugs should inform your test design in Task 2.
|
|
80
80
|
|
|
81
|
-
5. **
|
|
81
|
+
5. **Blueprint Citation Invariant** (UI test recommendations only). When step 2 returned recommendations grounded in the captured blueprints from step 1, every named UI element your recommendation \`reasoning\` mentions — heading text, button label, link text, role descriptions — must correspond to an element actually present in one of those captured blueprints.
|
|
82
|
+
|
|
83
|
+
Write the \`reasoning\` field in **natural prose** that names the elements as a human would describe them ("the Notifications heading", "the disabled Mark all as read button"). Do NOT use internal-identifier syntax like \`role=button, logicalName=...\` — that jargon leaks builder internals into a user-facing report.
|
|
84
|
+
|
|
85
|
+
Self-check before submitting: for each UI recommendation's \`reasoning\`, every element you mention by name should appear in one of the captured blueprints. If an element name doesn't appear in any blueprint, either rewrite the reasoning around an element that IS captured, or drop the element reference and describe the test target in higher-level terms ("the empty state of the notifications page"). Do not invent element names from the PR description, source diff, or component name.
|
|
86
|
+
|
|
87
|
+
**Non-UI entries (contract / integration / e2e / batch-scenario) are unaffected.** Their \`reasoning\` fields use the pre-existing formats — endpoint paths, request/response schemas, fixture chains. Do not reformat them.
|
|
88
|
+
|
|
89
|
+
**No upstream captures available?** If step 1 produced no candidate URLs or \`browser_blueprint\` failed on every candidate, all UI recommendations fall back to source-grounded prose drawn from the diff alone. Log the failure mode once in \`issuesFound\`. Non-UI work is unaffected.
|
|
82
90
|
|
|
83
91
|
---`;
|
|
84
92
|
const serviceContext = services?.length ? buildServiceContext(services) : '';
|
|
@@ -278,11 +278,12 @@ async function domEvaluationScript() {
|
|
|
278
278
|
"option"
|
|
279
279
|
]);
|
|
280
280
|
function findDescendantIconName(el) {
|
|
281
|
-
const descendants = el.querySelectorAll(
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
const descendants = el.querySelectorAll(
|
|
282
|
+
'[data-icon], svg > title, use[href*="#icon-"], use[*|href*="#icon-"]'
|
|
283
|
+
);
|
|
284
|
+
const cap = Math.min(descendants.length, 6);
|
|
285
|
+
for (let i = 0; i < cap; i++) {
|
|
286
|
+
const d = descendants[i];
|
|
286
287
|
const dataIcon = d.getAttribute("data-icon");
|
|
287
288
|
if (dataIcon && dataIcon.trim())
|
|
288
289
|
return dataIcon.trim();
|
|
@@ -18,9 +18,24 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
18
18
|
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
19
|
var blueprintCache_exports = {};
|
|
20
20
|
__export(blueprintCache_exports, {
|
|
21
|
-
BlueprintCache: () => BlueprintCache
|
|
21
|
+
BlueprintCache: () => BlueprintCache,
|
|
22
|
+
DEFAULT_BLUEPRINT_CACHE_SIZE: () => DEFAULT_BLUEPRINT_CACHE_SIZE,
|
|
23
|
+
resolveBlueprintCacheSize: () => resolveBlueprintCacheSize
|
|
22
24
|
});
|
|
23
25
|
module.exports = __toCommonJS(blueprintCache_exports);
|
|
26
|
+
const DEFAULT_BLUEPRINT_CACHE_SIZE = 10;
|
|
27
|
+
function resolveBlueprintCacheSize() {
|
|
28
|
+
const raw = process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE;
|
|
29
|
+
if (raw === void 0 || raw === "") return DEFAULT_BLUEPRINT_CACHE_SIZE;
|
|
30
|
+
const parsed = Number.parseInt(raw, 10);
|
|
31
|
+
if (!Number.isFinite(parsed) || parsed < 1) {
|
|
32
|
+
console.warn(
|
|
33
|
+
`SKYRAMP_BLUEPRINT_CACHE_SIZE=${raw} is invalid (expected positive integer); using default ${DEFAULT_BLUEPRINT_CACHE_SIZE}`
|
|
34
|
+
);
|
|
35
|
+
return DEFAULT_BLUEPRINT_CACHE_SIZE;
|
|
36
|
+
}
|
|
37
|
+
return parsed;
|
|
38
|
+
}
|
|
24
39
|
class BlueprintCache {
|
|
25
40
|
constructor(max) {
|
|
26
41
|
this.map = /* @__PURE__ */ new Map();
|
|
@@ -53,5 +68,7 @@ class BlueprintCache {
|
|
|
53
68
|
}
|
|
54
69
|
// Annotate the CommonJS export names for ESM import in node:
|
|
55
70
|
0 && (module.exports = {
|
|
56
|
-
BlueprintCache
|
|
71
|
+
BlueprintCache,
|
|
72
|
+
DEFAULT_BLUEPRINT_CACHE_SIZE,
|
|
73
|
+
resolveBlueprintCacheSize
|
|
57
74
|
});
|
|
@@ -55,3 +55,50 @@ function bp(url, pageHash) {
|
|
|
55
55
|
(0, import_vitest.expect)(c.get("http://a/")).toBeUndefined();
|
|
56
56
|
});
|
|
57
57
|
});
|
|
58
|
+
(0, import_vitest.describe)("resolveBlueprintCacheSize", () => {
|
|
59
|
+
let originalEnv;
|
|
60
|
+
let warnSpy;
|
|
61
|
+
(0, import_vitest.beforeEach)(() => {
|
|
62
|
+
originalEnv = process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE;
|
|
63
|
+
warnSpy = import_vitest.vi.spyOn(console, "warn").mockImplementation(() => {
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
(0, import_vitest.afterEach)(() => {
|
|
67
|
+
if (originalEnv === void 0)
|
|
68
|
+
delete process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE;
|
|
69
|
+
else
|
|
70
|
+
process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE = originalEnv;
|
|
71
|
+
warnSpy.mockRestore();
|
|
72
|
+
});
|
|
73
|
+
(0, import_vitest.it)("returns the default when env var is unset", () => {
|
|
74
|
+
delete process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE;
|
|
75
|
+
(0, import_vitest.expect)((0, import_blueprintCache.resolveBlueprintCacheSize)()).toBe(import_blueprintCache.DEFAULT_BLUEPRINT_CACHE_SIZE);
|
|
76
|
+
});
|
|
77
|
+
(0, import_vitest.it)("returns the default when env var is empty", () => {
|
|
78
|
+
process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE = "";
|
|
79
|
+
(0, import_vitest.expect)((0, import_blueprintCache.resolveBlueprintCacheSize)()).toBe(import_blueprintCache.DEFAULT_BLUEPRINT_CACHE_SIZE);
|
|
80
|
+
});
|
|
81
|
+
(0, import_vitest.it)("parses a valid positive integer override", () => {
|
|
82
|
+
process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE = "25";
|
|
83
|
+
(0, import_vitest.expect)((0, import_blueprintCache.resolveBlueprintCacheSize)()).toBe(25);
|
|
84
|
+
});
|
|
85
|
+
(0, import_vitest.it)("parses minimum value 1", () => {
|
|
86
|
+
process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE = "1";
|
|
87
|
+
(0, import_vitest.expect)((0, import_blueprintCache.resolveBlueprintCacheSize)()).toBe(1);
|
|
88
|
+
});
|
|
89
|
+
(0, import_vitest.it)("warns and falls back to default for non-numeric values", () => {
|
|
90
|
+
process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE = "not-a-number";
|
|
91
|
+
(0, import_vitest.expect)((0, import_blueprintCache.resolveBlueprintCacheSize)()).toBe(import_blueprintCache.DEFAULT_BLUEPRINT_CACHE_SIZE);
|
|
92
|
+
(0, import_vitest.expect)(warnSpy).toHaveBeenCalledOnce();
|
|
93
|
+
});
|
|
94
|
+
(0, import_vitest.it)("warns and falls back to default for zero", () => {
|
|
95
|
+
process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE = "0";
|
|
96
|
+
(0, import_vitest.expect)((0, import_blueprintCache.resolveBlueprintCacheSize)()).toBe(import_blueprintCache.DEFAULT_BLUEPRINT_CACHE_SIZE);
|
|
97
|
+
(0, import_vitest.expect)(warnSpy).toHaveBeenCalledOnce();
|
|
98
|
+
});
|
|
99
|
+
(0, import_vitest.it)("warns and falls back to default for negative values", () => {
|
|
100
|
+
process.env.SKYRAMP_BLUEPRINT_CACHE_SIZE = "-5";
|
|
101
|
+
(0, import_vitest.expect)((0, import_blueprintCache.resolveBlueprintCacheSize)()).toBe(import_blueprintCache.DEFAULT_BLUEPRINT_CACHE_SIZE);
|
|
102
|
+
(0, import_vitest.expect)(warnSpy).toHaveBeenCalledOnce();
|
|
103
|
+
});
|
|
104
|
+
});
|
|
@@ -37,11 +37,19 @@ const INTERACTIVE_ROLES = /* @__PURE__ */ new Set([
|
|
|
37
37
|
"dialog",
|
|
38
38
|
"alertdialog"
|
|
39
39
|
]);
|
|
40
|
+
const LIVE_REGION_ROLES = /* @__PURE__ */ new Set([
|
|
41
|
+
"alert",
|
|
42
|
+
"status",
|
|
43
|
+
"log",
|
|
44
|
+
"marquee",
|
|
45
|
+
"timer"
|
|
46
|
+
]);
|
|
40
47
|
function escapeForSingleQuote(s) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return
|
|
48
|
+
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/[\r\n]+/g, " ");
|
|
49
|
+
}
|
|
50
|
+
function truncateForDisplay(s, max = 80) {
|
|
51
|
+
if (s.length <= max) return s;
|
|
52
|
+
return s.slice(0, max - 1) + "\u2026";
|
|
45
53
|
}
|
|
46
54
|
function buildPossibleAssertions(delta) {
|
|
47
55
|
const results = [];
|
|
@@ -53,14 +61,18 @@ function buildPossibleAssertions(delta) {
|
|
|
53
61
|
const code = `await expect(page.getByRole('${el.role}', { name: '${escapedName}' })).toBeVisible();`;
|
|
54
62
|
let rationale;
|
|
55
63
|
let tier;
|
|
64
|
+
const displayName = truncateForDisplay(el.accessibleName);
|
|
56
65
|
if (INTERACTIVE_ROLES.has(el.role)) {
|
|
57
|
-
rationale = `Element added to DOM after action: ${el.role} "${
|
|
66
|
+
rationale = `Element added to DOM after action: ${el.role} "${displayName}"`;
|
|
58
67
|
tier = "MEDIUM";
|
|
59
68
|
} else if (el.role === "heading") {
|
|
60
|
-
rationale = `Heading appeared after action: "${
|
|
69
|
+
rationale = `Heading appeared after action: "${displayName}"`;
|
|
61
70
|
tier = "MEDIUM";
|
|
71
|
+
} else if (LIVE_REGION_ROLES.has(el.role)) {
|
|
72
|
+
rationale = `${el.role} live region appeared: "${displayName}"`;
|
|
73
|
+
tier = "LOW";
|
|
62
74
|
} else {
|
|
63
|
-
rationale = `${el.role}
|
|
75
|
+
rationale = `${el.role} appeared after action: "${displayName}"`;
|
|
64
76
|
tier = "LOW";
|
|
65
77
|
}
|
|
66
78
|
results.push({ code, rationale, tier });
|
|
@@ -69,15 +81,10 @@ function buildPossibleAssertions(delta) {
|
|
|
69
81
|
if (!el.accessibleName.trim()) continue;
|
|
70
82
|
const escapedName = escapeForSingleQuote(el.accessibleName);
|
|
71
83
|
const code = `await expect(page.getByRole('${el.role}', { name: '${escapedName}' })).not.toBeVisible();`;
|
|
72
|
-
|
|
73
|
-
if (INTERACTIVE_ROLES.has(el.role)) {
|
|
74
|
-
tier = "MEDIUM";
|
|
75
|
-
} else {
|
|
76
|
-
tier = "LOW";
|
|
77
|
-
}
|
|
84
|
+
const tier = INTERACTIVE_ROLES.has(el.role) ? "MEDIUM" : "LOW";
|
|
78
85
|
results.push({
|
|
79
86
|
code,
|
|
80
|
-
rationale: `Element removed from DOM after action: ${el.role} "${el.accessibleName}"`,
|
|
87
|
+
rationale: `Element removed from DOM after action: ${el.role} "${truncateForDisplay(el.accessibleName)}"`,
|
|
81
88
|
tier
|
|
82
89
|
});
|
|
83
90
|
}
|
|
@@ -89,15 +96,15 @@ function buildPossibleAssertions(delta) {
|
|
|
89
96
|
const code = `await expect(page.getByRole('${tc.role}', { name: '${escapedAccessibleName}' })).toHaveText('${escapedAfter}');`;
|
|
90
97
|
results.push({
|
|
91
98
|
code,
|
|
92
|
-
rationale: `Text changed: "${tc.before}" \u2192 "${tc.after}"`,
|
|
99
|
+
rationale: `Text changed: "${truncateForDisplay(tc.before)}" \u2192 "${truncateForDisplay(tc.after)}"`,
|
|
93
100
|
tier: "HIGH"
|
|
94
101
|
});
|
|
95
102
|
}
|
|
96
103
|
for (const rc of delta.repeatingCountChanges) {
|
|
97
104
|
if (rc.before === rc.after) continue;
|
|
98
105
|
if (!rc.accessibleNameTemplate.trim()) continue;
|
|
99
|
-
const
|
|
100
|
-
const code = `await expect(page.getByRole('${rc.role}', { name: ${
|
|
106
|
+
const regexExpr = templateToRegex(rc.accessibleNameTemplate);
|
|
107
|
+
const code = `await expect(page.getByRole('${rc.role}', { name: ${regexExpr} })).toHaveCount(${rc.after});`;
|
|
101
108
|
results.push({
|
|
102
109
|
code,
|
|
103
110
|
rationale: `Repeating element count changed: ${rc.before} \u2192 ${rc.after}`,
|
|
@@ -139,9 +146,9 @@ function findFirstHeading(blueprint) {
|
|
|
139
146
|
return null;
|
|
140
147
|
}
|
|
141
148
|
function templateToRegex(template) {
|
|
142
|
-
let pattern = template.replace(/[.+*?^$()|[\]
|
|
149
|
+
let pattern = template.replace(/[.+*?^$()|[\]\\\/]/g, "\\$&");
|
|
143
150
|
pattern = pattern.replace(/\{[a-zA-Z0-9_]+\}/g, ".+");
|
|
144
|
-
return
|
|
151
|
+
return `new RegExp('${escapeForSingleQuote(`^${pattern}$`)}', 'i')`;
|
|
145
152
|
}
|
|
146
153
|
// Annotate the CommonJS export names for ESM import in node:
|
|
147
154
|
0 && (module.exports = {
|
|
@@ -104,8 +104,8 @@ test("repeatingCountChanges 12 \u2192 13 \u2192 toHaveCount(13), HIGH", () => {
|
|
|
104
104
|
assertEqual(assertions.length, 1);
|
|
105
105
|
if (!assertions[0].code.includes("toHaveCount(13)"))
|
|
106
106
|
throw new Error("code should include toHaveCount(13)");
|
|
107
|
-
if (!assertions[0].code.includes(
|
|
108
|
-
throw new Error("code should include
|
|
107
|
+
if (!assertions[0].code.includes(`new RegExp('^View details for order .+$', 'i')`))
|
|
108
|
+
throw new Error("code should include RegExp constructor with template-derived pattern");
|
|
109
109
|
assertEqual(assertions[0].tier, "HIGH");
|
|
110
110
|
if (!assertions[0].rationale.includes("12") || !assertions[0].rationale.includes("13"))
|
|
111
111
|
throw new Error("rationale should mention before and after counts");
|
|
@@ -450,6 +450,129 @@ test("Full capture escapes single-quotes in URL and heading", () => {
|
|
|
450
450
|
assertEqual(assertions[0].code.includes(`\\'`), true);
|
|
451
451
|
assertEqual(assertions[1].code.includes(`\\'`), true);
|
|
452
452
|
});
|
|
453
|
+
test("long accessibleName is preserved in generated code, not truncated", () => {
|
|
454
|
+
const longName = "a".repeat(150);
|
|
455
|
+
const delta = {
|
|
456
|
+
hasStructuralChange: true,
|
|
457
|
+
sectionsAdded: [],
|
|
458
|
+
sectionsRemoved: [],
|
|
459
|
+
elementsAdded: [{
|
|
460
|
+
logicalName: "long_btn",
|
|
461
|
+
sectionLogicalName: "main",
|
|
462
|
+
role: "button",
|
|
463
|
+
accessibleName: longName
|
|
464
|
+
}],
|
|
465
|
+
elementsRemoved: [],
|
|
466
|
+
repeatingCountChanges: [],
|
|
467
|
+
repeatingItemsChanged: [],
|
|
468
|
+
textChanges: [],
|
|
469
|
+
enrichmentChanges: []
|
|
470
|
+
};
|
|
471
|
+
const assertions = (0, import_possibleAssertions.buildPossibleAssertions)(delta);
|
|
472
|
+
assertEqual(assertions.length, 1);
|
|
473
|
+
if (!assertions[0].code.includes(longName))
|
|
474
|
+
throw new Error("full accessibleName must appear in generated code");
|
|
475
|
+
if (assertions[0].rationale.length > 200)
|
|
476
|
+
throw new Error("rationale should be truncated for display");
|
|
477
|
+
});
|
|
478
|
+
test("long text-change values preserved in toHaveText, truncated in rationale", () => {
|
|
479
|
+
const longAfter = "b".repeat(120);
|
|
480
|
+
const delta = {
|
|
481
|
+
hasStructuralChange: false,
|
|
482
|
+
sectionsAdded: [],
|
|
483
|
+
sectionsRemoved: [],
|
|
484
|
+
elementsAdded: [],
|
|
485
|
+
elementsRemoved: [],
|
|
486
|
+
repeatingCountChanges: [],
|
|
487
|
+
repeatingItemsChanged: [],
|
|
488
|
+
textChanges: [{
|
|
489
|
+
logicalName: "msg",
|
|
490
|
+
sectionLogicalName: "main",
|
|
491
|
+
role: "status",
|
|
492
|
+
accessibleName: "Status",
|
|
493
|
+
before: "short",
|
|
494
|
+
after: longAfter
|
|
495
|
+
}],
|
|
496
|
+
enrichmentChanges: []
|
|
497
|
+
};
|
|
498
|
+
const assertions = (0, import_possibleAssertions.buildPossibleAssertions)(delta);
|
|
499
|
+
assertEqual(assertions.length, 1);
|
|
500
|
+
if (!assertions[0].code.includes(longAfter))
|
|
501
|
+
throw new Error("full after-text must appear in toHaveText() argument");
|
|
502
|
+
});
|
|
503
|
+
test("templateToRegex emits new RegExp(...) so slashes in the template do not break the literal", () => {
|
|
504
|
+
const delta = {
|
|
505
|
+
hasStructuralChange: true,
|
|
506
|
+
sectionsAdded: [],
|
|
507
|
+
sectionsRemoved: [],
|
|
508
|
+
elementsAdded: [],
|
|
509
|
+
elementsRemoved: [],
|
|
510
|
+
repeatingCountChanges: [{
|
|
511
|
+
logicalName: "page_link",
|
|
512
|
+
sectionLogicalName: "main",
|
|
513
|
+
role: "link",
|
|
514
|
+
accessibleNameTemplate: "A/B page {n}",
|
|
515
|
+
before: 0,
|
|
516
|
+
after: 3,
|
|
517
|
+
delta: 3
|
|
518
|
+
}],
|
|
519
|
+
repeatingItemsChanged: [],
|
|
520
|
+
textChanges: [],
|
|
521
|
+
enrichmentChanges: []
|
|
522
|
+
};
|
|
523
|
+
const assertions = (0, import_possibleAssertions.buildPossibleAssertions)(delta);
|
|
524
|
+
assertEqual(assertions.length, 1);
|
|
525
|
+
if (!assertions[0].code.includes(`new RegExp('^A\\\\/B page .+$', 'i')`))
|
|
526
|
+
throw new Error(
|
|
527
|
+
"code should use new RegExp(...) constructor with escaped slash; got:\n" + assertions[0].code
|
|
528
|
+
);
|
|
529
|
+
});
|
|
530
|
+
test('non-interactive non-heading roles get role-agnostic rationale (not "live region")', () => {
|
|
531
|
+
const delta = {
|
|
532
|
+
hasStructuralChange: true,
|
|
533
|
+
sectionsAdded: [],
|
|
534
|
+
sectionsRemoved: [],
|
|
535
|
+
elementsAdded: [{
|
|
536
|
+
logicalName: "embed",
|
|
537
|
+
sectionLogicalName: "main",
|
|
538
|
+
role: "figure",
|
|
539
|
+
accessibleName: "Embedded preview"
|
|
540
|
+
}],
|
|
541
|
+
elementsRemoved: [],
|
|
542
|
+
repeatingCountChanges: [],
|
|
543
|
+
repeatingItemsChanged: [],
|
|
544
|
+
textChanges: [],
|
|
545
|
+
enrichmentChanges: []
|
|
546
|
+
};
|
|
547
|
+
const assertions = (0, import_possibleAssertions.buildPossibleAssertions)(delta);
|
|
548
|
+
assertEqual(assertions.length, 1);
|
|
549
|
+
if (assertions[0].rationale.includes("live region"))
|
|
550
|
+
throw new Error('rationale should not call non-live-region roles "live region"');
|
|
551
|
+
if (!assertions[0].rationale.startsWith("figure appeared"))
|
|
552
|
+
throw new Error('rationale should be role-agnostic ("figure appeared..."); got: ' + assertions[0].rationale);
|
|
553
|
+
});
|
|
554
|
+
test('genuine live-region roles (status/alert) keep the "live region" rationale', () => {
|
|
555
|
+
const delta = {
|
|
556
|
+
hasStructuralChange: true,
|
|
557
|
+
sectionsAdded: [],
|
|
558
|
+
sectionsRemoved: [],
|
|
559
|
+
elementsAdded: [{
|
|
560
|
+
logicalName: "toast",
|
|
561
|
+
sectionLogicalName: "main",
|
|
562
|
+
role: "status",
|
|
563
|
+
accessibleName: "Saved"
|
|
564
|
+
}],
|
|
565
|
+
elementsRemoved: [],
|
|
566
|
+
repeatingCountChanges: [],
|
|
567
|
+
repeatingItemsChanged: [],
|
|
568
|
+
textChanges: [],
|
|
569
|
+
enrichmentChanges: []
|
|
570
|
+
};
|
|
571
|
+
const assertions = (0, import_possibleAssertions.buildPossibleAssertions)(delta);
|
|
572
|
+
assertEqual(assertions.length, 1);
|
|
573
|
+
if (!assertions[0].rationale.includes("live region"))
|
|
574
|
+
throw new Error('status role should keep the "live region" rationale; got: ' + assertions[0].rationale);
|
|
575
|
+
});
|
|
453
576
|
let passed = 0;
|
|
454
577
|
let failed = 0;
|
|
455
578
|
const failures = [];
|
|
@@ -59,7 +59,7 @@ class Tab extends import_events.EventEmitter {
|
|
|
59
59
|
* same URL instead of the full payload. Cleared on tab close (not on
|
|
60
60
|
* navigation — same-URL revisits should reuse the prior blueprint).
|
|
61
61
|
*/
|
|
62
|
-
this.blueprintCache = new import_blueprintCache.BlueprintCache(
|
|
62
|
+
this.blueprintCache = new import_blueprintCache.BlueprintCache((0, import_blueprintCache.resolveBlueprintCacheSize)());
|
|
63
63
|
this.context = context;
|
|
64
64
|
this.page = page;
|
|
65
65
|
this._onPageClose = onPageClose;
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@skyramp/mcp",
|
|
3
|
-
"version": "0.2.0-rc.
|
|
3
|
+
"version": "0.2.0-rc.3",
|
|
4
4
|
"main": "build/index.js",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./build/index.js",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"js-yaml": "^4.1.1",
|
|
62
62
|
"playwright": "file:vendor/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz",
|
|
63
63
|
"simple-git": "^3.30.0",
|
|
64
|
+
"typescript": "^5.8.3",
|
|
64
65
|
"zod": "^3.25.3"
|
|
65
66
|
},
|
|
66
67
|
"devDependencies": {
|
|
@@ -73,8 +74,7 @@
|
|
|
73
74
|
"@typescript-eslint/parser": "^8.0.0",
|
|
74
75
|
"eslint": "^9.0.0",
|
|
75
76
|
"jest": "^29.7.0",
|
|
76
|
-
"ts-jest": "^29.3.4"
|
|
77
|
-
"typescript": "^5.8.3"
|
|
77
|
+
"ts-jest": "^29.3.4"
|
|
78
78
|
},
|
|
79
79
|
"engines": {
|
|
80
80
|
"node": ">=18"
|