@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.
Files changed (117) hide show
  1. package/dist/bin/keve.js +2 -1
  2. package/dist/commands/clean.js +2 -2
  3. package/dist/commands/config.js +7 -7
  4. package/dist/commands/init.js +2 -2
  5. package/dist/commands/report.js +7 -7
  6. package/dist/commands/run.js +16 -12
  7. package/dist/commands/test.js +7 -7
  8. package/dist/config.js +7 -7
  9. package/dist/core/action-log-writer.js +4 -1
  10. package/dist/core/decorator/keve-decorators.d.ts +128 -0
  11. package/dist/core/decorator/keve-decorators.js +254 -0
  12. package/dist/core/decorator/keve-host.d.ts +40 -0
  13. package/dist/core/decorator/keve-host.js +33 -0
  14. package/dist/core/{keve-registry.d.ts → decorator/keve-registry.d.ts} +19 -0
  15. package/dist/core/{keve-registry.js → decorator/keve-registry.js} +13 -1
  16. package/dist/core/generateReport/report.d.ts +1 -1
  17. package/dist/core/generateReport/report.js +2 -2
  18. package/dist/core/generateReport/reportData.d.ts +3 -0
  19. package/dist/core/generateReport/reportData.js +55 -18
  20. package/dist/core/judge/confidence.d.ts +119 -0
  21. package/dist/core/judge/confidence.js +295 -0
  22. package/dist/core/judge/goal-heuristics.d.ts +32 -0
  23. package/dist/core/judge/goal-heuristics.js +128 -0
  24. package/dist/core/judge/pngCodec.d.ts +23 -0
  25. package/dist/core/judge/pngCodec.js +215 -0
  26. package/dist/core/judge/screenshotDiff.d.ts +112 -0
  27. package/dist/core/judge/screenshotDiff.js +420 -0
  28. package/dist/core/page-object/definePage.d.ts +95 -0
  29. package/dist/core/page-object/definePage.js +62 -0
  30. package/dist/core/page-object/elementResolver.d.ts +32 -0
  31. package/dist/core/page-object/elementResolver.js +236 -0
  32. package/dist/core/page-object/engine-types.d.ts +140 -0
  33. package/dist/core/page-object/engine-types.js +34 -0
  34. package/dist/core/sso-cookie.d.ts +18 -0
  35. package/dist/core/sso-cookie.js +10 -0
  36. package/dist/dsl/index.d.ts +15 -0
  37. package/dist/dsl/index.js +29 -0
  38. package/dist/dsl/locatorSource.d.ts +25 -0
  39. package/dist/dsl/locatorSource.js +323 -0
  40. package/dist/dsl/ops.d.ts +46 -0
  41. package/dist/dsl/ops.js +95 -0
  42. package/dist/dsl/toDecorator.d.ts +98 -0
  43. package/dist/dsl/toDecorator.js +747 -0
  44. package/dist/engine-cypress/adapter.d.ts +108 -0
  45. package/dist/engine-cypress/adapter.js +514 -0
  46. package/dist/engine-cypress/agent-page.d.ts +159 -0
  47. package/dist/engine-cypress/agent-page.js +566 -0
  48. package/dist/engine-cypress/agentDom.d.ts +22 -0
  49. package/dist/engine-cypress/agentDom.js +590 -0
  50. package/dist/engine-cypress/bridge.d.ts +39 -0
  51. package/dist/engine-cypress/bridge.js +598 -0
  52. package/dist/engine-cypress/cdp.d.ts +78 -0
  53. package/dist/engine-cypress/cdp.js +339 -0
  54. package/dist/engine-cypress/host.d.ts +22 -0
  55. package/dist/engine-cypress/host.js +480 -0
  56. package/dist/engine-cypress/index.d.ts +26 -0
  57. package/dist/engine-cypress/index.js +58 -0
  58. package/dist/engine-cypress/keveGoal.d.ts +43 -0
  59. package/dist/engine-cypress/keveGoal.js +265 -0
  60. package/dist/engine-cypress/pageOps.d.ts +41 -0
  61. package/dist/engine-cypress/pageOps.js +439 -0
  62. package/dist/engine-cypress/reporter.d.ts +20 -0
  63. package/dist/engine-cypress/reporter.js +128 -0
  64. package/dist/engine-cypress/setup.d.ts +21 -0
  65. package/dist/engine-cypress/setup.js +27 -0
  66. package/dist/engine-cypress/shotBuffer.d.ts +40 -0
  67. package/dist/engine-cypress/shotBuffer.js +41 -0
  68. package/dist/engine-cypress/state.d.ts +56 -0
  69. package/dist/engine-cypress/state.js +81 -0
  70. package/dist/engine-playwright/engineAdapter.d.ts +78 -0
  71. package/dist/engine-playwright/engineAdapter.js +371 -0
  72. package/dist/engine-playwright/global-setup.d.ts +28 -0
  73. package/dist/engine-playwright/global-setup.js +142 -0
  74. package/dist/{core → engine-playwright}/keve-report.d.ts +0 -1
  75. package/dist/engine-playwright/keve-report.js +137 -0
  76. package/dist/{core → engine-playwright}/keve-test.d.ts +10 -8
  77. package/dist/{core → engine-playwright}/keve-test.js +257 -207
  78. package/dist/{core → engine-playwright}/pw-config.js +8 -7
  79. package/dist/index.d.ts +13 -8
  80. package/dist/index.js +43 -28
  81. package/dist/page-agent/agent.d.ts +98 -7
  82. package/dist/page-agent/agent.js +338 -84
  83. package/dist/page-agent/diagnostics.d.ts +184 -0
  84. package/dist/page-agent/diagnostics.js +377 -0
  85. package/dist/page-agent/hooks.d.ts +3 -2
  86. package/dist/page-agent/hooks.js +4 -3
  87. package/dist/page-agent/page-like.d.ts +116 -0
  88. package/dist/page-agent/page-like.js +14 -0
  89. package/dist/page-agent/playwright-page.d.ts +20 -0
  90. package/dist/page-agent/playwright-page.js +98 -0
  91. package/dist/page-agent/system-prompt.d.ts +7 -0
  92. package/dist/page-agent/system-prompt.js +10 -0
  93. package/dist/page-agent/system_prompt.md +1 -1
  94. package/dist/page-agent/tools.d.ts +1 -1
  95. package/dist/page-agent/tools.js +441 -9
  96. package/doc/skills/keve-e2e-gen/references/script-generation-guide.md +11 -0
  97. package/package.json +24 -11
  98. package/scripts/gen-system-prompt.mjs +36 -0
  99. package/scripts/run-dsl-task.mjs +619 -0
  100. package/scripts/runtime/cypress-preprocessor.mjs +44 -0
  101. package/scripts/runtime/cypress.config.mjs +35 -0
  102. package/scripts/runtime/playwright.config.mjs +41 -0
  103. package/scripts/verify-shot-diff.mjs +243 -0
  104. package/dist/commands/auth-update.d.ts +0 -16
  105. package/dist/commands/auth-update.js +0 -88
  106. package/dist/core/auth.d.ts +0 -17
  107. package/dist/core/auth.js +0 -224
  108. package/dist/core/cdp-resolver.d.ts +0 -5
  109. package/dist/core/cdp-resolver.js +0 -93
  110. package/dist/core/global-setup.d.ts +0 -6
  111. package/dist/core/global-setup.js +0 -113
  112. package/dist/core/keve-decorators.d.ts +0 -51
  113. package/dist/core/keve-decorators.js +0 -95
  114. package/dist/core/keve-report.js +0 -376
  115. /package/dist/core/{keve-aspect.d.ts → decorator/keve-aspect.d.ts} +0 -0
  116. /package/dist/core/{keve-aspect.js → decorator/keve-aspect.js} +0 -0
  117. /package/dist/{core → engine-playwright}/pw-config.d.ts +0 -0
