@probolabs/playwright 1.5.0-rc.7 โ†’ 1.5.0-rc.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -321,6 +321,16 @@ const DEFAULT_RECORDER_SETTINGS = {
321
321
  };
322
322
 
323
323
  // --- Code generation utilities for Probo Labs Playwright scripts ---
324
+ /**
325
+ * Converts a test suite name to a Playwright tag (slugify + @ prefix).
326
+ * Playwright tags must start with @.
327
+ */
328
+ function slugifyTag(tag) {
329
+ if (!tag || typeof tag !== 'string')
330
+ return '@untagged';
331
+ const slug = slugify(tag);
332
+ return slug ? `@${slug}` : '@untagged';
333
+ }
324
334
  /**
325
335
  * Extracts environment variable names from parameter table rows
326
336
  * by parsing ${process.env.VAR_NAME} patterns from parameter values
@@ -620,6 +630,13 @@ function scriptTemplate(options, settings, viewPort) {
620
630
  }).join(',\n ')}
621
631
  ];
622
632
  ` : '';
633
+ // Playwright tags for test suite filtering (e.g. npx playwright test --grep @sanity)
634
+ const playwrightTags = options.testSuiteTags && options.testSuiteTags.length > 0
635
+ ? options.testSuiteTags.map(slugifyTag).filter((t) => t !== '@untagged')
636
+ : [];
637
+ const describeTagOption = playwrightTags.length > 0
638
+ ? `{ tag: [${playwrightTags.map((t) => `'${t}'`).join(', ')}] }, `
639
+ : '';
623
640
  const proboConstructor = hasAiInteractions ? `
624
641
  const probo = new Probo({
625
642
  scenarioName: '${options.scenarioName}',
@@ -696,7 +713,7 @@ ${hasSecrets ? `import { config } from 'dotenv';
696
713
  }
697
714
  });
698
715
 
699
- test.describe('${options.scenarioName}', () => {
716
+ test.describe('${options.scenarioName}', ${describeTagOption}() => {
700
717
  test.beforeEach(async ({ page }) => {
701
718
  // set the ProboPlaywright instance to the current page
702
719
  ppw.setPage(page);
@@ -735,7 +752,7 @@ const DEFAULT_PLAYWRIGHT_TEST_VERSION = '^1.57.0';
735
752
  * Generate package.json content for a Playwright test project
736
753
  */
737
754
  function generatePackageJson(options) {
738
- const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION } = options;
755
+ const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION, probolabsPlaywrightVersion = 'latest' } = options;
739
756
  return `{
740
757
  "name": "${name}",
741
758
  "version": "1.0.0",
@@ -746,7 +763,7 @@ function generatePackageJson(options) {
746
763
  },
747
764
  "dependencies": {
748
765
  "@playwright/test": "${playwrightTestVersion}",
749
- "@probolabs/playwright": "latest"${hasSecrets ? ',\n "dotenv": "latest"' : ''}
766
+ "@probolabs/playwright": "${probolabsPlaywrightVersion}"${hasSecrets ? ',\n "dotenv": "latest"' : ''}
750
767
  }
751
768
  }`;
752
769
  }
@@ -1139,12 +1156,12 @@ class ProboCodeGenerator {
1139
1156
  return response.json();
1140
1157
  }
1141
1158
  /**
1142
- * Fetch all scenarios for a project from the backend API
1159
+ * Fetch all scenarios for a project from the backend API (light endpoint includes test_suite_tags)
1143
1160
  */
1144
1161
  static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
1145
1162
  const normalizedUrl = this.normalizeApiUrl(apiUrl);
