gz-laya-pilot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/replay.js ADDED
@@ -0,0 +1,152 @@
1
+ import { openBrowser } from './browser.js';
2
+ import { waitForReady } from './readiness.js';
3
+ import { runAssertion } from './assertions.js';
4
+ import { parseCase } from './schema.js';
5
+ async function resolveTarget(page, target) {
6
+ await page.evaluate((selector) => document.querySelectorAll(selector).length, target.selector);
7
+ const locator = page.locator('css=' + target.selector);
8
+ await locator.waitFor({ state: 'attached' });
9
+ if ((await locator.count()) !== 1)
10
+ throw Error('目标不是唯一控件,请重新录制');
11
+ const metadata = await locator.evaluate((e) => {
12
+ const element = e;
13
+ const label = element.getAttribute('aria-label') ||
14
+ element.getAttribute('placeholder') ||
15
+ element.labels?.[0]?.textContent ||
16
+ element.textContent ||
17
+ element.tagName;
18
+ return {
19
+ label: label.trim().slice(0, 200),
20
+ private: Boolean(element.closest('[data-private],.rr-block,[data-research-assistant]')) ||
21
+ /password|passwd|token|secret|credential|验证码|密码|密钥|口令/i.test([element.type, element.name, element.id, element.autocomplete, label].join(' ')),
22
+ };
23
+ });
24
+ if (metadata.private)
25
+ throw Error('不回放敏感字段或小蜜自身控件');
26
+ if (target.role) {
27
+ const named = page.getByRole(target.role, {
28
+ name: target.label,
29
+ exact: true,
30
+ });
31
+ if ((await locator.and(named).count()) !== 1)
32
+ throw Error('控件语义已变化,请重新录制');
33
+ }
34
+ else if (metadata.label !== target.label)
35
+ throw Error('控件内容已变化,停止回放以避免误操作');
36
+ if (!(await locator.isVisible()))
37
+ await locator.locator('..').hover();
38
+ return locator;
39
+ }
40
+ /** Functions compose the execution path. No business route, login or data fixture is built in. */
41
+ export async function runCase(value, options) {
42
+ const testCase = parseCase(value);
43
+ const origin = new URL(options.origin);
44
+ if (!['http:', 'https:'].includes(origin.protocol) || origin.username || origin.password)
45
+ throw Error('测试站点地址无效');
46
+ const destination = new URL(testCase.path, origin);
47
+ if (destination.origin !== origin.origin)
48
+ throw Error('用例入口超出测试站点');
49
+ const allowed = new Set([
50
+ origin.origin,
51
+ ...(options.allowedOrigins ?? []).map((url) => new URL(url).origin),
52
+ ]);
53
+ const assertions = [];
54
+ let status = {
55
+ state: 'running',
56
+ step: 0,
57
+ total: testCase.steps.length,
58
+ message: '正在启动浏览器',
59
+ assertions,
60
+ };
61
+ const report = (patch) => {
62
+ status = { ...status, ...patch, assertions: [...assertions] };
63
+ options.onProgress?.(status);
64
+ };
65
+ if (options.signal?.aborted)
66
+ return { ...status, state: 'stopped', message: '回放已停止' };
67
+ const { browser, page, context } = await openBrowser({
68
+ launch: options.launch,
69
+ context: options.context,
70
+ });
71
+ const stop = () => {
72
+ void browser.close().catch(() => { });
73
+ };
74
+ options.signal?.addEventListener('abort', stop, { once: true });
75
+ try {
76
+ if (options.signal?.aborted)
77
+ throw Error('已停止');
78
+ await context.route('**/*', (route) => allowed.has(new URL(route.request().url()).origin) ? route.continue() : route.abort());
79
+ await context.addInitScript(() => {
80
+ const mount = () => {
81
+ const style = document.createElement('style');
82
+ style.textContent = '[data-research-assistant],.research-assistant{display:none!important}';
83
+ document.documentElement.append(style);
84
+ };
85
+ if (document.documentElement)
86
+ mount();
87
+ else
88
+ document.addEventListener('DOMContentLoaded', mount, { once: true });
89
+ });
90
+ page.setDefaultNavigationTimeout(60000);
91
+ await page.goto(destination.href, { waitUntil: 'domcontentloaded' });
92
+ await waitForReady(page, { timeout: 45000 });
93
+ await options.beforeRun?.(page, context);
94
+ for (const [index, step] of testCase.steps.entries()) {
95
+ if (options.signal?.aborted)
96
+ throw Error('已停止');
97
+ report({ step: index + 1, message: `正在执行第 ${index + 1} 步:${step.kind}` });
98
+ if (options.delay)
99
+ await page.waitForTimeout(options.delay);
100
+ if (step.kind === 'assert' || step.kind === 'assert-text') {
101
+ const assertion = step.assertion ?? {
102
+ name: step.value,
103
+ source: 'page-text',
104
+ operator: 'contains',
105
+ expected: step.value,
106
+ timeout: 10000,
107
+ };
108
+ const result = await runAssertion(page, assertion, index + 1, () => options.signal?.aborted === true);
109
+ assertions.push(result);
110
+ report({});
111
+ if (!result.passed)
112
+ throw Error(`第 ${index + 1} 步断言失败:${assertion.name}`);
113
+ }
114
+ else {
115
+ if (step.kind === 'unsupported' || !step.target)
116
+ throw Error('请先补充未支持的步骤');
117
+ const target = await resolveTarget(page, step.target);
118
+ if (step.kind === 'click')
119
+ await target.click();
120
+ else if (step.kind === 'fill')
121
+ await target.fill(step.value);
122
+ else if (step.kind === 'select')
123
+ await target.selectOption(step.value);
124
+ else if (step.kind === 'check')
125
+ await target.setChecked(step.checked);
126
+ else if (step.kind === 'press')
127
+ await target.press(step.value);
128
+ }
129
+ await waitForReady(page, { timeout: 10000, minimum: 200 });
130
+ }
131
+ report({
132
+ state: assertions.length ? 'passed' : 'completed',
133
+ message: assertions.length
134
+ ? `回放完成,${assertions.length} 条断言全部通过`
135
+ : '操作回放完成;未设置断言,尚未验证业务结果',
136
+ });
137
+ }
138
+ catch (error) {
139
+ report({
140
+ state: options.signal?.aborted ? 'stopped' : 'failed',
141
+ message: options.signal?.aborted
142
+ ? '回放已停止'
143
+ : (error instanceof Error ? error.message : String(error)).slice(0, 1500),
144
+ });
145
+ }
146
+ finally {
147
+ options.signal?.removeEventListener('abort', stop);
148
+ await browser.close();
149
+ }
150
+ return status;
151
+ }
152
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ export interface Target {
2
+ selector: string;
3
+ label: string;
4
+ role?: string;
5
+ }
6
+ export interface Step {
7
+ kind: 'click' | 'fill' | 'select' | 'check' | 'press' | 'assert-text' | 'assert' | 'unsupported';
8
+ assertion?: Assertion;
9
+ target?: Target;
10
+ value?: string;
11
+ checked?: boolean;
12
+ sourceEventId?: number;
13
+ supplemented?: boolean;
14
+ }
15
+ export declare const assertionSources: {
16
+ readonly 'page-text': "页面文字";
17
+ readonly 'element-text': "元素文字";
18
+ readonly 'element-value': "输入框值";
19
+ readonly 'element-state': "元素可见性";
20
+ readonly 'element-count': "元素数量";
21
+ };
22
+ export declare const assertionOperators: {
23
+ readonly contains: "包含";
24
+ readonly 'not-contains': "不包含";
25
+ readonly equals: "等于";
26
+ readonly 'not-equals': "不等于";
27
+ readonly visible: "可见";
28
+ readonly hidden: "不可见或不存在";
29
+ };
30
+ export interface Assertion {
31
+ name: string;
32
+ source: keyof typeof assertionSources;
33
+ operator: keyof typeof assertionOperators;
34
+ selector?: string;
35
+ expected?: string;
36
+ timeout: number;
37
+ }
38
+ export declare function operatorsFor(source: Assertion['source']): Assertion['operator'][];
39
+ export declare function parseAssertion(value: unknown): Assertion;
40
+ export interface AssertionResult {
41
+ step: number;
42
+ name: string;
43
+ passed: boolean;
44
+ expected: string;
45
+ actual: string;
46
+ duration: number;
47
+ }
48
+ export interface TimelineEvidence {
49
+ id: number;
50
+ time: number;
51
+ kind: string;
52
+ title: string;
53
+ page: string;
54
+ actionId?: number;
55
+ }
56
+ export interface TestCase {
57
+ schema: 'zhidao-recorded-case';
58
+ version: 1;
59
+ name: string;
60
+ path: string;
61
+ steps: Step[];
62
+ timeline?: {
63
+ capturedAt: string;
64
+ events: TimelineEvidence[];
65
+ };
66
+ }
67
+ export interface RunStatus {
68
+ id?: string;
69
+ state: 'idle' | 'running' | 'passed' | 'completed' | 'failed' | 'stopped';
70
+ step: number;
71
+ total: number;
72
+ message: string;
73
+ assertions?: AssertionResult[];
74
+ }
75
+ export declare function parseCase(value: unknown): TestCase;
package/dist/schema.js ADDED
@@ -0,0 +1,162 @@
1
+ export const assertionSources = {
2
+ 'page-text': '页面文字',
3
+ 'element-text': '元素文字',
4
+ 'element-value': '输入框值',
5
+ 'element-state': '元素可见性',
6
+ 'element-count': '元素数量',
7
+ };
8
+ export const assertionOperators = {
9
+ contains: '包含',
10
+ 'not-contains': '不包含',
11
+ equals: '等于',
12
+ 'not-equals': '不等于',
13
+ visible: '可见',
14
+ hidden: '不可见或不存在',
15
+ };
16
+ export function operatorsFor(source) {
17
+ if (source === 'element-state')
18
+ return ['visible', 'hidden'];
19
+ if (source === 'element-count')
20
+ return ['equals', 'not-equals'];
21
+ return ['contains', 'not-contains', 'equals', 'not-equals'];
22
+ }
23
+ export function parseAssertion(value) {
24
+ const a = value;
25
+ if (!a || typeof a.name !== 'string' || !a.name.trim() || a.name.length > 100)
26
+ throw Error('请填写 1–100 字的断言名称');
27
+ if (!Object.hasOwn(assertionSources, a.source) || !operatorsFor(a.source).includes(a.operator))
28
+ throw Error('不支持的断言对象或条件');
29
+ if (!Number.isInteger(a.timeout) || a.timeout < 500 || a.timeout > 30000)
30
+ throw Error('断言等待时间应为 500–30,000 毫秒');
31
+ if (a.source !== 'page-text' &&
32
+ (typeof a.selector !== 'string' || !a.selector.trim() || a.selector.length > 2000))
33
+ throw Error('请填写有效的 CSS 定位信息');
34
+ if (a.source !== 'element-state' && (typeof a.expected !== 'string' || a.expected.length > 2000))
35
+ throw Error('请填写期望值,最多 2,000 字');
36
+ if (a.source !== 'element-state' && a.source !== 'element-value' && !a.expected?.trim())
37
+ throw Error('期望值不能为空');
38
+ if (a.source === 'element-count' && (!/^\d+$/.test(a.expected) || Number(a.expected) > 100000))
39
+ throw Error('元素数量应为 0–100,000 的整数');
40
+ return {
41
+ name: a.name.trim(),
42
+ source: a.source,
43
+ operator: a.operator,
44
+ timeout: a.timeout,
45
+ ...(a.source !== 'page-text' ? { selector: a.selector.trim() } : {}),
46
+ ...(a.source !== 'element-state' ? { expected: a.expected } : {}),
47
+ };
48
+ }
49
+ // Shared by the browser and local bridge. No executable JavaScript in case files.
50
+ export function parseCase(value) {
51
+ const input = value;
52
+ if (!input || input.schema !== 'zhidao-recorded-case' || input.version !== 1)
53
+ throw Error('不是受支持的录制用例 JSON');
54
+ if (typeof input.name !== 'string' || !input.name.trim() || input.name.length > 100)
55
+ throw Error('用例名称应为 1–100 个字符');
56
+ if (typeof input.path !== 'string' ||
57
+ !input.path.startsWith('/') ||
58
+ input.path.startsWith('//') ||
59
+ /[\\\r\n]/.test(input.path) ||
60
+ input.path.length > 2000)
61
+ throw Error('用例入口必须是站点内的相对路径');
62
+ if (!Array.isArray(input.steps) || input.steps.length < 1 || input.steps.length > 100)
63
+ throw Error('用例需要 1–100 个步骤');
64
+ const steps = input.steps.map((step) => {
65
+ if (!step ||
66
+ ![
67
+ 'click',
68
+ 'fill',
69
+ 'select',
70
+ 'check',
71
+ 'press',
72
+ 'assert-text',
73
+ 'assert',
74
+ 'unsupported',
75
+ ].includes(step.kind))
76
+ throw Error('用例包含未知操作');
77
+ if (step.value !== undefined && (typeof step.value !== 'string' || step.value.length > 2000))
78
+ throw Error('步骤内容不能超过 2000 个字符');
79
+ if (step.kind === 'press' && !['Enter', 'Escape', 'Tab'].includes(step.value ?? ''))
80
+ throw Error('不支持该快捷键');
81
+ if (step.kind === 'assert-text' && !step.value?.trim())
82
+ throw Error('检查点文字不能为空');
83
+ if (step.kind === 'check' && typeof step.checked !== 'boolean')
84
+ throw Error('缺少勾选状态');
85
+ if (['fill', 'select'].includes(step.kind) && step.value === undefined)
86
+ throw Error('缺少输入值');
87
+ let target;
88
+ if (!['assert-text', 'assert', 'unsupported'].includes(step.kind)) {
89
+ if (!step.target ||
90
+ typeof step.target.selector !== 'string' ||
91
+ !step.target.selector ||
92
+ step.target.selector.length > 2000 ||
93
+ typeof step.target.label !== 'string' ||
94
+ step.target.label.length > 200)
95
+ throw Error('步骤缺少有效定位信息');
96
+ target = {
97
+ selector: step.target.selector,
98
+ label: step.target.label,
99
+ ...(typeof step.target.role === 'string' && /^[a-z]+$/.test(step.target.role)
100
+ ? { role: step.target.role }
101
+ : {}),
102
+ };
103
+ }
104
+ return {
105
+ kind: step.kind,
106
+ ...(step.kind === 'assert' ? { assertion: parseAssertion(step.assertion) } : {}),
107
+ ...(target ? { target } : {}),
108
+ ...(step.value !== undefined ? { value: step.value } : {}),
109
+ ...(step.kind === 'check' ? { checked: step.checked } : {}),
110
+ ...(Number.isSafeInteger(step.sourceEventId) && step.sourceEventId > 0
111
+ ? { sourceEventId: step.sourceEventId }
112
+ : {}),
113
+ ...(step.supplemented === true ? { supplemented: true } : {}),
114
+ };
115
+ });
116
+ let timeline;
117
+ if (input.timeline !== undefined) {
118
+ if (!input.timeline ||
119
+ typeof input.timeline.capturedAt !== 'string' ||
120
+ !Number.isFinite(Date.parse(input.timeline.capturedAt)) ||
121
+ !Array.isArray(input.timeline.events) ||
122
+ input.timeline.events.length > 300)
123
+ throw Error('时间线来源格式错误');
124
+ const ids = new Set();
125
+ const events = input.timeline.events.map((e) => {
126
+ if (!e ||
127
+ !Number.isSafeInteger(e.id) ||
128
+ e.id <= 0 ||
129
+ ids.has(e.id) ||
130
+ !Number.isFinite(e.time) ||
131
+ !['route', 'click', 'change', 'request', 'error', 'performance'].includes(e.kind) ||
132
+ typeof e.title !== 'string' ||
133
+ e.title.length > 200 ||
134
+ typeof e.page !== 'string' ||
135
+ e.page.length > 2000)
136
+ throw Error('时间线来源事件格式错误');
137
+ ids.add(e.id);
138
+ return {
139
+ id: e.id,
140
+ time: e.time,
141
+ kind: e.kind,
142
+ title: e.title,
143
+ page: e.page,
144
+ ...(Number.isSafeInteger(e.actionId) && e.actionId > 0 ? { actionId: e.actionId } : {}),
145
+ };
146
+ });
147
+ if (steps.some((s) => s.sourceEventId !== undefined && !ids.has(s.sourceEventId)))
148
+ throw Error('步骤引用的原始事件不存在');
149
+ timeline = { capturedAt: input.timeline.capturedAt, events };
150
+ }
151
+ else if (steps.some((s) => s.sourceEventId !== undefined))
152
+ throw Error('缺少步骤的原始时间线');
153
+ return {
154
+ schema: 'zhidao-recorded-case',
155
+ version: 1,
156
+ name: input.name.trim(),
157
+ path: input.path,
158
+ steps,
159
+ ...(timeline ? { timeline } : {}),
160
+ };
161
+ }
162
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { Target } from './schema.js';
2
+ export declare const excluded = "[data-research-assistant],.research-assistant,[data-private],.rr-block";
3
+ export declare const controls = "button,a[href],input,textarea,select,[role=\"button\"],[role=\"tab\"],[role=\"option\"],[role=\"menuitem\"],[role=\"checkbox\"],[role=\"combobox\"],summary";
4
+ export declare const normalizeText: (value: string) => string;
5
+ /** Accessible names follow the W3C AccName algorithm, including labelledby and label-for. */
6
+ export declare function describeControl(element: Element): {
7
+ role: string | undefined;
8
+ label: string;
9
+ labelSource: string;
10
+ testId: string | undefined;
11
+ };
12
+ export declare function privateInput(element: Element): boolean;
13
+ export declare function locate(element: Element): Target;
@@ -0,0 +1,49 @@
1
+ import { computeAccessibleName, getRole } from 'dom-accessibility-api';
2
+ export const excluded = '[data-research-assistant],.research-assistant,[data-private],.rr-block';
3
+ export const controls = 'button,a[href],input,textarea,select,[role="button"],[role="tab"],[role="option"],[role="menuitem"],[role="checkbox"],[role="combobox"],summary';
4
+ export const normalizeText = (value) => value.replace(/\s+/g, ' ').trim();
5
+ /** Accessible names follow the W3C AccName algorithm, including labelledby and label-for. */
6
+ export function describeControl(element) {
7
+ const role = getRole(element) ?? undefined;
8
+ const explicit = element.getAttribute('data-assistant-label');
9
+ const accessible = computeAccessibleName(element, { computedStyleSupportsPseudoElements: false });
10
+ const fallback = element.getAttribute('placeholder') ||
11
+ element.getAttribute('name') ||
12
+ role ||
13
+ element.tagName.toLowerCase();
14
+ return {
15
+ role,
16
+ label: normalizeText(explicit || accessible || fallback).slice(0, 200),
17
+ labelSource: explicit ? 'business-label' : accessible ? 'accessible-name' : 'attribute',
18
+ testId: element.getAttribute('data-testid') ?? undefined,
19
+ };
20
+ }
21
+ export function privateInput(element) {
22
+ return (Boolean(element.closest('[data-private],.rr-block')) ||
23
+ /password|passwd|token|secret|credential|验证码|密码|密钥|口令/i.test(['type', 'name', 'id', 'autocomplete', 'aria-label', 'placeholder']
24
+ .map((key) => element.getAttribute(key))
25
+ .join(' ') +
26
+ ' ' +
27
+ computeAccessibleName(element, { computedStyleSupportsPseudoElements: false })));
28
+ }
29
+ export function locate(element) {
30
+ const { label, role, labelSource } = describeControl(element);
31
+ const semantic = role && labelSource === 'accessible-name' ? { role } : {};
32
+ const doc = element.ownerDocument;
33
+ for (const attribute of ['data-testid', 'id', 'aria-label', 'placeholder', 'name']) {
34
+ const value = element.getAttribute(attribute);
35
+ const selector = `${element.tagName.toLowerCase()}[${attribute}=${JSON.stringify(value ?? '')}]`;
36
+ if (value && doc.querySelectorAll(selector).length === 1)
37
+ return { selector, label, ...semantic };
38
+ }
39
+ const path = [];
40
+ for (let current = element; current && current !== doc.body; current = current.parentElement) {
41
+ const parent = current.parentElement;
42
+ if (!parent)
43
+ break;
44
+ const index = [...parent.children].filter((e) => e.tagName === current.tagName).indexOf(current) + 1;
45
+ path.unshift(`${current.tagName.toLowerCase()}:nth-of-type(${index})`);
46
+ }
47
+ return { selector: `body > ${path.join(' > ')}`, label, ...semantic };
48
+ }
49
+ //# sourceMappingURL=targets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"targets.js","sourceRoot":"","sources":["../src/targets.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "gz-laya-pilot",
3
+ "version": "0.1.0",
4
+ "description": "Functional TypeScript browser testing runtime adapted from LayaPilot",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "files": [
8
+ "dist",
9
+ "src",
10
+ "NOTICE.md",
11
+ "README.md",
12
+ "LICENSE"
13
+ ],
14
+ "engines": {
15
+ "node": ">=22"
16
+ },
17
+ "sideEffects": false,
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./schema": {
24
+ "types": "./dist/schema.d.ts",
25
+ "import": "./dist/schema.js"
26
+ },
27
+ "./browser": {
28
+ "types": "./dist/targets.d.ts",
29
+ "import": "./dist/targets.js"
30
+ }
31
+ },
32
+ "scripts": {
33
+ "build": "tsc -p tsconfig.json",
34
+ "prepack": "npm run build",
35
+ "test": "node --test tests/*.test.mjs"
36
+ },
37
+ "dependencies": {
38
+ "playwright": "1.62.1",
39
+ "dom-accessibility-api": "^0.7.1"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "~5.9.3",
43
+ "@types/node": "^24.12.0"
44
+ },
45
+ "publishConfig": {
46
+ "access": "public",
47
+ "registry": "https://registry.npmjs.org/"
48
+ }
49
+ }
@@ -0,0 +1,116 @@
1
+ import type { Page } from 'playwright';
2
+ import type { Assertion, AssertionResult } from './schema.js';
3
+ // Declarative browser assertions. Native CSS only; imported cases never execute scripts.
4
+ export async function runAssertion(
5
+ page: Page,
6
+ assertion: Assertion,
7
+ step: number,
8
+ isStopped = () => false,
9
+ ): Promise<AssertionResult> {
10
+ const started = Date.now();
11
+ let actual = '尚未采样';
12
+ let passed = false;
13
+ let stableSince;
14
+ const names = {
15
+ contains: '包含',
16
+ 'not-contains': '不包含',
17
+ equals: '等于',
18
+ 'not-equals': '不等于',
19
+ visible: '可见',
20
+ hidden: '不可见或不存在',
21
+ };
22
+ while (Date.now() - started < assertion.timeout) {
23
+ if (isStopped()) throw Error('已停止');
24
+ const sample = await page.evaluate((a) => {
25
+ const normalize = (text: unknown) =>
26
+ String(text ?? '')
27
+ .replace(/\s+/g, ' ')
28
+ .trim();
29
+ const secret = (element: HTMLInputElement) =>
30
+ /password|passwd|token|secret|credential|验证码|密码|密钥|口令/i.test(
31
+ [
32
+ element.type,
33
+ element.name,
34
+ element.id,
35
+ element.autocomplete,
36
+ element.getAttribute('aria-label'),
37
+ element.getAttribute('placeholder'),
38
+ element.labels?.[0]?.textContent,
39
+ ].join(' '),
40
+ );
41
+ let value;
42
+ if (a.source === 'page-text') value = document.body.innerText;
43
+ else {
44
+ let elements;
45
+ try {
46
+ elements = [...document.querySelectorAll<HTMLElement>(a.selector!)].filter(
47
+ (e) => !e.closest('[data-research-assistant],.research-assistant'),
48
+ );
49
+ } catch {
50
+ return { fatal: 'CSS 定位语法无效' };
51
+ }
52
+ if (a.source === 'element-count') value = String(elements.length);
53
+ else if (!elements.length && a.source === 'element-state' && a.operator === 'hidden')
54
+ value = 'hidden';
55
+ else if (elements.length !== 1)
56
+ return { actual: `匹配到 ${elements.length} 个元素,要求唯一元素`, matched: false };
57
+ else {
58
+ const element = elements[0] as HTMLInputElement;
59
+ if (a.source === 'element-state') {
60
+ const rect = element.getBoundingClientRect();
61
+ const style = getComputedStyle(element);
62
+ value =
63
+ rect.width > 0 &&
64
+ rect.height > 0 &&
65
+ style.visibility !== 'hidden' &&
66
+ style.visibility !== 'collapse'
67
+ ? 'visible'
68
+ : 'hidden';
69
+ } else if (a.source === 'element-value') {
70
+ if (secret(element)) return { fatal: '不读取敏感字段的值' };
71
+ if (!element.matches('input,textarea,select'))
72
+ return { fatal: '输入框值断言只支持 input、textarea、select' };
73
+ value = element.value;
74
+ } else value = element.innerText ?? element.textContent;
75
+ }
76
+ }
77
+ // Text follows rendered whitespace; input values stay exact, including spaces.
78
+ const expected = a.source === 'element-value' ? (a.expected ?? '') : normalize(a.expected);
79
+ const current = a.source === 'element-value' ? String(value) : normalize(value);
80
+ const equal =
81
+ a.source === 'element-count' ? Number(current) === Number(expected) : current === expected;
82
+ const matched =
83
+ a.operator === 'contains'
84
+ ? current.includes(expected)
85
+ : a.operator === 'not-contains'
86
+ ? !current.includes(expected)
87
+ : a.operator === 'equals'
88
+ ? equal
89
+ : a.operator === 'not-equals'
90
+ ? !equal
91
+ : current === a.operator;
92
+ return { actual: current.slice(0, 500), matched };
93
+ }, assertion);
94
+ actual = sample.fatal ?? sample.actual ?? '';
95
+ if (sample.fatal) break;
96
+ if (sample.matched) {
97
+ stableSince ??= Date.now();
98
+ // Avoid accepting a transient match while a post-action view is settling.
99
+ if (Date.now() - stableSince >= 200) {
100
+ passed = true;
101
+ break;
102
+ }
103
+ } else stableSince = undefined;
104
+ await page.waitForTimeout(
105
+ Math.min(100, Math.max(1, assertion.timeout - (Date.now() - started))),
106
+ );
107
+ }
108
+ return {
109
+ step,
110
+ name: assertion.name,
111
+ passed,
112
+ expected: `${names[assertion.operator]}${assertion.expected === undefined ? '' : ` ${assertion.expected === '' ? '空字符串' : assertion.expected}`}`,
113
+ actual: actual === 'visible' ? '可见' : actual === 'hidden' ? '不可见或不存在' : actual,
114
+ duration: Date.now() - started,
115
+ };
116
+ }