assuremind 1.2.3 → 1.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/index.js CHANGED
@@ -1,1918 +1,62 @@
1
- "use strict";
2
- var __create = Object.create;
3
- var __defProp = Object.defineProperty;
4
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __getProtoOf = Object.getPrototypeOf;
7
- var __hasOwnProp = Object.prototype.hasOwnProperty;
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
- // If the importer is in node compatibility mode or this is not an ESM
22
- // file that has been converted to a CommonJS file using a Babel-
23
- // compatible transform (i.e. "__esModule" has not been set), then set
24
- // "default" to the CommonJS "module.exports" for node compatibility.
25
- isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
- mod
27
- ));
28
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
-
30
- // src/index.ts
31
- var src_exports = {};
32
- __export(src_exports, {
33
- AssuremindError: () => AssuremindError,
34
- ConfigError: () => ConfigError,
35
- ExecutionError: () => ExecutionError,
36
- HealingError: () => HealingError,
37
- ProviderError: () => ProviderError,
38
- StorageError: () => StorageError,
39
- ValidationError: () => ValidationError,
40
- acceptHealingEvent: () => acceptHealingEvent,
41
- appendPendingEvent: () => appendPendingEvent,
42
- callFaker: () => callFaker,
43
- clearEnvCache: () => clearEnvCache,
44
- configExists: () => configExists,
45
- createCase: () => createCase,
46
- createChildLogger: () => createChildLogger,
47
- createSuite: () => createSuite,
48
- defineConfig: () => defineConfig,
49
- deleteCase: () => deleteCase,
50
- deleteGlobalVariable: () => deleteGlobalVariable,
51
- deleteResult: () => deleteResult,
52
- deleteSuite: () => deleteSuite,
53
- formatError: () => formatError,
54
- generateCacheKey: () => generateCacheKey,
55
- getAvailableGenerators: () => getAvailableGenerators,
56
- getCasePath: () => getCasePath,
57
- getHealingStats: () => getHealingStats,
58
- isAssuremindError: () => isAssuremindError,
59
- listCasePaths: () => listCasePaths,
60
- listCases: () => listCases,
61
- listHealingReportIds: () => listHealingReportIds,
62
- listResultIds: () => listResultIds,
63
- listResults: () => listResults,
64
- listSuiteDirs: () => listSuiteDirs,
65
- listSuites: () => listSuites,
66
- listSuitesWithCounts: () => listSuitesWithCounts,
67
- listVariableFiles: () => listVariableFiles,
68
- logger: () => logger,
69
- normalizeUrl: () => normalizeUrl,
70
- pruneResolvedEvents: () => pruneResolvedEvents,
71
- readCase: () => readCase,
72
- readConfig: () => readConfig,
73
- readEnvVariables: () => readEnvVariables,
74
- readGlobalVariables: () => readGlobalVariables,
75
- readHealingReport: () => readHealingReport,
76
- readPendingEvents: () => readPendingEvents,
77
- readResult: () => readResult,
78
- readSuite: () => readSuite,
79
- readVariables: () => readVariables,
80
- redactSecrets: () => redactSecrets,
81
- rejectHealingEvent: () => rejectHealingEvent,
82
- resolveVariables: () => resolveVariables,
83
- sanitizeGeneratedCode: () => sanitizeGeneratedCode,
84
- screenshotsDir: () => screenshotsDir,
85
- setGlobalVariable: () => setGlobalVariable,
86
- sha256: () => sha256,
87
- toSlug: () => toSlug,
88
- tracesDir: () => tracesDir,
89
- tryGetEnv: () => tryGetEnv,
90
- updateCase: () => updateCase,
91
- updateConfig: () => updateConfig,
92
- updateSuite: () => updateSuite,
93
- validateConfig: () => validateConfig,
94
- validateEnv: () => validateEnv,
95
- videosDir: () => videosDir,
96
- writeCase: () => writeCase,
97
- writeConfig: () => writeConfig,
98
- writeHealingReport: () => writeHealingReport,
99
- writeResult: () => writeResult,
100
- writeSuite: () => writeSuite,
101
- writeVariables: () => writeVariables
102
- });
103
- module.exports = __toCommonJS(src_exports);
104
-
105
- // src/types/config.ts
106
- var import_zod = require("zod");
107
- var ScreenshotModeSchema = import_zod.z.enum(["off", "on", "only-on-failure"]);
108
- var VideoModeSchema = import_zod.z.enum(["off", "on", "on-first-retry", "retain-on-failure"]);
109
- var TraceModeSchema = import_zod.z.enum(["off", "on", "on-first-retry", "retain-on-failure"]);
110
- var BrowserNameSchema = import_zod.z.enum(["chromium", "firefox", "webkit"]);
111
- var PageLoadStrategySchema = import_zod.z.enum(["commit", "domcontentloaded", "load", "networkidle"]);
112
- var EnvironmentSchema = import_zod.z.enum(["dev", "stage", "test", "prod"]);
113
- var EnvironmentUrlsSchema = import_zod.z.object({
114
- dev: import_zod.z.string().url().or(import_zod.z.literal("")).default(""),
115
- stage: import_zod.z.string().url().or(import_zod.z.literal("")).default(""),
116
- test: import_zod.z.string().url().or(import_zod.z.literal("")).default(""),
117
- prod: import_zod.z.string().url().or(import_zod.z.literal("")).default("")
118
- });
119
- var HealingConfigSchema = import_zod.z.object({
120
- enabled: import_zod.z.boolean(),
121
- maxLevel: import_zod.z.number().int().min(1).max(5),
122
- dailyBudget: import_zod.z.number().positive(),
123
- autoPR: import_zod.z.boolean()
124
- });
125
- var ReportingConfigSchema = import_zod.z.object({
126
- allure: import_zod.z.boolean(),
127
- html: import_zod.z.boolean(),
128
- json: import_zod.z.boolean()
129
- });
130
- var ViewportSchema = import_zod.z.object({
131
- width: import_zod.z.number().int().positive(),
132
- height: import_zod.z.number().int().positive()
133
- });
134
- var EnvironmentProfileSchema = import_zod.z.object({
135
- name: import_zod.z.string().min(1),
136
- environment: EnvironmentSchema,
137
- baseUrl: import_zod.z.string().url(),
138
- browsers: import_zod.z.array(BrowserNameSchema).min(1),
139
- headless: import_zod.z.boolean().optional()
140
- });
141
- var AutotestConfigSchema = import_zod.z.object({
142
- baseUrl: import_zod.z.string().url(),
143
- environment: EnvironmentSchema.default("stage"),
144
- environmentUrls: EnvironmentUrlsSchema.default({
145
- dev: "",
146
- stage: "",
147
- test: "",
148
- prod: ""
149
- }),
150
- browsers: import_zod.z.array(BrowserNameSchema).min(1),
151
- headless: import_zod.z.boolean(),
152
- viewport: ViewportSchema.default({ width: 1280, height: 720 }),
153
- timeout: import_zod.z.number().int().positive(),
154
- retries: import_zod.z.number().int().min(0),
155
- parallel: import_zod.z.number().int().positive(),
156
- pageLoad: PageLoadStrategySchema.default("domcontentloaded"),
157
- screenshot: ScreenshotModeSchema,
158
- video: VideoModeSchema,
159
- trace: TraceModeSchema,
160
- healing: HealingConfigSchema,
161
- reporting: ReportingConfigSchema,
162
- studioPort: import_zod.z.number().int().min(1024).max(65535),
163
- profiles: import_zod.z.array(EnvironmentProfileSchema).default([]),
164
- activeProfile: import_zod.z.string().optional(),
165
- /** Playwright device descriptor name for emulation (e.g. 'iPhone 15 Pro'). */
166
- device: import_zod.z.string().optional(),
167
- /**
168
- * RAG (Retrieval-Augmented Generation) — semantic memory from past runs.
169
- *
170
- * The AI learns from every generation and healing event:
171
- * - Code Corpus: retrieve similar past steps during generation
172
- * - Healing Corpus: retrieve similar past fixes during self-healing
173
- * - Error Catalog: track recurring error patterns for preventive generation
174
- */
175
- rag: import_zod.z.object({
176
- /** Master switch for RAG memory. */
177
- enabled: import_zod.z.boolean(),
178
- /** Code Corpus — semantic retrieval of similar instruction→code mappings. */
179
- codeCorpus: import_zod.z.object({
180
- enabled: import_zod.z.boolean(),
181
- maxEntries: import_zod.z.number().int().positive(),
182
- similarityThreshold: import_zod.z.number().min(0).max(1),
183
- directUseThreshold: import_zod.z.number().min(0).max(1)
184
- }),
185
- /** Healing Corpus — retrieve similar past healing events during self-healing. */
186
- healingCorpus: import_zod.z.object({
187
- enabled: import_zod.z.boolean(),
188
- maxEntries: import_zod.z.number().int().positive(),
189
- similarityThreshold: import_zod.z.number().min(0).max(1)
190
- }),
191
- /** Error Catalog — aggregate recurring error patterns per URL. */
192
- errorCatalog: import_zod.z.object({
193
- enabled: import_zod.z.boolean(),
194
- maxEntries: import_zod.z.number().int().positive()
195
- }),
196
- /** Embedding strategy: 'tfidf' (local, free, offline). */
197
- embedder: import_zod.z.literal("tfidf")
198
- }).default({
199
- enabled: true,
200
- codeCorpus: { enabled: true, maxEntries: 500, similarityThreshold: 0.65, directUseThreshold: 0.9 },
201
- healingCorpus: { enabled: true, maxEntries: 300, similarityThreshold: 0.6 },
202
- errorCatalog: { enabled: true, maxEntries: 200 },
203
- embedder: "tfidf"
204
- }),
205
- /**
206
- * Playwright MCP integration for AI-sighted code generation.
207
- *
208
- * CI/CD strategy (default):
209
- * - MCP ON for code generation → AI sees real page elements → 90-95% selector accuracy
210
- * - MCP OFF for test execution → runner executes pre-generated code, no MCP overhead
211
- *
212
- * MCP is ONLY used during generation (SmartRouter, Studio, API generate endpoints).
213
- * The test runner (engine/runner.ts → code-runner.ts → AsyncFunction) never touches MCP —
214
- * it simply executes the pre-generated Playwright code as-is.
215
- */
216
- mcp: import_zod.z.object({
217
- /** Enable MCP-enhanced generation (real page snapshots for AI). ON by default. */
218
- enabled: import_zod.z.boolean(),
219
- /** Run MCP browser in headless mode. */
220
- headless: import_zod.z.boolean(),
221
- /** Two-phase: execute action via MCP first, then convert to script. */
222
- actThenScript: import_zod.z.boolean(),
223
- /** Pre-run element validation using MCP snapshots. */
224
- proactiveHealing: import_zod.z.boolean(),
225
- /** Timeout per MCP tool call in ms. */
226
- actionTimeout: import_zod.z.number().int().positive(),
227
- /** Idle timeout before MCP browser auto-disconnects in ms. */
228
- idleTimeout: import_zod.z.number().int().positive()
229
- }).default({
230
- enabled: true,
231
- headless: true,
232
- actThenScript: false,
233
- proactiveHealing: false,
234
- actionTimeout: 15e3,
235
- idleTimeout: 3e4
236
- })
237
- });
238
- var DEFAULT_CONFIG = {
239
- baseUrl: "http://localhost:3000",
240
- environment: "stage",
241
- environmentUrls: {
242
- dev: "",
243
- stage: "http://localhost:3000",
244
- test: "",
245
- prod: ""
246
- },
247
- browsers: ["chromium"],
248
- headless: true,
249
- viewport: { width: 1280, height: 720 },
250
- timeout: 3e4,
251
- retries: 1,
252
- parallel: 1,
253
- pageLoad: "domcontentloaded",
254
- screenshot: "only-on-failure",
255
- video: "off",
256
- trace: "on-first-retry",
257
- healing: {
258
- enabled: true,
259
- maxLevel: 5,
260
- dailyBudget: 5,
261
- autoPR: false
262
- },
263
- reporting: {
264
- allure: true,
265
- html: true,
266
- json: true
267
- },
268
- studioPort: 4400,
269
- profiles: [],
270
- rag: {
271
- enabled: true,
272
- codeCorpus: { enabled: true, maxEntries: 500, similarityThreshold: 0.65, directUseThreshold: 0.9 },
273
- healingCorpus: { enabled: true, maxEntries: 300, similarityThreshold: 0.6 },
274
- errorCatalog: { enabled: true, maxEntries: 200 },
275
- embedder: "tfidf"
276
- },
277
- mcp: {
278
- enabled: true,
279
- headless: true,
280
- actThenScript: false,
281
- proactiveHealing: false,
282
- actionTimeout: 15e3,
283
- idleTimeout: 3e4
284
- }
285
- };
286
-
287
- // src/storage/suite-store.ts
288
- var import_path2 = __toESM(require("path"));
289
- var import_fs_extra3 = __toESM(require("fs-extra"));
290
- var import_uuid = require("uuid");
291
-
292
- // src/types/suite.ts
293
- var import_zod2 = require("zod");
294
- var TestStepSchema = import_zod2.z.object({
295
- id: import_zod2.z.string().min(1),
296
- order: import_zod2.z.number().int().positive(),
297
- instruction: import_zod2.z.string().min(1),
298
- generatedCode: import_zod2.z.string(),
299
- strategy: import_zod2.z.enum(["template", "cache", "rag", "batch", "fast", "primary", "recorder"]),
300
- stepType: import_zod2.z.enum(["ui", "api", "mock"]).default("ui"),
301
- lastHealed: import_zod2.z.string().nullable(),
302
- timeout: import_zod2.z.number().int().positive().optional(),
303
- retries: import_zod2.z.number().int().min(0).optional(),
304
- mockUrl: import_zod2.z.string().optional(),
305
- mockResponse: import_zod2.z.string().optional(),
306
- mockStatus: import_zod2.z.number().int().optional(),
307
- runAudit: import_zod2.z.boolean().optional(),
308
- // Mark this step as a Lighthouse audit checkpoint
309
- soft: import_zod2.z.boolean().optional(),
310
- // Use expect.soft() — test continues on assertion failure
311
- visualCheck: import_zod2.z.boolean().optional()
312
- // Enable visual regression comparison for this step
313
- });
314
- var DataSourceSchema = import_zod2.z.object({
315
- type: import_zod2.z.enum(["inline", "json-file", "csv-file"]),
316
- path: import_zod2.z.string().optional(),
317
- data: import_zod2.z.array(import_zod2.z.record(import_zod2.z.string())).optional()
318
- }).optional();
319
- var CaseHookStepSchema = import_zod2.z.object({
320
- id: import_zod2.z.string(),
321
- instruction: import_zod2.z.string(),
322
- generatedCode: import_zod2.z.string().default(""),
323
- order: import_zod2.z.number().int().default(0)
324
- });
325
- var CaseHooksSchema = import_zod2.z.object({
326
- before: import_zod2.z.array(CaseHookStepSchema).default([]),
327
- after: import_zod2.z.array(CaseHookStepSchema).default([])
328
- }).default({ before: [], after: [] });
329
- var TestCaseSchema = import_zod2.z.object({
330
- id: import_zod2.z.string().min(1),
331
- name: import_zod2.z.string().min(1),
332
- description: import_zod2.z.string(),
333
- tags: import_zod2.z.array(import_zod2.z.string()),
334
- priority: import_zod2.z.enum(["critical", "high", "medium", "low"]),
335
- timeout: import_zod2.z.number().int().positive().optional(),
336
- dataSource: DataSourceSchema,
337
- steps: import_zod2.z.array(TestStepSchema),
338
- caseHooks: CaseHooksSchema,
339
- lighthouseCategories: import_zod2.z.array(import_zod2.z.enum(["performance", "accessibility", "seo"])).default(["performance", "accessibility", "seo"]),
340
- createdAt: import_zod2.z.string().datetime(),
341
- updatedAt: import_zod2.z.string().datetime()
342
- });
343
- var TestSuiteSchema = import_zod2.z.object({
344
- id: import_zod2.z.string().min(1),
345
- name: import_zod2.z.string().min(1),
346
- description: import_zod2.z.string(),
347
- tags: import_zod2.z.array(import_zod2.z.string()),
348
- type: import_zod2.z.enum(["ui", "api", "audit", "performance"]).default("ui"),
349
- timeout: import_zod2.z.number().int().positive().optional(),
350
- createdAt: import_zod2.z.string().datetime(),
351
- updatedAt: import_zod2.z.string().datetime()
352
- });
353
- var HookTypeEnum = import_zod2.z.enum(["before_all", "before_each", "after_each", "after_all"]);
354
- var SuiteHooksSchema = import_zod2.z.object({
355
- before_all: import_zod2.z.array(TestStepSchema).default([]),
356
- before_each: import_zod2.z.array(TestStepSchema).default([]),
357
- after_each: import_zod2.z.array(TestStepSchema).default([]),
358
- after_all: import_zod2.z.array(TestStepSchema).default([])
359
- });
360
-
361
- // src/utils/errors.ts
362
- var AssuremindError = class extends Error {
363
- code;
364
- constructor(message, code) {
365
- super(message);
366
- this.name = "AssuremindError";
367
- this.code = code;
368
- Object.setPrototypeOf(this, new.target.prototype);
369
- }
370
- };
371
- var ProviderError = class extends AssuremindError {
372
- provider;
373
- constructor(message, provider, code = "PROVIDER_ERROR") {
374
- super(message, code);
375
- this.name = "ProviderError";
376
- this.provider = provider;
377
- }
378
- };
379
- var ExecutionError = class extends AssuremindError {
380
- stepId;
381
- constructor(message, stepId, code = "EXECUTION_ERROR") {
382
- super(message, code);
383
- this.name = "ExecutionError";
384
- this.stepId = stepId;
385
- }
386
- };
387
- var ConfigError = class extends AssuremindError {
388
- constructor(message, code = "CONFIG_ERROR") {
389
- super(message, code);
390
- this.name = "ConfigError";
391
- }
392
- };
393
- var ValidationError = class extends AssuremindError {
394
- field;
395
- constructor(message, field, code = "VALIDATION_ERROR") {
396
- super(message, code);
397
- this.name = "ValidationError";
398
- this.field = field;
399
- }
400
- };
401
- var HealingError = class extends AssuremindError {
402
- level;
403
- constructor(message, level, code = "HEALING_ERROR") {
404
- super(message, code);
405
- this.name = "HealingError";
406
- this.level = level;
407
- }
408
- };
409
- var StorageError = class extends AssuremindError {
410
- path;
411
- constructor(message, path8, code = "STORAGE_ERROR") {
412
- super(message, code);
413
- this.name = "StorageError";
414
- this.path = path8;
415
- }
416
- };
417
- function isAssuremindError(error) {
418
- return error instanceof AssuremindError;
419
- }
420
- function formatError(error) {
421
- if (error instanceof AssuremindError) {
422
- return `[${error.code}] ${error.message}`;
423
- }
424
- if (error instanceof Error) {
425
- return error.message;
426
- }
427
- return String(error);
428
- }
429
-
430
- // src/utils/logger.ts
431
- var import_pino = __toESM(require("pino"));
432
- var import_fs_extra = __toESM(require("fs-extra"));
433
- var isDevelopment = process.env["NODE_ENV"] !== "production";
434
- var transport = isDevelopment ? {
435
- target: "pino-pretty",
436
- options: {
437
- colorize: true,
438
- translateTime: "HH:MM:ss",
439
- ignore: "pid,hostname",
440
- messageFormat: "[assuremind] {msg}"
441
- }
442
- } : void 0;
443
- var logger = (0, import_pino.default)(
444
- {
445
- level: process.env["LOG_LEVEL"] ?? "info",
446
- base: { name: "assuremind" }
447
- },
448
- transport ? import_pino.default.transport(transport) : void 0
449
- );
450
- function createChildLogger(component) {
451
- return logger.child({ component });
452
- }
453
-
454
- // src/utils/sanitize.ts
455
- function stripCodeFences(raw) {
456
- const fencePattern = /^```(?:typescript|javascript|ts|js)?\n?([\s\S]*?)```\s*$/m;
457
- const match = raw.match(fencePattern);
458
- if (match?.[1] !== void 0) {
459
- return match[1].trim();
460
- }
461
- return raw.trim();
462
- }
463
- var FORBIDDEN_PATTERNS = [
464
- /\brequire\s*\(/,
465
- /\bimport\s*\(/,
466
- /\bprocess\b/,
467
- /\bchild_process\b/,
468
- /\bexec\s*\(/,
469
- /\bspawn\s*\(/,
470
- /\beval\s*\(/,
471
- /\bFunction\s*\(/,
472
- /\b__dirname\b/,
473
- /\b__filename\b/,
474
- /\bglobal\b/,
475
- /\bwindow\.location\.href\s*=/,
476
- /\bdocument\.cookie\b/,
477
- /\blocalStorage\b/,
478
- /\bsessionStorage\b/,
479
- /\bIndexedDB\b/,
480
- /\bXMLHttpRequest\b/,
481
- /\bfetch\s*\(/,
482
- /\bWebSocket\s*\(/
483
- ];
484
- function validateGeneratedCode(code) {
485
- for (const pattern of FORBIDDEN_PATTERNS) {
486
- if (pattern.test(code)) {
487
- throw new ValidationError(
488
- `Generated code contains forbidden pattern: ${pattern.source}. This may be a prompt injection attempt. Please regenerate the step.`,
489
- "generatedCode",
490
- "UNSAFE_CODE"
491
- );
492
- }
493
- }
494
- }
495
- function fixAntiPatterns(code) {
496
- let result = code;
497
- result = result.replace(
498
- /\.or\((?:[^()]*|\([^()]*\))*\)(?:\.(?:first|last)\(\))?/g,
499
- ""
500
- );
501
- result = result.replace(/\.(?:first|last)\(\)(?=\.\w)/g, "");
502
- return result;
503
- }
504
- function sanitizeGeneratedCode(raw) {
505
- const stripped = stripCodeFences(raw);
506
- if (!stripped) {
507
- throw new ValidationError(
508
- "AI returned an empty code response. Please try regenerating.",
509
- "generatedCode",
510
- "EMPTY_CODE"
511
- );
512
- }
513
- const fixed = fixAntiPatterns(stripped);
514
- validateGeneratedCode(fixed);
515
- return fixed;
516
- }
517
- function toSlug(input) {
518
- return input.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
519
- }
520
- function redactSecrets(text, secrets) {
521
- let result = text;
522
- for (const secret of secrets) {
523
- if (secret.length > 0) {
524
- result = result.replaceAll(secret, "[REDACTED]");
525
- }
526
- }
527
- return result;
528
- }
529
-
530
- // src/storage/utils.ts
531
- var import_path = __toESM(require("path"));
532
- var import_fs_extra2 = __toESM(require("fs-extra"));
533
- async function atomicWriteJson(filePath, data) {
534
- const dir = import_path.default.dirname(filePath);
535
- await import_fs_extra2.default.ensureDir(dir);
536
- const tmpPath = `${filePath}.${process.pid}.tmp`;
537
- try {
538
- await import_fs_extra2.default.writeJson(tmpPath, data, { spaces: 2 });
539
- await import_fs_extra2.default.rename(tmpPath, filePath);
540
- } catch (err) {
541
- await import_fs_extra2.default.remove(tmpPath).catch(() => void 0);
542
- throw new StorageError(
543
- `Failed to write file "${filePath}": ${err instanceof Error ? err.message : String(err)}`,
544
- filePath,
545
- "WRITE_FAILED"
546
- );
547
- }
548
- }
549
- async function readJson(filePath) {
550
- try {
551
- return await import_fs_extra2.default.readJson(filePath);
552
- } catch (err) {
553
- throw new StorageError(
554
- `Failed to read JSON file "${filePath}": ${err instanceof Error ? err.message : String(err)}`,
555
- filePath,
556
- "READ_FAILED"
557
- );
558
- }
559
- }
560
- async function atomicWriteText(filePath, content) {
561
- const dir = import_path.default.dirname(filePath);
562
- await import_fs_extra2.default.ensureDir(dir);
563
- const tmpPath = `${filePath}.${process.pid}.tmp`;
564
- try {
565
- await import_fs_extra2.default.writeFile(tmpPath, content, "utf8");
566
- await import_fs_extra2.default.rename(tmpPath, filePath);
567
- } catch (err) {
568
- await import_fs_extra2.default.remove(tmpPath).catch(() => void 0);
569
- throw new StorageError(
570
- `Failed to write file "${filePath}": ${err instanceof Error ? err.message : String(err)}`,
571
- filePath,
572
- "WRITE_FAILED"
573
- );
574
- }
575
- }
576
-
577
- // src/storage/suite-store.ts
578
- var logger2 = createChildLogger("suite-store");
579
- var SUITE_FILE = "suite.json";
580
- async function readSuite(suiteDir) {
581
- const filePath = import_path2.default.join(suiteDir, SUITE_FILE);
582
- if (!await import_fs_extra3.default.pathExists(filePath)) {
583
- throw new StorageError(
584
- `Suite file not found at "${filePath}". Ensure the suite directory exists and contains a suite.json file.`,
585
- filePath,
586
- "SUITE_NOT_FOUND"
587
- );
588
- }
589
- const raw = await readJson(filePath);
590
- const result = TestSuiteSchema.safeParse(raw);
591
- if (!result.success) {
592
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
593
- throw new ValidationError(
594
- `Invalid suite.json at "${filePath}":
595
- ${issues}`,
596
- "suite",
597
- "INVALID_SUITE"
598
- );
599
- }
600
- const rawObj = raw;
601
- if (!rawObj.type) {
602
- const parentDir = import_path2.default.basename(import_path2.default.dirname(suiteDir));
603
- if (parentDir === "api") result.data.type = "api";
604
- else if (parentDir === "audit") result.data.type = "audit";
605
- else if (parentDir === "performance") result.data.type = "audit";
606
- else result.data.type = "ui";
607
- }
608
- return result.data;
609
- }
610
- async function writeSuite(suiteDir, suite) {
611
- await import_fs_extra3.default.ensureDir(suiteDir);
612
- const filePath = import_path2.default.join(suiteDir, SUITE_FILE);
613
- const result = TestSuiteSchema.safeParse(suite);
614
- if (!result.success) {
615
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
616
- throw new ValidationError(
617
- `Cannot write invalid suite to "${filePath}":
618
- ${issues}`,
619
- "suite",
620
- "INVALID_SUITE"
621
- );
622
- }
623
- await atomicWriteJson(filePath, result.data);
624
- logger2.debug({ suiteId: suite.id, path: filePath }, "Suite written");
625
- }
626
- async function createSuite(testsDir, suite) {
627
- const now = (/* @__PURE__ */ new Date()).toISOString();
628
- const suiteType = suite.type ?? "ui";
629
- const newSuite = {
630
- ...suite,
631
- type: suiteType,
632
- id: (0, import_uuid.v4)(),
633
- createdAt: now,
634
- updatedAt: now
635
- };
636
- const targetDir = import_path2.default.join(testsDir, suiteType);
637
- const suiteDir = import_path2.default.join(targetDir, toSlug(newSuite.name));
638
- if (await import_fs_extra3.default.pathExists(import_path2.default.join(suiteDir, SUITE_FILE))) {
639
- throw new StorageError(
640
- `Suite directory already exists at "${suiteDir}". Choose a different name or delete the existing suite first.`,
641
- suiteDir,
642
- "SUITE_ALREADY_EXISTS"
643
- );
644
- }
645
- await writeSuite(suiteDir, newSuite);
646
- logger2.info({ suiteId: newSuite.id, path: suiteDir }, "Suite created");
647
- return { suiteDir, suiteId: newSuite.id };
648
- }
649
- async function updateSuite(suiteDir, updates) {
650
- const existing = await readSuite(suiteDir);
651
- const updated = {
652
- ...existing,
653
- ...updates,
654
- id: existing.id,
655
- createdAt: existing.createdAt,
656
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
657
- };
658
- await writeSuite(suiteDir, updated);
659
- return updated;
660
- }
661
- async function deleteSuite(suiteDir) {
662
- if (!await import_fs_extra3.default.pathExists(suiteDir)) {
663
- throw new StorageError(
664
- `Suite directory not found at "${suiteDir}".`,
665
- suiteDir,
666
- "SUITE_NOT_FOUND"
667
- );
668
- }
669
- await import_fs_extra3.default.remove(suiteDir);
670
- logger2.info({ path: suiteDir }, "Suite deleted");
671
- }
672
- async function listSuiteDirs(testsDir) {
673
- const suiteDirs = [];
674
- const searchDirs = [
675
- import_path2.default.join(testsDir, "ui"),
676
- import_path2.default.join(testsDir, "api"),
677
- import_path2.default.join(testsDir, "audit"),
678
- import_path2.default.join(testsDir, "performance"),
679
- // legacy: keep scanning for backward compat
680
- testsDir
681
- // legacy: suites at root level
682
- ];
683
- for (const baseDir of searchDirs) {
684
- if (!await import_fs_extra3.default.pathExists(baseDir)) continue;
685
- const entries = await import_fs_extra3.default.readdir(baseDir, { withFileTypes: true });
686
- for (const entry of entries) {
687
- if (!entry.isDirectory()) continue;
688
- if (baseDir === testsDir && (entry.name === "ui" || entry.name === "api" || entry.name === "audit" || entry.name === "performance")) continue;
689
- const suiteFile = import_path2.default.join(baseDir, entry.name, SUITE_FILE);
690
- if (await import_fs_extra3.default.pathExists(suiteFile)) {
691
- suiteDirs.push(import_path2.default.join(baseDir, entry.name));
692
- }
693
- }
694
- }
695
- return suiteDirs;
696
- }
697
- async function listSuites(testsDir) {
698
- const dirs = await listSuiteDirs(testsDir);
699
- const suites = [];
700
- for (const dir of dirs) {
701
- try {
702
- suites.push(await readSuite(dir));
703
- } catch (err) {
704
- logger2.warn({ path: dir, err }, "Failed to read suite \u2014 skipping");
705
- }
706
- }
707
- return suites;
708
- }
709
- async function listSuitesWithCounts(testsDir) {
710
- const dirs = await listSuiteDirs(testsDir);
711
- const suites = [];
712
- for (const dir of dirs) {
713
- try {
714
- const suite = await readSuite(dir);
715
- const entries = await import_fs_extra3.default.readdir(dir, { withFileTypes: true });
716
- const caseCount = entries.filter((e) => e.isFile() && e.name.endsWith(".test.json")).length;
717
- suites.push({ ...suite, caseCount });
718
- } catch (err) {
719
- logger2.warn({ path: dir, err }, "Failed to read suite \u2014 skipping");
720
- }
721
- }
722
- return suites;
723
- }
724
-
725
- // src/storage/case-store.ts
726
- var import_path3 = __toESM(require("path"));
727
- var import_fs_extra4 = __toESM(require("fs-extra"));
728
- var import_uuid2 = require("uuid");
729
- var logger3 = createChildLogger("case-store");
730
- var CASE_EXTENSION = ".test.json";
731
- async function readCase(casePath) {
732
- if (!await import_fs_extra4.default.pathExists(casePath)) {
733
- throw new StorageError(
734
- `Test case file not found at "${casePath}".`,
735
- casePath,
736
- "CASE_NOT_FOUND"
737
- );
738
- }
739
- const raw = await readJson(casePath);
740
- const result = TestCaseSchema.safeParse(raw);
741
- if (!result.success) {
742
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
743
- throw new ValidationError(
744
- `Invalid test case file at "${casePath}":
745
- ${issues}`,
746
- "case",
747
- "INVALID_CASE"
748
- );
749
- }
750
- return result.data;
751
- }
752
- async function writeCase(casePath, testCase) {
753
- const result = TestCaseSchema.safeParse(testCase);
754
- if (!result.success) {
755
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
756
- throw new ValidationError(
757
- `Cannot write invalid test case to "${casePath}":
758
- ${issues}`,
759
- "case",
760
- "INVALID_CASE"
761
- );
762
- }
763
- await atomicWriteJson(casePath, result.data);
764
- logger3.debug({ caseId: testCase.id, path: casePath }, "Test case written");
765
- }
766
- async function createCase(suiteDir, testCase) {
767
- const now = (/* @__PURE__ */ new Date()).toISOString();
768
- const newCase = {
769
- ...testCase,
770
- id: (0, import_uuid2.v4)(),
771
- createdAt: now,
772
- updatedAt: now
773
- };
774
- const casePath = import_path3.default.join(suiteDir, `${toSlug(newCase.name)}${CASE_EXTENSION}`);
775
- await writeCase(casePath, newCase);
776
- logger3.info({ caseId: newCase.id, path: casePath }, "Test case created");
777
- return casePath;
778
- }
779
- async function updateCase(casePath, updates) {
780
- const existing = await readCase(casePath);
781
- const updated = {
782
- ...existing,
783
- ...updates,
784
- id: existing.id,
785
- createdAt: existing.createdAt,
786
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
787
- };
788
- await writeCase(casePath, updated);
789
- return updated;
790
- }
791
- async function deleteCase(casePath) {
792
- if (!await import_fs_extra4.default.pathExists(casePath)) {
793
- throw new StorageError(
794
- `Test case file not found at "${casePath}".`,
795
- casePath,
796
- "CASE_NOT_FOUND"
797
- );
798
- }
799
- await import_fs_extra4.default.remove(casePath);
800
- logger3.info({ path: casePath }, "Test case deleted");
801
- }
802
- async function listCasePaths(suiteDir) {
803
- if (!await import_fs_extra4.default.pathExists(suiteDir)) return [];
804
- const entries = await import_fs_extra4.default.readdir(suiteDir, { withFileTypes: true });
805
- return entries.filter((e) => e.isFile() && e.name.endsWith(CASE_EXTENSION)).map((e) => import_path3.default.join(suiteDir, e.name));
806
- }
807
- async function listCases(suiteDir) {
808
- const paths = await listCasePaths(suiteDir);
809
- const cases = [];
810
- for (const casePath of paths) {
811
- try {
812
- cases.push(await readCase(casePath));
813
- } catch (err) {
814
- logger3.warn({ path: casePath, err }, "Failed to read test case \u2014 skipping");
815
- }
816
- }
817
- return cases.sort((a, b) => a.name.localeCompare(b.name));
818
- }
819
- function getCasePath(suiteDir, caseName) {
820
- return import_path3.default.join(suiteDir, `${toSlug(caseName)}${CASE_EXTENSION}`);
821
- }
822
-
823
- // src/storage/variable-store.ts
824
- var import_path4 = __toESM(require("path"));
825
- var import_fs_extra5 = __toESM(require("fs-extra"));
826
-
827
- // src/types/variable.ts
828
- var import_zod3 = require("zod");
829
- var SecretVariableSchema = import_zod3.z.object({
830
- value: import_zod3.z.string(),
831
- secret: import_zod3.z.literal(true)
832
- });
833
- var VariableValueSchema = import_zod3.z.union([import_zod3.z.string(), SecretVariableSchema]);
834
- var VariableStoreSchema = import_zod3.z.record(import_zod3.z.string(), VariableValueSchema);
835
- function isSecretVariable(value) {
836
- return typeof value === "object" && value.secret === true;
837
- }
838
- function resolveVariableValue(value) {
839
- if (isSecretVariable(value)) {
840
- return value.value;
841
- }
842
- return value;
843
- }
844
-
845
- // src/storage/variable-store.ts
846
- var logger4 = createChildLogger("variable-store");
847
- var VARIABLES_DIR = "variables";
848
- var GLOBAL_FILE = "global.json";
849
- function envFileName(env) {
850
- return `${env}.env.json`;
851
- }
852
- async function readVariables(filePath) {
853
- if (!await import_fs_extra5.default.pathExists(filePath)) {
854
- return {};
855
- }
856
- const raw = await readJson(filePath);
857
- const result = VariableStoreSchema.safeParse(raw);
858
- if (!result.success) {
859
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
860
- throw new ValidationError(
861
- `Invalid variables file at "${filePath}":
862
- ${issues}`,
863
- "variables",
864
- "INVALID_VARIABLES"
865
- );
866
- }
867
- return result.data;
868
- }
869
- async function writeVariables(filePath, store) {
870
- const result = VariableStoreSchema.safeParse(store);
871
- if (!result.success) {
872
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
873
- throw new ValidationError(
874
- `Cannot write invalid variables to "${filePath}":
875
- ${issues}`,
876
- "variables",
877
- "INVALID_VARIABLES"
878
- );
879
- }
880
- await atomicWriteJson(filePath, result.data);
881
- logger4.debug({ path: filePath }, "Variables written");
882
- }
883
- async function readGlobalVariables(rootDir) {
884
- const filePath = import_path4.default.join(rootDir, VARIABLES_DIR, GLOBAL_FILE);
885
- return readVariables(filePath);
886
- }
887
- async function readEnvVariables(rootDir, env) {
888
- const filePath = import_path4.default.join(rootDir, VARIABLES_DIR, envFileName(env));
889
- return readVariables(filePath);
890
- }
891
- async function resolveVariables(rootDir, env) {
892
- const global = await readGlobalVariables(rootDir);
893
- const envSpecific = env ? await readEnvVariables(rootDir, env) : {};
894
- const merged = { ...global, ...envSpecific };
895
- const resolved = {};
896
- for (const [key, value] of Object.entries(merged)) {
897
- resolved[key] = resolveVariableValue(value);
898
- }
899
- return resolved;
900
- }
901
- async function setGlobalVariable(rootDir, key, value) {
902
- const filePath = import_path4.default.join(rootDir, VARIABLES_DIR, GLOBAL_FILE);
903
- await import_fs_extra5.default.ensureDir(import_path4.default.dirname(filePath));
904
- const existing = await readVariables(filePath);
905
- existing[key] = value;
906
- await writeVariables(filePath, existing);
907
- }
908
- async function deleteGlobalVariable(rootDir, key) {
909
- const filePath = import_path4.default.join(rootDir, VARIABLES_DIR, GLOBAL_FILE);
910
- const existing = await readVariables(filePath);
911
- if (!(key in existing)) {
912
- throw new StorageError(
913
- `Variable "${key}" not found in global variables.`,
914
- filePath,
915
- "VARIABLE_NOT_FOUND"
916
- );
917
- }
918
- delete existing[key];
919
- await writeVariables(filePath, existing);
920
- }
921
- async function listVariableFiles(rootDir) {
922
- const dir = import_path4.default.join(rootDir, VARIABLES_DIR);
923
- if (!await import_fs_extra5.default.pathExists(dir)) return [];
924
- const entries = await import_fs_extra5.default.readdir(dir, { withFileTypes: true });
925
- return entries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => import_path4.default.join(dir, e.name));
926
- }
927
-
928
- // src/storage/config-store.ts
929
- var import_path5 = __toESM(require("path"));
930
- var import_fs_extra6 = __toESM(require("fs-extra"));
931
- var logger5 = createChildLogger("config-store");
932
- var CONFIG_JSON = "autotest.config.json";
933
- var CONFIG_TS = "autotest.config.ts";
934
- async function readConfig(rootDir) {
935
- const jsonPath = import_path5.default.join(rootDir, CONFIG_JSON);
936
- if (!await import_fs_extra6.default.pathExists(jsonPath)) {
937
- logger5.debug({ rootDir }, "No autotest.config.json found \u2014 using defaults");
938
- return DEFAULT_CONFIG;
939
- }
940
- const raw = await readJson(jsonPath);
941
- const result = AutotestConfigSchema.safeParse(raw);
942
- if (!result.success) {
943
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
944
- throw new ValidationError(
945
- `Invalid autotest.config.json at "${jsonPath}":
946
- ${issues}
947
-
948
- How to fix: Run "npx assuremind init" to reset to defaults, or manually correct the file using autotest.config.ts as reference.`,
949
- "config",
950
- "INVALID_CONFIG"
951
- );
952
- }
953
- return result.data;
954
- }
955
- async function writeConfig(rootDir, config) {
956
- const result = AutotestConfigSchema.safeParse(config);
957
- if (!result.success) {
958
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
959
- throw new ValidationError(
960
- `Cannot write invalid config:
961
- ${issues}`,
962
- "config",
963
- "INVALID_CONFIG"
964
- );
965
- }
966
- const jsonPath = import_path5.default.join(rootDir, CONFIG_JSON);
967
- await atomicWriteJson(jsonPath, result.data);
968
- const tsPath = import_path5.default.join(rootDir, CONFIG_TS);
969
- await atomicWriteText(tsPath, generateConfigTs(result.data));
970
- logger5.info({ rootDir }, "Config saved");
971
- }
972
- async function updateConfig(rootDir, updates) {
973
- const current = await readConfig(rootDir);
974
- const merged = {
975
- ...current,
976
- ...updates,
977
- healing: { ...current.healing, ...updates.healing ?? {} },
978
- reporting: { ...current.reporting, ...updates.reporting ?? {} },
979
- environmentUrls: { ...current.environmentUrls, ...updates.environmentUrls ?? {} }
980
- };
981
- await writeConfig(rootDir, merged);
982
- return merged;
983
- }
984
- async function configExists(rootDir) {
985
- const jsonPath = import_path5.default.join(rootDir, CONFIG_JSON);
986
- const tsPath = import_path5.default.join(rootDir, CONFIG_TS);
987
- return await import_fs_extra6.default.pathExists(jsonPath) || await import_fs_extra6.default.pathExists(tsPath);
988
- }
989
- async function validateConfig(rootDir) {
990
- const config = await readConfig(rootDir);
991
- if (!config.baseUrl) {
992
- throw new ConfigError(
993
- 'baseUrl is required in autotest.config.ts. Set it to your application URL, e.g. "http://localhost:3000".',
994
- "CONFIG_BASE_URL_MISSING"
995
- );
996
- }
997
- logger5.debug({ config }, "Config validated successfully");
998
- }
999
- function generateConfigTs(config) {
1000
- return `import { defineConfig } from 'assuremind';
1
+ "use strict";var Ut=Object.create;var Y=Object.defineProperty;var kt=Object.getOwnPropertyDescriptor;var Gt=Object.getOwnPropertyNames;var Mt=Object.getPrototypeOf,Bt=Object.prototype.hasOwnProperty;var Wt=(e,t)=>{for(var n in t)Y(e,n,{get:t[n],enumerable:!0})},Ve=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let i of Gt(t))!Bt.call(e,i)&&i!==n&&Y(e,i,{get:()=>t[i],enumerable:!(r=kt(t,i))||r.enumerable});return e};var h=(e,t,n)=>(n=e!=null?Ut(Mt(e)):{},Ve(t||!e||!e.__esModule?Y(n,"default",{value:e,enumerable:!0}):n,e)),Jt=e=>Ve(Y({},"__esModule",{value:!0}),e);var Wn={};Wt(Wn,{AssuremindError:()=>v,ConfigError:()=>C,ExecutionError:()=>Z,HealingError:()=>Q,ProviderError:()=>X,StorageError:()=>g,ValidationError:()=>u,acceptHealingEvent:()=>Ct,appendPendingEvent:()=>jt,callFaker:()=>zt,clearEnvCache:()=>Dt,configExists:()=>ut,createCase:()=>et,createChildLogger:()=>b,createSuite:()=>Ke,defineConfig:()=>Bn,deleteCase:()=>nt,deleteGlobalVariable:()=>ct,deleteResult:()=>bt,deleteSuite:()=>qe,formatError:()=>Ue,generateCacheKey:()=>$t,getAvailableGenerators:()=>Ft,getCasePath:()=>it,getHealingStats:()=>_t,isAssuremindError:()=>ze,listCasePaths:()=>Se,listCases:()=>rt,listHealingReportIds:()=>Nt,listResultIds:()=>_e,listResults:()=>xt,listSuiteDirs:()=>ne,listSuites:()=>Xe,listSuitesWithCounts:()=>Ze,listVariableFiles:()=>lt,logger:()=>he,normalizeUrl:()=>$e,pruneResolvedEvents:()=>Pt,readCase:()=>ae,readConfig:()=>le,readEnvVariables:()=>ve,readGlobalVariables:()=>we,readHealingReport:()=>It,readPendingEvents:()=>V,readResult:()=>Pe,readSuite:()=>G,readVariables:()=>H,redactSecrets:()=>Be,rejectHealingEvent:()=>Tt,resolveVariables:()=>ot,sanitizeGeneratedCode:()=>Me,screenshotsDir:()=>St,setGlobalVariable:()=>st,sha256:()=>Oe,toSlug:()=>N,tracesDir:()=>Et,tryGetEnv:()=>Ht,updateCase:()=>tt,updateConfig:()=>pt,updateSuite:()=>Ye,validateConfig:()=>dt,validateEnv:()=>Le,videosDir:()=>yt,writeCase:()=>oe,writeConfig:()=>Ie,writeHealingReport:()=>Rt,writeResult:()=>ht,writeSuite:()=>te,writeVariables:()=>se});module.exports=Jt(Wn);var c=require("zod"),Kt=c.z.enum(["off","on","only-on-failure"]),Yt=c.z.enum(["off","on","on-first-retry","retain-on-failure"]),qt=c.z.enum(["off","on","on-first-retry","retain-on-failure"]),De=c.z.enum(["chromium","firefox","webkit"]),Xt=c.z.enum(["commit","domcontentloaded","load","networkidle"]),He=c.z.enum(["dev","stage","test","prod"]),Zt=c.z.object({dev:c.z.string().url().or(c.z.literal("")).default(""),stage:c.z.string().url().or(c.z.literal("")).default(""),test:c.z.string().url().or(c.z.literal("")).default(""),prod:c.z.string().url().or(c.z.literal("")).default("")}),Qt=c.z.object({enabled:c.z.boolean(),maxLevel:c.z.number().int().min(1).max(5),dailyBudget:c.z.number().positive(),autoPR:c.z.boolean()}),en=c.z.object({allure:c.z.boolean(),html:c.z.boolean(),json:c.z.boolean()}),tn=c.z.object({width:c.z.number().int().positive(),height:c.z.number().int().positive()}),nn=c.z.object({name:c.z.string().min(1),environment:He,baseUrl:c.z.string().url(),browsers:c.z.array(De).min(1),headless:c.z.boolean().optional()}),z=c.z.object({baseUrl:c.z.string().url(),environment:He.default("stage"),environmentUrls:Zt.default({dev:"",stage:"",test:"",prod:""}),browsers:c.z.array(De).min(1),headless:c.z.boolean(),viewport:tn.default({width:1280,height:720}),timeout:c.z.number().int().positive(),retries:c.z.number().int().min(0),parallel:c.z.number().int().positive(),pageLoad:Xt.default("domcontentloaded"),screenshot:Kt,video:Yt,trace:qt,healing:Qt,reporting:en,studioPort:c.z.number().int().min(1024).max(65535),profiles:c.z.array(nn).default([]),activeProfile:c.z.string().optional(),device:c.z.string().optional(),rag:c.z.object({enabled:c.z.boolean(),codeCorpus:c.z.object({enabled:c.z.boolean(),maxEntries:c.z.number().int().positive(),similarityThreshold:c.z.number().min(0).max(1),directUseThreshold:c.z.number().min(0).max(1)}),healingCorpus:c.z.object({enabled:c.z.boolean(),maxEntries:c.z.number().int().positive(),similarityThreshold:c.z.number().min(0).max(1)}),errorCatalog:c.z.object({enabled:c.z.boolean(),maxEntries:c.z.number().int().positive()}),embedder:c.z.literal("tfidf")}).default({enabled:!0,codeCorpus:{enabled:!0,maxEntries:500,similarityThreshold:.65,directUseThreshold:.9},healingCorpus:{enabled:!0,maxEntries:300,similarityThreshold:.6},errorCatalog:{enabled:!0,maxEntries:200},embedder:"tfidf"}),mcp:c.z.object({enabled:c.z.boolean(),headless:c.z.boolean(),actThenScript:c.z.boolean(),proactiveHealing:c.z.boolean(),actionTimeout:c.z.number().int().positive(),idleTimeout:c.z.number().int().positive()}).default({enabled:!0,headless:!0,actThenScript:!1,proactiveHealing:!1,actionTimeout:15e3,idleTimeout:3e4})}),q={baseUrl:"http://localhost:3000",environment:"stage",environmentUrls:{dev:"",stage:"http://localhost:3000",test:"",prod:""},browsers:["chromium"],headless:!0,viewport:{width:1280,height:720},timeout:3e4,retries:1,parallel:1,pageLoad:"domcontentloaded",screenshot:"only-on-failure",video:"off",trace:"on-first-retry",healing:{enabled:!0,maxLevel:5,dailyBudget:5,autoPR:!1},reporting:{allure:!0,html:!0,json:!0},studioPort:4400,profiles:[],rag:{enabled:!0,codeCorpus:{enabled:!0,maxEntries:500,similarityThreshold:.65,directUseThreshold:.9},healingCorpus:{enabled:!0,maxEntries:300,similarityThreshold:.6},errorCatalog:{enabled:!0,maxEntries:200},embedder:"tfidf"},mcp:{enabled:!0,headless:!0,actThenScript:!1,proactiveHealing:!1,actionTimeout:15e3,idleTimeout:3e4}};var S=h(require("path")),I=h(require("fs-extra")),Je=require("uuid");var l=require("zod"),U=l.z.object({id:l.z.string().min(1),order:l.z.number().int().positive(),instruction:l.z.string().min(1),generatedCode:l.z.string(),strategy:l.z.enum(["template","cache","rag","batch","fast","primary","recorder"]),stepType:l.z.enum(["ui","api","mock"]).default("ui"),lastHealed:l.z.string().nullable(),timeout:l.z.number().int().positive().optional(),retries:l.z.number().int().min(0).optional(),mockUrl:l.z.string().optional(),mockResponse:l.z.string().optional(),mockStatus:l.z.number().int().optional(),runAudit:l.z.boolean().optional(),soft:l.z.boolean().optional(),visualCheck:l.z.boolean().optional()}),rn=l.z.object({type:l.z.enum(["inline","json-file","csv-file"]),path:l.z.string().optional(),data:l.z.array(l.z.record(l.z.string())).optional()}).optional(),Fe=l.z.object({id:l.z.string(),instruction:l.z.string(),generatedCode:l.z.string().default(""),order:l.z.number().int().default(0)}),an=l.z.object({before:l.z.array(Fe).default([]),after:l.z.array(Fe).default([])}).default({before:[],after:[]}),de=l.z.object({id:l.z.string().min(1),name:l.z.string().min(1),description:l.z.string(),tags:l.z.array(l.z.string()),priority:l.z.enum(["critical","high","medium","low"]),timeout:l.z.number().int().positive().optional(),dataSource:rn,steps:l.z.array(U),caseHooks:an,lighthouseCategories:l.z.array(l.z.enum(["performance","accessibility","seo"])).default(["performance","accessibility","seo"]),createdAt:l.z.string().datetime(),updatedAt:l.z.string().datetime()}),ge=l.z.object({id:l.z.string().min(1),name:l.z.string().min(1),description:l.z.string(),tags:l.z.array(l.z.string()),type:l.z.enum(["ui","api","audit","performance"]).default("ui"),timeout:l.z.number().int().positive().optional(),createdAt:l.z.string().datetime(),updatedAt:l.z.string().datetime()}),qn=l.z.enum(["before_all","before_each","after_each","after_all"]),Xn=l.z.object({before_all:l.z.array(U).default([]),before_each:l.z.array(U).default([]),after_each:l.z.array(U).default([]),after_all:l.z.array(U).default([])});var v=class extends Error{code;constructor(t,n){super(t),this.name="AssuremindError",this.code=n,Object.setPrototypeOf(this,new.target.prototype)}},X=class extends v{provider;constructor(t,n,r="PROVIDER_ERROR"){super(t,r),this.name="ProviderError",this.provider=n}},Z=class extends v{stepId;constructor(t,n,r="EXECUTION_ERROR"){super(t,r),this.name="ExecutionError",this.stepId=n}},C=class extends v{constructor(t,n="CONFIG_ERROR"){super(t,n),this.name="ConfigError"}},u=class extends v{field;constructor(t,n,r="VALIDATION_ERROR"){super(t,r),this.name="ValidationError",this.field=n}},Q=class extends v{level;constructor(t,n,r="HEALING_ERROR"){super(t,r),this.name="HealingError",this.level=n}},g=class extends v{path;constructor(t,n,r="STORAGE_ERROR"){super(t,r),this.name="StorageError",this.path=n}};function ze(e){return e instanceof v}function Ue(e){return e instanceof v?`[${e.code}] ${e.message}`:e instanceof Error?e.message:String(e)}var fe=h(require("pino")),on=h(require("fs-extra"));var sn=process.env.NODE_ENV!=="production",ke=sn?{target:"pino-pretty",options:{colorize:!0,translateTime:"HH:MM:ss",ignore:"pid,hostname",messageFormat:"[assuremind] {msg}"}}:void 0,he=(0,fe.default)({level:process.env.LOG_LEVEL??"info",base:{name:"assuremind"}},ke?fe.default.transport(ke):void 0);function b(e){return he.child({component:e})}function cn(e){let t=/^```(?:typescript|javascript|ts|js)?\n?([\s\S]*?)```\s*$/m,n=e.match(t);return n?.[1]!==void 0?n[1].trim():e.trim()}var ln=[/\brequire\s*\(/,/\bimport\s*\(/,/\bprocess\b/,/\bchild_process\b/,/\bexec\s*\(/,/\bspawn\s*\(/,/\beval\s*\(/,/\bFunction\s*\(/,/\b__dirname\b/,/\b__filename\b/,/\bglobal\b/,/\bwindow\.location\.href\s*=/,/\bdocument\.cookie\b/,/\blocalStorage\b/,/\bsessionStorage\b/,/\bIndexedDB\b/,/\bXMLHttpRequest\b/,/\bfetch\s*\(/,/\bWebSocket\s*\(/];function mn(e){for(let t of ln)if(t.test(e))throw new u(`Generated code contains forbidden pattern: ${t.source}. This may be a prompt injection attempt. Please regenerate the step.`,"generatedCode","UNSAFE_CODE")}var pn=/\s+(?:field|input|box|textbox|text\s?box|drop-?\s?down|dropdown|combo\s?box|combobox|menu|selector|element|checkbox|radio\s?button|radio)$/i;function Ge(e){let t=e.replace(pn,"").trim();return t.length>0?t:e}function un(e){let t=e;return t=t.replace(/(getBy(?:Label|Placeholder)\(\s*)(['"`])([\s\S]*?)\2/g,(n,r,i,a)=>`${r}${i}${Ge(a)}${i}`),t=t.replace(/(name:\s*)(['"`])([\s\S]*?)\2/g,(n,r,i,a)=>`${r}${i}${Ge(a)}${i}`),t}function dn(e){let t=e;return t=t.replace(/\.or\((?:[^()]*|\([^()]*\))*\)(?:\.(?:first|last)\(\))?/g,""),t=t.replace(/\.(?:first|last)\(\)(?=\.\w)/g,""),t=un(t),t}function Me(e){let t=cn(e);if(!t)throw new u("AI returned an empty code response. Please try regenerating.","generatedCode","EMPTY_CODE");let n=dn(t);return mn(n),n}function N(e){return e.toLowerCase().trim().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"")}function Be(e,t){let n=e;for(let r of t)r.length>0&&(n=n.replaceAll(r,"[REDACTED]"));return n}var xe=h(require("path")),R=h(require("fs-extra"));async function x(e,t){let n=xe.default.dirname(e);await R.default.ensureDir(n);let r=`${e}.${process.pid}.tmp`;try{await R.default.writeJson(r,t,{spaces:2}),await R.default.rename(r,e)}catch(i){throw await R.default.remove(r).catch(()=>{}),new g(`Failed to write file "${e}": ${i instanceof Error?i.message:String(i)}`,e,"WRITE_FAILED")}}async function E(e){try{return await R.default.readJson(e)}catch(t){throw new g(`Failed to read JSON file "${e}": ${t instanceof Error?t.message:String(t)}`,e,"READ_FAILED")}}async function We(e,t){let n=xe.default.dirname(e);await R.default.ensureDir(n);let r=`${e}.${process.pid}.tmp`;try{await R.default.writeFile(r,t,"utf8"),await R.default.rename(r,e)}catch(i){throw await R.default.remove(r).catch(()=>{}),new g(`Failed to write file "${e}": ${i instanceof Error?i.message:String(i)}`,e,"WRITE_FAILED")}}var k=b("suite-store"),ee="suite.json";async function G(e){let t=S.default.join(e,ee);if(!await I.default.pathExists(t))throw new g(`Suite file not found at "${t}". Ensure the suite directory exists and contains a suite.json file.`,t,"SUITE_NOT_FOUND");let n=await E(t),r=ge.safeParse(n);if(!r.success){let a=r.error.issues.map(p=>` \u2022 ${p.path.join(".")}: ${p.message}`).join(`
2
+ `);throw new u(`Invalid suite.json at "${t}":
3
+ ${a}`,"suite","INVALID_SUITE")}if(!n.type){let a=S.default.basename(S.default.dirname(e));a==="api"?r.data.type="api":a==="audit"||a==="performance"?r.data.type="audit":r.data.type="ui"}return r.data}async function te(e,t){await I.default.ensureDir(e);let n=S.default.join(e,ee),r=ge.safeParse(t);if(!r.success){let i=r.error.issues.map(a=>` \u2022 ${a.path.join(".")}: ${a.message}`).join(`
4
+ `);throw new u(`Cannot write invalid suite to "${n}":
5
+ ${i}`,"suite","INVALID_SUITE")}await x(n,r.data),k.debug({suiteId:t.id,path:n},"Suite written")}async function Ke(e,t){let n=new Date().toISOString(),r=t.type??"ui",i={...t,type:r,id:(0,Je.v4)(),createdAt:n,updatedAt:n},a=S.default.join(e,r),p=S.default.join(a,N(i.name));if(await I.default.pathExists(S.default.join(p,ee)))throw new g(`Suite directory already exists at "${p}". Choose a different name or delete the existing suite first.`,p,"SUITE_ALREADY_EXISTS");return await te(p,i),k.info({suiteId:i.id,path:p},"Suite created"),{suiteDir:p,suiteId:i.id}}async function Ye(e,t){let n=await G(e),r={...n,...t,id:n.id,createdAt:n.createdAt,updatedAt:new Date().toISOString()};return await te(e,r),r}async function qe(e){if(!await I.default.pathExists(e))throw new g(`Suite directory not found at "${e}".`,e,"SUITE_NOT_FOUND");await I.default.remove(e),k.info({path:e},"Suite deleted")}async function ne(e){let t=[],n=[S.default.join(e,"ui"),S.default.join(e,"api"),S.default.join(e,"audit"),S.default.join(e,"performance"),e];for(let r of n){if(!await I.default.pathExists(r))continue;let i=await I.default.readdir(r,{withFileTypes:!0});for(let a of i){if(!a.isDirectory()||r===e&&(a.name==="ui"||a.name==="api"||a.name==="audit"||a.name==="performance"))continue;let p=S.default.join(r,a.name,ee);await I.default.pathExists(p)&&t.push(S.default.join(r,a.name))}}return t}async function Xe(e){let t=await ne(e),n=[];for(let r of t)try{n.push(await G(r))}catch(i){k.warn({path:r,err:i},"Failed to read suite \u2014 skipping")}return n}async function Ze(e){let t=await ne(e),n=[];for(let r of t)try{let i=await G(r),p=(await I.default.readdir(r,{withFileTypes:!0})).filter(y=>y.isFile()&&y.name.endsWith(".test.json")).length;n.push({...i,caseCount:p})}catch(i){k.warn({path:r,err:i},"Failed to read suite \u2014 skipping")}return n}var re=h(require("path")),D=h(require("fs-extra")),Qe=require("uuid");var ie=b("case-store"),be=".test.json";async function ae(e){if(!await D.default.pathExists(e))throw new g(`Test case file not found at "${e}".`,e,"CASE_NOT_FOUND");let t=await E(e),n=de.safeParse(t);if(!n.success){let r=n.error.issues.map(i=>` \u2022 ${i.path.join(".")}: ${i.message}`).join(`
6
+ `);throw new u(`Invalid test case file at "${e}":
7
+ ${r}`,"case","INVALID_CASE")}return n.data}async function oe(e,t){let n=de.safeParse(t);if(!n.success){let r=n.error.issues.map(i=>` \u2022 ${i.path.join(".")}: ${i.message}`).join(`
8
+ `);throw new u(`Cannot write invalid test case to "${e}":
9
+ ${r}`,"case","INVALID_CASE")}await x(e,n.data),ie.debug({caseId:t.id,path:e},"Test case written")}async function et(e,t){let n=new Date().toISOString(),r={...t,id:(0,Qe.v4)(),createdAt:n,updatedAt:n},i=re.default.join(e,`${N(r.name)}${be}`);return await oe(i,r),ie.info({caseId:r.id,path:i},"Test case created"),i}async function tt(e,t){let n=await ae(e),r={...n,...t,id:n.id,createdAt:n.createdAt,updatedAt:new Date().toISOString()};return await oe(e,r),r}async function nt(e){if(!await D.default.pathExists(e))throw new g(`Test case file not found at "${e}".`,e,"CASE_NOT_FOUND");await D.default.remove(e),ie.info({path:e},"Test case deleted")}async function Se(e){return await D.default.pathExists(e)?(await D.default.readdir(e,{withFileTypes:!0})).filter(n=>n.isFile()&&n.name.endsWith(be)).map(n=>re.default.join(e,n.name)):[]}async function rt(e){let t=await Se(e),n=[];for(let r of t)try{n.push(await ae(r))}catch(i){ie.warn({path:r,err:i},"Failed to read test case \u2014 skipping")}return n.sort((r,i)=>r.name.localeCompare(i.name))}function it(e,t){return re.default.join(e,`${N(t)}${be}`)}var P=h(require("path")),M=h(require("fs-extra"));var T=require("zod"),gn=T.z.object({value:T.z.string(),secret:T.z.literal(!0)}),fn=T.z.union([T.z.string(),gn]),ye=T.z.record(T.z.string(),fn);function hn(e){return typeof e=="object"&&e.secret===!0}function at(e){return hn(e)?e.value:e}var xn=b("variable-store"),B="variables",Ee="global.json";function bn(e){return`${e}.env.json`}async function H(e){if(!await M.default.pathExists(e))return{};let t=await E(e),n=ye.safeParse(t);if(!n.success){let r=n.error.issues.map(i=>` \u2022 ${i.path.join(".")}: ${i.message}`).join(`
10
+ `);throw new u(`Invalid variables file at "${e}":
11
+ ${r}`,"variables","INVALID_VARIABLES")}return n.data}async function se(e,t){let n=ye.safeParse(t);if(!n.success){let r=n.error.issues.map(i=>` \u2022 ${i.path.join(".")}: ${i.message}`).join(`
12
+ `);throw new u(`Cannot write invalid variables to "${e}":
13
+ ${r}`,"variables","INVALID_VARIABLES")}await x(e,n.data),xn.debug({path:e},"Variables written")}async function we(e){let t=P.default.join(e,B,Ee);return H(t)}async function ve(e,t){let n=P.default.join(e,B,bn(t));return H(n)}async function ot(e,t){let n=await we(e),r=t?await ve(e,t):{},i={...n,...r},a={};for(let[p,y]of Object.entries(i))a[p]=at(y);return a}async function st(e,t,n){let r=P.default.join(e,B,Ee);await M.default.ensureDir(P.default.dirname(r));let i=await H(r);i[t]=n,await se(r,i)}async function ct(e,t){let n=P.default.join(e,B,Ee),r=await H(n);if(!(t in r))throw new g(`Variable "${t}" not found in global variables.`,n,"VARIABLE_NOT_FOUND");delete r[t],await se(n,r)}async function lt(e){let t=P.default.join(e,B);return await M.default.pathExists(t)?(await M.default.readdir(t,{withFileTypes:!0})).filter(r=>r.isFile()&&r.name.endsWith(".json")).map(r=>P.default.join(t,r.name)):[]}var F=h(require("path")),ce=h(require("fs-extra"));var Ae=b("config-store"),Re="autotest.config.json",mt="autotest.config.ts";async function le(e){let t=F.default.join(e,Re);if(!await ce.default.pathExists(t))return Ae.debug({rootDir:e},"No autotest.config.json found \u2014 using defaults"),q;let n=await E(t),r=z.safeParse(n);if(!r.success){let i=r.error.issues.map(a=>` \u2022 ${a.path.join(".")}: ${a.message}`).join(`
14
+ `);throw new u(`Invalid autotest.config.json at "${t}":
15
+ ${i}
16
+
17
+ How to fix: Run "npx assuremind init" to reset to defaults, or manually correct the file using autotest.config.ts as reference.`,"config","INVALID_CONFIG")}return r.data}async function Ie(e,t){let n=z.safeParse(t);if(!n.success){let a=n.error.issues.map(p=>` \u2022 ${p.path.join(".")}: ${p.message}`).join(`
18
+ `);throw new u(`Cannot write invalid config:
19
+ ${a}`,"config","INVALID_CONFIG")}let r=F.default.join(e,Re);await x(r,n.data);let i=F.default.join(e,mt);await We(i,Sn(n.data)),Ae.info({rootDir:e},"Config saved")}async function pt(e,t){let n=await le(e),r={...n,...t,healing:{...n.healing,...t.healing??{}},reporting:{...n.reporting,...t.reporting??{}},environmentUrls:{...n.environmentUrls,...t.environmentUrls??{}}};return await Ie(e,r),r}async function ut(e){let t=F.default.join(e,Re),n=F.default.join(e,mt);return await ce.default.pathExists(t)||await ce.default.pathExists(n)}async function dt(e){let t=await le(e);if(!t.baseUrl)throw new C('baseUrl is required in autotest.config.ts. Set it to your application URL, e.g. "http://localhost:3000".',"CONFIG_BASE_URL_MISSING");Ae.debug({config:t},"Config validated successfully")}function Sn(e){return`import { defineConfig } from 'assuremind';
1001
20
 
1002
21
  export default defineConfig({
1003
- baseUrl: '${config.baseUrl}',
1004
- browsers: ${JSON.stringify(config.browsers)},
1005
- headless: ${config.headless},
1006
- timeout: ${config.timeout},
1007
- retries: ${config.retries},
1008
- parallel: ${config.parallel},
1009
- screenshot: '${config.screenshot}',
1010
- video: '${config.video}',
1011
- trace: '${config.trace}',
22
+ baseUrl: '${e.baseUrl}',
23
+ browsers: ${JSON.stringify(e.browsers)},
24
+ headless: ${e.headless},
25
+ timeout: ${e.timeout},
26
+ retries: ${e.retries},
27
+ parallel: ${e.parallel},
28
+ screenshot: '${e.screenshot}',
29
+ video: '${e.video}',
30
+ trace: '${e.trace}',
1012
31
  healing: {
1013
- enabled: ${config.healing.enabled},
1014
- maxLevel: ${config.healing.maxLevel},
1015
- dailyBudget: ${config.healing.dailyBudget},
1016
- autoPR: ${config.healing.autoPR},
32
+ enabled: ${e.healing.enabled},
33
+ maxLevel: ${e.healing.maxLevel},
34
+ dailyBudget: ${e.healing.dailyBudget},
35
+ autoPR: ${e.healing.autoPR},
1017
36
  },
1018
37
  reporting: {
1019
- allure: ${config.reporting.allure},
1020
- html: ${config.reporting.html},
1021
- json: ${config.reporting.json},
38
+ allure: ${e.reporting.allure},
39
+ html: ${e.reporting.html},
40
+ json: ${e.reporting.json},
1022
41
  },
1023
- studioPort: ${config.studioPort},
1024
- });
1025
- `;
1026
- }
1027
-
1028
- // src/storage/result-store.ts
1029
- var import_path6 = __toESM(require("path"));
1030
- var import_fs_extra7 = __toESM(require("fs-extra"));
1031
-
1032
- // src/types/run.ts
1033
- var import_zod4 = require("zod");
1034
- var RunStatusSchema = import_zod4.z.enum(["pending", "running", "passed", "failed", "skipped"]);
1035
- var ApiRequestSchema = import_zod4.z.object({
1036
- method: import_zod4.z.string(),
1037
- url: import_zod4.z.string(),
1038
- headers: import_zod4.z.record(import_zod4.z.string()).optional(),
1039
- body: import_zod4.z.string().optional()
1040
- }).optional();
1041
- var ApiResponseSchema = import_zod4.z.object({
1042
- status: import_zod4.z.number(),
1043
- statusText: import_zod4.z.string(),
1044
- headers: import_zod4.z.record(import_zod4.z.string()).optional(),
1045
- body: import_zod4.z.string().optional(),
1046
- duration: import_zod4.z.number()
1047
- }).optional();
1048
- var optionalNum = import_zod4.z.number().nullish().transform((v) => v ?? void 0);
1049
- var AuditItemSchema = import_zod4.z.object({
1050
- id: import_zod4.z.string(),
1051
- title: import_zod4.z.string(),
1052
- passed: import_zod4.z.boolean(),
1053
- // true = score === 1
1054
- partial: import_zod4.z.boolean().optional(),
1055
- // true = 0 < score < 1 (needs work)
1056
- na: import_zod4.z.boolean().optional()
1057
- // true = score === null (not applicable)
1058
- });
1059
- var PageLoadMetricSchema = import_zod4.z.object({
1060
- url: import_zod4.z.string(),
1061
- stepIndex: import_zod4.z.number().int(),
1062
- stepInstruction: import_zod4.z.string(),
1063
- score: optionalNum,
1064
- // Performance score 0-100 (pre-multiplied in runner)
1065
- a11yScore: optionalNum,
1066
- // Accessibility score 0-100 (pre-multiplied in runner)
1067
- seoScore: optionalNum,
1068
- // SEO score 0-100 (pre-multiplied in runner)
1069
- fcp: optionalNum,
1070
- // First Contentful Paint (ms)
1071
- lcp: optionalNum,
1072
- // Largest Contentful Paint (ms)
1073
- cls: optionalNum,
1074
- // Cumulative Layout Shift (score)
1075
- ttfb: optionalNum,
1076
- // Time to First Byte (ms)
1077
- tbt: optionalNum,
1078
- // Total Blocking Time (ms)
1079
- si: optionalNum,
1080
- // Speed Index (ms)
1081
- tti: optionalNum,
1082
- // Time to Interactive (ms)
1083
- inp: optionalNum,
1084
- // Interaction to Next Paint (ms)
1085
- // Individual audit items for Accessibility and SEO categories
1086
- a11yAudits: import_zod4.z.array(AuditItemSchema).optional(),
1087
- seoAudits: import_zod4.z.array(AuditItemSchema).optional(),
1088
- lighthouseError: import_zod4.z.string().optional()
1089
- });
1090
- var StepResultSchema = import_zod4.z.object({
1091
- stepId: import_zod4.z.string(),
1092
- instruction: import_zod4.z.string(),
1093
- status: RunStatusSchema,
1094
- code: import_zod4.z.string(),
1095
- error: import_zod4.z.string().optional(),
1096
- duration: import_zod4.z.number(),
1097
- screenshotPath: import_zod4.z.string().optional(),
1098
- healed: import_zod4.z.boolean().optional(),
1099
- healedCode: import_zod4.z.string().optional(),
1100
- stepType: import_zod4.z.enum(["ui", "api", "mock"]).optional(),
1101
- apiRequest: ApiRequestSchema,
1102
- apiResponse: ApiResponseSchema,
1103
- navigatedToUrl: import_zod4.z.string().optional(),
1104
- auditUrl: import_zod4.z.string().optional()
1105
- // URL captured when step has runAudit=true (may not have navigated)
1106
- });
1107
- var TestCaseResultSchema = import_zod4.z.object({
1108
- caseId: import_zod4.z.string(),
1109
- caseName: import_zod4.z.string(),
1110
- status: RunStatusSchema,
1111
- steps: import_zod4.z.array(StepResultSchema),
1112
- duration: import_zod4.z.number(),
1113
- browser: import_zod4.z.string(),
1114
- /** Playwright device descriptor used for this case (undefined = no emulation). */
1115
- device: import_zod4.z.string().optional(),
1116
- startedAt: import_zod4.z.string().datetime(),
1117
- finishedAt: import_zod4.z.string().datetime(),
1118
- videoPath: import_zod4.z.string().optional(),
1119
- tracePath: import_zod4.z.string().optional(),
1120
- dataRowIndex: import_zod4.z.number().int().optional(),
1121
- dataRow: import_zod4.z.record(import_zod4.z.string()).optional(),
1122
- pageLoads: import_zod4.z.array(PageLoadMetricSchema).default([])
1123
- });
1124
- var SuiteResultSchema = import_zod4.z.object({
1125
- suiteId: import_zod4.z.string(),
1126
- suiteName: import_zod4.z.string(),
1127
- suiteType: import_zod4.z.enum(["ui", "api", "audit", "performance"]).optional(),
1128
- status: RunStatusSchema,
1129
- cases: import_zod4.z.array(TestCaseResultSchema),
1130
- duration: import_zod4.z.number(),
1131
- browser: import_zod4.z.string(),
1132
- startedAt: import_zod4.z.string().datetime(),
1133
- finishedAt: import_zod4.z.string().datetime()
1134
- });
1135
- var RunResultSchema = import_zod4.z.object({
1136
- runId: import_zod4.z.string(),
1137
- status: RunStatusSchema,
1138
- environment: import_zod4.z.string().optional(),
1139
- suites: import_zod4.z.array(SuiteResultSchema),
1140
- duration: import_zod4.z.number(),
1141
- startedAt: import_zod4.z.string().datetime(),
1142
- finishedAt: import_zod4.z.string().datetime(),
1143
- totalTests: import_zod4.z.number().int(),
1144
- passed: import_zod4.z.number().int(),
1145
- failed: import_zod4.z.number().int(),
1146
- skipped: import_zod4.z.number().int(),
1147
- logFilePath: import_zod4.z.string().optional()
1148
- });
1149
-
1150
- // src/storage/result-store.ts
1151
- var logger6 = createChildLogger("result-store");
1152
- var RESULTS_DIR = "results";
1153
- var RUNS_DIR = "runs";
1154
- function runFilePath(resultsDir, runId) {
1155
- return import_path6.default.join(resultsDir, RUNS_DIR, `${runId}.json`);
1156
- }
1157
- async function writeResult(rootDir, result) {
1158
- const schema = RunResultSchema.safeParse(result);
1159
- if (!schema.success) {
1160
- const issues = schema.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
1161
- throw new ValidationError(
1162
- `Cannot write invalid run result:
1163
- ${issues}`,
1164
- "result",
1165
- "INVALID_RESULT"
1166
- );
1167
- }
1168
- const filePath = runFilePath(import_path6.default.join(rootDir, RESULTS_DIR), result.runId);
1169
- await atomicWriteJson(filePath, schema.data);
1170
- logger6.info({ runId: result.runId, status: result.status }, "Run result saved");
1171
- }
1172
- async function readResult(rootDir, runId) {
1173
- const filePath = runFilePath(import_path6.default.join(rootDir, RESULTS_DIR), runId);
1174
- if (!await import_fs_extra7.default.pathExists(filePath)) {
1175
- throw new StorageError(
1176
- `Run result not found for runId "${runId}".`,
1177
- filePath,
1178
- "RESULT_NOT_FOUND"
1179
- );
1180
- }
1181
- const raw = await readJson(filePath);
1182
- const result = RunResultSchema.safeParse(raw);
1183
- if (!result.success) {
1184
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
1185
- throw new ValidationError(
1186
- `Invalid run result at "${filePath}":
1187
- ${issues}`,
1188
- "result",
1189
- "INVALID_RESULT"
1190
- );
1191
- }
1192
- return result.data;
1193
- }
1194
- async function listResultIds(rootDir) {
1195
- const runsDir = import_path6.default.join(rootDir, RESULTS_DIR, RUNS_DIR);
1196
- if (!await import_fs_extra7.default.pathExists(runsDir)) return [];
1197
- const entries = await import_fs_extra7.default.readdir(runsDir, { withFileTypes: true });
1198
- const ids = entries.filter((e) => e.isFile() && e.name.endsWith(".json")).map((e) => e.name.replace(".json", ""));
1199
- const withStats = await Promise.all(
1200
- ids.map(async (id) => {
1201
- const stat = await import_fs_extra7.default.stat(import_path6.default.join(runsDir, `${id}.json`));
1202
- return { id, mtime: stat.mtimeMs };
1203
- })
1204
- );
1205
- return withStats.sort((a, b) => b.mtime - a.mtime).map((x) => x.id);
1206
- }
1207
- async function listResults(rootDir, limit = 20) {
1208
- const ids = await listResultIds(rootDir);
1209
- const results = [];
1210
- for (const id of ids.slice(0, limit)) {
1211
- try {
1212
- const result = await readResult(rootDir, id);
1213
- results.push({
1214
- runId: result.runId,
1215
- status: result.status,
1216
- environment: result.environment,
1217
- startedAt: result.startedAt,
1218
- finishedAt: result.finishedAt,
1219
- totalTests: result.totalTests,
1220
- passed: result.passed,
1221
- failed: result.failed,
1222
- skipped: result.skipped,
1223
- duration: result.duration,
1224
- suiteIds: result.suites.map((s) => s.suiteId)
1225
- });
1226
- } catch (err) {
1227
- logger6.warn({ runId: id, err }, "Failed to read run result \u2014 skipping");
1228
- }
1229
- }
1230
- return results;
1231
- }
1232
- async function deleteResult(rootDir, runId) {
1233
- const filePath = runFilePath(import_path6.default.join(rootDir, RESULTS_DIR), runId);
1234
- if (!await import_fs_extra7.default.pathExists(filePath)) {
1235
- throw new StorageError(
1236
- `Run result not found for runId "${runId}".`,
1237
- filePath,
1238
- "RESULT_NOT_FOUND"
1239
- );
1240
- }
1241
- await import_fs_extra7.default.remove(filePath);
1242
- logger6.info({ runId }, "Run result deleted");
1243
- }
1244
- function screenshotsDir(rootDir) {
1245
- return import_path6.default.join(rootDir, RESULTS_DIR, "screenshots");
1246
- }
1247
- function videosDir(rootDir) {
1248
- return import_path6.default.join(rootDir, RESULTS_DIR, "videos");
1249
- }
1250
- function tracesDir(rootDir) {
1251
- return import_path6.default.join(rootDir, RESULTS_DIR, "traces");
1252
- }
1253
-
1254
- // src/storage/healing-store.ts
1255
- var import_path7 = __toESM(require("path"));
1256
- var import_fs_extra8 = __toESM(require("fs-extra"));
1257
-
1258
- // src/types/healing.ts
1259
- var import_zod5 = require("zod");
1260
- var HealingStatusSchema = import_zod5.z.enum(["pending", "accepted", "rejected"]);
1261
- var HealingStrategySchema = import_zod5.z.enum([
1262
- "retry",
1263
- "regenerate",
1264
- "multi-selector",
1265
- "visual",
1266
- "decompose",
1267
- "manual"
1268
- ]);
1269
- var HealingEventSchema = import_zod5.z.object({
1270
- id: import_zod5.z.string(),
1271
- runId: import_zod5.z.string(),
1272
- suiteId: import_zod5.z.string(),
1273
- caseId: import_zod5.z.string(),
1274
- stepId: import_zod5.z.string(),
1275
- stepInstruction: import_zod5.z.string(),
1276
- failedCode: import_zod5.z.string(),
1277
- healedCode: import_zod5.z.string(),
1278
- error: import_zod5.z.string(),
1279
- strategy: HealingStrategySchema,
1280
- level: import_zod5.z.number().int().min(1).max(6),
1281
- status: HealingStatusSchema,
1282
- pageUrl: import_zod5.z.string(),
1283
- timestamp: import_zod5.z.string().datetime(),
1284
- acceptedAt: import_zod5.z.string().datetime().optional(),
1285
- rejectedAt: import_zod5.z.string().datetime().optional()
1286
- });
1287
- var HealingReportSchema = import_zod5.z.object({
1288
- runId: import_zod5.z.string(),
1289
- generatedAt: import_zod5.z.string().datetime(),
1290
- totalHeals: import_zod5.z.number().int(),
1291
- accepted: import_zod5.z.number().int(),
1292
- rejected: import_zod5.z.number().int(),
1293
- pending: import_zod5.z.number().int(),
1294
- events: import_zod5.z.array(HealingEventSchema)
1295
- });
1296
-
1297
- // src/storage/healing-store.ts
1298
- var logger7 = createChildLogger("healing-store");
1299
- var HEALING_DIR = import_path7.default.join("results", "healing");
1300
- var PENDING_FILE = "pending.json";
1301
- var REPORT_PREFIX = "healing-report-";
1302
- function reportFilePath(healingDir, runId) {
1303
- return import_path7.default.join(healingDir, `${REPORT_PREFIX}${runId}.json`);
1304
- }
1305
- function pendingFilePath(healingDir) {
1306
- return import_path7.default.join(healingDir, PENDING_FILE);
1307
- }
1308
- async function writeHealingReport(rootDir, report) {
1309
- const schema = HealingReportSchema.safeParse(report);
1310
- if (!schema.success) {
1311
- const issues = schema.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
1312
- throw new ValidationError(
1313
- `Cannot write invalid healing report:
1314
- ${issues}`,
1315
- "healingReport",
1316
- "INVALID_HEALING_REPORT"
1317
- );
1318
- }
1319
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1320
- const filePath = reportFilePath(healingDir, report.runId);
1321
- await atomicWriteJson(filePath, schema.data);
1322
- logger7.info({ runId: report.runId, totalHeals: report.totalHeals }, "Healing report saved");
1323
- }
1324
- async function readHealingReport(rootDir, runId) {
1325
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1326
- const filePath = reportFilePath(healingDir, runId);
1327
- if (!await import_fs_extra8.default.pathExists(filePath)) {
1328
- throw new StorageError(
1329
- `Healing report not found for runId "${runId}".`,
1330
- filePath,
1331
- "HEALING_REPORT_NOT_FOUND"
1332
- );
1333
- }
1334
- const raw = await readJson(filePath);
1335
- const result = HealingReportSchema.safeParse(raw);
1336
- if (!result.success) {
1337
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
1338
- throw new ValidationError(
1339
- `Invalid healing report at "${filePath}":
1340
- ${issues}`,
1341
- "healingReport",
1342
- "INVALID_HEALING_REPORT"
1343
- );
1344
- }
1345
- return result.data;
1346
- }
1347
- async function readPendingEvents(rootDir) {
1348
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1349
- const filePath = pendingFilePath(healingDir);
1350
- if (!await import_fs_extra8.default.pathExists(filePath)) return [];
1351
- const raw = await readJson(filePath);
1352
- const result = HealingEventSchema.array().safeParse(raw);
1353
- if (!result.success) {
1354
- logger7.warn({ path: filePath }, "Pending healing file is malformed \u2014 resetting");
1355
- return [];
1356
- }
1357
- return result.data;
1358
- }
1359
- var MAX_HEALING_EVENTS = 50;
1360
- var PRUNE_PRIORITY = ["pending", "rejected", "accepted"];
1361
- function pruneToLimit(events) {
1362
- if (events.length <= MAX_HEALING_EVENTS) return events;
1363
- const byPriority = [...events].sort((a, b) => {
1364
- const pa = PRUNE_PRIORITY.indexOf(a.status);
1365
- const pb = PRUNE_PRIORITY.indexOf(b.status);
1366
- if (pa !== pb) return pa - pb;
1367
- return a.timestamp < b.timestamp ? -1 : 1;
1368
- });
1369
- const excess = events.length - MAX_HEALING_EVENTS;
1370
- const toDelete = new Set(byPriority.slice(0, excess).map((e) => e.id));
1371
- logger7.info(
1372
- { excess, deleted: toDelete.size },
1373
- "Auto-pruning healing events to enforce cap"
1374
- );
1375
- return events.filter((e) => !toDelete.has(e.id));
1376
- }
1377
- async function appendPendingEvent(rootDir, event) {
1378
- const schema = HealingEventSchema.safeParse(event);
1379
- if (!schema.success) {
1380
- throw new ValidationError(
1381
- `Invalid healing event: ${schema.error.message}`,
1382
- "healingEvent",
1383
- "INVALID_HEALING_EVENT"
1384
- );
1385
- }
1386
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1387
- await import_fs_extra8.default.ensureDir(healingDir);
1388
- const existing = await readPendingEvents(rootDir);
1389
- existing.push(schema.data);
1390
- await atomicWriteJson(pendingFilePath(healingDir), pruneToLimit(existing));
1391
- }
1392
- async function acceptHealingEvent(rootDir, eventId) {
1393
- const pending = await readPendingEvents(rootDir);
1394
- const idx = pending.findIndex((e) => e.id === eventId);
1395
- if (idx === -1) {
1396
- throw new StorageError(
1397
- `Healing event "${eventId}" not found in pending list.`,
1398
- eventId,
1399
- "HEALING_EVENT_NOT_FOUND"
1400
- );
1401
- }
1402
- pending[idx] = {
1403
- ...pending[idx],
1404
- status: "accepted",
1405
- acceptedAt: (/* @__PURE__ */ new Date()).toISOString()
1406
- };
1407
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1408
- await atomicWriteJson(pendingFilePath(healingDir), pending);
1409
- logger7.info({ eventId }, "Healing event accepted");
1410
- return pending[idx];
1411
- }
1412
- async function rejectHealingEvent(rootDir, eventId) {
1413
- const pending = await readPendingEvents(rootDir);
1414
- const idx = pending.findIndex((e) => e.id === eventId);
1415
- if (idx === -1) {
1416
- throw new StorageError(
1417
- `Healing event "${eventId}" not found in pending list.`,
1418
- eventId,
1419
- "HEALING_EVENT_NOT_FOUND"
1420
- );
1421
- }
1422
- pending[idx] = {
1423
- ...pending[idx],
1424
- status: "rejected",
1425
- rejectedAt: (/* @__PURE__ */ new Date()).toISOString()
1426
- };
1427
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1428
- await atomicWriteJson(pendingFilePath(healingDir), pending);
1429
- logger7.info({ eventId }, "Healing event rejected");
1430
- return pending[idx];
1431
- }
1432
- async function pruneResolvedEvents(rootDir, retentionDays = 30) {
1433
- const pending = await readPendingEvents(rootDir);
1434
- const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1e3;
1435
- const before = pending.length;
1436
- const kept = pending.filter((e) => {
1437
- if (e.status === "pending") return true;
1438
- const resolvedAt = e.acceptedAt ?? e.rejectedAt;
1439
- if (!resolvedAt) return true;
1440
- return new Date(resolvedAt).getTime() > cutoff;
1441
- });
1442
- if (kept.length < before) {
1443
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1444
- await atomicWriteJson(pendingFilePath(healingDir), kept);
1445
- }
1446
- return before - kept.length;
1447
- }
1448
- async function getHealingStats(rootDir) {
1449
- const events = await readPendingEvents(rootDir);
1450
- const pending = events.filter((e) => e.status === "pending").length;
1451
- const accepted = events.filter((e) => e.status === "accepted").length;
1452
- const rejected = events.filter((e) => e.status === "rejected").length;
1453
- return { pending, accepted, rejected, total: events.length };
1454
- }
1455
- async function listHealingReportIds(rootDir) {
1456
- const healingDir = import_path7.default.join(rootDir, HEALING_DIR);
1457
- if (!await import_fs_extra8.default.pathExists(healingDir)) return [];
1458
- const entries = await import_fs_extra8.default.readdir(healingDir, { withFileTypes: true });
1459
- const ids = entries.filter((e) => e.isFile() && e.name.startsWith(REPORT_PREFIX) && e.name.endsWith(".json")).map((e) => e.name.slice(REPORT_PREFIX.length, -".json".length));
1460
- const withStats = await Promise.all(
1461
- ids.map(async (id) => {
1462
- const stat = await import_fs_extra8.default.stat(import_path7.default.join(healingDir, `${REPORT_PREFIX}${id}.json`));
1463
- return { id, mtime: stat.mtimeMs };
1464
- })
1465
- );
1466
- return withStats.sort((a, b) => b.mtime - a.mtime).map((x) => x.id);
1467
- }
1468
-
1469
- // src/utils/hash.ts
1470
- var import_crypto = require("crypto");
1471
- function sha256(input) {
1472
- return (0, import_crypto.createHash)("sha256").update(input, "utf8").digest("hex");
1473
- }
1474
- function normalizeUrl(url) {
1475
- try {
1476
- const parsed = new URL(url);
1477
- const normalizedPath = parsed.pathname.split("/").map((segment) => {
1478
- if (/^\d+$/.test(segment)) return "*";
1479
- if (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(segment)) {
1480
- return "*";
1481
- }
1482
- if (/^[a-z0-9-]{8,}$/i.test(segment) && /-/.test(segment)) return "*";
1483
- return segment;
1484
- }).join("/");
1485
- return normalizedPath;
1486
- } catch {
1487
- return url;
1488
- }
1489
- }
1490
- function generateCacheKey(instruction, url) {
1491
- const normalizedInstruction = instruction.toLowerCase().replace(/\s+/g, " ").trim();
1492
- const urlPattern = normalizeUrl(url);
1493
- return sha256(`${normalizedInstruction}|${urlPattern}`).slice(0, 16);
1494
- }
1495
-
1496
- // src/utils/env.ts
1497
- var import_dotenv = require("dotenv");
1498
- var import_zod6 = require("zod");
1499
- (0, import_dotenv.config)();
1500
- var AI_PROVIDERS = [
1501
- "anthropic",
1502
- "openai",
1503
- "google",
1504
- "azure-openai",
1505
- "bedrock",
1506
- "deepseek",
1507
- "groq",
1508
- "together",
1509
- "qwen",
1510
- "perplexity",
1511
- "ollama",
1512
- "custom"
1513
- ];
1514
- var BaseEnvSchema = import_zod6.z.object({
1515
- NODE_ENV: import_zod6.z.enum(["development", "production", "test"]).default("development"),
1516
- LOG_LEVEL: import_zod6.z.enum(["trace", "debug", "info", "warn", "error", "fatal"]).default("info"),
1517
- AI_PROVIDER: import_zod6.z.enum(AI_PROVIDERS),
1518
- AI_TIERED_ENABLED: import_zod6.z.string().transform((v) => v === "true").default("false"),
1519
- AI_TIERED_FAST_PROVIDER: import_zod6.z.enum(AI_PROVIDERS).optional(),
1520
- AI_TIERED_FAST_MODEL: import_zod6.z.string().optional(),
1521
- AI_TIMEOUT: import_zod6.z.coerce.number().int().positive().default(30),
1522
- AI_MAX_RETRIES: import_zod6.z.coerce.number().int().min(0).max(10).default(2)
1523
- });
1524
- var AnthropicEnvSchema = import_zod6.z.object({
1525
- ANTHROPIC_API_KEY: import_zod6.z.string().min(1),
1526
- ANTHROPIC_MODEL: import_zod6.z.string().default("claude-sonnet-4-20250514")
1527
- });
1528
- var OpenAIEnvSchema = import_zod6.z.object({
1529
- OPENAI_API_KEY: import_zod6.z.string().min(1),
1530
- OPENAI_MODEL: import_zod6.z.string().default("gpt-4o")
1531
- });
1532
- var GoogleEnvSchema = import_zod6.z.object({
1533
- GOOGLE_API_KEY: import_zod6.z.string().min(1),
1534
- GOOGLE_MODEL: import_zod6.z.string().default("gemini-2.5-pro")
1535
- });
1536
- var AzureOpenAIEnvSchema = import_zod6.z.object({
1537
- AZURE_OPENAI_API_KEY: import_zod6.z.string().min(1),
1538
- AZURE_OPENAI_ENDPOINT: import_zod6.z.string().url(),
1539
- AZURE_OPENAI_DEPLOYMENT: import_zod6.z.string().min(1),
1540
- AZURE_OPENAI_API_VERSION: import_zod6.z.string().default("2024-10-21")
1541
- });
1542
- var BedrockEnvSchema = import_zod6.z.object({
1543
- AWS_ACCESS_KEY_ID: import_zod6.z.string().min(1),
1544
- AWS_SECRET_ACCESS_KEY: import_zod6.z.string().min(1),
1545
- AWS_SESSION_TOKEN: import_zod6.z.string().optional(),
1546
- AWS_REGION: import_zod6.z.string().default("us-east-1"),
1547
- BEDROCK_MODEL: import_zod6.z.string().default("anthropic.claude-sonnet-4-20250514-v1:0")
1548
- });
1549
- var DeepSeekEnvSchema = import_zod6.z.object({
1550
- DEEPSEEK_API_KEY: import_zod6.z.string().min(1),
1551
- DEEPSEEK_MODEL: import_zod6.z.string().default("deepseek-chat")
1552
- });
1553
- var GroqEnvSchema = import_zod6.z.object({
1554
- GROQ_API_KEY: import_zod6.z.string().min(1),
1555
- GROQ_MODEL: import_zod6.z.string().default("llama-3.3-70b-versatile")
1556
- });
1557
- var TogetherEnvSchema = import_zod6.z.object({
1558
- TOGETHER_API_KEY: import_zod6.z.string().min(1),
1559
- TOGETHER_MODEL: import_zod6.z.string().default("meta-llama/Llama-3.3-70B-Instruct-Turbo")
1560
- });
1561
- var QwenEnvSchema = import_zod6.z.object({
1562
- QWEN_API_KEY: import_zod6.z.string().min(1),
1563
- QWEN_BASE_URL: import_zod6.z.string().url().default("https://dashscope-intl.aliyuncs.com/compatible-mode/v1"),
1564
- QWEN_MODEL: import_zod6.z.string().default("qwen-max")
1565
- });
1566
- var PerplexityEnvSchema = import_zod6.z.object({
1567
- PERPLEXITY_API_KEY: import_zod6.z.string().min(1),
1568
- PERPLEXITY_MODEL: import_zod6.z.string().default("sonar-pro")
1569
- });
1570
- var OllamaEnvSchema = import_zod6.z.object({
1571
- OLLAMA_BASE_URL: import_zod6.z.string().url().default("http://localhost:11434"),
1572
- OLLAMA_MODEL: import_zod6.z.string().default("llama3.3")
1573
- });
1574
- var CustomEnvSchema = import_zod6.z.object({
1575
- CUSTOM_API_KEY: import_zod6.z.string().min(1),
1576
- CUSTOM_BASE_URL: import_zod6.z.string().url(),
1577
- CUSTOM_MODEL: import_zod6.z.string().min(1)
1578
- });
1579
- var PROVIDER_SCHEMAS = {
1580
- anthropic: AnthropicEnvSchema,
1581
- openai: OpenAIEnvSchema,
1582
- google: GoogleEnvSchema,
1583
- "azure-openai": AzureOpenAIEnvSchema,
1584
- bedrock: BedrockEnvSchema,
1585
- deepseek: DeepSeekEnvSchema,
1586
- groq: GroqEnvSchema,
1587
- together: TogetherEnvSchema,
1588
- qwen: QwenEnvSchema,
1589
- perplexity: PerplexityEnvSchema,
1590
- ollama: OllamaEnvSchema,
1591
- custom: CustomEnvSchema
1592
- };
1593
- var _cachedEnv = null;
1594
- function validateEnv() {
1595
- if (_cachedEnv !== null) return _cachedEnv;
1596
- const baseResult = BaseEnvSchema.safeParse(process.env);
1597
- if (!baseResult.success) {
1598
- const issues = baseResult.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
1599
- throw new ConfigError(
1600
- `Missing or invalid environment variables:
1601
- ${issues}
1602
-
1603
- How to fix: Copy .env.example to .env and fill in your AI provider credentials.`,
1604
- "ENV_VALIDATION_FAILED"
1605
- );
1606
- }
1607
- const providerName = baseResult.data.AI_PROVIDER;
1608
- const providerSchema = PROVIDER_SCHEMAS[providerName];
1609
- const providerResult = providerSchema.safeParse(process.env);
1610
- if (!providerResult.success) {
1611
- const issues = providerResult.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
1612
- throw new ConfigError(
1613
- `Provider "${providerName}" is missing required environment variables:
1614
- ${issues}
1615
-
1616
- How to fix: Check .env.example for the ${providerName} section and add the required keys to .env.`,
1617
- "PROVIDER_ENV_MISSING"
1618
- );
1619
- }
1620
- const merged = {
1621
- ...baseResult.data,
1622
- ...providerResult.data
1623
- };
1624
- _cachedEnv = merged;
1625
- return _cachedEnv;
1626
- }
1627
- function clearEnvCache() {
1628
- _cachedEnv = null;
1629
- }
1630
- function tryGetEnv() {
1631
- try {
1632
- return validateEnv();
1633
- } catch {
1634
- return null;
1635
- }
1636
- }
1637
-
1638
- // src/engine/data-generator.ts
1639
- var import_faker = require("@faker-js/faker");
1640
- function getAvailableGenerators() {
1641
- return [
1642
- {
1643
- category: "person",
1644
- methods: [
1645
- { name: "fullName", example: import_faker.faker.person.fullName() },
1646
- { name: "firstName", example: import_faker.faker.person.firstName() },
1647
- { name: "lastName", example: import_faker.faker.person.lastName() },
1648
- { name: "middleName", example: import_faker.faker.person.middleName() },
1649
- { name: "sex", example: import_faker.faker.person.sex() },
1650
- { name: "prefix", example: import_faker.faker.person.prefix() },
1651
- { name: "suffix", example: import_faker.faker.person.suffix() },
1652
- { name: "jobTitle", example: import_faker.faker.person.jobTitle() },
1653
- { name: "jobArea", example: import_faker.faker.person.jobArea() },
1654
- { name: "jobType", example: import_faker.faker.person.jobType() },
1655
- { name: "bio", example: import_faker.faker.person.bio() }
1656
- ]
1657
- },
1658
- {
1659
- category: "internet",
1660
- methods: [
1661
- { name: "email", example: import_faker.faker.internet.email() },
1662
- { name: "userName", example: import_faker.faker.internet.username() },
1663
- { name: "password", example: import_faker.faker.internet.password() },
1664
- { name: "url", example: import_faker.faker.internet.url() },
1665
- { name: "domainName", example: import_faker.faker.internet.domainName() },
1666
- { name: "domainWord", example: import_faker.faker.internet.domainWord() },
1667
- { name: "ip", example: import_faker.faker.internet.ip() },
1668
- { name: "ipv6", example: import_faker.faker.internet.ipv6() },
1669
- { name: "mac", example: import_faker.faker.internet.mac() },
1670
- { name: "userAgent", example: import_faker.faker.internet.userAgent() },
1671
- { name: "emoji", example: import_faker.faker.internet.emoji() }
1672
- ]
1673
- },
1674
- {
1675
- category: "phone",
1676
- methods: [
1677
- { name: "number", example: import_faker.faker.phone.number() },
1678
- { name: "imei", example: import_faker.faker.phone.imei() }
1679
- ]
1680
- },
1681
- {
1682
- category: "location",
1683
- methods: [
1684
- { name: "streetAddress", example: import_faker.faker.location.streetAddress() },
1685
- { name: "street", example: import_faker.faker.location.street() },
1686
- { name: "city", example: import_faker.faker.location.city() },
1687
- { name: "state", example: import_faker.faker.location.state() },
1688
- { name: "zipCode", example: import_faker.faker.location.zipCode() },
1689
- { name: "country", example: import_faker.faker.location.country() },
1690
- { name: "countryCode", example: import_faker.faker.location.countryCode() },
1691
- { name: "county", example: import_faker.faker.location.county() },
1692
- { name: "latitude", example: String(import_faker.faker.location.latitude()) },
1693
- { name: "longitude", example: String(import_faker.faker.location.longitude()) },
1694
- { name: "timeZone", example: import_faker.faker.location.timeZone() },
1695
- { name: "buildingNumber", example: import_faker.faker.location.buildingNumber() }
1696
- ]
1697
- },
1698
- {
1699
- category: "company",
1700
- methods: [
1701
- { name: "name", example: import_faker.faker.company.name() },
1702
- { name: "catchPhrase", example: import_faker.faker.company.catchPhrase() },
1703
- { name: "buzzPhrase", example: import_faker.faker.company.buzzPhrase() },
1704
- { name: "buzzNoun", example: import_faker.faker.company.buzzNoun() },
1705
- { name: "buzzVerb", example: import_faker.faker.company.buzzVerb() },
1706
- { name: "buzzAdjective", example: import_faker.faker.company.buzzAdjective() }
1707
- ]
1708
- },
1709
- {
1710
- category: "finance",
1711
- methods: [
1712
- { name: "accountNumber", example: import_faker.faker.finance.accountNumber() },
1713
- { name: "accountName", example: import_faker.faker.finance.accountName() },
1714
- { name: "creditCardNumber", example: import_faker.faker.finance.creditCardNumber() },
1715
- { name: "creditCardCVV", example: import_faker.faker.finance.creditCardCVV() },
1716
- { name: "currencyCode", example: import_faker.faker.finance.currencyCode() },
1717
- { name: "currencyName", example: import_faker.faker.finance.currencyName() },
1718
- { name: "amount", example: import_faker.faker.finance.amount() },
1719
- { name: "iban", example: import_faker.faker.finance.iban() },
1720
- { name: "bic", example: import_faker.faker.finance.bic() },
1721
- { name: "bitcoinAddress", example: import_faker.faker.finance.bitcoinAddress() },
1722
- { name: "transactionType", example: import_faker.faker.finance.transactionType() }
1723
- ]
1724
- },
1725
- {
1726
- category: "date",
1727
- methods: [
1728
- { name: "past", example: import_faker.faker.date.past().toISOString() },
1729
- { name: "future", example: import_faker.faker.date.future().toISOString() },
1730
- { name: "recent", example: import_faker.faker.date.recent().toISOString() },
1731
- { name: "soon", example: import_faker.faker.date.soon().toISOString() },
1732
- { name: "birthdate", example: import_faker.faker.date.birthdate().toISOString() },
1733
- { name: "month", example: import_faker.faker.date.month() },
1734
- { name: "weekday", example: import_faker.faker.date.weekday() }
1735
- ]
1736
- },
1737
- {
1738
- category: "lorem",
1739
- methods: [
1740
- { name: "word", example: import_faker.faker.lorem.word() },
1741
- { name: "words", example: import_faker.faker.lorem.words() },
1742
- { name: "sentence", example: import_faker.faker.lorem.sentence() },
1743
- { name: "sentences", example: import_faker.faker.lorem.sentences() },
1744
- { name: "paragraph", example: import_faker.faker.lorem.paragraph() },
1745
- { name: "slug", example: import_faker.faker.lorem.slug() },
1746
- { name: "lines", example: import_faker.faker.lorem.lines(1) }
1747
- ]
1748
- },
1749
- {
1750
- category: "string",
1751
- methods: [
1752
- { name: "uuid", example: import_faker.faker.string.uuid() },
1753
- { name: "alphanumeric", example: import_faker.faker.string.alphanumeric(10) },
1754
- { name: "numeric", example: import_faker.faker.string.numeric(6) },
1755
- { name: "alpha", example: import_faker.faker.string.alpha(8) },
1756
- { name: "nanoid", example: import_faker.faker.string.nanoid() },
1757
- { name: "hexadecimal", example: import_faker.faker.string.hexadecimal({ length: 8 }) },
1758
- { name: "sample", example: import_faker.faker.string.sample(12) }
1759
- ]
1760
- },
1761
- {
1762
- category: "number",
1763
- methods: [
1764
- { name: "int", example: String(import_faker.faker.number.int({ max: 9999 })) },
1765
- { name: "float", example: String(import_faker.faker.number.float({ max: 100, fractionDigits: 2 })) }
1766
- ]
1767
- },
1768
- {
1769
- category: "datatype",
1770
- methods: [
1771
- { name: "boolean", example: String(import_faker.faker.datatype.boolean()) }
1772
- ]
1773
- },
1774
- {
1775
- category: "image",
1776
- methods: [
1777
- { name: "avatar", example: import_faker.faker.image.avatar() },
1778
- { name: "url", example: import_faker.faker.image.url() }
1779
- ]
1780
- },
1781
- {
1782
- category: "color",
1783
- methods: [
1784
- { name: "human", example: import_faker.faker.color.human() },
1785
- { name: "rgb", example: import_faker.faker.color.rgb() },
1786
- { name: "hex", example: import_faker.faker.color.rgb({ format: "hex" }) }
1787
- ]
1788
- },
1789
- {
1790
- category: "commerce",
1791
- methods: [
1792
- { name: "productName", example: import_faker.faker.commerce.productName() },
1793
- { name: "price", example: import_faker.faker.commerce.price() },
1794
- { name: "department", example: import_faker.faker.commerce.department() },
1795
- { name: "productDescription", example: import_faker.faker.commerce.productDescription().slice(0, 60) + "..." },
1796
- { name: "isbn", example: import_faker.faker.commerce.isbn() }
1797
- ]
1798
- },
1799
- {
1800
- category: "vehicle",
1801
- methods: [
1802
- { name: "vehicle", example: import_faker.faker.vehicle.vehicle() },
1803
- { name: "manufacturer", example: import_faker.faker.vehicle.manufacturer() },
1804
- { name: "model", example: import_faker.faker.vehicle.model() },
1805
- { name: "vin", example: import_faker.faker.vehicle.vin() },
1806
- { name: "vrm", example: import_faker.faker.vehicle.vrm() }
1807
- ]
1808
- },
1809
- {
1810
- category: "system",
1811
- methods: [
1812
- { name: "fileName", example: import_faker.faker.system.fileName() },
1813
- { name: "mimeType", example: import_faker.faker.system.mimeType() },
1814
- { name: "fileExt", example: import_faker.faker.system.fileExt() },
1815
- { name: "filePath", example: import_faker.faker.system.filePath() },
1816
- { name: "semver", example: import_faker.faker.system.semver() }
1817
- ]
1818
- }
1819
- ];
1820
- }
1821
- var METHOD_ALIASES = {
1822
- "internet.userName": "username"
1823
- };
1824
- function callFaker(category, method) {
1825
- const fakerModule = import_faker.faker[category];
1826
- const resolvedMethod = METHOD_ALIASES[`${category}.${method}`] ?? method;
1827
- if (!fakerModule || typeof fakerModule[resolvedMethod] !== "function") {
1828
- return `[unknown: ${category}.${method}]`;
1829
- }
1830
- const result = fakerModule[resolvedMethod]();
1831
- if (result instanceof Date) return result.toISOString();
1832
- return String(result);
1833
- }
1834
-
1835
- // src/index.ts
1836
- function defineConfig(config) {
1837
- const merged = { ...DEFAULT_CONFIG, ...config };
1838
- const result = AutotestConfigSchema.safeParse(merged);
1839
- if (!result.success) {
1840
- const issues = result.error.issues.map((i) => ` \u2022 ${i.path.join(".")}: ${i.message}`).join("\n");
1841
- throw new Error(`Invalid assuremind config:
1842
- ${issues}`);
1843
- }
1844
- return result.data;
1845
- }
1846
- // Annotate the CommonJS export names for ESM import in node:
1847
- 0 && (module.exports = {
1848
- AssuremindError,
1849
- ConfigError,
1850
- ExecutionError,
1851
- HealingError,
1852
- ProviderError,
1853
- StorageError,
1854
- ValidationError,
1855
- acceptHealingEvent,
1856
- appendPendingEvent,
1857
- callFaker,
1858
- clearEnvCache,
1859
- configExists,
1860
- createCase,
1861
- createChildLogger,
1862
- createSuite,
1863
- defineConfig,
1864
- deleteCase,
1865
- deleteGlobalVariable,
1866
- deleteResult,
1867
- deleteSuite,
1868
- formatError,
1869
- generateCacheKey,
1870
- getAvailableGenerators,
1871
- getCasePath,
1872
- getHealingStats,
1873
- isAssuremindError,
1874
- listCasePaths,
1875
- listCases,
1876
- listHealingReportIds,
1877
- listResultIds,
1878
- listResults,
1879
- listSuiteDirs,
1880
- listSuites,
1881
- listSuitesWithCounts,
1882
- listVariableFiles,
1883
- logger,
1884
- normalizeUrl,
1885
- pruneResolvedEvents,
1886
- readCase,
1887
- readConfig,
1888
- readEnvVariables,
1889
- readGlobalVariables,
1890
- readHealingReport,
1891
- readPendingEvents,
1892
- readResult,
1893
- readSuite,
1894
- readVariables,
1895
- redactSecrets,
1896
- rejectHealingEvent,
1897
- resolveVariables,
1898
- sanitizeGeneratedCode,
1899
- screenshotsDir,
1900
- setGlobalVariable,
1901
- sha256,
1902
- toSlug,
1903
- tracesDir,
1904
- tryGetEnv,
1905
- updateCase,
1906
- updateConfig,
1907
- updateSuite,
1908
- validateConfig,
1909
- validateEnv,
1910
- videosDir,
1911
- writeCase,
1912
- writeConfig,
1913
- writeHealingReport,
1914
- writeResult,
1915
- writeSuite,
1916
- writeVariables
1917
- });
1918
- //# sourceMappingURL=index.js.map
42
+ studioPort: ${e.studioPort},
43
+ });
44
+ `}var j=h(require("path")),O=h(require("fs-extra"));var s=require("zod"),me=s.z.enum(["pending","running","passed","failed","skipped"]),yn=s.z.object({method:s.z.string(),url:s.z.string(),headers:s.z.record(s.z.string()).optional(),body:s.z.string().optional()}).optional(),En=s.z.object({status:s.z.number(),statusText:s.z.string(),headers:s.z.record(s.z.string()).optional(),body:s.z.string().optional(),duration:s.z.number()}).optional(),A=s.z.number().nullish().transform(e=>e??void 0),gt=s.z.object({id:s.z.string(),title:s.z.string(),passed:s.z.boolean(),partial:s.z.boolean().optional(),na:s.z.boolean().optional()}),wn=s.z.object({url:s.z.string(),stepIndex:s.z.number().int(),stepInstruction:s.z.string(),score:A,a11yScore:A,seoScore:A,fcp:A,lcp:A,cls:A,ttfb:A,tbt:A,si:A,tti:A,inp:A,a11yAudits:s.z.array(gt).optional(),seoAudits:s.z.array(gt).optional(),lighthouseError:s.z.string().optional()}),vn=s.z.object({stepId:s.z.string(),instruction:s.z.string(),status:me,code:s.z.string(),error:s.z.string().optional(),duration:s.z.number(),screenshotPath:s.z.string().optional(),healed:s.z.boolean().optional(),healedCode:s.z.string().optional(),stepType:s.z.enum(["ui","api","mock"]).optional(),apiRequest:yn,apiResponse:En,navigatedToUrl:s.z.string().optional(),auditUrl:s.z.string().optional()}),An=s.z.object({caseId:s.z.string(),caseName:s.z.string(),status:me,steps:s.z.array(vn),duration:s.z.number(),browser:s.z.string(),device:s.z.string().optional(),startedAt:s.z.string().datetime(),finishedAt:s.z.string().datetime(),videoPath:s.z.string().optional(),tracePath:s.z.string().optional(),dataRowIndex:s.z.number().int().optional(),dataRow:s.z.record(s.z.string()).optional(),pageLoads:s.z.array(wn).default([])}),Rn=s.z.object({suiteId:s.z.string(),suiteName:s.z.string(),suiteType:s.z.enum(["ui","api","audit","performance"]).optional(),status:me,cases:s.z.array(An),duration:s.z.number(),browser:s.z.string(),startedAt:s.z.string().datetime(),finishedAt:s.z.string().datetime()}),je=s.z.object({runId:s.z.string(),status:me,environment:s.z.string().optional(),suites:s.z.array(Rn),duration:s.z.number(),startedAt:s.z.string().datetime(),finishedAt:s.z.string().datetime(),totalTests:s.z.number().int(),passed:s.z.number().int(),failed:s.z.number().int(),skipped:s.z.number().int(),logFilePath:s.z.string().optional()});var Ce=b("result-store"),$="results",ft="runs";function Te(e,t){return j.default.join(e,ft,`${t}.json`)}async function ht(e,t){let n=je.safeParse(t);if(!n.success){let i=n.error.issues.map(a=>` \u2022 ${a.path.join(".")}: ${a.message}`).join(`
45
+ `);throw new u(`Cannot write invalid run result:
46
+ ${i}`,"result","INVALID_RESULT")}let r=Te(j.default.join(e,$),t.runId);await x(r,n.data),Ce.info({runId:t.runId,status:t.status},"Run result saved")}async function Pe(e,t){let n=Te(j.default.join(e,$),t);if(!await O.default.pathExists(n))throw new g(`Run result not found for runId "${t}".`,n,"RESULT_NOT_FOUND");let r=await E(n),i=je.safeParse(r);if(!i.success){let a=i.error.issues.map(p=>` \u2022 ${p.path.join(".")}: ${p.message}`).join(`
47
+ `);throw new u(`Invalid run result at "${n}":
48
+ ${a}`,"result","INVALID_RESULT")}return i.data}async function _e(e){let t=j.default.join(e,$,ft);if(!await O.default.pathExists(t))return[];let r=(await O.default.readdir(t,{withFileTypes:!0})).filter(a=>a.isFile()&&a.name.endsWith(".json")).map(a=>a.name.replace(".json",""));return(await Promise.all(r.map(async a=>{let p=await O.default.stat(j.default.join(t,`${a}.json`));return{id:a,mtime:p.mtimeMs}}))).sort((a,p)=>p.mtime-a.mtime).map(a=>a.id)}async function xt(e,t=20){let n=await _e(e),r=[];for(let i of n.slice(0,t))try{let a=await Pe(e,i);r.push({runId:a.runId,status:a.status,environment:a.environment,startedAt:a.startedAt,finishedAt:a.finishedAt,totalTests:a.totalTests,passed:a.passed,failed:a.failed,skipped:a.skipped,duration:a.duration,suiteIds:a.suites.map(p=>p.suiteId)})}catch(a){Ce.warn({runId:i,err:a},"Failed to read run result \u2014 skipping")}return r}async function bt(e,t){let n=Te(j.default.join(e,$),t);if(!await O.default.pathExists(n))throw new g(`Run result not found for runId "${t}".`,n,"RESULT_NOT_FOUND");await O.default.remove(n),Ce.info({runId:t},"Run result deleted")}function St(e){return j.default.join(e,$,"screenshots")}function yt(e){return j.default.join(e,$,"videos")}function Et(e){return j.default.join(e,$,"traces")}var w=h(require("path")),L=h(require("fs-extra"));var d=require("zod"),In=d.z.enum(["pending","accepted","rejected"]),jn=d.z.enum(["retry","regenerate","multi-selector","visual","decompose","manual"]),pe=d.z.object({id:d.z.string(),runId:d.z.string(),suiteId:d.z.string(),caseId:d.z.string(),stepId:d.z.string(),stepInstruction:d.z.string(),failedCode:d.z.string(),healedCode:d.z.string(),error:d.z.string(),strategy:jn,level:d.z.number().int().min(1).max(6),status:In,pageUrl:d.z.string(),timestamp:d.z.string().datetime(),acceptedAt:d.z.string().datetime().optional(),rejectedAt:d.z.string().datetime().optional()}),Ne=d.z.object({runId:d.z.string(),generatedAt:d.z.string().datetime(),totalHeals:d.z.number().int(),accepted:d.z.number().int(),rejected:d.z.number().int(),pending:d.z.number().int(),events:d.z.array(pe)});var W=b("healing-store"),_=w.default.join("results","healing"),Cn="pending.json",ue="healing-report-";function At(e,t){return w.default.join(e,`${ue}${t}.json`)}function J(e){return w.default.join(e,Cn)}async function Rt(e,t){let n=Ne.safeParse(t);if(!n.success){let a=n.error.issues.map(p=>` \u2022 ${p.path.join(".")}: ${p.message}`).join(`
49
+ `);throw new u(`Cannot write invalid healing report:
50
+ ${a}`,"healingReport","INVALID_HEALING_REPORT")}let r=w.default.join(e,_),i=At(r,t.runId);await x(i,n.data),W.info({runId:t.runId,totalHeals:t.totalHeals},"Healing report saved")}async function It(e,t){let n=w.default.join(e,_),r=At(n,t);if(!await L.default.pathExists(r))throw new g(`Healing report not found for runId "${t}".`,r,"HEALING_REPORT_NOT_FOUND");let i=await E(r),a=Ne.safeParse(i);if(!a.success){let p=a.error.issues.map(y=>` \u2022 ${y.path.join(".")}: ${y.message}`).join(`
51
+ `);throw new u(`Invalid healing report at "${r}":
52
+ ${p}`,"healingReport","INVALID_HEALING_REPORT")}return a.data}async function V(e){let t=w.default.join(e,_),n=J(t);if(!await L.default.pathExists(n))return[];let r=await E(n),i=pe.array().safeParse(r);return i.success?i.data:(W.warn({path:n},"Pending healing file is malformed \u2014 resetting"),[])}var wt=50,vt=["pending","rejected","accepted"];function Tn(e){if(e.length<=wt)return e;let t=[...e].sort((i,a)=>{let p=vt.indexOf(i.status),y=vt.indexOf(a.status);return p!==y?p-y:i.timestamp<a.timestamp?-1:1}),n=e.length-wt,r=new Set(t.slice(0,n).map(i=>i.id));return W.info({excess:n,deleted:r.size},"Auto-pruning healing events to enforce cap"),e.filter(i=>!r.has(i.id))}async function jt(e,t){let n=pe.safeParse(t);if(!n.success)throw new u(`Invalid healing event: ${n.error.message}`,"healingEvent","INVALID_HEALING_EVENT");let r=w.default.join(e,_);await L.default.ensureDir(r);let i=await V(e);i.push(n.data),await x(J(r),Tn(i))}async function Ct(e,t){let n=await V(e),r=n.findIndex(a=>a.id===t);if(r===-1)throw new g(`Healing event "${t}" not found in pending list.`,t,"HEALING_EVENT_NOT_FOUND");n[r]={...n[r],status:"accepted",acceptedAt:new Date().toISOString()};let i=w.default.join(e,_);return await x(J(i),n),W.info({eventId:t},"Healing event accepted"),n[r]}async function Tt(e,t){let n=await V(e),r=n.findIndex(a=>a.id===t);if(r===-1)throw new g(`Healing event "${t}" not found in pending list.`,t,"HEALING_EVENT_NOT_FOUND");n[r]={...n[r],status:"rejected",rejectedAt:new Date().toISOString()};let i=w.default.join(e,_);return await x(J(i),n),W.info({eventId:t},"Healing event rejected"),n[r]}async function Pt(e,t=30){let n=await V(e),r=Date.now()-t*24*60*60*1e3,i=n.length,a=n.filter(p=>{if(p.status==="pending")return!0;let y=p.acceptedAt??p.rejectedAt;return y?new Date(y).getTime()>r:!0});if(a.length<i){let p=w.default.join(e,_);await x(J(p),a)}return i-a.length}async function _t(e){let t=await V(e),n=t.filter(a=>a.status==="pending").length,r=t.filter(a=>a.status==="accepted").length,i=t.filter(a=>a.status==="rejected").length;return{pending:n,accepted:r,rejected:i,total:t.length}}async function Nt(e){let t=w.default.join(e,_);if(!await L.default.pathExists(t))return[];let r=(await L.default.readdir(t,{withFileTypes:!0})).filter(a=>a.isFile()&&a.name.startsWith(ue)&&a.name.endsWith(".json")).map(a=>a.name.slice(ue.length,-5));return(await Promise.all(r.map(async a=>{let p=await L.default.stat(w.default.join(t,`${ue}${a}.json`));return{id:a,mtime:p.mtimeMs}}))).sort((a,p)=>p.mtime-a.mtime).map(a=>a.id)}var Ot=require("crypto");function Oe(e){return(0,Ot.createHash)("sha256").update(e,"utf8").digest("hex")}function $e(e){try{return new URL(e).pathname.split("/").map(r=>/^\d+$/.test(r)||/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(r)||/^[a-z0-9-]{8,}$/i.test(r)&&/-/.test(r)?"*":r).join("/")}catch{return e}}function $t(e,t){let n=e.toLowerCase().replace(/\s+/g," ").trim(),r=$e(t);return Oe(`${n}|${r}`).slice(0,16)}var Vt=require("dotenv"),m=require("zod");(0,Vt.config)();var Lt=["anthropic","openai","google","azure-openai","bedrock","deepseek","groq","together","qwen","perplexity","ollama","custom"],Pn=m.z.object({NODE_ENV:m.z.enum(["development","production","test"]).default("development"),LOG_LEVEL:m.z.enum(["trace","debug","info","warn","error","fatal"]).default("info"),AI_PROVIDER:m.z.enum(Lt),AI_TIERED_ENABLED:m.z.string().transform(e=>e==="true").default("false"),AI_TIERED_FAST_PROVIDER:m.z.enum(Lt).optional(),AI_TIERED_FAST_MODEL:m.z.string().optional(),AI_TIMEOUT:m.z.coerce.number().int().positive().default(30),AI_MAX_RETRIES:m.z.coerce.number().int().min(0).max(10).default(2)}),_n=m.z.object({ANTHROPIC_API_KEY:m.z.string().min(1),ANTHROPIC_MODEL:m.z.string().default("claude-sonnet-4-20250514")}),Nn=m.z.object({OPENAI_API_KEY:m.z.string().min(1),OPENAI_MODEL:m.z.string().default("gpt-4o")}),On=m.z.object({GOOGLE_API_KEY:m.z.string().min(1),GOOGLE_MODEL:m.z.string().default("gemini-2.5-pro")}),$n=m.z.object({AZURE_OPENAI_API_KEY:m.z.string().min(1),AZURE_OPENAI_ENDPOINT:m.z.string().url(),AZURE_OPENAI_DEPLOYMENT:m.z.string().min(1),AZURE_OPENAI_API_VERSION:m.z.string().default("2024-10-21")}),Ln=m.z.object({AWS_ACCESS_KEY_ID:m.z.string().min(1),AWS_SECRET_ACCESS_KEY:m.z.string().min(1),AWS_SESSION_TOKEN:m.z.string().optional(),AWS_REGION:m.z.string().default("us-east-1"),BEDROCK_MODEL:m.z.string().default("anthropic.claude-sonnet-4-20250514-v1:0")}),Vn=m.z.object({DEEPSEEK_API_KEY:m.z.string().min(1),DEEPSEEK_MODEL:m.z.string().default("deepseek-chat")}),Dn=m.z.object({GROQ_API_KEY:m.z.string().min(1),GROQ_MODEL:m.z.string().default("llama-3.3-70b-versatile")}),Hn=m.z.object({TOGETHER_API_KEY:m.z.string().min(1),TOGETHER_MODEL:m.z.string().default("meta-llama/Llama-3.3-70B-Instruct-Turbo")}),Fn=m.z.object({QWEN_API_KEY:m.z.string().min(1),QWEN_BASE_URL:m.z.string().url().default("https://dashscope-intl.aliyuncs.com/compatible-mode/v1"),QWEN_MODEL:m.z.string().default("qwen-max")}),zn=m.z.object({PERPLEXITY_API_KEY:m.z.string().min(1),PERPLEXITY_MODEL:m.z.string().default("sonar-pro")}),Un=m.z.object({OLLAMA_BASE_URL:m.z.string().url().default("http://localhost:11434"),OLLAMA_MODEL:m.z.string().default("llama3.3")}),kn=m.z.object({CUSTOM_API_KEY:m.z.string().min(1),CUSTOM_BASE_URL:m.z.string().url(),CUSTOM_MODEL:m.z.string().min(1)}),Gn={anthropic:_n,openai:Nn,google:On,"azure-openai":$n,bedrock:Ln,deepseek:Vn,groq:Dn,together:Hn,qwen:Fn,perplexity:zn,ollama:Un,custom:kn},K=null;function Le(){if(K!==null)return K;let e=Pn.safeParse(process.env);if(!e.success){let a=e.error.issues.map(p=>` \u2022 ${p.path.join(".")}: ${p.message}`).join(`
53
+ `);throw new C(`Missing or invalid environment variables:
54
+ ${a}
55
+
56
+ How to fix: Copy .env.example to .env and fill in your AI provider credentials.`,"ENV_VALIDATION_FAILED")}let t=e.data.AI_PROVIDER,r=Gn[t].safeParse(process.env);if(!r.success){let a=r.error.issues.map(p=>` \u2022 ${p.path.join(".")}: ${p.message}`).join(`
57
+ `);throw new C(`Provider "${t}" is missing required environment variables:
58
+ ${a}
59
+
60
+ How to fix: Check .env.example for the ${t} section and add the required keys to .env.`,"PROVIDER_ENV_MISSING")}return K={...e.data,...r.data},K}function Dt(){K=null}function Ht(){try{return Le()}catch{return null}}var o=require("@faker-js/faker");function Ft(){return[{category:"person",methods:[{name:"fullName",example:o.faker.person.fullName()},{name:"firstName",example:o.faker.person.firstName()},{name:"lastName",example:o.faker.person.lastName()},{name:"middleName",example:o.faker.person.middleName()},{name:"sex",example:o.faker.person.sex()},{name:"prefix",example:o.faker.person.prefix()},{name:"suffix",example:o.faker.person.suffix()},{name:"jobTitle",example:o.faker.person.jobTitle()},{name:"jobArea",example:o.faker.person.jobArea()},{name:"jobType",example:o.faker.person.jobType()},{name:"bio",example:o.faker.person.bio()}]},{category:"internet",methods:[{name:"email",example:o.faker.internet.email()},{name:"userName",example:o.faker.internet.username()},{name:"password",example:o.faker.internet.password()},{name:"url",example:o.faker.internet.url()},{name:"domainName",example:o.faker.internet.domainName()},{name:"domainWord",example:o.faker.internet.domainWord()},{name:"ip",example:o.faker.internet.ip()},{name:"ipv6",example:o.faker.internet.ipv6()},{name:"mac",example:o.faker.internet.mac()},{name:"userAgent",example:o.faker.internet.userAgent()},{name:"emoji",example:o.faker.internet.emoji()}]},{category:"phone",methods:[{name:"number",example:o.faker.phone.number()},{name:"imei",example:o.faker.phone.imei()}]},{category:"location",methods:[{name:"streetAddress",example:o.faker.location.streetAddress()},{name:"street",example:o.faker.location.street()},{name:"city",example:o.faker.location.city()},{name:"state",example:o.faker.location.state()},{name:"zipCode",example:o.faker.location.zipCode()},{name:"country",example:o.faker.location.country()},{name:"countryCode",example:o.faker.location.countryCode()},{name:"county",example:o.faker.location.county()},{name:"latitude",example:String(o.faker.location.latitude())},{name:"longitude",example:String(o.faker.location.longitude())},{name:"timeZone",example:o.faker.location.timeZone()},{name:"buildingNumber",example:o.faker.location.buildingNumber()}]},{category:"company",methods:[{name:"name",example:o.faker.company.name()},{name:"catchPhrase",example:o.faker.company.catchPhrase()},{name:"buzzPhrase",example:o.faker.company.buzzPhrase()},{name:"buzzNoun",example:o.faker.company.buzzNoun()},{name:"buzzVerb",example:o.faker.company.buzzVerb()},{name:"buzzAdjective",example:o.faker.company.buzzAdjective()}]},{category:"finance",methods:[{name:"accountNumber",example:o.faker.finance.accountNumber()},{name:"accountName",example:o.faker.finance.accountName()},{name:"creditCardNumber",example:o.faker.finance.creditCardNumber()},{name:"creditCardCVV",example:o.faker.finance.creditCardCVV()},{name:"currencyCode",example:o.faker.finance.currencyCode()},{name:"currencyName",example:o.faker.finance.currencyName()},{name:"amount",example:o.faker.finance.amount()},{name:"iban",example:o.faker.finance.iban()},{name:"bic",example:o.faker.finance.bic()},{name:"bitcoinAddress",example:o.faker.finance.bitcoinAddress()},{name:"transactionType",example:o.faker.finance.transactionType()}]},{category:"date",methods:[{name:"past",example:o.faker.date.past().toISOString()},{name:"future",example:o.faker.date.future().toISOString()},{name:"recent",example:o.faker.date.recent().toISOString()},{name:"soon",example:o.faker.date.soon().toISOString()},{name:"birthdate",example:o.faker.date.birthdate().toISOString()},{name:"month",example:o.faker.date.month()},{name:"weekday",example:o.faker.date.weekday()}]},{category:"lorem",methods:[{name:"word",example:o.faker.lorem.word()},{name:"words",example:o.faker.lorem.words()},{name:"sentence",example:o.faker.lorem.sentence()},{name:"sentences",example:o.faker.lorem.sentences()},{name:"paragraph",example:o.faker.lorem.paragraph()},{name:"slug",example:o.faker.lorem.slug()},{name:"lines",example:o.faker.lorem.lines(1)}]},{category:"string",methods:[{name:"uuid",example:o.faker.string.uuid()},{name:"alphanumeric",example:o.faker.string.alphanumeric(10)},{name:"numeric",example:o.faker.string.numeric(6)},{name:"alpha",example:o.faker.string.alpha(8)},{name:"nanoid",example:o.faker.string.nanoid()},{name:"hexadecimal",example:o.faker.string.hexadecimal({length:8})},{name:"sample",example:o.faker.string.sample(12)}]},{category:"number",methods:[{name:"int",example:String(o.faker.number.int({max:9999}))},{name:"float",example:String(o.faker.number.float({max:100,fractionDigits:2}))}]},{category:"datatype",methods:[{name:"boolean",example:String(o.faker.datatype.boolean())}]},{category:"image",methods:[{name:"avatar",example:o.faker.image.avatar()},{name:"url",example:o.faker.image.url()}]},{category:"color",methods:[{name:"human",example:o.faker.color.human()},{name:"rgb",example:o.faker.color.rgb()},{name:"hex",example:o.faker.color.rgb({format:"hex"})}]},{category:"commerce",methods:[{name:"productName",example:o.faker.commerce.productName()},{name:"price",example:o.faker.commerce.price()},{name:"department",example:o.faker.commerce.department()},{name:"productDescription",example:o.faker.commerce.productDescription().slice(0,60)+"..."},{name:"isbn",example:o.faker.commerce.isbn()}]},{category:"vehicle",methods:[{name:"vehicle",example:o.faker.vehicle.vehicle()},{name:"manufacturer",example:o.faker.vehicle.manufacturer()},{name:"model",example:o.faker.vehicle.model()},{name:"vin",example:o.faker.vehicle.vin()},{name:"vrm",example:o.faker.vehicle.vrm()}]},{category:"system",methods:[{name:"fileName",example:o.faker.system.fileName()},{name:"mimeType",example:o.faker.system.mimeType()},{name:"fileExt",example:o.faker.system.fileExt()},{name:"filePath",example:o.faker.system.filePath()},{name:"semver",example:o.faker.system.semver()}]}]}var Mn={"internet.userName":"username"};function zt(e,t){let n=o.faker[e],r=Mn[`${e}.${t}`]??t;if(!n||typeof n[r]!="function")return`[unknown: ${e}.${t}]`;let i=n[r]();return i instanceof Date?i.toISOString():String(i)}function Bn(e){let t={...q,...e},n=z.safeParse(t);if(!n.success){let r=n.error.issues.map(i=>` \u2022 ${i.path.join(".")}: ${i.message}`).join(`
61
+ `);throw new Error(`Invalid assuremind config:
62
+ ${r}`)}return n.data}0&&(module.exports={AssuremindError,ConfigError,ExecutionError,HealingError,ProviderError,StorageError,ValidationError,acceptHealingEvent,appendPendingEvent,callFaker,clearEnvCache,configExists,createCase,createChildLogger,createSuite,defineConfig,deleteCase,deleteGlobalVariable,deleteResult,deleteSuite,formatError,generateCacheKey,getAvailableGenerators,getCasePath,getHealingStats,isAssuremindError,listCasePaths,listCases,listHealingReportIds,listResultIds,listResults,listSuiteDirs,listSuites,listSuitesWithCounts,listVariableFiles,logger,normalizeUrl,pruneResolvedEvents,readCase,readConfig,readEnvVariables,readGlobalVariables,readHealingReport,readPendingEvents,readResult,readSuite,readVariables,redactSecrets,rejectHealingEvent,resolveVariables,sanitizeGeneratedCode,screenshotsDir,setGlobalVariable,sha256,toSlug,tracesDir,tryGetEnv,updateCase,updateConfig,updateSuite,validateConfig,validateEnv,videosDir,writeCase,writeConfig,writeHealingReport,writeResult,writeSuite,writeVariables});