1146
- // Scenarios endpoint is at root level, not under /api/
1147
- const url = `${normalizedUrl}/scenarios/?project_id=${projectId}`;
1163
+ // Use api/scenarios/light/ to get test_suite_tags for Playwright tagging
1164
+ const url = `${normalizedUrl}/api/scenarios/light/?project_id=${projectId}`;
1148
1165
  const response = await fetch(url, {
1149
1166
  method: 'GET',
1150
1167
  headers: {
@@ -1282,6 +1299,7 @@ class ProboCodeGenerator {
1282
1299
  scenarioName: scenarioData.name,
1283
1300
  interactions: interactions,
1284
1301
  rows: rows,
1302
+ ...((options === null || options === void 0 ? void 0 : options.testSuiteTags) && options.testSuiteTags.length > 0 ? { testSuiteTags: options.testSuiteTags } : {}),
1285
1303
  };
1286
1304
  return generateCode(codeOptions, settings, viewPort);
1287
1305
  }
@@ -1341,14 +1359,15 @@ class ProboCodeGenerator {
1341
1359
  if (scenarios.length === 0) {
1342
1360
  throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
1343
1361
  }
1344
- // Generate code for each scenario
1362
+ // Generate code for each scenario (with test_suite_tags for Playwright tagging)
1345
1363
  const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
1346
1364
  try {
1347
- const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, options);
1365
+ const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, { ...options, testSuiteTags: scenario.test_suite_tags });
1348
1366
  return {
1349
1367
  scenarioId: scenario.id,
1350
1368
  scenarioName: scenario.name,
1351
1369
  code: code,
1370
+ testSuiteTags: scenario.test_suite_tags,
1352
1371
  };
1353
1372
  }
1354
1373
  catch (error) {
@@ -1375,15 +1394,13 @@ function ensureDirectoryExists(dir) {
1375
1394
  }
1376
1395
  }
1377
1396
  /**
1378
- * Gets the default test suite directory path
1397
+ * Gets the project directory path (unified for test suite runs and export).
1398
+ * Path: ~/.probium/projects/{projectId}-{projectName-slugified}/
1379
1399
  */
1380
- function getDefaultTestSuiteDir(testSuiteId, testSuiteName) {
1381
- const baseDir = path.join(os.homedir(), '.probium', 'test-suites');
1382
- if (testSuiteName) {
1383
- const sanitizedName = slugify(testSuiteName);
1384
- return path.join(baseDir, `${testSuiteId}-${sanitizedName}`);
1385
- }
1386
- return path.join(baseDir, testSuiteId.toString());
1400
+ function getProjectDir(projectId, projectName) {
1401
+ const baseDir = path.join(os.homedir(), '.probium', 'projects');
1402
+ const sanitizedName = projectName ? slugify(projectName) : `project-${projectId}`;
1403
+ return path.join(baseDir, `${projectId}-${sanitizedName}`);
1387
1404
  }
1388
1405
  /**
1389
1406
  * Count total tests by scanning spec files
@@ -1578,36 +1595,67 @@ class TestSuiteRunner {
1578
1595
  throw new Error(`Test suite "${testSuiteName}" not found in project "${projectName}"`);
1579
1596
  }
1580
1597
  /**
1581
- * Generate all files for a test suite
1598
+ * Fetch test suite details (project id, project name, test suite name)
1599
+ */
1600
+ static async fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl) {
1601
+ var _a, _b, _c;
1602
+ const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
1603
+ const response = await fetch$1(`${baseUrl}/test-suites/${testSuiteId}/`, {
1604
+ method: 'GET',
1605
+ headers: {
1606
+ 'Authorization': `Token ${apiToken}`,
1607
+ 'Content-Type': 'application/json',
1608
+ },
1609
+ });
1610
+ if (!response.ok) {
1611
+ const errorText = await response.text();
1612
+ throw new Error(`Failed to fetch test suite ${testSuiteId}: ${response.status} ${errorText}`);
1613
+ }
1614
+ const data = await response.json();
1615
+ const projectId = (_a = data.project) !== null && _a !== void 0 ? _a : data.project_id;
1616
+ const projectName = (_b = data.project_name) !== null && _b !== void 0 ? _b : `project-${projectId}`;
1617
+ const testSuiteName = (_c = data.name) !== null && _c !== void 0 ? _c : `suite-${testSuiteId}`;
1618
+ if (!projectId) {
1619
+ throw new Error(`Test suite ${testSuiteId} has no project`);
1620
+ }
1621
+ return { projectId, projectName, testSuiteName };
1622
+ }
1623
+ /**
1624
+ * Generate all files for a test suite (writes to project-level directory)
1582
1625
  */
1583
1626
  static async generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
1584
- const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
1585
- // Generate code for all scenarios
1627
+ const { projectId, projectName, testSuiteName: resolvedSuiteName } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
1628
+ const projectDir = outputDir || getProjectDir(projectId, projectName);
1629
+ await this.generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName !== null && testSuiteName !== void 0 ? testSuiteName : resolvedSuiteName, includeReporter, runId);
1630
+ }
1631
+ /**
1632
+ * Generate files only for the scenarios that participate in the test suite.
1633
+ * Used by the recorder app test-suite runner so only suite specs are written; then we run npx playwright test normally.
1634
+ */
1635
+ static async generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
1636
+ var _a;
1637
+ const { projectId } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
1586
1638
  const codeGenResult = await ProboCodeGenerator.generateCodeForTestSuite(testSuiteId, apiToken, apiUrl);
