@kkeve/suite 0.0.13 → 0.0.17
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/dist/bin/keve.js +2 -1
- package/dist/commands/clean.js +2 -2
- package/dist/commands/config.js +7 -7
- package/dist/commands/init.js +2 -2
- package/dist/commands/report.js +7 -7
- package/dist/commands/run.js +16 -12
- package/dist/commands/test.js +7 -7
- package/dist/config.js +7 -7
- package/dist/core/action-log-writer.js +4 -1
- package/dist/core/decorator/keve-decorators.d.ts +128 -0
- package/dist/core/decorator/keve-decorators.js +254 -0
- package/dist/core/decorator/keve-host.d.ts +40 -0
- package/dist/core/decorator/keve-host.js +33 -0
- package/dist/core/{keve-registry.d.ts → decorator/keve-registry.d.ts} +19 -0
- package/dist/core/{keve-registry.js → decorator/keve-registry.js} +13 -1
- package/dist/core/generateReport/report.d.ts +1 -1
- package/dist/core/generateReport/report.js +2 -2
- package/dist/core/generateReport/reportData.d.ts +3 -0
- package/dist/core/generateReport/reportData.js +55 -18
- package/dist/core/judge/confidence.d.ts +119 -0
- package/dist/core/judge/confidence.js +295 -0
- package/dist/core/judge/goal-heuristics.d.ts +32 -0
- package/dist/core/judge/goal-heuristics.js +128 -0
- package/dist/core/judge/pngCodec.d.ts +23 -0
- package/dist/core/judge/pngCodec.js +215 -0
- package/dist/core/judge/screenshotDiff.d.ts +112 -0
- package/dist/core/judge/screenshotDiff.js +420 -0
- package/dist/core/page-object/definePage.d.ts +95 -0
- package/dist/core/page-object/definePage.js +62 -0
- package/dist/core/page-object/elementResolver.d.ts +32 -0
- package/dist/core/page-object/elementResolver.js +236 -0
- package/dist/core/page-object/engine-types.d.ts +140 -0
- package/dist/core/page-object/engine-types.js +34 -0
- package/dist/core/sso-cookie.d.ts +18 -0
- package/dist/core/sso-cookie.js +10 -0
- package/dist/dsl/index.d.ts +15 -0
- package/dist/dsl/index.js +29 -0
- package/dist/dsl/locatorSource.d.ts +25 -0
- package/dist/dsl/locatorSource.js +323 -0
- package/dist/dsl/ops.d.ts +46 -0
- package/dist/dsl/ops.js +95 -0
- package/dist/dsl/toDecorator.d.ts +98 -0
- package/dist/dsl/toDecorator.js +747 -0
- package/dist/engine-cypress/adapter.d.ts +108 -0
- package/dist/engine-cypress/adapter.js +514 -0
- package/dist/engine-cypress/agent-page.d.ts +159 -0
- package/dist/engine-cypress/agent-page.js +566 -0
- package/dist/engine-cypress/agentDom.d.ts +22 -0
- package/dist/engine-cypress/agentDom.js +590 -0
- package/dist/engine-cypress/bridge.d.ts +39 -0
- package/dist/engine-cypress/bridge.js +598 -0
- package/dist/engine-cypress/cdp.d.ts +78 -0
- package/dist/engine-cypress/cdp.js +339 -0
- package/dist/engine-cypress/host.d.ts +22 -0
- package/dist/engine-cypress/host.js +480 -0
- package/dist/engine-cypress/index.d.ts +26 -0
- package/dist/engine-cypress/index.js +58 -0
- package/dist/engine-cypress/keveGoal.d.ts +43 -0
- package/dist/engine-cypress/keveGoal.js +265 -0
- package/dist/engine-cypress/pageOps.d.ts +41 -0
- package/dist/engine-cypress/pageOps.js +439 -0
- package/dist/engine-cypress/reporter.d.ts +20 -0
- package/dist/engine-cypress/reporter.js +128 -0
- package/dist/engine-cypress/setup.d.ts +21 -0
- package/dist/engine-cypress/setup.js +27 -0
- package/dist/engine-cypress/shotBuffer.d.ts +40 -0
- package/dist/engine-cypress/shotBuffer.js +41 -0
- package/dist/engine-cypress/state.d.ts +56 -0
- package/dist/engine-cypress/state.js +81 -0
- package/dist/engine-playwright/engineAdapter.d.ts +78 -0
- package/dist/engine-playwright/engineAdapter.js +371 -0
- package/dist/engine-playwright/global-setup.d.ts +28 -0
- package/dist/engine-playwright/global-setup.js +142 -0
- package/dist/{core → engine-playwright}/keve-report.d.ts +0 -1
- package/dist/engine-playwright/keve-report.js +137 -0
- package/dist/{core → engine-playwright}/keve-test.d.ts +10 -8
- package/dist/{core → engine-playwright}/keve-test.js +257 -207
- package/dist/{core → engine-playwright}/pw-config.js +8 -7
- package/dist/index.d.ts +13 -8
- package/dist/index.js +43 -28
- package/dist/page-agent/agent.d.ts +98 -7
- package/dist/page-agent/agent.js +338 -84
- package/dist/page-agent/diagnostics.d.ts +184 -0
- package/dist/page-agent/diagnostics.js +377 -0
- package/dist/page-agent/hooks.d.ts +3 -2
- package/dist/page-agent/hooks.js +4 -3
- package/dist/page-agent/page-like.d.ts +116 -0
- package/dist/page-agent/page-like.js +14 -0
- package/dist/page-agent/playwright-page.d.ts +20 -0
- package/dist/page-agent/playwright-page.js +98 -0
- package/dist/page-agent/system-prompt.d.ts +7 -0
- package/dist/page-agent/system-prompt.js +10 -0
- package/dist/page-agent/system_prompt.md +1 -1
- package/dist/page-agent/tools.d.ts +1 -1
- package/dist/page-agent/tools.js +441 -9
- package/doc/skills/keve-e2e-gen/references/script-generation-guide.md +11 -0
- package/package.json +24 -11
- package/scripts/gen-system-prompt.mjs +36 -0
- package/scripts/run-dsl-task.mjs +619 -0
- package/scripts/runtime/cypress-preprocessor.mjs +44 -0
- package/scripts/runtime/cypress.config.mjs +35 -0
- package/scripts/runtime/playwright.config.mjs +41 -0
- package/scripts/verify-shot-diff.mjs +243 -0
- package/dist/commands/auth-update.d.ts +0 -16
- package/dist/commands/auth-update.js +0 -88
- package/dist/core/auth.d.ts +0 -17
- package/dist/core/auth.js +0 -224
- package/dist/core/cdp-resolver.d.ts +0 -5
- package/dist/core/cdp-resolver.js +0 -93
- package/dist/core/global-setup.d.ts +0 -6
- package/dist/core/global-setup.js +0 -113
- package/dist/core/keve-decorators.d.ts +0 -51
- package/dist/core/keve-decorators.js +0 -95
- package/dist/core/keve-report.js +0 -376
- /package/dist/core/{keve-aspect.d.ts → decorator/keve-aspect.d.ts} +0 -0
- /package/dist/core/{keve-aspect.js → decorator/keve-aspect.js} +0 -0
- /package/dist/{core → engine-playwright}/pw-config.d.ts +0 -0
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* agentDom — Cypress 侧 Re-Act agent 的页内能力源码
|
|
4
|
+
*
|
|
5
|
+
* 与 locatorSource.ts / pageOps.ts 同样以「自包含源码字符串」形式注入 AUT 的
|
|
6
|
+
* isolated world:页内函数无法闭包引用 bundle 变量,只能整段送过去求值。
|
|
7
|
+
*
|
|
8
|
+
* 职责:
|
|
9
|
+
* snapshot() 生成与 Playwright `ariaSnapshot({mode:'ai'})` 同形的文本,
|
|
10
|
+
* 并为每个可见节点分配稳定 ref(`e1`、`e2`…),
|
|
11
|
+
* 供 `locator('aria-ref=eN')` 解析。
|
|
12
|
+
* resolve() locator 链求值(css / aria-ref / text / role / 过滤),
|
|
13
|
+
* 语义对齐 Playwright 的后代搜索 + 集合传递。
|
|
14
|
+
* act/read 元素动作与读取(点击/输入/勾选/下拉/属性/坐标/可见性)。
|
|
15
|
+
*
|
|
16
|
+
* 之所以不复用 CDP 的 `Accessibility.getFullAXTree`:AX 树的 backendDOMNodeId
|
|
17
|
+
* 需要在 AUT isolated world 里二次解析成元素,链路更长且多一次跨进程往返;
|
|
18
|
+
* 页内快照可以直接持有元素引用,ref 天然稳定。
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.agentDomSource = agentDomSource;
|
|
22
|
+
exports.agentBootstrapSource = agentBootstrapSource;
|
|
23
|
+
/** 注入页内的 agent DOM 能力源码(幂等:重复注入直接返回 cached) */
|
|
24
|
+
function agentDomSource() {
|
|
25
|
+
return `(() => {
|
|
26
|
+
"use strict";
|
|
27
|
+
if (window.__keveAgentSource === "v1") return "cached";
|
|
28
|
+
|
|
29
|
+
var MAX_NAME = 120;
|
|
30
|
+
var MAX_LINES = 1500;
|
|
31
|
+
|
|
32
|
+
var SKIP_TAGS = {
|
|
33
|
+
SCRIPT: 1, STYLE: 1, META: 1, LINK: 1, TEMPLATE: 1, NOSCRIPT: 1, HEAD: 1,
|
|
34
|
+
TITLE: 1, BASE: 1, COL: 1, COLGROUP: 1, SOURCE: 1, TRACK: 1, PARAM: 1,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** 需要出现在快照里的语义角色(无名称的 generic 会被折叠,避免噪声) */
|
|
38
|
+
var NAMED_ONLY_ROLES = {
|
|
39
|
+
generic: 1, paragraph: 1, group: 1, region: 1, article: 1, figure: 1,
|
|
40
|
+
list: 1, table: 1, row: 1, cell: 1, code: 1, blockquote: 1, caption: 1,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function locatorLib() {
|
|
44
|
+
return window.__keveLocator || null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function queryAllDeep(root, css) {
|
|
48
|
+
var lib = locatorLib();
|
|
49
|
+
if (lib && lib.queryAllDeep) return lib.queryAllDeep(root, css);
|
|
50
|
+
try { return Array.prototype.slice.call(root.querySelectorAll(css)); }
|
|
51
|
+
catch (e) { return []; }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function implicitRole(el) {
|
|
55
|
+
var explicit = el.getAttribute && el.getAttribute("role");
|
|
56
|
+
if (explicit) return String(explicit).trim().split(/\\s+/)[0];
|
|
57
|
+
var tag = String(el.tagName || "").toUpperCase();
|
|
58
|
+
var cls = String(el.className || "");
|
|
59
|
+
if (tag === "A") return el.hasAttribute("href") ? "link" : "generic";
|
|
60
|
+
if (tag === "BUTTON" || tag === "SUMMARY") return "button";
|
|
61
|
+
if (cls.indexOf("el-message-box") >= 0 || cls.indexOf("message-box") >= 0) return "dialog";
|
|
62
|
+
if (cls.indexOf("el-dialog") >= 0 || cls.indexOf("ks-dialog") >= 0 || cls.indexOf("ant-modal") >= 0) return "dialog";
|
|
63
|
+
if (tag === "INPUT") {
|
|
64
|
+
var t = String(el.getAttribute("type") || "text").toLowerCase();
|
|
65
|
+
if (t === "checkbox") return "checkbox";
|
|
66
|
+
if (t === "radio") return "radio";
|
|
67
|
+
if (t === "submit" || t === "button" || t === "reset" || t === "image") return "button";
|
|
68
|
+
if (t === "range") return "slider";
|
|
69
|
+
if (t === "number") return "spinbutton";
|
|
70
|
+
if (t === "search") return "searchbox";
|
|
71
|
+
if (t === "hidden") return "";
|
|
72
|
+
return "textbox";
|
|
73
|
+
}
|
|
74
|
+
if (tag === "TEXTAREA") return "textbox";
|
|
75
|
+
if (tag === "SELECT") return el.multiple ? "listbox" : "combobox";
|
|
76
|
+
if (tag === "IMG") return "img";
|
|
77
|
+
if (tag === "IFRAME") return "iframe";
|
|
78
|
+
if (/^H[1-6]$/.test(tag)) return "heading";
|
|
79
|
+
if (tag === "UL" || tag === "OL") return "list";
|
|
80
|
+
if (tag === "LI") return "listitem";
|
|
81
|
+
if (tag === "TABLE") return "table";
|
|
82
|
+
if (tag === "TR") return "row";
|
|
83
|
+
if (tag === "TD") return "cell";
|
|
84
|
+
if (tag === "TH") return "columnheader";
|
|
85
|
+
if (tag === "DIALOG") return "dialog";
|
|
86
|
+
if (tag === "OPTION") return "option";
|
|
87
|
+
if (tag === "OPTGROUP") return "group";
|
|
88
|
+
if (tag === "FORM") return "form";
|
|
89
|
+
if (tag === "NAV") return "navigation";
|
|
90
|
+
if (tag === "MAIN") return "main";
|
|
91
|
+
if (tag === "HEADER") return "banner";
|
|
92
|
+
if (tag === "FOOTER") return "contentinfo";
|
|
93
|
+
if (tag === "ASIDE") return "complementary";
|
|
94
|
+
if (tag === "SECTION") return "region";
|
|
95
|
+
if (tag === "ARTICLE") return "article";
|
|
96
|
+
if (tag === "LABEL") return "label";
|
|
97
|
+
if (tag === "P") return "paragraph";
|
|
98
|
+
if (tag === "PRE" || tag === "CODE") return "code";
|
|
99
|
+
if (tag === "HR") return "separator";
|
|
100
|
+
if (tag === "PROGRESS") return "progressbar";
|
|
101
|
+
if (tag === "METER") return "meter";
|
|
102
|
+
if (tag === "OUTPUT") return "status";
|
|
103
|
+
if (tag === "FIELDSET") return "group";
|
|
104
|
+
if (tag === "FIGURE") return "figure";
|
|
105
|
+
if (tag === "FIGCAPTION") return "caption";
|
|
106
|
+
if (tag === "BLOCKQUOTE") return "blockquote";
|
|
107
|
+
return "generic";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function accessibleName(el) {
|
|
111
|
+
var lib = locatorLib();
|
|
112
|
+
if (lib && lib.accessibleName) {
|
|
113
|
+
var libName = String(lib.accessibleName(el) || "").replace(/\\s+/g, " ").trim();
|
|
114
|
+
if (libName) return libName;
|
|
115
|
+
}
|
|
116
|
+
var ids = String(el.getAttribute("aria-labelledby") || "").split(/\\s+/).filter(Boolean);
|
|
117
|
+
var labelled = "";
|
|
118
|
+
for (var i = 0; i < ids.length; i += 1) {
|
|
119
|
+
var node = document.getElementById(ids[i]);
|
|
120
|
+
if (node) labelled += " " + String(node.textContent || "");
|
|
121
|
+
}
|
|
122
|
+
var alt = el.getAttribute && el.getAttribute("alt");
|
|
123
|
+
var value = el.getAttribute && el.getAttribute("aria-label");
|
|
124
|
+
var title = el.getAttribute && el.getAttribute("title");
|
|
125
|
+
var placeholder = el.getAttribute && el.getAttribute("placeholder");
|
|
126
|
+
var text = labelled || value || alt || title || placeholder || "";
|
|
127
|
+
if (!text && el.tagName === "INPUT") {
|
|
128
|
+
var t = String(el.getAttribute("type") || "").toLowerCase();
|
|
129
|
+
if (t === "checkbox" || t === "radio") text = el.value || "";
|
|
130
|
+
}
|
|
131
|
+
return String(text).replace(/\\s+/g, " ").trim();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function ownText(el) {
|
|
135
|
+
var out = "";
|
|
136
|
+
for (var i = 0; i < el.childNodes.length; i += 1) {
|
|
137
|
+
var n = el.childNodes[i];
|
|
138
|
+
if (n.nodeType === 3) out += " " + String(n.nodeValue || "");
|
|
139
|
+
}
|
|
140
|
+
return out.replace(/\\s+/g, " ").trim();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function allText(el) {
|
|
144
|
+
return String(el.textContent || "").replace(/\\s+/g, " ").trim();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function isVisible(el) {
|
|
148
|
+
if (!el || !el.isConnected || !el.getBoundingClientRect) return false;
|
|
149
|
+
var rect = el.getBoundingClientRect();
|
|
150
|
+
if (rect.width === 0 && rect.height === 0) return false;
|
|
151
|
+
var style = window.getComputedStyle(el);
|
|
152
|
+
if (!style) return true;
|
|
153
|
+
if (style.visibility === "hidden" || style.display === "none") return false;
|
|
154
|
+
if (Number(style.opacity) === 0) return false;
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function isAriaHidden(el) {
|
|
159
|
+
var cur = el;
|
|
160
|
+
while (cur && cur.getAttribute) {
|
|
161
|
+
if (cur.getAttribute("aria-hidden") === "true") return true;
|
|
162
|
+
if (cur.hasAttribute && cur.hasAttribute("hidden")) return true;
|
|
163
|
+
cur = cur.parentElement;
|
|
164
|
+
}
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── ref 注册表:元素 → ref 稳定映射(同一元素在多次快照中保持同一 ref) ──
|
|
169
|
+
function registry() {
|
|
170
|
+
if (!window.__keveAgentRefs) {
|
|
171
|
+
window.__keveAgentRefs = { byRef: {}, byEl: new WeakMap(), seq: 0 };
|
|
172
|
+
}
|
|
173
|
+
return window.__keveAgentRefs;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function refFor(el) {
|
|
177
|
+
var reg = registry();
|
|
178
|
+
var existing = reg.byEl.get(el);
|
|
179
|
+
if (existing) return existing;
|
|
180
|
+
reg.seq += 1;
|
|
181
|
+
var ref = "e" + reg.seq;
|
|
182
|
+
reg.byEl.set(el, ref);
|
|
183
|
+
reg.byRef[ref] = el;
|
|
184
|
+
return ref;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function elByRef(ref) {
|
|
188
|
+
var reg = registry();
|
|
189
|
+
var el = reg.byRef[String(ref || "")];
|
|
190
|
+
if (el && el.isConnected) return el;
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function stateAttrs(el, role) {
|
|
195
|
+
var attrs = [];
|
|
196
|
+
var tag = String(el.tagName || "").toUpperCase();
|
|
197
|
+
var type = String((el.getAttribute && el.getAttribute("type")) || "").toLowerCase();
|
|
198
|
+
if (el.disabled) attrs.push("disabled");
|
|
199
|
+
if (el.readOnly && (tag === "INPUT" || tag === "TEXTAREA")) attrs.push("readonly");
|
|
200
|
+
if (el.required) attrs.push("required");
|
|
201
|
+
if (role === "checkbox" || role === "radio" || role === "switch") {
|
|
202
|
+
var explicit = el.getAttribute && el.getAttribute("aria-checked");
|
|
203
|
+
var checked = explicit !== null && explicit !== undefined ? explicit === "true" : !!el.checked;
|
|
204
|
+
if (checked) attrs.push("checked");
|
|
205
|
+
}
|
|
206
|
+
if (type === "checkbox" || type === "radio") {
|
|
207
|
+
if (el.indeterminate) attrs.push("indeterminate");
|
|
208
|
+
}
|
|
209
|
+
var expanded = el.getAttribute && el.getAttribute("aria-expanded");
|
|
210
|
+
if (expanded === "true" || expanded === "false") attrs.push("expanded=" + expanded);
|
|
211
|
+
var selected = el.getAttribute && el.getAttribute("aria-selected");
|
|
212
|
+
if (selected === "true") attrs.push("selected");
|
|
213
|
+
var pressed = el.getAttribute && el.getAttribute("aria-pressed");
|
|
214
|
+
if (pressed === "true" || pressed === "mixed") attrs.push("pressed=" + pressed);
|
|
215
|
+
var modal = el.getAttribute && el.getAttribute("aria-modal");
|
|
216
|
+
if (modal === "true") attrs.push("modal");
|
|
217
|
+
if (role === "heading") {
|
|
218
|
+
var level = el.getAttribute && el.getAttribute("aria-level");
|
|
219
|
+
var tagLevel = /^H[1-6]$/.test(tag) ? tag.slice(1) : "";
|
|
220
|
+
attrs.push("level=" + (level || tagLevel || "2"));
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
if (window.getComputedStyle(el).cursor === "pointer" && !el.disabled) attrs.push("cursor=pointer");
|
|
224
|
+
} catch (e) { /* 忽略 */ }
|
|
225
|
+
return attrs.length ? " [" + attrs.join("] [") + "]" : "";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function valueOf(el, role) {
|
|
229
|
+
var tag = String(el.tagName || "").toUpperCase();
|
|
230
|
+
if (role === "textbox" || role === "searchbox" || role === "spinbutton") {
|
|
231
|
+
return el.value !== undefined && String(el.value) !== "" ? String(el.value) : "";
|
|
232
|
+
}
|
|
233
|
+
if (role === "combobox" || role === "listbox") {
|
|
234
|
+
if (tag === "SELECT") {
|
|
235
|
+
var opt = el.selectedOptions && el.selectedOptions[0];
|
|
236
|
+
return opt ? String(opt.textContent || "").trim() : "";
|
|
237
|
+
}
|
|
238
|
+
return String(el.value || "").trim();
|
|
239
|
+
}
|
|
240
|
+
if (role === "option" || role === "listitem") return "";
|
|
241
|
+
return "";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function shouldRender(el, role, name, direct) {
|
|
245
|
+
if (!role) return false;
|
|
246
|
+
if (SKIP_TAGS[String(el.tagName || "").toUpperCase()]) return false;
|
|
247
|
+
if (role === "iframe") return true;
|
|
248
|
+
if (NAMED_ONLY_ROLES[role]) {
|
|
249
|
+
if (name) return true;
|
|
250
|
+
if (direct && direct.length <= MAX_NAME) return true;
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function snapshot(maxLines) {
|
|
257
|
+
var limit = Number(maxLines) || MAX_LINES;
|
|
258
|
+
var lines = [];
|
|
259
|
+
var truncated = false;
|
|
260
|
+
|
|
261
|
+
function walk(node, depth) {
|
|
262
|
+
if (truncated) return;
|
|
263
|
+
var children = node.children || [];
|
|
264
|
+
for (var i = 0; i < children.length; i += 1) {
|
|
265
|
+
var el = children[i];
|
|
266
|
+
var tag = String(el.tagName || "").toUpperCase();
|
|
267
|
+
if (SKIP_TAGS[tag]) continue;
|
|
268
|
+
if (isAriaHidden(el)) continue;
|
|
269
|
+
|
|
270
|
+
var childDepth = depth;
|
|
271
|
+
if (isVisible(el)) {
|
|
272
|
+
var role = implicitRole(el);
|
|
273
|
+
var name = accessibleName(el);
|
|
274
|
+
var direct = ownText(el);
|
|
275
|
+
if (shouldRender(el, role, name, direct)) {
|
|
276
|
+
if (lines.length >= limit) { truncated = true; return; }
|
|
277
|
+
var attrs = stateAttrs(el, role);
|
|
278
|
+
var ref = refFor(el);
|
|
279
|
+
var value = valueOf(el, role);
|
|
280
|
+
var text = direct && direct.length <= MAX_NAME ? direct : "";
|
|
281
|
+
var line = " ".repeat(depth + 1) + "- " + role + " [ref=" + ref + "]" + attrs;
|
|
282
|
+
if (name) line += " \\"" + name.slice(0, MAX_NAME).replace(/"/g, "'") + "\\"";
|
|
283
|
+
if (value) line += ": " + value.slice(0, MAX_NAME);
|
|
284
|
+
else if (text) line += ": " + text;
|
|
285
|
+
lines.push(line);
|
|
286
|
+
childDepth = depth + 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// 同源 iframe 穿透(与 Playwright 的快照语义对齐)
|
|
291
|
+
if (tag === "IFRAME") {
|
|
292
|
+
var doc = null;
|
|
293
|
+
try { doc = el.contentDocument; } catch (e) { doc = null; }
|
|
294
|
+
if (doc && doc.body) walk(doc.body, childDepth);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
walk(el, childDepth);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
walk(document.body || document.documentElement, 0);
|
|
302
|
+
if (truncated) lines.push("... (snapshot truncated at " + limit + " lines)");
|
|
303
|
+
return lines.join("\\n");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ── 定位:ops 是 locator 链,每跳在上一跳结果的后代里求值 ──
|
|
307
|
+
|
|
308
|
+
function dedupe(list) {
|
|
309
|
+
var seen = [];
|
|
310
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
311
|
+
if (seen.indexOf(list[i]) < 0) seen.push(list[i]);
|
|
312
|
+
}
|
|
313
|
+
return seen;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
function descendants(scope, css) {
|
|
317
|
+
if (scope === document) return queryAllDeep(document, css);
|
|
318
|
+
return queryAllDeep(scope, css);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/** 文本匹配取「包含该文本的最深元素」,与 locatorSource 的 includes 跳一致 */
|
|
322
|
+
function textIn(scopes, needle, exact) {
|
|
323
|
+
var want = String(needle == null ? "" : needle).replace(/\\s+/g, " ").trim();
|
|
324
|
+
if (!want) return [];
|
|
325
|
+
var hits = [];
|
|
326
|
+
for (var s = 0; s < scopes.length; s += 1) {
|
|
327
|
+
var scope = scopes[s];
|
|
328
|
+
var pool = scope === document
|
|
329
|
+
? queryAllDeep(document, "*")
|
|
330
|
+
: queryAllDeep(scope, "*");
|
|
331
|
+
var local = [];
|
|
332
|
+
for (var i = 0; i < pool.length; i += 1) {
|
|
333
|
+
var text = allText(pool[i]);
|
|
334
|
+
if (!text) continue;
|
|
335
|
+
if (exact ? text === want : text.indexOf(want) >= 0) local.push(pool[i]);
|
|
336
|
+
}
|
|
337
|
+
for (var j = 0; j < local.length; j += 1) {
|
|
338
|
+
var el = local[j];
|
|
339
|
+
var hasInnerMatch = false;
|
|
340
|
+
for (var k = 0; k < local.length; k += 1) {
|
|
341
|
+
if (k !== j && el.contains(local[k])) { hasInnerMatch = true; break; }
|
|
342
|
+
}
|
|
343
|
+
if (!hasInnerMatch) hits.push(el);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return dedupe(hits);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
var ROLE_SELECTOR = "[role],button,a[href],input,select,textarea,img,summary,dialog,option,[aria-label],[aria-labelledby],[tabindex],.el-message-box,.el-dialog,.ks-dialog,.ant-modal";
|
|
350
|
+
|
|
351
|
+
function roleIn(scopes, role, name, exact) {
|
|
352
|
+
var want = String(role || "").toLowerCase();
|
|
353
|
+
var wantName = name == null ? "" : String(name);
|
|
354
|
+
var hits = [];
|
|
355
|
+
for (var s = 0; s < scopes.length; s += 1) {
|
|
356
|
+
var scope = scopes[s];
|
|
357
|
+
var pool = scope === document ? queryAllDeep(document, ROLE_SELECTOR) : queryAllDeep(scope, ROLE_SELECTOR);
|
|
358
|
+
for (var i = 0; i < pool.length; i += 1) {
|
|
359
|
+
var el = pool[i];
|
|
360
|
+
if (String(implicitRole(el)).toLowerCase() !== want) continue;
|
|
361
|
+
if (wantName) {
|
|
362
|
+
var actual = accessibleName(el);
|
|
363
|
+
if (exact ? actual !== wantName : actual.indexOf(wantName) < 0) continue;
|
|
364
|
+
}
|
|
365
|
+
hits.push(el);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return dedupe(hits);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function resolve(ops) {
|
|
372
|
+
var list = Array.isArray(ops) ? ops : [];
|
|
373
|
+
var cur = [document];
|
|
374
|
+
for (var i = 0; i < list.length; i += 1) {
|
|
375
|
+
var op = list[i] || {};
|
|
376
|
+
var t = String(op.t || "");
|
|
377
|
+
if (t === "css") {
|
|
378
|
+
var next = [];
|
|
379
|
+
for (var s = 0; s < cur.length; s += 1) {
|
|
380
|
+
next = next.concat(descendants(cur[s], String(op.selector || "")));
|
|
381
|
+
}
|
|
382
|
+
cur = dedupe(next);
|
|
383
|
+
} else if (t === "ariaRef") {
|
|
384
|
+
var el = elByRef(op.ref);
|
|
385
|
+
cur = el ? [el] : [];
|
|
386
|
+
} else if (t === "text") {
|
|
387
|
+
cur = textIn(cur, op.text, op.exact !== false);
|
|
388
|
+
} else if (t === "role") {
|
|
389
|
+
cur = roleIn(cur, op.role, op.name, op.exact !== false);
|
|
390
|
+
} else if (t === "filterHasText") {
|
|
391
|
+
var needle = String(op.text == null ? "" : op.text);
|
|
392
|
+
var kept = [];
|
|
393
|
+
for (var f = 0; f < cur.length; f += 1) {
|
|
394
|
+
if (allText(cur[f]).indexOf(needle) >= 0) kept.push(cur[f]);
|
|
395
|
+
}
|
|
396
|
+
cur = kept;
|
|
397
|
+
} else if (t === "filterVisible") {
|
|
398
|
+
var visibleKept = [];
|
|
399
|
+
for (var v = 0; v < cur.length; v += 1) {
|
|
400
|
+
if (isVisible(cur[v])) visibleKept.push(cur[v]);
|
|
401
|
+
}
|
|
402
|
+
cur = visibleKept;
|
|
403
|
+
}
|
|
404
|
+
if (!cur.length) return [];
|
|
405
|
+
}
|
|
406
|
+
return cur;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function pick(els, index) {
|
|
410
|
+
if (!els || !els.length) return null;
|
|
411
|
+
if (index === null || index === undefined || index === "") return els[0];
|
|
412
|
+
var i = Number(index);
|
|
413
|
+
if (!isFinite(i)) return els[0];
|
|
414
|
+
if (i < 0) return els[els.length + i] || null;
|
|
415
|
+
return els[i] || null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** 定位失败时的可读错误(与 CypressEngine.withWait 的重试判定词对齐) */
|
|
419
|
+
function pickOrThrow(els, index) {
|
|
420
|
+
var el = pick(els, index);
|
|
421
|
+
if (!el) {
|
|
422
|
+
throw new Error("[keve] 元素未找到(no-match): index=" + index + ", matched=" + ((els && els.length) || 0));
|
|
423
|
+
}
|
|
424
|
+
return el;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ── 动作 ──
|
|
428
|
+
|
|
429
|
+
function fire(el, type, init) {
|
|
430
|
+
var rect = el.getBoundingClientRect ? el.getBoundingClientRect() : { left: 0, top: 0, width: 0, height: 0 };
|
|
431
|
+
var base = {
|
|
432
|
+
bubbles: true, cancelable: true, composed: true, view: window, detail: 1, button: 0,
|
|
433
|
+
clientX: Math.round(rect.left + rect.width / 2),
|
|
434
|
+
clientY: Math.round(rect.top + rect.height / 2),
|
|
435
|
+
};
|
|
436
|
+
if (init) for (var k in init) base[k] = init[k];
|
|
437
|
+
el.dispatchEvent(new MouseEvent(type, base));
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function clickEl(el, dbl) {
|
|
441
|
+
if (el.scrollIntoView) el.scrollIntoView({ block: "center", inline: "center" });
|
|
442
|
+
if (el.focus) { try { el.focus(); } catch (e) { /* 忽略 */ } }
|
|
443
|
+
fire(el, "mousedown");
|
|
444
|
+
fire(el, "mouseup");
|
|
445
|
+
if (typeof el.click === "function") el.click();
|
|
446
|
+
else fire(el, "click");
|
|
447
|
+
fire(el, "click");
|
|
448
|
+
if (dbl) {
|
|
449
|
+
fire(el, "mousedown", { detail: 2 });
|
|
450
|
+
fire(el, "mouseup", { detail: 2 });
|
|
451
|
+
fire(el, "dblclick", { detail: 2 });
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function setNativeValue(el, value) {
|
|
456
|
+
var text = value == null ? "" : String(value);
|
|
457
|
+
if (el.isContentEditable) {
|
|
458
|
+
el.textContent = text;
|
|
459
|
+
el.dispatchEvent(new InputEvent("input", { bubbles: true, data: text }));
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
var proto = el.tagName === "TEXTAREA"
|
|
463
|
+
? HTMLTextAreaElement.prototype
|
|
464
|
+
: (el.tagName === "SELECT" ? HTMLSelectElement.prototype : HTMLInputElement.prototype);
|
|
465
|
+
var desc = Object.getOwnPropertyDescriptor(proto, "value");
|
|
466
|
+
if (desc && desc.set) desc.set.call(el, text);
|
|
467
|
+
else el.value = text;
|
|
468
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
469
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function act(el, action, payload) {
|
|
473
|
+
var data = payload || {};
|
|
474
|
+
if (action === "click") { clickEl(el, false); return "ok"; }
|
|
475
|
+
if (action === "dblclick") { clickEl(el, true); return "ok"; }
|
|
476
|
+
if (action === "hover") {
|
|
477
|
+
if (el.scrollIntoView) el.scrollIntoView({ block: "center" });
|
|
478
|
+
fire(el, "mouseover"); fire(el, "mouseenter"); fire(el, "mousemove");
|
|
479
|
+
return "ok";
|
|
480
|
+
}
|
|
481
|
+
if (action === "focus") { if (el.focus) el.focus(); return "ok"; }
|
|
482
|
+
if (action === "fill") { setNativeValue(el, data.value); return "ok"; }
|
|
483
|
+
if (action === "setChecked") {
|
|
484
|
+
var want = !!data.checked;
|
|
485
|
+
if (el.checked !== want) {
|
|
486
|
+
if (typeof el.click === "function" && !el.disabled) el.click();
|
|
487
|
+
else { el.checked = want; el.dispatchEvent(new Event("change", { bubbles: true })); }
|
|
488
|
+
}
|
|
489
|
+
return "ok";
|
|
490
|
+
}
|
|
491
|
+
if (action === "selectOption") {
|
|
492
|
+
var tag = String(el.tagName || "").toUpperCase();
|
|
493
|
+
if (tag !== "SELECT") throw new Error("[keve] 非原生 select,无法 selectOption");
|
|
494
|
+
var opts = Array.prototype.slice.call(el.options || []);
|
|
495
|
+
var chosen = null;
|
|
496
|
+
if (data.value !== undefined && data.value !== null) {
|
|
497
|
+
chosen = opts.filter(function (o) { return o.value === data.value; })[0] || null;
|
|
498
|
+
}
|
|
499
|
+
if (!chosen && data.label !== undefined) {
|
|
500
|
+
chosen = opts.filter(function (o) { return String(o.textContent || "").trim() === data.label; })[0] || null;
|
|
501
|
+
}
|
|
502
|
+
if (!chosen && data.label !== undefined) {
|
|
503
|
+
chosen = opts.filter(function (o) { return String(o.textContent || "").trim().indexOf(data.label) >= 0; })[0] || null;
|
|
504
|
+
}
|
|
505
|
+
if (!chosen) throw new Error("[keve] select 中没有匹配的 option: " + JSON.stringify(data));
|
|
506
|
+
el.value = chosen.value;
|
|
507
|
+
chosen.selected = true;
|
|
508
|
+
el.dispatchEvent(new Event("input", { bubbles: true }));
|
|
509
|
+
el.dispatchEvent(new Event("change", { bubbles: true }));
|
|
510
|
+
return "ok";
|
|
511
|
+
}
|
|
512
|
+
if (action === "press") {
|
|
513
|
+
var key = String(data.key || "");
|
|
514
|
+
if (el.focus) el.focus();
|
|
515
|
+
var init = { key: key, bubbles: true, cancelable: true };
|
|
516
|
+
el.dispatchEvent(new KeyboardEvent("keydown", init));
|
|
517
|
+
el.dispatchEvent(new KeyboardEvent("keypress", init));
|
|
518
|
+
el.dispatchEvent(new KeyboardEvent("keyup", init));
|
|
519
|
+
return "ok";
|
|
520
|
+
}
|
|
521
|
+
throw new Error("[keve] 未知动作: " + action);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function read(el, what, arg) {
|
|
525
|
+
if (what === "text") {
|
|
526
|
+
var v = el.value !== undefined && String(el.value) !== "" ? String(el.value) : String(el.textContent || "");
|
|
527
|
+
return v.replace(/\\s+/g, " ").trim();
|
|
528
|
+
}
|
|
529
|
+
if (what === "innerText") return String(el.innerText !== undefined ? el.innerText : el.textContent || "").replace(/\\s+/g, " ").trim();
|
|
530
|
+
if (what === "value") return el.value === undefined || el.value === null ? "" : String(el.value);
|
|
531
|
+
if (what === "checked") {
|
|
532
|
+
var explicit = el.getAttribute && el.getAttribute("aria-checked");
|
|
533
|
+
if (explicit === "true" || explicit === "false") return explicit === "true";
|
|
534
|
+
return !!el.checked;
|
|
535
|
+
}
|
|
536
|
+
if (what === "attr") return el.getAttribute ? el.getAttribute(String(arg)) : null;
|
|
537
|
+
if (what === "visible") return isVisible(el);
|
|
538
|
+
if (what === "box") {
|
|
539
|
+
if (!isVisible(el)) return null;
|
|
540
|
+
var r = el.getBoundingClientRect();
|
|
541
|
+
return { x: r.left, y: r.top, width: r.width, height: r.height };
|
|
542
|
+
}
|
|
543
|
+
if (what === "eval") {
|
|
544
|
+
var src = String(arg && arg.fn || "");
|
|
545
|
+
if (!src) return null;
|
|
546
|
+
var fn = (0, eval)("(" + src + ")");
|
|
547
|
+
if (typeof fn !== "function") return null;
|
|
548
|
+
return fn(el, arg && arg.arg);
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
window.__keveAgent = {
|
|
554
|
+
version: "v1",
|
|
555
|
+
snapshot: snapshot,
|
|
556
|
+
resolve: resolve,
|
|
557
|
+
pick: pick,
|
|
558
|
+
count: function (ops, index) {
|
|
559
|
+
var els = resolve(ops);
|
|
560
|
+
if (index === null || index === undefined || index === "") return els.length;
|
|
561
|
+
return pick(els, index) ? 1 : 0;
|
|
562
|
+
},
|
|
563
|
+
read: function (ops, index, what, arg) { return read(pickOrThrow(resolve(ops), index), what, arg); },
|
|
564
|
+
act: function (ops, index, action, payload) { return act(pickOrThrow(resolve(ops), index), action, payload); },
|
|
565
|
+
texts: function (ops, index) {
|
|
566
|
+
var els = resolve(ops);
|
|
567
|
+
if (index !== null && index !== undefined && index !== "") {
|
|
568
|
+
var one = pick(els, index);
|
|
569
|
+
return one ? [read(one, "innerText")] : [];
|
|
570
|
+
}
|
|
571
|
+
var out = [];
|
|
572
|
+
for (var i = 0; i < els.length; i += 1) out.push(read(els[i], "innerText"));
|
|
573
|
+
return out;
|
|
574
|
+
},
|
|
575
|
+
visible: function (ops, index) {
|
|
576
|
+
var el = pick(resolve(ops), index);
|
|
577
|
+
return el ? isVisible(el) : false;
|
|
578
|
+
},
|
|
579
|
+
exists: function (ops, index) { return !!pick(resolve(ops), index); },
|
|
580
|
+
focus: function (ops, index) { var el = pickOrThrow(resolve(ops), index); if (el.focus) el.focus(); return "ok"; },
|
|
581
|
+
evalScript: function (src) { return (0, eval)(String(src)); },
|
|
582
|
+
};
|
|
583
|
+
window.__keveAgentSource = "v1";
|
|
584
|
+
return "ok";
|
|
585
|
+
})()`;
|
|
586
|
+
}
|
|
587
|
+
/** locatorSource + pageOps + agentDom 的一次性注入 */
|
|
588
|
+
function agentBootstrapSource(locatorSrc) {
|
|
589
|
+
return `${locatorSrc};\n${agentDomSource()}`;
|
|
590
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bridge — Cypress spec(浏览器) ↔ Node 的同步通道
|
|
3
|
+
*
|
|
4
|
+
* 为什么需要它:`cy.task` 在 `cy.then(async …)` 内只入队、立刻 resolve 成 undefined,
|
|
5
|
+
* 不能作为「可 await 的 Node 往返」(探针 15/19 实测)。而截图对比、写文件、
|
|
6
|
+
* 生成报告这些能力必须在 Node 侧执行。这里用 `setupNodeEvents` 起一个本地 HTTP
|
|
7
|
+
* 服务,浏览器侧用 `fetch` 直接 await —— 这是唯一可靠的同步通道。
|
|
8
|
+
*
|
|
9
|
+
* 端口经 `config.env.KEVE_BRIDGE_PORT` 注入 spec,spec 内用
|
|
10
|
+
* `Cypress.env('KEVE_BRIDGE_PORT')` 取。
|
|
11
|
+
*
|
|
12
|
+
* 报告一致性:本模块与 keve-report.ts 写同一份 confidence-data.jsonl 与
|
|
13
|
+
* report-data.json,并且都调用 core/confidence.ts 的分类与
|
|
14
|
+
* core/generateReport/reportData.ts 的汇总,保证 Cypress / Playwright 报告同构。
|
|
15
|
+
*/
|
|
16
|
+
import { type AttachmentLike } from '../core/judge/confidence.js';
|
|
17
|
+
export interface CypressTestResultRecord {
|
|
18
|
+
/** scene.title,形如 `S90061: 描述` */
|
|
19
|
+
title: string;
|
|
20
|
+
/** @keveModel 的 suite title,形如 `M_SG90061: 描述` */
|
|
21
|
+
suiteTitle: string;
|
|
22
|
+
/** spec 文件名(仅展示用) */
|
|
23
|
+
file?: string;
|
|
24
|
+
status: 'passed' | 'failed' | 'skipped' | 'timedOut';
|
|
25
|
+
duration: number;
|
|
26
|
+
startTime: string;
|
|
27
|
+
error?: string;
|
|
28
|
+
/** 应用侧未捕获异常(仅供结果归因,报告 error 已包含合并文本) */
|
|
29
|
+
uncaughtErrors?: string[];
|
|
30
|
+
/** 用例执行日志(对齐 Playwright test-results.json 的 results[].stdout) */
|
|
31
|
+
logs?: string[];
|
|
32
|
+
attachments: AttachmentLike[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 起桥 + 注册 Cypress task。
|
|
36
|
+
* 返回补好 `config.env.KEVE_BRIDGE_PORT` 的 config(Cypress 要求 return config)。
|
|
37
|
+
*/
|
|
38
|
+
export declare function setupKeveBridge(on: any, config: any): Promise<any>;
|
|
39
|
+
export default setupKeveBridge;
|