intellitester 0.1.12

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.
Files changed (89) hide show
  1. package/README.md +200 -0
  2. package/dist/chunk-35WJGNDA.cjs +136 -0
  3. package/dist/chunk-35WJGNDA.cjs.map +1 -0
  4. package/dist/chunk-4B54JUOP.js +234 -0
  5. package/dist/chunk-4B54JUOP.js.map +1 -0
  6. package/dist/chunk-5LFSLMQ7.js +2517 -0
  7. package/dist/chunk-5LFSLMQ7.js.map +1 -0
  8. package/dist/chunk-6PYKWWH5.js +63 -0
  9. package/dist/chunk-6PYKWWH5.js.map +1 -0
  10. package/dist/chunk-ARJYJVRM.cjs +302 -0
  11. package/dist/chunk-ARJYJVRM.cjs.map +1 -0
  12. package/dist/chunk-CN6HSJJX.js +133 -0
  13. package/dist/chunk-CN6HSJJX.js.map +1 -0
  14. package/dist/chunk-DE5UFTTG.js +31 -0
  15. package/dist/chunk-DE5UFTTG.js.map +1 -0
  16. package/dist/chunk-ECBA4GJ3.js +287 -0
  17. package/dist/chunk-ECBA4GJ3.js.map +1 -0
  18. package/dist/chunk-OFXNJXMV.cjs +237 -0
  19. package/dist/chunk-OFXNJXMV.cjs.map +1 -0
  20. package/dist/chunk-PAKODOH4.cjs +66 -0
  21. package/dist/chunk-PAKODOH4.cjs.map +1 -0
  22. package/dist/chunk-QMYM2TCH.cjs +36 -0
  23. package/dist/chunk-QMYM2TCH.cjs.map +1 -0
  24. package/dist/chunk-SAVY6D3X.js +125 -0
  25. package/dist/chunk-SAVY6D3X.js.map +1 -0
  26. package/dist/chunk-UUJXCHVT.cjs +128 -0
  27. package/dist/chunk-UUJXCHVT.cjs.map +1 -0
  28. package/dist/chunk-XWGUA67E.cjs +2552 -0
  29. package/dist/chunk-XWGUA67E.cjs.map +1 -0
  30. package/dist/cli/index.cjs +1985 -0
  31. package/dist/cli/index.cjs.map +1 -0
  32. package/dist/cli/index.d.cts +1 -0
  33. package/dist/cli/index.d.ts +1 -0
  34. package/dist/cli/index.js +1957 -0
  35. package/dist/cli/index.js.map +1 -0
  36. package/dist/core/cleanup/index.cjs +45 -0
  37. package/dist/core/cleanup/index.cjs.map +1 -0
  38. package/dist/core/cleanup/index.d.cts +117 -0
  39. package/dist/core/cleanup/index.d.ts +117 -0
  40. package/dist/core/cleanup/index.js +8 -0
  41. package/dist/core/cleanup/index.js.map +1 -0
  42. package/dist/index.cjs +110 -0
  43. package/dist/index.cjs.map +1 -0
  44. package/dist/index.d.cts +852 -0
  45. package/dist/index.d.ts +852 -0
  46. package/dist/index.js +9 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/integration/index.cjs +22 -0
  49. package/dist/integration/index.cjs.map +1 -0
  50. package/dist/integration/index.d.cts +42 -0
  51. package/dist/integration/index.d.ts +42 -0
  52. package/dist/integration/index.js +20 -0
  53. package/dist/integration/index.js.map +1 -0
  54. package/dist/providers/appwrite/index.cjs +16 -0
  55. package/dist/providers/appwrite/index.cjs.map +1 -0
  56. package/dist/providers/appwrite/index.d.cts +12 -0
  57. package/dist/providers/appwrite/index.d.ts +12 -0
  58. package/dist/providers/appwrite/index.js +3 -0
  59. package/dist/providers/appwrite/index.js.map +1 -0
  60. package/dist/providers/index.cjs +60 -0
  61. package/dist/providers/index.cjs.map +1 -0
  62. package/dist/providers/index.d.cts +13 -0
  63. package/dist/providers/index.d.ts +13 -0
  64. package/dist/providers/index.js +7 -0
  65. package/dist/providers/index.js.map +1 -0
  66. package/dist/providers/mysql/index.cjs +16 -0
  67. package/dist/providers/mysql/index.cjs.map +1 -0
  68. package/dist/providers/mysql/index.d.cts +14 -0
  69. package/dist/providers/mysql/index.d.ts +14 -0
  70. package/dist/providers/mysql/index.js +3 -0
  71. package/dist/providers/mysql/index.js.map +1 -0
  72. package/dist/providers/postgres/index.cjs +16 -0
  73. package/dist/providers/postgres/index.cjs.map +1 -0
  74. package/dist/providers/postgres/index.d.cts +10 -0
  75. package/dist/providers/postgres/index.d.ts +10 -0
  76. package/dist/providers/postgres/index.js +3 -0
  77. package/dist/providers/postgres/index.js.map +1 -0
  78. package/dist/providers/sqlite/index.cjs +16 -0
  79. package/dist/providers/sqlite/index.cjs.map +1 -0
  80. package/dist/providers/sqlite/index.d.cts +11 -0
  81. package/dist/providers/sqlite/index.d.ts +11 -0
  82. package/dist/providers/sqlite/index.js +3 -0
  83. package/dist/providers/sqlite/index.js.map +1 -0
  84. package/dist/types-LONNVTIF.d.cts +56 -0
  85. package/dist/types-l-ZaFKC-.d.ts +56 -0
  86. package/package.json +114 -0
  87. package/schemas/intellitester.config.schema.json +384 -0
  88. package/schemas/test.schema.json +517 -0
  89. package/schemas/workflow.schema.json +227 -0
