@kkeve/suite 0.0.15 → 0.0.18

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 (115) 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 +12 -11
  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 +49 -15
  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 +204 -200
  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 +82 -7
  82. package/dist/page-agent/agent.js +252 -88
  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/tools.d.ts +1 -1
  94. package/dist/page-agent/tools.js +144 -8
  95. package/package.json +24 -11
  96. package/scripts/gen-system-prompt.mjs +36 -0
  97. package/scripts/run-dsl-task.mjs +652 -0
  98. package/scripts/runtime/cypress-preprocessor.mjs +44 -0
  99. package/scripts/runtime/cypress.config.mjs +35 -0
  100. package/scripts/runtime/playwright.config.mjs +41 -0
  101. package/scripts/verify-shot-diff.mjs +243 -0
  102. package/dist/commands/auth-update.d.ts +0 -16
  103. package/dist/commands/auth-update.js +0 -88
  104. package/dist/core/auth.d.ts +0 -17
  105. package/dist/core/auth.js +0 -224
  106. package/dist/core/cdp-resolver.d.ts +0 -5
  107. package/dist/core/cdp-resolver.js +0 -93
  108. package/dist/core/global-setup.d.ts +0 -6
  109. package/dist/core/global-setup.js +0 -123
  110. package/dist/core/keve-decorators.d.ts +0 -51
  111. package/dist/core/keve-decorators.js +0 -95
  112. package/dist/core/keve-report.js +0 -376
  113. /package/dist/core/{keve-aspect.d.ts → decorator/keve-aspect.d.ts} +0 -0
  114. /package/dist/core/{keve-aspect.js → decorator/keve-aspect.js} +0 -0
  115. /package/dist/{core → engine-playwright}/pw-config.d.ts +0 -0
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ /**
3
+ * shotBuffer — 截图对比结果的 goal 级缓冲(浏览器侧)
4
+ *
5
+ * core/screenshotDiff.ts 是 Node 模块(依赖 fs),不能被打进 Cypress 的浏览器
6
+ * bundle。但结构必须一致:keveGoal 要把本 goal 内所有对比结果(matched /
7
+ * baseline_required / exceeded)写进 attachment 的 screenshotDiff 字段。
8
+ * 这里只做「暂存 + 取出」,真正的对比在 Node 侧经 bridge 完成。
9
+ */
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.CypressScreenshotDiffError = void 0;
12
+ exports.beginGoalShotDiffCapture = beginGoalShotDiffCapture;
13
+ exports.isGoalShotDiffCaptureActive = isGoalShotDiffCaptureActive;
14
+ exports.drainGoalShotDiff = drainGoalShotDiff;
15
+ exports.recordShotDiff = recordShotDiff;
16
+ /** 与 Playwright 的 ScreenshotDiffError 同形:带 shotDiff 结构,keveGoal 据此短路 */
17
+ class CypressScreenshotDiffError extends Error {
18
+ shotDiff;
19
+ constructor(result) {
20
+ super(result.message);
21
+ this.name = 'ScreenshotDiffError';
22
+ this.shotDiff = result;
23
+ }
24
+ }
25
+ exports.CypressScreenshotDiffError = CypressScreenshotDiffError;
26
+ let buffer = null;
27
+ function beginGoalShotDiffCapture() {
28
+ buffer = [];
29
+ }
30
+ function isGoalShotDiffCaptureActive() {
31
+ return buffer !== null;
32
+ }
33
+ function drainGoalShotDiff() {
34
+ const out = buffer || [];
35
+ buffer = null;
36
+ return out;
37
+ }
38
+ function recordShotDiff(result) {
39
+ if (buffer)
40
+ buffer.push(result);
41
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * state — Cypress spec 内的「当前用例」执行状态(浏览器侧)
3
+ *
4
+ * Cypress 没有 Playwright 那样的 fixture 作用域,装饰器宿主、keveGoal、
5
+ * 报告收集三者需要共享同一份状态。这里用模块级单例承载,
6
+ * 由 host.ts 在 beforeEach 里重置、在测试体收口时读取。
7
+ *
8
+ * 约束:零依赖(会被打进 Cypress 浏览器 bundle)。
9
+ */
10
+ /** 与 Playwright 附件同形的内存附件(body 为 JSON 字符串) */
11
+ export interface CyAttachment {
12
+ name: string;
13
+ contentType: string;
14
+ body: string;
15
+ }
16
+ export interface CyTestState {
17
+ /** 用例完整标题(`${id}: ${description}`),报告匹配的规范键 */
18
+ title: string;
19
+ /** spec 文件路径(仅展示用) */
20
+ file: string;
21
+ /** 当前用例的附件集合(keveGoalResult 等) */
22
+ attachments: CyAttachment[];
23
+ /** 当前用例的执行日志(对齐 Playwright 侧 test-results.json 的 stdout 字段) */
24
+ logs: string[];
25
+ /** 应用侧未捕获异常(由 Cypress uncaught:exception 事件收集) */
26
+ uncaughtErrors: string[];
27
+ /** keveGoal 在当前用例内的序号 */
28
+ goalOrder: number;
29
+ /** 是否被 skip(前置条件不满足 → blocked,不是 fail) */
30
+ skipReason: string;
31
+ }
32
+ export declare function beginTestState(title: string, file?: string): CyTestState;
33
+ export declare function getTestState(): CyTestState | null;
34
+ /** 取当前状态;不在用例内时抛出可诊断的错误 */
35
+ export declare function requireTestState(): CyTestState;
36
+ export declare function endTestState(): CyTestState | null;
37
+ export declare function addAttachment(att: CyAttachment): void;
38
+ /** 记录应用侧未捕获异常(Cypress uncaught:exception 事件) */
39
+ export declare function addUncaughtError(message: string): void;
40
+ /**
41
+ * 记录一行执行日志。
42
+ *
43
+ * Cypress spec 跑在浏览器里,console.log 不会进入 Node 侧进程输出,
44
+ * 因此 Playwright 那份 stdout 轨迹在 Cypress 下会缺失。这里显式收集,
45
+ * 由 reporter 随结果一起送回 Node,保证两端报告同构。
46
+ */
47
+ export declare function addLog(line: string): void;
48
+ export declare function nextGoalOrder(): number;
49
+ /**
50
+ * 把 JSON 文本编码成 base64。
51
+ *
52
+ * 必须这么做:Playwright 的 JSON reporter 会把 attachment 的 Buffer body 序列化成
53
+ * base64 字符串,reportData.ts 也是无条件按 base64 解码。Cypress 侧直接写明文 JSON
54
+ * 会导致报告里的 keveGoalResult 解析不出来(截图与步骤全丢)。
55
+ */
56
+ export declare function encodeAttachmentBody(json: string): string;
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ /**
3
+ * state — Cypress spec 内的「当前用例」执行状态(浏览器侧)
4
+ *
5
+ * Cypress 没有 Playwright 那样的 fixture 作用域,装饰器宿主、keveGoal、
6
+ * 报告收集三者需要共享同一份状态。这里用模块级单例承载,
7
+ * 由 host.ts 在 beforeEach 里重置、在测试体收口时读取。
8
+ *
9
+ * 约束:零依赖(会被打进 Cypress 浏览器 bundle)。
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.beginTestState = beginTestState;
13
+ exports.getTestState = getTestState;
14
+ exports.requireTestState = requireTestState;
15
+ exports.endTestState = endTestState;
16
+ exports.addAttachment = addAttachment;
17
+ exports.addUncaughtError = addUncaughtError;
18
+ exports.addLog = addLog;
19
+ exports.nextGoalOrder = nextGoalOrder;
20
+ exports.encodeAttachmentBody = encodeAttachmentBody;
21
+ let current = null;
22
+ function beginTestState(title, file = '') {
23
+ current = { title, file, attachments: [], logs: [], uncaughtErrors: [], goalOrder: 0, skipReason: '' };
24
+ return current;
25
+ }
26
+ function getTestState() {
27
+ return current;
28
+ }
29
+ /** 取当前状态;不在用例内时抛出可诊断的错误 */
30
+ function requireTestState() {
31
+ if (!current) {
32
+ throw new Error('[keve] 当前不在用例执行上下文中:keveGoal 必须在 @keveScene 方法体内调用');
33
+ }
34
+ return current;
35
+ }
36
+ function endTestState() {
37
+ const out = current;
38
+ current = null;
39
+ return out;
40
+ }
41
+ function addAttachment(att) {
42
+ requireTestState().attachments.push(att);
43
+ }
44
+ /** 记录应用侧未捕获异常(Cypress uncaught:exception 事件) */
45
+ function addUncaughtError(message) {
46
+ const state = getTestState();
47
+ if (!state)
48
+ return;
49
+ state.uncaughtErrors.push(message);
50
+ }
51
+ /**
52
+ * 记录一行执行日志。
53
+ *
54
+ * Cypress spec 跑在浏览器里,console.log 不会进入 Node 侧进程输出,
55
+ * 因此 Playwright 那份 stdout 轨迹在 Cypress 下会缺失。这里显式收集,
56
+ * 由 reporter 随结果一起送回 Node,保证两端报告同构。
57
+ */
58
+ function addLog(line) {
59
+ const state = getTestState();
60
+ if (!state)
61
+ return;
62
+ state.logs.push(line.endsWith('\n') ? line : `${line}\n`);
63
+ }
64
+ function nextGoalOrder() {
65
+ const state = requireTestState();
66
+ return state.goalOrder++;
67
+ }
68
+ /**
69
+ * 把 JSON 文本编码成 base64。
70
+ *
71
+ * 必须这么做:Playwright 的 JSON reporter 会把 attachment 的 Buffer body 序列化成
72
+ * base64 字符串,reportData.ts 也是无条件按 base64 解码。Cypress 侧直接写明文 JSON
73
+ * 会导致报告里的 keveGoalResult 解析不出来(截图与步骤全丢)。
74
+ */
75
+ function encodeAttachmentBody(json) {
76
+ const bytes = new TextEncoder().encode(json);
77
+ let binary = '';
78
+ for (let i = 0; i < bytes.length; i += 1)
79
+ binary += String.fromCharCode(bytes[i]);
80
+ return btoa(binary);
81
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * engineAdapter — 引擎无关操作接口 + Playwright 实现
3
+ *
4
+ * 所有测试脚本通过 engine.* 调用操作,不直接使用 page.locator / page.click。
5
+ * 未来可替换为其他引擎实现(如 WebDriver)。
6
+ */
7
+ import type { Page } from '@playwright/test';
8
+ import { type ExpectScreenshotOptions, type ShotDiffResult } from '../core/judge/screenshotDiff.js';
9
+ import type { ElementRef, ElementEntry, DefinedPage } from '../core/page-object/definePage.js';
10
+ import type { EngineAdapter, WaitForResponseOptions, WaitForResponseResult } from '../core/page-object/engine-types.js';
11
+ export type { EngineAdapter, WaitForResponseOptions, WaitForResponseResult } from '../core/page-object/engine-types.js';
12
+ export { KEVE_ENV, resolvePageUrl } from '../core/page-object/engine-types.js';
13
+ export declare class PlaywrightEngine implements EngineAdapter {
14
+ private page;
15
+ constructor(page: Page);
16
+ navigate(url: string): Promise<void>;
17
+ openPage(page: DefinedPage, overrides?: Record<string, string>): Promise<void>;
18
+ callScene(sceneId: string, keveGoal?: unknown): Promise<void>;
19
+ waitForReady(defPage: DefinedPage): Promise<void>;
20
+ /**
21
+ * 等待指定响应 —— 配置型视觉对比场景的「就绪门」。
22
+ *
23
+ * 与 waitForReady.api 的差异:不需要 DefinedPage(配置型场景没有页面对象),
24
+ * 并且能拿到响应体做渲染健康断言(老平台 interceptUrl 的 statuses 语义)。
25
+ * 默认**软等待**(超时不阻塞,与老平台 cy.wait 后继续的容错一致),required=true 时硬失败。
26
+ */
27
+ waitForResponse(urlPart: string | RegExp, opts?: WaitForResponseOptions): Promise<WaitForResponseResult>;
28
+ private resolve;
29
+ click(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
30
+ dblClick(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
31
+ forceClick(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
32
+ clear(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
33
+ drag(source: ElementRef | ElementEntry, target: ElementRef | ElementEntry): Promise<void>;
34
+ scroll(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
35
+ wait(ms: number): Promise<void>;
36
+ waitForElement(selector: string, opts?: {
37
+ timeout?: number;
38
+ visible?: boolean;
39
+ }): Promise<{
40
+ found: boolean;
41
+ }>;
42
+ refresh(): Promise<void>;
43
+ evaluate(expression: string): Promise<any>;
44
+ screenshot(name: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
45
+ expectScreenshot(key: string, opts?: ExpectScreenshotOptions, ...refs: (ElementRef | ElementEntry)[]): Promise<ShotDiffResult>;
46
+ type(text: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
47
+ hover(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
48
+ text(...refs: (ElementRef | ElementEntry)[]): Promise<string>;
49
+ expectVisible(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
50
+ expectHidden(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
51
+ expectText(expected: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
52
+ expectNotText(expected: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
53
+ expectTextEquals(expected: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
54
+ expectTextNotEquals(expected: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
55
+ expectCount(count: number, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
56
+ expectNonEmptyCount(count: number, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
57
+ expectExists(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
58
+ expectNotExists(...refs: (ElementRef | ElementEntry)[]): Promise<void>;
59
+ expectClass(className: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
60
+ expectNotClass(className: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
61
+ expectAttribute(attr: string, value: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
62
+ expectAttributeContains(attr: string, value: string, ...refs: (ElementRef | ElementEntry)[]): Promise<void>;
63
+ expectUrlContains(part: string): Promise<void>;
64
+ expectUrlNotContains(part: string): Promise<void>;
65
+ expectUrlEquals(url: string): Promise<void>;
66
+ expectUrlNotEquals(url: string): Promise<void>;
67
+ expectTitle(part: string): Promise<void>;
68
+ expectTitleEquals(title: string): Promise<void>;
69
+ rawPage(): Page;
70
+ ariaSnapshot(options?: {
71
+ mode?: string;
72
+ }): Promise<string>;
73
+ injectCookies(cookies: any[]): Promise<void>;
74
+ }
75
+ /**
76
+ * 从 Playwright page 创建 engine 实例
77
+ */
78
+ export declare function createEngine(page: Page): EngineAdapter;
@@ -0,0 +1,371 @@
1
+ "use strict";
2
+ /**
3
+ * engineAdapter — 引擎无关操作接口 + Playwright 实现
4
+ *
5
+ * 所有测试脚本通过 engine.* 调用操作,不直接使用 page.locator / page.click。
6
+ * 未来可替换为其他引擎实现(如 WebDriver)。
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.PlaywrightEngine = exports.resolvePageUrl = exports.KEVE_ENV = void 0;
43
+ exports.createEngine = createEngine;
44
+ const test_1 = require("@playwright/test");
45
+ const elementResolver_js_1 = require("../core/page-object/elementResolver.js");
46
+ const screenshotDiff_js_1 = require("../core/judge/screenshotDiff.js");
47
+ const definePage_js_1 = require("../core/page-object/definePage.js");
48
+ const engine_types_js_1 = require("../core/page-object/engine-types.js");
49
+ var engine_types_js_2 = require("../core/page-object/engine-types.js");
50
+ Object.defineProperty(exports, "KEVE_ENV", { enumerable: true, get: function () { return engine_types_js_2.KEVE_ENV; } });
51
+ Object.defineProperty(exports, "resolvePageUrl", { enumerable: true, get: function () { return engine_types_js_2.resolvePageUrl; } });
52
+ // ─── Playwright Engine Implementation ─────────────────────────────────
53
+ class PlaywrightEngine {
54
+ page;
55
+ constructor(page) {
56
+ this.page = page;
57
+ }
58
+ async navigate(url) {
59
+ await this.page.goto(url, { waitUntil: 'domcontentloaded' });
60
+ }
61
+ async openPage(page, overrides) {
62
+ await this.navigate((0, engine_types_js_1.resolvePageUrl)(page, overrides));
63
+ }
64
+ async callScene(sceneId, keveGoal) {
65
+ const { sceneImplMap } = await Promise.resolve().then(() => __importStar(require('../core/decorator/keve-registry.js')));
66
+ const fn = sceneImplMap.get(String(sceneId));
67
+ if (!fn) {
68
+ throw new Error(`callScene: 场景 '${sceneId}' 未注册(可能被排除或不存在)`);
69
+ }
70
+ await fn({ engine: this, keveGoal });
71
+ }
72
+ async waitForReady(defPage) {
73
+ const { waitForReady } = defPage;
74
+ if (!waitForReady)
75
+ return;
76
+ // 等待 API
77
+ if (waitForReady.api) {
78
+ await this.page.waitForResponse((resp) => resp.url().includes(waitForReady.api), { timeout: 15000 }).catch(() => {
79
+ // API 等待失败不阻塞,继续等 UI
80
+ });
81
+ }
82
+ // 等待 UI 元素
83
+ if (waitForReady.ui) {
84
+ const entry = defPage.elements[waitForReady.ui];
85
+ if (entry && !(0, definePage_js_1.isElementFactory)(entry)) {
86
+ const locator = (0, elementResolver_js_1.resolveElement)(this.page, entry);
87
+ await locator.waitFor({ state: 'visible', timeout: 10000 });
88
+ }
89
+ }
90
+ }
91
+ /**
92
+ * 等待指定响应 —— 配置型视觉对比场景的「就绪门」。
93
+ *
94
+ * 与 waitForReady.api 的差异:不需要 DefinedPage(配置型场景没有页面对象),
95
+ * 并且能拿到响应体做渲染健康断言(老平台 interceptUrl 的 statuses 语义)。
96
+ * 默认**软等待**(超时不阻塞,与老平台 cy.wait 后继续的容错一致),required=true 时硬失败。
97
+ */
98
+ async waitForResponse(urlPart, opts = {}) {
99
+ const timeout = opts.timeout ?? 15000;
100
+ const started = Date.now();
101
+ const miss = { hit: false, url: '', status: 0, body: null, elapsedMs: 0 };
102
+ try {
103
+ const resp = await this.page.waitForResponse((r) => {
104
+ const urlOk = typeof urlPart === 'string' ? r.url().includes(urlPart) : urlPart.test(r.url());
105
+ if (!urlOk)
106
+ return false;
107
+ if (opts.method && r.request().method().toUpperCase() !== opts.method.toUpperCase())
108
+ return false;
109
+ if (opts.status?.length && !opts.status.includes(r.status()))
110
+ return false;
111
+ return true;
112
+ }, { timeout });
113
+ let body = null;
114
+ try {
115
+ const text = await resp.text();
116
+ if (text && text.length <= 2_000_000) {
117
+ try {
118
+ body = JSON.parse(text);
119
+ }
120
+ catch {
121
+ body = text;
122
+ }
123
+ }
124
+ }
125
+ catch { /* 响应体不可读(重定向/流)不影响就绪判定 */ }
126
+ if (opts.afterMs && opts.afterMs > 0)
127
+ await this.page.waitForTimeout(opts.afterMs);
128
+ return { hit: true, url: resp.url(), status: resp.status(), body, elapsedMs: Date.now() - started };
129
+ }
130
+ catch (err) {
131
+ if (opts.required) {
132
+ throw new Error(`waitForResponse 超时(${timeout}ms):未等到匹配「${String(urlPart)}」的响应 —— ${err?.message || err}`);
133
+ }
134
+ console.warn(`[engine.waitForResponse] 软等待超时(${timeout}ms),继续执行:${String(urlPart)}`);
135
+ return { ...miss, elapsedMs: Date.now() - started };
136
+ }
137
+ }
138
+ resolve(refs) {
139
+ // 规范化:工厂函数需要已调用,这里不做工厂调用
140
+ const normalized = refs.map((r) => {
141
+ if ((0, definePage_js_1.isElementFactory)(r)) {
142
+ throw new Error('Factory function must be called before passing to engine. e.g. engine.click(page.elements.itemByName("时间"))');
143
+ }
144
+ return r;
145
+ });
146
+ return (0, elementResolver_js_1.resolveChain)(this.page, ...normalized);
147
+ }
148
+ async click(...refs) {
149
+ const locator = this.resolve(refs);
150
+ await locator.first().click();
151
+ }
152
+ async dblClick(...refs) {
153
+ const locator = this.resolve(refs);
154
+ await locator.first().dblclick();
155
+ }
156
+ async forceClick(...refs) {
157
+ const locator = this.resolve(refs);
158
+ await locator.first().click({ force: true });
159
+ }
160
+ async clear(...refs) {
161
+ const locator = this.resolve(refs);
162
+ await locator.first().fill('');
163
+ }
164
+ async drag(source, target) {
165
+ const src = this.resolve([source]).first();
166
+ const dst = this.resolve([target]).first();
167
+ await src.dragTo(dst);
168
+ }
169
+ async scroll(...refs) {
170
+ if (refs.length === 0) {
171
+ await this.page.mouse.wheel(0, 600);
172
+ return;
173
+ }
174
+ const locator = this.resolve(refs);
175
+ await locator.first().scrollIntoViewIfNeeded();
176
+ }
177
+ async wait(ms) {
178
+ await this.page.waitForTimeout(Math.max(0, Number(ms) || 0));
179
+ }
180
+ async waitForElement(selector, opts = {}) {
181
+ const timeout = opts.timeout ?? 10000;
182
+ const state = opts.visible === false ? 'hidden' : 'visible';
183
+ try {
184
+ await this.page.locator(selector).first().waitFor({ state, timeout });
185
+ return { found: true };
186
+ }
187
+ catch {
188
+ return { found: false };
189
+ }
190
+ }
191
+ async refresh() {
192
+ await this.page.reload({ waitUntil: 'domcontentloaded' });
193
+ }
194
+ async evaluate(expression) {
195
+ // 平台 WRITE_GLOBAL 的 javascript hop 是表达式形态(如 'UI自动化看板'+new Date().getTime())
196
+ return await this.page.evaluate(`(() => (${expression}))()`);
197
+ }
198
+ async screenshot(name, ...refs) {
199
+ await this.expectScreenshot(name, {}, ...refs);
200
+ }
201
+ async expectScreenshot(key, opts = {}, ...refs) {
202
+ const { test: pwTest } = await Promise.resolve().then(() => __importStar(require('@playwright/test')));
203
+ // keveGoal 未开缓冲时(如探索/调试直调),就地开一个临时缓冲,保证结果也能入 attachment
204
+ const owned = !(0, screenshotDiff_js_1.isGoalShotDiffCaptureActive)();
205
+ if (owned)
206
+ (0, screenshotDiff_js_1.beginGoalShotDiffCapture)();
207
+ try {
208
+ const buf = refs.length > 0
209
+ ? await this.resolve(refs).first().screenshot()
210
+ : await this.page.screenshot({ fullPage: !!opts.fullPage });
211
+ // 统一走 compareWithBaseline:其内部按 key+env 判定
212
+ // - 精确视口无基线、同 key 也无其它视口基线 → 建候选基线(status=baseline_required,不算通过)
213
+ // - 同 key 有其它视口基线 → size_mismatch(尺寸变化不自举,杜绝掩盖变化)
214
+ // - 候选基线未审核 → baseline_required(不比对不判通过)
215
+ // - active 基线 → 像素比对(matched / exceeded,超阈值抛 ScreenshotDiffError)
216
+ const result = (0, screenshotDiff_js_1.compareWithBaseline)(key, buf, { threshold: opts.threshold, mask: opts.mask });
217
+ // 记录已下沉至 screenshotDiff 模块(成功路径 + 抛错路径均自动记录)
218
+ // 原始截图仍挂附件(证据双保险:报告附件 + test-artifacts 路径);
219
+ // 测试上下文外(独立验证脚本)test.info() 会抛错 —— 对比功能不受影响,仅跳过附件
220
+ try {
221
+ await pwTest.info()?.attach(`screenshot-${key}`, { body: buf, contentType: 'image/png' });
222
+ }
223
+ catch { /* 非测试上下文:跳过附件 */ }
224
+ return result;
225
+ }
226
+ finally {
227
+ if (owned)
228
+ (0, screenshotDiff_js_1.drainGoalShotDiff)();
229
+ }
230
+ }
231
+ async type(text, ...refs) {
232
+ const locator = this.resolve(refs);
233
+ await locator.first().fill(text);
234
+ }
235
+ async hover(...refs) {
236
+ const locator = this.resolve(refs);
237
+ await locator.first().hover();
238
+ }
239
+ async text(...refs) {
240
+ const locator = this.resolve(refs);
241
+ return await locator.first().textContent() ?? '';
242
+ }
243
+ async expectVisible(...refs) {
244
+ const locator = this.resolve(refs);
245
+ await (0, test_1.expect)(locator.first()).toBeVisible();
246
+ }
247
+ async expectHidden(...refs) {
248
+ const locator = this.resolve(refs);
249
+ await (0, test_1.expect)(locator.first()).toBeHidden();
250
+ }
251
+ async expectText(expected, ...refs) {
252
+ const locator = this.resolve(refs);
253
+ await (0, test_1.expect)(locator.first()).toContainText(expected);
254
+ }
255
+ async expectNotText(expected, ...refs) {
256
+ const locator = this.resolve(refs);
257
+ await (0, test_1.expect)(locator.first()).not.toContainText(expected);
258
+ }
259
+ async expectTextEquals(expected, ...refs) {
260
+ const locator = this.resolve(refs);
261
+ await (0, test_1.expect)(locator.first()).toHaveText(expected);
262
+ }
263
+ async expectTextNotEquals(expected, ...refs) {
264
+ const locator = this.resolve(refs);
265
+ await (0, test_1.expect)(locator.first()).not.toHaveText(expected);
266
+ }
267
+ async expectCount(count, ...refs) {
268
+ const locator = this.resolve(refs);
269
+ await (0, test_1.expect)(locator).toHaveCount(count);
270
+ }
271
+ async expectNonEmptyCount(count, ...refs) {
272
+ const locator = this.resolve(refs);
273
+ // 历史语义(老平台 EXPECTED_VALUE ctype=index):匹配到的元素里,
274
+ // 文本非空的有几个。空文本元素不计入 —— 直接 toHaveCount 会把占位节点也算进去。
275
+ await test_1.expect
276
+ .poll(async () => {
277
+ const texts = await locator.allTextContents();
278
+ return texts.filter((t) => String(t).trim().length > 0).length;
279
+ })
280
+ .toBe(count);
281
+ }
282
+ async expectExists(...refs) {
283
+ const locator = this.resolve(refs);
284
+ await (0, test_1.expect)(locator.first()).toBeAttached();
285
+ }
286
+ async expectNotExists(...refs) {
287
+ const locator = this.resolve(refs);
288
+ await (0, test_1.expect)(locator).toHaveCount(0);
289
+ }
290
+ async expectClass(className, ...refs) {
291
+ const locator = this.resolve(refs);
292
+ // 检查元素 class 属性包含指定 class
293
+ const classAttr = await locator.first().getAttribute('class') ?? '';
294
+ if (!classAttr.split(/\s+/).includes(className)) {
295
+ throw new Error(`Element does not have class '${className}'. Actual classes: '${classAttr}'`);
296
+ }
297
+ }
298
+ async expectNotClass(className, ...refs) {
299
+ const locator = this.resolve(refs);
300
+ const classAttr = await locator.first().getAttribute('class') ?? '';
301
+ if (classAttr.split(/\s+/).includes(className)) {
302
+ throw new Error(`Element should not have class '${className}'. Actual classes: '${classAttr}'`);
303
+ }
304
+ }
305
+ async expectAttribute(attr, value, ...refs) {
306
+ const locator = this.resolve(refs);
307
+ await (0, test_1.expect)(locator.first()).toHaveAttribute(attr, value);
308
+ }
309
+ async expectAttributeContains(attr, value, ...refs) {
310
+ const locator = this.resolve(refs);
311
+ const actual = await locator.first().getAttribute(attr);
312
+ if (actual === null || !actual.includes(value)) {
313
+ throw new Error(`Element attribute '${attr}' should contain '${value}'. Actual: ${JSON.stringify(actual)}`);
314
+ }
315
+ }
316
+ async expectUrlContains(part) {
317
+ await (0, test_1.expect)(this.page).toHaveURL(new RegExp(escapeRegExp(part)));
318
+ }
319
+ async expectUrlNotContains(part) {
320
+ const url = this.page.url();
321
+ if (url.includes(part)) {
322
+ throw new Error(`URL '${url}' should not contain '${part}'`);
323
+ }
324
+ }
325
+ async expectUrlEquals(url) {
326
+ await (0, test_1.expect)(this.page).toHaveURL(url);
327
+ }
328
+ async expectUrlNotEquals(url) {
329
+ const actual = this.page.url();
330
+ if (actual === url) {
331
+ throw new Error(`URL should not equal '${url}'`);
332
+ }
333
+ }
334
+ async expectTitle(part) {
335
+ await (0, test_1.expect)(this.page).toHaveTitle(new RegExp(escapeRegExp(part)));
336
+ }
337
+ async expectTitleEquals(title) {
338
+ await (0, test_1.expect)(this.page).toHaveTitle(title);
339
+ }
340
+ rawPage() {
341
+ return this.page;
342
+ }
343
+ async ariaSnapshot(options) {
344
+ // 使用 Playwright 的 aria snapshot (--yarn/1.49+)
345
+ // 回退到 page.locator('body').innerText() 作为简化快照
346
+ try {
347
+ const snapshot = await this.page.locator('body').ariaSnapshot();
348
+ return snapshot ?? '';
349
+ }
350
+ catch {
351
+ // 旧版 Playwright 回退
352
+ return await this.page.locator('body').innerText();
353
+ }
354
+ }
355
+ async injectCookies(cookies) {
356
+ const context = this.page.context();
357
+ await context.addCookies(cookies);
358
+ }
359
+ }
360
+ exports.PlaywrightEngine = PlaywrightEngine;
361
+ // ─── Factory ──────────────────────────────────────────────────────────
362
+ /** RegExp 元字符转义(expectUrlContains 用) */
363
+ function escapeRegExp(s) {
364
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
365
+ }
366
+ /**
367
+ * 从 Playwright page 创建 engine 实例
368
+ */
369
+ function createEngine(page) {
370
+ return new PlaywrightEngine(page);
371
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * global-setup — 登录态装配(纯数据准备,不启动浏览器)
3
+ *
4
+ * 设计调整(与平台执行链路对齐):
5
+ * - 不再由测试侧启动 chromium,也不再通过 CDP 接管已开浏览器。
6
+ * - 登录态由**服务端**解析(eve-backend account-resolver → SSO cookies),
7
+ * 执行前以环境变量下发,这里只负责把 cookie 写成 Playwright storageState。
8
+ *
9
+ * 环境变量:
10
+ * KEVE_SSO_COOKIES — JSON 字符串化的 SsoCookie[](服务端解析结果,首选)
11
+ * KEVE_STORAGE_STATE — storageState 输出路径(默认 ./.auth/storage-state.json)
12
+ * KEVE_TARGET_URL — 目标站点 URL(用于派生 cookie domain,缺省 kuaishou.com)
13
+ *
14
+ * 兼容:KEVE_SSO_COOKIES_FILE 指向一个包含同样 JSON 的文件。
15
+ */
16
+ import type { SsoCookie } from '../core/sso-cookie.js';
17
+ /** 由 storageState 路径解析(默认 .auth/storage-state.json) */
18
+ export declare function resolveStorageStatePath(): string;
19
+ /** 目标站点派生 cookie domain:https://foo.kuaishou.com/x → .kuaishou.com */
20
+ export declare function deriveCookieDomain(targetUrl?: string): string;
21
+ /** 读取服务端下发的 cookies(环境变量优先,其次文件) */
22
+ export declare function readInjectedCookies(): SsoCookie[];
23
+ /**
24
+ * 写出 storageState(只写文件,不启动浏览器)。
25
+ * 返回实际写入的 cookie 数,供执行侧日志/断言使用。
26
+ */
27
+ export declare function writeStorageState(storageStatePath: string): number;
28
+ export default function (): Promise<void>;