1587
1639
  if (codeGenResult.scenarios.length === 0) {
1588
- throw new Error(`No scenarios found in test suite ${testSuiteId}. Cannot generate test files.`);
1640
+ throw new Error(`No scenarios found in test suite "${codeGenResult.testSuiteName}" (ID: ${testSuiteId}). Cannot generate test files.`);
1589
1641
  }
1590
- // Delete everything in the test suite directory except node_modules
1591
- // This ensures a clean state while preserving dependencies for faster subsequent runs
1592
- if (fs.existsSync(testSuiteDir)) {
1642
+ const projectDir = outputDir;
1643
+ if (fs.existsSync(projectDir)) {
1593
1644
  try {
1594
- const nodeModulesPath = path.join(testSuiteDir, 'node_modules');
1645
+ const nodeModulesPath = path.join(projectDir, 'node_modules');
1595
1646
  const hasNodeModules = fs.existsSync(nodeModulesPath);
1596
- // Temporarily move node_modules out of the way if it exists
1597
1647
  let tempNodeModulesPath = null;
1598
1648
  if (hasNodeModules) {
1599
- tempNodeModulesPath = path.join(testSuiteDir, '..', `node_modules.temp.${testSuiteId}`);
1600
- // Remove temp directory if it exists from a previous failed run
1649
+ tempNodeModulesPath = path.join(projectDir, '..', `node_modules.temp.${projectId}`);
1601
1650
  if (fs.existsSync(tempNodeModulesPath)) {
1602
1651
  fs.rmSync(tempNodeModulesPath, { recursive: true, force: true });
1603
1652
  }
1604
1653
  fs.renameSync(nodeModulesPath, tempNodeModulesPath);
1605
1654
  console.log(`๐Ÿ“ฆ Preserved node_modules temporarily`);
1606
1655
  }
1607
- // Delete everything in the directory
1608
- const entries = fs.readdirSync(testSuiteDir, { withFileTypes: true });
1656
+ const entries = fs.readdirSync(projectDir, { withFileTypes: true });
1609
1657
  for (const entry of entries) {
1610
- const entryPath = path.join(testSuiteDir, entry.name);
1658
+ const entryPath = path.join(projectDir, entry.name);
1611
1659
  try {
1612
1660
  if (entry.isDirectory()) {
1613
1661
  fs.rmSync(entryPath, { recursive: true, force: true });
@@ -1620,36 +1668,33 @@ class TestSuiteRunner {
1620
1668
  console.warn(`โš ๏ธ Failed to delete ${entry.name}:`, error);
1621
1669
  }
1622
1670
  }
1623
- console.log(`๐Ÿ—‘๏ธ Cleaned test suite directory (preserved node_modules)`);
1624
- // Move node_modules back
1671
+ console.log(`๐Ÿ—‘๏ธ Cleaned project directory (preserved node_modules)`);
1625
1672
  if (hasNodeModules && tempNodeModulesPath) {
1626
1673
  fs.renameSync(tempNodeModulesPath, nodeModulesPath);
1627
1674
  console.log(`๐Ÿ“ฆ Restored node_modules`);
1628
1675
  }
1629
1676
  }
1630
1677
  catch (error) {
1631
- console.warn(`โš ๏ธ Failed to clean test suite directory ${testSuiteDir}:`, error);
1632
- // Continue anyway - we'll overwrite files as needed
1678
+ console.warn(`โš ๏ธ Failed to clean project directory ${projectDir}:`, error);
1633
1679
  }
1634
1680
  }
1635
- // Ensure directories exist (will recreate after deletion)
1636
- ensureDirectoryExists(testSuiteDir);
1637
- const testsDir = path.join(testSuiteDir, 'tests');
1681
+ ensureDirectoryExists(projectDir);
1682
+ const testsDir = path.join(projectDir, 'tests');
1638
1683
  ensureDirectoryExists(testsDir);
1639
- // Save each scenario's code to a .spec.ts file
1684
+ const seenSlugs = new Map();
1640
1685
  for (const scenario of codeGenResult.scenarios) {
1641
- const fileName = `${slugify(scenario.scenarioName)}.spec.ts`;
1686
+ const slug = slugify(scenario.scenarioName);
1687
+ const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
1688
+ seenSlugs.set(slug, count + 1);
1689
+ const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
1642
1690
  const filePath = path.join(testsDir, fileName);
1643
1691
  fs.writeFileSync(filePath, scenario.code, 'utf-8');
1644
1692
  console.log(`โœ… Generated test file: ${filePath}`);
1645
1693
  }
1646
- // Generate package.json
1647
- await this.generatePackageJson(testSuiteDir, codeGenResult);
1648
- // Generate playwright.config.ts with runId if available
1649
- await this.generatePlaywrightConfig(testSuiteDir, includeReporter, runId);
1650
- // Generate custom reporter file for live progress streaming (only if requested)
1694
+ await this.generatePackageJson(projectDir, codeGenResult);
1695
+ await this.generatePlaywrightConfig(projectDir, includeReporter, runId);
1651
1696
  if (includeReporter) {
1652
- await this.generateProboReporter(testSuiteDir);
1697
+ await this.generateProboReporter(projectDir);
1653
1698
  }
1654
1699
  }
1655
1700
  /**
@@ -1693,7 +1738,8 @@ class TestSuiteRunner {
1693
1738
  const packageJsonContent = generatePackageJson({
1694
1739
  name: sanitizedName,
1695
1740
  hasSecrets: hasSecrets,
1696
- testScript: 'npx playwright test'
1741
+ testScript: 'npx playwright test',
1742
+ probolabsPlaywrightVersion: process.env.PROBO_PLAYWRIGHT_VERSION || 'latest',
1697
1743
  });
1698
1744
  const filePath = path.join(outputDir, 'package.json');
1699
1745
  fs.writeFileSync(filePath, packageJsonContent, 'utf-8');
@@ -1723,7 +1769,8 @@ class TestSuiteRunner {
1723
1769
  */
1724
1770
  static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
1725
1771
  const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
1726
- const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
1772
+ const details = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
1773
+ const projectDir = outputDir || getProjectDir(details.projectId, details.projectName);
1727
1774
  let currentRunId = runId;
1728
1775
  try {
1729
1776
  // Create run record if not provided and not skipping run creation
@@ -1751,9 +1798,9 @@ class TestSuiteRunner {
1751
1798
  }
1752
1799
  // Generate all files (with runId if available for run-specific report directories)
1753
1800
  console.log(`๐Ÿ“ Generating test suite files for test suite ${testSuiteId}...`);
1754
- await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, testSuiteDir, testSuiteName, includeReporter, currentRunId);
1801
+ await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName, includeReporter, currentRunId);
1755
1802
  // Count total tests
1756
- const testsTotal = countTotalTests(testSuiteDir);
1803
+ const testsTotal = countTotalTests(projectDir);
1757
1804
  if (currentRunId && testsTotal > 0) {
1758
1805
  await updateRunStatus(currentRunId, testSuiteId, { tests_total: testsTotal }, apiToken, apiUrl);
1759
1806
  if (onStatusUpdate) {
@@ -1761,10 +1808,10 @@ class TestSuiteRunner {
1761
1808
  }
1762
1809
  }
1763
1810
  // Install dependencies
1764
- console.log(`๐Ÿ“ฆ Installing dependencies in ${testSuiteDir}...`);
1811
+ console.log(`๐Ÿ“ฆ Installing dependencies in ${projectDir}...`);
1765
1812
  try {
1766
1813
  const npmExecutable = findNpmExecutable();
1767
- const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd: testSuiteDir, timeout: 300000 } // 5 minute timeout for install
1814
+ const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for install
1768
1815
  );
1769
1816
  console.log('โœ… Dependencies installed successfully');
1770
1817
  if (installStdout)
@@ -1804,7 +1851,7 @@ class TestSuiteRunner {
1804
1851
  console.log(`๐ŸŒ Installing Playwright Chromium browser...`);
1805
1852
  try {
1806
1853
  const npxExecutable = findNpxExecutable();
1807
- const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd: testSuiteDir, timeout: 300000 } // 5 minute timeout for browser install
1854
+ const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for browser install
1808
1855
  );
1809
1856
  console.log('โœ… Playwright Chromium browser installed successfully');
1810
1857
  if (browserStdout)
@@ -1818,7 +1865,7 @@ class TestSuiteRunner {
1818
1865
  // or the error will be caught when trying to run tests
1819
1866
  }
1820
1867
  // Run Playwright tests with streaming output
1821
- console.log(`๐Ÿš€ Running Playwright tests in ${testSuiteDir}...`);
1868
+ console.log(`๐Ÿš€ Running Playwright tests in ${projectDir}...`);
1822
1869
  if (playwrightArgs.length > 0) {
1823
1870
  console.log(`๐Ÿงช Playwright extra args: ${playwrightArgs.join(' ')}`);
1824
1871
  }
@@ -1840,7 +1887,7 @@ class TestSuiteRunner {
1840
1887
  // Use spawn for streaming output
1841
1888
  const npxExecutable = findNpxExecutable();
1842
1889
  const testProcess = spawn(npxExecutable, ['playwright', 'test', ...playwrightArgs], {
1843
- cwd: testSuiteDir,
1890
+ cwd: projectDir,
1844
1891
  shell: true,
1845
1892
  stdio: ['ignore', 'pipe', 'pipe'],
1846
1893
  env: {
@@ -2026,8 +2073,8 @@ class TestSuiteRunner {
2026
2073
  console.log(success ? 'โœ… Tests completed successfully' : (isTestFailure ? 'โš ๏ธ Tests completed with failures' : 'โŒ Test execution failed'));
2027
2074
  // Parse final statistics from metadata/stdout as fallback
2028
2075
  let parsedStats = null;
2029
- if (fs.existsSync(path.join(testSuiteDir, 'playwright-report'))) {
2030
- parsedStats = parseStatisticsFromMetadata(testSuiteDir);
2076
+ if (fs.existsSync(path.join(projectDir, 'playwright-report'))) {
2077
+ parsedStats = parseStatisticsFromMetadata(projectDir);
2031
2078
  }
2032
2079
  if (!parsedStats) {
2033
2080
  parsedStats = parsePlaywrightStatistics(stdout);
@@ -2229,7 +2276,8 @@ class TestSuiteRunner {
2229
2276
  * Generate all files for a project (all scenarios)
2230
2277
  */
2231
2278
  static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
2232
- const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
2279
+ var _a;
2280
+ const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
2233
2281
  // Generate code for all scenarios
2234
2282
  const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
2235
2283
  if (codeGenResult.scenarios.length === 0) {
@@ -2283,9 +2331,12 @@ class TestSuiteRunner {
2283
2331
  ensureDirectoryExists(projectDir);
2284
2332
  const testsDir = path.join(projectDir, 'tests');
2285
2333
  ensureDirectoryExists(testsDir);
2286
- // Save each scenario's code to a .spec.ts file
2334
+ const seenSlugs = new Map();
2287
2335
  for (const scenario of codeGenResult.scenarios) {
2288
- const fileName = `${slugify(scenario.scenarioName)}.spec.ts`;
2336
+ const slug = slugify(scenario.scenarioName);
2337
+ const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
2338
+ seenSlugs.set(slug, count + 1);
2339
+ const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
2289
2340
  const filePath = path.join(testsDir, fileName);
2290
2341
  fs.writeFileSync(filePath, scenario.code, 'utf-8');
2291
2342
  console.log(`โœ… Generated test file: ${filePath}`);
@@ -2299,6 +2350,18 @@ class TestSuiteRunner {
2299
2350
  await this.generateProboReporter(projectDir);
2300
2351
  }
2301
2352
  }
2353
+ /**
2354
+ * Export project code to a directory.
2355
+ * If outputDir is provided, creates a subfolder {outputDir}/{projectId}-{projectNameSlug}/ and writes there.
2356
+ * Otherwise writes to the default project dir (~/.probium/projects/...).
2357
+ */
2358
+ static async exportProjectCode(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = false) {
2359
+ const targetDir = outputDir
2360
+ ? path.join(outputDir, `${projectId}-${slugify(projectName || `project-${projectId}`)}`)
2361
+ : getProjectDir(projectId, projectName);
2362
+ await this.generateProjectFiles(projectId, apiToken, apiUrl, targetDir, projectName, includeReporter);
2363
+ return targetDir;
2364
+ }
2302
2365
  /**
2303
2366
  * Run all scenarios in a project
2304
2367
  * Generates files, installs dependencies, and executes tests
@@ -2307,7 +2370,7 @@ class TestSuiteRunner {
2307
2370
  static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
2308
2371
  const { outputDir, includeReporter = false, // CLI mode - no custom reporter
2309
2372
  playwrightArgs = [], onStdout, onStderr, } = options;
2310
- const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
2373
+ const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
2311
2374
  try {
2312
2375
  // Generate all files
2313
2376
  console.log(`๐Ÿ“ Generating test files for project ${projectId}...`);