@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,480 @@
1
+ "use strict";
2
+ /**
3
+ * host — 把 Cypress 的 describe/it 适配成 KeveHost
4
+ *
5
+ * 关键约束(探针结论,见 EVE/docs/cypress-engine-cdp-findings.md):
6
+ * 1. 用例体整体跑在 `cy.then({ timeout }, async () => …)` 内,内部**零 cy 命令**,
7
+ * 全靠 CDP + 原生 DOM。默认 cy.then 超时只有 4000ms,必须显式放大。
8
+ * 2. 必须先 `cy.visit(base)` 把 AUT iframe 建起来,否则 CDP 找不到 frame。
9
+ * 3. `cy.task` 在 async cy.then 内不可 await —— Node 侧能力一律走 HTTP 桥。
10
+ *
11
+ * 这样同一份 @keveModel/@keveScene 脚本在 Playwright 与 Cypress 下注册出的
12
+ * 场景元数据完全一致,报告才能同构。
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.resolveBaseUrl = resolveBaseUrl;
49
+ exports.createCypressEngine = createCypressEngine;
50
+ exports.installCypressHost = installCypressHost;
51
+ exports.getBlockedReason = getBlockedReason;
52
+ const keve_host_js_1 = require("../core/decorator/keve-host.js");
53
+ const cdp_js_1 = require("./cdp.js");
54
+ const adapter_js_1 = require("./adapter.js");
55
+ const state_js_1 = require("./state.js");
56
+ const keveGoal_js_1 = require("./keveGoal.js");
57
+ const agent_page_js_1 = require("./agent-page.js");
58
+ const agent_js_1 = require("../page-agent/agent.js");
59
+ const diagnostics_js_1 = require("../page-agent/diagnostics.js");
60
+ /** 单个用例体允许的最长执行时间(与 Playwright 侧 timeout 对齐) */
61
+ const CY_TEST_TIMEOUT_MS = Number(process.env.KEVE_CY_TEST_TIMEOUT || 300_000);
62
+ /** 单个 goal 的探索上限(与 Playwright keve-test.ts 的 GOAL_TIMEOUT_MS 对齐) */
63
+ const CY_GOAL_TIMEOUT_MS = 300_000;
64
+ /**
65
+ * Cypress 的运行时对象(`Cypress`):env / config / automation / currentTest / spec。
66
+ * 注意它**不是**命令链,不能 `.visit()` / `.wrap()`。
67
+ */
68
+ function cy() {
69
+ const c = globalThis.Cypress;
70
+ if (!c)
71
+ throw new Error('[cypress-engine] 未找到 Cypress 运行时:本模块只能在 Cypress spec 中运行');
72
+ return c;
73
+ }
74
+ /**
75
+ * 命令链 `cy`:visit / then / wrap 等。
76
+ * 与运行时对象严格区分 —— `Cypress.visit` 不存在,混用会报 "cy(...).visit is not a function"。
77
+ * 注意 Cypress 12 里 `cy` 是**可调用对象**(typeof === 'object'),所以只能按能力探测。
78
+ */
79
+ function cyChain() {
80
+ const chain = globalThis.cy;
81
+ if (!chain || typeof chain.visit !== 'function') {
82
+ throw new Error('[cypress-engine] 未找到 cy 命令链:本模块只能在 Cypress spec 中运行');
83
+ }
84
+ return chain;
85
+ }
86
+ /** AUT 打底 URL:优先 cypress.config baseUrl,其次 KEVE_TARGET_URL */
87
+ function resolveBaseUrl() {
88
+ const fromConfig = String(cy().config?.('baseUrl') || '');
89
+ if (/^https?:\/\//.test(fromConfig))
90
+ return fromConfig;
91
+ const fromEnv = String(cy().env?.('KEVE_TARGET_URL') || '');
92
+ if (/^https?:\/\//.test(fromEnv))
93
+ return fromEnv;
94
+ throw new Error('[cypress-engine] 缺少 baseUrl:请在 cypress.config 设置 baseUrl,或注入 KEVE_TARGET_URL 环境变量');
95
+ }
96
+ /** 服务端解析好的 SSO cookie(经 config.env 下发,浏览器侧可读) */
97
+ function readInjectedCookies() {
98
+ const raw = cy().env?.('KEVE_SSO_COOKIES');
99
+ if (!raw)
100
+ return [];
101
+ try {
102
+ const parsed = typeof raw === 'string' ? JSON.parse(raw) : raw;
103
+ return Array.isArray(parsed) ? parsed : [];
104
+ }
105
+ catch {
106
+ console.warn('[cypress-engine] KEVE_SSO_COOKIES 解析失败,按未登录执行');
107
+ return [];
108
+ }
109
+ }
110
+ function createCypressEngine(bridge = (0, adapter_js_1.createBridge)()) {
111
+ const send = (command, params) => cy().automation('remote:debugger:protocol', { command, params: params || {} });
112
+ return new adapter_js_1.CypressEngine((0, cdp_js_1.createCdp)(send), bridge);
113
+ }
114
+ // ─── Agent Re-Act(P0-3:Cypress 侧与 Playwright 同构) ─────────────────
115
+ /** 浏览器侧读环境变量:cy.env 优先,其次非敏感 config.env 兜底。 */
116
+ function readBrowserEnv(name) {
117
+ const raw = cy().env?.(name);
118
+ return raw === undefined || raw === null ? '' : String(raw);
119
+ }
120
+ /**
121
+ * Cypress 侧的 AgentPage 宿主能力(与 core/keve-test.ts 的注入一一对应)。
122
+ *
123
+ * 与 Playwright 的唯一差异:Node 能力全部经桥往返 ——
124
+ * - 截图落盘 → `/shot/save`
125
+ * - 读初始截图 → `/fs/read-binary`
126
+ * - LLM 调用 → `/llm/chat`(apiKey 不出 Node)
127
+ */
128
+ function createCyAgentHost(bridge) {
129
+ const targetUrl = readBrowserEnv('KEVE_TARGET_URL') || resolveBaseUrl();
130
+ const getEnv = (name) => {
131
+ const direct = readBrowserEnv(name);
132
+ if (direct)
133
+ return direct;
134
+ // 兼容 Node 侧命名差异:LLM_BASE_URL → KEVE_LLM_BASE_URL
135
+ const alias = {
136
+ KEVE_LLM_BASE_URL: 'LLM_BASE_URL',
137
+ KEVE_LLM_MODEL_NAME: 'LLM_MODEL_NAME',
138
+ KEVE_LLM_MODEL: 'LLM_MODEL_NAME',
139
+ KEVE_LLM_API_KEY: 'LLM_API_KEY',
140
+ KEVE_TARGET_URL: 'BASE_URL',
141
+ };
142
+ return alias[name] ? readBrowserEnv(alias[name]) : '';
143
+ };
144
+ const saveScreenshot = async (pngBase64, stepIndex, stepName) => {
145
+ // 命名规则与 Playwright 的 saveAgentScreenshot 保持一致(桥自动追加时间戳)
146
+ const safeName = String(stepName || 'step').replace(/[^a-zA-Z0-9一-鿿]/g, '_').slice(0, 30);
147
+ const resp = await bridge.post('/shot/save', {
148
+ name: `agent-step${stepIndex}-${safeName}`,
149
+ pngBase64,
150
+ });
151
+ return resp?.ok ? String(resp.path || '') || undefined : undefined;
152
+ };
153
+ const loadInitialImages = async (paths) => {
154
+ const items = [];
155
+ for (const [label, rel] of [
156
+ ['goal-before', paths.goalScreenshotBefore],
157
+ ['fn-after', paths.fnAfterScreenshot],
158
+ ]) {
159
+ if (!rel)
160
+ continue;
161
+ try {
162
+ const resp = await bridge.post('/fs/read-binary', { file: rel });
163
+ if (!resp?.ok || !resp.base64)
164
+ continue;
165
+ items.push({ type: 'text', text: `Screenshot (${label}):` });
166
+ items.push({ type: 'image_url', image_url: { url: `data:image/png;base64,${resp.base64}` } });
167
+ }
168
+ catch { /* 单张图读失败不影响探索 */ }
169
+ }
170
+ return items;
171
+ };
172
+ /**
173
+ * LLM 配置:baseURL/model 下发浏览器,调用经桥转发回 Node。
174
+ *
175
+ * 刻意不传 apiKey —— `@kkeve/core` 只在 apiKey 非空时才拼 Authorization,
176
+ * 这里留空即可;真正的鉴权头由桥在 Node 侧补齐。
177
+ */
178
+ const customFetch = (async (url, init) => {
179
+ const headers = {};
180
+ try {
181
+ new Headers(init?.headers).forEach((v, k) => { headers[k] = v; });
182
+ }
183
+ catch { /* headers 不可枚举时留空 */ }
184
+ const resp = await bridge.post('/llm/chat', {
185
+ url: String(url),
186
+ method: String(init?.method || 'POST'),
187
+ headers,
188
+ body: typeof init?.body === 'string' ? init.body : JSON.stringify(init?.body ?? {}),
189
+ });
190
+ if (!resp?.ok)
191
+ throw new Error(resp?.error || 'llm/chat 桥调用失败');
192
+ return new Response(String(resp.body ?? ''), {
193
+ status: Number(resp.status) || 200,
194
+ statusText: String(resp.statusText || ''),
195
+ headers: (resp.headers || {}),
196
+ });
197
+ });
198
+ return {
199
+ targetUrl,
200
+ getEnv,
201
+ saveScreenshot,
202
+ loadInitialImages,
203
+ llm: {
204
+ baseURL: readBrowserEnv('KEVE_LLM_BASE_URL') || getEnv('KEVE_LLM_BASE_URL'),
205
+ model: readBrowserEnv('KEVE_LLM_MODEL_NAME') || getEnv('KEVE_LLM_MODEL_NAME'),
206
+ customFetch,
207
+ },
208
+ };
209
+ }
210
+ /**
211
+ * 注册 Cypress 侧的 goal 探索器:fn 成功/失败都用同一个 Re-Act 循环做最终判定。
212
+ *
213
+ * 必须在**每次 test 内**重新注册 —— 探索器闭包绑定了本次 test 的 engine/bridge/
214
+ * 超时控制,跨用例复用会污染到已结束的 CDP 通道。
215
+ */
216
+ function installGoalExplorer(engine, bridge) {
217
+ const cdp = engine.rawPage();
218
+ const host = createCyAgentHost(bridge);
219
+ (0, keveGoal_js_1.setCyGoalExplorer)(async (options, ctx) => {
220
+ const page = new agent_page_js_1.CyAgentPage(cdp);
221
+ const controller = new AbortController();
222
+ const timer = setTimeout(() => controller.abort(), CY_GOAL_TIMEOUT_MS);
223
+ try {
224
+ const result = await (0, agent_js_1.reactLoop)(page, options.step, options.expected, {
225
+ specFilePath: ctx.specFilePath,
226
+ fnSource: ctx.fnSource,
227
+ // fn 只是前置确定性校验:成功时也要让 Agent 重新走一遍 Re-Act 给出
228
+ // 最终结论,否则报告里不会产生 AI 推理轨迹。
229
+ fnResult: ctx.fnError ? { error: ctx.fnError } : { success: true },
230
+ signal: controller.signal,
231
+ goalScreenshotBefore: ctx.goalScreenshotBefore,
232
+ fnAfterScreenshot: ctx.fnAfterScreenshot,
233
+ targetUrl: host.targetUrl,
234
+ getEnv: host.getEnv,
235
+ saveScreenshot: host.saveScreenshot,
236
+ loadInitialImages: host.loadInitialImages,
237
+ llm: host.llm,
238
+ });
239
+ /**
240
+ * Cypress 侧 reactLoop 的原生页面事件监听不会产生数据(CyAgentPage 不派发
241
+ * console/network 事件)。真正的采集在主世界预加载脚本里完成,这里读取
242
+ * DOM 快照,把页面级性能与浏览器/网络样本补进同一份诊断结构。
243
+ */
244
+ let diagnostics = result.diagnostics;
245
+ try {
246
+ const snap = await page.readDiagnosticsSnapshot();
247
+ if (snap) {
248
+ const pageDiag = (0, diagnostics_js_1.buildRuntimeDiagnostics)({
249
+ consoleRecords: snap.browser,
250
+ networkRecords: snap.network,
251
+ pageMetrics: snap.page,
252
+ });
253
+ diagnostics = (0, diagnostics_js_1.mergeRuntimeDiagnostics)([result.diagnostics || (0, diagnostics_js_1.emptyRuntimeDiagnostics)(), pageDiag]);
254
+ }
255
+ }
256
+ catch { /* 诊断读取失败不阻断用例 */ }
257
+ return {
258
+ expectedMet: result.expectedMet,
259
+ conclusion: result.conclusion,
260
+ actions: result.actions,
261
+ finalSnapshot: result.finalSnapshot,
262
+ agentScreenshots: result.agentScreenshots,
263
+ diagnosticHints: result.diagnosticHints,
264
+ diagnostics,
265
+ };
266
+ }
267
+ finally {
268
+ clearTimeout(timer);
269
+ page.dispose();
270
+ }
271
+ });
272
+ }
273
+ /** 模块级前置校验失败 → 该 describe 下所有 Scene 跳过(blocked,不是 fail) */
274
+ const blockedSuites = new Map();
275
+ /**
276
+ * 当前用例所属的模块 describe 标题(`M_SG90061: …`)。
277
+ *
278
+ * Cypress 12 的 `currentTest` 上**没有** `titlePath`(探针实测),必须沿 Mocha
279
+ * `parent` 链取最靠近用例的非 root 祖先标题。取空会让 blockedSuites 全部落到同一个
280
+ * 空键上 —— 模块前置校验失败时既不能精准跳过,又会跨 spec 残留污染其他用例。
281
+ */
282
+ function currentSuiteTitle() {
283
+ const titles = [];
284
+ let node = cy().currentTest?.parent;
285
+ while (node && !node.root) {
286
+ if (node.title)
287
+ titles.push(String(node.title));
288
+ node = node.parent;
289
+ }
290
+ return titles.length ? titles[titles.length - 1] : '';
291
+ }
292
+ /**
293
+ * 安装 Cypress → KeveHost 适配,并注册装饰器宿主。
294
+ *
295
+ * 幂等:重复调用(多次 import)不会重复注册钩子。
296
+ */
297
+ let installed = false;
298
+ function installCypressHost() {
299
+ if (installed)
300
+ return host;
301
+ installed = true;
302
+ const c = cy();
303
+ // 应用侧未捕获异常不放过测试继续执行:记录错误并交给 reporter 归为失败,
304
+ // 否则单一页面运行时异常会让整个用例在 keveGoal 收尾前被 Cypress 中断,
305
+ // 报告拿不到截图/推理日志/真实步骤。
306
+ c.on('uncaught:exception', (err) => {
307
+ (0, state_js_1.addUncaughtError)(String(err?.message || err?.stack || err));
308
+ return false;
309
+ });
310
+ const mochaDescribe = c.describe || globalThis.describe;
311
+ const mochaIt = c.it || globalThis.it;
312
+ const mochaBefore = c.before || globalThis.before;
313
+ const mochaBeforeEach = c.beforeEach || globalThis.beforeEach;
314
+ const mochaAfter = c.after || globalThis.after;
315
+ const mochaAfterEach = c.afterEach || globalThis.afterEach;
316
+ // 每个用例开始:重置状态容器(附件 / goal 序号 / skip 标记)
317
+ mochaBeforeEach(function () {
318
+ (0, state_js_1.beginTestState)(cy().currentTest?.title || '', cy().spec?.relative || '');
319
+ const reason = blockedSuites.get(currentSuiteTitle());
320
+ if (reason)
321
+ this.skip(reason);
322
+ });
323
+ host = {
324
+ describe: (title, fn) => mochaDescribe(title, fn),
325
+ // Cypress 的执行天然串行,serial 与普通 describe 等价
326
+ describeSerial: (title, fn) => mochaDescribe(title, fn),
327
+ beforeAll: (fn) => {
328
+ // 模块前置校验:失败不抛(抛会让所有 Scene 变 failed),
329
+ // 而是记住原因,交给每个 Scene 的 beforeEach 统一 skip。
330
+ mochaBefore(async function () {
331
+ const suite = currentSuiteTitle();
332
+ try {
333
+ await fn();
334
+ }
335
+ catch (err) {
336
+ const reason = err?.message || String(err);
337
+ if (suite)
338
+ blockedSuites.set(suite, reason);
339
+ console.warn(`[cypress-engine] 模块前置校验未通过,跳过 "${suite}":${reason}`);
340
+ }
341
+ });
342
+ },
343
+ beforeEach: (fn) => {
344
+ mochaBeforeEach(async function () {
345
+ const title = cy().currentTest?.title || '';
346
+ const info = {
347
+ title,
348
+ skip: (reason) => this.skip(reason),
349
+ };
350
+ await fn(info);
351
+ });
352
+ },
353
+ afterEach: (fn) => {
354
+ mochaAfterEach(async function () {
355
+ const title = cy().currentTest?.title || '';
356
+ const info = {
357
+ title,
358
+ skip: (reason) => this.skip(reason),
359
+ };
360
+ await fn(info);
361
+ });
362
+ },
363
+ afterAll: (fn) => {
364
+ mochaAfter(async function () {
365
+ await fn();
366
+ });
367
+ },
368
+ /**
369
+ * 注册一个 Scene 用例。
370
+ * 场景方法体形如 `({ engine, keveGoal }) => …`,这里装配同形 fixture。
371
+ */
372
+ test: (title, fn) => {
373
+ mochaIt(title, function () {
374
+ const state = (0, state_js_1.requireTestState)();
375
+ const base = resolveBaseUrl();
376
+ /**
377
+ * 登录态:服务端解析好的 cookie 经 config.env 下发。
378
+ *
379
+ * 必须在**首次 cy.visit 之前**注入:目标站未登录时会把首个导航 302 到
380
+ * SSO 登录页,随后的 Re-Act 会拿到登录页截图并尝试用密码登录(必然失败,
381
+ * 报告里表现为 "SSO login failed … credentials rejected")。
382
+ * Playwright 侧由 storageState 在 context 创建时就带好 cookie,没有这个
383
+ * 竞态;Cypress 侧只能在命令队列里把注入排到 visit 前面。
384
+ *
385
+ * 该步骤跑在 Cypress 的 test body 内 —— Cypress 已在用例开始前清过 cookie,
386
+ * 此处写入的 cookie 不会被再次清掉。
387
+ */
388
+ const injectCookiesBeforeVisit = async () => {
389
+ const cookies = readInjectedCookies();
390
+ if (!cookies.length) {
391
+ console.warn('[cypress-engine] 未收到 KEVE_SSO_COOKIES,按未登录执行(可能跳转 SSO 登录页)');
392
+ return;
393
+ }
394
+ try {
395
+ const engine = createCypressEngine((0, adapter_js_1.createBridge)());
396
+ await engine.injectCookies(cookies);
397
+ console.log(`[cypress-engine] 首次导航前已注入 SSO cookie ${cookies.length} 条`);
398
+ }
399
+ catch (err) {
400
+ console.warn(`[cypress-engine] cookie 注入失败(继续执行):${err?.message || err}`);
401
+ }
402
+ };
403
+ return cyChain()
404
+ .then({ timeout: CY_TEST_TIMEOUT_MS }, injectCookiesBeforeVisit)
405
+ /**
406
+ * 唯一的 cy 命令:把 AUT iframe 建起来(CDP 通道依赖它)。
407
+ *
408
+ * `base` 只作为占位首页,真实目标由用例内的 `engine.navigate` 决定。
409
+ * 部分环境(如 rc-eve 根域)即便带上有效 SSO cookie 也会返回 403,
410
+ * 若用默认校验会让整条用例在 bootstrap 阶段就失败 —— 这里关掉状态码
411
+ * 校验,只要 iframe 建起来即可;注入的 cookie 已在访问前写好,不会丢。
412
+ */
413
+ .then({ timeout: CY_TEST_TIMEOUT_MS }, () => cyChain().visit(base, { failOnStatusCode: false }))
414
+ .then({ timeout: CY_TEST_TIMEOUT_MS }, async () => {
415
+ const bridge = (0, adapter_js_1.createBridge)();
416
+ const engine = createCypressEngine(bridge);
417
+ /**
418
+ * 给后续每次新文档预注册浏览器运行时 API 兜底。
419
+ *
420
+ * 现状:部分业务页面(如 kwaibi 多维)会直接调用 rAF,而 Cypress 的
421
+ * `<iframe.aut-iframe>` 主世界没有带出该实现,页面脚本会在导航后抛
422
+ * `window.requestAnimationFrame is not a function`。Playwright 上下文
423
+ * 自带完整 DOM 运行时,所以两边差异只在 Cypress 侧暴露。
424
+ */
425
+ try {
426
+ await engine.rawPage().installPreloadScript(`
427
+ (() => {
428
+ if (typeof window.requestAnimationFrame !== 'function') {
429
+ window.requestAnimationFrame = function (cb) {
430
+ return setTimeout(function () { cb(Date.now()); }, 16);
431
+ };
432
+ }
433
+ if (typeof window.cancelAnimationFrame !== 'function') {
434
+ window.cancelAnimationFrame = function (id) { clearTimeout(id); };
435
+ }
436
+ })();
437
+ `);
438
+ }
439
+ catch (err) {
440
+ console.warn(`[cypress-engine] rAF polyfill 预注册失败:${err?.message || err}`);
441
+ }
442
+ /**
443
+ * 运行诊断采集:必须在**主世界**注册(isolated world 看不到应用主世界的
444
+ * console / fetch),且要早于业务脚本执行。通过
445
+ * `Page.addScriptToEvaluateOnNewDocument` 保证每次导航后自动重放。
446
+ */
447
+ try {
448
+ const { diagnosticsSource } = await Promise.resolve().then(() => __importStar(require('./pageOps.js')));
449
+ await engine.rawPage().installPreloadScript(diagnosticsSource());
450
+ }
451
+ catch (err) {
452
+ console.warn(`[cypress-engine] 诊断采集预注册失败:${err?.message || err}`);
453
+ }
454
+ // Agent Re-Act:fn 成功/失败都由同一个 Re-Act 循环判定(per-test 注册,避免跨用例污染)
455
+ installGoalExplorer(engine, bridge);
456
+ const keveGoal = (0, keveGoal_js_1.createCyKeveGoal)(engine);
457
+ const testInfo = {
458
+ title: state.title,
459
+ file: state.file,
460
+ skip: () => { },
461
+ };
462
+ try {
463
+ await fn({ engine, keveGoal, testInfo });
464
+ }
465
+ finally {
466
+ // 用例结束即摘掉探索器:闭包绑定的 CDP 通道已随 frame 失效
467
+ (0, keveGoal_js_1.setCyGoalExplorer)(null);
468
+ }
469
+ });
470
+ });
471
+ },
472
+ };
473
+ (0, keve_host_js_1.setKeveHost)(host);
474
+ return host;
475
+ }
476
+ let host;
477
+ /** 模块级前置校验的跳过原因(供 reporter 归因展示) */
478
+ function getBlockedReason(suiteTitle) {
479
+ return blockedSuites.get(suiteTitle);
480
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @kkeve/suite/engine-cypress — Cypress 引擎出口(浏览器侧)
3
+ *
4
+ * 与 `@kkeve/suite/keve-test`(Playwright)**同构**:脚本只换 import 路径,
5
+ * 装饰器、keveGoal、附件契约、报告产物全部一致。
6
+ *
7
+ * // Playwright
8
+ * import { keveModel, keveScene } from '@kkeve/suite/keve-test';
9
+ * // Cypress
10
+ * import { keveModel, keveScene } from '@kkeve/suite/engine-cypress';
11
+ *
12
+ * ⚠️ 本模块被打进 Cypress 的**浏览器 bundle**:不得 import bridge.ts / cdp 之外
13
+ * 任何 Node 模块,也不得 import 任何会间接拉入 `@playwright/test`(含
14
+ * playwright-core → node:fs)的文件。Node 侧入口单独走 `engine-cypress/setup`。
15
+ */
16
+ import '../core/decorator/keve-decorators.js';
17
+ export { keveModel, keveScene, getModelScenes, setKeveTestHost, getKeveTest, hasKeveTest, } from '../core/decorator/keve-decorators.js';
18
+ export type { KeveSceneMeta, KeveModelOptions, KeveSceneOptions } from '../core/decorator/keve-decorators.js';
19
+ export { sceneCodeMap, sceneEvalMetaMap, sceneMetaMap, sceneGoalsMap, sceneImplMap, } from '../core/decorator/keve-registry.js';
20
+ export type { KeveGoalMeta, KeveEvalMeta, ErrorCategory, KeveSceneStaticMeta } from '../core/decorator/keve-registry.js';
21
+ export { createCyKeveGoal, setCyGoalExplorer, type KeveGoalCallOptions, type CyGoalExploreResult, type CyGoalExplorer, type CyKeveGoal, } from './keveGoal.js';
22
+ export { CypressEngine, createBridge } from './adapter.js';
23
+ export { createCdp, pollUntil } from './cdp.js';
24
+ export { createCypressEngine, resolveBaseUrl } from './host.js';
25
+ export { getKeveHost, setKeveHost, hasKeveHost } from '../core/decorator/keve-host.js';
26
+ export type { KeveHost, KeveTestInfo } from '../core/decorator/keve-host.js';
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ /**
3
+ * @kkeve/suite/engine-cypress — Cypress 引擎出口(浏览器侧)
4
+ *
5
+ * 与 `@kkeve/suite/keve-test`(Playwright)**同构**:脚本只换 import 路径,
6
+ * 装饰器、keveGoal、附件契约、报告产物全部一致。
7
+ *
8
+ * // Playwright
9
+ * import { keveModel, keveScene } from '@kkeve/suite/keve-test';
10
+ * // Cypress
11
+ * import { keveModel, keveScene } from '@kkeve/suite/engine-cypress';
12
+ *
13
+ * ⚠️ 本模块被打进 Cypress 的**浏览器 bundle**:不得 import bridge.ts / cdp 之外
14
+ * 任何 Node 模块,也不得 import 任何会间接拉入 `@playwright/test`(含
15
+ * playwright-core → node:fs)的文件。Node 侧入口单独走 `engine-cypress/setup`。
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.hasKeveHost = exports.setKeveHost = exports.getKeveHost = exports.resolveBaseUrl = exports.createCypressEngine = exports.pollUntil = exports.createCdp = exports.createBridge = exports.CypressEngine = exports.setCyGoalExplorer = exports.createCyKeveGoal = exports.sceneImplMap = exports.sceneGoalsMap = exports.sceneMetaMap = exports.sceneEvalMetaMap = exports.sceneCodeMap = exports.hasKeveTest = exports.getKeveTest = exports.setKeveTestHost = exports.getModelScenes = exports.keveScene = exports.keveModel = void 0;
19
+ require("../core/decorator/keve-decorators.js");
20
+ const host_js_1 = require("./host.js");
21
+ const reporter_js_1 = require("./reporter.js");
22
+ // 安装顺序:结果收集钩子先注册(保证 afterEach 顺序在外层),再装宿主。
23
+ (0, reporter_js_1.installCypressReporter)();
24
+ (0, host_js_1.installCypressHost)();
25
+ // ── 装饰器(引擎无关,与 Playwright 侧同一实现) ──
26
+ var keve_decorators_js_1 = require("../core/decorator/keve-decorators.js");
27
+ Object.defineProperty(exports, "keveModel", { enumerable: true, get: function () { return keve_decorators_js_1.keveModel; } });
28
+ Object.defineProperty(exports, "keveScene", { enumerable: true, get: function () { return keve_decorators_js_1.keveScene; } });
29
+ Object.defineProperty(exports, "getModelScenes", { enumerable: true, get: function () { return keve_decorators_js_1.getModelScenes; } });
30
+ Object.defineProperty(exports, "setKeveTestHost", { enumerable: true, get: function () { return keve_decorators_js_1.setKeveTestHost; } });
31
+ Object.defineProperty(exports, "getKeveTest", { enumerable: true, get: function () { return keve_decorators_js_1.getKeveTest; } });
32
+ Object.defineProperty(exports, "hasKeveTest", { enumerable: true, get: function () { return keve_decorators_js_1.hasKeveTest; } });
33
+ // ── 场景注册表(报告与组合用例共用) ──
34
+ var keve_registry_js_1 = require("../core/decorator/keve-registry.js");
35
+ Object.defineProperty(exports, "sceneCodeMap", { enumerable: true, get: function () { return keve_registry_js_1.sceneCodeMap; } });
36
+ Object.defineProperty(exports, "sceneEvalMetaMap", { enumerable: true, get: function () { return keve_registry_js_1.sceneEvalMetaMap; } });
37
+ Object.defineProperty(exports, "sceneMetaMap", { enumerable: true, get: function () { return keve_registry_js_1.sceneMetaMap; } });
38
+ Object.defineProperty(exports, "sceneGoalsMap", { enumerable: true, get: function () { return keve_registry_js_1.sceneGoalsMap; } });
39
+ Object.defineProperty(exports, "sceneImplMap", { enumerable: true, get: function () { return keve_registry_js_1.sceneImplMap; } });
40
+ // ── goal 原语与探索器注册 ──
41
+ var keveGoal_js_1 = require("./keveGoal.js");
42
+ Object.defineProperty(exports, "createCyKeveGoal", { enumerable: true, get: function () { return keveGoal_js_1.createCyKeveGoal; } });
43
+ Object.defineProperty(exports, "setCyGoalExplorer", { enumerable: true, get: function () { return keveGoal_js_1.setCyGoalExplorer; } });
44
+ // ── 引擎适配器(AI Re-Act / 自定义场景可直接使用) ──
45
+ var adapter_js_1 = require("./adapter.js");
46
+ Object.defineProperty(exports, "CypressEngine", { enumerable: true, get: function () { return adapter_js_1.CypressEngine; } });
47
+ Object.defineProperty(exports, "createBridge", { enumerable: true, get: function () { return adapter_js_1.createBridge; } });
48
+ var cdp_js_1 = require("./cdp.js");
49
+ Object.defineProperty(exports, "createCdp", { enumerable: true, get: function () { return cdp_js_1.createCdp; } });
50
+ Object.defineProperty(exports, "pollUntil", { enumerable: true, get: function () { return cdp_js_1.pollUntil; } });
51
+ var host_js_2 = require("./host.js");
52
+ Object.defineProperty(exports, "createCypressEngine", { enumerable: true, get: function () { return host_js_2.createCypressEngine; } });
53
+ Object.defineProperty(exports, "resolveBaseUrl", { enumerable: true, get: function () { return host_js_2.resolveBaseUrl; } });
54
+ // ── 宿主已安装(副作用),此处保持与 Playwright 侧一致的导入体验 ──
55
+ var keve_host_js_1 = require("../core/decorator/keve-host.js");
56
+ Object.defineProperty(exports, "getKeveHost", { enumerable: true, get: function () { return keve_host_js_1.getKeveHost; } });
57
+ Object.defineProperty(exports, "setKeveHost", { enumerable: true, get: function () { return keve_host_js_1.setKeveHost; } });
58
+ Object.defineProperty(exports, "hasKeveHost", { enumerable: true, get: function () { return keve_host_js_1.hasKeveHost; } });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * keveGoal — Cypress 版的唯一操作原语
3
+ *
4
+ * 与 Playwright 的 keveGoal fixture(core/keve-test.ts)**语义同构**:
5
+ * - 记录 goal 元数据到 sceneGoalsMap(报告归因用)
6
+ * - fn 前 / fn 后各截一张图,落盘后写进 attachment
7
+ * - fn 成功/失败都进入 Agent Re-Act,由观察真实页面后给最终结论;
8
+ * 仅截图差异超阈值、环境阻断这两类确定性事实短路
9
+ * - 产物是 `keveGoalResult` attachment,字段与 Playwright 完全一致
10
+ *
11
+ * 唯一的实现差异:截图与落盘必须经 HTTP 桥(cy.task 在 async cy.then 内不可 await)。
12
+ * 所以这里不 import 任何 Node 模块,也不 import @playwright/test。
13
+ */
14
+ import type { CypressEngine } from './adapter.js';
15
+ import type { RuntimeDiagnostics } from '../page-agent/diagnostics.js';
16
+ export interface KeveGoalCallOptions {
17
+ precondition?: string;
18
+ step: string;
19
+ expected: string;
20
+ }
21
+ /** agent Re-Act 的结果(P0-3 接入;未接入时由 fn 结果直接判定) */
22
+ export interface CyGoalExploreResult {
23
+ expectedMet: boolean;
24
+ conclusion?: 'pass' | 'fail' | 'blocked';
25
+ actions?: any[];
26
+ finalSnapshot?: string;
27
+ agentScreenshots?: string[];
28
+ diagnosticHints?: string[];
29
+ /** 运行诊断:性能、网络、浏览器错误与质量信号 */
30
+ diagnostics?: RuntimeDiagnostics;
31
+ }
32
+ export type CyGoalExplorer = (options: KeveGoalCallOptions, ctx: {
33
+ fnSource?: string;
34
+ fnError?: string;
35
+ goalScreenshotBefore?: string;
36
+ fnAfterScreenshot?: string;
37
+ /** 当前 spec 文件路径(探索器记录上下文用,可为空) */
38
+ specFilePath?: string;
39
+ testTitle?: string;
40
+ }) => Promise<CyGoalExploreResult | undefined>;
41
+ export declare function setCyGoalExplorer(fn: CyGoalExplorer | null): void;
42
+ export type CyKeveGoal = (options: KeveGoalCallOptions, fn?: () => Promise<any>) => Promise<any>;
43
+ export declare function createCyKeveGoal(engine: CypressEngine): CyKeveGoal;