@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/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
  }
@@ -5125,16 +5142,21 @@ export default class ProboReporter implements Reporter {
5125
5142
  this.nextTabIndex = 1;
5126
5143
  this.params = {};
5127
5144
  this.isCanceled = null;
5145
+ proboLogger.setLogLevel(debugLevel);
5128
5146
  this.enableSmartSelectors = enableSmartSelectors;
5129
5147
  this.timeoutConfig = mergeTimeoutConfigWithEnv({
5130
5148
  ...DEFAULT_PLAYWRIGHT_TIMEOUT_CONFIG,
5131
5149
  ...timeoutConfig
5132
5150
  });
5151
+ proboLogger.debug(`ProboPlaywright timeout config: playwrightActionTimeout=${this.timeoutConfig.playwrightActionTimeout}ms, ` +
5152
+ `playwrightNavigationTimeout=${this.timeoutConfig.playwrightNavigationTimeout}ms, ` +
5153
+ `playwrightLocatorTimeout=${this.timeoutConfig.playwrightLocatorTimeout}ms, ` +
5154
+ `waitForStabilityQuietTimeout=${this.timeoutConfig.waitForStabilityQuietTimeout}ms, ` +
5155
+ `waitForStabilityGlobalTimeout=${this.timeoutConfig.waitForStabilityGlobalTimeout}ms`);
5133
5156
  this.isCanceled = isCanceled || null;
5134
5157
  this.highlighter = new Highlighter(enableSmartSelectors, false, debugLevel);
5135
5158
  this.context = context !== null && context !== void 0 ? context : null;
5136
5159
  this.setPage(page);
5137
- proboLogger.setLogLevel(debugLevel);
5138
5160
  }
5139
5161
  /**
5140
5162
  * Sets the Playwright page instance for this ProboPlaywright instance.
@@ -6125,12 +6147,12 @@ export default class ProboReporter implements Reporter {
6125
6147
  return response.json();
6126
6148
  }
6127
6149
  /**
6128
- * 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)
6129
6151
  */
6130
6152
  static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
6131
6153
  const normalizedUrl = this.normalizeApiUrl(apiUrl);
6132
- // Scenarios endpoint is at root level, not under /api/
6133
- 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}`;
6134
6156
  const response = await fetch(url, {
6135
6157
  method: 'GET',
6136
6158
  headers: {
@@ -6268,6 +6290,7 @@ export default class ProboReporter implements Reporter {
6268
6290
  scenarioName: scenarioData.name,
6269
6291
  interactions: interactions,
6270
6292
  rows: rows,
6293
+ ...((options === null || options === void 0 ? void 0 : options.testSuiteTags) && options.testSuiteTags.length > 0 ? { testSuiteTags: options.testSuiteTags } : {}),
6271
6294
  };
6272
6295
  return generateCode(codeOptions, settings, viewPort);
6273
6296
  }
@@ -6327,14 +6350,15 @@ export default class ProboReporter implements Reporter {
6327
6350
  if (scenarios.length === 0) {
6328
6351
  throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
6329
6352
  }
6330
- // Generate code for each scenario
6353
+ // Generate code for each scenario (with test_suite_tags for Playwright tagging)
6331
6354
  const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
6332
6355
  try {
6333
- 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 });
6334
6357
  return {
6335
6358
  scenarioId: scenario.id,
6336
6359
  scenarioName: scenario.name,
6337
6360
  code: code,
6361
+ testSuiteTags: scenario.test_suite_tags,
6338
6362
  };
6339
6363
  }
6340
6364
  catch (error) {
@@ -6361,15 +6385,13 @@ export default class ProboReporter implements Reporter {
6361
6385
  }
6362
6386
  }
6363
6387
  /**
6364
- * 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}/
6365
6390
  */
6366
- function getDefaultTestSuiteDir(testSuiteId, testSuiteName) {
6367
- const baseDir = path__namespace.join(os__namespace.homedir(), '.probium', 'test-suites');
6368
- if (testSuiteName) {
6369
- const sanitizedName = slugify(testSuiteName);
6370
- return path__namespace.join(baseDir, `${testSuiteId}-${sanitizedName}`);
6371
- }
6372
- 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}`);
6373
6395
  }
6374
6396
  /**
6375
6397
  * Count total tests by scanning spec files
@@ -6564,36 +6586,67 @@ export default class ProboReporter implements Reporter {
6564
6586
  throw new Error(`Test suite "${testSuiteName}" not found in project "${projectName}"`);
6565
6587
  }
6566
6588
  /**
6567
- * 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)
6568
6616
  */
6569
6617
  static async generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
