testchimp-runner-core 0.0.87 → 0.0.89

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,702 @@
1
+ "use strict";
2
+ /**
3
+ * Utilities for parsing test files to extract hooks and tests
4
+ * Supports Playwright hooks: test.beforeAll, test.afterAll, test.beforeEach, test.afterEach
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.TestFileParser = void 0;
44
+ const parser_1 = require("@babel/parser");
45
+ const traverse_1 = __importDefault(require("@babel/traverse"));
46
+ const t = __importStar(require("@babel/types"));
47
+ const generator_1 = __importDefault(require("@babel/generator"));
48
+ class TestFileParser {
49
+ /**
50
+ * Generate full test name with suite prefix
51
+ * @param suitePath - Array of suite names from root to current suite
52
+ * @param testName - Original test name
53
+ * @returns Full test name with suite prefix (e.g., "LoginSuite__testLogin")
54
+ */
55
+ static generateTestFullName(suitePath, testName) {
56
+ if (suitePath.length === 0) {
57
+ return testName;
58
+ }
59
+ return `${suitePath.join('__')}__${testName}`;
60
+ }
61
+ /**
62
+ * Find all parent describe blocks for a given AST path
63
+ * @param path - Babel AST path
64
+ * @returns Array of suite names from root to current (empty if not in any describe block)
65
+ */
66
+ static findSuitePath(path) {
67
+ const suitePath = [];
68
+ let currentPath = path.parentPath;
69
+ while (currentPath) {
70
+ // Check if current path is a test.describe() call
71
+ if (currentPath.isCallExpression()) {
72
+ const callee = currentPath.node.callee;
73
+ if (t.isMemberExpression(callee)) {
74
+ const object = callee.object;
75
+ const property = callee.property;
76
+ if (t.isIdentifier(object) && object.name === 'test' &&
77
+ t.isIdentifier(property) && property.name === 'describe') {
78
+ // Extract suite name from first argument
79
+ if (currentPath.node.arguments.length >= 1) {
80
+ const suiteNameArg = currentPath.node.arguments[0];
81
+ let suiteName = null;
82
+ if (t.isStringLiteral(suiteNameArg)) {
83
+ suiteName = suiteNameArg.value;
84
+ }
85
+ else if (t.isTemplateLiteral(suiteNameArg)) {
86
+ if (suiteNameArg.quasis.length > 0) {
87
+ suiteName = suiteNameArg.quasis[0].value.raw;
88
+ }
89
+ }
90
+ if (suiteName) {
91
+ suitePath.unshift(suiteName); // Add to beginning (root first)
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+ currentPath = currentPath.parentPath;
98
+ }
99
+ return suitePath;
100
+ }
101
+ /**
102
+ * Parse a test file to extract hooks and tests, supporting test.describe() blocks
103
+ * @param script - The test file content
104
+ * @param testNames - Optional array of test names to filter (supports both original names and full names with suite prefix)
105
+ * @returns Parsed structure with hooks and tests
106
+ */
107
+ static parseTestFile(script, testNames) {
108
+ const result = {
109
+ fileHooks: {
110
+ beforeAll: [],
111
+ afterAll: [],
112
+ beforeEach: [],
113
+ afterEach: []
114
+ },
115
+ tests: [],
116
+ suites: []
117
+ };
118
+ try {
119
+ // Parse the script with Babel
120
+ const ast = (0, parser_1.parse)(script, {
121
+ sourceType: 'module',
122
+ plugins: ['typescript', 'classProperties', 'decorators-legacy'],
123
+ allowImportExportEverywhere: true
124
+ });
125
+ // Track if we should filter tests
126
+ const shouldFilter = testNames && testNames.length > 0;
127
+ const testNameSet = shouldFilter ? new Set(testNames.map(name => name.trim())) : null;
128
+ // Map to store suites by their path (for nested suite tracking)
129
+ const suiteMap = new Map();
130
+ /**
131
+ * Recursively parse a suite (describe block) and its nested suites
132
+ */
133
+ const parseSuite = (describePath, parentSuitePath) => {
134
+ if (!describePath.isCallExpression()) {
135
+ return null;
136
+ }
137
+ const callee = describePath.node.callee;
138
+ if (!t.isMemberExpression(callee)) {
139
+ return null;
140
+ }
141
+ const object = callee.object;
142
+ const property = callee.property;
143
+ if (!t.isIdentifier(object) || object.name !== 'test' ||
144
+ !t.isIdentifier(property) || property.name !== 'describe') {
145
+ return null;
146
+ }
147
+ // Extract suite name
148
+ if (describePath.node.arguments.length < 2) {
149
+ return null;
150
+ }
151
+ const suiteNameArg = describePath.node.arguments[0];
152
+ let suiteName = null;
153
+ if (t.isStringLiteral(suiteNameArg)) {
154
+ suiteName = suiteNameArg.value;
155
+ }
156
+ else if (t.isTemplateLiteral(suiteNameArg)) {
157
+ if (suiteNameArg.quasis.length > 0) {
158
+ suiteName = suiteNameArg.quasis[0].value.raw;
159
+ }
160
+ }
161
+ if (!suiteName) {
162
+ return null;
163
+ }
164
+ // Build current suite path
165
+ const currentSuitePath = [...parentSuitePath, suiteName];
166
+ const suitePathKey = currentSuitePath.join('__');
167
+ // Check if suite already exists (shouldn't happen, but safety check)
168
+ if (suiteMap.has(suitePathKey)) {
169
+ return suiteMap.get(suitePathKey);
170
+ }
171
+ // Create new suite
172
+ const suite = {
173
+ name: suiteName,
174
+ suitePath: currentSuitePath,
175
+ beforeAll: [],
176
+ afterAll: [],
177
+ beforeEach: [],
178
+ afterEach: [],
179
+ tests: [],
180
+ nestedSuites: []
181
+ };
182
+ suiteMap.set(suitePathKey, suite);
183
+ // Get callback function (second argument)
184
+ const callback = describePath.node.arguments[1];
185
+ if (!t.isFunctionExpression(callback) && !t.isArrowFunctionExpression(callback)) {
186
+ return suite;
187
+ }
188
+ // Traverse the callback body to find hooks, tests, and nested describe blocks
189
+ const callbackBody = callback.body;
190
+ if (!t.isBlockStatement(callbackBody)) {
191
+ return suite;
192
+ }
193
+ // Traverse the suite's callback body
194
+ // Traverse the block statement directly - Babel can handle this
195
+ (0, traverse_1.default)(callbackBody, {
196
+ CallExpression(path) {
197
+ const callee = path.node.callee;
198
+ // Check for test.beforeAll, test.afterAll, test.beforeEach, test.afterEach
199
+ if (t.isMemberExpression(callee)) {
200
+ const object = callee.object;
201
+ const property = callee.property;
202
+ if (t.isIdentifier(object) && object.name === 'test' && t.isIdentifier(property)) {
203
+ const hookName = property.name;
204
+ // Check if this is a hook
205
+ if (hookName === 'beforeAll' || hookName === 'afterAll' ||
206
+ hookName === 'beforeEach' || hookName === 'afterEach') {
207
+ // Get the callback function
208
+ if (path.node.arguments.length >= 1) {
209
+ const hookCallback = path.node.arguments[0];
210
+ if (t.isFunctionExpression(hookCallback) || t.isArrowFunctionExpression(hookCallback)) {
211
+ let hookBody;
212
+ if (t.isBlockStatement(hookCallback.body)) {
213
+ hookBody = (0, generator_1.default)(hookCallback.body, { comments: false }).code;
214
+ }
215
+ else {
216
+ const exprCode = (0, generator_1.default)(hookCallback.body, { comments: false }).code;
217
+ hookBody = `{ return ${exprCode}; }`;
218
+ }
219
+ const hook = {
220
+ code: hookBody,
221
+ name: hookName,
222
+ suitePath: currentSuitePath,
223
+ scope: 'suite'
224
+ };
225
+ // Add to suite's appropriate array
226
+ if (hookName === 'beforeAll') {
227
+ suite.beforeAll.push(hook);
228
+ }
229
+ else if (hookName === 'afterAll') {
230
+ suite.afterAll.push(hook);
231
+ }
232
+ else if (hookName === 'beforeEach') {
233
+ suite.beforeEach.push(hook);
234
+ }
235
+ else if (hookName === 'afterEach') {
236
+ suite.afterEach.push(hook);
237
+ }
238
+ }
239
+ }
240
+ return;
241
+ }
242
+ }
243
+ }
244
+ // Check for test() calls
245
+ if (t.isIdentifier(callee) && callee.name === 'test') {
246
+ if (path.node.arguments.length >= 2) {
247
+ const testNameArg = path.node.arguments[0];
248
+ const testCallback = path.node.arguments[1];
249
+ // Extract test name
250
+ let testName = null;
251
+ if (t.isStringLiteral(testNameArg)) {
252
+ testName = testNameArg.value;
253
+ }
254
+ else if (t.isTemplateLiteral(testNameArg)) {
255
+ if (testNameArg.quasis.length > 0) {
256
+ testName = testNameArg.quasis[0].value.raw;
257
+ }
258
+ }
259
+ if (testName) {
260
+ const fullName = TestFileParser.generateTestFullName(currentSuitePath, testName);
261
+ // Filter tests if testNames provided (support both original and full names)
262
+ if (shouldFilter && testNameSet) {
263
+ if (!testNameSet.has(testName) && !testNameSet.has(fullName)) {
264
+ return; // Skip this test
265
+ }
266
+ }
267
+ // Extract test body
268
+ if (t.isFunctionExpression(testCallback) || t.isArrowFunctionExpression(testCallback)) {
269
+ let testBody;
270
+ let statements;
271
+ if (t.isBlockStatement(testCallback.body)) {
272
+ testBody = (0, generator_1.default)(testCallback.body, { comments: false }).code;
273
+ // Extract ALL statements from BlockStatement.body (includes variables)
274
+ const bodyStatements = testCallback.body.body;
275
+ statements = bodyStatements.map((stmt, index) => {
276
+ const code = (0, generator_1.default)(stmt, { comments: false }).code;
277
+ const isVar = t.isVariableDeclaration(stmt);
278
+ // Extract intent comment (leading comment, exclude @Screen/@State)
279
+ let intentComment;
280
+ if (stmt.leadingComments && stmt.leadingComments.length > 0) {
281
+ for (const comment of stmt.leadingComments) {
282
+ const commentText = comment.value.trim();
283
+ if (!commentText.includes('@Screen') && !commentText.includes('@State')) {
284
+ intentComment = commentText;
285
+ break;
286
+ }
287
+ }
288
+ }
289
+ // Extract screen-state annotation (trailing or next statement's leading)
290
+ let screenStateAnnotation;
291
+ if (stmt.trailingComments && stmt.trailingComments.length > 0) {
292
+ for (const comment of stmt.trailingComments) {
293
+ const commentText = comment.value.trim();
294
+ if (commentText.includes('@Screen') || commentText.includes('@State')) {
295
+ screenStateAnnotation = commentText;
296
+ break;
297
+ }
298
+ }
299
+ }
300
+ // Fallback: check next statement's leading @Screen/@State comment
301
+ if (!screenStateAnnotation && index + 1 < bodyStatements.length) {
302
+ const nextStmt = bodyStatements[index + 1];
303
+ if (nextStmt.leadingComments && nextStmt.leadingComments.length > 0) {
304
+ for (const comment of nextStmt.leadingComments) {
305
+ const commentText = comment.value.trim();
306
+ if (commentText.includes('@Screen') || commentText.includes('@State')) {
307
+ screenStateAnnotation = commentText;
308
+ break;
309
+ }
310
+ }
311
+ }
312
+ }
313
+ return { code, isVariableDeclaration: isVar, intentComment, screenStateAnnotation };
314
+ });
315
+ }
316
+ else {
317
+ // Expression body - single statement
318
+ const exprCode = (0, generator_1.default)(testCallback.body, { comments: false }).code;
319
+ testBody = `{ return ${exprCode}; }`;
320
+ statements = [{
321
+ code: exprCode,
322
+ isVariableDeclaration: false
323
+ }];
324
+ }
325
+ suite.tests.push({
326
+ name: testName,
327
+ code: testBody,
328
+ suitePath: currentSuitePath,
329
+ fullName: fullName,
330
+ statements: statements
331
+ });
332
+ }
333
+ }
334
+ }
335
+ return;
336
+ }
337
+ // Check for nested test.describe() calls
338
+ if (t.isMemberExpression(callee)) {
339
+ const object = callee.object;
340
+ const property = callee.property;
341
+ if (t.isIdentifier(object) && object.name === 'test' &&
342
+ t.isIdentifier(property) && property.name === 'describe') {
343
+ // This is a nested describe block - parse it recursively
344
+ const nestedSuite = parseSuite(path, currentSuitePath);
345
+ if (nestedSuite) {
346
+ suite.nestedSuites.push(nestedSuite);
347
+ }
348
+ return;
349
+ }
350
+ }
351
+ }
352
+ });
353
+ return suite;
354
+ };
355
+ // Traverse the AST to find top-level hooks, tests, and describe blocks
356
+ (0, traverse_1.default)(ast, {
357
+ CallExpression(path) {
358
+ const callee = path.node.callee;
359
+ // Check for test.describe() at top level
360
+ if (t.isMemberExpression(callee)) {
361
+ const object = callee.object;
362
+ const property = callee.property;
363
+ if (t.isIdentifier(object) && object.name === 'test' &&
364
+ t.isIdentifier(property) && property.name === 'describe') {
365
+ // Check if this is at the top level (not nested in another describe)
366
+ const suitePath = TestFileParser.findSuitePath(path);
367
+ if (suitePath.length === 0) {
368
+ // This is a top-level describe block
369
+ const suite = parseSuite(path, []);
370
+ if (suite) {
371
+ result.suites.push(suite);
372
+ }
373
+ }
374
+ return;
375
+ }
376
+ }
377
+ // Check for file-level hooks (test.beforeAll, etc.) - not inside any describe block
378
+ if (t.isMemberExpression(callee)) {
379
+ const object = callee.object;
380
+ const property = callee.property;
381
+ if (t.isIdentifier(object) && object.name === 'test' && t.isIdentifier(property)) {
382
+ const hookName = property.name;
383
+ if (hookName === 'beforeAll' || hookName === 'afterAll' ||
384
+ hookName === 'beforeEach' || hookName === 'afterEach') {
385
+ // Check if this hook is at file level (not inside any describe block)
386
+ const suitePath = TestFileParser.findSuitePath(path);
387
+ if (suitePath.length === 0) {
388
+ // This is a file-level hook
389
+ if (path.node.arguments.length >= 1) {
390
+ const callback = path.node.arguments[0];
391
+ if (t.isFunctionExpression(callback) || t.isArrowFunctionExpression(callback)) {
392
+ let hookBody;
393
+ if (t.isBlockStatement(callback.body)) {
394
+ hookBody = (0, generator_1.default)(callback.body, { comments: false }).code;
395
+ }
396
+ else {
397
+ const exprCode = (0, generator_1.default)(callback.body, { comments: false }).code;
398
+ hookBody = `{ return ${exprCode}; }`;
399
+ }
400
+ const hook = {
401
+ code: hookBody,
402
+ name: hookName,
403
+ suitePath: [],
404
+ scope: 'file'
405
+ };
406
+ // Add to appropriate array
407
+ if (hookName === 'beforeAll') {
408
+ result.fileHooks.beforeAll.push(hook);
409
+ }
410
+ else if (hookName === 'afterAll') {
411
+ result.fileHooks.afterAll.push(hook);
412
+ }
413
+ else if (hookName === 'beforeEach') {
414
+ result.fileHooks.beforeEach.push(hook);
415
+ }
416
+ else if (hookName === 'afterEach') {
417
+ result.fileHooks.afterEach.push(hook);
418
+ }
419
+ }
420
+ }
421
+ }
422
+ return;
423
+ }
424
+ }
425
+ }
426
+ // Check for file-level test() calls (not inside any describe block)
427
+ if (t.isIdentifier(callee) && callee.name === 'test') {
428
+ // Check if this test is at file level
429
+ const suitePath = TestFileParser.findSuitePath(path);
430
+ if (suitePath.length === 0) {
431
+ // This is a file-level test
432
+ if (path.node.arguments.length >= 2) {
433
+ const testNameArg = path.node.arguments[0];
434
+ const testCallback = path.node.arguments[1];
435
+ // Extract test name
436
+ let testName = null;
437
+ if (t.isStringLiteral(testNameArg)) {
438
+ testName = testNameArg.value;
439
+ }
440
+ else if (t.isTemplateLiteral(testNameArg)) {
441
+ if (testNameArg.quasis.length > 0) {
442
+ testName = testNameArg.quasis[0].value.raw;
443
+ }
444
+ }
445
+ if (testName) {
446
+ const fullName = TestFileParser.generateTestFullName([], testName);
447
+ // Filter tests if testNames provided
448
+ if (shouldFilter && testNameSet) {
449
+ if (!testNameSet.has(testName) && !testNameSet.has(fullName)) {
450
+ return; // Skip this test
451
+ }
452
+ }
453
+ // Extract test body
454
+ if (t.isFunctionExpression(testCallback) || t.isArrowFunctionExpression(testCallback)) {
455
+ let testBody;
456
+ let statements;
457
+ if (t.isBlockStatement(testCallback.body)) {
458
+ testBody = (0, generator_1.default)(testCallback.body, { comments: false }).code;
459
+ // Extract ALL statements from BlockStatement.body (includes variables)
460
+ const bodyStatements = testCallback.body.body;
461
+ statements = bodyStatements.map((stmt, index) => {
462
+ const code = (0, generator_1.default)(stmt, { comments: false }).code;
463
+ const isVar = t.isVariableDeclaration(stmt);
464
+ // Extract intent comment (leading comment, exclude @Screen/@State)
465
+ let intentComment;
466
+ if (stmt.leadingComments && stmt.leadingComments.length > 0) {
467
+ for (const comment of stmt.leadingComments) {
468
+ const commentText = comment.value.trim();
469
+ if (!commentText.includes('@Screen') && !commentText.includes('@State')) {
470
+ intentComment = commentText;
471
+ break;
472
+ }
473
+ }
474
+ }
475
+ // Extract screen-state annotation (trailing or next statement's leading)
476
+ let screenStateAnnotation;
477
+ if (stmt.trailingComments && stmt.trailingComments.length > 0) {
478
+ for (const comment of stmt.trailingComments) {
479
+ const commentText = comment.value.trim();
480
+ if (commentText.includes('@Screen') || commentText.includes('@State')) {
481
+ screenStateAnnotation = commentText;
482
+ break;
483
+ }
484
+ }
485
+ }
486
+ // Fallback: check next statement's leading @Screen/@State comment
487
+ if (!screenStateAnnotation && index + 1 < bodyStatements.length) {
488
+ const nextStmt = bodyStatements[index + 1];
489
+ if (nextStmt.leadingComments && nextStmt.leadingComments.length > 0) {
490
+ for (const comment of nextStmt.leadingComments) {
491
+ const commentText = comment.value.trim();
492
+ if (commentText.includes('@Screen') || commentText.includes('@State')) {
493
+ screenStateAnnotation = commentText;
494
+ break;
495
+ }
496
+ }
497
+ }
498
+ }
499
+ return { code, isVariableDeclaration: isVar, intentComment, screenStateAnnotation };
500
+ });
501
+ }
502
+ else {
503
+ // Expression body - single statement
504
+ const exprCode = (0, generator_1.default)(testCallback.body, { comments: false }).code;
505
+ testBody = `{ return ${exprCode}; }`;
506
+ statements = [{
507
+ code: exprCode,
508
+ isVariableDeclaration: false
509
+ }];
510
+ }
511
+ result.tests.push({
512
+ name: testName,
513
+ code: testBody,
514
+ suitePath: [],
515
+ fullName: fullName,
516
+ statements: statements
517
+ });
518
+ }
519
+ }
520
+ }
521
+ }
522
+ }
523
+ }
524
+ });
525
+ }
526
+ catch (error) {
527
+ throw new Error(`Failed to parse test file: ${error instanceof Error ? error.message : String(error)}`);
528
+ }
529
+ return result;
530
+ }
531
+ /**
532
+ * Flatten suite structure into execution-ready format
533
+ * Separates per-test hooks (beforeEach/afterEach) from per-suite hooks (beforeAll/afterAll)
534
+ * @param parsed - Parsed test file structure
535
+ * @returns Flattened structure ready for execution
536
+ */
537
+ static flattenForExecution(parsed) {
538
+ const result = {
539
+ fileLevelHooks: {
540
+ beforeAll: parsed.fileHooks.beforeAll,
541
+ afterAll: parsed.fileHooks.afterAll,
542
+ beforeEach: parsed.fileHooks.beforeEach,
543
+ afterEach: parsed.fileHooks.afterEach
544
+ },
545
+ tests: [],
546
+ suites: []
547
+ };
548
+ let testIndex = 0;
549
+ /**
550
+ * Recursively collect tests and hooks from a suite and its nested suites
551
+ * Returns the test indices collected (including from nested suites)
552
+ */
553
+ const collectFromSuite = (suite, parentBeforeEach, parentAfterEach) => {
554
+ // Collect beforeEach/afterEach hooks from this suite (parent hooks first)
555
+ const suiteBeforeEach = [...parentBeforeEach, ...suite.beforeEach];
556
+ const suiteAfterEach = [...suite.afterEach, ...parentAfterEach]; // Reverse order for afterEach
557
+ // Track test indices for this suite (includes tests from this suite and nested suites)
558
+ const suiteTestIndices = [];
559
+ // Add tests from this suite
560
+ for (const test of suite.tests) {
561
+ suiteTestIndices.push(testIndex);
562
+ result.tests.push({
563
+ test: test,
564
+ suitePath: test.suitePath || [],
565
+ suiteBeforeEachHooks: suiteBeforeEach,
566
+ suiteAfterEachHooks: suiteAfterEach
567
+ });
568
+ testIndex++;
569
+ }
570
+ // Process nested suites and collect their test indices
571
+ for (const nestedSuite of suite.nestedSuites) {
572
+ const nestedTestIndices = collectFromSuite(nestedSuite, suiteBeforeEach, suiteAfterEach);
573
+ // Include nested suite test indices in parent suite's test indices
574
+ // (needed for afterAll execution - parent suite's afterAll runs after all nested tests)
575
+ suiteTestIndices.push(...nestedTestIndices);
576
+ }
577
+ // Add suite info (for beforeAll/afterAll execution)
578
+ // Include suite if it has hooks OR if it has tests (direct or nested)
579
+ if (suite.beforeAll.length > 0 || suite.afterAll.length > 0 || suiteTestIndices.length > 0) {
580
+ result.suites.push({
581
+ suitePath: suite.suitePath,
582
+ beforeAll: suite.beforeAll,
583
+ afterAll: suite.afterAll,
584
+ testIndices: suiteTestIndices
585
+ });
586
+ }
587
+ return suiteTestIndices;
588
+ };
589
+ // Add file-level tests
590
+ for (const test of parsed.tests) {
591
+ result.tests.push({
592
+ test: test,
593
+ suitePath: [],
594
+ suiteBeforeEachHooks: [],
595
+ suiteAfterEachHooks: []
596
+ });
597
+ testIndex++;
598
+ }
599
+ // Process all top-level suites
600
+ for (const suite of parsed.suites) {
601
+ collectFromSuite(suite, [], []);
602
+ }
603
+ return result;
604
+ }
605
+ /**
606
+ * Construct a test script with imports and a single test using AST
607
+ * This is more robust than string interpolation
608
+ * @param originalScript - The full original script (to extract imports)
609
+ * @param testName - The test name
610
+ * @param testBodyCode - The test body code (already extracted)
611
+ * @returns A complete script with imports and the test
612
+ */
613
+ static constructTestScriptWithImports(originalScript, testName, testBodyCode) {
614
+ try {
615
+ // Parse original script to extract import statements as AST nodes
616
+ const originalAst = (0, parser_1.parse)(originalScript, {
617
+ sourceType: 'module',
618
+ plugins: ['typescript', 'classProperties', 'decorators-legacy'],
619
+ allowImportExportEverywhere: true
620
+ });
621
+ // Collect import declarations
622
+ const importDeclarations = [];
623
+ (0, traverse_1.default)(originalAst, {
624
+ ImportDeclaration(path) {
625
+ importDeclarations.push(path.node);
626
+ }
627
+ });
628
+ // Parse the test body code to get it as an AST node
629
+ // The test body is a block statement, so we need to parse it
630
+ let testBody;
631
+ try {
632
+ // Try parsing as a block statement
633
+ const bodyAst = (0, parser_1.parse)(`{ ${testBodyCode} }`, {
634
+ sourceType: 'module',
635
+ plugins: ['typescript', 'classProperties', 'decorators-legacy'],
636
+ allowReturnOutsideFunction: true
637
+ });
638
+ // Extract the block statement from the parsed code
639
+ if (bodyAst.program.body.length > 0 && t.isBlockStatement(bodyAst.program.body[0])) {
640
+ testBody = bodyAst.program.body[0];
641
+ }
642
+ else {
643
+ // Fallback: create a block with the statements
644
+ const statements = bodyAst.program.body.filter(stmt => !t.isImportDeclaration(stmt));
645
+ testBody = t.blockStatement(statements);
646
+ }
647
+ }
648
+ catch (parseError) {
649
+ // If parsing fails, try parsing as statements
650
+ const bodyAst = (0, parser_1.parse)(testBodyCode, {
651
+ sourceType: 'module',
652
+ plugins: ['typescript', 'classProperties', 'decorators-legacy'],
653
+ allowReturnOutsideFunction: true,
654
+ allowAwaitOutsideFunction: true
655
+ });
656
+ const statements = bodyAst.program.body.filter(stmt => !t.isImportDeclaration(stmt));
657
+ testBody = t.blockStatement(statements);
658
+ }
659
+ // Create the test call expression
660
+ // test('testName', async ({ page, browser, context }) => { ... })
661
+ const testIdentifier = t.identifier('test');
662
+ const testNameLiteral = t.stringLiteral(testName);
663
+ // Create async arrow function with parameters
664
+ const pageParam = t.objectPattern([
665
+ t.objectProperty(t.identifier('page'), t.identifier('page'), false, true),
666
+ t.objectProperty(t.identifier('browser'), t.identifier('browser'), false, true),
667
+ t.objectProperty(t.identifier('context'), t.identifier('context'), false, true)
668
+ ]);
669
+ const asyncArrowFunction = t.arrowFunctionExpression([pageParam], testBody, true // async
670
+ );
671
+ // Create the test() call
672
+ const testCall = t.callExpression(testIdentifier, [
673
+ testNameLiteral,
674
+ asyncArrowFunction
675
+ ]);
676
+ // Create a new program with imports + test call
677
+ const programBody = [
678
+ ...importDeclarations,
679
+ t.expressionStatement(testCall)
680
+ ];
681
+ const newProgram = t.program(programBody);
682
+ // Generate code from AST
683
+ const output = (0, generator_1.default)(newProgram, {
684
+ retainLines: false,
685
+ compact: false
686
+ });
687
+ return output.code;
688
+ }
689
+ catch (error) {
690
+ // Fallback to string interpolation if AST construction fails
691
+ const importStatements = originalScript.match(/import\s+.*?from\s+['"]([^'"]+)['"];?/g) || [];
692
+ const importsCode = importStatements.length > 0
693
+ ? importStatements.join('\n') + '\n\n'
694
+ : '';
695
+ // Escape test name properly for fallback
696
+ const escapedName = testName.replace(/'/g, "\\'").replace(/\n/g, '\\n');
697
+ return `${importsCode}test('${escapedName}', async ({ page, browser, context }) => {\n${testBodyCode}\n});`;
698
+ }
699
+ }
700
+ }
701
+ exports.TestFileParser = TestFileParser;
702
+ //# sourceMappingURL=test-file-parser.js.map