@fasttest-ai/qa-agent 0.2.0 → 0.4.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/bin/install.js +3 -0
- package/bin/qa-agent.js +7 -2
- package/dist/actions.d.ts +3 -0
- package/dist/actions.js +38 -4
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +30 -0
- package/dist/browser.js +120 -6
- package/dist/browser.js.map +1 -1
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +5 -5
- package/dist/cli.js.map +1 -1
- package/dist/cloud.d.ts +96 -38
- package/dist/cloud.js +96 -35
- package/dist/cloud.js.map +1 -1
- package/dist/config.d.ts +5 -4
- package/dist/config.js +20 -7
- package/dist/config.js.map +1 -1
- package/dist/healer.d.ts +5 -1
- package/dist/healer.js +106 -29
- package/dist/healer.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +697 -88
- package/dist/index.js.map +1 -1
- package/dist/install.d.ts +11 -0
- package/dist/install.js +225 -0
- package/dist/install.js.map +1 -0
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +245 -19
- package/dist/runner.js.map +1 -1
- package/dist/variables.d.ts +30 -0
- package/dist/variables.js +104 -0
- package/dist/variables.js.map +1 -0
- package/package.json +6 -3
package/dist/cloud.d.ts
CHANGED
|
@@ -5,19 +5,50 @@ export interface CloudClientOptions {
|
|
|
5
5
|
apiKey: string;
|
|
6
6
|
baseUrl?: string;
|
|
7
7
|
}
|
|
8
|
-
export interface
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
export interface DeviceCodeResponse {
|
|
9
|
+
code: string;
|
|
10
|
+
poll_token: string;
|
|
11
|
+
expires_in: number;
|
|
12
|
+
verification_url: string;
|
|
13
|
+
}
|
|
14
|
+
export interface DeviceCodeStatusResponse {
|
|
15
|
+
status: "pending" | "completed" | "expired";
|
|
16
|
+
api_key?: string;
|
|
17
|
+
org_name?: string;
|
|
18
|
+
org_slug?: string;
|
|
15
19
|
}
|
|
16
20
|
export interface RunResponse {
|
|
17
21
|
execution_id: string;
|
|
18
22
|
status: string;
|
|
19
23
|
base_url: string | null;
|
|
20
24
|
test_cases: TestCasePayload[];
|
|
25
|
+
previous_execution_id: string | null;
|
|
26
|
+
}
|
|
27
|
+
export interface RegressionItem {
|
|
28
|
+
test_case_id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
previous_status: string;
|
|
31
|
+
current_status: string;
|
|
32
|
+
error?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface ExecutionDiff {
|
|
35
|
+
previous_execution_id: string | null;
|
|
36
|
+
regressions: RegressionItem[];
|
|
37
|
+
fixes: RegressionItem[];
|
|
38
|
+
new_tests: Array<{
|
|
39
|
+
test_case_id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
status: string;
|
|
42
|
+
}>;
|
|
43
|
+
removed_tests: Array<{
|
|
44
|
+
test_case_id: string;
|
|
45
|
+
name: string;
|
|
46
|
+
}>;
|
|
47
|
+
unchanged: {
|
|
48
|
+
passed: number;
|
|
49
|
+
failed: number;
|
|
50
|
+
};
|
|
51
|
+
healed_count: number;
|
|
21
52
|
}
|
|
22
53
|
export interface TestCasePayload {
|
|
23
54
|
id: string;
|
|
@@ -26,6 +57,7 @@ export interface TestCasePayload {
|
|
|
26
57
|
assertions: TestAssertion[];
|
|
27
58
|
test_data: Record<string, unknown>[];
|
|
28
59
|
timeout_seconds: number;
|
|
60
|
+
retry_count: number;
|
|
29
61
|
}
|
|
30
62
|
export interface TestStep {
|
|
31
63
|
action: string;
|
|
@@ -39,6 +71,9 @@ export interface TestStep {
|
|
|
39
71
|
key?: string;
|
|
40
72
|
file_paths?: string[];
|
|
41
73
|
expression?: string;
|
|
74
|
+
target?: string;
|
|
75
|
+
width?: number;
|
|
76
|
+
height?: number;
|
|
42
77
|
type?: string;
|
|
43
78
|
text?: string;
|
|
44
79
|
count?: number;
|
|
@@ -60,20 +95,21 @@ export declare class CloudClient {
|
|
|
60
95
|
private baseUrl;
|
|
61
96
|
constructor(options: CloudClientOptions);
|
|
62
97
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
98
|
+
* Request a device code for browser-based authentication.
|
|
99
|
+
* Unauthenticated — no API key needed.
|
|
65
100
|
*/
|
|
66
|
-
static
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
101
|
+
static requestDeviceCode(baseUrl: string): Promise<DeviceCodeResponse>;
|
|
102
|
+
/**
|
|
103
|
+
* Poll for device code status. Returns when completed or expired.
|
|
104
|
+
* Unauthenticated — uses the poll_token for identity.
|
|
105
|
+
*/
|
|
106
|
+
static pollDeviceCode(baseUrl: string, pollToken: string): Promise<DeviceCodeStatusResponse>;
|
|
70
107
|
private request;
|
|
71
108
|
get<T>(path: string): Promise<T>;
|
|
72
109
|
post<T>(path: string, body?: unknown): Promise<T>;
|
|
73
110
|
health(): Promise<{
|
|
74
111
|
status: string;
|
|
75
112
|
}>;
|
|
76
|
-
getOrg(): Promise<Record<string, unknown>>;
|
|
77
113
|
listProjects(): Promise<Record<string, unknown>>;
|
|
78
114
|
/** Get-or-create a project by name. Returns project with id. */
|
|
79
115
|
resolveProject(name: string, baseUrl?: string): Promise<{
|
|
@@ -98,15 +134,6 @@ export declare class CloudClient {
|
|
|
98
134
|
id: string;
|
|
99
135
|
name: string;
|
|
100
136
|
}>;
|
|
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
137
|
/** Create a test case and link it to suite(s). */
|
|
111
138
|
createTestCase(body: {
|
|
112
139
|
name: string;
|
|
@@ -135,24 +162,12 @@ export declare class CloudClient {
|
|
|
135
162
|
id: string;
|
|
136
163
|
name: string;
|
|
137
164
|
}>;
|
|
138
|
-
/**
|
|
139
|
-
|
|
165
|
+
/** Resolve an environment by name within a suite's project. */
|
|
166
|
+
resolveEnvironment(suiteId: string, name: string): Promise<{
|
|
140
167
|
id: string;
|
|
141
168
|
name: string;
|
|
142
|
-
|
|
143
|
-
test_cases: Array<{
|
|
144
|
-
id: string;
|
|
145
|
-
name: string;
|
|
146
|
-
steps: Array<Record<string, unknown>>;
|
|
147
|
-
assertions: Array<Record<string, unknown>>;
|
|
148
|
-
}>;
|
|
169
|
+
base_url: string;
|
|
149
170
|
}>;
|
|
150
|
-
startExploration(body: {
|
|
151
|
-
url: string;
|
|
152
|
-
max_pages?: number;
|
|
153
|
-
focus?: string;
|
|
154
|
-
project_id?: string;
|
|
155
|
-
}): Promise<Record<string, unknown>>;
|
|
156
171
|
/** Start a test run — returns execution_id + test cases to execute locally. */
|
|
157
172
|
startRun(body: {
|
|
158
173
|
suite_id: string;
|
|
@@ -169,6 +184,15 @@ export declare class CloudClient {
|
|
|
169
184
|
screenshots?: string[];
|
|
170
185
|
console_logs?: string[];
|
|
171
186
|
step_results?: Record<string, unknown>[];
|
|
187
|
+
retry_attempt?: number;
|
|
188
|
+
network_summary?: Array<{
|
|
189
|
+
url: string;
|
|
190
|
+
method: string;
|
|
191
|
+
status: number;
|
|
192
|
+
duration: number;
|
|
193
|
+
mimeType: string;
|
|
194
|
+
responseSize: number;
|
|
195
|
+
}>;
|
|
172
196
|
}): Promise<{
|
|
173
197
|
status: string;
|
|
174
198
|
}>;
|
|
@@ -178,6 +202,14 @@ export declare class CloudClient {
|
|
|
178
202
|
cancelExecution(executionId: string): Promise<Record<string, unknown>>;
|
|
179
203
|
/** Get execution status. */
|
|
180
204
|
getExecutionStatus(executionId: string): Promise<Record<string, unknown>>;
|
|
205
|
+
/** Get regression diff comparing this execution against the previous run. */
|
|
206
|
+
getExecutionDiff(executionId: string): Promise<ExecutionDiff>;
|
|
207
|
+
/** Notify that a test case is about to start executing. Non-fatal. */
|
|
208
|
+
notifyTestStarted(executionId: string, testCaseId: string, testCaseName: string): Promise<void>;
|
|
209
|
+
/** Notify that selector healing is being attempted. Non-fatal. */
|
|
210
|
+
notifyHealingStarted(executionId: string, testCaseId: string, originalSelector: string): Promise<void>;
|
|
211
|
+
/** Lightweight check for control signals (pause/cancel) from the dashboard. */
|
|
212
|
+
checkControlStatus(executionId: string): Promise<string>;
|
|
181
213
|
/** Set the org's GitHub token. */
|
|
182
214
|
setGithubToken(token: string): Promise<Record<string, unknown>>;
|
|
183
215
|
/** Post test results as a PR comment. */
|
|
@@ -201,5 +233,31 @@ export declare class CloudClient {
|
|
|
201
233
|
strategy?: string;
|
|
202
234
|
confidence?: number;
|
|
203
235
|
}>;
|
|
236
|
+
flaky_retries?: Array<{
|
|
237
|
+
name?: string;
|
|
238
|
+
retry_attempts?: number;
|
|
239
|
+
}>;
|
|
240
|
+
regressions?: Array<{
|
|
241
|
+
name: string;
|
|
242
|
+
previous_status: string;
|
|
243
|
+
current_status: string;
|
|
244
|
+
error?: string;
|
|
245
|
+
}>;
|
|
246
|
+
fixes?: Array<{
|
|
247
|
+
name: string;
|
|
248
|
+
previous_status: string;
|
|
249
|
+
current_status: string;
|
|
250
|
+
}>;
|
|
251
|
+
}): Promise<Record<string, unknown>>;
|
|
252
|
+
/** Save a chaos report with adversarial findings. */
|
|
253
|
+
saveChaosReport(projectId: string | undefined, body: {
|
|
254
|
+
url: string;
|
|
255
|
+
findings: Array<{
|
|
256
|
+
severity: string;
|
|
257
|
+
category: string;
|
|
258
|
+
description: string;
|
|
259
|
+
reproduction_steps: string[];
|
|
260
|
+
console_errors?: string[];
|
|
261
|
+
}>;
|
|
204
262
|
}): Promise<Record<string, unknown>>;
|
|
205
263
|
}
|
package/dist/cloud.js
CHANGED
|
@@ -6,22 +6,31 @@ export class CloudClient {
|
|
|
6
6
|
baseUrl;
|
|
7
7
|
constructor(options) {
|
|
8
8
|
this.apiKey = options.apiKey;
|
|
9
|
-
this.baseUrl = (options.baseUrl ?? "https://api.
|
|
9
|
+
this.baseUrl = (options.baseUrl ?? "https://api.fasttest.ai").replace(/\/$/, "");
|
|
10
10
|
}
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Request a device code for browser-based authentication.
|
|
13
|
+
* Unauthenticated — no API key needed.
|
|
14
14
|
*/
|
|
15
|
-
static async
|
|
16
|
-
const url = `${baseUrl.replace(/\/$/, "")}/api/v1/
|
|
17
|
-
const resp = await fetch(url, {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}
|
|
15
|
+
static async requestDeviceCode(baseUrl) {
|
|
16
|
+
const url = `${baseUrl.replace(/\/$/, "")}/api/v1/auth/device-code`;
|
|
17
|
+
const resp = await fetch(url, { method: "POST" });
|
|
18
|
+
if (!resp.ok) {
|
|
19
|
+
const text = await resp.text();
|
|
20
|
+
throw new Error(`Device code request failed (${resp.status}): ${text}`);
|
|
21
|
+
}
|
|
22
|
+
return (await resp.json());
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Poll for device code status. Returns when completed or expired.
|
|
26
|
+
* Unauthenticated — uses the poll_token for identity.
|
|
27
|
+
*/
|
|
28
|
+
static async pollDeviceCode(baseUrl, pollToken) {
|
|
29
|
+
const url = `${baseUrl.replace(/\/$/, "")}/api/v1/auth/device-code/status?poll_token=${encodeURIComponent(pollToken)}`;
|
|
30
|
+
const resp = await fetch(url);
|
|
22
31
|
if (!resp.ok) {
|
|
23
32
|
const text = await resp.text();
|
|
24
|
-
throw new Error(`
|
|
33
|
+
throw new Error(`Device code poll failed (${resp.status}): ${text}`);
|
|
25
34
|
}
|
|
26
35
|
return (await resp.json());
|
|
27
36
|
}
|
|
@@ -31,16 +40,40 @@ export class CloudClient {
|
|
|
31
40
|
"x-api-key": this.apiKey,
|
|
32
41
|
"Content-Type": "application/json",
|
|
33
42
|
};
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
const maxRetries = 2;
|
|
44
|
+
const baseDelay = 1000;
|
|
45
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
const timeoutId = setTimeout(() => controller.abort(), 30_000);
|
|
48
|
+
try {
|
|
49
|
+
const init = { method, headers, signal: controller.signal };
|
|
50
|
+
if (body !== undefined) {
|
|
51
|
+
init.body = JSON.stringify(body);
|
|
52
|
+
}
|
|
53
|
+
const resp = await fetch(url, init);
|
|
54
|
+
clearTimeout(timeoutId);
|
|
55
|
+
if (!resp.ok) {
|
|
56
|
+
const text = await resp.text();
|
|
57
|
+
if (resp.status >= 500 && attempt < maxRetries) {
|
|
58
|
+
await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
throw new Error(`Cloud API ${method} ${path} → ${resp.status}: ${text}`);
|
|
62
|
+
}
|
|
63
|
+
return (await resp.json());
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
clearTimeout(timeoutId);
|
|
67
|
+
const isRetryable = err instanceof Error &&
|
|
68
|
+
(err.name === "AbortError" || err.message.includes("fetch failed"));
|
|
69
|
+
if (isRetryable && attempt < maxRetries) {
|
|
70
|
+
await new Promise((r) => setTimeout(r, baseDelay * 2 ** attempt));
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
throw err;
|
|
74
|
+
}
|
|
42
75
|
}
|
|
43
|
-
|
|
76
|
+
throw new Error(`Cloud API ${method} ${path}: max retries exceeded`);
|
|
44
77
|
}
|
|
45
78
|
async get(path) {
|
|
46
79
|
return this.request("GET", path);
|
|
@@ -54,10 +87,6 @@ export class CloudClient {
|
|
|
54
87
|
const resp = await fetch(url);
|
|
55
88
|
return (await resp.json());
|
|
56
89
|
}
|
|
57
|
-
// --- Organization ---
|
|
58
|
-
async getOrg() {
|
|
59
|
-
return this.get("/orgs/me");
|
|
60
|
-
}
|
|
61
90
|
// --- Projects ---
|
|
62
91
|
async listProjects() {
|
|
63
92
|
return this.get("/qa/projects/");
|
|
@@ -90,10 +119,6 @@ export class CloudClient {
|
|
|
90
119
|
project_id: projectId,
|
|
91
120
|
});
|
|
92
121
|
}
|
|
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
122
|
/** Create a test case and link it to suite(s). */
|
|
98
123
|
async createTestCase(body) {
|
|
99
124
|
return this.post("/qa/test-cases/", body);
|
|
@@ -102,13 +127,10 @@ export class CloudClient {
|
|
|
102
127
|
async updateTestCase(testCaseId, body) {
|
|
103
128
|
return this.request("PUT", `/qa/test-cases/${testCaseId}`, body);
|
|
104
129
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
// --- Exploration ---
|
|
110
|
-
async startExploration(body) {
|
|
111
|
-
return this.post("/explore/", body);
|
|
130
|
+
// --- Environments ---
|
|
131
|
+
/** Resolve an environment by name within a suite's project. */
|
|
132
|
+
async resolveEnvironment(suiteId, name) {
|
|
133
|
+
return this.post("/qa/environments/resolve", { suite_id: suiteId, name });
|
|
112
134
|
}
|
|
113
135
|
// --- Execution (Phase 3) ---
|
|
114
136
|
/** Start a test run — returns execution_id + test cases to execute locally. */
|
|
@@ -131,6 +153,39 @@ export class CloudClient {
|
|
|
131
153
|
async getExecutionStatus(executionId) {
|
|
132
154
|
return this.get(`/qa/execution/executions/${executionId}`);
|
|
133
155
|
}
|
|
156
|
+
/** Get regression diff comparing this execution against the previous run. */
|
|
157
|
+
async getExecutionDiff(executionId) {
|
|
158
|
+
return this.get(`/qa/execution/executions/${executionId}/diff`);
|
|
159
|
+
}
|
|
160
|
+
/** Notify that a test case is about to start executing. Non-fatal. */
|
|
161
|
+
async notifyTestStarted(executionId, testCaseId, testCaseName) {
|
|
162
|
+
try {
|
|
163
|
+
await this.post(`/qa/execution/executions/${executionId}/test-started`, {
|
|
164
|
+
test_case_id: testCaseId,
|
|
165
|
+
test_case_name: testCaseName,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// Non-fatal — War Room will just miss the "running" indicator
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/** Notify that selector healing is being attempted. Non-fatal. */
|
|
173
|
+
async notifyHealingStarted(executionId, testCaseId, originalSelector) {
|
|
174
|
+
try {
|
|
175
|
+
await this.post(`/qa/execution/executions/${executionId}/healing-started`, {
|
|
176
|
+
test_case_id: testCaseId,
|
|
177
|
+
original_selector: originalSelector,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Non-fatal — War Room will just miss the "healing..." indicator
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
/** Lightweight check for control signals (pause/cancel) from the dashboard. */
|
|
185
|
+
async checkControlStatus(executionId) {
|
|
186
|
+
const resp = await this.get(`/qa/execution/executions/${executionId}/control-status`);
|
|
187
|
+
return resp.status;
|
|
188
|
+
}
|
|
134
189
|
// --- GitHub (Phase 6) ---
|
|
135
190
|
/** Set the org's GitHub token. */
|
|
136
191
|
async setGithubToken(token) {
|
|
@@ -140,5 +195,11 @@ export class CloudClient {
|
|
|
140
195
|
async postPrComment(body) {
|
|
141
196
|
return this.post("/qa/github/pr-comment", body);
|
|
142
197
|
}
|
|
198
|
+
// --- Chaos (Break My App) ---
|
|
199
|
+
/** Save a chaos report with adversarial findings. */
|
|
200
|
+
async saveChaosReport(projectId, body) {
|
|
201
|
+
const qs = projectId ? `?project_id=${projectId}` : "";
|
|
202
|
+
return this.post(`/qa/chaos/reports${qs}`, body);
|
|
203
|
+
}
|
|
143
204
|
}
|
|
144
205
|
//# sourceMappingURL=cloud.js.map
|
package/dist/cloud.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"cloud.js","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Global config manager — reads/writes ~/.
|
|
2
|
+
* Global config manager — reads/writes ~/.fasttest/config.json
|
|
3
3
|
* Stores API key and base URL so the MCP server can boot without CLI args.
|
|
4
|
+
* Falls back to legacy ~/.qa-agent/config.json for backward compatibility.
|
|
4
5
|
*/
|
|
5
6
|
declare const CONFIG_PATH: string;
|
|
6
7
|
export interface GlobalConfig {
|
|
@@ -8,13 +9,13 @@ export interface GlobalConfig {
|
|
|
8
9
|
base_url?: string;
|
|
9
10
|
}
|
|
10
11
|
/**
|
|
11
|
-
* Load config from ~/.qa-agent/config.json.
|
|
12
|
+
* Load config from ~/.fasttest/config.json (or legacy ~/.qa-agent/config.json).
|
|
12
13
|
* Returns empty object if file doesn't exist or is malformed.
|
|
13
14
|
*/
|
|
14
15
|
export declare function loadGlobalConfig(): GlobalConfig;
|
|
15
16
|
/**
|
|
16
|
-
* Merge new values into ~/.
|
|
17
|
-
*
|
|
17
|
+
* Merge new values into ~/.fasttest/config.json and write with 0o600 permissions.
|
|
18
|
+
* Always writes to the new location (~/.fasttest/).
|
|
18
19
|
*/
|
|
19
20
|
export declare function saveGlobalConfig(updates: Partial<GlobalConfig>): void;
|
|
20
21
|
export { CONFIG_PATH };
|
package/dist/config.js
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Global config manager — reads/writes ~/.
|
|
2
|
+
* Global config manager — reads/writes ~/.fasttest/config.json
|
|
3
3
|
* Stores API key and base URL so the MCP server can boot without CLI args.
|
|
4
|
+
* Falls back to legacy ~/.qa-agent/config.json for backward compatibility.
|
|
4
5
|
*/
|
|
5
6
|
import * as fs from "node:fs";
|
|
6
7
|
import * as path from "node:path";
|
|
7
8
|
import * as os from "node:os";
|
|
8
|
-
const
|
|
9
|
+
const NEW_CONFIG_DIR = path.join(os.homedir(), ".fasttest");
|
|
10
|
+
const OLD_CONFIG_DIR = path.join(os.homedir(), ".qa-agent");
|
|
11
|
+
/** Resolve config dir: prefer ~/.fasttest/, fall back to ~/.qa-agent/ */
|
|
12
|
+
function resolveConfigDir() {
|
|
13
|
+
if (fs.existsSync(path.join(NEW_CONFIG_DIR, "config.json")))
|
|
14
|
+
return NEW_CONFIG_DIR;
|
|
15
|
+
if (fs.existsSync(path.join(OLD_CONFIG_DIR, "config.json")))
|
|
16
|
+
return OLD_CONFIG_DIR;
|
|
17
|
+
return NEW_CONFIG_DIR;
|
|
18
|
+
}
|
|
19
|
+
const CONFIG_DIR = resolveConfigDir();
|
|
9
20
|
const CONFIG_PATH = path.join(CONFIG_DIR, "config.json");
|
|
10
21
|
/**
|
|
11
|
-
* Load config from ~/.qa-agent/config.json.
|
|
22
|
+
* Load config from ~/.fasttest/config.json (or legacy ~/.qa-agent/config.json).
|
|
12
23
|
* Returns empty object if file doesn't exist or is malformed.
|
|
13
24
|
*/
|
|
14
25
|
export function loadGlobalConfig() {
|
|
@@ -22,15 +33,17 @@ export function loadGlobalConfig() {
|
|
|
22
33
|
}
|
|
23
34
|
}
|
|
24
35
|
/**
|
|
25
|
-
* Merge new values into ~/.
|
|
26
|
-
*
|
|
36
|
+
* Merge new values into ~/.fasttest/config.json and write with 0o600 permissions.
|
|
37
|
+
* Always writes to the new location (~/.fasttest/).
|
|
27
38
|
*/
|
|
28
39
|
export function saveGlobalConfig(updates) {
|
|
29
40
|
const existing = loadGlobalConfig();
|
|
30
41
|
const merged = { ...existing, ...updates };
|
|
31
|
-
|
|
42
|
+
const saveDir = NEW_CONFIG_DIR;
|
|
43
|
+
const savePath = path.join(saveDir, "config.json");
|
|
44
|
+
fs.mkdirSync(saveDir, { recursive: true });
|
|
32
45
|
const content = JSON.stringify(merged, null, 2) + "\n";
|
|
33
|
-
fs.writeFileSync(
|
|
46
|
+
fs.writeFileSync(savePath, content, { mode: 0o600 });
|
|
34
47
|
}
|
|
35
48
|
export { CONFIG_PATH };
|
|
36
49
|
//# sourceMappingURL=config.js.map
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AAC5D,MAAM,cAAc,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,WAAW,CAAC,CAAC;AAE5D,yEAAyE;AACzE,SAAS,gBAAgB;IACvB,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;QAAE,OAAO,cAAc,CAAC;IACnF,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,aAAa,CAAC,CAAC;QAAE,OAAO,cAAc,CAAC;IACnF,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,GAAG,gBAAgB,EAAE,CAAC;AACtC,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,MAAM,OAAO,GAAG,cAAc,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACnD,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IACvD,EAAE,CAAC,aAAa,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACvD,CAAC;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}
|
package/dist/healer.d.ts
CHANGED
|
@@ -20,8 +20,12 @@ export interface HealResult {
|
|
|
20
20
|
confidence?: number;
|
|
21
21
|
error?: string;
|
|
22
22
|
}
|
|
23
|
+
export interface ElementContext {
|
|
24
|
+
action: string;
|
|
25
|
+
description?: string;
|
|
26
|
+
}
|
|
23
27
|
/**
|
|
24
28
|
* Attempt to heal a broken selector by trying 5 strategies in order.
|
|
25
29
|
* Returns the first working selector with its confidence score.
|
|
26
30
|
*/
|
|
27
|
-
export declare function healSelector(page: Page, cloud: CloudClient | null, originalSelector: string, failureType: string, errorMessage: string, pageUrl: string): Promise<HealResult>;
|
|
31
|
+
export declare function healSelector(page: Page, cloud: CloudClient | null, originalSelector: string, failureType: string, errorMessage: string, pageUrl: string, context?: ElementContext): Promise<HealResult>;
|