@probolabs/playwright 1.5.0-rc.8 โ†’ 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/index.cjs CHANGED
@@ -1315,6 +1315,16 @@ const highlighterCode = "(function (global, factory) {\n typeof exports === '
1315
1315
  };
1316
1316
 
1317
1317
  // --- Code generation utilities for Probo Labs Playwright scripts ---
1318
+ /**
1319
+ * Converts a test suite name to a Playwright tag (slugify + @ prefix).
1320
+ * Playwright tags must start with @.
1321
+ */
1322
+ function slugifyTag(tag) {
1323
+ if (!tag || typeof tag !== 'string')
1324
+ return '@untagged';
1325
+ const slug = slugify(tag);
1326
+ return slug ? `@${slug}` : '@untagged';
1327
+ }
1318
1328
  /**
1319
1329
  * Extracts environment variable names from parameter table rows
1320
1330
  * by parsing ${process.env.VAR_NAME} patterns from parameter values
@@ -1623,6 +1633,13 @@ const highlighterCode = "(function (global, factory) {\n typeof exports === '
1623
1633
  }).join(',\n ')}
1624
1634
  ];
1625
1635
  ` : '';
1636
+ // Playwright tags for test suite filtering (e.g. npx playwright test --grep @sanity)
1637
+ const playwrightTags = options.testSuiteTags && options.testSuiteTags.length > 0
1638
+ ? options.testSuiteTags.map(slugifyTag).filter((t) => t !== '@untagged')
1639
+ : [];
1640
+ const describeTagOption = playwrightTags.length > 0
1641
+ ? `{ tag: [${playwrightTags.map((t) => `'${t}'`).join(', ')}] }, `
1642
+ : '';
1626
1643
  const proboConstructor = hasAiInteractions ? `
1627
1644
  const probo = new Probo({
1628
1645
  scenarioName: '${options.scenarioName}',
@@ -1699,7 +1716,7 @@ ${hasSecrets ? `import { config } from 'dotenv';
1699
1716
  }
1700
1717
  });
1701
1718
 
1702
- test.describe('${options.scenarioName}', () => {
1719
+ test.describe('${options.scenarioName}', ${describeTagOption}() => {
1703
1720
  test.beforeEach(async ({ page }) => {
1704
1721
  // set the ProboPlaywright instance to the current page
1705
1722
  ppw.setPage(page);
@@ -1775,7 +1792,7 @@ ${hasSecrets ? `import { config } from 'dotenv';
1775
1792
  * Generate package.json content for a Playwright test project
1776
1793
  */
1777
1794
  function generatePackageJson(options) {
1778
- const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION } = options;
1795
+ const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION, probolabsPlaywrightVersion = 'latest' } = options;
1779
1796
  return `{
1780
1797
  "name": "${name}",
1781
1798
  "version": "1.0.0",
@@ -1786,7 +1803,7 @@ ${hasSecrets ? `import { config } from 'dotenv';
1786
1803
  },
1787
1804
  "dependencies": {
1788
1805
  "@playwright/test": "${playwrightTestVersion}",
1789
- "@probolabs/playwright": "latest"${hasSecrets ? ',\n "dotenv": "latest"' : ''}
1806
+ "@probolabs/playwright": "${probolabsPlaywrightVersion}"${hasSecrets ? ',\n "dotenv": "latest"' : ''}
1790
1807
  }