6570
- const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
6571
- // 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);
6572
6629
  const codeGenResult = await ProboCodeGenerator.generateCodeForTestSuite(testSuiteId, apiToken, apiUrl);
6573
6630
  if (codeGenResult.scenarios.length === 0) {
6574
- 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.`);
6575
6632
  }
6576
- // Delete everything in the test suite directory except node_modules
6577
- // This ensures a clean state while preserving dependencies for faster subsequent runs
6578
- if (fs__namespace.existsSync(testSuiteDir)) {
6633
+ const projectDir = outputDir;
6634
+ if (fs__namespace.existsSync(projectDir)) {
6579
6635
  try {
6580
- const nodeModulesPath = path__namespace.join(testSuiteDir, 'node_modules');
6636
+ const nodeModulesPath = path__namespace.join(projectDir, 'node_modules');
6581
6637
  const hasNodeModules = fs__namespace.existsSync(nodeModulesPath);
6582
- // Temporarily move node_modules out of the way if it exists
6583
6638
  let tempNodeModulesPath = null;
6584
6639
  if (hasNodeModules) {
6585
- tempNodeModulesPath = path__namespace.join(testSuiteDir, '..', `node_modules.temp.${testSuiteId}`);
6586
- // Remove temp directory if it exists from a previous failed run
6640
+ tempNodeModulesPath = path__namespace.join(projectDir, '..', `node_modules.temp.${projectId}`);
6587
6641
  if (fs__namespace.existsSync(tempNodeModulesPath)) {
6588
6642
  fs__namespace.rmSync(tempNodeModulesPath, { recursive: true, force: true });
6589
6643
  }
6590
6644
  fs__namespace.renameSync(nodeModulesPath, tempNodeModulesPath);
6591
6645
  console.log(`๐Ÿ“ฆ Preserved node_modules temporarily`);
6592
6646
  }
6593
- // Delete everything in the directory
6594
- const entries = fs__namespace.readdirSync(testSuiteDir, { withFileTypes: true });
6647
+ const entries = fs__namespace.readdirSync(projectDir, { withFileTypes: true });
6595
6648
  for (const entry of entries) {
6596
- const entryPath = path__namespace.join(testSuiteDir, entry.name);
6649
+ const entryPath = path__namespace.join(projectDir, entry.name);
6597
6650
  try {
6598
6651
  if (entry.isDirectory()) {
6599
6652
  fs__namespace.rmSync(entryPath, { recursive: true, force: true });
@@ -6606,36 +6659,33 @@ export default class ProboReporter implements Reporter {
6606
6659
  console.warn(`โš ๏ธ Failed to delete ${entry.name}:`, error);
6607
6660
  }
6608
6661
  }
6609
- console.log(`๐Ÿ—‘๏ธ Cleaned test suite directory (preserved node_modules)`);
6610
- // Move node_modules back
6662
+ console.log(`๐Ÿ—‘๏ธ Cleaned project directory (preserved node_modules)`);
6611
6663
  if (hasNodeModules && tempNodeModulesPath) {
6612
6664
  fs__namespace.renameSync(tempNodeModulesPath, nodeModulesPath);
6613
6665
  console.log(`๐Ÿ“ฆ Restored node_modules`);
6614
6666
  }
6615
6667
  }
6616
6668
  catch (error) {
6617
- console.warn(`โš ๏ธ Failed to clean test suite directory ${testSuiteDir}:`, error);
6618
- // Continue anyway - we'll overwrite files as needed
6669
+ console.warn(`โš ๏ธ Failed to clean project directory ${projectDir}:`, error);
6619
6670
  }
6620
6671
  }
6621
- // Ensure directories exist (will recreate after deletion)
6622
- ensureDirectoryExists(testSuiteDir);
6623
- const testsDir = path__namespace.join(testSuiteDir, 'tests');
6672
+ ensureDirectoryExists(projectDir);
6673
+ const testsDir = path__namespace.join(projectDir, 'tests');
6624
6674
  ensureDirectoryExists(testsDir);
6625
- // Save each scenario's code to a .spec.ts file
6675
+ const seenSlugs = new Map();
6626
6676
  for (const scenario of codeGenResult.scenarios) {
6627
- 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`;
6628
6681
  const filePath = path__namespace.join(testsDir, fileName);
6629
6682
  fs__namespace.writeFileSync(filePath, scenario.code, 'utf-8');
6630
6683
  console.log(`โœ… Generated test file: ${filePath}`);
6631
6684
  }
