pageqa 0.1.0 → 0.3.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/jev.js ADDED
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Jev(TypeSafe System One)API 客户端
3
+ *
4
+ * Jev 是一个结构化决策模型(System One),不做文本生成,只返回
5
+ * 校准后的概率分布。适合用来增强断言验证的语义判断能力。
6
+ *
7
+ * API 文档:https://docs.typesafe.ai
8
+ * Endpoint: POST https://api.typesafe.ai/v1/systemone
9
+ *
10
+ * 用法:
11
+ * const client = new JevClient(config);
12
+ * if (client.enabled) {
13
+ * const prob = await client.assertText(pageText, expectation);
14
+ * // prob ∈ [0, 1],超过阈值即判定成立
15
+ * }
16
+ */
17
+ const DEFAULT_ENDPOINT = "https://api.typesafe.ai/v1/systemone";
18
+ const DEFAULT_TIMEOUT = 15_000; // 毫秒
19
+ /** Jev 客户端 —— 封装对 TypeSafe API 的调用 */
20
+ export class JevClient {
21
+ cfg;
22
+ debug;
23
+ constructor(cfg, debug = false) {
24
+ this.cfg = cfg;
25
+ this.debug = debug;
26
+ }
27
+ /** 当 API 密钥不为空时视为可用 */
28
+ get enabled() {
29
+ return this.cfg.enabled && Boolean(this.cfg.apiKey);
30
+ }
31
+ get threshold() {
32
+ return this.cfg.threshold;
33
+ }
34
+ /**
35
+ * 向 Jev API 发送评估请求
36
+ * @param state 待评估的内容(字符串 / 对象 / 数组)
37
+ * @param questions 问题定义,key 为答案返回时的 id
38
+ */
39
+ async evaluate(state, questions) {
40
+ const t0 = Date.now();
41
+ if (this.debug) {
42
+ process.stderr.write(`[jev] POST /v1/systemone model=${this.cfg.model} state_len=${state.length}\n`);
43
+ }
44
+ const controller = new AbortController();
45
+ const timer = setTimeout(() => controller.abort(), DEFAULT_TIMEOUT);
46
+ try {
47
+ const resp = await fetch(DEFAULT_ENDPOINT, {
48
+ method: "POST",
49
+ headers: {
50
+ "Content-Type": "application/json",
51
+ Authorization: `Bearer ${this.cfg.apiKey}`,
52
+ },
53
+ body: JSON.stringify({
54
+ model: this.cfg.model,
55
+ state,
56
+ questions,
57
+ }),
58
+ signal: controller.signal,
59
+ });
60
+ if (!resp.ok) {
61
+ const body = await resp.text();
62
+ throw new Error(`Jev API ${resp.status}: ${body}`);
63
+ }
64
+ const result = (await resp.json());
65
+ if (this.debug) {
66
+ const answer = result.answers?.["assertion_check"];
67
+ const noul = answer?.type === "noul" ? answer.noul : undefined;
68
+ process.stderr.write(`[jev] 响应 ${resp.status} 耗时=${Date.now() - t0}ms noul=${noul} usage=${JSON.stringify(result.usage)}\n`);
69
+ }
70
+ return result;
71
+ }
72
+ finally {
73
+ clearTimeout(timer);
74
+ }
75
+ }
76
+ /**
77
+ * 语义判断:页面文本是否匹配断言期望
78
+ * @returns 匹配概率 [0, 1]
79
+ */
80
+ async assertText(pageText, expectation) {
81
+ const t0 = Date.now();
82
+ if (this.debug) {
83
+ process.stderr.write(`[jev] assertText 期望="${expectation}" page_len=${pageText.length}\n`);
84
+ }
85
+ const resp = await this.evaluate(pageText, {
86
+ assertion_check: {
87
+ type: "noul",
88
+ instructions: `以下是一段浏览器页面的快照文本。请判断它是否包含或等效于给定的断言期望。考虑大小写变体、同义词、近义表达和格式差异。"${expectation}"`,
89
+ criteria: {
90
+ yes: "页面文本中包含或等效于期望的内容",
91
+ no: "页面文本中没有找到与期望相关的内容",
92
+ },
93
+ },
94
+ });
95
+ const answer = resp.answers?.["assertion_check"];
96
+ const noul = answer?.type === "noul" ? answer.noul : 0;
97
+ if (this.debug) {
98
+ process.stderr.write(`[jev] assertText 结果 noul=${noul} 阈值=${this.cfg.threshold} 耗时=${Date.now() - t0}ms\n`);
99
+ }
100
+ return noul;
101
+ }
102
+ }
103
+ //# sourceMappingURL=jev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jev.js","sourceRoot":"","sources":["../src/jev.ts"],"names":[],"mappings":"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"}
package/dist/llm.js CHANGED
@@ -2,12 +2,12 @@ import { createModels, createProvider } from "@earendil-works/pi-ai";
2
2
  import * as openaiCompletions from "@earendil-works/pi-ai/api/openai-completions";