@@ -0,0 +1,1985 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var chunkXWGUA67E_cjs = require('../chunk-XWGUA67E.cjs');
5
+ var chunkARJYJVRM_cjs = require('../chunk-ARJYJVRM.cjs');
6
+ require('../chunk-QMYM2TCH.cjs');
7
+ require('../chunk-PAKODOH4.cjs');
8
+ require('../chunk-OFXNJXMV.cjs');
9
+ require('../chunk-UUJXCHVT.cjs');
10
+ require('../chunk-35WJGNDA.cjs');
11
+ var dotenv2 = require('dotenv');
12
+ var fs3 = require('fs/promises');
13
+ var path4 = require('path');
14
+ var process2 = require('process');
15
+ var commander = require('commander');
16
+ var child_process = require('child_process');
17
+ var crypto = require('crypto');
18
+ var playwright = require('playwright');
19
+ var yaml = require('yaml');
20
+ var glob = require('glob');
21
+ var fs = require('fs');
22
+ var prompts = require('prompts');
23
+
24
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
25
+
26
+ function _interopNamespace(e) {
27
+ if (e && e.__esModule) return e;
28
+ var n = Object.create(null);
29
+ if (e) {
30
+ Object.keys(e).forEach(function (k) {
31
+ if (k !== 'default') {
32
+ var d = Object.getOwnPropertyDescriptor(e, k);
33
+ Object.defineProperty(n, k, d.get ? d : {
34
+ enumerable: true,
35
+ get: function () { return e[k]; }
36
+ });
37
+ }
38
+ });
39
+ }
40
+ n.default = e;
41
+ return Object.freeze(n);
42
+ }
43
+
44
+ var dotenv2__default = /*#__PURE__*/_interopDefault(dotenv2);
45
+ var fs3__default = /*#__PURE__*/_interopDefault(fs3);
46
+ var path4__namespace = /*#__PURE__*/_interopNamespace(path4);
47
+ var process2__default = /*#__PURE__*/_interopDefault(process2);
48
+ var crypto__default = /*#__PURE__*/_interopDefault(crypto);
49
+ var prompts__default = /*#__PURE__*/_interopDefault(prompts);
50
+
51
+ var getBrowser = (browser) => {
52
+ switch (browser) {
53
+ case "firefox":
54
+ return playwright.firefox;
55
+ case "webkit":
56
+ return playwright.webkit;
57
+ default:
58
+ return playwright.chromium;
59
+ }
60
+ };
61
+ function buildExecutionOrder(workflows) {
62
+ const workflowMap = /* @__PURE__ */ new Map();
63
+ const workflowIds = [];
64
+ for (let i = 0; i < workflows.length; i++) {
65
+ const workflow = workflows[i];
66
+ const id = workflow.id ?? `workflow_${i}`;
67
+ workflowIds.push(id);
68
+ workflowMap.set(id, { ...workflow, id });
69
+ }
70
+ const adjacencyList = /* @__PURE__ */ new Map();
71
+ const inDegree = /* @__PURE__ */ new Map();
72
+ for (const id of workflowIds) {
73
+ adjacencyList.set(id, []);
74
+ inDegree.set(id, 0);
75
+ }
76
+ for (const id of workflowIds) {
77
+ const workflow = workflowMap.get(id);
78
+ const deps = workflow.depends_on ?? [];
79
+ for (const depId of deps) {
80
+ if (!workflowMap.has(depId)) {
81
+ throw new Error(
82
+ `Workflow "${id}" depends on "${depId}" which does not exist in the pipeline`
83
+ );
84
+ }
85
+ adjacencyList.get(depId).push(id);
86
+ inDegree.set(id, (inDegree.get(id) ?? 0) + 1);
87
+ }
88
+ }
89
+ const queue = [];
90
+ for (const id of workflowIds) {
91
+ if (inDegree.get(id) === 0) {
92
+ queue.push(id);
93
+ }
94
+ }
95
+ const sorted = [];
96
+ while (queue.length > 0) {
97
+ const currentId = queue.shift();
98
+ sorted.push(workflowMap.get(currentId));
99
+ for (const dependentId of adjacencyList.get(currentId) ?? []) {
100
+ const newInDegree = (inDegree.get(dependentId) ?? 1) - 1;
101
+ inDegree.set(dependentId, newInDegree);
102
+ if (newInDegree === 0) {
103
+ queue.push(dependentId);
104
+ }
105
+ }
106
+ }
107
+ if (sorted.length !== workflowIds.length) {
108
+ const remaining = workflowIds.filter((id) => !sorted.some((w) => w.id === id));
109
+ throw new Error(
110
+ `Circular dependency detected in pipeline. Workflows involved: ${remaining.join(", ")}`
111
+ );
112
+ }
113
+ return sorted;
114
+ }
115
+ function inferCleanupConfig(config) {
116
+ if (!config) return void 0;
117
+ if (config.cleanup) {
118
+ return config.cleanup;
119
+ }
120
+ if (config.appwrite?.cleanup) {
121
+ return {
122
+ provider: "appwrite",
123
+ appwrite: {
124
+ endpoint: config.appwrite.endpoint,
125
+ projectId: config.appwrite.projectId,
126
+ apiKey: config.appwrite.apiKey,
127
+ cleanupOnFailure: config.appwrite.cleanupOnFailure
128
+ }
129
+ };
130
+ }
131
+ return void 0;
132
+ }
133
+ async function runPipeline(pipeline, pipelinePath, options = {}) {
134
+ const pipelineDir = path4__namespace.default.dirname(pipelinePath);
135
+ const sessionId = crypto__default.default.randomUUID();
136
+ const testStartTime = (/* @__PURE__ */ new Date()).toISOString();
137
+ console.log(`
138
+ ${"=".repeat(60)}`);
139
+ console.log(`Pipeline: ${pipeline.name}`);
140
+ console.log(`Session ID: ${sessionId}`);
141
+ console.log(`${"=".repeat(60)}
142
+ `);
143
+ let executionOrder;
144
+ try {
145
+ executionOrder = buildExecutionOrder(pipeline.workflows);
146
+ console.log(
147
+ `Execution order: ${executionOrder.map((w) => w.id ?? w.file).join(" -> ")}
148
+ `
149
+ );
150
+ } catch (error) {
151
+ const message = error instanceof Error ? error.message : String(error);
152
+ console.error(`Failed to build execution order: ${message}`);
153
+ return {
154
+ status: "failed",
155
+ workflows: [],
156
+ sessionId
157
+ };
158
+ }
159
+ let trackingServer = null;
160
+ try {
161
+ trackingServer = await chunkXWGUA67E_cjs.startTrackingServer({ port: 0 });
162
+ console.log(`Tracking server started on port ${trackingServer.port}`);
163
+ } catch (error) {
164
+ console.warn("Failed to start tracking server:", error);
165
+ }
166
+ if (trackingServer) {
167
+ process.env.INTELLITESTER_SESSION_ID = sessionId;
168
+ process.env.INTELLITESTER_TRACK_URL = `http://localhost:${trackingServer.port}`;
169
+ }
170
+ let serverProcess = null;
171
+ if (pipeline.config?.webServer) {
172
+ try {
173
+ serverProcess = await chunkXWGUA67E_cjs.startWebServer({
174
+ ...pipeline.config.webServer,
175
+ cwd: pipelineDir
176
+ });
177
+ } catch (error) {
178
+ console.error("Failed to start web server:", error);
179
+ if (trackingServer) await trackingServer.stop();
180
+ throw error;
181
+ }
182
+ }
183
+ const signalCleanup = async () => {
184
+ console.log("\n\nInterrupted - cleaning up...");
185
+ chunkXWGUA67E_cjs.killServer(serverProcess);
186
+ if (trackingServer) await trackingServer.stop();
187
+ process.exit(1);
188
+ };
189
+ process.on("SIGINT", signalCleanup);
190
+ process.on("SIGTERM", signalCleanup);
191
+ const browserName = options.browser ?? pipeline.config?.web?.browser ?? "chromium";
192
+ const headless = options.headed ? false : pipeline.config?.web?.headless ?? true;
193
+ const browser = await getBrowser(browserName).launch({ headless });
194
+ const browserContext = await browser.newContext();
195
+ const page = await browserContext.newPage();
196
+ page.setDefaultTimeout(3e4);
197
+ const executionContext = {
198
+ variables: /* @__PURE__ */ new Map(),
199
+ lastEmail: null,
200
+ emailClient: null,
201
+ appwriteContext: chunkXWGUA67E_cjs.createTestContext(),
202
+ appwriteConfig: pipeline.config?.appwrite ? {
203
+ endpoint: pipeline.config.appwrite.endpoint,
204
+ projectId: pipeline.config.appwrite.projectId,
205
+ apiKey: pipeline.config.appwrite.apiKey
206
+ } : void 0
207
+ };
208
+ if (pipeline.config?.appwrite) {
209
+ chunkXWGUA67E_cjs.setupAppwriteTracking(page, executionContext);
210
+ }
211
+ const completedIds = /* @__PURE__ */ new Set();
212
+ const failedIds = /* @__PURE__ */ new Set();
213
+ const skippedIds = /* @__PURE__ */ new Set();
214
+ const workflowResults = [];
215
+ let pipelineFailed = false;
216
+ let shouldStopPipeline = false;
217
+ try {
218
+ for (const workflowRef of executionOrder) {
219
+ const workflowId = workflowRef.id ?? workflowRef.file;
220
+ if (shouldStopPipeline) {
221
+ workflowResults.push({
222
+ id: workflowRef.id,
223
+ file: workflowRef.file,
224
+ status: "skipped",
225
+ error: "Pipeline stopped due to previous failure"
226
+ });
227
+ skippedIds.add(workflowId);
228
+ continue;
229
+ }
230
+ const deps = workflowRef.depends_on ?? [];
231
+ const depsFailed = deps.some((id) => failedIds.has(id) || skippedIds.has(id));
232
+ const depsNotMet = deps.some(
233
+ (id) => !completedIds.has(id) && !failedIds.has(id) && !skippedIds.has(id)
234
+ );
235
+ if (depsFailed || depsNotMet) {
236
+ const onFailure = workflowRef.on_failure ?? pipeline.on_failure;
237
+ if (onFailure === "skip") {
238
+ console.log(`
239
+ Skipping workflow "${workflowId}" - dependencies not met`);
240
+ workflowResults.push({
241
+ id: workflowRef.id,
242
+ file: workflowRef.file,
243
+ status: "skipped",
244
+ error: `Dependencies not met: ${deps.filter((d) => failedIds.has(d) || skippedIds.has(d)).join(", ")}`
245
+ });
246
+ skippedIds.add(workflowId);
247
+ continue;
248
+ } else if (onFailure === "fail") {
249
+ console.log(
250
+ `
251
+ Pipeline stopped - workflow "${workflowId}" dependencies failed`
252
+ );
253
+ workflowResults.push({
254
+ id: workflowRef.id,
255
+ file: workflowRef.file,
256
+ status: "failed",
257
+ error: `Dependencies failed: ${deps.filter((d) => failedIds.has(d) || skippedIds.has(d)).join(", ")}`
258
+ });
259
+ failedIds.add(workflowId);
260
+ pipelineFailed = true;
261
+ shouldStopPipeline = true;
262
+ continue;
263
+ }
264
+ console.log(
265
+ `
266
+ Running workflow "${workflowId}" despite dependency failure (on_failure: ignore)`
267
+ );
268
+ }
269
+ const workflowFilePath = path4__namespace.default.resolve(pipelineDir, workflowRef.file);
270
+ console.log(`
271
+ ${"=".repeat(40)}`);
272
+ console.log(`Workflow: ${workflowId}`);
273
+ console.log(`File: ${workflowRef.file}`);
274
+ console.log(`${"=".repeat(40)}`);
275
+ try {
276
+ const workflowDefinition = await chunkXWGUA67E_cjs.loadWorkflowDefinition(workflowFilePath);
277
+ if (workflowRef.variables) {
278
+ for (const [key, value] of Object.entries(workflowRef.variables)) {
279
+ const interpolated = value.replace(/\{\{(\w+)\}\}/g, (match, varName) => {
280
+ if (varName === "uuid") {
281
+ return crypto__default.default.randomUUID().split("-")[0];
282
+ }
283
+ return executionContext.variables.get(varName) ?? match;
284
+ });
285
+ executionContext.variables.set(key, interpolated);
286
+ }
287
+ }
288
+ const workflowOptions = {
289
+ ...options,
290
+ page,
291
+ executionContext,
292
+ skipCleanup: true,
293
+ // Defer cleanup to pipeline end
294
+ sessionId,
295
+ testStartTime
296
+ };
297
+ const result = await chunkXWGUA67E_cjs.runWorkflowWithContext(
298
+ workflowDefinition,
299
+ workflowFilePath,
300
+ workflowOptions
301
+ );
302
+ if (result.status === "passed") {
303
+ completedIds.add(workflowId);
304
+ workflowResults.push({
305
+ id: workflowRef.id,
306
+ file: workflowRef.file,
307
+ status: "passed",
308
+ workflowResult: result
309
+ });
310
+ } else {
311
+ failedIds.add(workflowId);
312
+ pipelineFailed = true;
313
+ workflowResults.push({
314
+ id: workflowRef.id,
315
+ file: workflowRef.file,
316
+ status: "failed",
317
+ workflowResult: result,
318
+ error: result.tests.find((t) => t.status === "failed")?.error
319
+ });
320
+ const onFailure = workflowRef.on_failure ?? pipeline.on_failure;
321
+ if (onFailure === "fail") {
322
+ console.log(`
323
+ Pipeline stopped due to workflow "${workflowId}" failure`);
324
+ shouldStopPipeline = true;
325
+ }
326
+ }
327
+ } catch (error) {
328
+ const message = error instanceof Error ? error.message : String(error);
329
+ console.error(`Failed to load/run workflow "${workflowId}": ${message}`);
330
+ failedIds.add(workflowId);
331
+ pipelineFailed = true;
332
+ workflowResults.push({
333
+ id: workflowRef.id,
334
+ file: workflowRef.file,
335
+ status: "failed",
336
+ error: message
337
+ });
338
+ const onFailure = workflowRef.on_failure ?? pipeline.on_failure;
339
+ if (onFailure === "fail") {
340
+ console.log(`
341
+ Pipeline stopped due to workflow "${workflowId}" failure`);
342
+ shouldStopPipeline = true;
343
+ }
344
+ }
345
+ }
346
+ if (trackingServer) {
347
+ const serverResources = trackingServer.getResources(sessionId);
348
+ if (serverResources.length > 0) {
349
+ console.log(`
350
+ Collected ${serverResources.length} server-tracked resources`);
351
+ executionContext.appwriteContext.resources.push(...serverResources);
352
+ }
353
+ }
354
+ let cleanupResult;
355
+ const cleanupConfig = inferCleanupConfig(pipeline.config);
356
+ if (cleanupConfig) {
357
+ const shouldCleanup = pipelineFailed ? pipeline.cleanup_on_failure : true;
358
+ if (shouldCleanup) {
359
+ try {
360
+ console.log("\n---");
361
+ console.log("[Cleanup] Starting pipeline cleanup...");
362
+ const { handlers, typeMappings, provider } = await chunkARJYJVRM_cjs.loadCleanupHandlers(
363
+ cleanupConfig,
364
+ process.cwd()
365
+ );
366
+ const genericResources = executionContext.appwriteContext.resources.map(
367
+ (r) => ({ ...r })
368
+ );
369
+ const providerConfig = {
370
+ provider: cleanupConfig.provider || "appwrite"
371
+ };
372
+ if (cleanupConfig.provider === "appwrite" && cleanupConfig.appwrite) {
373
+ const appwriteCleanupConfig = cleanupConfig.appwrite;
374
+ providerConfig.endpoint = appwriteCleanupConfig.endpoint;
375
+ providerConfig.projectId = appwriteCleanupConfig.projectId;
376
+ }
377
+ cleanupResult = await chunkARJYJVRM_cjs.executeCleanup(
378
+ genericResources,
379
+ handlers,
380
+ typeMappings,
381
+ {
382
+ parallel: cleanupConfig.parallel ?? false,
383
+ retries: cleanupConfig.retries ?? 3,
384
+ sessionId,
385
+ testStartTime,
386
+ userId: executionContext.appwriteContext.userId,
387
+ providerConfig,
388
+ cwd: process.cwd(),
389
+ config: cleanupConfig,
390
+ provider
391
+ }
392
+ );
393
+ if (cleanupResult.success) {
394
+ console.log(
395
+ `[Cleanup] Cleanup complete: ${cleanupResult.deleted.length} resources deleted`
396
+ );
397
+ } else {
398
+ console.log(
399
+ `[Cleanup] Cleanup partial: ${cleanupResult.deleted.length} deleted, ${cleanupResult.failed.length} failed`
400
+ );
401
+ for (const failed of cleanupResult.failed) {
402
+ console.log(` - ${failed}`);
403
+ }
404
+ }
405
+ } catch (error) {
406
+ console.error("[Cleanup] Cleanup failed:", error);
407
+ }
408
+ } else {
409
+ console.log("\nSkipping cleanup (cleanup_on_failure is false)");
410
+ }
411
+ }
412
+ const passedCount = workflowResults.filter((w) => w.status === "passed").length;
413
+ const failedCount = workflowResults.filter((w) => w.status === "failed").length;
414
+ const skippedCount = workflowResults.filter((w) => w.status === "skipped").length;
415
+ console.log(`
416
+ ${"=".repeat(60)}`);
417
+ console.log(`Pipeline: ${pipelineFailed ? "FAILED" : "PASSED"}`);
418
+ console.log(
419
+ `Workflows: ${passedCount} passed, ${failedCount} failed, ${skippedCount} skipped`
420
+ );
421
+ console.log(`${"=".repeat(60)}
422
+ `);
423
+ return {
424
+ status: pipelineFailed ? "failed" : "passed",
425
+ workflows: workflowResults,
426
+ sessionId,
427
+ cleanupResult
428
+ };
429
+ } finally {
430
+ process.off("SIGINT", signalCleanup);
431
+ process.off("SIGTERM", signalCleanup);
432
+ await browserContext.close();
433
+ await browser.close();
434
+ chunkXWGUA67E_cjs.killServer(serverProcess);
435
+ if (trackingServer) {
436
+ await trackingServer.stop();
437
+ }
438
+ delete process.env.INTELLITESTER_SESSION_ID;
439
+ delete process.env.INTELLITESTER_TRACK_URL;
440
+ }
441
+ }
442
+
443
+ // src/generator/elementExtractor.ts
444
+ var INTERACTIVE_TAGS = [
445
+ "button",
446
+ "input",
447
+ "textarea",
448
+ "select",
449
+ "a",
450
+ "form",
451
+ "label",
452
+ "option"
453
+ ];
454
+ function extractTemplateContent(content, filePath) {
455
+ const ext = filePath.split(".").pop()?.toLowerCase();
456
+ switch (ext) {
457
+ case "vue": {
458
+ const templateMatch = content.match(/<template[^>]*>([\s\S]*?)<\/template>/);
459
+ return templateMatch ? templateMatch[1] : "";
460
+ }
461
+ case "astro": {
462
+ const frontmatterEnd = content.lastIndexOf("---");
463
+ if (frontmatterEnd > 0) {
464
+ return content.substring(frontmatterEnd + 3);
465
+ }
466
+ return content;
467
+ }
468
+ case "tsx":
469
+ case "jsx": {
470
+ const jsxPatterns = [
471
+ // return ( ... )
472
+ /return\s*\(([\s\S]*?)\);/g,
473
+ // return <...>
474
+ /return\s+(<[\s\S]*?>[\s\S]*?<\/[\w.]+>)/g,
475
+ // return <... />
476
+ /return\s+(<[^>]+\/\s*>)/g
477
+ ];
478
+ const jsxParts = [];
479
+ for (const pattern of jsxPatterns) {
480
+ const matches = content.matchAll(pattern);
481
+ for (const match of matches) {
482
+ jsxParts.push(match[1]);
483
+ }
484
+ }
485
+ return jsxParts.join("\n");
486
+ }
487
+ case "svelte": {
488
+ let template = content;
489
+ template = template.replace(/<script[^>]*>[\s\S]*?<\/script>/g, "");
490
+ template = template.replace(/<style[^>]*>[\s\S]*?<\/style>/g, "");
491
+ return template;
492
+ }
493
+ default:
494
+ return content;
495
+ }
496
+ }
497
+ function extractAttribute(element, attrName) {
498
+ const patterns = [
499
+ new RegExp(`${attrName}\\s*=\\s*"([^"]*)"`, "i"),
500
+ new RegExp(`${attrName}\\s*=\\s*'([^']*)'`, "i"),
501
+ new RegExp(`${attrName}\\s*=\\s*{([^}]*)}`, "i")
502
+ // JSX expressions
503
+ ];
504
+ for (const pattern of patterns) {
505
+ const match = element.match(pattern);
506
+ if (match && match[1]) {
507
+ let value = match[1].trim();
508
+ if (value.startsWith('"') && value.endsWith('"')) {
509
+ value = value.slice(1, -1);
510
+ } else if (value.startsWith("'") && value.endsWith("'")) {
511
+ value = value.slice(1, -1);
512
+ }
513
+ return value;
514
+ }
515
+ }
516
+ return void 0;
517
+ }
518
+ function extractTextContent(element, tag) {
519
+ const regex = new RegExp(`<${tag}[^>]*>([^<]*)</${tag}>`, "i");
520
+ const match = element.match(regex);
521
+ if (match && match[1]) {
522
+ let text = match[1].trim();
523
+ text = text.replace(/\{[^}]*\}/g, "").trim();
524
+ if (text.length > 0 && text.length < 100) {
525
+ return text;
526
+ }
527
+ }
528
+ return void 0;
529
+ }
530
+ function generateDescription(element) {
531
+ const parts = [];
532
+ if (element.tag === "input" && element.type) {
533
+ parts.push(`${element.type} input`);
534
+ } else if (element.tag === "a") {
535
+ parts.push("link");
536
+ } else if (element.tag) {
537
+ parts.push(element.tag);
538
+ }
539
+ if (element.text) {
540
+ parts.push(`"${element.text}"`);
541
+ } else if (element.name) {
542
+ parts.push(`"${element.name}"`);
543
+ } else if (element.placeholder) {
544
+ parts.push(`with placeholder "${element.placeholder}"`);
545
+ } else if (element.testId) {
546
+ parts.push(`(${element.testId})`);
547
+ }
548
+ return parts.join(" ");
549
+ }
550
+ function parseElement(elementStr, file, route) {
551
+ const tagMatch = elementStr.match(/<(\w+)/);
552
+ if (!tagMatch) return null;
553
+ const tag = tagMatch[1].toLowerCase();
554
+ if (!INTERACTIVE_TAGS.includes(tag)) {
555
+ return null;
556
+ }
557
+ const element = {
558
+ tag,
559
+ file,
560
+ route
561
+ };
562
+ element.testId = extractAttribute(elementStr, "data-testid");
563
+ element.role = extractAttribute(elementStr, "role");
564
+ element.name = extractAttribute(elementStr, "aria-label");
565
+ element.type = extractAttribute(elementStr, "type");
566
+ element.placeholder = extractAttribute(elementStr, "placeholder");
567
+ if (!element.name) {
568
+ element.name = extractAttribute(elementStr, "name");
569
+ }
570
+ if (["button", "a", "label", "option"].includes(tag)) {
571
+ element.text = extractTextContent(elementStr, tag);
572
+ }
573
+ element.description = generateDescription(element);
574
+ return element;
575
+ }
576
+ function extractElements(content, filePath, route) {
577
+ const template = extractTemplateContent(content, filePath);
578
+ if (!template.trim()) {
579
+ return [];
580
+ }
581
+ const elements = [];
582
+ const elementPattern = /<(\w+)(?:\s+[^>]*)?(?:\/>|>[\s\S]*?<\/\1>)/g;
583
+ const matches = template.matchAll(elementPattern);
584
+ for (const match of matches) {
585
+ const elementStr = match[0];
586
+ const element = parseElement(elementStr, filePath, route);
587
+ if (element) {
588
+ elements.push(element);
589
+ }
590
+ }
591
+ return elements;
592
+ }
593
+
594
+ // src/generator/sourceScanner.ts
595
+ var DEFAULT_EXTENSIONS = [".vue", ".astro", ".tsx", ".jsx", ".svelte"];
596
+ function fileToRoute(filePath, pagesDir) {
597
+ let route = path4__namespace.relative(pagesDir, filePath);
598
+ route = route.replace(/\.(vue|astro|tsx|jsx|svelte)$/, "");
599
+ route = route.replace(/\/index$/, "");
600
+ route = route.replace(/^index$/, "");
601
+ route = route.replace(/\[\.\.\.(\w+)\]/g, "*");
602
+ route = route.replace(/\[(\w+)\]/g, ":$1");
603
+ route = "/" + route;
604
+ route = route.replace(/\/+/g, "/");
605
+ if (route.length > 1) {
606
+ route = route.replace(/\/$/, "");
607
+ }
608
+ return route;
609
+ }
610
+ function getComponentName(filePath) {
611
+ const basename2 = path4__namespace.basename(filePath);
612
+ return basename2.replace(/\.(vue|astro|tsx|jsx|svelte)$/, "");
613
+ }
614
+ async function scanDirectory(dir, extensions, cwd) {
615
+ const fullDir = path4__namespace.resolve(cwd, dir);
616
+ try {
617
+ await fs.promises.access(fullDir);
618
+ } catch {
619
+ return [];
620
+ }
621
+ const patterns = extensions.map((ext) => `**/*${ext}`);
622
+ const files = [];
623
+ for (const pattern of patterns) {
624
+ const matches = await glob.glob(pattern, {
625
+ cwd: fullDir,
626
+ absolute: true,
627
+ nodir: true
628
+ });
629
+ files.push(...matches);
630
+ }
631
+ return files;
632
+ }
633
+ async function scanProjectSource(config) {
634
+ const cwd = config.cwd ?? process.cwd();
635
+ const extensions = config.extensions ?? DEFAULT_EXTENSIONS;
636
+ const routes = [];
637
+ const components = [];
638
+ const allElements = [];
639
+ if (config.pagesDir) {
640
+ const pageFiles = await scanDirectory(config.pagesDir, extensions, cwd);
641
+ const pagesFullDir = path4__namespace.resolve(cwd, config.pagesDir);
642
+ for (const file of pageFiles) {
643
+ const routePath = fileToRoute(file, pagesFullDir);
644
+ const name = getComponentName(file);
645
+ routes.push({
646
+ path: routePath,
647
+ file,
648
+ name
649
+ });
650
+ const content = await fs.promises.readFile(file, "utf-8");
651
+ const elements = extractElements(content, file, routePath);
652
+ components.push({
653
+ name,
654
+ file,
655
+ elements
656
+ });
657
+ allElements.push(...elements);
658
+ }
659
+ }
660
+ if (config.componentsDir) {
661
+ const componentFiles = await scanDirectory(config.componentsDir, extensions, cwd);
662
+ for (const file of componentFiles) {
663
+ if (components.some((c) => c.file === file)) {
664
+ continue;
665
+ }
666
+ const name = getComponentName(file);
667
+ const content = await fs.promises.readFile(file, "utf-8");
668
+ const elements = extractElements(content, file, void 0);
669
+ components.push({
670
+ name,
671
+ file,
672
+ elements
673
+ });
674
+ allElements.push(...elements);
675
+ }
676
+ }
677
+ if (!config.pagesDir && !config.componentsDir) {
678
+ const commonPageDirs = ["src/pages", "pages", "app", "src/app", "src/routes"];
679
+ const commonComponentDirs = ["src/components", "components", "src/lib", "lib"];
680
+ for (const dir of commonPageDirs) {
681
+ const files = await scanDirectory(dir, extensions, cwd);
682
+ if (files.length > 0) {
683
+ const pagesFullDir = path4__namespace.resolve(cwd, dir);
684
+ for (const file of files) {
685
+ const routePath = fileToRoute(file, pagesFullDir);
686
+ const name = getComponentName(file);
687
+ routes.push({
688
+ path: routePath,
689
+ file,
690
+ name
691
+ });
692
+ const content = await fs.promises.readFile(file, "utf-8");
693
+ const elements = extractElements(content, file, routePath);
694
+ components.push({
695
+ name,
696
+ file,
697
+ elements
698
+ });
699
+ allElements.push(...elements);
700
+ }
701
+ break;
702
+ }
703
+ }
704
+ for (const dir of commonComponentDirs) {
705
+ const files = await scanDirectory(dir, extensions, cwd);
706
+ if (files.length > 0) {
707
+ for (const file of files) {
708
+ if (components.some((c) => c.file === file)) {
709
+ continue;
710
+ }
711
+ const name = getComponentName(file);
712
+ const content = await fs.promises.readFile(file, "utf-8");
713
+ const elements = extractElements(content, file, void 0);
714
+ components.push({
715
+ name,
716
+ file,
717
+ elements
718
+ });
719
+ allElements.push(...elements);
720
+ }
721
+ break;
722
+ }
723
+ }
724
+ }
725
+ return {
726
+ routes,
727
+ components,
728
+ allElements
729
+ };
730
+ }
731
+ function formatScanResultsForPrompt(result) {
732
+ const lines = [];
733
+ if (result.routes.length > 0) {
734
+ lines.push("## ROUTES");
735
+ lines.push("");
736
+ for (const route of result.routes) {
737
+ lines.push(`- ${route.path}: ${route.name}`);
738
+ }
739
+ lines.push("");
740
+ }
741
+ const elementsByRoute = /* @__PURE__ */ new Map();
742
+ for (const element of result.allElements) {
743
+ const route = element.route ?? "shared";
744
+ if (!elementsByRoute.has(route)) {
745
+ elementsByRoute.set(route, []);
746
+ }
747
+ elementsByRoute.get(route).push(element);
748
+ }
749
+ lines.push("## ELEMENTS");
750
+ lines.push("");
751
+ for (const [route, elements] of elementsByRoute) {
752
+ lines.push(`### ${route === "shared" ? "Shared Components" : route}`);
753
+ lines.push("");
754
+ for (const el of elements) {
755
+ const locators = [];
756
+ if (el.testId) locators.push(`data-testid="${el.testId}"`);
757
+ if (el.text) locators.push(`text="${el.text}"`);
758
+ if (el.role) locators.push(`role="${el.role}"`);
759
+ if (el.name) locators.push(`name="${el.name}"`);
760
+ if (el.placeholder) locators.push(`placeholder="${el.placeholder}"`);
761
+ if (el.type) locators.push(`type="${el.type}"`);
762
+ const locatorStr = locators.length > 0 ? `[${locators.join(", ")}]` : "";
763
+ const description = el.description ? ` - ${el.description}` : "";
764
+ lines.push(`- <${el.tag}>${locatorStr}${description}`);
765
+ }
766
+ lines.push("");
767
+ }
768
+ return lines.join("\n");
769
+ }
770
+
771
+ // src/generator/prompts.ts
772
+ var SYSTEM_PROMPT = `You are a test automation expert that converts natural language test descriptions into YAML test definitions.
773
+
774
+ ## Schema Structure
775
+
776
+ A test definition must have:
777
+ - name: A descriptive test name (non-empty string)
778
+ - platform: One of 'web', 'android', or 'ios'
779
+ - config: Optional configuration object
780
+ - steps: Array of actions (minimum 1 action required)
781
+
782
+ ## Available Actions
783
+
784
+ 1. navigate - Navigate to a URL
785
+ { type: 'navigate', value: string }
786
+
787
+ 2. tap - Click or tap an element
788
+ { type: 'tap', target: Locator }
789
+
790
+ 3. input - Type text into an input field
791
+ { type: 'input', target: Locator, value: string }
792
+
793
+ 4. assert - Assert element exists or contains text
794
+ { type: 'assert', target: Locator, value?: string }
795
+
796
+ 5. wait - Wait for an element or timeout
797
+ { type: 'wait', target?: Locator, timeout?: number }
798
+ Note: Requires either target or timeout
799
+
800
+ 6. scroll - Scroll the page or to an element
801
+ { type: 'scroll', target?: Locator, direction?: 'up'|'down', amount?: number }
802
+
803
+ 7. screenshot - Take a screenshot
804
+ { type: 'screenshot', name?: string }
805
+
806
+ ## Locator Structure
807
+
808
+ A locator must have AT LEAST ONE of these properties:
809
+ - description: Human-readable description for AI healing
810
+ - testId: data-testid attribute value
811
+ - text: Text content to match
812
+ - css: CSS selector
813
+ - xpath: XPath expression
814
+ - role: ARIA role attribute
815
+ - name: Accessible name
816
+
817
+ ## Configuration Options
818
+
819
+ web:
820
+ baseUrl: Base URL for the application
821
+ browser: Browser to use (e.g., 'chromium', 'firefox', 'webkit')
822
+ headless: Run browser in headless mode (boolean)
823
+ timeout: Default timeout in milliseconds
824
+
825
+ android:
826
+ appId: Android application package ID
827
+ device: Device name or ID
828
+
829
+ ios:
830
+ bundleId: iOS bundle identifier
831
+ simulator: Simulator name
832
+
833
+ ## Example 1: Login Test
834
+
835
+ \`\`\`yaml
836
+ name: Login with valid credentials
837
+ platform: web
838
+ config:
839
+ web:
840
+ baseUrl: https://example.com
841
+ headless: true
842
+ steps:
843
+ - type: navigate
844
+ value: /login
845
+ - type: input
846
+ target:
847
+ testId: email-input
848
+ description: Email input field
849
+ value: test@example.com
850
+ - type: input
851
+ target:
852
+ testId: password-input
853
+ description: Password input field
854
+ value: password123
855
+ - type: tap
856
+ target:
857
+ text: Sign In
858
+ role: button
859
+ description: Sign in button
860
+ - type: assert
861
+ target:
862
+ text: Welcome
863
+ description: Welcome message after login
864
+ \`\`\`
865
+
866
+ ## Example 2: Search Test
867
+
868
+ \`\`\`yaml
869
+ name: Search for products
870
+ platform: web
871
+ config:
872
+ web:
873
+ baseUrl: https://shop.example.com
874
+ steps:
875
+ - type: navigate
876
+ value: /
877
+ - type: input
878
+ target:
879
+ css: input[type="search"]
880
+ description: Product search input
881
+ value: laptop
882
+ - type: tap
883
+ target:
884
+ role: button
885
+ name: Search
886
+ description: Search button
887
+ - type: wait
888
+ target:
889
+ css: .search-results
890
+ description: Search results container
891
+ timeout: 5000
892
+ - type: assert
893
+ target:
894
+ text: results found
895
+ description: Results count message
896
+ \`\`\`
897
+
898
+ ## Example 3: Mobile App Test
899
+
900
+ \`\`\`yaml
901
+ name: Add item to cart
902
+ platform: android
903
+ config:
904
+ android:
905
+ appId: com.example.shop
906
+ steps:
907
+ - type: tap
908
+ target:
909
+ testId: category-electronics
910
+ description: Electronics category button
911
+ - type: scroll
912
+ direction: down
913
+ amount: 300
914
+ - type: tap
915
+ target:
916
+ text: Laptop Pro
917
+ description: Product card for Laptop Pro
918
+ - type: tap
919
+ target:
920
+ testId: add-to-cart-button
921
+ description: Add to cart button
922
+ - type: assert
923
+ target:
924
+ text: Added to cart
925
+ description: Success message
926
+ - type: screenshot
927
+ name: cart-confirmation
928
+ \`\`\`
929
+
930
+ ## Important Instructions
931
+
932
+ 1. Output ONLY valid YAML - no markdown code blocks, no explanations
933
+ 2. Every locator MUST have at least one selector property
934
+ 3. Include descriptive locator descriptions for AI healing
935
+ 4. Use multiple locator strategies when possible for resilience
936
+ 5. For wait actions, provide either a target or timeout (or both)
937
+ 6. Use appropriate platform-specific configurations
938
+ 7. Ensure all strings are properly quoted if they contain special characters
939
+ 8. Action steps must be in logical order
940
+
941
+ Generate the test definition now based on the user's description.`;
942
+ function buildPrompt(naturalLanguage, context) {
943
+ const parts = [
944
+ "Generate a test definition for the following scenario:",
945
+ "",
946
+ naturalLanguage
947
+ ];
948
+ if (context) {
949
+ parts.push("", "Additional Context:");
950
+ if (context.platform) {
951
+ parts.push(`- Platform: ${context.platform}`);
952
+ }
953
+ if (context.baseUrl) {
954
+ parts.push(`- Base URL: ${context.baseUrl}`);
955
+ }
956
+ if (context.additionalContext) {
957
+ parts.push(`- ${context.additionalContext}`);
958
+ }
959
+ }
960
+ parts.push("", "Output only valid YAML without code block markers.");
961
+ return parts.join("\n");
962
+ }
963
+ function buildSourceAwareSystemPrompt(scanResult) {
964
+ const parts = [
965
+ "You are a test automation expert that converts natural language test descriptions into YAML test definitions.",
966
+ "",
967
+ "## Schema Structure",
968
+ "",
969
+ "A test definition must have:",
970
+ "- name: A descriptive test name (non-empty string)",
971
+ "- platform: One of 'web', 'android', or 'ios'",
972
+ "- config: Optional configuration object",
973
+ "- steps: Array of actions (minimum 1 action required)",
974
+ "",
975
+ "## Available Actions",
976
+ "",
977
+ "1. navigate - Navigate to a URL",
978
+ " { type: 'navigate', value: string }",
979
+ "",
980
+ "2. tap - Click or tap an element",
981
+ " { type: 'tap', target: Locator }",
982
+ "",
983
+ "3. input - Type text into an input field",
984
+ " { type: 'input', target: Locator, value: string }",
985
+ "",
986
+ "4. assert - Assert element exists or contains text",
987
+ " { type: 'assert', target: Locator, value?: string }",
988
+ "",
989
+ "5. wait - Wait for an element or timeout",
990
+ " { type: 'wait', target?: Locator, timeout?: number }",
991
+ " Note: Requires either target or timeout",
992
+ "",
993
+ "6. scroll - Scroll the page or to an element",
994
+ " { type: 'scroll', target?: Locator, direction?: 'up'|'down', amount?: number }",
995
+ "",
996
+ "7. screenshot - Take a screenshot",
997
+ " { type: 'screenshot', name?: string }",
998
+ "",
999
+ "## Locator Structure",
1000
+ "",
1001
+ "A locator must have AT LEAST ONE of these properties:",
1002
+ "- description: Human-readable description for AI healing",
1003
+ "- testId: data-testid attribute value",
1004
+ "- text: Text content to match",
1005
+ "- css: CSS selector",
1006
+ "- xpath: XPath expression",
1007
+ "- role: ARIA role attribute",
1008
+ "- name: Accessible name",
1009
+ "",
1010
+ "## PROJECT STRUCTURE",
1011
+ "",
1012
+ "The following routes and elements were extracted from the project source code.",
1013
+ "Use these REAL selectors in your generated tests.",
1014
+ "",
1015
+ "### Selector Priority (prefer earlier options):",
1016
+ "1. text - Most resilient to DOM changes",
1017
+ "2. role + name - ARIA-compliant, accessible",
1018
+ "3. testId - Explicit but requires dev setup",
1019
+ "4. css - Last resort, fragile",
1020
+ ""
1021
+ ];
1022
+ parts.push(formatScanResultsForPrompt(scanResult));
1023
+ parts.push(
1024
+ "## Configuration Options",
1025
+ "",
1026
+ "web:",
1027
+ " baseUrl: Base URL for the application",
1028
+ " browser: Browser to use (e.g., 'chromium', 'firefox', 'webkit')",
1029
+ " headless: Run browser in headless mode (boolean)",
1030
+ " timeout: Default timeout in milliseconds",
1031
+ "",
1032
+ "android:",
1033
+ " appId: Android application package ID",
1034
+ " device: Device name or ID",
1035
+ "",
1036
+ "ios:",
1037
+ " bundleId: iOS bundle identifier",
1038
+ " simulator: Simulator name",
1039
+ "",
1040
+ "## Example Test Structure",
1041
+ "",
1042
+ "```yaml",
1043
+ "name: Example test name",
1044
+ "platform: web",
1045
+ "config:",
1046
+ " web:",
1047
+ " baseUrl: https://example.com",
1048
+ " headless: true",
1049
+ "steps:",
1050
+ " - type: navigate",
1051
+ " value: /login",
1052
+ " - type: input",
1053
+ " target:",
1054
+ " text: Email",
1055
+ " description: Email input field",
1056
+ " value: test@example.com",
1057
+ " - type: input",
1058
+ " target:",
1059
+ " role: textbox",
1060
+ " name: Password",
1061
+ " description: Password input field",
1062
+ " value: password123",
1063
+ " - type: tap",
1064
+ " target:",
1065
+ " text: Sign In",
1066
+ " role: button",
1067
+ " description: Sign in button",
1068
+ " - type: assert",
1069
+ " target:",
1070
+ " text: Welcome",
1071
+ " description: Welcome message after login",
1072
+ "```",
1073
+ "",
1074
+ "## Important Instructions",
1075
+ "",
1076
+ "1. Output ONLY valid YAML - no markdown code blocks, no explanations",
1077
+ "2. Use REAL selectors from the project structure above whenever possible",
1078
+ "3. Every locator MUST have at least one selector property",
1079
+ "4. Include descriptive locator descriptions for AI healing",
1080
+ "5. Prefer text and role selectors over testId and css for resilience",
1081
+ "6. Use multiple locator strategies when possible for resilience",
1082
+ "7. For wait actions, provide either a target or timeout (or both)",
1083
+ "8. Use appropriate platform-specific configurations",
1084
+ "9. Ensure all strings are properly quoted if they contain special characters",
1085
+ "10. Action steps must be in logical order",
1086
+ "",
1087
+ "Generate the test definition now based on the user's description."
1088
+ );
1089
+ return parts.join("\n");
1090
+ }
1091
+
1092
+ // src/generator/testGenerator.ts
1093
+ function cleanYamlResponse(response) {
1094
+ let cleaned = response.replace(/```ya?ml\n?/gi, "").replace(/```\n?/g, "");
1095
+ cleaned = cleaned.trim();
1096
+ return cleaned;
1097
+ }
1098
+ async function generateTest(naturalLanguage, options) {
1099
+ const provider = chunkXWGUA67E_cjs.createAIProvider(options.aiConfig);
1100
+ let systemPrompt = SYSTEM_PROMPT;
1101
+ if (options.source !== null) {
1102
+ const sourceConfig = options.source ?? {};
1103
+ const scanResult = await scanProjectSource(sourceConfig);
1104
+ if (scanResult.allElements.length > 0) {
1105
+ systemPrompt = buildSourceAwareSystemPrompt(scanResult);
1106
+ }
1107
+ }
1108
+ const context = {
1109
+ baseUrl: options.baseUrl,
1110
+ platform: options.platform,
1111
+ additionalContext: options.additionalContext
1112
+ };
1113
+ const userPrompt = buildPrompt(naturalLanguage, context);
1114
+ const maxRetries = options.maxRetries ?? 3;
1115
+ let lastError;
1116
+ let lastYaml;
1117
+ for (let attempt = 0; attempt < maxRetries; attempt++) {
1118
+ try {
1119
+ let promptWithFeedback = userPrompt;
1120
+ if (attempt > 0 && lastError) {
1121
+ promptWithFeedback = `${userPrompt}
1122
+
1123
+ Previous attempt failed with error: ${lastError.message}
1124
+
1125
+ Please fix the issue and generate valid YAML.`;
1126
+ }
1127
+ const response = await provider.generateCompletion(promptWithFeedback, systemPrompt);
1128
+ const yaml$1 = cleanYamlResponse(response);
1129
+ lastYaml = yaml$1;
1130
+ const parsed = yaml.parse(yaml$1);
1131
+ const validated = chunkXWGUA67E_cjs.TestDefinitionSchema.parse(parsed);
1132
+ return {
1133
+ success: true,
1134
+ test: validated,
1135
+ yaml: yaml$1,
1136
+ attempts: attempt + 1
1137
+ };
1138
+ } catch (error) {
1139
+ lastError = error instanceof Error ? error : new Error(String(error));
1140
+ if (attempt === maxRetries - 1) {
1141
+ return {
1142
+ success: false,
1143
+ error: `Failed to generate valid test after ${maxRetries} attempts. Last error: ${lastError.message}`,
1144
+ yaml: lastYaml,
1145
+ attempts: maxRetries
1146
+ };
1147
+ }
1148
+ }
1149
+ }
1150
+ return {
1151
+ success: false,
1152
+ error: "Unknown error occurred during test generation",
1153
+ attempts: maxRetries
1154
+ };
1155
+ }
1156
+ function displayMissingEnvVars(missing) {
1157
+ console.log("\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510");
1158
+ console.log("\u2502 \u26A0\uFE0F Missing Environment Variables \u2502");
1159
+ console.log("\u251C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2524");
1160
+ for (const name of missing) {
1161
+ console.log(`\u2502 \u2022 ${name.padEnd(39)}\u2502`);
1162
+ }
1163
+ console.log("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n");
1164
+ }
1165
+ async function promptAddToEnv(missing, envPath) {
1166
+ const { shouldAdd } = await prompts__default.default({
1167
+ type: "confirm",
1168
+ name: "shouldAdd",
1169
+ message: `Add missing variables to ${path4__namespace.default.basename(envPath)}?`,
1170
+ initial: true
1171
+ });
1172
+ if (!shouldAdd) return false;
1173
+ const values = {};
1174
+ for (const name of missing) {
1175
+ const { value } = await prompts__default.default({
1176
+ type: "password",
1177
+ // Hide sensitive values
1178
+ name: "value",
1179
+ message: `Enter value for ${name}:`
1180
+ });
1181
+ if (value !== void 0) {
1182
+ values[name] = value;
1183
+ }
1184
+ }
1185
+ const lines = Object.entries(values).map(([key, val]) => `${key}=${val}`).join("\n");
1186
+ let existingContent = "";
1187
+ try {
1188
+ existingContent = await fs3__default.default.readFile(envPath, "utf8");
1189
+ } catch {
1190
+ }
1191
+ const newContent = existingContent ? `${existingContent.trimEnd()}
1192
+ ${lines}
1193
+ ` : `${lines}
1194
+ `;
1195
+ await fs3__default.default.writeFile(envPath, newContent, "utf8");
1196
+ console.log(`
1197
+ \u2713 Added ${missing.length} variable(s) to ${path4__namespace.default.basename(envPath)}
1198
+ `);
1199
+ dotenv2__default.default.config({ path: envPath, override: true });
1200
+ return true;
1201
+ }
1202
+ async function validateEnvVars(missing, projectDir) {
1203
+ if (missing.length === 0) return true;
1204
+ displayMissingEnvVars(missing);
1205
+ const envPath = path4__namespace.default.join(projectDir, ".env");
1206
+ const added = await promptAddToEnv(missing, envPath);
1207
+ if (!added) {
1208
+ console.log("Cannot continue without required environment variables.");
1209
+ return false;
1210
+ }
1211
+ return true;
1212
+ }
1213
+
1214
+ // src/cli/index.ts
1215
+ dotenv2__default.default.config();
1216
+ var CONFIG_FILENAME = "intellitester.config.yaml";
1217
+ var BROWSER_ALIASES = {
1218
+ chrome: "chromium",
1219
+ chromium: "chromium",
1220
+ safari: "webkit",
1221
+ webkit: "webkit",
1222
+ firefox: "firefox",
1223
+ ff: "firefox"
1224
+ };
1225
+ var resolveBrowserName = (input) => {
1226
+ const normalized = input.toLowerCase().trim();
1227
+ const resolved = BROWSER_ALIASES[normalized];
1228
+ if (!resolved) {
1229
+ const valid = Object.keys(BROWSER_ALIASES).join(", ");
1230
+ throw new Error(`Unknown browser "${input}". Valid options: ${valid}`);
1231
+ }
1232
+ return resolved;
1233
+ };
1234
+ var detectPackageManager = async () => {
1235
+ if (await fileExists("pnpm-lock.yaml")) return "pnpm";
1236
+ if (await fileExists("bun.lockb")) return "bun";
1237
+ if (await fileExists("yarn.lock")) return "yarn";
1238
+ return "npm";
1239
+ };
1240
+ var execCommand = async (cmd, args, cwd) => {
1241
+ return new Promise((resolve2, reject) => {
1242
+ console.log(`Running: ${cmd} ${args.join(" ")}`);
1243
+ const child = child_process.spawn(cmd, args, {
1244
+ cwd,
1245
+ stdio: "inherit",
1246
+ shell: true
1247
+ });
1248
+ child.on("close", (code) => {
1249
+ if (code === 0) resolve2();
1250
+ else reject(new Error(`Command failed with exit code ${code}`));
1251
+ });
1252
+ child.on("error", reject);
1253
+ });
1254
+ };
1255
+ var buildAndPreview = async (config, cwd) => {
1256
+ const pm = await detectPackageManager();
1257
+ const previewConfig = config?.preview || {};
1258
+ const buildCmd = previewConfig.build?.command || `${pm} run build`;
1259
+ const [buildExec, ...buildArgs] = buildCmd.split(" ");
1260
+ const previewCmd = previewConfig.preview?.command || `${pm} run preview`;
1261
+ const [previewExec, ...previewArgs] = previewCmd.split(" ");
1262
+ const previewUrl = previewConfig.url || config?.webServer?.url || config?.platforms?.web?.baseUrl || "http://localhost:4321";
1263
+ const timeout = previewConfig.timeout || 6e4;
1264
+ console.log("\n\u{1F4E6} Building project...\n");
1265
+ await execCommand(buildExec, buildArgs, cwd);
1266
+ console.log("\n\u2705 Build complete\n");
1267
+ console.log("\n\u{1F680} Starting preview server...\n");
1268
+ const previewProcess = await startPreviewServer(previewExec, previewArgs, cwd, previewUrl, timeout);
1269
+ const cleanup = () => {
1270
+ if (previewProcess && !previewProcess.killed) {
1271
+ console.log("\n\u{1F6D1} Stopping preview server...");
1272
+ previewProcess.kill("SIGTERM");
1273
+ }
1274
+ };
1275
+ process2__default.default.on("SIGINT", cleanup);
1276
+ process2__default.default.on("SIGTERM", cleanup);
1277
+ return { previewProcess, cleanup };
1278
+ };
1279
+ var startPreviewServer = async (cmd, args, cwd, url, timeout = 6e4) => {
1280
+ return new Promise((resolve2, reject) => {
1281
+ console.log(`Starting preview server: ${cmd} ${args.join(" ")}`);
1282
+ const child = child_process.spawn(cmd, args, {
1283
+ cwd,
1284
+ stdio: "pipe",
1285
+ shell: true
1286
+ });
1287
+ let output = "";
1288
+ const startTime = Date.now();
1289
+ const checkServer = async () => {
1290
+ try {
1291
+ const response = await fetch(url, { method: "HEAD" });
1292
+ if (response.ok || response.status < 500) {
1293
+ console.log(`Preview server ready at ${url}`);
1294
+ resolve2(child);
1295
+ return true;
1296
+ }
1297
+ } catch {
1298
+ }
1299
+ return false;
1300
+ };
1301
+ const pollInterval = setInterval(async () => {
1302
+ if (await checkServer()) {
1303
+ clearInterval(pollInterval);
1304
+ } else if (Date.now() - startTime > timeout) {
1305
+ clearInterval(pollInterval);
1306
+ child.kill();
1307
+ reject(new Error(`Preview server failed to start within ${timeout}ms`));
1308
+ }
1309
+ }, 500);
1310
+ child.stdout?.on("data", (data) => {
1311
+ output += data.toString();
1312
+ process2__default.default.stdout.write(data);
1313
+ });
1314
+ child.stderr?.on("data", (data) => {
1315
+ output += data.toString();
1316
+ process2__default.default.stderr.write(data);
1317
+ });
1318
+ child.on("error", (err) => {
1319
+ clearInterval(pollInterval);
1320
+ reject(err);
1321
+ });
1322
+ child.on("close", (code) => {
1323
+ clearInterval(pollInterval);
1324
+ if (code !== 0 && code !== null) {
1325
+ reject(new Error(`Preview server exited with code ${code}
1326
+ ${output}`));
1327
+ }
1328
+ });
1329
+ });
1330
+ };
1331
+ var logError = (message) => {
1332
+ console.error(`Error: ${message}`);
1333
+ };
1334
+ var findProjectRoot = async (startPath) => {
1335
+ let currentDir = path4__namespace.default.isAbsolute(startPath) ? startPath : path4__namespace.default.resolve(startPath);
1336
+ try {
1337
+ const stat = await fs3__default.default.stat(currentDir);
1338
+ if (stat.isFile()) {
1339
+ currentDir = path4__namespace.default.dirname(currentDir);
1340
+ }
1341
+ } catch {
1342
+ currentDir = path4__namespace.default.dirname(currentDir);
1343
+ }
1344
+ const root = path4__namespace.default.parse(currentDir).root;
1345
+ while (currentDir !== root) {
1346
+ const markers = ["package.json", ".git", CONFIG_FILENAME];
1347
+ for (const marker of markers) {
1348
+ if (await fileExists(path4__namespace.default.join(currentDir, marker))) {
1349
+ return currentDir;
1350
+ }
1351
+ }
1352
+ const parentDir = path4__namespace.default.dirname(currentDir);
1353
+ if (parentDir === currentDir) break;
1354
+ currentDir = parentDir;
1355
+ }
1356
+ return null;
1357
+ };
1358
+ var loadProjectEnv = async (targetPath) => {
1359
+ const projectRoot = await findProjectRoot(targetPath);
1360
+ if (projectRoot) {
1361
+ const envPath = path4__namespace.default.join(projectRoot, ".env");
1362
+ if (await fileExists(envPath)) {
1363
+ dotenv2__default.default.config({ path: envPath, override: false });
1364
+ console.log(`Loaded .env from ${projectRoot}`);
1365
+ }
1366
+ }
1367
+ };
1368
+ var fileExists = async (filePath) => {
1369
+ try {
1370
+ await fs3__default.default.access(filePath);
1371
+ return true;
1372
+ } catch {
1373
+ return false;
1374
+ }
1375
+ };
1376
+ var collectYamlFiles = async (target) => {
1377
+ const stat = await fs3__default.default.stat(target);
1378
+ if (stat.isFile()) return [target];
1379
+ if (!stat.isDirectory()) {
1380
+ throw new Error(`Unsupported target: ${target}`);
1381
+ }
1382
+ const entries = await fs3__default.default.readdir(target, { withFileTypes: true });
1383
+ const files = [];
1384
+ for (const entry of entries) {
1385
+ const fullPath = path4__namespace.default.join(target, entry.name);
1386
+ if (entry.isDirectory()) {
1387
+ const nested = await collectYamlFiles(fullPath);
1388
+ files.push(...nested);
1389
+ } else if (entry.isFile() && (entry.name.endsWith(".yaml") || entry.name.endsWith(".yml"))) {
1390
+ files.push(fullPath);
1391
+ }
1392
+ }
1393
+ return files;
1394
+ };
1395
+ var discoverTestFiles = async (testsDir = "tests") => {
1396
+ const absoluteDir = path4__namespace.default.resolve(testsDir);
1397
+ if (!await fileExists(absoluteDir)) {
1398
+ return { pipelines: [], workflows: [], tests: [] };
1399
+ }
1400
+ const allFiles = await collectYamlFiles(absoluteDir);
1401
+ const pipelines = [];
1402
+ const workflows = [];
1403
+ const tests = [];
1404
+ for (const file of allFiles) {
1405
+ const name = path4__namespace.default.basename(file).toLowerCase();
1406
+ if (name.endsWith(".pipeline.yaml") || name.endsWith(".pipeline.yml")) {
1407
+ pipelines.push(file);
1408
+ } else if (name.endsWith(".workflow.yaml") || name.endsWith(".workflow.yml")) {
1409
+ workflows.push(file);
1410
+ } else if (name.endsWith(".test.yaml") || name.endsWith(".test.yml")) {
1411
+ tests.push(file);
1412
+ }
1413
+ }
1414
+ return { pipelines, workflows, tests };
1415
+ };
1416
+ var writeFileIfMissing = async (filePath, contents) => {
1417
+ if (await fileExists(filePath)) return;
1418
+ await fs3__default.default.mkdir(path4__namespace.default.dirname(filePath), { recursive: true });
1419
+ await fs3__default.default.writeFile(filePath, contents, "utf8");
1420
+ };
1421
+ var initCommand = async () => {
1422
+ const configTemplate = `defaults:
1423
+ timeout: 30000
1424
+ screenshots: on-failure
1425
+
1426
+ platforms:
1427
+ web:
1428
+ baseUrl: http://localhost:3000
1429
+ headless: true
1430
+
1431
+ ai:
1432
+ provider: anthropic
1433
+ model: claude-3-5-sonnet-20241022
1434
+ apiKey: \${ANTHROPIC_API_KEY}
1435
+ temperature: 0
1436
+ maxTokens: 4096
1437
+
1438
+ email:
1439
+ provider: inbucket
1440
+ endpoint: http://localhost:9000
1441
+
1442
+ appwrite:
1443
+ endpoint: https://cloud.appwrite.io/v1
1444
+ projectId: your-project-id
1445
+ apiKey: your-api-key
1446
+ `;
1447
+ const sampleTest = `name: Example web smoke test
1448
+ platform: web
1449
+ config:
1450
+ web:
1451
+ baseUrl: http://localhost:3000
1452
+
1453
+ steps:
1454
+ - type: navigate
1455
+ value: /
1456
+
1457
+ - type: assert
1458
+ target:
1459
+ text: "Welcome"
1460
+ `;
1461
+ await writeFileIfMissing(path4__namespace.default.resolve(CONFIG_FILENAME), configTemplate);
1462
+ await writeFileIfMissing(path4__namespace.default.resolve("tests", "example.web.test.yaml"), sampleTest);
1463
+ console.log("Initialized intellitester.config.yaml and tests/example.web.test.yaml");
1464
+ };
1465
+ var validateCommand = async (target) => {
1466
+ const absoluteTarget = path4__namespace.default.resolve(target);
1467
+ const files = await collectYamlFiles(absoluteTarget);
1468
+ if (files.length === 0) {
1469
+ throw new Error(`No YAML files found at ${absoluteTarget}`);
1470
+ }
1471
+ for (const file of files) {
1472
+ await chunkXWGUA67E_cjs.loadTestDefinition(file);
1473
+ console.log(`\u2713 ${path4__namespace.default.relative(process2__default.default.cwd(), file)} valid`);
1474
+ }
1475
+ };
1476
+ var resolveBaseUrl = (test, configBaseUrl) => test.config?.web?.baseUrl ?? configBaseUrl;
1477
+ var runTestCommand = async (target, options) => {
1478
+ const absoluteTarget = path4__namespace.default.resolve(target);
1479
+ await loadProjectEnv(absoluteTarget);
1480
+ const { parse: parse2 } = await import('yaml');
1481
+ const testContent = await fs3__default.default.readFile(absoluteTarget, "utf8");
1482
+ const parsedTest = parse2(testContent);
1483
+ const hasConfigFile = await fileExists(CONFIG_FILENAME);
1484
+ let parsedConfig = void 0;
1485
+ if (hasConfigFile) {
1486
+ const configContent = await fs3__default.default.readFile(CONFIG_FILENAME, "utf8");
1487
+ parsedConfig = parse2(configContent);
1488
+ }
1489
+ const configMissing = parsedConfig ? chunkXWGUA67E_cjs.collectMissingEnvVars(parsedConfig) : [];
1490
+ const testMissing = chunkXWGUA67E_cjs.collectMissingEnvVars(parsedTest);
1491
+ const allMissing = [.../* @__PURE__ */ new Set([...configMissing, ...testMissing])];
1492
+ if (allMissing.length > 0) {
1493
+ const projectRoot = await findProjectRoot(absoluteTarget);
1494
+ const canContinue = await validateEnvVars(allMissing, projectRoot || process2__default.default.cwd());
1495
+ if (!canContinue) {
1496
+ process2__default.default.exit(1);
1497
+ }
1498
+ }
1499
+ const test = await chunkXWGUA67E_cjs.loadTestDefinition(absoluteTarget);
1500
+ const config = hasConfigFile ? await chunkXWGUA67E_cjs.loadIntellitesterConfig(CONFIG_FILENAME) : void 0;
1501
+ const baseUrl = resolveBaseUrl(test, config?.platforms?.web?.baseUrl);
1502
+ const headed = options.headed ?? false;
1503
+ const browser = options.browser ?? "chromium";
1504
+ const skipWebServer = options.noServer ?? false;
1505
+ const debug = options.debug ?? false;
1506
+ const interactive = options.interactive ?? false;
1507
+ const modeFlags = [];
1508
+ if (headed) modeFlags.push("headed");
1509
+ if (debug) modeFlags.push("debug mode");
1510
+ if (interactive) modeFlags.push("interactive");
1511
+ console.log(
1512
+ `Running ${path4__namespace.default.basename(absoluteTarget)} on web (${browser}${modeFlags.length > 0 ? ", " + modeFlags.join(", ") : ""})`
1513
+ );
1514
+ const result = await chunkXWGUA67E_cjs.runWebTest(test, {
1515
+ baseUrl,
1516
+ headed,
1517
+ browser,
1518
+ defaultTimeoutMs: config?.defaults?.timeout,
1519
+ webServer: !skipWebServer && config?.webServer ? config.webServer : void 0,
1520
+ debug,
1521
+ interactive,
1522
+ aiConfig: interactive ? config?.ai : void 0
1523
+ });
1524
+ for (const step of result.steps) {
1525
+ const label = `[${step.status === "passed" ? "OK" : "FAIL"}] ${step.action.type}`;
1526
+ if (step.error) {
1527
+ console.error(`${label} - ${step.error}`);
1528
+ } else {
1529
+ console.log(label);
1530
+ }
1531
+ }
1532
+ if (result.status === "failed") {
1533
+ process2__default.default.exitCode = 1;
1534
+ }
1535
+ };
1536
+ var generateCommand = async (prompt, options) => {
1537
+ const targetPath = options.output ? path4__namespace.default.resolve(options.output) : process2__default.default.cwd();
1538
+ await loadProjectEnv(targetPath);
1539
+ const hasConfigFile = await fileExists(CONFIG_FILENAME);
1540
+ if (!hasConfigFile) {
1541
+ throw new Error('No intellitester.config.yaml found. Run "intellitester init" first and configure AI settings.');
1542
+ }
1543
+ const { parse: parse2 } = await import('yaml');
1544
+ const configContent = await fs3__default.default.readFile(CONFIG_FILENAME, "utf8");
1545
+ const parsedConfig = parse2(configContent);
1546
+ const configMissing = chunkXWGUA67E_cjs.collectMissingEnvVars(parsedConfig);
1547
+ if (configMissing.length > 0) {
1548
+ const projectRoot = await findProjectRoot(CONFIG_FILENAME);
1549
+ const canContinue = await validateEnvVars(configMissing, projectRoot || process2__default.default.cwd());
1550
+ if (!canContinue) {
1551
+ process2__default.default.exit(1);
1552
+ }
1553
+ }
1554
+ const config = await chunkXWGUA67E_cjs.loadIntellitesterConfig(CONFIG_FILENAME);
1555
+ if (!config.ai) {
1556
+ throw new Error('AI configuration missing in intellitester.config.yaml. Add "ai:" section with provider, model, and apiKey.');
1557
+ }
1558
+ const source = options.noSource ? null : options.pagesDir || options.componentsDir ? {
1559
+ pagesDir: options.pagesDir,
1560
+ componentsDir: options.componentsDir
1561
+ } : void 0;
1562
+ const generateOptions = {
1563
+ aiConfig: config.ai,
1564
+ baseUrl: options.baseUrl,
1565
+ platform: options.platform,
1566
+ source
1567
+ };
1568
+ console.log("Generating test...");
1569
+ const result = await generateTest(prompt, generateOptions);
1570
+ if (!result.success) {
1571
+ throw new Error(result.error || "Failed to generate test");
1572
+ }
1573
+ if (options.output) {
1574
+ await fs3__default.default.mkdir(path4__namespace.default.dirname(options.output), { recursive: true });
1575
+ await fs3__default.default.writeFile(options.output, result.yaml, "utf8");
1576
+ console.log(`\u2713 Test saved to ${options.output}`);
1577
+ } else {
1578
+ console.log("\n--- Generated Test ---\n");
1579
+ console.log(result.yaml);
1580
+ }
1581
+ };
1582
+ var runWorkflowCommand = async (file, options) => {
1583
+ const workflowPath = path4__namespace.default.resolve(file);
1584
+ if (!await fileExists(workflowPath)) {
1585
+ logError(`Workflow file not found: ${file}`);
1586
+ process2__default.default.exit(1);
1587
+ }
1588
+ await loadProjectEnv(workflowPath);
1589
+ console.log(`Running workflow: ${file}`);
1590
+ const { parse: parse2 } = await import('yaml');
1591
+ const workflowContent = await fs3__default.default.readFile(workflowPath, "utf8");
1592
+ const parsedWorkflow = parse2(workflowContent);
1593
+ const hasConfigFile = await fileExists(CONFIG_FILENAME);
1594
+ let parsedConfig = void 0;
1595
+ if (hasConfigFile) {
1596
+ const configContent = await fs3__default.default.readFile(CONFIG_FILENAME, "utf8");
1597
+ parsedConfig = parse2(configContent);
1598
+ }
1599
+ const configMissing = parsedConfig ? chunkXWGUA67E_cjs.collectMissingEnvVars(parsedConfig) : [];
1600
+ const workflowMissing = chunkXWGUA67E_cjs.collectMissingEnvVars(parsedWorkflow);
1601
+ const allMissing = [.../* @__PURE__ */ new Set([...configMissing, ...workflowMissing])];
1602
+ if (allMissing.length > 0) {
1603
+ const projectRoot = await findProjectRoot(workflowPath);
1604
+ const canContinue = await validateEnvVars(allMissing, projectRoot || process2__default.default.cwd());
1605
+ if (!canContinue) {
1606
+ process2__default.default.exit(1);
1607
+ }
1608
+ }
1609
+ const workflow = await chunkXWGUA67E_cjs.loadWorkflowDefinition(workflowPath);
1610
+ const config = hasConfigFile ? await chunkXWGUA67E_cjs.loadIntellitesterConfig(CONFIG_FILENAME) : void 0;
1611
+ const result = await chunkXWGUA67E_cjs.runWorkflow(workflow, workflowPath, {
1612
+ headed: options.visible,
1613
+ browser: options.browser,
1614
+ interactive: options.interactive,
1615
+ debug: options.debug,
1616
+ aiConfig: config?.ai
1617
+ });
1618
+ console.log(`
1619
+ Workflow: ${workflow.name}`);
1620
+ console.log(`Session ID: ${result.sessionId}`);
1621
+ console.log(`Status: ${result.status}
1622
+ `);
1623
+ for (const test of result.tests) {
1624
+ const icon = test.status === "passed" ? "\u2713" : test.status === "failed" ? "\u2717" : "\u25CB";
1625
+ console.log(` ${icon} ${test.file} (${test.status})`);
1626
+ if (test.error) {
1627
+ console.log(` Error: ${test.error}`);
1628
+ }
1629
+ }
1630
+ if (result.cleanupResult) {
1631
+ console.log(`
1632
+ Cleanup: ${result.cleanupResult.deleted.length} resources deleted`);
1633
+ if (result.cleanupResult.failed.length > 0) {
1634
+ console.log(` Failed to delete: ${result.cleanupResult.failed.join(", ")}`);
1635
+ }
1636
+ }
1637
+ process2__default.default.exit(result.status === "passed" ? 0 : 1);
1638
+ };
1639
+ var runPipelineCommand = async (file, options) => {
1640
+ const pipelinePath = path4__namespace.default.resolve(file);
1641
+ if (!await fileExists(pipelinePath)) {
1642
+ logError(`Pipeline file not found: ${file}`);
1643
+ process2__default.default.exit(1);
1644
+ }
1645
+ await loadProjectEnv(pipelinePath);
1646
+ console.log(`Running pipeline: ${file}`);
1647
+ const { parse: parse2 } = await import('yaml');
1648
+ const pipelineContent = await fs3__default.default.readFile(pipelinePath, "utf8");
1649
+ const parsedPipeline = parse2(pipelineContent);
1650
+ const hasConfigFile = await fileExists(CONFIG_FILENAME);
1651
+ let parsedConfig = void 0;
1652
+ if (hasConfigFile) {
1653
+ const configContent = await fs3__default.default.readFile(CONFIG_FILENAME, "utf8");
1654
+ parsedConfig = parse2(configContent);
1655
+ }
1656
+ const configMissing = parsedConfig ? chunkXWGUA67E_cjs.collectMissingEnvVars(parsedConfig) : [];
1657
+ const pipelineMissing = chunkXWGUA67E_cjs.collectMissingEnvVars(parsedPipeline);
1658
+ const allMissing = [.../* @__PURE__ */ new Set([...configMissing, ...pipelineMissing])];
1659
+ if (allMissing.length > 0) {
1660
+ const projectRoot = await findProjectRoot(pipelinePath);
1661
+ const canContinue = await validateEnvVars(allMissing, projectRoot || process2__default.default.cwd());
1662
+ if (!canContinue) {
1663
+ process2__default.default.exit(1);
1664
+ }
1665
+ }
1666
+ const pipeline = await chunkXWGUA67E_cjs.loadPipelineDefinition(pipelinePath);
1667
+ hasConfigFile ? await chunkXWGUA67E_cjs.loadIntellitesterConfig(CONFIG_FILENAME) : void 0;
1668
+ const result = await runPipeline(pipeline, pipelinePath, {
1669
+ headed: options.visible,
1670
+ browser: options.browser,
1671
+ interactive: options.interactive,
1672
+ debug: options.debug
1673
+ });
1674
+ console.log(`
1675
+ Pipeline: ${pipeline.name}`);
1676
+ console.log(`Session ID: ${result.sessionId}`);
1677
+ console.log(`Status: ${result.status}
1678
+ `);
1679
+ for (const workflow of result.workflows) {
1680
+ const icon = workflow.status === "passed" ? "\u2713" : workflow.status === "failed" ? "\u2717" : "\u25CB";
1681
+ console.log(` ${icon} ${workflow.file} (${workflow.status})`);
1682
+ if (workflow.error) {
1683
+ console.log(` Error: ${workflow.error}`);
1684
+ }
1685
+ }
1686
+ if (result.cleanupResult) {
1687
+ console.log(`
1688
+ Cleanup: ${result.cleanupResult.deleted.length} resources deleted`);
1689
+ if (result.cleanupResult.failed.length > 0) {
1690
+ console.log(` Failed to delete: ${result.cleanupResult.failed.join(", ")}`);
1691
+ }
1692
+ }
1693
+ process2__default.default.exit(result.status === "passed" ? 0 : 1);
1694
+ };
1695
+ var main = async () => {
1696
+ const program = new commander.Command();
1697
+ program.name("intellitester").description("AI-powered cross-platform test automation").version("1.0.0");
1698
+ program.command("init").description("Initialize IntelliTester in current directory").action(async () => {
1699
+ try {
1700
+ await initCommand();
1701
+ } catch (error) {
1702
+ const message = error instanceof Error ? error.message : String(error);
1703
+ logError(message);
1704
+ process2__default.default.exitCode = 1;
1705
+ }
1706
+ });
1707
+ program.command("validate").description("Validate test YAML files").argument("[file]", "Test file or directory to validate", "tests").action(async (file) => {
1708
+ try {
1709
+ await validateCommand(file);
1710
+ } catch (error) {
1711
+ const message = error instanceof Error ? error.message : String(error);
1712
+ logError(message);
1713
+ process2__default.default.exitCode = 1;
1714
+ }
1715
+ });
1716
+ program.command("run").description("Run test file(s), workflow, or auto-discover tests in tests/ directory").argument("[file]", "Test file, workflow, or pipeline to run (auto-discovers if omitted)").option("--visible", "Run browser in visible mode (not headless)").option("--browser <name>", "Browser to use (chrome, safari, firefox)", "chrome").option("--preview", "Build project and run against preview server").option("--no-server", "Skip auto-starting web server").option("-i, --interactive", "Interactive mode - AI suggests fixes on failure").option("--debug", "Debug mode - verbose logging").action(async (file, options) => {
1717
+ let previewCleanup = null;
1718
+ try {
1719
+ const browser = resolveBrowserName(options.browser || "chrome");
1720
+ if (options.preview) {
1721
+ const hasConfigFile = await fileExists(CONFIG_FILENAME);
1722
+ const config = hasConfigFile ? await chunkXWGUA67E_cjs.loadIntellitesterConfig(CONFIG_FILENAME) : void 0;
1723
+ const { cleanup } = await buildAndPreview(config, process2__default.default.cwd());
1724
+ previewCleanup = cleanup;
1725
+ }
1726
+ const runOpts = {
1727
+ visible: options.visible,
1728
+ browser,
1729
+ interactive: options.interactive,
1730
+ debug: options.debug
1731
+ };
1732
+ if (!file) {
1733
+ const discovered = await discoverTestFiles("tests");
1734
+ const total = discovered.pipelines.length + discovered.workflows.length + discovered.tests.length;
1735
+ if (total === 0) {
1736
+ logError("No test files found in tests/ directory. Create .pipeline.yaml, .workflow.yaml, or .test.yaml files.");
1737
+ process2__default.default.exit(1);
1738
+ }
1739
+ console.log(`Discovered ${total} test file(s):`);
1740
+ if (discovered.pipelines.length > 0) {
1741
+ console.log(` Pipelines: ${discovered.pipelines.length}`);
1742
+ }
1743
+ if (discovered.workflows.length > 0) {
1744
+ console.log(` Workflows: ${discovered.workflows.length}`);
1745
+ }
1746
+ if (discovered.tests.length > 0) {
1747
+ console.log(` Tests: ${discovered.tests.length}`);
1748
+ }
1749
+ console.log("");
1750
+ let failed = false;
1751
+ for (const pipeline of discovered.pipelines) {
1752
+ try {
1753
+ await runPipelineCommand(pipeline, runOpts);
1754
+ } catch {
1755
+ failed = true;
1756
+ }
1757
+ }
1758
+ for (const workflow of discovered.workflows) {
1759
+ try {
1760
+ await runWorkflowCommand(workflow, runOpts);
1761
+ } catch {
1762
+ failed = true;
1763
+ }
1764
+ }
1765
+ for (const test of discovered.tests) {
1766
+ try {
1767
+ await runTestCommand(test, {
1768
+ headed: options.visible,
1769
+ browser,
1770
+ noServer: !options.server,
1771
+ interactive: options.interactive,
1772
+ debug: options.debug
1773
+ });
1774
+ } catch {
1775
+ failed = true;
1776
+ }
1777
+ }
1778
+ if (failed) {
1779
+ process2__default.default.exitCode = 1;
1780
+ }
1781
+ return;
1782
+ }
1783
+ if (chunkXWGUA67E_cjs.isPipelineFile(file)) {
1784
+ await runPipelineCommand(file, runOpts);
1785
+ return;
1786
+ }
1787
+ if (chunkXWGUA67E_cjs.isWorkflowFile(file)) {
1788
+ await runWorkflowCommand(file, runOpts);
1789
+ return;
1790
+ }
1791
+ await runTestCommand(file, {
1792
+ headed: options.visible,
1793
+ browser,
1794
+ noServer: !options.server,
1795
+ interactive: options.interactive,
1796
+ debug: options.debug
1797
+ });
1798
+ } catch (error) {
1799
+ const message = error instanceof Error ? error.message : String(error);
1800
+ logError(message);
1801
+ process2__default.default.exitCode = 1;
1802
+ } finally {
1803
+ if (previewCleanup) {
1804
+ previewCleanup();
1805
+ }
1806
+ }
1807
+ });
1808
+ program.command("generate").description("Generate test from natural language").argument("<description>", "Natural language description of the test").option("--output <file>", "Output file path").option("--platform <platform>", "Target platform", "web").option("--baseUrl <url>", "Base URL for the app").option("--pagesDir <dir>", "Pages directory for source scanning").option("--componentsDir <dir>", "Components directory for source scanning").option("--no-source", "Disable source scanning").action(async (description, options) => {
1809
+ try {
1810
+ await generateCommand(description, {
1811
+ output: options.output,
1812
+ platform: options.platform,
1813
+ baseUrl: options.baseUrl,
1814
+ pagesDir: options.pagesDir,
1815
+ componentsDir: options.componentsDir,
1816
+ noSource: !options.source
1817
+ });
1818
+ } catch (error) {
1819
+ const message = error instanceof Error ? error.message : String(error);
1820
+ logError(message);
1821
+ process2__default.default.exitCode = 1;
1822
+ }
1823
+ });
1824
+ program.command("cleanup:list").description("List pending failed cleanup operations from previous test runs").action(async () => {
1825
+ try {
1826
+ const failedCleanups = await chunkARJYJVRM_cjs.loadFailedCleanups(process2__default.default.cwd());
1827
+ if (failedCleanups.length === 0) {
1828
+ console.log("No failed cleanups found.");
1829
+ return;
1830
+ }
1831
+ console.log(`
1832
+ Found ${failedCleanups.length} failed cleanup(s):
1833
+ `);
1834
+ for (const failed of failedCleanups) {
1835
+ console.log(`Session: ${failed.sessionId}`);
1836
+ console.log(` Timestamp: ${failed.timestamp}`);
1837
+ console.log(` Provider: ${failed.providerConfig.provider}`);
1838
+ console.log(` Resources: ${failed.resources.length}`);
1839
+ for (const resource of failed.resources) {
1840
+ console.log(` - ${resource.type}:${resource.id}`);
1841
+ }
1842
+ console.log(` Errors: ${failed.errors.length}`);
1843
+ for (const error of failed.errors.slice(0, 3)) {
1844
+ console.log(` - ${error}`);
1845
+ }
1846
+ if (failed.errors.length > 3) {
1847
+ console.log(` ... and ${failed.errors.length - 3} more`);
1848
+ }
1849
+ console.log("");
1850
+ }
1851
+ console.log(`Use 'intellitester cleanup:retry' to retry these cleanups.
1852
+ `);
1853
+ } catch (error) {
1854
+ logError(error instanceof Error ? error.message : String(error));
1855
+ process2__default.default.exit(1);
1856
+ }
1857
+ });
1858
+ program.command("cleanup:retry").description("Retry failed cleanup operations from previous test runs").action(async () => {
1859
+ try {
1860
+ const hasConfigFile = await fileExists(CONFIG_FILENAME);
1861
+ if (!hasConfigFile) {
1862
+ throw new Error(`No ${CONFIG_FILENAME} found. Cannot retry cleanup without provider configuration.`);
1863
+ }
1864
+ const config = await chunkXWGUA67E_cjs.loadIntellitesterConfig(CONFIG_FILENAME);
1865
+ const failedCleanups = await chunkARJYJVRM_cjs.loadFailedCleanups(process2__default.default.cwd());
1866
+ if (failedCleanups.length === 0) {
1867
+ console.log("No failed cleanups to retry.");
1868
+ return;
1869
+ }
1870
+ console.log(`Found ${failedCleanups.length} failed cleanup(s) to retry.`);
1871
+ for (const failed of failedCleanups) {
1872
+ console.log(`
1873
+ Retrying cleanup for session ${failed.sessionId}...`);
1874
+ console.log(` Provider: ${failed.providerConfig.provider}`);
1875
+ console.log(` Resources: ${failed.resources.length}`);
1876
+ const provider = failed.providerConfig.provider;
1877
+ const cleanupConfig = {
1878
+ provider,
1879
+ parallel: false,
1880
+ retries: 3
1881
+ };
1882
+ const configAny = config;
1883
+ if (provider === "appwrite") {
1884
+ if (!config.appwrite?.apiKey) {
1885
+ console.log(` \u2717 Skipping: Appwrite API key not configured in ${CONFIG_FILENAME}`);
1886
+ continue;
1887
+ }
1888
+ cleanupConfig.appwrite = {
1889
+ endpoint: failed.providerConfig.endpoint,
1890
+ projectId: failed.providerConfig.projectId,
1891
+ apiKey: config.appwrite.apiKey
1892
+ };
1893
+ } else if (provider === "postgres") {
1894
+ const pgConfig = configAny.postgres;
1895
+ if (!pgConfig?.connectionString && !pgConfig?.password) {
1896
+ console.log(` \u2717 Skipping: Postgres credentials not configured in ${CONFIG_FILENAME}`);
1897
+ continue;
1898
+ }
1899
+ if (pgConfig.connectionString) {
1900
+ cleanupConfig.postgres = {
1901
+ connectionString: pgConfig.connectionString
1902
+ };
1903
+ } else {
1904
+ const host = failed.providerConfig.host;
1905
+ const port = failed.providerConfig.port;
1906
+ const database = failed.providerConfig.database;
1907
+ const user = failed.providerConfig.user;
1908
+ const password = pgConfig.password;
1909
+ cleanupConfig.postgres = {
1910
+ connectionString: `postgresql://${user}:${password}@${host}:${port}/${database}`
1911
+ };
1912
+ }
1913
+ } else if (provider === "mysql") {
1914
+ const mysqlConfig = configAny.mysql;
1915
+ if (!mysqlConfig?.password) {
1916
+ console.log(` \u2717 Skipping: MySQL password not configured in ${CONFIG_FILENAME}`);
1917
+ continue;
1918
+ }
1919
+ cleanupConfig.mysql = {
1920
+ host: failed.providerConfig.host,
1921
+ port: failed.providerConfig.port,
1922
+ user: failed.providerConfig.user,
1923
+ password: mysqlConfig.password,
1924
+ database: failed.providerConfig.database
1925
+ };
1926
+ } else if (provider === "sqlite") {
1927
+ const sqliteConfig = configAny.sqlite;
1928
+ if (!sqliteConfig?.database && !failed.providerConfig.database) {
1929
+ console.log(` \u2717 Skipping: SQLite database path not configured`);
1930
+ continue;
1931
+ }
1932
+ cleanupConfig.sqlite = {
1933
+ database: failed.providerConfig.database || sqliteConfig?.database
1934
+ };
1935
+ } else {
1936
+ console.log(` \u2717 Skipping: Unknown provider "${provider}"`);
1937
+ continue;
1938
+ }
1939
+ try {
1940
+ const { handlers, typeMappings } = await chunkARJYJVRM_cjs.loadCleanupHandlers(
1941
+ cleanupConfig,
1942
+ process2__default.default.cwd()
1943
+ );
1944
+ const result = await chunkARJYJVRM_cjs.executeCleanup(
1945
+ failed.resources,
1946
+ handlers,
1947
+ typeMappings,
1948
+ {
1949
+ parallel: false,
1950
+ retries: 3,
1951
+ cwd: process2__default.default.cwd()
1952
+ // Don't save failed cleanups again during retry
1953
+ }
1954
+ );
1955
+ if (result.success) {
1956
+ console.log(` \u2713 Successfully cleaned up ${result.deleted.length} resources`);
1957
+ await chunkARJYJVRM_cjs.removeFailedCleanup(failed.sessionId, process2__default.default.cwd());
1958
+ } else {
1959
+ console.log(` \u26A0 Partial cleanup: ${result.deleted.length} deleted, ${result.failed.length} failed`);
1960
+ for (const failedResource of result.failed) {
1961
+ console.log(` \u2717 ${failedResource}`);
1962
+ }
1963
+ }
1964
+ } catch (error) {
1965
+ console.log(` \u2717 Error during cleanup: ${error instanceof Error ? error.message : String(error)}`);
1966
+ }
1967
+ }
1968
+ const remaining = await chunkARJYJVRM_cjs.loadFailedCleanups(process2__default.default.cwd());
1969
+ if (remaining.length === 0) {
1970
+ console.log("\n\u2713 All failed cleanups have been resolved.");
1971
+ } else {
1972
+ console.log(`
1973
+ \u26A0 ${remaining.length} failed cleanup(s) still remaining.`);
1974
+ console.log(` Use 'intellitester cleanup:list' to see details.`);
1975
+ }
1976
+ } catch (error) {
1977
+ logError(error instanceof Error ? error.message : String(error));
1978
+ process2__default.default.exit(1);
1979
+ }
1980
+ });
1981
+ await program.parseAsync(process2__default.default.argv);
1982
+ };
1983
+ main();
1984
+ //# sourceMappingURL=index.cjs.map
1985
+ //# sourceMappingURL=index.cjs.map