guardrail-ship 1.0.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.
Files changed (119) hide show
  1. package/dist/index.d.ts +7 -0
  2. package/dist/index.d.ts.map +1 -0
  3. package/dist/index.js +7 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/mock-implementation.d.ts +1 -0
  6. package/dist/mock-implementation.d.ts.map +1 -0
  7. package/dist/mock-implementation.js +2 -0
  8. package/dist/mock-implementation.js.map +1 -0
  9. package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts +5 -0
  10. package/dist/mockproof/__tests__/import-graph-scanner.test.d.ts.map +1 -0
  11. package/dist/mockproof/__tests__/import-graph-scanner.test.js +92 -0
  12. package/dist/mockproof/__tests__/import-graph-scanner.test.js.map +1 -0
  13. package/dist/mockproof/import-graph-scanner.d.ts +93 -0
  14. package/dist/mockproof/import-graph-scanner.d.ts.map +1 -0
  15. package/dist/mockproof/import-graph-scanner.js +411 -0
  16. package/dist/mockproof/import-graph-scanner.js.map +1 -0
  17. package/dist/mockproof/index.d.ts +10 -0
  18. package/dist/mockproof/index.d.ts.map +1 -0
  19. package/dist/mockproof/index.js +10 -0
  20. package/dist/mockproof/index.js.map +1 -0
  21. package/dist/reality-mode/auth-enforcer.d.ts +13 -0
  22. package/dist/reality-mode/auth-enforcer.d.ts.map +1 -0
  23. package/dist/reality-mode/auth-enforcer.js +90 -0
  24. package/dist/reality-mode/auth-enforcer.js.map +1 -0
  25. package/dist/reality-mode/explorer/critical-flows.d.ts +71 -0
  26. package/dist/reality-mode/explorer/critical-flows.d.ts.map +1 -0
  27. package/dist/reality-mode/explorer/critical-flows.js +463 -0
  28. package/dist/reality-mode/explorer/critical-flows.js.map +1 -0
  29. package/dist/reality-mode/explorer/flow-parser.d.ts +52 -0
  30. package/dist/reality-mode/explorer/flow-parser.d.ts.map +1 -0
  31. package/dist/reality-mode/explorer/flow-parser.js +250 -0
  32. package/dist/reality-mode/explorer/flow-parser.js.map +1 -0
  33. package/dist/reality-mode/explorer/index.d.ts +11 -0
  34. package/dist/reality-mode/explorer/index.d.ts.map +1 -0
  35. package/dist/reality-mode/explorer/index.js +11 -0
  36. package/dist/reality-mode/explorer/index.js.map +1 -0
  37. package/dist/reality-mode/explorer/runtime-explorer.d.ts +35 -0
  38. package/dist/reality-mode/explorer/runtime-explorer.d.ts.map +1 -0
  39. package/dist/reality-mode/explorer/runtime-explorer.js +688 -0
  40. package/dist/reality-mode/explorer/runtime-explorer.js.map +1 -0
  41. package/dist/reality-mode/explorer/surface-discovery.d.ts +60 -0
  42. package/dist/reality-mode/explorer/surface-discovery.d.ts.map +1 -0
  43. package/dist/reality-mode/explorer/surface-discovery.js +357 -0
  44. package/dist/reality-mode/explorer/surface-discovery.js.map +1 -0
  45. package/dist/reality-mode/explorer/types.d.ts +275 -0
  46. package/dist/reality-mode/explorer/types.d.ts.map +1 -0
  47. package/dist/reality-mode/explorer/types.js +8 -0
  48. package/dist/reality-mode/explorer/types.js.map +1 -0
  49. package/dist/reality-mode/fake-success-detector.d.ts +10 -0
  50. package/dist/reality-mode/fake-success-detector.d.ts.map +1 -0
  51. package/dist/reality-mode/fake-success-detector.js +76 -0
  52. package/dist/reality-mode/fake-success-detector.js.map +1 -0
  53. package/dist/reality-mode/index.d.ts +14 -0
  54. package/dist/reality-mode/index.d.ts.map +1 -0
  55. package/dist/reality-mode/index.js +14 -0
  56. package/dist/reality-mode/index.js.map +1 -0
  57. package/dist/reality-mode/reality-scanner.d.ts +48 -0
  58. package/dist/reality-mode/reality-scanner.d.ts.map +1 -0
  59. package/dist/reality-mode/reality-scanner.js +516 -0
  60. package/dist/reality-mode/reality-scanner.js.map +1 -0
  61. package/dist/reality-mode/report-generator.d.ts +11 -0
  62. package/dist/reality-mode/report-generator.d.ts.map +1 -0
  63. package/dist/reality-mode/report-generator.js +233 -0
  64. package/dist/reality-mode/report-generator.js.map +1 -0
  65. package/dist/reality-mode/traffic-classifier.d.ts +14 -0
  66. package/dist/reality-mode/traffic-classifier.d.ts.map +1 -0
  67. package/dist/reality-mode/traffic-classifier.js +131 -0
  68. package/dist/reality-mode/traffic-classifier.js.map +1 -0
  69. package/dist/reality-mode/types.d.ts +90 -0
  70. package/dist/reality-mode/types.d.ts.map +1 -0
  71. package/dist/reality-mode/types.js +2 -0
  72. package/dist/reality-mode/types.js.map +1 -0
  73. package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts +5 -0
  74. package/dist/ship-badge/__tests__/ship-badge-generator.test.d.ts.map +1 -0
  75. package/dist/ship-badge/__tests__/ship-badge-generator.test.js +146 -0
  76. package/dist/ship-badge/__tests__/ship-badge-generator.test.js.map +1 -0
  77. package/dist/ship-badge/index.d.ts +9 -0
  78. package/dist/ship-badge/index.d.ts.map +1 -0
  79. package/dist/ship-badge/index.js +9 -0
  80. package/dist/ship-badge/index.js.map +1 -0
  81. package/dist/ship-badge/ship-badge-generator.d.ts +136 -0
  82. package/dist/ship-badge/ship-badge-generator.d.ts.map +1 -0
  83. package/dist/ship-badge/ship-badge-generator.js +681 -0
  84. package/dist/ship-badge/ship-badge-generator.js.map +1 -0
  85. package/package.json +20 -0
  86. package/src/index.ts +7 -0
  87. package/src/mock-implementation.ts +0 -0
  88. package/src/mockproof/__tests__/import-graph-scanner.test.ts +115 -0
  89. package/src/mockproof/import-graph-scanner.d.ts +93 -0
  90. package/src/mockproof/import-graph-scanner.d.ts.map +1 -0
  91. package/src/mockproof/import-graph-scanner.js +482 -0
  92. package/src/mockproof/import-graph-scanner.ts +540 -0
  93. package/src/mockproof/index.ts +18 -0
  94. package/src/reality-mode/auth-enforcer.ts +97 -0
  95. package/src/reality-mode/explorer/critical-flows.ts +504 -0
  96. package/src/reality-mode/explorer/flow-parser.ts +293 -0
  97. package/src/reality-mode/explorer/index.ts +22 -0
  98. package/src/reality-mode/explorer/runtime-explorer.ts +715 -0
  99. package/src/reality-mode/explorer/surface-discovery.ts +498 -0
  100. package/src/reality-mode/explorer/templates/example-flows/auth-flow.yaml +41 -0
  101. package/src/reality-mode/explorer/templates/example-flows/checkout-flow.yaml +66 -0
  102. package/src/reality-mode/explorer/templates/example-flows/contact-form.yaml +43 -0
  103. package/src/reality-mode/explorer/templates/github-action.yml +132 -0
  104. package/src/reality-mode/explorer/types.ts +356 -0
  105. package/src/reality-mode/fake-success-detector.ts +89 -0
  106. package/src/reality-mode/index.ts +19 -0
  107. package/src/reality-mode/reality-scanner.d.ts +123 -0
  108. package/src/reality-mode/reality-scanner.d.ts.map +1 -0
  109. package/src/reality-mode/reality-scanner.js +526 -0
  110. package/src/reality-mode/reality-scanner.ts +576 -0
  111. package/src/reality-mode/report-generator.ts +253 -0
  112. package/src/reality-mode/traffic-classifier.ts +169 -0
  113. package/src/reality-mode/types.ts +95 -0
  114. package/src/ship-badge/__tests__/ship-badge-generator.test.ts +162 -0
  115. package/src/ship-badge/index.ts +16 -0
  116. package/src/ship-badge/ship-badge-generator.d.ts +136 -0
  117. package/src/ship-badge/ship-badge-generator.d.ts.map +1 -0
  118. package/src/ship-badge/ship-badge-generator.js +779 -0
  119. package/src/ship-badge/ship-badge-generator.ts +873 -0