@@ -0,0 +1,566 @@
1
+ "use strict";
2
+ /**
3
+ * agent-page — Cypress 侧的 AgentPage 实现(CDP + 页内 agentDom)
4
+ *
5
+ * 与 Playwright 侧的 `page-agent/playwright-page.ts` 一一对应:同一个 Re-Act
6
+ * 循环吃同一份 `AgentPage` 契约,差异只在这层:
7
+ * - DOM 读写全部经 CDP 在 AUT isolated world 内求值(绝不碰 runner 顶层页)
8
+ * - 键盘/鼠标走 CDP Input 域,坐标需叠加 AUT iframe 在 runner 里的偏移
9
+ * - 截图统一裁剪到 AUT iframe,元素截图在此基础上再按 boundingBox 裁一次
10
+ *
11
+ * 本模块零 Node 依赖,也不得 import '@playwright/test'(连类型都不需要)。
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.CyAgentPage = exports.CyAgentLocator = void 0;
48
+ exports.mergeFrameDiagnostics = mergeFrameDiagnostics;
49
+ exports.createCyAgentPage = createCyAgentPage;
50
+ const cdp_js_1 = require("./cdp.js");
51
+ const agentDom_js_1 = require("./agentDom.js");
52
+ function maxPageMetric(values) {
53
+ let max = 0;
54
+ for (const value of values) {
55
+ if (value !== undefined && Number(value) > max)
56
+ max = Number(value);
57
+ }
58
+ return max > 0 ? max : undefined;
59
+ }
60
+ /**
61
+ * 合并多个 frame 的主世界诊断快照。
62
+ *
63
+ * 业务页面(如 kwaibi)可能把主要内容放在同源/跨域 iframe 内;单个 frame
64
+ * 的 dataset 只包含自己上下文里发生的 console/网络/性能事件,不能只看 AUT
65
+ * 顶层 frame。计数类网络/console 记录直接拼接,页面性能只有 longtask 是
66
+ * 可累加阻塞量,其余指标取各 frame 的最大值。
67
+ */
68
+ function mergeFrameDiagnostics(snaps) {
69
+ const valid = snaps.filter(Boolean);
70
+ if (!valid.length)
71
+ return undefined;
72
+ const browser = [];
73
+ const network = [];
74
+ const page = {};
75
+ const longTaskCount = [];
76
+ const longTaskMs = [];
77
+ for (const snap of valid) {
78
+ if (snap.browser?.length)
79
+ browser.push(...snap.browser);
80
+ if (snap.network?.length)
81
+ network.push(...snap.network);
82
+ if (!snap.page)
83
+ continue;
84
+ const current = snap.page;
85
+ page.domContentLoadedMs = maxPageMetric([page.domContentLoadedMs, current.domContentLoadedMs]);
86
+ page.loadMs = maxPageMetric([page.loadMs, current.loadMs]);
87
+ page.lcpMs = maxPageMetric([page.lcpMs, current.lcpMs]);
88
+ page.heapUsedMb = maxPageMetric([page.heapUsedMb, current.heapUsedMb]);
89
+ if (current.longTaskCount !== undefined)
90
+ longTaskCount.push(current.longTaskCount);
91
+ if (current.longTaskMs !== undefined)
92
+ longTaskMs.push(current.longTaskMs);
93
+ }
94
+ if (longTaskCount.length) {
95
+ page.longTaskCount = longTaskCount.reduce((sum, value) => sum + value, 0);
96
+ }
97
+ if (longTaskMs.length) {
98
+ page.longTaskMs = longTaskMs.reduce((sum, value) => sum + value, 0);
99
+ }
100
+ return {
101
+ browser,
102
+ network,
103
+ page: Object.keys(page).length ? page : undefined,
104
+ };
105
+ }
106
+ /** `aria-ref=e12` / Playwright 风格的 `text=`、`role=` 选择器解析 */
107
+ function parseSelector(selector) {
108
+ const raw = String(selector || '').trim();
109
+ const ariaRef = raw.match(/^aria-ref=(.+)$/);
110
+ if (ariaRef)
111
+ return { t: 'ariaRef', ref: ariaRef[1].trim() };
112
+ const textPrefix = raw.match(/^text=(.+)$/s);
113
+ if (textPrefix) {
114
+ const body = textPrefix[1].trim();
115
+ const quoted = body.match(/^(["'])([\s\S]*)\1$/);
116
+ return { t: 'text', text: quoted ? quoted[2] : body, exact: !!quoted };
117
+ }
118
+ return { t: 'css', selector: raw };
119
+ }
120
+ /** CSS 转义,用于把字符串安全塞进 Runtime.evaluate 表达式 */
121
+ function js(value) {
122
+ return JSON.stringify(value === undefined ? null : value);
123
+ }
124
+ /** Playwright 的 `Control+a` 形式 → CDP modifiers 位掩码 */
125
+ const MODIFIER_BITS = { Alt: 1, Control: 2, Meta: 4, Shift: 8 };
126
+ /** 特殊键 → (key, code, windowsVirtualKeyCode) */
127
+ const SPECIAL_KEYS = {
128
+ Enter: ['Enter', 'Enter', 13],
129
+ NumpadEnter: ['Enter', 'NumpadEnter', 13],
130
+ Escape: ['Escape', 'Escape', 27],
131
+ Tab: ['Tab', 'Tab', 9],
132
+ Backspace: ['Backspace', 'Backspace', 8],
133
+ Delete: ['Delete', 'Delete', 46],
134
+ ArrowUp: ['ArrowUp', 'ArrowUp', 38],
135
+ ArrowDown: ['ArrowDown', 'ArrowDown', 40],
136
+ ArrowLeft: ['ArrowLeft', 'ArrowLeft', 37],
137
+ ArrowRight: ['ArrowRight', 'ArrowRight', 39],
138
+ Home: ['Home', 'Home', 36],
139
+ End: ['End', 'End', 35],
140
+ PageUp: ['PageUp', 'PageUp', 33],
141
+ PageDown: ['PageDown', 'PageDown', 34],
142
+ Space: [' ', 'Space', 32],
143
+ };
144
+ class CyAgentLocator {
145
+ page;
146
+ ops;
147
+ index;
148
+ constructor(page, ops,
149
+ /** null = 集合本身(count/allInnerTexts 用全部);数字 = 选中项,-1 = last */
150
+ index = null) {
151
+ this.page = page;
152
+ this.ops = ops;
153
+ this.index = index;
154
+ }
155
+ extend(op, index = this.index) {
156
+ return new CyAgentLocator(this.page, [...this.ops, op], index);
157
+ }
158
+ withIndex(index) {
159
+ return new CyAgentLocator(this.page, this.ops, index);
160
+ }
161
+ /** 集合语义的索引:null(未约束)在执行动作时按 Playwright 的「首个匹配」处理 */
162
+ pickIndex() {
163
+ return this.index === null ? 0 : this.index;
164
+ }
165
+ resolveExpr() {
166
+ return `window.__keveAgent.resolve(${js(this.ops)})`;
167
+ }
168
+ async click(options) {
169
+ await this.page.callAgent('act', [this.ops, this.pickIndex(), 'click'], options);
170
+ }
171
+ async dblclick(options) {
172
+ await this.page.callAgent('act', [this.ops, this.pickIndex(), 'dblclick'], options);
173
+ }
174
+ async fill(value, options) {
175
+ await this.page.callAgent('act', [this.ops, this.pickIndex(), 'fill', { value }], options);
176
+ }
177
+ async hover(options) {
178
+ await this.page.callAgent('act', [this.ops, this.pickIndex(), 'hover'], options);
179
+ }
180
+ async pressSequentially(text, _options) {
181
+ await this.focus();
182
+ await this.page.keyboard.type(text);
183
+ }
184
+ async focus() {
185
+ await this.page.callAgent('focus', [this.ops, this.pickIndex()]);
186
+ }
187
+ async evaluate(fn, arg) {
188
+ const src = typeof fn === 'string' ? fn : String(fn);
189
+ return await this.page.callAgent('read', [this.ops, this.pickIndex(), 'eval', { fn: src, arg }]);
190
+ }
191
+ async textContent(_options) {
192
+ return await this.page.callAgent('read', [this.ops, this.pickIndex(), 'text']);
193
+ }
194
+ async innerText(_options) {
195
+ return (await this.page.callAgent('read', [this.ops, this.pickIndex(), 'innerText'])) ?? '';
196
+ }
197
+ async inputValue(_options) {
198
+ return (await this.page.callAgent('read', [this.ops, this.pickIndex(), 'value'])) ?? '';
199
+ }
200
+ async isChecked(_options) {
201
+ return !!await this.page.callAgent('read', [this.ops, this.pickIndex(), 'checked']);
202
+ }
203
+ async setChecked(checked, _options) {
204
+ await this.page.callAgent('act', [this.ops, this.pickIndex(), 'setChecked', { checked }]);
205
+ }
206
+ async selectOption(values, _options) {
207
+ const spec = typeof values === 'string'
208
+ ? { value: values }
209
+ : Array.isArray(values)
210
+ ? { value: values[0] }
211
+ : { value: values?.value, label: values?.label };
212
+ await this.page.callAgent('act', [this.ops, this.pickIndex(), 'selectOption', spec]);
213
+ return [spec.value ?? spec.label];
214
+ }
215
+ async waitFor(options) {
216
+ const state = String(options?.state || 'visible');
217
+ const timeout = Number(options?.timeout ?? 30_000);
218
+ const deadline = Date.now() + timeout;
219
+ for (;;) {
220
+ let ok = false;
221
+ if (state === 'detached') {
222
+ ok = !(await this.page.callAgent('exists', [this.ops, this.pickIndex()]).catch(() => false));
223
+ }
224
+ else if (state === 'hidden') {
225
+ const exists = await this.page.callAgent('exists', [this.ops, this.pickIndex()]).catch(() => false);
226
+ ok = !exists || !(await this.page.callAgent('visible', [this.ops, this.pickIndex()]).catch(() => false));
227
+ }
228
+ else if (state === 'attached') {
229
+ ok = await this.page.callAgent('exists', [this.ops, this.pickIndex()]).catch(() => false);
230
+ }
231
+ else {
232
+ ok = await this.page.callAgent('visible', [this.ops, this.pickIndex()]).catch(() => false);
233
+ }
234
+ if (ok)
235
+ return;
236
+ if (Date.now() >= deadline) {
237
+ throw new Error(`[keve] locator.waitFor(${state}) 超时(${timeout}ms): ${JSON.stringify(this.ops)}`);
238
+ }
239
+ await new Promise((r) => setTimeout(r, 100));
240
+ }
241
+ }
242
+ async boundingBox(_options) {
243
+ return await this.page.callAgent('read', [this.ops, this.pickIndex(), 'box']);
244
+ }
245
+ async getAttribute(name, _options) {
246
+ return await this.page.callAgent('read', [this.ops, this.pickIndex(), 'attr', name]);
247
+ }
248
+ async count() {
249
+ // count() 语义是「整个集合」,不受 first/last/nth 影响
250
+ return await this.page.callAgent('count', [this.ops, null]);
251
+ }
252
+ async allInnerTexts() {
253
+ return (await this.page.callAgent('texts', [this.ops, null])) || [];
254
+ }
255
+ async isVisible(_options) {
256
+ return !!await this.page.callAgent('visible', [this.ops, this.pickIndex()]);
257
+ }
258
+ async screenshot(_options) {
259
+ const box = await this.boundingBox();
260
+ if (box) {
261
+ return { base64: await this.page.cdp.screenshotClip(box) };
262
+ }
263
+ return await this.page.screenshot();
264
+ }
265
+ first() {
266
+ return this.withIndex(0);
267
+ }
268
+ last() {
269
+ return this.withIndex(-1);
270
+ }
271
+ nth(index) {
272
+ return this.withIndex(Number(index));
273
+ }
274
+ filter(options) {
275
+ if (options?.hasText !== undefined) {
276
+ return this.extend({ t: 'filterHasText', text: String(options.hasText) });
277
+ }
278
+ if (options?.visible !== undefined) {
279
+ return this.extend({ t: 'filterVisible' });
280
+ }
281
+ return this;
282
+ }
283
+ locator(selector) {
284
+ return this.extend(parseSelector(selector));
285
+ }
286
+ /** 仅调试用:暴露定位链 */
287
+ toOps() {
288
+ return [...this.ops];
289
+ }
290
+ /** 供 page.evaluate 之外的诊断使用 */
291
+ resolveHandleExpr() {
292
+ return this.resolveExpr();
293
+ }
294
+ }
295
+ exports.CyAgentLocator = CyAgentLocator;
296
+ class CyAgentPage {
297
+ cdp;
298
+ dialogHandler = null;
299
+ dialogPreference = null;
300
+ listeners = new Map();
301
+ constructor(cdp) {
302
+ this.cdp = cdp;
303
+ this.mouse = this.createMouse();
304
+ }
305
+ // ── 页内 agent 能力注入 ────────────────────────────────────────────
306
+ async ensureAgent() {
307
+ const ready = await this.cdp
308
+ .evaluateInAut('Boolean(window.__keveAgentSource === "v1")')
309
+ .catch(() => false);
310
+ if (ready)
311
+ return;
312
+ const { locatorSource } = await Promise.resolve().then(() => __importStar(require('../dsl/locatorSource.js')));
313
+ await this.cdp.evaluateInAut((0, agentDom_js_1.agentBootstrapSource)(locatorSource()));
314
+ }
315
+ /**
316
+ * 调用页内 `window.__keveAgent` 的某个方法。
317
+ *
318
+ * 刻意不走 `Runtime.callFunctionOn`:isolated world 里每次求值都是独立表达式,
319
+ * 用 JSON 组装调用最简单,且 agentDom 的入参全是可序列化的 ops/字面量。
320
+ */
321
+ async callAgent(method, args, _options) {
322
+ await this.ensureAgent();
323
+ const expr = `window.__keveAgent.${method}(${args.map((a) => js(a)).join(', ')})`;
324
+ try {
325
+ return await this.cdp.evaluateInAut(expr);
326
+ }
327
+ catch (err) {
328
+ // 名称/参数顺序错位会让 isolated world 重新注入,重试一次可避开一次性抖动
329
+ const msg = String(err?.message || err);
330
+ throw new Error(msg);
331
+ }
332
+ }
333
+ // ── AgentPage ─────────────────────────────────────────────────────
334
+ locator(selector) {
335
+ return new CyAgentLocator(this, [parseSelector(selector)]);
336
+ }
337
+ getByText(text, options) {
338
+ return new CyAgentLocator(this, [{
339
+ t: 'text',
340
+ text: String(text),
341
+ exact: options?.exact === true,
342
+ }]);
343
+ }
344
+ getByRole(role, options) {
345
+ return new CyAgentLocator(this, [{
346
+ t: 'role',
347
+ role: String(role),
348
+ name: options?.name === undefined ? undefined : String(options.name),
349
+ exact: options?.exact === true,
350
+ }]);
351
+ }
352
+ async ariaSnapshot(_options) {
353
+ await this.ensureAgent();
354
+ return (await this.cdp.evaluateInAut('window.__keveAgent.snapshot(1500)')) || '';
355
+ }
356
+ async url() {
357
+ return await this.cdp.currentUrl();
358
+ }
359
+ async title() {
360
+ return (await this.cdp.snapshot()).title || '';
361
+ }
362
+ async goto(url, _options) {
363
+ await this.cdp.goto(url);
364
+ // 导航会重建 AUT frame → 注入物一起丢失,下次调用 ensureAgent 时补回来
365
+ this.cdp.invalidateWorld();
366
+ }
367
+ async reload(_options) {
368
+ await this.cdp.reload();
369
+ this.cdp.invalidateWorld();
370
+ }
371
+ async evaluate(fn) {
372
+ // tools.ts 传进来的可能是函数,也可能是已拼好的 `(function(){…})()` 字符串
373
+ if (typeof fn === 'string')
374
+ return await this.cdp.evaluateInAut(fn);
375
+ const src = String(fn);
376
+ return await this.cdp.evaluateInAut(`(${src})()`);
377
+ }
378
+ /**
379
+ * 读取主世界诊断快照。
380
+ *
381
+ * Cypress 的 CDP 通道没有 domain event 订阅,主世界里的 console/网络采集
382
+ * 结果通过 `documentElement.dataset` 暴露给 isolated world —— 两个世界共享
383
+ * DOM,但不共享 JS 全局,这是唯一的稳定只读通道。
384
+ */
385
+ async readDiagnosticsSnapshot() {
386
+ try {
387
+ const allFrames = await this.cdp.frames().catch(() => []);
388
+ const targets = allFrames.filter((frame) => !(0, cdp_js_1.isRunnerFrameUrl)(frame.url));
389
+ const snaps = [];
390
+ for (const frame of targets) {
391
+ try {
392
+ // 让该 frame 的主世界先把 debounce 中的最后几条记录 flush 进 DOM 再读
393
+ await this.cdp.evaluateInFrame(frame.id, 'window.dispatchEvent(new Event("keve:flush-diagnostics")), "ok"').catch(() => undefined);
394
+ const raw = await this.cdp.evaluateInFrame(frame.id, 'String((document.documentElement && document.documentElement.dataset.keveDiagnostics) || "")');
395
+ if (!raw)
396
+ continue;
397
+ const parsed = JSON.parse(raw);
398
+ if (parsed && typeof parsed === 'object') {
399
+ snaps.push(parsed);
400
+ }
401
+ }
402
+ catch {
403
+ // 单个 frame 读取失败不丢弃其他 frame 的采集结果
404
+ }
405
+ }
406
+ return mergeFrameDiagnostics(snaps);
407
+ }
408
+ catch {
409
+ return undefined;
410
+ }
411
+ }
412
+ async screenshot(_options) {
413
+ return { base64: await this.cdp.screenshot() };
414
+ }
415
+ async viewportSize() {
416
+ const size = await this.cdp
417
+ .evaluateInAut('({ width: window.innerWidth, height: window.innerHeight })')
418
+ .catch(() => null);
419
+ if (size && size.width > 0 && size.height > 0)
420
+ return size;
421
+ return null;
422
+ }
423
+ /** 调整 AUT iframe 尺寸(只改 runner 里的 iframe 盒子,不触碰被测页 DOM) */
424
+ async setViewportSize(size) {
425
+ await this.cdp.evaluateInRunner(`(() => {
426
+ var f = document.querySelector('iframe.aut-iframe');
427
+ if (!f) return false;
428
+ f.style.width = ${js(`${size.width}px`)};
429
+ f.style.height = ${js(`${size.height}px`)};
430
+ return true;
431
+ })()`);
432
+ }
433
+ async waitForTimeout(ms) {
434
+ const wait = Number(ms);
435
+ if (!Number.isFinite(wait) || wait <= 0)
436
+ return;
437
+ await new Promise((r) => setTimeout(r, wait));
438
+ }
439
+ /**
440
+ * Cypress 下拿不到 Playwright 的页面事件流(Cypress.automation 只暴露命令,
441
+ * 不暴露 CDP 事件订阅)。这里保留注册表,让 agent 的 setupPageListeners 不炸;
442
+ * dialog 由 `setDialogPreference` + 原生 dialog 自动处理兜底。
443
+ */
444
+ on(event, handler) {
445
+ const list = this.listeners.get(event) || [];
446
+ list.push(handler);
447
+ this.listeners.set(event, list);
448
+ if (event === 'dialog')
449
+ this.dialogHandler = handler;
450
+ }
451
+ /** 供未来事件桥接使用;当前仅保证接口存在 */
452
+ emit(event, ...args) {
453
+ for (const h of this.listeners.get(event) || []) {
454
+ try {
455
+ h(...args);
456
+ }
457
+ catch { /* 监听器异常不影响主流程 */ }
458
+ }
459
+ }
460
+ setDialogPreference(accept, promptText) {
461
+ this.dialogPreference = { accept, promptText };
462
+ }
463
+ // ── 键盘 / 鼠标(CDP Input 域) ────────────────────────────────────
464
+ async dispatchKey(type, key, modifiers) {
465
+ const special = SPECIAL_KEYS[key];
466
+ const isSingle = key.length === 1;
467
+ const params = {
468
+ type,
469
+ modifiers,
470
+ key: special ? special[0] : key,
471
+ code: special ? special[1] : (isSingle ? `Key${key.toUpperCase()}` : key),
472
+ windowsVirtualKeyCode: special ? special[2] : (isSingle ? key.toUpperCase().charCodeAt(0) : 0),
473
+ nativeVirtualKeyCode: special ? special[2] : (isSingle ? key.toUpperCase().charCodeAt(0) : 0),
474
+ };
475
+ if (type === 'keyDown' && isSingle && modifiers === 0)
476
+ params.text = key;
477
+ await this.cdp.send('Input.dispatchKeyEvent', params);
478
+ }
479
+ parseKeyCombo(combo) {
480
+ const parts = String(combo || '').split('+').filter(Boolean);
481
+ let modifiers = 0;
482
+ while (parts.length > 1 && MODIFIER_BITS[parts[0]] !== undefined) {
483
+ modifiers |= MODIFIER_BITS[parts.shift()];
484
+ }
485
+ return { key: parts.join('+'), modifiers };
486
+ }
487
+ async topLevelPoint(x, y) {
488
+ const rect = await this.cdp.autRect().catch(() => null);
489
+ return { x: x + (rect?.x || 0), y: y + (rect?.y || 0) };
490
+ }
491
+ keyboard = {
492
+ press: async (combo, _options) => {
493
+ const { key, modifiers } = this.parseKeyCombo(combo);
494
+ await this.dispatchKey('keyDown', key, modifiers);
495
+ await this.dispatchKey('keyUp', key, modifiers);
496
+ },
497
+ type: async (text, _options) => {
498
+ // insertText 只走 composition 输入,不产生 keydown —— 对被测页等价于真实键入
499
+ await this.cdp.send('Input.insertText', { text: String(text) });
500
+ },
501
+ down: async (key) => {
502
+ const { key: k, modifiers } = this.parseKeyCombo(key);
503
+ await this.dispatchKey('keyDown', k, modifiers);
504
+ },
505
+ up: async (key) => {
506
+ const { key: k, modifiers } = this.parseKeyCombo(key);
507
+ await this.dispatchKey('keyUp', k, modifiers);
508
+ },
509
+ };
510
+ /** 最近一次鼠标坐标(AUT 视口坐标),供 down/up 复用 */
511
+ lastMouse = { x: 0, y: 0 };
512
+ mouse;
513
+ createMouse() {
514
+ const moveTo = async (x, y) => {
515
+ this.lastMouse = { x: Number(x) || 0, y: Number(y) || 0 };
516
+ const p = await this.topLevelPoint(this.lastMouse.x, this.lastMouse.y);
517
+ await this.cdp.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x: p.x, y: p.y });
518
+ };
519
+ const buttonAt = async (type, buttons, options) => {
520
+ const p = await this.topLevelPoint(this.lastMouse.x, this.lastMouse.y);
521
+ await this.cdp.send('Input.dispatchMouseEvent', {
522
+ type,
523
+ x: p.x,
524
+ y: p.y,
525
+ button: String(options?.button || 'left'),
526
+ buttons,
527
+ clickCount: Number(options?.clickCount || 1),
528
+ });
529
+ };
530
+ return {
531
+ click: async (x, y, options) => {
532
+ await moveTo(x, y);
533
+ const button = String(options?.button || 'left');
534
+ const clickCount = Number(options?.clickCount || 1);
535
+ const p = await this.topLevelPoint(x, y);
536
+ await this.cdp.send('Input.dispatchMouseEvent', {
537
+ type: 'mousePressed', x: p.x, y: p.y, button, buttons: 1, clickCount,
538
+ });
539
+ await this.cdp.send('Input.dispatchMouseEvent', {
540
+ type: 'mouseReleased', x: p.x, y: p.y, button, buttons: 0, clickCount,
541
+ });
542
+ },
543
+ move: moveTo,
544
+ down: (options) => buttonAt('mousePressed', 1, options),
545
+ up: (options) => buttonAt('mouseReleased', 0, options),
546
+ wheel: async (deltaX, deltaY) => {
547
+ const p = await this.topLevelPoint(this.lastMouse.x, this.lastMouse.y);
548
+ await this.cdp.send('Input.dispatchMouseEvent', {
549
+ type: 'mouseWheel',
550
+ x: p.x,
551
+ y: p.y,
552
+ deltaX: Number(deltaX) || 0,
553
+ deltaY: Number(deltaY) || 0,
554
+ });
555
+ },
556
+ };
557
+ }
558
+ dispose() {
559
+ this.listeners.clear();
560
+ this.dialogHandler = null;
561
+ }
562
+ }
563
+ exports.CyAgentPage = CyAgentPage;
564
+ function createCyAgentPage(cdp) {
565
+ return new CyAgentPage(cdp);
566
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * agentDom — Cypress 侧 Re-Act agent 的页内能力源码
3
+ *
4
+ * 与 locatorSource.ts / pageOps.ts 同样以「自包含源码字符串」形式注入 AUT 的
5
+ * isolated world:页内函数无法闭包引用 bundle 变量,只能整段送过去求值。
6
+ *
7
+ * 职责:
8
+ * snapshot() 生成与 Playwright `ariaSnapshot({mode:'ai'})` 同形的文本,
9
+ * 并为每个可见节点分配稳定 ref(`e1`、`e2`…),
10
+ * 供 `locator('aria-ref=eN')` 解析。
11
+ * resolve() locator 链求值(css / aria-ref / text / role / 过滤),
12
+ * 语义对齐 Playwright 的后代搜索 + 集合传递。
13
+ * act/read 元素动作与读取(点击/输入/勾选/下拉/属性/坐标/可见性)。
14
+ *
15
+ * 之所以不复用 CDP 的 `Accessibility.getFullAXTree`:AX 树的 backendDOMNodeId
16
+ * 需要在 AUT isolated world 里二次解析成元素,链路更长且多一次跨进程往返;
17
+ * 页内快照可以直接持有元素引用,ref 天然稳定。
18
+ */
19
+ /** 注入页内的 agent DOM 能力源码(幂等:重复注入直接返回 cached) */
20
+ export declare function agentDomSource(): string;
21
+ /** locatorSource + pageOps + agentDom 的一次性注入 */
22
+ export declare function agentBootstrapSource(locatorSrc: string): string;