1791
1808
  }`;
1792
1809
  }
@@ -6130,12 +6147,12 @@ export default class ProboReporter implements Reporter {
6130
6147
  return response.json();
6131
6148
  }
6132
6149
  /**
6133
- * Fetch all scenarios for a project from the backend API
6150
+ * Fetch all scenarios for a project from the backend API (light endpoint includes test_suite_tags)
6134
6151
  */
6135
6152
  static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
6136
6153
  const normalizedUrl = this.normalizeApiUrl(apiUrl);
6137
- // Scenarios endpoint is at root level, not under /api/
6138
- const url = `${normalizedUrl}/scenarios/?project_id=${projectId}`;
6154
+ // Use api/scenarios/light/ to get test_suite_tags for Playwright tagging
6155
+ const url = `${normalizedUrl}/api/scenarios/light/?project_id=${projectId}`;
6139
6156
  const response = await fetch(url, {
6140
6157
  method: 'GET',
6141
6158
  headers: {
@@ -6273,6 +6290,7 @@ export default class ProboReporter implements Reporter {
6273
6290
  scenarioName: scenarioData.name,
6274
6291
  interactions: interactions,
6275
6292
  rows: rows,
6293
+ ...((options === null || options === void 0 ? void 0 : options.testSuiteTags) && options.testSuiteTags.length > 0 ? { testSuiteTags: options.testSuiteTags } : {}),
6276
6294
  };
6277
6295
  return generateCode(codeOptions, settings, viewPort);
6278
6296
  }
@@ -6332,14 +6350,15 @@ export default class ProboReporter implements Reporter {
6332
6350
  if (scenarios.length === 0) {
6333
6351
  throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
6334
6352
  }
6335
- // Generate code for each scenario
6353
+ // Generate code for each scenario (with test_suite_tags for Playwright tagging)
6336
6354
  const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
6337
6355
  try {
6338
- const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, options);
6356
+ const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, { ...options, testSuiteTags: scenario.test_suite_tags });
6339
6357
  return {
6340
6358
  scenarioId: scenario.id,
6341
6359
  scenarioName: scenario.name,
6342
6360
  code: code,
6361
+ testSuiteTags: scenario.test_suite_tags,
6343
6362
  };
6344
6363
  }
6345
6364
  catch (error) {
@@ -6366,15 +6385,13 @@ export default class ProboReporter implements Reporter {
6366
6385
  }
6367
6386
  }
6368
6387
  /**
6369
- * Gets the default test suite directory path
6388
+ * Gets the project directory path (unified for test suite runs and export).
6389
+ * Path: ~/.probium/projects/{projectId}-{projectName-slugified}/
6370
6390
  */
6371
- function getDefaultTestSuiteDir(testSuiteId, testSuiteName) {
6372
- const baseDir = path__namespace.join(os__namespace.homedir(), '.probium', 'test-suites');
6373
- if (testSuiteName) {
6374
- const sanitizedName = slugify(testSuiteName);
6375
- return path__namespace.join(baseDir, `${testSuiteId}-${sanitizedName}`);
6376
- }
6377
- return path__namespace.join(baseDir, testSuiteId.toString());
6391
+ function getProjectDir(projectId, projectName) {
6392
+ const baseDir = path__namespace.join(os__namespace.homedir(), '.probium', 'projects');
6393
+ const sanitizedName = projectName ? slugify(projectName) : `project-${projectId}`;
6394
+ return path__namespace.join(baseDir, `${projectId}-${sanitizedName}`);
6378
6395
  }
6379
6396
  /**
6380
6397
  * Count total tests by scanning spec files
@@ -6569,36 +6586,67 @@ export default class ProboReporter implements Reporter {
6569
6586
  throw new Error(`Test suite "${testSuiteName}" not found in project "${projectName}"`);
6570
6587
  }
6571
6588
  /**
6572
- * Generate all files for a test suite
6589
+ * Fetch test suite details (project id, project name, test suite name)
6590
+ */
6591
+ static async fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl) {
6592
+ var _a, _b, _c;
6593
+ const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
6594
+ const response = await fetch$1(`${baseUrl}/test-suites/${testSuiteId}/`, {
6595
+ method: 'GET',
6596
+ headers: {
6597
+ 'Authorization': `Token ${apiToken}`,
6598
+ 'Content-Type': 'application/json',
6599
+ },
6600
+ });
6601
+ if (!response.ok) {
6602
+ const errorText = await response.text();
6603
+ throw new Error(`Failed to fetch test suite ${testSuiteId}: ${response.status} ${errorText}`);
6604
+ }
6605
+ const data = await response.json();
6606
+ const projectId = (_a = data.project) !== null && _a !== void 0 ? _a : data.project_id;
6607
+ const projectName = (_b = data.project_name) !== null && _b !== void 0 ? _b : `project-${projectId}`;
6608
+ const testSuiteName = (_c = data.name) !== null && _c !== void 0 ? _c : `suite-${testSuiteId}`;
6609
+ if (!projectId) {
6610
+ throw new Error(`Test suite ${testSuiteId} has no project`);
6611
+ }
6612
+ return { projectId, projectName, testSuiteName };
6613
+ }
6614
+ /**
6615
+ * Generate all files for a test suite (writes to project-level directory)
6573
6616
  */
6574
6617
  static async generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
6575
- const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
6576
- // Generate code for all scenarios
6618
+ const { projectId, projectName, testSuiteName: resolvedSuiteName } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
6619
+ const projectDir = outputDir || getProjectDir(projectId, projectName);
6620
+ await this.generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName !== null && testSuiteName !== void 0 ? testSuiteName : resolvedSuiteName, includeReporter, runId);
6621
+ }
6622
+ /**
6623
+ * Generate files only for the scenarios that participate in the test suite.
6624
+ * Used by the recorder app test-suite runner so only suite specs are written; then we run npx playwright test normally.
6625
+ */
6626
+ static async generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
6627
+ var _a;
6628
+ const { projectId } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
6577
6629
  const codeGenResult = await ProboCodeGenerator.generateCodeForTestSuite(testSuiteId, apiToken, apiUrl);
6578
6630
  if (codeGenResult.scenarios.length === 0) {
6579
- throw new Error(`No scenarios found in test suite ${testSuiteId}. Cannot generate test files.`);
6631
+ throw new Error(`No scenarios found in test suite "${codeGenResult.testSuiteName}" (ID: ${testSuiteId}). Cannot generate test files.`);
6580
6632
  }
6581
- // Delete everything in the test suite directory except node_modules
6582
- // This ensures a clean state while preserving dependencies for faster subsequent runs
6583
- if (fs__namespace.existsSync(testSuiteDir)) {
6633
+ const projectDir = outputDir;
6634
+ if (fs__namespace.existsSync(projectDir)) {
6584
6635
  try {
6585
- const nodeModulesPath = path__namespace.join(testSuiteDir, 'node_modules');
6636
+ const nodeModulesPath = path__namespace.join(projectDir, 'node_modules');
6586
6637
  const hasNodeModules = fs__namespace.existsSync(nodeModulesPath);
6587
- // Temporarily move node_modules out of the way if it exists
6588
6638
  let tempNodeModulesPath = null;
6589
6639
  if (hasNodeModules) {
6590
- tempNodeModulesPath = path__namespace.join(testSuiteDir, '..', `node_modules.temp.${testSuiteId}`);
6591
- // Remove temp directory if it exists from a previous failed run
6640
+ tempNodeModulesPath = path__namespace.join(projectDir, '..', `node_modules.temp.${projectId}`);
6592
6641
  if (fs__namespace.existsSync(tempNodeModulesPath)) {
6593
6642
  fs__namespace.rmSync(tempNodeModulesPath, { recursive: true, force: true });
6594
6643
  }
6595
6644
  fs__namespace.renameSync(nodeModulesPath, tempNodeModulesPath);
6596
6645
  console.log(`๐Ÿ“ฆ Preserved node_modules temporarily`);
6597
6646
  }
6598
- // Delete everything in the directory
6599
- const entries = fs__namespace.readdirSync(testSuiteDir, { withFileTypes: true });
6647
+ const entries = fs__namespace.readdirSync(projectDir, { withFileTypes: true });
6600
6648
  for (const entry of entries) {
6601
- const entryPath = path__namespace.join(testSuiteDir, entry.name);
6649
+ const entryPath = path__namespace.join(projectDir, entry.name);
6602
6650
  try {
6603
6651
  if (entry.isDirectory()) {
6604
6652
  fs__namespace.rmSync(entryPath, { recursive: true, force: true });
@@ -6611,36 +6659,33 @@ export default class ProboReporter implements Reporter {
6611
6659
  console.warn(`โš ๏ธ Failed to delete ${entry.name}:`, error);
6612
6660
  }
6613
6661
  }
6614
- console.log(`๐Ÿ—‘๏ธ Cleaned test suite directory (preserved node_modules)`);
6615
- // Move node_modules back
6662
+ console.log(`๐Ÿ—‘๏ธ Cleaned project directory (preserved node_modules)`);
6616
6663
  if (hasNodeModules && tempNodeModulesPath) {
6617
6664
  fs__namespace.renameSync(tempNodeModulesPath, nodeModulesPath);
6618
6665
  console.log(`๐Ÿ“ฆ Restored node_modules`);
6619
6666
  }
6620
6667
  }
6621
6668
  catch (error) {
6622
- console.warn(`โš ๏ธ Failed to clean test suite directory ${testSuiteDir}:`, error);
6623
- // Continue anyway - we'll overwrite files as needed
6669
+ console.warn(`โš ๏ธ Failed to clean project directory ${projectDir}:`, error);
6624
6670
  }
6625
6671
  }
6626
- // Ensure directories exist (will recreate after deletion)
6627
- ensureDirectoryExists(testSuiteDir);
6628
- const testsDir = path__namespace.join(testSuiteDir, 'tests');
6672
+ ensureDirectoryExists(projectDir);
6673
+ const testsDir = path__namespace.join(projectDir, 'tests');
6629
6674
  ensureDirectoryExists(testsDir);
6630
- // Save each scenario's code to a .spec.ts file
6675
+ const seenSlugs = new Map();
6631
6676
  for (const scenario of codeGenResult.scenarios) {
6632
- const fileName = `${slugify(scenario.scenarioName)}.spec.ts`;
6677
+ const slug = slugify(scenario.scenarioName);
6678
+ const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
6679
+ seenSlugs.set(slug, count + 1);
6680
+ const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
6633
6681
  const filePath = path__namespace.join(testsDir, fileName);
6634
6682
  fs__namespace.writeFileSync(filePath, scenario.code, 'utf-8');
6635
6683
  console.log(`โœ… Generated test file: ${filePath}`);
6636
6684
  }
6637
- // Generate package.json
6638
- await this.generatePackageJson(testSuiteDir, codeGenResult);
6639
- // Generate playwright.config.ts with runId if available
6640
- await this.generatePlaywrightConfig(testSuiteDir, includeReporter, runId);
6641
- // Generate custom reporter file for live progress streaming (only if requested)
6685
+ await this.generatePackageJson(projectDir, codeGenResult);
6686
+ await this.generatePlaywrightConfig(projectDir, includeReporter, runId);
6642
6687
  if (includeReporter) {
6643
- await this.generateProboReporter(testSuiteDir);
6688
+ await this.generateProboReporter(projectDir);
6644
6689
  }
6645
6690
  }
6646
6691
  /**
@@ -6684,7 +6729,8 @@ export default class ProboReporter implements Reporter {
6684
6729
  const packageJsonContent = generatePackageJson({
6685
6730
  name: sanitizedName,
6686
6731
  hasSecrets: hasSecrets,
6687
- testScript: 'npx playwright test'
6732
+ testScript: 'npx playwright test',
6733
+ probolabsPlaywrightVersion: process.env.PROBO_PLAYWRIGHT_VERSION || 'latest',
6688
6734
  });
6689
6735
  const filePath = path__namespace.join(outputDir, 'package.json');
6690
6736
  fs__namespace.writeFileSync(filePath, packageJsonContent, 'utf-8');
@@ -6714,7 +6760,8 @@ export default class ProboReporter implements Reporter {
6714
6760
  */
6715
6761
  static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
6716
6762
  const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
6717
- const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
6763
+ const details = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
6764
+ const projectDir = outputDir || getProjectDir(details.projectId, details.projectName);
6718
6765
  let currentRunId = runId;
6719
6766
  try {
6720
6767
  // Create run record if not provided and not skipping run creation
@@ -6742,9 +6789,9 @@ export default class ProboReporter implements Reporter {
6742
6789
  }
6743
6790
  // Generate all files (with runId if available for run-specific report directories)
6744
6791
  console.log(`๐Ÿ“ Generating test suite files for test suite ${testSuiteId}...`);
6745
- await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, testSuiteDir, testSuiteName, includeReporter, currentRunId);
6792
+ await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName, includeReporter, currentRunId);
6746
6793
  // Count total tests
6747
- const testsTotal = countTotalTests(testSuiteDir);
6794
+ const testsTotal = countTotalTests(projectDir);
6748
6795
  if (currentRunId && testsTotal > 0) {
6749
6796
  await updateRunStatus(currentRunId, testSuiteId, { tests_total: testsTotal }, apiToken, apiUrl);
6750
6797
  if (onStatusUpdate) {
@@ -6752,10 +6799,10 @@ export default class ProboReporter implements Reporter {
6752
6799
  }
6753
6800
  }
6754
6801
  // Install dependencies
6755
- console.log(`๐Ÿ“ฆ Installing dependencies in ${testSuiteDir}...`);
6802
+ console.log(`๐Ÿ“ฆ Installing dependencies in ${projectDir}...`);
6756
6803
  try {
6757
6804
  const npmExecutable = findNpmExecutable();
6758
- const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd: testSuiteDir, timeout: 300000 } // 5 minute timeout for install
6805
+ const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for install
6759
6806
  );
6760
6807
  console.log('โœ… Dependencies installed successfully');
6761
6808
  if (installStdout)
@@ -6795,7 +6842,7 @@ export default class ProboReporter implements Reporter {
6795
6842
  console.log(`๐ŸŒ Installing Playwright Chromium browser...`);
6796
6843
  try {
6797
6844
  const npxExecutable = findNpxExecutable();
6798
- const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd: testSuiteDir, timeout: 300000 } // 5 minute timeout for browser install
6845
+ const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for browser install
6799
6846
  );
6800
6847
  console.log('โœ… Playwright Chromium browser installed successfully');
6801
6848
  if (browserStdout)
@@ -6809,7 +6856,7 @@ export default class ProboReporter implements Reporter {
6809
6856
  // or the error will be caught when trying to run tests
6810
6857
  }
6811
6858
  // Run Playwright tests with streaming output
6812
- console.log(`๐Ÿš€ Running Playwright tests in ${testSuiteDir}...`);
6859
+ console.log(`๐Ÿš€ Running Playwright tests in ${projectDir}...`);
6813
6860
  if (playwrightArgs.length > 0) {
6814
6861
  console.log(`๐Ÿงช Playwright extra args: ${playwrightArgs.join(' ')}`);
6815
6862
  }
@@ -6831,7 +6878,7 @@ export default class ProboReporter implements Reporter {
6831
6878
  // Use spawn for streaming output
6832
6879
  const npxExecutable = findNpxExecutable();
6833
6880
  const testProcess = child_process.spawn(npxExecutable, ['playwright', 'test', ...playwrightArgs], {
6834
- cwd: testSuiteDir,
6881
+ cwd: projectDir,
6835
6882
  shell: true,
6836
6883
  stdio: ['ignore', 'pipe', 'pipe'],
6837
6884
  env: {
@@ -7017,8 +7064,8 @@ export default class ProboReporter implements Reporter {
7017
7064
  console.log(success ? 'โœ… Tests completed successfully' : (isTestFailure ? 'โš ๏ธ Tests completed with failures' : 'โŒ Test execution failed'));
7018
7065
  // Parse final statistics from metadata/stdout as fallback
7019
7066
  let parsedStats = null;
7020
- if (fs__namespace.existsSync(path__namespace.join(testSuiteDir, 'playwright-report'))) {
7021
- parsedStats = parseStatisticsFromMetadata(testSuiteDir);
7067
+ if (fs__namespace.existsSync(path__namespace.join(projectDir, 'playwright-report'))) {
7068
+ parsedStats = parseStatisticsFromMetadata(projectDir);
7022
7069
  }
7023
7070
  if (!parsedStats) {
7024
7071
  parsedStats = parsePlaywrightStatistics(stdout);
@@ -7220,7 +7267,8 @@ export default class ProboReporter implements Reporter {
7220
7267
  * Generate all files for a project (all scenarios)
7221
7268
  */
7222
7269
  static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
7223
- const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
7270
+ var _a;
7271
+ const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
7224
7272
  // Generate code for all scenarios
7225
7273
  const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
7226
7274
  if (codeGenResult.scenarios.length === 0) {
@@ -7274,9 +7322,12 @@ export default class ProboReporter implements Reporter {
7274
7322
  ensureDirectoryExists(projectDir);
7275
7323
  const testsDir = path__namespace.join(projectDir, 'tests');
7276
7324
  ensureDirectoryExists(testsDir);
7277
- // Save each scenario's code to a .spec.ts file
7325
+ const seenSlugs = new Map();
7278
7326
  for (const scenario of codeGenResult.scenarios) {
7279
- const fileName = `${slugify(scenario.scenarioName)}.spec.ts`;
7327
+ const slug = slugify(scenario.scenarioName);
7328
+ const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
7329
+ seenSlugs.set(slug, count + 1);
7330
+ const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
7280
7331
  const filePath = path__namespace.join(testsDir, fileName);
7281
7332
  fs__namespace.writeFileSync(filePath, scenario.code, 'utf-8');
7282
7333
  console.log(`โœ… Generated test file: ${filePath}`);
@@ -7290,6 +7341,18 @@ export default class ProboReporter implements Reporter {
7290
7341
  await this.generateProboReporter(projectDir);
7291
7342
  }
7292
7343
  }
7344
+ /**
7345
+ * Export project code to a directory.
7346
+ * If outputDir is provided, creates a subfolder {outputDir}/{projectId}-{projectNameSlug}/ and writes there.
7347
+ * Otherwise writes to the default project dir (~/.probium/projects/...).
7348
+ */
7349
+ static async exportProjectCode(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = false) {
7350
+ const targetDir = outputDir
7351
+ ? path__namespace.join(outputDir, `${projectId}-${slugify(projectName || `project-${projectId}`)}`)
7352
+ : getProjectDir(projectId, projectName);
7353
+ await this.generateProjectFiles(projectId, apiToken, apiUrl, targetDir, projectName, includeReporter);
7354
+ return targetDir;
7355
+ }
7293
7356
  /**
7294
7357
  * Run all scenarios in a project
7295
7358
  * Generates files, installs dependencies, and executes tests
@@ -7298,7 +7361,7 @@ export default class ProboReporter implements Reporter {
7298
7361
  static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
7299
7362
  const { outputDir, includeReporter = false, // CLI mode - no custom reporter
7300
7363
  playwrightArgs = [], onStdout, onStderr, } = options;
7301
- const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
7364
+ const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
7302
7365
  try {
7303
7366
  // Generate all files
7304
7367
  console.log(`๐Ÿ“ Generating test files for project ${projectId}...`);
@@ -7890,6 +7953,7 @@ export default class ProboReporter implements Reporter {
7890
7953
  exports.extractRequiredEnvVars = extractRequiredEnvVars;
7891
7954
  exports.findClosestVisibleElement = findClosestVisibleElement;
7892
7955
  exports.generateCode = generateCode;
7956
+ exports.getProjectDir = getProjectDir;
7893
7957
  exports.getRequiredEnvVars = getRequiredEnvVars;
7894
7958
  exports.interpolateWithParams = interpolateWithParams;
7895
7959