@yule-h2o/web_auto_flow 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
File without changes
package/README.md ADDED
@@ -0,0 +1,156 @@
1
+ # H2O Auto Flow
2
+
3
+ > Playwright + Stagehand AI — 端到端自动化测试框架
4
+
5
+ 写脚本像写普通代码,填表/点击用原生 API(零成本),找不到选择器时让 AI 接管(按需付费)。
6
+
7
+ ```ts
8
+ import { definePlan, runPlan, isMain } from "h2o-auto-flow";
9
+
10
+ const plan = definePlan("登录测试", async ({ page, has, click, fill, screenshot, sleep }) => {
11
+ await page.goto("https://example.com/login");
12
+ await sleep(2000);
13
+
14
+ await fill('input[type="email"]', "test@test.com");
15
+ await fill('input[type="password"]', "123456");
16
+ await click('[type="submit"]');
17
+ await sleep(3000);
18
+
19
+ if (await has(".dashboard")) {
20
+ await screenshot("登录成功");
21
+ }
22
+ });
23
+
24
+ if (isMain(import.meta.url)) await runPlan(plan);
25
+ export default plan;
26
+ ```
27
+
28
+ ## 安装
29
+
30
+ ```bash
31
+ # npm
32
+ npm i h2o-auto-flow
33
+ # pnpm
34
+ pnpm add h2o-auto-flow
35
+ ```
36
+
37
+ ## 配置
38
+
39
+ 创建 `h2o.config.ts`(可选,也可以只用环境变量):
40
+
41
+ ```ts
42
+ import { defineConfig } from "h2o-auto-flow";
43
+
44
+ export default defineConfig({
45
+ ai: {
46
+ apiKey: "sk-xxx",
47
+ model: "deepseek/deepseek-v4-flash",
48
+ },
49
+ output: {
50
+ dir: "./test-logs", // 日志/截图/报告 输出目录,默认 ./logs
51
+ },
52
+ browser: {
53
+ headless: false, // 默认 false
54
+ viewport: { width: 1280, height: 720 },
55
+ },
56
+ });
57
+ ```
58
+
59
+ 没有配置文件时,自动从环境变量取值:`DEEPSEEK_API_KEY`、`OPENAI_API_KEY`、`AI_MODEL`。
60
+
61
+ ## API
62
+
63
+ ### 核心方法
64
+
65
+ ```ts
66
+ definePlan("测试名", async ({ page, ...ctx }) => {
67
+ // ...
68
+ });
69
+ ```
70
+
71
+ | 方法 | 说明 | 示例 |
72
+ | ----------------------------- | ----------------- | --------------------------------------- |
73
+ | `has(sel)` → `boolean` | 元素是否在页面上 | `if (await has(".btn"))` |
74
+ | `which(selectors)` → `number` | 一组选择器哪个存在,返回索引 | `await which([".a", ".b", ".c"])` |
75
+ | `click(sel, nth?)` | 点击第一个(或第 n 个)可见元素 | `await click(".btn")` |
76
+ | `fill(sel, val)` | 填充输入框 | `await fill("input", "hello")` |
77
+ | `screenshot(label)` | 截图,自动编号 | `await screenshot("首页")` |
78
+ | `sleep(ms)` | 等待 | `await sleep(2000)` |
79
+ | `wait(sel, timeout?)` | 等待元素出现 | `await wait(".modal")` |
80
+ | `ai(instruction)` | AI 交互 + 异常检测 | `await ai("click the continue button")` |
81
+
82
+ ### 进阶
83
+
84
+ | 方法 | 说明 |
85
+ | ------------------------------ | -------------------------------------------------------------- |
86
+ | `check(name)` | 链式断言:`.element().text().url().performance()` |
87
+ | `network()` | 抓包:`net.filter("/api/").assertCall("/api/x", { status: 200 })` |
88
+ | `task(label, fn, aiFallback?)` | 原生优先,失败切 AI 兜底 |
89
+
90
+ ### `check()` 断言链
91
+
92
+ ```ts
93
+ await check("登录页校验")
94
+ .element("input", { minCount: 2 })
95
+ .text(".title", "欢迎", { ignoreCase: true })
96
+ .url("/dashboard")
97
+ .performance("FCP", { lt: 3000 });
98
+ ```
99
+
100
+ ### `task()` 成本优先
101
+
102
+ ```ts
103
+ // 纯原生
104
+ await task("填表", () => page.locator("input").fill("test"));
105
+
106
+ // 原生失败 → AI 兜底
107
+ await task(
108
+ "选择体态",
109
+ () => page.locator(".body-type").first().click(),
110
+ "select the first body type option",
111
+ );
112
+ ```
113
+
114
+ ### 抓包
115
+
116
+ ```ts
117
+ const net = network(); // 必须在 page.goto 之前
118
+ await page.goto("https://example.com");
119
+
120
+ net.filter("/api/login");
121
+ net.assertCall("/api/login", { status: 200 });
122
+ net.assertCall("/api/user", {
123
+ bodySchema: { id: "number", name: "string" },
124
+ });
125
+ ```
126
+
127
+ ## 常用交互模式
128
+
129
+ ```ts
130
+ // 条件点击
131
+ if (await has(".continue-btn")) await click(".continue-btn");
132
+
133
+ // 等元素出现再点击
134
+ await wait(".modal");
135
+ await click(".modal .confirm");
136
+
137
+ // 多个选择器模糊匹配
138
+ const idx = await which([".card-a", ".card-b", ".card-c"]);
139
+ if (idx !== -1) await click([".card-a", ".card-b", ".card-c"][idx]);
140
+ ```
141
+
142
+ ## 报告
143
+
144
+ 每次运行生成 `logs/run-{时间戳}/`:
145
+
146
+ ```
147
+ run-2026-06-18T18-30-05/
148
+ ├── chrome-profile/ # 浏览器 Profile
149
+ ├── screenshots/ # 截图(自动编号)
150
+ ├── report.json # 结构化数据
151
+ └── report.html # 可视化(截图可点击放大,← → 切换)
152
+ ```
153
+
154
+ ## License
155
+
156
+ MIT
@@ -0,0 +1,27 @@
1
+ import type { PageLike, AIAssertionResult, AIResult, AIEngineOptions, QualityOptions, AnomalyOptions } from "./types";
2
+ import { LLMClient } from "./llm-client";
3
+ export declare class AITest {
4
+ readonly name: string;
5
+ private tasks;
6
+ private stagehand;
7
+ private llm;
8
+ private options;
9
+ constructor(name: string, options: AIEngineOptions);
10
+ /** 自然语言交互:AI 自动找元素并操作(填充/点击/选择等) */
11
+ act(instruction: string): this;
12
+ /** 结构化提取:从页面元素提取数据并校验 Schema */
13
+ extract(selector: string, schema: Record<string, string>, instruction?: string): this;
14
+ /** 情感断言:判断文本情感是否符合预期 */
15
+ assertSentiment(selector: string, expected: "positive" | "negative" | "neutral", criteria?: string): this;
16
+ /** 内容质量断言:检查长度、关键词、语气、禁用词等 */
17
+ assertQuality(selector: string, opts: QualityOptions): this;
18
+ /** 异常检测:检查页面是否处于错误/空白/加载失败等非预期状态 */
19
+ detectAnomaly(opts?: AnomalyOptions): this;
20
+ /** 自定义断言 */
21
+ assertCustom(label: string, fn: (page: PageLike, llm: LLMClient, stagehand: any) => Promise<Omit<AIAssertionResult, "type">>): this;
22
+ run(page: PageLike): Promise<AIResult>;
23
+ }
24
+ /**
25
+ * 快捷工厂函数
26
+ */
27
+ export declare function ai(name: string, options: AIEngineOptions): AITest;
@@ -0,0 +1,6 @@
1
+ import type { PageLike, AnomalyOptions, AIAssertionResult } from "../types";
2
+ import type { LLMClient } from "../llm-client";
3
+ /**
4
+ * 全局异常检测 — 检查页面是否处于错误/空白/加载失败等非预期状态
5
+ */
6
+ export declare function assertAnomaly(page: PageLike, llm: LLMClient, options?: AnomalyOptions): Promise<AIAssertionResult>;
@@ -0,0 +1,6 @@
1
+ import type { PageLike, ExtractionOptions, AIAssertionResult } from "../types";
2
+ import type { LLMClient } from "../llm-client";
3
+ /**
4
+ * 从页面元素中提取结构化数据,并校验是否符合 Schema
5
+ */
6
+ export declare function assertExtraction(page: PageLike, llm: LLMClient, selector: string, options: ExtractionOptions): Promise<AIAssertionResult>;
@@ -0,0 +1,6 @@
1
+ import type { PageLike, QualityOptions, AIAssertionResult } from "../types";
2
+ import type { LLMClient } from "../llm-client";
3
+ /**
4
+ * 断言页面内容质量(长度、关键词、语气、禁用词)
5
+ */
6
+ export declare function assertQuality(page: PageLike, llm: LLMClient, selector: string, options: QualityOptions): Promise<AIAssertionResult>;
@@ -0,0 +1,6 @@
1
+ import type { PageLike, SentimentOptions, AIAssertionResult } from "../types";
2
+ import type { LLMClient } from "../llm-client";
3
+ /**
4
+ * 断言页面元素的文本情感倾向
5
+ */
6
+ export declare function assertSentiment(page: PageLike, llm: LLMClient, selector: string, options: SentimentOptions): Promise<AIAssertionResult>;
@@ -0,0 +1,24 @@
1
+ import type { LLMConfig, ChatMessage, ChatResponse } from "./types";
2
+ /**
3
+ * 简易 OpenAI 兼容 LLM 客户端
4
+ *
5
+ * 支持 DeepSeek、OpenAI、及任何 OpenAI 兼容 API。
6
+ * 自动从模型名推断 endpoint 和 API Key。
7
+ */
8
+ export declare class LLMClient {
9
+ private config;
10
+ private totalTokens;
11
+ constructor(config: LLMConfig);
12
+ /** 获取累计 token 消耗 */
13
+ getTotalTokens(): number;
14
+ /** 发送 Chat Completion 请求 */
15
+ chat(messages: ChatMessage[]): Promise<ChatResponse>;
16
+ /** 单轮对话快捷方法 */
17
+ complete(systemPrompt: string, userMessage: string): Promise<ChatResponse>;
18
+ /** 判断类快捷方法 — 返回布尔值 */
19
+ judge(systemPrompt: string, userMessage: string, expectedAnswer?: string): Promise<{
20
+ passed: boolean;
21
+ reason: string;
22
+ raw: string;
23
+ }>;
24
+ }
@@ -0,0 +1,3 @@
1
+ export declare const SENTIMENT_PROMPT = "You are a sentiment analyzer for UI testing. Given a piece of UI text, classify its emotional tone.\n\nCategories:\n- positive: encouraging, friendly, warm, optimistic, praising, welcoming\n- negative: angry, frustrated, blaming, cold, dismissive, alarming\n- neutral: factual, informational, neutral instructions\n\nReply with ONLY one word: \"positive\", \"negative\", or \"neutral\". Then a brief reason.";
2
+ export declare const EXTRACTION_PROMPT = "You are a data extraction tool for UI testing. Extract structured information from the given HTML or text content.\n\nRules:\n- Return ONLY valid JSON, no markdown, no explanation\n- Use the exact field names provided in the schema\n- If a field cannot be found, use null\n- For numbers, return the numeric value (not strings)";
3
+ export declare const ANOMALY_PROMPT = "You are an anomaly detector for UI testing. Analyze the given page content and determine if the page is in an error or unexpected state.\n\nAnomalies to detect:\n- Blank/white page with no meaningful content\n- Error messages (e.g., \"500 Internal Server Error\", \"Something went wrong\", \"Page not found\")\n- Loading failures (e.g., infinite spinner, \"Loading...\" stuck forever)\n- Broken elements (e.g., missing images, unstyled content)\n- Unexpected redirects to error pages\n\nReply with ONLY \"YES\" if an anomaly is detected, or \"NO\" if the page looks normal.\nThen a brief reason.";
@@ -0,0 +1,102 @@
1
+ import type { PageLike } from "../../core/rule-engine/types";
2
+ export type { PageLike };
3
+ export interface LLMConfig {
4
+ /** 模型标识,如 "deepseek/deepseek-v4-flash" 或 "openai/gpt-4o-mini" */
5
+ model: string;
6
+ /** API Base URL,默认 DeepSeek: https://api.deepseek.com/v1 */
7
+ baseURL?: string;
8
+ /** API Key */
9
+ apiKey?: string;
10
+ /** 温度 0-2,默认 0.1(测试场景需要确定性) */
11
+ temperature?: number;
12
+ /** 最大输出 token */
13
+ maxTokens?: number;
14
+ /** 请求超时 (ms) */
15
+ timeout?: number;
16
+ }
17
+ export interface ChatMessage {
18
+ role: "system" | "user" | "assistant";
19
+ content: string;
20
+ }
21
+ export interface ChatResponse {
22
+ content: string;
23
+ usage?: {
24
+ promptTokens: number;
25
+ completionTokens: number;
26
+ totalTokens: number;
27
+ };
28
+ }
29
+ export interface SentimentOptions {
30
+ /** 期望情感: positive / negative / neutral */
31
+ expected: "positive" | "negative" | "neutral";
32
+ /** 自定义判断标准 */
33
+ criteria?: string;
34
+ /** 超时 (ms) */
35
+ timeout?: number;
36
+ }
37
+ export interface ExtractionOptions {
38
+ /** JSON Schema 约束输出格式 */
39
+ schema: Record<string, string>;
40
+ /** 提取指令 */
41
+ instruction?: string;
42
+ timeout?: number;
43
+ }
44
+ export interface QualityOptions {
45
+ /** 最低文本长度 */
46
+ minLength?: number;
47
+ /** 必须包含关键词 */
48
+ containsKeywords?: string[];
49
+ /** 期望语气 */
50
+ tone?: "professional" | "friendly" | "casual" | "urgent";
51
+ /** 不允许包含的词 */
52
+ forbiddenWords?: string[];
53
+ /** 是否检查拼写/语法 */
54
+ checkGrammar?: boolean;
55
+ timeout?: number;
56
+ }
57
+ export interface AnomalyOptions {
58
+ /** 是否检查空白页 */
59
+ checkBlank?: boolean;
60
+ /** 是否检查错误消息 */
61
+ checkError?: boolean;
62
+ /** 是否检查加载失败 */
63
+ checkLoading?: boolean;
64
+ timeout?: number;
65
+ }
66
+ export type AIAssertionType = "act" | "extract" | "sentiment" | "quality" | "anomaly" | "custom";
67
+ export interface AIAssertionTask {
68
+ type: AIAssertionType;
69
+ name: string;
70
+ execute: (page: PageLike, stagehand: any) => Promise<AIAssertionResult>;
71
+ }
72
+ export interface AIAssertionResult {
73
+ passed: boolean;
74
+ type: AIAssertionType;
75
+ name: string;
76
+ message: string;
77
+ duration: number;
78
+ details?: string;
79
+ /** LLM 原始响应 */
80
+ rawResponse?: string;
81
+ /** 失败截图 base64 */
82
+ screenshot?: string;
83
+ }
84
+ export interface AIResult {
85
+ name: string;
86
+ assertions: AIAssertionResult[];
87
+ hasFailure: boolean;
88
+ passRate: number;
89
+ totalDuration: number;
90
+ /** 估算的 token 消耗 */
91
+ estimatedTokens: number;
92
+ }
93
+ export interface AIEngineOptions {
94
+ /** Stagehand 实例(用于 act/extract) */
95
+ stagehand: any;
96
+ /** LLM 客户端配置(用于 sentiment/quality/anomaly) */
97
+ llm?: LLMConfig;
98
+ /** 失败时截图 */
99
+ screenshotOnFailure?: boolean;
100
+ /** 首个断言失败即停 */
101
+ bailOnFailure?: boolean;
102
+ }
@@ -0,0 +1,36 @@
1
+ import type { PageLike, AssertionResult, RuleResult, RuleEngineOptions, ElementAssertionOptions, TextAssertionOptions, AttributeAssertionOptions, NavigationAssertionOptions, NetworkAssertionOptions, PerformanceAssertionOptions, AccessibilityAssertionOptions } from "./types";
2
+ import type { PerfMetric } from "./assertions/performance";
3
+ export declare class RuleTest {
4
+ readonly name: string;
5
+ private tasks;
6
+ private options;
7
+ constructor(name: string, options?: RuleEngineOptions);
8
+ /** 断言元素存在性/可见性/可编辑性/数量 */
9
+ assertElement(selector: string, opts?: ElementAssertionOptions): this;
10
+ /** 断言元素文本内容 */
11
+ assertText(selector: string, expected: string | RegExp, opts?: TextAssertionOptions): this;
12
+ /** 断言元素 HTML 属性值 */
13
+ assertAttribute(selector: string, attribute: string, expected: string | RegExp, opts?: AttributeAssertionOptions): this;
14
+ /** 断言当前页面 URL */
15
+ assertUrl(expected: string | RegExp, opts?: NavigationAssertionOptions): this;
16
+ /** 断言页面 <title> */
17
+ assertTitle(expected: string | RegExp, opts?: NavigationAssertionOptions): this;
18
+ /** 断言页面 <meta> 标签 */
19
+ assertMeta(metaName: string, expected: string | RegExp, opts?: NavigationAssertionOptions): this;
20
+ /** 断言 HTTP 状态码(可附加响应体/响应头校验) */
21
+ assertStatusCode(urlOrPattern: string | RegExp, expectedStatus: number, opts?: NetworkAssertionOptions): this;
22
+ /** 断言 Web 性能指标 */
23
+ assertPerformance(metric: PerfMetric, opts?: PerformanceAssertionOptions, customValue?: number): this;
24
+ /** 断言可访问性(alt / label / role / 对比度) */
25
+ assertAccessibility(selector: string, opts?: AccessibilityAssertionOptions): this;
26
+ /** 自定义断言:传入任意异步校验函数 */
27
+ assertCustom(label: string, fn: (page: PageLike) => Promise<Omit<AssertionResult, "type">>): this;
28
+ /**
29
+ * 运行全部断言,返回结果
30
+ */
31
+ run(page: PageLike): Promise<RuleResult>;
32
+ }
33
+ /**
34
+ * 快捷工厂函数
35
+ */
36
+ export declare function rule(name: string, options?: RuleEngineOptions): RuleTest;
@@ -0,0 +1,9 @@
1
+ import type { PageLike, AccessibilityAssertionOptions, AssertionResult } from "../types";
2
+ /**
3
+ * 断言元素的可访问性:
4
+ * - 图片是否有 alt 文本
5
+ * - 表单控件是否有关联 label
6
+ * - ARIA role 是否正确
7
+ * - 颜色对比度(基础检查)
8
+ */
9
+ export declare function assertAccessibility(page: PageLike, selector: string, options?: AccessibilityAssertionOptions): Promise<AssertionResult>;
@@ -0,0 +1,9 @@
1
+ import type { PageLike, TextAssertionOptions, AttributeAssertionOptions, AssertionResult } from "../types";
2
+ /**
3
+ * 断言元素的文本内容
4
+ */
5
+ export declare function assertText(page: PageLike, selector: string, expected: string | RegExp, options?: TextAssertionOptions): Promise<AssertionResult>;
6
+ /**
7
+ * 断言元素的 HTML 属性值
8
+ */
9
+ export declare function assertAttribute(page: PageLike, selector: string, attribute: string, expected: string | RegExp, _options?: AttributeAssertionOptions): Promise<AssertionResult>;
@@ -0,0 +1,2 @@
1
+ import type { PageLike, ElementAssertionOptions, AssertionResult } from "../types";
2
+ export declare function assertElement(page: PageLike, selector: string, options?: ElementAssertionOptions): Promise<AssertionResult>;
@@ -0,0 +1,13 @@
1
+ import type { PageLike, NavigationAssertionOptions, AssertionResult } from "../types";
2
+ /**
3
+ * 断⾔当前页面 URL
4
+ */
5
+ export declare function assertUrl(page: PageLike, expected: string | RegExp, options?: NavigationAssertionOptions): Promise<AssertionResult>;
6
+ /**
7
+ * 断言页面标题
8
+ */
9
+ export declare function assertTitle(page: PageLike, expected: string | RegExp, _options?: NavigationAssertionOptions): Promise<AssertionResult>;
10
+ /**
11
+ * 断言页面 <meta> 标签内容
12
+ */
13
+ export declare function assertMeta(page: PageLike, metaName: string, expected: string | RegExp, _options?: NavigationAssertionOptions): Promise<AssertionResult>;
@@ -0,0 +1,9 @@
1
+ import type { PageLike, NetworkAssertionOptions, AssertionResult } from "../types";
2
+ /**
3
+ * 断言某个请求的 HTTP 状态码
4
+ *
5
+ * 支持两种模式:
6
+ * 1. 等待模式:传入 url 字符串,拦截匹配的请求并验证
7
+ * 2. 被动模式:若已通过其他方式捕获 response,可直接传入验证
8
+ */
9
+ export declare function assertStatusCode(page: PageLike, urlOrPattern: string | RegExp, expectedStatus: number, options?: NetworkAssertionOptions): Promise<AssertionResult>;
@@ -0,0 +1,11 @@
1
+ import type { PageLike, PerformanceAssertionOptions, AssertionResult } from "../types";
2
+ /**
3
+ * Web Vitals 指标名称
4
+ */
5
+ export type PerfMetric = "FCP" | "LCP" | "TTI" | "CLS" | "TBT" | "FID" | "load" | "dns" | "tls" | "ttfb" | "dom" | "custom";
6
+ /**
7
+ * 从页面提取 Web Vitals 指标(基于 Performance API)
8
+ */
9
+ export declare function assertPerformance(page: PageLike, metric: PerfMetric, options?: PerformanceAssertionOptions,
10
+ /** 自定义指标时传入具体数值 */
11
+ customValue?: number): Promise<AssertionResult>;
@@ -0,0 +1,17 @@
1
+ import type { PageLike, LocatorLike } from "./types";
2
+ /**
3
+ * 标准化选择器:对常见简写做展开,确保跨浏览器兼容。
4
+ * - `#id` 保持不变
5
+ * - `.class` 保持不变
6
+ * - `tag` 保持不变
7
+ * - 含空格的复合选择器保持不变
8
+ */
9
+ export declare function normalizeSelector(selector: string): string;
10
+ /**
11
+ * 生成描述性的选择器标签(用于报告)
12
+ */
13
+ export declare function selectorLabel(selector: string): string;
14
+ /**
15
+ * 安全获取 locator,捕获 Playwright 语法错误
16
+ */
17
+ export declare function safeLocator(page: PageLike, selector: string): LocatorLike;
@@ -0,0 +1,208 @@
1
+ /** 最小化 FrameLocator 接口,用于操作 iframe 内部元素 */
2
+ export interface FrameLocatorLike {
3
+ locator(selector: string): LocatorLike;
4
+ frameLocator(selector: string): FrameLocatorLike;
5
+ }
6
+ /** 最小化 Page 接口,兼容 Playwright / Stagehand 的 page 对象 */
7
+ export interface PageLike {
8
+ url(): string;
9
+ title(): Promise<string>;
10
+ locator(selector: string): LocatorLike;
11
+ frameLocator(selector: string): FrameLocatorLike;
12
+ waitForURL(url: string | RegExp, options?: {
13
+ timeout?: number;
14
+ }): Promise<void>;
15
+ waitForTimeout(ms: number): Promise<void>;
16
+ waitForResponse(urlOrPredicate: string | RegExp | ((response: ResponseLike) => boolean), options?: {
17
+ timeout?: number;
18
+ }): Promise<ResponseLike>;
19
+ goto(url: string, options?: {
20
+ waitUntil?: string;
21
+ timeout?: number;
22
+ }): Promise<ResponseLike | null>;
23
+ evaluate<T>(fn: string | ((...args: any[]) => T), arg?: any): Promise<T>;
24
+ screenshot(options?: {
25
+ path?: string;
26
+ fullPage?: boolean;
27
+ }): Promise<Buffer>;
28
+ setViewportSize(size: {
29
+ width: number;
30
+ height: number;
31
+ }): Promise<void>;
32
+ keyboard: {
33
+ press(key: string): Promise<void>;
34
+ type(text: string, options?: {
35
+ delay?: number;
36
+ }): Promise<void>;
37
+ };
38
+ mouse: {
39
+ click(x: number, y: number): Promise<void>;
40
+ };
41
+ }
42
+ export interface LocatorLike {
43
+ count(): Promise<number>;
44
+ first(): LocatorLike;
45
+ nth(index: number): LocatorLike;
46
+ isVisible(options?: {
47
+ timeout?: number;
48
+ }): Promise<boolean>;
49
+ isHidden(options?: {
50
+ timeout?: number;
51
+ }): Promise<boolean>;
52
+ isEnabled(options?: {
53
+ timeout?: number;
54
+ }): Promise<boolean>;
55
+ isDisabled(options?: {
56
+ timeout?: number;
57
+ }): Promise<boolean>;
58
+ isEditable(options?: {
59
+ timeout?: number;
60
+ }): Promise<boolean>;
61
+ textContent(options?: {
62
+ timeout?: number;
63
+ }): Promise<string | null>;
64
+ innerText(options?: {
65
+ timeout?: number;
66
+ }): Promise<string>;
67
+ getAttribute(name: string, options?: {
68
+ timeout?: number;
69
+ }): Promise<string | null>;
70
+ inputValue(options?: {
71
+ timeout?: number;
72
+ }): Promise<string>;
73
+ click(options?: {
74
+ timeout?: number;
75
+ }): Promise<void>;
76
+ fill(value: string, options?: {
77
+ timeout?: number;
78
+ }): Promise<void>;
79
+ waitFor(options?: {
80
+ state?: "visible" | "hidden" | "attached" | "detached";
81
+ timeout?: number;
82
+ }): Promise<void>;
83
+ evaluate<T>(fn: (el: any) => T): Promise<T>;
84
+ screenshot(options?: {
85
+ path?: string;
86
+ }): Promise<Buffer>;
87
+ }
88
+ export interface ResponseLike {
89
+ url(): string;
90
+ status(): number;
91
+ headers(): Record<string, string>;
92
+ json<T = any>(): Promise<T>;
93
+ text(): Promise<string>;
94
+ }
95
+ export interface ElementAssertionOptions {
96
+ /** 元素必须可见 */
97
+ visible?: boolean;
98
+ /** 元素必须隐藏 */
99
+ hidden?: boolean;
100
+ /** 元素必须可编辑(input/textarea 等) */
101
+ editable?: boolean;
102
+ /** 元素必须启用 */
103
+ enabled?: boolean;
104
+ /** 元素必须禁用 */
105
+ disabled?: boolean;
106
+ /** 精确数量 */
107
+ count?: number;
108
+ /** 最小数量 */
109
+ minCount?: number;
110
+ /** 最大数量 */
111
+ maxCount?: number;
112
+ /** 等待超时 (ms) */
113
+ timeout?: number;
114
+ }
115
+ export interface TextAssertionOptions {
116
+ /** 精确匹配(默认) */
117
+ exact?: boolean;
118
+ /** 正则匹配 */
119
+ regex?: boolean;
120
+ /** 忽略大小写 */
121
+ ignoreCase?: boolean;
122
+ /** 是否使用 innerText(默认 textContent) */
123
+ useInnerText?: boolean;
124
+ /** 等待超时 (ms) */
125
+ timeout?: number;
126
+ }
127
+ export interface AttributeAssertionOptions {
128
+ timeout?: number;
129
+ }
130
+ export interface NavigationAssertionOptions {
131
+ /** 检查 URL 前需要执行的操作(选择器或动作描述) */
132
+ after?: string;
133
+ /** 超时 */
134
+ timeout?: number;
135
+ }
136
+ export interface NetworkAssertionOptions {
137
+ /** 等待响应超时 */
138
+ timeout?: number;
139
+ /** 验证响应体 JSON Schema */
140
+ schema?: Record<string, any>;
141
+ /** 验证响应头 */
142
+ headers?: Record<string, string>;
143
+ /** 验证响应体包含文本 */
144
+ contains?: string;
145
+ }
146
+ export interface PerformanceAssertionOptions {
147
+ /** 小于 (ms) */
148
+ lt?: number;
149
+ /** 小于等于 (ms) */
150
+ lte?: number;
151
+ /** 大于 (ms) */
152
+ gt?: number;
153
+ /** 大于等于 (ms) */
154
+ gte?: number;
155
+ /** 介于 [min, max] */
156
+ between?: [number, number];
157
+ }
158
+ export interface AccessibilityAssertionOptions {
159
+ /** 必须包含 alt 文本 */
160
+ altText?: string | RegExp;
161
+ /** 必须有对应的 label */
162
+ hasLabel?: boolean;
163
+ /** 颜色对比度最低要求 (1-21, 默认 4.5) */
164
+ minContrast?: number;
165
+ /** 检查 ARIA role */
166
+ role?: string;
167
+ /** 超时 */
168
+ timeout?: number;
169
+ }
170
+ export type AssertionType = "element" | "text" | "attribute" | "navigation" | "network" | "performance" | "accessibility" | "custom";
171
+ export interface AssertionTask {
172
+ type: AssertionType;
173
+ name: string;
174
+ execute: (page: PageLike) => Promise<AssertionResult>;
175
+ }
176
+ export interface AssertionResult {
177
+ passed: boolean;
178
+ type: AssertionType;
179
+ name: string;
180
+ message: string;
181
+ duration: number;
182
+ /** 失败时附加的上下文信息 */
183
+ details?: string;
184
+ /** 失败截图 base64 */
185
+ screenshot?: string;
186
+ }
187
+ export interface RuleResult {
188
+ /** 测试名称 */
189
+ name: string;
190
+ /** 全部断言结果 */
191
+ assertions: AssertionResult[];
192
+ /** 是否有失败项 */
193
+ hasFailure: boolean;
194
+ /** 通过率 0-1 */
195
+ passRate: number;
196
+ /** 总耗时 (ms) */
197
+ totalDuration: number;
198
+ }
199
+ export interface RuleEngineOptions {
200
+ /** 全局超时 (ms),默认 10000 */
201
+ timeout?: number;
202
+ /** 失败时自动截图,默认 true */
203
+ screenshotOnFailure?: boolean;
204
+ /** 首个断言失败后是否继续,默认 false */
205
+ bailOnFailure?: boolean;
206
+ /** 失败重试次数,默认 0 */
207
+ retryCount?: number;
208
+ }