3
3
  import { loadConfig } from "./config.js";
4
4
  /**
5
- * LLM 后端:基于 @earendil-works/pi-ai 构造一个指向 CodeBuddy 本地反代的自定义 provider。
5
+ * LLM 后端:基于 @earendil-works/pi-ai 构造一个指向可配置的 OpenAI 兼容端点的自定义 provider。
6
6
  *
7
7
  * 配置优先级(高 -> 低):环境变量 PAGEQA_LLM_* > 用户配置文件(~/.pageqa/config.json) > 内置默认值。
8
- * 首次运行会自动在用户主目录创建配置文件,便于用户修改模型/反代地址/密钥。
8
+ * 首次运行会自动在用户主目录创建配置文件,便于用户修改模型/端点地址/密钥。
9
9
  */
10
- const PROVIDER_ID = "codebuddy";
10
+ const PROVIDER_ID = "pageqa";
11
11
  const MODELS = [
12
12
  { id: "hunyuan-2.0-instruct", name: "Hunyuan 2.0 Instruct", contextWindow: 32000, maxOutput: 64000 },
13
13
  { id: "hy3", name: "Hy3", contextWindow: 192000, maxOutput: 64000 },
@@ -16,7 +16,7 @@ const MODELS = [
16
16
  /** 构造一个静态解析的 ApiKeyAuth(避免交互式 env 探测)。 */
17
17
  function staticApiKeyAuth(apiKey, baseUrl) {
18
18
  return {
19
- name: "CodeBuddy Proxy",
19
+ name: "OpenAI Compatible",
20
20
  resolve: async () => ({ auth: { apiKey, baseUrl }, source: "static" }),
21
21
  };
22
22
  }
@@ -28,7 +28,7 @@ export function createLlmBackend() {
28
28
  const models = createModels();
29
29
  const provider = createProvider({
30
30
  id: PROVIDER_ID,
31
- name: "CodeBuddy (本地反代)",
31
+ name: "OpenAI Compatible (自定义端点)",
32
32
  auth: { apiKey: staticApiKeyAuth(DEFAULT_API_KEY, DEFAULT_BASE_URL) },
33
33
  models: MODELS.map((m) => ({
34
34
  id: m.id,
@@ -55,7 +55,7 @@ export function createLlmBackend() {
55
55
  models.setProvider(provider);
56
56
  const model = models.getModel(PROVIDER_ID, DEFAULT_MODEL);
57
57
  if (!model) {
58
- throw new Error(`未找到模型 ${DEFAULT_MODEL}(provider=${PROVIDER_ID})。请确认 CodeBuddy 反代可用。`);
58
+ throw new Error(`未找到模型 ${DEFAULT_MODEL}(provider=${PROVIDER_ID})。请检查 ~/.pageqa/config.json 中的 model 与 baseUrl 配置。`);
59
59
  }
60
60
  return { models, model };
61
61
  }
package/dist/llm.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAwB,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,iBAAiB,MAAM,8CAA8C,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AAEH,MAAM,WAAW,GAAG,WAAW,CAAC;AAShC,MAAM,MAAM,GAA2B;IACrC,EAAE,EAAE,EAAE,sBAAsB,EAAE,IAAI,EAAE,sBAAsB,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE;IACpG,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE;IACnE,EAAE,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,qBAAqB,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE;CACzG,CAAC;AAEF,0CAA0C;AAC1C,SAAS,gBAAgB,CAAC,MAAc,EAAE,OAAe;IACvD,OAAO;QACL,IAAI,EAAE,iBAAiB;QACvB,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KACvE,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,gBAAgB;IAC9B,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,MAAM,gBAAgB,GAAG,GAAG,CAAC,OAAO,CAAC;IACrC,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC;IACnC,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC;IAChC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,QAAQ,GAAG,cAAc,CAAC;QAC9B,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,kBAAkB;QACxB,IAAI,EAAE,EAAE,MAAM,EAAE,gBAAgB,CAAC,eAAe,EAAE,gBAAgB,CAAC,EAAE;QACrE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzB,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,EAAE,oBAAoB;YACzB,QAAQ,EAAE,WAAW;YACrB,OAAO,EAAE,gBAAgB;YACzB,KAAK,EAAE,CAAC,MAAM,CAAC;YACf,SAAS,EAAE,KAAK;YAChB,aAAa,EAAE,CAAC,CAAC,aAAa;YAC9B,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE;YAC1D,MAAM,EAAE;gBACN,aAAa,EAAE,KAAK;gBACpB,qBAAqB,EAAE,KAAK;gBAC5B,uBAAuB,EAAE,KAAK;gBAC9B,cAAc,EAAE,YAAY;gBAC5B,0BAA0B,EAAE,KAAK;aAClC;SACF,CAAC,CAAC;QACH,GAAG,EAAE,iBAAiB;KACvB,CAAC,CAAC;IACH,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAE7B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,SAAS,aAAa,aAAa,WAAW,uBAAuB,CAAC,CAAC;IACzF,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC"}
1
+ {"version":3,"file":"llm.js","sourceRoot":"","sources":["../src/llm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAwB,MAAM,uBAAuB,CAAC;AAC3F,OAAO,KAAK,iBAAiB,MAAM,8CAA8C,CAAC;AAClF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;;;GAKG;AAEH,MAAM,WAAW,GAAG,QAAQ,CAAC;AAS7B,MAAM,MAAM,GAAqB;IAC/B,EAAE,EAAE,EAAE,sBAAsB,EAAE,IAAI,EAAE,sBAAsB,EAAE,aAAa,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE;IACpG,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE;IACnE,EAAE,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,qBAAqB,EAAE,aAAa,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE;CACzG,CAAC;AAEF,0CAA0C;AAC1C,SAAS,gBAAgB,CAAC,MAAc,EAAE,OAAe;IACvD,OAAO;QACL,IAAI,EAAE,mBAAmB;QACzB,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KACvE,CAAC;AACJ,CAAC;AAOD,MAAM,UAAU,gBAAgB;IAC9B,MAAM,GAAG,GAAG,UAAU,EAAE,CAAC;IACzB,MAAM,gBAAgB,GAAG,GAAG,CAAC,OAAO,CAAC;IACrC,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC;IACnC,MAAM,aAAa,GAAG,GAAG,CAAC,KAAK,CAAC;IAChC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,QAAQ,GAAG,cAAc,CAAC;QAC9B,EAAE,EAAE,WAAW;QACf,IAAI,EAAE,2BAA2B;QACjC,IAAI,EAAE,EAAE,MAAM,EAAE,gBAAgB,CAAC,eAAe,EAAE,gBAAgB,CAAC,EAAE;QACrE,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YACzB,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,GAAG,EAAE,oBAAoB;YACzB,QAAQ,EAAE,WAAW;YACrB,OAAO,EAAE,gBAAgB;YACzB,KAAK,EAAE,CAAC,MAAM,CAAC;YACf,SAAS,EAAE,KAAK;YAChB,aAAa,EAAE,CAAC,CAAC,aAAa;YAC9B,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,SAAS,EAAE,CAAC,CAAC,SAAS;YACtB,IAAI,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC,EAAE;YAC1D,MAAM,EAAE;gBACN,aAAa,EAAE,KAAK;gBACpB,qBAAqB,EAAE,KAAK;gBAC5B,uBAAuB,EAAE,KAAK;gBAC9B,cAAc,EAAE,YAAY;gBAC5B,0BAA0B,EAAE,KAAK;aAClC;SACF,CAAC,CAAC;QACH,GAAG,EAAE,iBAAiB;KACvB,CAAC,CAAC;IACH,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IAE7B,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC1D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,SAAS,aAAa,aAAa,WAAW,oDAAoD,CACnG,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;AAC3B,CAAC"}
package/dist/report.d.ts CHANGED
@@ -14,3 +14,14 @@ export interface TestReport {
14
14
  * 并用「断言「X」:成立。证据...」句式。无法解析时降级为基于 PASS/FAIL 关键字的整体判断。
15
15
  */
16
16
  export declare function buildReport(input: string, transcript: string): TestReport;
17
+ /** 统计用例(自然语言测试步骤)中的断言数量,用于校验执行完整性。 */
18
+ export declare function countAssertions(script: string): number;
19
+ /**
20
+ * 解析 agent 的进度声明「步骤完成:M/N」。
21
+ * 取最后一次声明:续跑时 agent 会输出多条进度,只有最后一条代表当前状态。
22
+ */
23
+ export declare function parseProgress(text: string): {
24
+ done: number;
25
+ total: number;
26
+ } | null;
27
+ export declare function parseAssertions(text: string): AssertionResult[];
package/dist/report.js CHANGED
@@ -10,32 +10,88 @@ export function buildReport(input, transcript) {
10
10
  }
11
11
  else {
12
12
  // 无结构化断言:依据关键字与「不成立/失败」判定
13
- const lowered = transcript.toLowerCase();
14
13
  const negative = /不成立|未找到|失败|不存在|not found|fail|missing/.test(transcript);
15
14
  status = negative ? "fail" : "pass";
16
15
  }
16
+ // 完整性校验:用例里写了 N 条断言,就应当看到 N 条结果;
17
+ // 少了解析结果说明 agent 只跑了其中一部分(长流程最容易半途结束)。
18
+ const expected = countAssertions(input);
19
+ if (expected > 0 && assertions.length < expected) {
20
+ status = "fail";
21
+ assertions.push({
22
+ expectation: `用例中的断言全部执行(实际 ${assertions.length}/${expected})`,
23
+ verdict: "fail",
24
+ evidence: "解析到的断言少于用例中的断言数量,疑似步骤未执行完就结束",
25
+ });
26
+ }
27
+ // agent 自报的步骤进度未跑满,同样判失败。
28
+ const progress = parseProgress(transcript);
29
+ if (progress && progress.done < progress.total) {
30
+ status = "fail";
31
+ assertions.push({
32
+ expectation: `全部步骤执行完成(${progress.done}/${progress.total})`,
33
+ verdict: "fail",
34
+ evidence: "agent 自报的步骤完成度不足",
35
+ });
36
+ }
17
37
  const summary = extractSummary(transcript);
18
38
  return { status, assertions, summary, transcript };
19
39
  }
20
- function parseAssertions(text) {
40
+ /** 统计用例(自然语言测试步骤)中的断言数量,用于校验执行完整性。 */
41
+ export function countAssertions(script) {
42
+ return (script.match(/断言/g) ?? []).length;
43
+ }
44
+ /**
45
+ * 解析 agent 的进度声明「步骤完成:M/N」。
46
+ * 取最后一次声明:续跑时 agent 会输出多条进度,只有最后一条代表当前状态。
47
+ */
48
+ export function parseProgress(text) {
49
+ const re = /步骤完成\s*[::]\s*(\d+)\s*[//]\s*(\d+)/g;
50
+ const matches = [...text.matchAll(re)];
51
+ const m = matches.at(-1);
52
+ if (!m)
53
+ return null;
54
+ const done = Number(m[1]);
55
+ const total = Number(m[2]);
56
+ if (!Number.isFinite(done) || !Number.isFinite(total) || total <= 0)
57
+ return null;
58
+ return { done, total };
59
+ }
60
+ export function parseAssertions(text) {
21
61
  const results = [];
22
- // 匹配两种常见句式:
23
- // 断言「X」:成立/不成立。证据...
62
+ // 预处理:
63
+ // 1) 去掉 markdown 加粗标记(agent 常输出「…:**成立**」)
64
+ // 2) 把「成立/不成立」前的换行折回同一行
65
+ const cleaned = text
66
+ .replace(/\*\*|__/g, "")
67
+ .replace(/[ \t]*\n[ \t]*(?=(?:成立|不成立))/g, "");
68
+ // 匹配两种常见句式(描述里可含「」/引号等标点):
69
+ // 断言 X:成立/不成立。证据...
24
70
  // 断言「X」成立。证据...
25
- const re = /断言[「"']?([^」"']+?)[""」]?\s*(?:[::]\s*)?(成立|不成立)/g;
71
+ const re = /断言\s*(.+?)\s*(?:[::]\s*)?(不成立|成立)/g;
26
72
  let m;
27
- while ((m = re.exec(text)) !== null) {
28
- const expectation = m[1].trim();
73
+ while ((m = re.exec(cleaned)) !== null) {
74
+ const expectation = stripEdges(m[1]);
29
75
  const verdict = m[2] === "成立" ? "pass" : "fail";
30
- const after = text.slice(m.index + m[0].length).split("\n")[0].trim();
31
- const evidence = after.startsWith("。") ? after.slice(1).trim() : after;
76
+ const after = cleaned.slice(m.index + m[0].length).split("\n")[0];
77
+ const evidence = stripEdges(after);
32
78
  results.push({ expectation, verdict, evidence: evidence || undefined });
33
79
  }
34
80
  return results;
35
81
  }
82
+ /** 去掉首尾的连接符/标点噪声(agent 常写「…:**成立**,证据…」「…」——成立)。 */
83
+ function stripEdges(text) {
84
+ return text
85
+ .replace(/^[\s,,。、;;::]+/, "")
86
+ .replace(/[\s,,。、;;::\-—]+$/, "")
87
+ .trim();
88
+ }
36
89
  function extractSummary(text) {
37
90
  // 取最后一段非空文本作为摘要
38
- const paras = text.split("\n").map((l) => l.trim()).filter(Boolean);
91
+ const paras = text
92
+ .split("\n")
93
+ .map((l) => l.trim())
94
+ .filter(Boolean);
39
95
  return paras.at(-1) ?? "";
40
96
  }
41
97
  //# sourceMappingURL=report.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,UAAkB;IAC3D,MAAM,UAAU,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAE/C,IAAI,MAAuB,CAAC;IAC5B,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IAC3E,CAAC;SAAM,CAAC;QACN,0BAA0B;QAC1B,MAAM,OAAO,GAAG,UAAU,CAAC,WAAW,EAAE,CAAC;QACzC,MAAM,QAAQ,GAAG,uCAAuC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC1E,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;IACtC,CAAC;IAED,MAAM,OAAO,GAAG,cAAc,CAAC,UAAU,CAAC,CAAC;IAC3C,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC;AACrD,CAAC;AAED,SAAS,eAAe,CAAC,IAAY;IACnC,MAAM,OAAO,GAAsB,EAAE,CAAC;IACtC,YAAY;IACZ,uBAAuB;IACvB,kBAAkB;IAClB,MAAM,EAAE,GAAG,kDAAkD,CAAC;IAC9D,IAAI,CAAyB,CAAC;IAC9B,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC;QACpC,MAAM,WAAW,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAChC,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC;QAChD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtE,MAAM,QAAQ,GAAG,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;QACvE,OAAO,CAAC,IAAI,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,IAAI,SAAS,EAAE,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,SAAS,cAAc,CAAC,IAAY;IAClC,gBAAgB;IAChB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACpE,OAAO,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;AAC5B,CAAC"}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
package/dist/vars.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 脚本运行时变量:读取测试脚本时把 `${...}` 占位符替换为本次运行的实际取值,
3
+ * 避免用例名称里的时间戳被写死(例如反复运行会撞名)。
4
+ *
5
+ * 支持 `${名称}` 与 `${名称:<格式>}` 两种写法:
6
+ * - timestamp -> yyyyMMddHHmm(默认格式)
7
+ * - date -> yyyyMMdd
8
+ * - time -> HHmmss
9
+ * - datetime -> yyyyMMddHHmmss
10
+ * 自定义格式支持 yyyy / yy / MM / dd / HH / mm / ss / SSS 组合,例如
11
+ * `${timestamp:yyyy-MM-dd HH:mm}`。
12
+ *
13
+ * 未识别的名称或格式原样保留,因此脚本里写 `${PATH}` 之类的字面量不会被误替换。
14
+ */
15
+ /** 按本机本地时间格式化,支持 yyyy/yy/MM/dd/HH/mm/ss/SSS。 */
16
+ export declare function formatLocalTime(date: Date, pattern: string): string;
17
+ /** 展开脚本中的运行时变量占位符;同一脚本共用同一时刻,保证各处取值一致。 */
18
+ export declare function expandVars(script: string, now?: Date): string;
19
+ /** 供 `--help`/文档展示的占位符清单(读取脚本时按运行时本地时间展开)。 */
20
+ export declare const VAR_HELP: string;
package/dist/vars.js ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * 脚本运行时变量:读取测试脚本时把 `${...}` 占位符替换为本次运行的实际取值,
3
+ * 避免用例名称里的时间戳被写死(例如反复运行会撞名)。
4
+ *
5
+ * 支持 `${名称}` 与 `${名称:<格式>}` 两种写法:
6
+ * - timestamp -> yyyyMMddHHmm(默认格式)
7
+ * - date -> yyyyMMdd
8
+ * - time -> HHmmss
9
+ * - datetime -> yyyyMMddHHmmss
10
+ * 自定义格式支持 yyyy / yy / MM / dd / HH / mm / ss / SSS 组合,例如
11
+ * `${timestamp:yyyy-MM-dd HH:mm}`。
12
+ *
13
+ * 未识别的名称或格式原样保留,因此脚本里写 `${PATH}` 之类的字面量不会被误替换。
14
+ */
15
+ const PRESETS = {
16
+ timestamp: "yyyyMMddHHmm",
17
+ date: "yyyyMMdd",
18
+ time: "HHmmss",
19
+ datetime: "yyyyMMddHHmmss",
20
+ };
21
+ const PLACEHOLDER = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?::([^}]*))?\}/g;
22
+ function pad(n, len) {
23
+ return String(n).padStart(len, "0");
24
+ }
25
+ /** 按本机本地时间格式化,支持 yyyy/yy/MM/dd/HH/mm/ss/SSS。 */
26
+ export function formatLocalTime(date, pattern) {
27
+ return pattern.replace(/yyyy|yy|MM|dd|HH|mm|ss|SSS/g, (token) => {
28
+ switch (token) {
29
+ case "yyyy":
30
+ return pad(date.getFullYear(), 4);
31
+ case "yy":
32
+ return pad(date.getFullYear() % 100, 2);
33
+ case "MM":
34
+ return pad(date.getMonth() + 1, 2);
35
+ case "dd":
36
+ return pad(date.getDate(), 2);
37
+ case "HH":
38
+ return pad(date.getHours(), 2);
39
+ case "mm":
40
+ return pad(date.getMinutes(), 2);
41
+ case "ss":
42
+ return pad(date.getSeconds(), 2);
43
+ case "SSS":
44
+ return pad(date.getMilliseconds(), 3);
45
+ default:
46
+ return token;
47
+ }
48
+ });
49
+ }
50
+ /** 展开脚本中的运行时变量占位符;同一脚本共用同一时刻,保证各处取值一致。 */
51
+ export function expandVars(script, now = new Date()) {
52
+ return script.replace(PLACEHOLDER, (whole, name, pattern) => {
53
+ const preset = Object.prototype.hasOwnProperty.call(PRESETS, name)
54
+ ? PRESETS[name]
55
+ : undefined;
56
+ if (preset === undefined)
57
+ return whole;
58
+ const fmt = pattern?.trim();
59
+ return formatLocalTime(now, fmt ? fmt : preset);
60
+ });
61
+ }
62
+ /** 供 `--help`/文档展示的占位符清单(读取脚本时按运行时本地时间展开)。 */
63
+ export const VAR_HELP = [
64
+ " ${timestamp} yyyyMMddHHmm(如 202609191146)",
65
+ " ${date} / ${time} yyyyMMdd / HHmmss",
66
+ " ${datetime} yyyyMMddHHmmss",
67
+ " ${timestamp:<格式>} 自定义格式,支持 yyyy/yy/MM/dd/HH/mm/ss/SSS",
68
+ " 例:${timestamp:yyyy-MM-dd HH:mm}",
69
+ ].join("\n");
70
+ //# sourceMappingURL=vars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vars.js","sourceRoot":"","sources":["../src/vars.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,OAAO,GAA2B;IACtC,SAAS,EAAE,cAAc;IACzB,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,gBAAgB;CAC3B,CAAC;AAEF,MAAM,WAAW,GAAG,8CAA8C,CAAC;AAEnE,SAAS,GAAG,CAAC,CAAS,EAAE,GAAW;IACjC,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AACtC,CAAC;AAED,gDAAgD;AAChD,MAAM,UAAU,eAAe,CAAC,IAAU,EAAE,OAAe;IACzD,OAAO,OAAO,CAAC,OAAO,CAAC,6BAA6B,EAAE,CAAC,KAAK,EAAE,EAAE;QAC9D,QAAQ,KAAK,EAAE,CAAC;YACd,KAAK,MAAM;gBACT,OAAO,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;YACpC,KAAK,IAAI;gBACP,OAAO,GAAG,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;YAC1C,KAAK,IAAI;gBACP,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACrC,KAAK,IAAI;gBACP,OAAO,GAAG,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;YAChC,KAAK,IAAI;gBACP,OAAO,GAAG,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,CAAC;YACjC,KAAK,IAAI;gBACP,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;YACnC,KAAK,IAAI;gBACP,OAAO,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,CAAC;YACnC,KAAK,KAAK;gBACR,OAAO,GAAG,CAAC,IAAI,CAAC,eAAe,EAAE,EAAE,CAAC,CAAC,CAAC;YACxC;gBACE,OAAO,KAAK,CAAC;QACjB,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC;AAED,0CAA0C;AAC1C,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,MAAY,IAAI,IAAI,EAAE;IAC/D,OAAO,MAAM,CAAC,OAAO,CACnB,WAAW,EACX,CAAC,KAAa,EAAE,IAAY,EAAE,OAAgB,EAAE,EAAE;QAChD,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC;YAChE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC;YACf,CAAC,CAAC,SAAS,CAAC;QACd,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,KAAK,CAAC;QACvC,MAAM,GAAG,GAAG,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5B,OAAO,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAClD,CAAC,CACF,CAAC;AACJ,CAAC;AAED,8CAA8C;AAC9C,MAAM,CAAC,MAAM,QAAQ,GAAG;IACtB,6DAA6D;IAC7D,kDAAkD;IAClD,+CAA+C;IAC/C,kEAAkE;IAClE,gEAAgE;CACjE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ # Element Plus 上传组件用例:点击 Click to upload 上传本地图片
2
+
3
+ > 本文件是 pageqa 的自然语言测试脚本。`# ` 标题与本节说明会被忽略,只有 `## ` 场景下的内容会被执行。
4
+ > 前置:bsk daemon 已连接一个真实浏览器;本地文件 `D:\Downloads\二维码.png` 存在。
5
+
6
+ ## U1 点击 Click to upload 上传文件并校验文件出现在上传列表
7
+
8
+ 打开 https://element-plus.org/zh-CN/component/upload
9
+ 读取页面内容,确认已进入 Element Plus 的 Upload 上传组件文档页
10
+ 在「基础用法」示例中定位显示「Click to upload」的上传触发按钮
11
+ 点击该「Click to upload」按钮并上传本地文件 D:\Downloads\二维码.png(用 upload 工具把该按钮作为触发元素,不要单独 click,否则会弹出无法自动化的系统文件选择框)
12
+ 等待 2 秒,让上传列表完成渲染
13
+ 断言页面中已出现上传的文件名 二维码.png
@@ -0,0 +1,19 @@
1
+ # GitHub 仓库 Star 用例:klren0312/pageqa
2
+
3
+ > 本文件是 pageqa 的自然语言测试脚本。`# ` 标题与本节说明会被忽略,只有 `## ` 场景下的内容会被执行。
4
+ > 前置:bsk daemon 已连接一个**已登录 GitHub** 的真实浏览器;未登录时 Star 会跳转登录页,G2 将 FAIL。
5
+
6
+ ## G1 打开仓库页并校验内容
7
+
8
+ 打开 https://github.com/klren0312/pageqa
9
+ 读取页面内容
10
+ 断言标题包含 pageqa
11
+ 断言页面包含 klren0312/pageqa
12
+
13
+ ## G2 为仓库点 Star(幂等)
14
+
15
+ 打开 https://github.com/klren0312/pageqa
16
+ 读取页面内容,定位页面右上角的星标按钮(当前显示为「Star」或「Starred」)
17
+ 如果星标按钮显示「Star」,点击它并等待 2 秒
18
+ 如果星标按钮已经显示「Starred」,跳过点击,直接进入断言
19
+ 断言星标按钮当前显示为「Starred」
package/examples/smoke.md CHANGED
@@ -10,6 +10,3 @@
10
10
 
11
11
  ## A3 失败场景(断言不存在文本)
12
12
  打开 https://example.com 并断言页面包含 'THIS_TEXT_SHOULD_NOT_EXIST_XYZ'
13
-
14
- > 说明:自然语言意图由 pi-agent-core 驱动的 agent 解析为浏览器操作步骤,并经 browserskill(bsk)连接真实浏览器执行。
15
- > 默认 LLM 后端为 CodeBuddy 本地反代(混元),无需官网 Key。
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "pageqa",
3
- "version": "0.1.0",
4
- "description": "用自然语言驱动的页面测试 agent:基于 pi-agent-core 编排 + browserskill 驱动真实浏览器,经 CodeBuddy 反代(混元)实现免 Key 自然语言测试。",
3
+ "version": "0.3.0",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/klren0312/pageqa.git"
7
+ },
8
+ "description": "用自然语言驱动的页面 QA agent:pi-agent-core 编排测试步骤,browserskill(bsk) 驱动真实浏览器执行,LLM 端点可配置(OpenAI 兼容);输出文本/JSON 报告与可直接接入 CI 的退出码。",
5
9
  "type": "module",
6
10
  "bin": {
7
11
  "pageqa": "dist/index.js"
@@ -12,6 +16,7 @@
12
16
  "start": "node dist/index.js",
13
17
  "pretest": "npm run build",
14
18
  "test": "node --test tests/smoke.test.mjs",
19
+ "test:unit": "npm run build && node --test tests/report.test.mjs",
15
20
  "prepublishOnly": "npm run build"
16
21
  },
17
22
  "files": [