synapse-hub 0.0.0-stage → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/agents/failure-analyst.md +47 -0
- package/.agents/agents/live-explorer.md +41 -0
- package/.agents/agents/orchestrator.md +43 -0
- package/.agents/agents/pom-crafter.md +81 -0
- package/.agents/agents/quality-gatekeeper.md +47 -0
- package/.agents/agents/test-runner.md +43 -0
- package/.agents/rules/context-engineering.md +88 -0
- package/.agents/rules/git-push-protocol.md +101 -0
- package/.agents/rules/graphify.md +14 -0
- package/.agents/rules/lessons-learned.md +23 -0
- package/.agents/rules/mandatory-playwright-live.md +29 -0
- package/.agents/rules/skill-precedence.md +42 -0
- package/.agents/rules/systematic-debugging.md +99 -0
- package/.agents/skills/agent-orchestration-multi-agent-optimize/SKILL.md +239 -0
- package/.agents/skills/antigravity-test-sentinel/SKILL.md +64 -0
- package/.agents/skills/api-network-mocking-interception/SKILL.md +142 -0
- package/.agents/skills/api-spec-to-e2e-matrix/SKILL.md +70 -0
- package/.agents/skills/architecture-decision-records/SKILL.md +441 -0
- package/.agents/skills/auth-session-and-cookie-manager/SKILL.md +72 -0
- package/.agents/skills/backend-load-and-stress-patterns/SKILL.md +214 -0
- package/.agents/skills/bats-testing-patterns/SKILL.md +100 -0
- package/.agents/skills/bats-testing-patterns/resources/implementation-playbook.md +614 -0
- package/.agents/skills/clean-pom-crafter/SKILL.md +75 -0
- package/.agents/skills/clean-pom-crafter/examples/standard_test_case.groovy +28 -0
- package/.agents/skills/clean-pom-crafter/references/pom_patterns.groovy +138 -0
- package/.agents/skills/code-refactoring-refactor-clean/SKILL.md +64 -0
- package/.agents/skills/code-refactoring-refactor-clean/resources/implementation-playbook.md +879 -0
- package/.agents/skills/db-state-verifier-and-seeder/SKILL.md +123 -0
- package/.agents/skills/e2e-testing-patterns/SKILL.md +103 -0
- package/.agents/skills/e2e-testing-patterns/resources/implementation-playbook.md +522 -0
- package/.agents/skills/failure-autopsy-and-healing/SKILL.md +54 -0
- package/.agents/skills/failure-autopsy-and-healing/references/healing_matrix.md +64 -0
- package/.agents/skills/failure-autopsy-and-healing/references/qa_investigation_protocol.md +56 -0
- package/.agents/skills/failure-autopsy-and-healing/scripts/triage_failures.sh +98 -0
- package/.agents/skills/file-and-report-validator/SKILL.md +90 -0
- package/.agents/skills/flaky-test-sentinel-and-quarantine/SKILL.md +114 -0
- package/.agents/skills/github-actions-templates/SKILL.md +334 -0
- package/.agents/skills/graphify-impact-analyzer/SKILL.md +78 -0
- package/.agents/skills/katalon-cli-runner/SKILL.md +76 -0
- package/.agents/skills/katalon-cli-runner/references/groovy-patterns.md +209 -0
- package/.agents/skills/katalon-cli-runner/references/katalon-api.md +155 -0
- package/.agents/skills/katalon-cli-runner/references/runner_commands.md +67 -0
- package/.agents/skills/lam-script-generator/SKILL.md +83 -0
- package/.agents/skills/playwright-live-interaction/SKILL.md +86 -0
- package/.agents/skills/playwright-live-interaction/references/advanced_superpowers.md +160 -0
- package/.agents/skills/playwright-live-interaction/references/compact_accessibility_snapshots.md +75 -0
- package/.agents/skills/playwright-live-interaction/references/recipes.md +388 -0
- package/.agents/skills/playwright-live-interaction/references/swarm_orchestration.md +134 -0
- package/.agents/skills/security-dast-sentinel/SKILL.md +176 -0
- package/.agents/skills/self-healing-locator/SKILL.md +73 -0
- package/.agents/skills/synthetic-test-data-generator/SKILL.md +135 -0
- package/.agents/skills/token-and-context-optimizer/SKILL.md +70 -0
- package/.agents/skills/visual-regression-sentinel/SKILL.md +62 -0
- package/.agents/skills/wcag-audit-patterns/SKILL.md +77 -0
- package/.agents/skills/wcag-audit-patterns/resources/implementation-playbook.md +541 -0
- package/.agents/skills/web-vitals-and-performance-profiler/SKILL.md +65 -0
- package/.agents/templates/completion_report.md +57 -0
- package/.agents/workflows/graphify.md +10 -0
- package/.agents/workflows/test-impact.md +15 -0
- package/.agents/workflows/verify.md +34 -0
- package/AGENTS.md +115 -0
- package/README.md +537 -2
- package/bin/agent-qa.js +90 -0
- package/package.json +59 -4
- package/skills_manifest.json +262 -0
- package/templates/PROJECT_AI_GUIDE_TEMPLATE.md +97 -0
- package/templates/PROJECT_STATE_TEMPLATE.md +20 -0
- package/tools/TOOLS_RUNBOOK.md +298 -0
- package/tools/agent_canary.py +501 -0
- package/tools/analyze_project.py +242 -0
- package/tools/api_drift_sentinel.py +208 -0
- package/tools/auto_heal.py +189 -0
- package/tools/browser_helpers.js +593 -0
- package/tools/bug_reporter.py +224 -0
- package/tools/cache_resolver.py +846 -0
- package/tools/check_env.py +288 -0
- package/tools/ci_healing_hook.py +128 -0
- package/tools/cli.py +402 -0
- package/tools/config_decoupler.py +253 -0
- package/tools/db_sandbox.py +341 -0
- package/tools/distill_log.py +152 -0
- package/tools/e2e_flow_prober.ps1 +65 -0
- package/tools/flaky_detector.py +222 -0
- package/tools/fuzz_payloads.py +197 -0
- package/tools/generate_allure_report.ps1 +30 -0
- package/tools/generate_api_matrix.py +193 -0
- package/tools/generate_tc_from_story.py +182 -0
- package/tools/generate_test_reports.ps1 +135 -0
- package/tools/git_pre_commit_guard.py +203 -0
- package/tools/heal_pr_automator.py +195 -0
- package/tools/heuristic_locator_matcher.py +202 -0
- package/tools/infer_linter.py +238 -0
- package/tools/lint_ai_docs.py +254 -0
- package/tools/lookup_tc.py +165 -0
- package/tools/lookup_xpath.py +167 -0
- package/tools/matrix_sharder.py +146 -0
- package/tools/mcp_excel_server.ps1 +251 -0
- package/tools/mcp_server.py +490 -0
- package/tools/micro_mutator.py +309 -0
- package/tools/network_mock_shield.py +170 -0
- package/tools/omni_emitter.py +500 -0
- package/tools/omni_parser.py +435 -0
- package/tools/omni_transpiler.py +168 -0
- package/tools/pinpoint_failure.py +88 -0
- package/tools/project_state.py +169 -0
- package/tools/repo_map.json +12 -0
- package/tools/repo_map.py +277 -0
- package/tools/repo_map.txt +7 -0
- package/tools/safety_firewall.py +245 -0
- package/tools/setup_project.py +265 -0
- package/tools/smart_visual_masker.py +134 -0
- package/tools/test_impact.py +321 -0
- package/tools/tms_sync_bridge.py +342 -0
- package/tools/trace_data_flow.py +240 -0
- package/tools/traffic_session_converter.py +256 -0
- package/tools/universal_ir.py +480 -0
- package/tools/validate_groovy_scripts.py +173 -0
- package/tools/verify_all.py +496 -0
- package/tools/verify_all.sh +4 -0
- package/tools/visual_diff_canvas.py +142 -0
- package/tools/visual_som_helper.py +425 -0
- package/tools/wcag_audit.py +214 -0
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enterprise Browser Runtime Helper (window.__qa)
|
|
3
|
+
* -------------------------------------------------------------
|
|
4
|
+
* Provides advanced AI testing capabilities directly inside the live browser DOM:
|
|
5
|
+
* 1. Smart Action Batching (qa.batch) - Atomic execution with pause & state preservation.
|
|
6
|
+
* 2. Instant Form Audit (qa.audit) - Detects Angular/React/Vue invalid fields in 1-shot.
|
|
7
|
+
* 3. Self-Healing Locator Engine (qa.heal) - Suggests resilient selectors when locators break.
|
|
8
|
+
* 4. Safe Click & Overlay Bypass (qa.clickSafe) - Clears CDK/PrimeNG/Modal overlays before clicking.
|
|
9
|
+
* 5. Session State Exporter/Importer (qa.exportSession / qa.importSession).
|
|
10
|
+
* 6. Deterministic Sync Engine (qa.isStable / qa.waitForStable / qa.waitForNetworkIdle).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
(function () {
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
if (window.__qa && window.__qa.__version === '2.0.1') {
|
|
17
|
+
return window.__qa;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Active in-flight network request tracker
|
|
21
|
+
let activeNetworkRequests = 0;
|
|
22
|
+
if (!window.__qa_net_hooked) {
|
|
23
|
+
window.__qa_net_hooked = true;
|
|
24
|
+
if (window.fetch) {
|
|
25
|
+
const origFetch = window.fetch;
|
|
26
|
+
window.fetch = function () {
|
|
27
|
+
activeNetworkRequests++;
|
|
28
|
+
return origFetch.apply(window, arguments).finally(() => {
|
|
29
|
+
activeNetworkRequests = Math.max(0, activeNetworkRequests - 1);
|
|
30
|
+
});
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
if (window.XMLHttpRequest) {
|
|
34
|
+
const origOpen = XMLHttpRequest.prototype.open;
|
|
35
|
+
const origSend = XMLHttpRequest.prototype.send;
|
|
36
|
+
XMLHttpRequest.prototype.open = function () {
|
|
37
|
+
this.addEventListener('loadend', () => {
|
|
38
|
+
activeNetworkRequests = Math.max(0, activeNetworkRequests - 1);
|
|
39
|
+
});
|
|
40
|
+
return origOpen.apply(this, arguments);
|
|
41
|
+
};
|
|
42
|
+
XMLHttpRequest.prototype.send = function () {
|
|
43
|
+
activeNetworkRequests++;
|
|
44
|
+
return origSend.apply(this, arguments);
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const qa = {
|
|
50
|
+
__version: '2.0.1',
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 1. Smart Action Batching
|
|
54
|
+
* Runs a sequence of step functions atomically.
|
|
55
|
+
* Halts immediately upon failure without resetting previously filled inputs.
|
|
56
|
+
* @param {Array<{name: string, fn: Function}>} steps
|
|
57
|
+
* @returns {Object} { status: 'SUCCESS' | 'FAILED', failedStep?: number, name?: string, error?: string }
|
|
58
|
+
*/
|
|
59
|
+
batch: function (steps) {
|
|
60
|
+
if (!Array.isArray(steps)) {
|
|
61
|
+
return { status: 'FAILED', error: 'Steps must be an array of {name, fn}' };
|
|
62
|
+
}
|
|
63
|
+
for (let i = 0; i < steps.length; i++) {
|
|
64
|
+
const step = steps[i];
|
|
65
|
+
try {
|
|
66
|
+
if (typeof step.fn === 'function') {
|
|
67
|
+
step.fn();
|
|
68
|
+
} else if (typeof step.action === 'function') {
|
|
69
|
+
step.action();
|
|
70
|
+
} else {
|
|
71
|
+
throw new Error(`Step #${i + 1} (${step.name}) has no runnable function.`);
|
|
72
|
+
}
|
|
73
|
+
} catch (err) {
|
|
74
|
+
return {
|
|
75
|
+
status: 'FAILED',
|
|
76
|
+
failedStep: i + 1,
|
|
77
|
+
stepName: step.name || `Step #${i + 1}`,
|
|
78
|
+
error: err.message,
|
|
79
|
+
preservedStateUrl: window.location.href
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return { status: 'SUCCESS', totalSteps: steps.length };
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 2. Instant Form Audit
|
|
88
|
+
* Finds all invalid fields in active forms (diagnosing disabled Save buttons).
|
|
89
|
+
* Covers Angular Material, PrimeNG, Ant Design, Bootstrap, and HTML5 validation.
|
|
90
|
+
* @returns {Array<Object>} List of invalid controls with metadata
|
|
91
|
+
*/
|
|
92
|
+
audit: function () {
|
|
93
|
+
const invalidNodes = Array.from(
|
|
94
|
+
document.querySelectorAll(
|
|
95
|
+
'form .ng-invalid, input.ng-invalid, mat-form-field.ng-invalid, mat-select.ng-invalid, textarea.ng-invalid, p-dropdown.ng-invalid, :invalid'
|
|
96
|
+
)
|
|
97
|
+
);
|
|
98
|
+
return invalidNodes.map(el => ({
|
|
99
|
+
tag: el.tagName.toLowerCase(),
|
|
100
|
+
control: el.getAttribute('formcontrolname') || el.getAttribute('name') || el.id || 'unnamed',
|
|
101
|
+
placeholder: el.getAttribute('placeholder') || '',
|
|
102
|
+
classes: el.className,
|
|
103
|
+
value: el.value || el.innerText.trim()
|
|
104
|
+
}));
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 3. Self-Healing Locator Engine
|
|
109
|
+
* When a selector returns 0 elements, scans DOM for matching text, label, or placeholder.
|
|
110
|
+
* @param {string} brokenSelector
|
|
111
|
+
* @param {string} [searchText]
|
|
112
|
+
* @returns {Object} Suggested robust selector
|
|
113
|
+
*/
|
|
114
|
+
heal: function (brokenSelector, searchText) {
|
|
115
|
+
const query = (searchText || brokenSelector || '').replace(/['"\\\[\]]/g, '').trim().toLowerCase();
|
|
116
|
+
const candidates = [];
|
|
117
|
+
|
|
118
|
+
// Scan buttons
|
|
119
|
+
document.querySelectorAll('button, a.p-button, span.p-button-text, .mat-mdc-button, [role="button"]').forEach(btn => {
|
|
120
|
+
const txt = btn.innerText.trim().toLowerCase();
|
|
121
|
+
if (query && txt.includes(query)) {
|
|
122
|
+
candidates.push({
|
|
123
|
+
type: 'button',
|
|
124
|
+
text: btn.innerText.trim(),
|
|
125
|
+
suggestedXPath: `//button[contains(normalize-space(.), '${btn.innerText.trim()}')]`,
|
|
126
|
+
score: 0.95
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Scan inputs & form controls
|
|
132
|
+
document.querySelectorAll('input, textarea, mat-select, p-dropdown, select').forEach(inp => {
|
|
133
|
+
const fcn = inp.getAttribute('formcontrolname');
|
|
134
|
+
const ph = inp.getAttribute('placeholder') || '';
|
|
135
|
+
const name = inp.getAttribute('name');
|
|
136
|
+
const testid = inp.getAttribute('data-testid');
|
|
137
|
+
if (testid && testid.toLowerCase().includes(query)) {
|
|
138
|
+
candidates.push({
|
|
139
|
+
type: 'input',
|
|
140
|
+
dataTestId: testid,
|
|
141
|
+
suggestedCss: `[data-testid='${testid}']`,
|
|
142
|
+
score: 0.99
|
|
143
|
+
});
|
|
144
|
+
} else if ((fcn || name) && ((fcn || name).toLowerCase().includes(query) || (query && ph.toLowerCase().includes(query)))) {
|
|
145
|
+
const ctrl = fcn || name;
|
|
146
|
+
candidates.push({
|
|
147
|
+
type: 'input',
|
|
148
|
+
formControlName: fcn,
|
|
149
|
+
name: name,
|
|
150
|
+
placeholder: ph,
|
|
151
|
+
suggestedXPath: `//*[@formcontrolname='${ctrl}']`,
|
|
152
|
+
suggestedCss: `[formcontrolname='${ctrl}']`,
|
|
153
|
+
score: 0.98
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
originalSelector: brokenSelector,
|
|
160
|
+
candidatesFound: candidates.length,
|
|
161
|
+
bestMatch: candidates[0] || null
|
|
162
|
+
};
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 4. Safe Click & Overlay Bypass
|
|
167
|
+
* Dispatches click event directly, bypassing blocking overlays
|
|
168
|
+
* (.cdk-overlay-backdrop, .p-component-overlay, .modal-backdrop).
|
|
169
|
+
* @param {string | HTMLElement} target
|
|
170
|
+
* @returns {boolean}
|
|
171
|
+
*/
|
|
172
|
+
clickSafe: function (target) {
|
|
173
|
+
const el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
174
|
+
if (!el) return false;
|
|
175
|
+
|
|
176
|
+
// Temporarily neutralize blocking overlays if present
|
|
177
|
+
document.querySelectorAll('.cdk-overlay-backdrop, .p-component-overlay, .modal-backdrop').forEach(overlay => {
|
|
178
|
+
overlay.style.pointerEvents = 'none';
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
el.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
182
|
+
el.dispatchEvent(new MouseEvent('mouseover', { bubbles: true }));
|
|
183
|
+
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true }));
|
|
184
|
+
el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true }));
|
|
185
|
+
el.click();
|
|
186
|
+
return true;
|
|
187
|
+
},
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* 5. Session State Exporter
|
|
191
|
+
* Exports tokens and localStorage to bypass UI login (Zero-Second Auth).
|
|
192
|
+
* @returns {Object}
|
|
193
|
+
*/
|
|
194
|
+
exportSession: function () {
|
|
195
|
+
const storage = {};
|
|
196
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
197
|
+
const key = localStorage.key(i);
|
|
198
|
+
storage[key] = localStorage.getItem(key);
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
url: window.location.href,
|
|
202
|
+
cookies: document.cookie,
|
|
203
|
+
localStorage: storage
|
|
204
|
+
};
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Restore session state
|
|
209
|
+
* @param {Object} data
|
|
210
|
+
*/
|
|
211
|
+
importSession: function (data) {
|
|
212
|
+
if (data && data.localStorage) {
|
|
213
|
+
for (const [k, v] of Object.entries(data.localStorage)) {
|
|
214
|
+
localStorage.setItem(k, v);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return { status: 'SESSION_IMPORTED' };
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* 6. Zone.js & Microtask Stability Sync Engine
|
|
222
|
+
* Checks if framework microtasks, promises, and animations are fully settled.
|
|
223
|
+
* @returns {boolean}
|
|
224
|
+
*/
|
|
225
|
+
isStable: function () {
|
|
226
|
+
try {
|
|
227
|
+
if (window.getAllAngularTestabilities) {
|
|
228
|
+
const testabilities = window.getAllAngularTestabilities();
|
|
229
|
+
if (testabilities && testabilities.length > 0) {
|
|
230
|
+
return testabilities.every(t => t.isStable());
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
const hasBlocker = document.querySelector(
|
|
234
|
+
'.p-blockui, .p-progress-spinner, .mat-mdc-progress-bar, .spinner, .loading, .ng-animating'
|
|
235
|
+
);
|
|
236
|
+
return !hasBlocker;
|
|
237
|
+
} catch (e) {
|
|
238
|
+
return true;
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Asynchronously wait for framework to become stable
|
|
244
|
+
* @param {number} [timeoutMs=10000]
|
|
245
|
+
* @returns {Promise<boolean>}
|
|
246
|
+
*/
|
|
247
|
+
waitForStable: function (timeoutMs = 10000) {
|
|
248
|
+
const self = this;
|
|
249
|
+
const start = Date.now();
|
|
250
|
+
return new Promise((resolve) => {
|
|
251
|
+
const interval = setInterval(() => {
|
|
252
|
+
if (self.isStable()) {
|
|
253
|
+
clearInterval(interval);
|
|
254
|
+
resolve(true);
|
|
255
|
+
} else if (Date.now() - start > timeoutMs) {
|
|
256
|
+
clearInterval(interval);
|
|
257
|
+
resolve(false);
|
|
258
|
+
}
|
|
259
|
+
}, 100);
|
|
260
|
+
});
|
|
261
|
+
},
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 7. CDP Network Drain & In-Flight Traffic Monitor (Network Idle 0)
|
|
265
|
+
*/
|
|
266
|
+
getActiveRequests: function () {
|
|
267
|
+
return activeNetworkRequests;
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
waitForNetworkIdle: function (idleTimeMs = 500, timeoutMs = 10000) {
|
|
271
|
+
const start = Date.now();
|
|
272
|
+
let idleStart = null;
|
|
273
|
+
return new Promise((resolve) => {
|
|
274
|
+
const interval = setInterval(() => {
|
|
275
|
+
if (activeNetworkRequests === 0) {
|
|
276
|
+
if (!idleStart) idleStart = Date.now();
|
|
277
|
+
if (Date.now() - idleStart >= idleTimeMs) {
|
|
278
|
+
clearInterval(interval);
|
|
279
|
+
resolve(true);
|
|
280
|
+
}
|
|
281
|
+
} else {
|
|
282
|
+
idleStart = null;
|
|
283
|
+
}
|
|
284
|
+
if (Date.now() - start > timeoutMs) {
|
|
285
|
+
clearInterval(interval);
|
|
286
|
+
resolve(false);
|
|
287
|
+
}
|
|
288
|
+
}, 50);
|
|
289
|
+
});
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 8. Action Safety Firewall (Blast Radius Containment)
|
|
294
|
+
*/
|
|
295
|
+
firewall: {
|
|
296
|
+
DANGEROUS_PATTERNS: /\b(delete|remove|purge|wipe|drop|pay|purchase|checkout|terminate|destroy|transfer|reset)\b/i,
|
|
297
|
+
DANGEROUS_SELECTORS: ['[data-action*="delete"]', '[data-action*="pay"]', '.btn-danger', '.p-button-danger', '[aria-label*="delete" i]'],
|
|
298
|
+
|
|
299
|
+
isDangerous: function (element, actionName = 'click') {
|
|
300
|
+
if (!element) return false;
|
|
301
|
+
const text = (element.innerText || element.textContent || element.value || element.getAttribute('aria-label') || '').trim();
|
|
302
|
+
if (this.DANGEROUS_PATTERNS.test(text)) return true;
|
|
303
|
+
for (const sel of this.DANGEROUS_SELECTORS) {
|
|
304
|
+
if (element.matches && element.matches(sel)) return true;
|
|
305
|
+
if (element.closest && element.closest(sel)) return true;
|
|
306
|
+
}
|
|
307
|
+
return false;
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
checkSafeAction: function (element, actionName = 'click') {
|
|
311
|
+
const dangerous = this.isDangerous(element, actionName);
|
|
312
|
+
if (!dangerous) {
|
|
313
|
+
return { allowed: true, reason: 'ACTION_SAFE' };
|
|
314
|
+
}
|
|
315
|
+
if (window.__ALLOW_DESTRUCTIVE__ === true) {
|
|
316
|
+
return { allowed: true, reason: 'BYPASS_FLAG_ENABLED' };
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
allowed: false,
|
|
320
|
+
reason: 'DANGEROUS_ACTION_BLOCKED',
|
|
321
|
+
message: `Action '${actionName}' on destructive element was blocked by Safety Firewall. Set window.__ALLOW_DESTRUCTIVE__ = true to override.`
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* 9. Multimodal Vision-Grounded Set-of-Marks (SoM) Engine
|
|
328
|
+
* Pierces Shadow DOM and inspects HTML5 Canvas elements for coordinate grounding.
|
|
329
|
+
*/
|
|
330
|
+
som: {
|
|
331
|
+
getInteractiveMarks: function () {
|
|
332
|
+
const marks = [];
|
|
333
|
+
let idCounter = 1;
|
|
334
|
+
|
|
335
|
+
function traverse(root) {
|
|
336
|
+
if (!root || !root.querySelectorAll) return;
|
|
337
|
+
const candidates = root.querySelectorAll('button, a, input, select, textarea, [role="button"], canvas, svg');
|
|
338
|
+
candidates.forEach(el => {
|
|
339
|
+
const rect = el.getBoundingClientRect();
|
|
340
|
+
if (rect.width > 5 && rect.height > 5 && window.getComputedStyle(el).visibility !== 'hidden') {
|
|
341
|
+
const label = el.getAttribute('aria-label') || el.innerText || el.value || el.tagName.toLowerCase();
|
|
342
|
+
marks.push({
|
|
343
|
+
mark_id: idCounter++,
|
|
344
|
+
tag: el.tagName.toLowerCase(),
|
|
345
|
+
label: (label || '').trim().substring(0, 50),
|
|
346
|
+
x: Math.round(rect.left),
|
|
347
|
+
y: Math.round(rect.top),
|
|
348
|
+
width: Math.round(rect.width),
|
|
349
|
+
height: Math.round(rect.height),
|
|
350
|
+
center_x: Math.round(rect.left + rect.width / 2),
|
|
351
|
+
center_y: Math.round(rect.top + rect.height / 2)
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
// Pierce Shadow DOM
|
|
355
|
+
if (el.shadowRoot) {
|
|
356
|
+
traverse(el.shadowRoot);
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
traverse(document);
|
|
362
|
+
return marks;
|
|
363
|
+
},
|
|
364
|
+
|
|
365
|
+
overlayMarks: function () {
|
|
366
|
+
this.clearMarks();
|
|
367
|
+
const container = document.createElement('div');
|
|
368
|
+
container.id = '__qa_som_overlay';
|
|
369
|
+
container.style.position = 'fixed';
|
|
370
|
+
container.style.top = '0';
|
|
371
|
+
container.style.left = '0';
|
|
372
|
+
container.style.width = '100vw';
|
|
373
|
+
container.style.height = '100vh';
|
|
374
|
+
container.style.pointerEvents = 'none';
|
|
375
|
+
container.style.zIndex = '999999';
|
|
376
|
+
|
|
377
|
+
const marks = this.getInteractiveMarks();
|
|
378
|
+
marks.forEach(m => {
|
|
379
|
+
const badge = document.createElement('div');
|
|
380
|
+
badge.style.position = 'absolute';
|
|
381
|
+
badge.style.left = m.x + 'px';
|
|
382
|
+
badge.style.top = Math.max(0, m.y - 18) + 'px';
|
|
383
|
+
badge.style.background = '#FF0055';
|
|
384
|
+
badge.style.color = '#FFFFFF';
|
|
385
|
+
badge.style.fontSize = '12px';
|
|
386
|
+
badge.style.fontWeight = 'bold';
|
|
387
|
+
badge.style.padding = '1px 4px';
|
|
388
|
+
badge.style.borderRadius = '3px';
|
|
389
|
+
badge.innerText = m.mark_id;
|
|
390
|
+
|
|
391
|
+
const box = document.createElement('div');
|
|
392
|
+
box.style.position = 'absolute';
|
|
393
|
+
box.style.left = m.x + 'px';
|
|
394
|
+
box.style.top = m.y + 'px';
|
|
395
|
+
box.style.width = m.width + 'px';
|
|
396
|
+
box.style.height = m.height + 'px';
|
|
397
|
+
box.style.border = '2px solid #FF0055';
|
|
398
|
+
|
|
399
|
+
container.appendChild(box);
|
|
400
|
+
container.appendChild(badge);
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
document.body.appendChild(container);
|
|
404
|
+
return marks;
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
clearMarks: function () {
|
|
408
|
+
const existing = document.getElementById('__qa_som_overlay');
|
|
409
|
+
if (existing) existing.remove();
|
|
410
|
+
}
|
|
411
|
+
},
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 7. Ultra-Compact Accessibility Object Model (AOM) Distillation
|
|
415
|
+
* Filters noise (wrapper divs, styling spans) and returns an ultra-sparse
|
|
416
|
+
* tree representation (Role, Name, State, TestId) capped at maxDepth (default: 3).
|
|
417
|
+
* Prevents context rot / token bloat by condensing thousands of DOM nodes into ~300 tokens.
|
|
418
|
+
* @param {Element|Document} [root=document]
|
|
419
|
+
* @param {number} [maxDepth=3]
|
|
420
|
+
* @returns {Array<{role: string, name: string, state?: Object, testId?: string, depth: number}>}
|
|
421
|
+
*/
|
|
422
|
+
getCompactAOM: function (root = document, maxDepth = 3) {
|
|
423
|
+
const aomNodes = [];
|
|
424
|
+
const INTERACTIVE_ROLES = new Set([
|
|
425
|
+
'button', 'link', 'textbox', 'checkbox', 'radio', 'combobox', 'menuitem',
|
|
426
|
+
'tab', 'switch', 'dialog', 'alert', 'searchbox', 'spinbutton', 'option', 'treeitem'
|
|
427
|
+
]);
|
|
428
|
+
|
|
429
|
+
function getRole(el) {
|
|
430
|
+
const explicitRole = el.getAttribute('role');
|
|
431
|
+
if (explicitRole) return explicitRole.toLowerCase();
|
|
432
|
+
const tag = el.tagName.toLowerCase();
|
|
433
|
+
if (tag === 'button') return 'button';
|
|
434
|
+
if (tag === 'a' && el.hasAttribute('href')) return 'link';
|
|
435
|
+
if (tag === 'input') {
|
|
436
|
+
const type = (el.getAttribute('type') || 'text').toLowerCase();
|
|
437
|
+
if (['checkbox', 'radio', 'button', 'submit'].includes(type)) return type;
|
|
438
|
+
return 'textbox';
|
|
439
|
+
}
|
|
440
|
+
if (tag === 'select') return 'combobox';
|
|
441
|
+
if (tag === 'textarea') return 'textbox';
|
|
442
|
+
return null;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
function getAccessibleName(el) {
|
|
446
|
+
return (
|
|
447
|
+
el.getAttribute('aria-label') ||
|
|
448
|
+
el.getAttribute('aria-placeholder') ||
|
|
449
|
+
el.getAttribute('placeholder') ||
|
|
450
|
+
el.getAttribute('title') ||
|
|
451
|
+
(el.innerText ? el.innerText.trim().slice(0, 50).replace(/\s+/g, ' ') : '') ||
|
|
452
|
+
el.getAttribute('name') ||
|
|
453
|
+
el.getAttribute('value') ||
|
|
454
|
+
''
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function getState(el) {
|
|
459
|
+
const state = {};
|
|
460
|
+
if (el.disabled || el.getAttribute('aria-disabled') === 'true') state.disabled = true;
|
|
461
|
+
if (el.getAttribute('aria-expanded') !== null) state.expanded = el.getAttribute('aria-expanded') === 'true';
|
|
462
|
+
if (el.checked || el.getAttribute('aria-checked') === 'true') state.checked = true;
|
|
463
|
+
if (el.getAttribute('aria-invalid') === 'true') state.invalid = true;
|
|
464
|
+
if (el.getAttribute('aria-selected') === 'true') state.selected = true;
|
|
465
|
+
return Object.keys(state).length > 0 ? state : undefined;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function isVisible(el) {
|
|
469
|
+
if (!el || el.nodeType !== 1) return false;
|
|
470
|
+
const style = window.getComputedStyle(el);
|
|
471
|
+
if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
|
|
472
|
+
const rect = el.getBoundingClientRect();
|
|
473
|
+
return rect.width > 0 && rect.height > 0;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
function walk(node, currentDepth) {
|
|
477
|
+
if (currentDepth > maxDepth || !node) return;
|
|
478
|
+
const children = node.children || [];
|
|
479
|
+
for (let i = 0; i < children.length; i++) {
|
|
480
|
+
const child = children[i];
|
|
481
|
+
if (!isVisible(child)) continue;
|
|
482
|
+
|
|
483
|
+
const role = getRole(child);
|
|
484
|
+
const testId = child.getAttribute('data-testid') || child.getAttribute('data-cy') || child.id || '';
|
|
485
|
+
|
|
486
|
+
if (role && (INTERACTIVE_ROLES.has(role) || testId)) {
|
|
487
|
+
aomNodes.push({
|
|
488
|
+
role: role,
|
|
489
|
+
name: getAccessibleName(child),
|
|
490
|
+
state: getState(child),
|
|
491
|
+
testId: testId || undefined,
|
|
492
|
+
depth: currentDepth
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Pierce Shadow DOM
|
|
497
|
+
if (child.shadowRoot) {
|
|
498
|
+
walk(child.shadowRoot, currentDepth + 1);
|
|
499
|
+
}
|
|
500
|
+
walk(child, currentDepth + 1);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
walk(root === document ? document.body : root, 1);
|
|
505
|
+
return aomNodes;
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* 8. Dynamic Stability Probe (Event-Driven Wait Weaver)
|
|
510
|
+
* Replaces flaky static sleeps (Thread.sleep / cy.wait) by dynamically probing:
|
|
511
|
+
* 1. Coordinate / Layout Stability: Bounding rect unchanged for 100ms consecutive duration.
|
|
512
|
+
* 2. Form Editable State: Ensures form inputs are editable (!disabled && !readOnly).
|
|
513
|
+
* 3. Network Quiescence: Ensures active in-flight network requests == 0.
|
|
514
|
+
* @param {string|Element} target
|
|
515
|
+
* @param {Object} [options]
|
|
516
|
+
* @returns {Promise<boolean>}
|
|
517
|
+
*/
|
|
518
|
+
probeStability: function (target, options = {}) {
|
|
519
|
+
const opts = Object.assign({
|
|
520
|
+
checkPosition: true,
|
|
521
|
+
checkEditable: false,
|
|
522
|
+
stabilityDuration: 100,
|
|
523
|
+
timeout: 5000
|
|
524
|
+
}, options);
|
|
525
|
+
|
|
526
|
+
return new Promise((resolve) => {
|
|
527
|
+
const startTime = Date.now();
|
|
528
|
+
let el = null;
|
|
529
|
+
if (typeof target === 'string') {
|
|
530
|
+
el = document.querySelector(target);
|
|
531
|
+
} else if (target && target.nodeType === 1) {
|
|
532
|
+
el = target;
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let lastRect = null;
|
|
536
|
+
let stableSince = null;
|
|
537
|
+
|
|
538
|
+
function check() {
|
|
539
|
+
const now = Date.now();
|
|
540
|
+
if (now - startTime > opts.timeout) {
|
|
541
|
+
return resolve(false);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
if (!el && typeof target === 'string') {
|
|
545
|
+
el = document.querySelector(target);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
if (el) {
|
|
549
|
+
if (opts.checkEditable) {
|
|
550
|
+
const tag = el.tagName.toLowerCase();
|
|
551
|
+
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
|
552
|
+
if (el.disabled || el.readOnly) {
|
|
553
|
+
return setTimeout(check, 25);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (opts.checkPosition) {
|
|
559
|
+
const rect = el.getBoundingClientRect();
|
|
560
|
+
if (
|
|
561
|
+
lastRect &&
|
|
562
|
+
Math.abs(rect.top - lastRect.top) < 1 &&
|
|
563
|
+
Math.abs(rect.left - lastRect.left) < 1 &&
|
|
564
|
+
Math.abs(rect.width - lastRect.width) < 1 &&
|
|
565
|
+
Math.abs(rect.height - lastRect.height) < 1
|
|
566
|
+
) {
|
|
567
|
+
if (!stableSince) stableSince = now;
|
|
568
|
+
if (now - stableSince >= opts.stabilityDuration) {
|
|
569
|
+
return resolve(true);
|
|
570
|
+
}
|
|
571
|
+
} else {
|
|
572
|
+
lastRect = rect;
|
|
573
|
+
stableSince = null;
|
|
574
|
+
}
|
|
575
|
+
return setTimeout(check, 25);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
if (activeNetworkRequests === 0) {
|
|
580
|
+
return resolve(true);
|
|
581
|
+
}
|
|
582
|
+
setTimeout(check, 25);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
check();
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
|
|
590
|
+
window.__qa = qa;
|
|
591
|
+
window.__helper = qa;
|
|
592
|
+
return qa;
|
|
593
|
+
})();
|