6632
- // Generate package.json
6633
- await this.generatePackageJson(testSuiteDir, codeGenResult);
6634
- // Generate playwright.config.ts with runId if available
6635
- await this.generatePlaywrightConfig(testSuiteDir, includeReporter, runId);
6636
- // Generate custom reporter file for live progress streaming (only if requested)
6685
+ await this.generatePackageJson(projectDir, codeGenResult);
6686
+ await this.generatePlaywrightConfig(projectDir, includeReporter, runId);
6637
6687
  if (includeReporter) {
6638
- await this.generateProboReporter(testSuiteDir);
6688
+ await this.generateProboReporter(projectDir);
6639
6689
  }
6640
6690
  }
6641
6691
  /**
@@ -6679,7 +6729,8 @@ export default class ProboReporter implements Reporter {
6679
6729
  const packageJsonContent = generatePackageJson({
6680
6730
  name: sanitizedName,
6681
6731
  hasSecrets: hasSecrets,
6682
- testScript: 'npx playwright test'
6732
+ testScript: 'npx playwright test',
6733
+ probolabsPlaywrightVersion: process.env.PROBO_PLAYWRIGHT_VERSION || 'latest',
6683
6734
  });
6684
6735
  const filePath = path__namespace.join(outputDir, 'package.json');
6685
6736
  fs__namespace.writeFileSync(filePath, packageJsonContent, 'utf-8');
@@ -6709,7 +6760,8 @@ export default class ProboReporter implements Reporter {
6709
6760
  */
6710
6761
  static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
6711
6762
  const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
6712
- const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
6763
+ const details = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
6764
+ const projectDir = outputDir || getProjectDir(details.projectId, details.projectName);
6713
6765
  let currentRunId = runId;
6714
6766
  try {
6715
6767
  // Create run record if not provided and not skipping run creation
@@ -6737,9 +6789,9 @@ export default class ProboReporter implements Reporter {
6737
6789
  }
6738
6790
  // Generate all files (with runId if available for run-specific report directories)
6739
6791
  console.log(`๐Ÿ“ Generating test suite files for test suite ${testSuiteId}...`);
6740
- await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, testSuiteDir, testSuiteName, includeReporter, currentRunId);
6792
+ await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName, includeReporter, currentRunId);
6741
6793
  // Count total tests