@@ -0,0 +1,504 @@
1
+ /**
2
+ * Critical Flows - Pre-built flow templates for common app patterns
3
+ *
4
+ * These are "known good" test sequences that Reality Mode can run
5
+ * against any app that follows standard patterns (login forms, signup, etc.)
6
+ */
7
+
8
+ import type { CriticalFlow, FlowStep, FlowAssertion } from "./types";
9
+
10
+ /**
11
+ * Standard authentication flow - tries common login patterns
12
+ */
13
+ export const AUTH_LOGIN_FLOW: CriticalFlow = {
14
+ id: "auth-login",
15
+ name: "Authentication - Login",
16
+ description: "Tests standard email/password login flow",
17
+ steps: [
18
+ { action: "navigate", target: "/login" },
19
+ { action: "wait", timeout: 2000 },
20
+ {
21
+ action: "fill",
22
+ target: 'input[name="email"], input[type="email"], #email',
23
+ value: "{{email}}",
24
+ },
25
+ {
26
+ action: "fill",
27
+ target: 'input[name="password"], input[type="password"], #password',
28
+ value: "{{password}}",
29
+ },
30
+ {
31
+ action: "click",
32
+ target:
33
+ 'button[type="submit"], input[type="submit"], button:has-text("Log in"), button:has-text("Sign in")',
34
+ },
35
+ { action: "wait", timeout: 5000 },
36
+ ],
37
+ assertions: [
38
+ { type: "url-contains", value: "/dashboard|/home|/app", critical: true },
39
+ { type: "no-errors", value: "", critical: true },
40
+ {
41
+ type: "element-hidden",
42
+ value: 'input[type="password"]',
43
+ critical: false,
44
+ },
45
+ ],
46
+ required: true,
47
+ };
48
+
49
+ /**
50
+ * Standard signup/registration flow
51
+ */
52
+ export const AUTH_SIGNUP_FLOW: CriticalFlow = {
53
+ id: "auth-signup",
54
+ name: "Authentication - Signup",
55
+ description: "Tests standard email/password registration flow",
56
+ steps: [
57
+ { action: "navigate", target: "/signup|/register|/sign-up" },
58
+ { action: "wait", timeout: 2000 },
59
+ {
60
+ action: "fill",
61
+ target: 'input[name="name"], input[name="fullName"], #name',
62
+ value: "{{name}}",
63
+ },
64
+ {
65
+ action: "fill",
66
+ target: 'input[name="email"], input[type="email"], #email',
67
+ value: "{{email}}",
68
+ },
69
+ {
70
+ action: "fill",
71
+ target: 'input[name="password"], input[type="password"], #password',
72
+ value: "{{password}}",
73
+ },
74
+ {
75
+ action: "fill",
76
+ target:
77
+ 'input[name="confirmPassword"], input[name="password_confirmation"]',
78
+ value: "{{password}}",
79
+ },
80
+ {
81
+ action: "click",
82
+ target:
83
+ 'button[type="submit"], button:has-text("Sign up"), button:has-text("Create account")',
84
+ },
85
+ { action: "wait", timeout: 5000 },
86
+ ],
87
+ assertions: [
88
+ { type: "no-errors", value: "", critical: true },
89
+ {
90
+ type: "url-contains",
91
+ value: "/verify|/confirm|/dashboard|/welcome",
92
+ critical: false,
93
+ },
94
+ ],
95
+ required: false,
96
+ };
97
+
98
+ /**
99
+ * Logout flow
100
+ */
101
+ export const AUTH_LOGOUT_FLOW: CriticalFlow = {
102
+ id: "auth-logout",
103
+ name: "Authentication - Logout",
104
+ description: "Tests logout functionality",
105
+ steps: [
106
+ {
107
+ action: "click",
108
+ target:
109
+ '[data-testid="logout"], button:has-text("Log out"), button:has-text("Sign out"), a:has-text("Logout")',
110
+ },
111
+ { action: "wait", timeout: 3000 },
112
+ ],
113
+ assertions: [
114
+ { type: "url-contains", value: "/login|/|/home", critical: true },
115
+ { type: "no-errors", value: "", critical: true },
116
+ ],
117
+ required: false,
118
+ };
119
+
120
+ /**
121
+ * Profile update flow
122
+ */
123
+ export const PROFILE_UPDATE_FLOW: CriticalFlow = {
124
+ id: "profile-update",
125
+ name: "Profile - Update",
126
+ description: "Tests profile/settings update flow",
127
+ steps: [
128
+ { action: "navigate", target: "/settings|/profile|/account" },
129
+ { action: "wait", timeout: 2000 },
130
+ {
131
+ action: "fill",
132
+ target: 'input[name="name"], input[name="displayName"], #name',
133
+ value: "{{name}} Updated",
134
+ },
135
+ {
136
+ action: "click",
137
+ target:
138
+ 'button[type="submit"], button:has-text("Save"), button:has-text("Update")',
139
+ },
140
+ { action: "wait", timeout: 3000 },
141
+ ],
142
+ assertions: [
143
+ { type: "no-errors", value: "", critical: true },
144
+ {
145
+ type: "element-visible",
146
+ value: '[data-testid="success"], .toast, .notification',
147
+ critical: false,
148
+ },
149
+ ],
150
+ required: false,
151
+ };
152
+
153
+ /**
154
+ * Search functionality flow
155
+ */
156
+ export const SEARCH_FLOW: CriticalFlow = {
157
+ id: "search",
158
+ name: "Search",
159
+ description: "Tests search functionality",
160
+ steps: [
161
+ {
162
+ action: "click",
163
+ target:
164
+ '[data-testid="search"], button[aria-label="Search"], input[type="search"], .search-input',
165
+ },
166
+ {
167
+ action: "fill",
168
+ target:
169
+ 'input[type="search"], input[name="search"], input[name="q"], .search-input',
170
+ value: "test query",
171
+ },
172
+ {
173
+ action: "click",
174
+ target: 'button[type="submit"], button:has-text("Search")',
175
+ },
176
+ { action: "wait", timeout: 3000 },
177
+ ],
178
+ assertions: [
179
+ { type: "no-errors", value: "", critical: true },
180
+ {
181
+ type: "element-visible",
182
+ value: '[data-testid="results"], .results, .search-results',
183
+ critical: false,
184
+ },
185
+ ],
186
+ required: false,
187
+ };
188
+
189
+ /**
190
+ * Modal interaction flow
191
+ */
192
+ export const MODAL_FLOW: CriticalFlow = {
193
+ id: "modal-interaction",
194
+ name: "Modal - Open/Close",
195
+ description: "Tests modal dialog functionality",
196
+ steps: [
197
+ {
198
+ action: "click",
199
+ target:
200
+ '[data-modal-trigger], button[aria-haspopup="dialog"], [data-testid*="modal"]',
201
+ },
202
+ { action: "wait", timeout: 1000 },
203
+ {
204
+ action: "assert",
205
+ target: '[role="dialog"], .modal, [data-state="open"]',
206
+ },
207
+ {
208
+ action: "click",
209
+ target:
210
+ '[data-testid="close"], button[aria-label="Close"], .modal-close, button:has-text("Cancel")',
211
+ },
212
+ { action: "wait", timeout: 500 },
213
+ ],
214
+ assertions: [
215
+ { type: "element-hidden", value: '[role="dialog"]', critical: true },
216
+ { type: "no-errors", value: "", critical: true },
217
+ ],
218
+ required: false,
219
+ };
220
+
221
+ /**
222
+ * Navigation flow - tests main nav links
223
+ */
224
+ export const NAVIGATION_FLOW: CriticalFlow = {
225
+ id: "navigation",
226
+ name: "Navigation",
227
+ description: "Tests main navigation links",
228
+ steps: [
229
+ { action: "click", target: "nav a:first-of-type, header a:first-of-type" },
230
+ { action: "wait", timeout: 2000 },
231
+ { action: "navigate", target: "/" },
232
+ {
233
+ action: "click",
234
+ target: "nav a:nth-of-type(2), header a:nth-of-type(2)",
235
+ },
236
+ { action: "wait", timeout: 2000 },
237
+ ],
238
+ assertions: [{ type: "no-errors", value: "", critical: true }],
239
+ required: false,
240
+ };
241
+
242
+ /**
243
+ * Form validation flow - tests that validation works
244
+ */
245
+ export const FORM_VALIDATION_FLOW: CriticalFlow = {
246
+ id: "form-validation",
247
+ name: "Form Validation",
248
+ description: "Tests that form validation prevents bad submissions",
249
+ steps: [
250
+ { action: "navigate", target: "/contact|/signup|/register" },
251
+ { action: "wait", timeout: 2000 },
252
+ // Submit empty form
253
+ { action: "click", target: 'button[type="submit"]' },
254
+ { action: "wait", timeout: 1000 },
255
+ ],
256
+ assertions: [
257
+ // Should show validation errors, not submit
258
+ {
259
+ type: "element-visible",
260
+ value: '.error, [data-error], [aria-invalid="true"], .invalid-feedback',
261
+ critical: true,
262
+ },
263
+ { type: "no-errors", value: "", critical: false },
264
+ ],
265
+ required: false,
266
+ };
267
+
268
+ /**
269
+ * Dark mode toggle flow
270
+ */
271
+ export const DARK_MODE_FLOW: CriticalFlow = {
272
+ id: "dark-mode",
273
+ name: "Dark Mode Toggle",
274
+ description: "Tests dark/light mode switching",
275
+ steps: [
276
+ {
277
+ action: "click",
278
+ target:
279
+ '[data-testid="theme-toggle"], button[aria-label*="theme"], button[aria-label*="dark"], .theme-toggle',
280
+ },
281
+ { action: "wait", timeout: 500 },
282
+ ],
283
+ assertions: [{ type: "no-errors", value: "", critical: true }],
284
+ required: false,
285
+ };
286
+
287
+ /**
288
+ * Checkout flow (e-commerce)
289
+ */
290
+ export const CHECKOUT_FLOW: CriticalFlow = {
291
+ id: "checkout",
292
+ name: "E-commerce - Checkout",
293
+ description: "Tests basic checkout flow",
294
+ steps: [
295
+ { action: "navigate", target: "/cart|/checkout" },
296
+ { action: "wait", timeout: 2000 },
297
+ {
298
+ action: "click",
299
+ target:
300
+ 'button:has-text("Checkout"), button:has-text("Continue"), a:has-text("Checkout")',
301
+ },
302
+ { action: "wait", timeout: 3000 },
303
+ ],
304
+ assertions: [
305
+ { type: "no-errors", value: "", critical: true },
306
+ { type: "url-contains", value: "/checkout|/payment", critical: false },
307
+ ],
308
+ required: false,
309
+ };
310
+
311
+ /**
312
+ * All available flow packs
313
+ */
314
+ export const FLOW_PACKS = {
315
+ auth: [AUTH_LOGIN_FLOW, AUTH_SIGNUP_FLOW, AUTH_LOGOUT_FLOW],
316
+ profile: [PROFILE_UPDATE_FLOW],
317
+ search: [SEARCH_FLOW],
318
+ ui: [MODAL_FLOW, NAVIGATION_FLOW, DARK_MODE_FLOW],
319
+ forms: [FORM_VALIDATION_FLOW],
320
+ ecommerce: [CHECKOUT_FLOW],
321
+ };
322
+
323
+ /**
324
+ * Get all critical flows
325
+ */
326
+ export function getAllFlows(): CriticalFlow[] {
327
+ return Object.values(FLOW_PACKS).flat();
328
+ }
329
+
330
+ /**
331
+ * Get flows by pack name
332
+ */
333
+ export function getFlowPack(packName: keyof typeof FLOW_PACKS): CriticalFlow[] {
334
+ return FLOW_PACKS[packName] || [];
335
+ }
336
+
337
+ /**
338
+ * Generate Playwright code for a critical flow
339
+ */
340
+ export function generateFlowTest(
341
+ flow: CriticalFlow,
342
+ testData: Record<string, string>,
343
+ ): string {
344
+ const steps = flow.steps
345
+ .map((step, idx) => {
346
+ let code = "";
347
+
348
+ // Replace template variables
349
+ const target = step.target?.replace(
350
+ /\{\{(\w+)\}\}/g,
351
+ (_, key) => testData[key] || "",
352
+ );
353
+ const value = step.value?.replace(
354
+ /\{\{(\w+)\}\}/g,
355
+ (_, key) => testData[key] || "",
356
+ );
357
+
358
+ switch (step.action) {
359
+ case "navigate":
360
+ // Handle multiple possible paths (pipe-separated)
361
+ if (target?.includes("|")) {
362
+ const paths = target.split("|");
363
+ code = `
364
+ // Step ${idx + 1}: Navigate to ${flow.name} page
365
+ let navigated = false;
366
+ for (const path of ${JSON.stringify(paths)}) {
367
+ try {
368
+ const response = await page.goto(CONFIG.baseUrl + path);
369
+ if (response?.status() < 400) { navigated = true; break; }
370
+ } catch {}
371
+ }
372
+ if (!navigated) { flowResult.failedAt = 'Navigation failed'; return; }`;
373
+ } else {
374
+ code = `
375
+ // Step ${idx + 1}: Navigate
376
+ await page.goto(CONFIG.baseUrl + '${target}');`;
377
+ }
378
+ break;
379
+
380
+ case "fill":
381
+ // Handle multiple possible selectors
382
+ if (target?.includes(",")) {
383
+ code = `
384
+ // Step ${idx + 1}: Fill field
385
+ const field${idx} = await page.$('${target}');
386
+ if (field${idx}) await field${idx}.fill('${value}');`;
387
+ } else {
388
+ code = `
389
+ // Step ${idx + 1}: Fill
390
+ await page.fill('${target}', '${value}').catch(() => {});`;
391
+ }
392
+ break;
393
+
394
+ case "click":
395
+ if (target?.includes(",") || target?.includes("|")) {
396
+ const selectors = target.split(/[,|]/).map((s) => s.trim());
397
+ code = `
398
+ // Step ${idx + 1}: Click
399
+ for (const sel of ${JSON.stringify(selectors)}) {
400
+ const el = await page.$(sel);
401
+ if (el && await el.isVisible()) { await el.click(); break; }
402
+ }`;
403
+ } else {
404
+ code = `
405
+ // Step ${idx + 1}: Click
406
+ await page.click('${target}').catch(() => {});`;
407
+ }
408
+ break;
409
+
410
+ case "wait":
411
+ code = `
412
+ // Step ${idx + 1}: Wait
413
+ await page.waitForTimeout(${step.timeout || 1000});
414
+ await page.waitForLoadState('networkidle').catch(() => {});`;
415
+ break;
416
+
417
+ case "assert":
418
+ code = `
419
+ // Step ${idx + 1}: Assert element exists
420
+ const exists${idx} = await page.$('${target}');
421
+ if (!exists${idx}) { flowResult.failedAt = 'Assertion failed: ${target}'; }`;
422
+ break;
423
+ }
424
+
425
+ return code;
426
+ })
427
+ .join("\n");
428
+
429
+ const assertions = flow.assertions
430
+ .map((assertion, idx) => {
431
+ switch (assertion.type) {
432
+ case "url-contains":
433
+ const patterns = assertion.value
434
+ .split("|")
435
+ .map((p) => `page.url().includes('${p}')`)
436
+ .join(" || ");
437
+ return `
438
+ // Assertion: URL contains ${assertion.value}
439
+ if (!(${patterns})) {
440
+ flowResult.assertionsFailed.push('URL should contain ${assertion.value}');
441
+ ${assertion.critical ? "flowResult.success = false;" : ""}
442
+ }`;
443
+ case "element-visible":
444
+ return `
445
+ // Assertion: Element visible
446
+ const visible${idx} = await page.$('${assertion.value}');
447
+ if (!visible${idx} || !(await visible${idx}.isVisible())) {
448
+ flowResult.assertionsFailed.push('Element should be visible: ${assertion.value}');
449
+ ${assertion.critical ? "flowResult.success = false;" : ""}
450
+ }`;
451
+ case "element-hidden":
452
+ return `
453
+ // Assertion: Element hidden
454
+ const hidden${idx} = await page.$('${assertion.value}');
455
+ if (hidden${idx} && await hidden${idx}.isVisible()) {
456
+ flowResult.assertionsFailed.push('Element should be hidden: ${assertion.value}');
457
+ ${assertion.critical ? "flowResult.success = false;" : ""}
458
+ }`;
459
+ case "no-errors":
460
+ return `
461
+ // Assertion: No errors
462
+ // (errors are captured globally)`;
463
+ default:
464
+ return "";
465
+ }
466
+ })
467
+ .join("\n");
468
+
469
+ return `
470
+ test('Flow: ${flow.name}', async () => {
471
+ const flowResult = {
472
+ id: '${flow.id}',
473
+ name: '${flow.name}',
474
+ success: true,
475
+ stepsCompleted: 0,
476
+ failedAt: null as string | null,
477
+ assertionsFailed: [] as string[],
478
+ duration: 0,
479
+ };
480
+
481
+ const startTime = Date.now();
482
+
483
+ try {
484
+ ${steps}
485
+
486
+ flowResult.stepsCompleted = ${flow.steps.length};
487
+
488
+ ${assertions}
489
+
490
+ } catch (error: any) {
491
+ flowResult.success = false;
492
+ flowResult.failedAt = error.message;
493
+ }
494
+
495
+ flowResult.duration = Date.now() - startTime;
496
+ results.flows.push(flowResult);
497
+
498
+ console.log(flowResult.success ?
499
+ '✅ Flow passed: ${flow.name}' :
500
+ '❌ Flow failed: ${flow.name} - ' + (flowResult.failedAt || flowResult.assertionsFailed.join(', '))
501
+ );
502
+ });
503
+ `;
504
+ }