@fasttest-ai/qa-agent 0.1.2 → 0.2.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.
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ // CLI entry point — CI runner (no MCP)
3
+ import "../dist/cli.js";
package/dist/cli.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * QA Agent CI Runner — headless test execution for CI/CD pipelines.
4
+ * No MCP dependency. Calls executeRun() directly.
5
+ *
6
+ * Usage:
7
+ * qa-agent-ci --api-key <key> --suite-id <id> [options]
8
+ *
9
+ * Options:
10
+ * --api-key Organization API key (required)
11
+ * --suite-id Test suite ID to run (required)
12
+ * --base-url Cloud API base URL (default: https://api.qa-agent.dev)
13
+ * --app-url Override the application URL for test navigation
14
+ * --pr-url GitHub PR URL for posting results as a comment
15
+ * --browser Browser engine: chromium | firefox | webkit (default: chromium)
16
+ * --test-case-ids Comma-separated test case IDs to run (default: all)
17
+ * --json Output results as JSON instead of formatted text
18
+ */
19
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,182 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * QA Agent CI Runner — headless test execution for CI/CD pipelines.
4
+ * No MCP dependency. Calls executeRun() directly.
5
+ *
6
+ * Usage:
7
+ * qa-agent-ci --api-key <key> --suite-id <id> [options]
8
+ *
9
+ * Options:
10
+ * --api-key Organization API key (required)
11
+ * --suite-id Test suite ID to run (required)
12
+ * --base-url Cloud API base URL (default: https://api.qa-agent.dev)
13
+ * --app-url Override the application URL for test navigation
14
+ * --pr-url GitHub PR URL for posting results as a comment
15
+ * --browser Browser engine: chromium | firefox | webkit (default: chromium)
16
+ * --test-case-ids Comma-separated test case IDs to run (default: all)
17
+ * --json Output results as JSON instead of formatted text
18
+ */
19
+ import { BrowserManager } from "./browser.js";
20
+ import { CloudClient } from "./cloud.js";
21
+ import { executeRun } from "./runner.js";
22
+ function parseCliArgs() {
23
+ const args = process.argv.slice(2);
24
+ let apiKey = "";
25
+ let suiteId = "";
26
+ let baseUrl = "https://api.qa-agent.dev";
27
+ let appUrl;
28
+ let prUrl;
29
+ let browserType = "chromium";
30
+ let testCaseIds;
31
+ let json = false;
32
+ for (let i = 0; i < args.length; i++) {
33
+ switch (args[i]) {
34
+ case "--api-key":
35
+ apiKey = args[++i] ?? "";
36
+ break;
37
+ case "--suite-id":
38
+ suiteId = args[++i] ?? "";
39
+ break;
40
+ case "--base-url":
41
+ baseUrl = args[++i] ?? baseUrl;
42
+ break;
43
+ case "--app-url":
44
+ appUrl = args[++i];
45
+ break;
46
+ case "--pr-url":
47
+ prUrl = args[++i];
48
+ break;
49
+ case "--browser":
50
+ browserType = (args[++i] ?? "chromium");
51
+ break;
52
+ case "--test-case-ids":
53
+ testCaseIds = (args[++i] ?? "").split(",").map((s) => s.trim()).filter(Boolean);
54
+ break;
55
+ case "--json":
56
+ json = true;
57
+ break;
58
+ }
59
+ }
60
+ if (!apiKey || !suiteId) {
61
+ console.error("Usage: qa-agent-ci --api-key <key> --suite-id <id> [--base-url <url>] " +
62
+ "[--app-url <url>] [--pr-url <url>] [--browser chromium|firefox|webkit] " +
63
+ "[--test-case-ids id1,id2] [--json]");
64
+ process.exit(1);
65
+ }
66
+ return { apiKey, suiteId, baseUrl, appUrl, prUrl, browser: browserType, testCaseIds, json };
67
+ }
68
+ // ---------------------------------------------------------------------------
69
+ // Formatted output
70
+ // ---------------------------------------------------------------------------
71
+ function printFormattedResults(summary) {
72
+ const statusLabel = summary.status === "passed" ? "PASSED" : "FAILED";
73
+ console.log(`--- Results: ${statusLabel} ---`);
74
+ console.log(`Execution: ${summary.execution_id}`);
75
+ console.log(`Total: ${summary.total} | Passed: ${summary.passed} | Failed: ${summary.failed} | Skipped: ${summary.skipped}`);
76
+ console.log(`Duration: ${(summary.duration_ms / 1000).toFixed(1)}s`);
77
+ console.log("");
78
+ for (const r of summary.results) {
79
+ const icon = r.status === "passed" ? "PASS" : r.status === "failed" ? "FAIL" : "SKIP";
80
+ console.log(` [${icon}] ${r.name} (${r.duration_ms}ms)`);
81
+ if (r.error) {
82
+ console.log(` Error: ${r.error}`);
83
+ }
84
+ }
85
+ if (summary.healed.length > 0) {
86
+ console.log("");
87
+ console.log(`--- Self-Healed: ${summary.healed.length} selector(s) ---`);
88
+ for (const h of summary.healed) {
89
+ console.log(` "${h.test_case}" step ${h.step_index + 1}`);
90
+ console.log(` ${h.original_selector} -> ${h.new_selector}`);
91
+ console.log(` Strategy: ${h.strategy} (${Math.round(h.confidence * 100)}% confidence)`);
92
+ }
93
+ }
94
+ }
95
+ // ---------------------------------------------------------------------------
96
+ // Main
97
+ // ---------------------------------------------------------------------------
98
+ async function main() {
99
+ const config = parseCliArgs();
100
+ const orgSlug = config.apiKey.split("_")[1] ?? "default";
101
+ const browserMgr = new BrowserManager({
102
+ browserType: config.browser,
103
+ headless: true,
104
+ orgSlug,
105
+ });
106
+ const cloud = new CloudClient({
107
+ apiKey: config.apiKey,
108
+ baseUrl: config.baseUrl,
109
+ });
110
+ const consoleLogs = [];
111
+ console.log("FastTest CI Runner v0.1.3");
112
+ console.log(`Suite: ${config.suiteId}`);
113
+ console.log(`Browser: ${config.browser}`);
114
+ if (config.appUrl)
115
+ console.log(`App URL: ${config.appUrl}`);
116
+ console.log("");
117
+ let summary;
118
+ try {
119
+ summary = await executeRun(browserMgr, cloud, {
120
+ suiteId: config.suiteId,
121
+ testCaseIds: config.testCaseIds,
122
+ appUrlOverride: config.appUrl,
123
+ }, consoleLogs);
124
+ }
125
+ catch (err) {
126
+ console.error(`Fatal: ${err}`);
127
+ await safeClose(browserMgr);
128
+ process.exit(1);
129
+ }
130
+ // Output results
131
+ if (config.json) {
132
+ console.log(JSON.stringify(summary, null, 2));
133
+ }
134
+ else {
135
+ printFormattedResults(summary);
136
+ }
137
+ // Post PR comment if requested
138
+ if (config.prUrl) {
139
+ try {
140
+ const prResult = await cloud.postPrComment({
141
+ pr_url: config.prUrl,
142
+ execution_id: summary.execution_id,
143
+ status: summary.status,
144
+ total: summary.total,
145
+ passed: summary.passed,
146
+ failed: summary.failed,
147
+ skipped: summary.skipped,
148
+ duration_seconds: Math.round(summary.duration_ms / 1000),
149
+ test_results: summary.results.map((r) => ({
150
+ name: r.name,
151
+ status: r.status,
152
+ error: r.error,
153
+ })),
154
+ healed: summary.healed.map((h) => ({
155
+ original_selector: h.original_selector,
156
+ new_selector: h.new_selector,
157
+ strategy: h.strategy,
158
+ confidence: h.confidence,
159
+ })),
160
+ });
161
+ const commentUrl = prResult.comment_url;
162
+ console.log(`\nPR comment posted: ${commentUrl ?? config.prUrl}`);
163
+ }
164
+ catch (err) {
165
+ console.error(`\nFailed to post PR comment: ${err}`);
166
+ }
167
+ }
168
+ await safeClose(browserMgr);
169
+ process.exit(summary.status === "passed" ? 0 : 1);
170
+ }
171
+ /** Close browser with a timeout so CI doesn't hang. */
172
+ async function safeClose(browserMgr) {
173
+ await Promise.race([
174
+ browserMgr.close(),
175
+ new Promise((resolve) => setTimeout(resolve, 5000)),
176
+ ]);
177
+ }
178
+ main().catch((err) => {
179
+ console.error("Fatal:", err);
180
+ process.exit(1);
181
+ });
182
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cloud.d.ts CHANGED
@@ -5,6 +5,14 @@ export interface CloudClientOptions {
5
5
  apiKey: string;
6
6
  baseUrl?: string;
7
7
  }
8
+ export interface CreateOrgResponse {
9
+ id: string;
10
+ name: string;
11
+ slug: string;
12
+ api_key: string;
13
+ plan: string;
14
+ created_at: string;
15
+ }
8
16
  export interface RunResponse {
9
17
  execution_id: string;
10
18
  status: string;
@@ -51,6 +59,14 @@ export declare class CloudClient {
51
59
  private apiKey;
52
60
  private baseUrl;
53
61
  constructor(options: CloudClientOptions);
62
+ /**
63
+ * Create a new organization. This is unauthenticated — no API key needed.
64
+ * Used by the `setup` tool for first-time onboarding.
65
+ */
66
+ static createOrg(baseUrl: string, body: {
67
+ name: string;
68
+ slug: string;
69
+ }): Promise<CreateOrgResponse>;
54
70
  private request;
55
71
  get<T>(path: string): Promise<T>;
56
72
  post<T>(path: string, body?: unknown): Promise<T>;
@@ -59,17 +75,84 @@ export declare class CloudClient {
59
75
  }>;
60
76
  getOrg(): Promise<Record<string, unknown>>;
61
77
  listProjects(): Promise<Record<string, unknown>>;
78
+ /** Get-or-create a project by name. Returns project with id. */
79
+ resolveProject(name: string, baseUrl?: string): Promise<{
80
+ id: string;
81
+ name: string;
82
+ }>;
83
+ /** List all test suites across all projects for the org. */
84
+ listSuites(search?: string): Promise<Array<Record<string, unknown>>>;
85
+ /** Resolve a suite by name (case-insensitive). Returns the best match. */
86
+ resolveSuite(name: string, projectId?: string): Promise<{
87
+ id: string;
88
+ name: string;
89
+ }>;
90
+ /** Create a test suite under a project. */
91
+ createSuite(projectId: string, body: {
92
+ name: string;
93
+ description?: string;
94
+ test_type?: string;
95
+ auto_generated?: boolean;
96
+ tags?: string[];
97
+ }): Promise<{
98
+ id: string;
99
+ name: string;
100
+ }>;
101
+ /** Update an existing test suite. */
102
+ updateSuite(projectId: string, suiteId: string, body: {
103
+ name?: string;
104
+ description?: string;
105
+ tags?: string[];
106
+ }): Promise<{
107
+ id: string;
108
+ name: string;
109
+ }>;
110
+ /** Create a test case and link it to suite(s). */
111
+ createTestCase(body: {
112
+ name: string;
113
+ description?: string;
114
+ priority?: string;
115
+ steps: Array<Record<string, unknown>>;
116
+ assertions: Array<Record<string, unknown>>;
117
+ tags?: string[];
118
+ test_suite_ids: string[];
119
+ auto_generated?: boolean;
120
+ generated_by_agent?: boolean;
121
+ natural_language_source?: string;
122
+ }): Promise<{
123
+ id: string;
124
+ name: string;
125
+ }>;
126
+ /** Update an existing test case. */
127
+ updateTestCase(testCaseId: string, body: {
128
+ name?: string;
129
+ description?: string;
130
+ priority?: string;
131
+ steps?: Array<Record<string, unknown>>;
132
+ assertions?: Array<Record<string, unknown>>;
133
+ tags?: string[];
134
+ }): Promise<{
135
+ id: string;
136
+ name: string;
137
+ }>;
138
+ /** Get a suite with its test cases. */
139
+ getSuiteWithCases(projectId: string, suiteId: string): Promise<{
140
+ id: string;
141
+ name: string;
142
+ description?: string;
143
+ test_cases: Array<{
144
+ id: string;
145
+ name: string;
146
+ steps: Array<Record<string, unknown>>;
147
+ assertions: Array<Record<string, unknown>>;
148
+ }>;
149
+ }>;
62
150
  startExploration(body: {
63
151
  url: string;
64
152
  max_pages?: number;
65
153
  focus?: string;
154
+ project_id?: string;
66
155
  }): Promise<Record<string, unknown>>;
67
- startConversation(body: {
68
- description: string;
69
- url?: string;
70
- }): Promise<Record<string, unknown>>;
71
- answerConversation(sessionId: string, answers: string): Promise<Record<string, unknown>>;
72
- approveConversation(sessionId: string, exclude?: number[]): Promise<Record<string, unknown>>;
73
156
  /** Start a test run — returns execution_id + test cases to execute locally. */
74
157
  startRun(body: {
75
158
  suite_id: string;
package/dist/cloud.js CHANGED
@@ -8,6 +8,23 @@ export class CloudClient {
8
8
  this.apiKey = options.apiKey;
9
9
  this.baseUrl = (options.baseUrl ?? "https://api.qa-agent.dev").replace(/\/$/, "");
10
10
  }
11
+ /**
12
+ * Create a new organization. This is unauthenticated — no API key needed.
13
+ * Used by the `setup` tool for first-time onboarding.
14
+ */
15
+ static async createOrg(baseUrl, body) {
16
+ const url = `${baseUrl.replace(/\/$/, "")}/api/v1/orgs`;
17
+ const resp = await fetch(url, {
18
+ method: "POST",
19
+ headers: { "Content-Type": "application/json" },
20
+ body: JSON.stringify(body),
21
+ });
22
+ if (!resp.ok) {
23
+ const text = await resp.text();
24
+ throw new Error(`Create org failed (${resp.status}): ${text}`);
25
+ }
26
+ return (await resp.json());
27
+ }
11
28
  async request(method, path, body) {
12
29
  const url = `${this.baseUrl}/api/v1${path}`;
13
30
  const headers = {
@@ -45,20 +62,54 @@ export class CloudClient {
45
62
  async listProjects() {
46
63
  return this.get("/qa/projects/");
47
64
  }
65
+ /** Get-or-create a project by name. Returns project with id. */
66
+ async resolveProject(name, baseUrl) {
67
+ const body = { name };
68
+ if (baseUrl)
69
+ body.base_url = baseUrl;
70
+ return this.post("/qa/projects/resolve", body);
71
+ }
72
+ // --- Suites ---
73
+ /** List all test suites across all projects for the org. */
74
+ async listSuites(search) {
75
+ const qs = search ? `?search=${encodeURIComponent(search)}` : "";
76
+ return this.get(`/qa/projects/suites/all${qs}`);
77
+ }
78
+ /** Resolve a suite by name (case-insensitive). Returns the best match. */
79
+ async resolveSuite(name, projectId) {
80
+ const body = { name };
81
+ if (projectId)
82
+ body.project_id = projectId;
83
+ return this.post("/qa/projects/suites/resolve", body);
84
+ }
85
+ // --- Suite + Test Case Management ---
86
+ /** Create a test suite under a project. */
87
+ async createSuite(projectId, body) {
88
+ return this.post(`/qa/projects/${projectId}/test-suites`, {
89
+ ...body,
90
+ project_id: projectId,
91
+ });
92
+ }
93
+ /** Update an existing test suite. */
94
+ async updateSuite(projectId, suiteId, body) {
95
+ return this.request("PUT", `/qa/projects/${projectId}/test-suites/${suiteId}`, body);
96
+ }
97
+ /** Create a test case and link it to suite(s). */
98
+ async createTestCase(body) {
99
+ return this.post("/qa/test-cases/", body);
100
+ }
101
+ /** Update an existing test case. */
102
+ async updateTestCase(testCaseId, body) {
103
+ return this.request("PUT", `/qa/test-cases/${testCaseId}`, body);
104
+ }
105
+ /** Get a suite with its test cases. */
106
+ async getSuiteWithCases(projectId, suiteId) {
107
+ return this.get(`/qa/projects/${projectId}/suites/${suiteId}/test-cases`);
108
+ }
48
109
  // --- Exploration ---
49
110
  async startExploration(body) {
50
111
  return this.post("/explore/", body);
51
112
  }
52
- // --- Conversation (Phase 4) ---
53
- async startConversation(body) {
54
- return this.post("/conversation/start", body);
55
- }
56
- async answerConversation(sessionId, answers) {
57
- return this.post(`/conversation/${sessionId}/answer`, { answers });
58
- }
59
- async approveConversation(sessionId, exclude) {
60
- return this.post(`/conversation/${sessionId}/approve`, { exclude });
61
- }
62
113
  // --- Execution (Phase 3) ---
63
114
  /** Start a test run — returns execution_id + test cases to execute locally. */
64
115
  async startRun(body) {
package/dist/cloud.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Global config manager — reads/writes ~/.qa-agent/config.json
3
+ * Stores API key and base URL so the MCP server can boot without CLI args.
4
+ */
5
+ declare const CONFIG_PATH: string;
6
+ export interface GlobalConfig {
7
+ api_key?: string;
8
+ base_url?: string;
9
+ }
10
+ /**
11
+ * Load config from ~/.qa-agent/config.json.
12
+ * Returns empty object if file doesn't exist or is malformed.
13
+ */
14
+ export declare function loadGlobalConfig(): GlobalConfig;
15
+ /**
16
+ * Merge new values into ~/.qa-agent/config.json and write with 0o600 permissions.
17
+ * Creates ~/.qa-agent/ directory if it doesn't exist.
18
+ */
19
+ export declare function saveGlobalConfig(updates: Partial<GlobalConfig>): void;
20
+ export { CONFIG_PATH };
package/dist/config.js ADDED
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Global config manager — reads/writes ~/.qa-agent/config.json
3
+ * Stores API key and base URL so the MCP server can boot without CLI args.
4
+ */
5
+ import * as fs from "node:fs";
6
+ import * as path from "node:path";
7
+ import * as os from "node:os";
8
+ const CONFIG_DIR = path.join(os.homedir(), ".qa-agent");
9
+ const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
10
+ /**
11
+ * Load config from ~/.qa-agent/config.json.
12
+ * Returns empty object if file doesn't exist or is malformed.
13
+ */
14
+ export function loadGlobalConfig() {
15
+ if (!fs.existsSync(CONFIG_PATH))
16
+ return {};
17
+ try {
18
+ return JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ /**
25
+ * Merge new values into ~/.qa-agent/config.json and write with 0o600 permissions.
26
+ * Creates ~/.qa-agent/ directory if it doesn't exist.
27
+ */
28
+ export function saveGlobalConfig(updates) {
29
+ const existing = loadGlobalConfig();
30
+ const merged = { ...existing, ...updates };
31
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
32
+ const content = JSON.stringify(merged, null, 2) + "\n";
33
+ fs.writeFileSync(CONFIG_PATH, content, { mode: 0o600 });
34
+ }
35
+ export { CONFIG_PATH };
36
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AACxD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAOzD;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC;QAAE,OAAO,EAAE,CAAC;IAC3C,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAiB,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,OAA8B;IAC7D,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC;IAC3C,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IACvD,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AAC1D,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
package/dist/healer.d.ts CHANGED
@@ -24,4 +24,4 @@ export interface HealResult {
24
24
  * Attempt to heal a broken selector by trying 5 strategies in order.
25
25
  * Returns the first working selector with its confidence score.
26
26
  */
27
- export declare function healSelector(page: Page, cloud: CloudClient, originalSelector: string, failureType: string, errorMessage: string, pageUrl: string): Promise<HealResult>;
27
+ export declare function healSelector(page: Page, cloud: CloudClient | null, originalSelector: string, failureType: string, errorMessage: string, pageUrl: string): Promise<HealResult>;
package/dist/healer.js CHANGED
@@ -11,7 +11,6 @@
11
11
  * After a successful heal the pattern is stored in the Cloud DB so
12
12
  * future failures with the same signature are fixed instantly.
13
13
  */
14
- import * as actions from "./actions.js";
15
14
  // ---------------------------------------------------------------------------
16
15
  // Strategy confidence map
17
16
  // ---------------------------------------------------------------------------
@@ -30,34 +29,36 @@ const CONFIDENCE = {
30
29
  * Returns the first working selector with its confidence score.
31
30
  */
32
31
  export async function healSelector(page, cloud, originalSelector, failureType, errorMessage, pageUrl) {
33
- // 1. Check Cloud for a stored pattern first
34
- try {
35
- const classification = await cloud.post("/qa/healing/classify", {
36
- failure_type: failureType,
37
- selector: originalSelector,
38
- page_url: pageUrl,
39
- error_message: errorMessage,
40
- });
41
- if (classification.is_real_bug) {
42
- return { healed: false, error: classification.reason ?? "Classified as real bug" };
43
- }
44
- if (classification.pattern) {
45
- // Validate the stored pattern still works
46
- const found = await testSelector(page, classification.pattern.healed_value);
47
- if (found) {
48
- return {
49
- healed: true,
50
- newSelector: classification.pattern.healed_value,
51
- strategy: classification.pattern.strategy,
52
- confidence: classification.pattern.confidence,
53
- };
32
+ // 1. Check Cloud for a stored pattern first (only if cloud is available)
33
+ if (cloud) {
34
+ try {
35
+ const classification = await cloud.post("/qa/healing/classify", {
36
+ failure_type: failureType,
37
+ selector: originalSelector,
38
+ page_url: pageUrl,
39
+ error_message: errorMessage,
40
+ });
41
+ if (classification.is_real_bug) {
42
+ return { healed: false, error: classification.reason ?? "Classified as real bug" };
43
+ }
44
+ if (classification.pattern) {
45
+ // Validate the stored pattern still works
46
+ const found = await testSelector(page, classification.pattern.healed_value);
47
+ if (found) {
48
+ return {
49
+ healed: true,
50
+ newSelector: classification.pattern.healed_value,
51
+ strategy: classification.pattern.strategy,
52
+ confidence: classification.pattern.confidence,
53
+ };
54
+ }
54
55
  }
55
56
  }
57
+ catch {
58
+ // Non-fatal — continue with local strategies
59
+ }
56
60
  }
57
- catch {
58
- // Non-fatal — continue with local strategies
59
- }
60
- // 2. Try local repair strategies in order
61
+ // 2. Try local repair strategies in order (no cloud needed)
61
62
  const strategies = [
62
63
  { name: "data_testid", fn: () => tryDataTestId(page, originalSelector) },
63
64
  { name: "aria", fn: () => tryAria(page, originalSelector) },
@@ -67,8 +68,10 @@ export async function healSelector(page, cloud, originalSelector, failureType, e
67
68
  for (const strategy of strategies) {
68
69
  const candidate = await strategy.fn();
69
70
  if (candidate) {
70
- // Report the pattern to Cloud for future reuse
71
- await storePatternQuietly(cloud, failureType, originalSelector, candidate, strategy.name, CONFIDENCE[strategy.name] ?? 0.8, pageUrl);
71
+ // Report the pattern to Cloud for future reuse (if connected)
72
+ if (cloud) {
73
+ await storePatternQuietly(cloud, failureType, originalSelector, candidate, strategy.name, CONFIDENCE[strategy.name] ?? 0.8, pageUrl);
74
+ }
72
75
  return {
73
76
  healed: true,
74
77
  newSelector: candidate,
@@ -77,32 +80,9 @@ export async function healSelector(page, cloud, originalSelector, failureType, e
77
80
  };
78
81
  }
79
82
  }
80
- // 3. AI-generatedask the Cloud LLM
81
- try {
82
- const snapshot = await actions.getSnapshot(page);
83
- const suggestions = await cloud.post("/qa/healing/ai-suggest", {
84
- original_selector: originalSelector,
85
- page_snapshot: JSON.stringify(snapshot),
86
- error_message: errorMessage,
87
- page_url: pageUrl,
88
- });
89
- for (const suggestion of suggestions.suggestions ?? []) {
90
- const found = await testSelector(page, suggestion.selector);
91
- if (found) {
92
- await storePatternQuietly(cloud, failureType, originalSelector, suggestion.selector, "ai", suggestion.confidence, pageUrl);
93
- return {
94
- healed: true,
95
- newSelector: suggestion.selector,
96
- strategy: "ai",
97
- confidence: suggestion.confidence,
98
- };
99
- }
100
- }
101
- }
102
- catch {
103
- // Non-fatal
104
- }
105
- return { healed: false, error: "All healing strategies exhausted" };
83
+ // 3. No AI strategy here — the caller (index.ts) will fall back to
84
+ // the host AI with a page snapshot + prompt if local strategies fail.
85
+ return { healed: false, error: "Local healing strategies exhausted" };
106
86
  }
107
87
  // ---------------------------------------------------------------------------
108
88
  // Strategy implementations