6742
- const testsTotal = countTotalTests(testSuiteDir);
6794
+ const testsTotal = countTotalTests(projectDir);
6743
6795
  if (currentRunId && testsTotal > 0) {
6744
6796
  await updateRunStatus(currentRunId, testSuiteId, { tests_total: testsTotal }, apiToken, apiUrl);
6745
6797
  if (onStatusUpdate) {
@@ -6747,10 +6799,10 @@ export default class ProboReporter implements Reporter {
6747
6799
  }
6748
6800
  }
6749
6801
  // Install dependencies
6750
- console.log(`๐Ÿ“ฆ Installing dependencies in ${testSuiteDir}...`);
6802
+ console.log(`๐Ÿ“ฆ Installing dependencies in ${projectDir}...`);
6751
6803
  try {
6752
6804
  const npmExecutable = findNpmExecutable();
6753
- 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
6754
6806
  );
6755
6807
  console.log('โœ… Dependencies installed successfully');
6756
6808
  if (installStdout)
@@ -6790,7 +6842,7 @@ export default class ProboReporter implements Reporter {
6790
6842
  console.log(`๐ŸŒ Installing Playwright Chromium browser...`);
6791
6843
  try {
6792
6844
  const npxExecutable = findNpxExecutable();
6793
- 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
6794
6846
  );
6795
6847
  console.log('โœ… Playwright Chromium browser installed successfully');
6796
6848
  if (browserStdout)
@@ -6804,7 +6856,7 @@ export default class ProboReporter implements Reporter {
6804
6856
  // or the error will be caught when trying to run tests
6805
6857
  }
6806
6858
  // Run Playwright tests with streaming output
6807
- console.log(`๐Ÿš€ Running Playwright tests in ${testSuiteDir}...`);
6859
+ console.log(`๐Ÿš€ Running Playwright tests in ${projectDir}...`);
6808
6860
  if (playwrightArgs.length > 0) {
6809
6861
  console.log(`๐Ÿงช Playwright extra args: ${playwrightArgs.join(' ')}`);
6810
6862
  }
@@ -6826,7 +6878,7 @@ export default class ProboReporter implements Reporter {
6826
6878
  // Use spawn for streaming output
6827
6879
  const npxExecutable = findNpxExecutable();
6828
6880
  const testProcess = child_process.spawn(npxExecutable, ['playwright', 'test', ...playwrightArgs], {
6829
- cwd: testSuiteDir,
6881
+ cwd: projectDir,
6830
6882
  shell: true,
6831
6883
  stdio: ['ignore', 'pipe', 'pipe'],
6832
6884
  env: {
@@ -7012,8 +7064,8 @@ export default class ProboReporter implements Reporter {
7012
7064
  console.log(success ? 'โœ… Tests completed successfully' : (isTestFailure ? 'โš ๏ธ Tests completed with failures' : 'โŒ Test execution failed'));
7013
7065
  // Parse final statistics from metadata/stdout as fallback
7014
7066
  let parsedStats = null;
7015
- if (fs__namespace.existsSync(path__namespace.join(testSuiteDir, 'playwright-report'))) {
7016
- parsedStats = parseStatisticsFromMetadata(testSuiteDir);
7067
+ if (fs__namespace.existsSync(path__namespace.join(projectDir, 'playwright-report'))) {
7068
+ parsedStats = parseStatisticsFromMetadata(projectDir);
7017
7069
  }
7018
7070
  if (!parsedStats) {
7019
7071
  parsedStats = parsePlaywrightStatistics(stdout);
@@ -7215,7 +7267,8 @@ export default class ProboReporter implements Reporter {
7215
7267
  * Generate all files for a project (all scenarios)
7216
7268
  */
7217
7269
  static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
7218
- const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
7270
+ var _a;
7271
+ const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
7219
7272
  // Generate code for all scenarios
7220
7273
  const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
7221
7274
  if (codeGenResult.scenarios.length === 0) {
@@ -7269,9 +7322,12 @@ export default class ProboReporter implements Reporter {
7269
7322
  ensureDirectoryExists(projectDir);
7270
7323
  const testsDir = path__namespace.join(projectDir, 'tests');
7271
7324
  ensureDirectoryExists(testsDir);
7272
- // Save each scenario's code to a .spec.ts file
7325
+ const seenSlugs = new Map();
7273
7326
  for (const scenario of codeGenResult.scenarios) {
7274
- 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`;
7275
7331
  const filePath = path__namespace.join(testsDir, fileName);
7276
7332
  fs__namespace.writeFileSync(filePath, scenario.code, 'utf-8');
7277
7333
  console.log(`โœ… Generated test file: ${filePath}`);
@@ -7285,6 +7341,18 @@ export default class ProboReporter implements Reporter {
7285
7341
  await this.generateProboReporter(projectDir);
7286
7342
  }
7287
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
+ }
7288
7356
  /**
7289
7357
  * Run all scenarios in a project
7290
7358
  * Generates files, installs dependencies, and executes tests
@@ -7293,7 +7361,7 @@ export default class ProboReporter implements Reporter {
7293
7361
  static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
7294
7362
  const { outputDir, includeReporter = false, // CLI mode - no custom reporter
7295
7363
  playwrightArgs = [], onStdout, onStderr, } = options;
7296
- const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
7364
+ const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
7297
7365
  try {
7298
7366
  // Generate all files
7299
7367
  console.log(`๐Ÿ“ Generating test files for project ${projectId}...`);
@@ -7532,6 +7600,11 @@ export default class ProboReporter implements Reporter {
7532
7600
  this.timeoutConfig = mergeTimeoutConfigWithEnv({ ...DEFAULT_PLAYWRIGHT_TIMEOUT_CONFIG, ...timeoutConfig });
7533
7601
  // set the log level for the api client
7534
7602
  apiLogger.setLogLevel(this.debugLevel);
7603
+ proboLogger.debug(`Timeout config: playwrightActionTimeout=${this.timeoutConfig.playwrightActionTimeout}ms, ` +
7604
+ `playwrightNavigationTimeout=${this.timeoutConfig.playwrightNavigationTimeout}ms, ` +
7605
+ `playwrightLocatorTimeout=${this.timeoutConfig.playwrightLocatorTimeout}ms, ` +
7606
+ `waitForStabilityQuietTimeout=${this.timeoutConfig.waitForStabilityQuietTimeout}ms, ` +
7607
+ `waitForStabilityGlobalTimeout=${this.timeoutConfig.waitForStabilityGlobalTimeout}ms`);
7535
7608
  proboLogger.info(`Initializing: scenario=${scenarioName}, apiUrl=${apiEndPoint}, ` +
7536
7609
  `enableConsoleLogs=${enableConsoleLogs}, debugLevel=${debugLevel}, aiModel=${aiModel}`);
7537
7610
  }
@@ -7880,6 +7953,7 @@ export default class ProboReporter implements Reporter {
7880
7953
  exports.extractRequiredEnvVars = extractRequiredEnvVars;
7881
7954
  exports.findClosestVisibleElement = findClosestVisibleElement;
7882
7955
  exports.generateCode = generateCode;
7956
+ exports.getProjectDir = getProjectDir;
7883
7957
  exports.getRequiredEnvVars = getRequiredEnvVars;
7884
7958
  exports.interpolateWithParams = interpolateWithParams;
7885
7959