@probolabs/playwright 1.4.0-rc.5 โ†’ 1.4.0-rc.7

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.d.ts CHANGED
@@ -404,6 +404,18 @@ interface TestSuiteCodeGenResult {
404
404
  code: string;
405
405
  }>;
406
406
  }
407
+ /**
408
+ * Result of generating code for a project (all scenarios)
409
+ */
410
+ interface ProjectCodeGenResult {
411
+ projectId: number;
412
+ projectName: string;
413
+ scenarios: Array<{
414
+ scenarioId: number;
415
+ scenarioName: string;
416
+ code: string;
417
+ }>;
418
+ }
407
419
  /**
408
420
  * ProboCodeGenerator - Handles fetching test suite/scenario data and generating Playwright code
409
421
  */
@@ -421,6 +433,14 @@ declare class ProboCodeGenerator {
421
433
  * Fetch test suite data from the backend API
422
434
  */
423
435
  private static fetchTestSuiteData;
436
+ /**
437
+ * Fetch project data from the backend API
438
+ */
439
+ private static fetchProjectData;
440
+ /**
441
+ * Fetch all scenarios for a project from the backend API
442
+ */
443
+ private static fetchProjectScenarios;
424
444
  /**
425
445
  * Convert backend interaction format to Interaction[] format
426
446
  */
@@ -448,6 +468,11 @@ declare class ProboCodeGenerator {
448
468
  * Returns map of scenario names to generated code
449
469
  */
450
470
  static generateCodeForTestSuite(testSuiteId: number, apiToken: string, apiUrl: string, options?: CodeGenOptions): Promise<TestSuiteCodeGenResult>;
471
+ /**
472
+ * Generate code for all scenarios in a project
473
+ * Returns map of scenario names to generated code
474
+ */
475
+ static generateCodeForProject(projectId: number, apiToken: string, apiUrl: string, options?: CodeGenOptions): Promise<ProjectCodeGenResult>;
451
476
  }
452
477
 
453
478
  /**
@@ -476,6 +501,7 @@ interface TestSuiteRunOptions {
476
501
  outputDir?: string;
477
502
  includeReporter?: boolean;
478
503
  playwrightArgs?: string[];
504
+ skipRunCreation?: boolean;
479
505
  onStatusUpdate?: (updates: Partial<{
480
506
  status: string;
481
507
  exit_code: number;
@@ -495,6 +521,10 @@ interface TestSuiteRunOptions {
495
521
  * TestSuiteRunner - Handles test suite file generation and execution
496
522
  */
497
523
  declare class TestSuiteRunner {
524
+ /**
525
+ * Lookup project ID by name
526
+ */
527
+ static lookupProjectByName(projectName: string, apiToken: string, apiUrl: string): Promise<number>;
498
528
  /**
499
529
  * Lookup test suite ID by name and project
500
530
  */
@@ -506,7 +536,7 @@ declare class TestSuiteRunner {
506
536
  /**
507
537
  * Generate package.json file
508
538
  */
509
- static generatePackageJson(outputDir: string, codeGenResult: TestSuiteCodeGenResult): Promise<void>;
539
+ static generatePackageJson(outputDir: string, codeGenResult: TestSuiteCodeGenResult | ProjectCodeGenResult): Promise<void>;
510
540
  /**
511
541
  * Generate playwright.config.ts file
512
542
  */
@@ -520,6 +550,16 @@ declare class TestSuiteRunner {
520
550
  * Generates files, installs dependencies, and executes tests
521
551
  */
522
552
  static runTestSuite(testSuiteId: number, apiToken: string, apiUrl: string, testSuiteName?: string, runId?: number, options?: TestSuiteRunOptions): Promise<TestSuiteRunResult>;
553
+ /**
554
+ * Generate all files for a project (all scenarios)
555
+ */
556
+ static generateProjectFiles(projectId: number, apiToken: string, apiUrl: string, outputDir?: string, projectName?: string, includeReporter?: boolean): Promise<void>;
557
+ /**
558
+ * Run all scenarios in a project
559
+ * Generates files, installs dependencies, and executes tests
560
+ * Does not create run records in the database (CLI mode)
561
+ */
562
+ static runProjectScenarios(projectId: number, apiToken: string, apiUrl: string, projectName?: string, options?: TestSuiteRunOptions): Promise<TestSuiteRunResult>;
523
563
  /**
524
564
  * Upload artifacts for a test suite run
525
565
  *
package/dist/index.js CHANGED
@@ -5742,6 +5742,7 @@ class ProboCodeGenerator {
5742
5742
  */
5743
5743
  static async fetchScenarioData(scenarioId, apiToken, apiUrl) {
5744
5744
  const normalizedUrl = this.normalizeApiUrl(apiUrl);
5745
+ // Scenario interactions endpoint is under /api/ (special endpoint)
5745
5746
  const url = `${normalizedUrl}/api/scenarios/${scenarioId}/interactions`;
5746
5747
  const response = await fetch(url, {
5747
5748
  method: 'GET',
@@ -5776,6 +5777,54 @@ class ProboCodeGenerator {
5776
5777
  }
5777
5778
  return response.json();
5778
5779
  }
5780
+ /**
5781
+ * Fetch project data from the backend API
5782
+ */
5783
+ static async fetchProjectData(projectId, apiToken, apiUrl) {
5784
+ const normalizedUrl = this.normalizeApiUrl(apiUrl);
5785
+ // Projects endpoint is at root level, not under /api/
5786
+ const url = `${normalizedUrl}/projects/${projectId}/`;
5787
+ const response = await fetch(url, {
5788
+ method: 'GET',
5789
+ headers: {
5790
+ 'Authorization': `Token ${apiToken}`,
5791
+ 'Content-Type': 'application/json',
5792
+ },
5793
+ });
5794
+ if (!response.ok) {
5795
+ const errorText = await this.readResponseErrorText(response);
5796
+ throw new Error(`Failed to fetch project ${projectId}: ${response.status} ${errorText}`);
5797
+ }
5798
+ return response.json();
5799
+ }
5800
+ /**
5801
+ * Fetch all scenarios for a project from the backend API
5802
+ */
5803
+ static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
5804
+ const normalizedUrl = this.normalizeApiUrl(apiUrl);
5805
+ // Scenarios endpoint is at root level, not under /api/
5806
+ const url = `${normalizedUrl}/scenarios/?project_id=${projectId}`;
5807
+ const response = await fetch(url, {
5808
+ method: 'GET',
5809
+ headers: {
5810
+ 'Authorization': `Token ${apiToken}`,
5811
+ 'Content-Type': 'application/json',
5812
+ },
5813
+ });
5814
+ if (!response.ok) {
5815
+ const errorText = await this.readResponseErrorText(response);
5816
+ throw new Error(`Failed to fetch scenarios for project ${projectId}: ${response.status} ${errorText}`);
5817
+ }
5818
+ const data = await response.json();
5819
+ // Handle both array and paginated responses
5820
+ if (Array.isArray(data)) {
5821
+ return data;
5822
+ }
5823
+ if (data.results && Array.isArray(data.results)) {
5824
+ return data.results;
5825
+ }
5826
+ return [];
5827
+ }
5779
5828
  /**
5780
5829
  * Convert backend interaction format to Interaction[] format
5781
5830
  */
@@ -5926,6 +5975,48 @@ class ProboCodeGenerator {
5926
5975
  scenarios: scenarioResults,
5927
5976
  };
5928
5977
  }
5978
+ /**
5979
+ * Generate code for all scenarios in a project
5980
+ * Returns map of scenario names to generated code
5981
+ */
5982
+ static async generateCodeForProject(projectId, apiToken, apiUrl, options) {
5983
+ // Validate inputs
5984
+ if (!apiToken) {
5985
+ throw new Error('API token is required');
5986
+ }
5987
+ if (!apiUrl) {
5988
+ throw new Error('API URL is required');
5989
+ }
5990
+ // Fetch project data and scenarios
5991
+ const [projectData, scenarios] = await Promise.all([
5992
+ this.fetchProjectData(projectId, apiToken, apiUrl),
5993
+ this.fetchProjectScenarios(projectId, apiToken, apiUrl),
5994
+ ]);
5995
+ if (scenarios.length === 0) {
5996
+ throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
5997
+ }
5998
+ // Generate code for each scenario
5999
+ const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
6000
+ try {
6001
+ const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, options);
6002
+ return {
6003
+ scenarioId: scenario.id,
6004
+ scenarioName: scenario.name,
6005
+ code: code,
6006
+ };
6007
+ }
6008
+ catch (error) {
6009
+ // Log error but continue with other scenarios
6010
+ console.error(`Failed to generate code for scenario ${scenario.id}: ${error.message}`);
6011
+ throw error; // Re-throw to fail fast for now
6012
+ }
6013
+ }));
6014
+ return {
6015
+ projectId: projectData.id,
6016
+ projectName: projectData.name,
6017
+ scenarios: scenarioResults,
6018
+ };
6019
+ }
5929
6020
  }
5930
6021
 
5931
6022
  const execAsync = promisify(exec);
@@ -6048,11 +6139,12 @@ async function updateRunStatus(runId, testSuiteId, updates, apiToken, apiUrl) {
6048
6139
  */
6049
6140
  class TestSuiteRunner {
6050
6141
  /**
6051
- * Lookup test suite ID by name and project
6142
+ * Lookup project ID by name
6052
6143
  */
6053
- static async lookupTestSuiteByName(testSuiteName, projectName, apiToken, apiUrl) {
6144
+ static async lookupProjectByName(projectName, apiToken, apiUrl) {
6054
6145
  const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
6055
- const url = `${baseUrl}/test-suites/?name=${encodeURIComponent(testSuiteName)}&project=${encodeURIComponent(projectName)}`;
6146
+ // Projects endpoint is at root level, not under /api/
6147
+ const url = `${baseUrl}/projects/?name=${encodeURIComponent(projectName)}`;
6056
6148
  const response = await fetch$1(url, {
6057
6149
  method: 'GET',
6058
6150
  headers: {
@@ -6062,25 +6154,35 @@ class TestSuiteRunner {
6062
6154
  });
6063
6155
  if (!response.ok) {
6064
6156
  const errorText = await response.text();
6065
- throw new Error(`Failed to lookup test suite: ${response.status} ${errorText}`);
6157
+ throw new Error(`Failed to lookup project: ${response.status} ${errorText}`);
6066
6158
  }
6067
6159
  const data = await response.json();
6068
- // TEMPORARY DEBUG: Output the full response
6069
- console.log('๐Ÿ” DEBUG: API Response URL:', url);
6070
- console.log('๐Ÿ” DEBUG: API Response Status:', response.status);
6071
- console.log('๐Ÿ” DEBUG: API Response Data:', JSON.stringify(data, null, 2));
6072
- console.log('๐Ÿ” DEBUG: Is Array?', Array.isArray(data));
6073
- console.log('๐Ÿ” DEBUG: Array length:', Array.isArray(data) ? data.length : 'N/A');
6074
- console.log('๐Ÿ” DEBUG: Has results?', !!data.results);
6075
- console.log('๐Ÿ” DEBUG: Results is Array?', Array.isArray(data.results));
6076
- console.log('๐Ÿ” DEBUG: Results length:', Array.isArray(data.results) ? data.results.length : 'N/A');
6077
6160
  if (Array.isArray(data) && data.length > 0) {
6078
- console.log('๐Ÿ” DEBUG: First item:', JSON.stringify(data[0], null, 2));
6161
+ return data[0].id;
6079
6162
  }
6080
6163
  if (data.results && Array.isArray(data.results) && data.results.length > 0) {
6081
- console.log('๐Ÿ” DEBUG: First result:', JSON.stringify(data.results[0], null, 2));
6164
+ return data.results[0].id;
6082
6165
  }
6083
- console.log('');
6166
+ throw new Error(`Project "${projectName}" not found`);
6167
+ }
6168
+ /**
6169
+ * Lookup test suite ID by name and project
6170
+ */
6171
+ static async lookupTestSuiteByName(testSuiteName, projectName, apiToken, apiUrl) {
6172
+ const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
6173
+ const url = `${baseUrl}/test-suites/?name=${encodeURIComponent(testSuiteName)}&project=${encodeURIComponent(projectName)}`;
6174
+ const response = await fetch$1(url, {
6175
+ method: 'GET',
6176
+ headers: {
6177
+ 'Authorization': `Token ${apiToken}`,
6178
+ 'Content-Type': 'application/json',
6179
+ },
6180
+ });
6181
+ if (!response.ok) {
6182
+ const errorText = await response.text();
6183
+ throw new Error(`Failed to lookup test suite: ${response.status} ${errorText}`);
6184
+ }
6185
+ const data = await response.json();
6084
6186
  if (Array.isArray(data) && data.length > 0) {
6085
6187
  return data[0].id;
6086
6188
  }
@@ -6198,7 +6300,10 @@ class TestSuiteRunner {
6198
6300
  }
6199
6301
  }
6200
6302
  }
6201
- const sanitizedName = slugify(codeGenResult.testSuiteName);
6303
+ // Type guard: check for projectId to distinguish ProjectCodeGenResult from TestSuiteCodeGenResult
6304
+ const sanitizedName = 'projectId' in codeGenResult && 'projectName' in codeGenResult
6305
+ ? slugify(codeGenResult.projectName)
6306
+ : slugify(codeGenResult.testSuiteName);
6202
6307
  const packageJsonContent = generatePackageJson({
6203
6308
  name: sanitizedName,
6204
6309
  hasSecrets: hasSecrets,
@@ -6231,12 +6336,12 @@ class TestSuiteRunner {
6231
6336
  * Generates files, installs dependencies, and executes tests
6232
6337
  */
6233
6338
  static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
6234
- const { outputDir, includeReporter = true, playwrightArgs = [], onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
6339
+ const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
6235
6340
  const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
6236
6341
  let currentRunId = runId;
6237
6342
  try {
6238
- // Create run record if not provided (needed for run-specific report directories)
6239
- if (!currentRunId) {
6343
+ // Create run record if not provided and not skipping run creation
6344
+ if (!currentRunId && !skipRunCreation) {
6240
6345
  try {
6241
6346
  const createResponse = await fetch$1(`${apiUrl}/test-suites/${testSuiteId}/runs/`, {
6242
6347
  method: 'POST',
@@ -6661,6 +6766,233 @@ class TestSuiteRunner {
6661
6766
  };
6662
6767
  }
6663
6768
  }
6769
+ /**
6770
+ * Generate all files for a project (all scenarios)
6771
+ */
6772
+ static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
6773
+ const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
6774
+ // Generate code for all scenarios
6775
+ const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
6776
+ if (codeGenResult.scenarios.length === 0) {
6777
+ throw new Error(`No scenarios found in project "${codeGenResult.projectName}" (ID: ${projectId}). Cannot generate test files.`);
6778
+ }
6779
+ // Delete everything in the project directory except node_modules
6780
+ if (fs.existsSync(projectDir)) {
6781
+ try {
6782
+ const nodeModulesPath = path.join(projectDir, 'node_modules');
6783
+ const hasNodeModules = fs.existsSync(nodeModulesPath);
6784
+ // Temporarily move node_modules out of the way if it exists
6785
+ let tempNodeModulesPath = null;
6786
+ if (hasNodeModules) {
6787
+ tempNodeModulesPath = path.join(projectDir, '..', `node_modules.temp.${projectId}`);
6788
+ // Remove temp directory if it exists from a previous failed run
6789
+ if (fs.existsSync(tempNodeModulesPath)) {
6790
+ fs.rmSync(tempNodeModulesPath, { recursive: true, force: true });
6791
+ }
6792
+ fs.renameSync(nodeModulesPath, tempNodeModulesPath);
6793
+ console.log(`๐Ÿ“ฆ Preserved node_modules temporarily`);
6794
+ }
6795
+ // Delete everything in the directory
6796
+ const entries = fs.readdirSync(projectDir, { withFileTypes: true });
6797
+ for (const entry of entries) {
6798
+ const entryPath = path.join(projectDir, entry.name);
6799
+ try {
6800
+ if (entry.isDirectory()) {
6801
+ fs.rmSync(entryPath, { recursive: true, force: true });
6802
+ }
6803
+ else {
6804
+ fs.unlinkSync(entryPath);
6805
+ }
6806
+ }
6807
+ catch (error) {
6808
+ console.warn(`โš ๏ธ Failed to delete ${entry.name}:`, error);
6809
+ }
6810
+ }
6811
+ console.log(`๐Ÿ—‘๏ธ Cleaned project directory (preserved node_modules)`);
6812
+ // Move node_modules back
6813
+ if (hasNodeModules && tempNodeModulesPath) {
6814
+ fs.renameSync(tempNodeModulesPath, nodeModulesPath);
6815
+ console.log(`๐Ÿ“ฆ Restored node_modules`);
6816
+ }
6817
+ }
6818
+ catch (error) {
6819
+ console.warn(`โš ๏ธ Failed to clean project directory ${projectDir}:`, error);
6820
+ // Continue anyway - we'll overwrite files as needed
6821
+ }
6822
+ }
6823
+ // Ensure directories exist (will recreate after deletion)
6824
+ ensureDirectoryExists(projectDir);
6825
+ const testsDir = path.join(projectDir, 'tests');
6826
+ ensureDirectoryExists(testsDir);
6827
+ // Save each scenario's code to a .spec.ts file
6828
+ for (const scenario of codeGenResult.scenarios) {
6829
+ const fileName = `${slugify(scenario.scenarioName)}.spec.ts`;
6830
+ const filePath = path.join(testsDir, fileName);
6831
+ fs.writeFileSync(filePath, scenario.code, 'utf-8');
6832
+ console.log(`โœ… Generated test file: ${filePath}`);
6833
+ }
6834
+ // Generate package.json
6835
+ await this.generatePackageJson(projectDir, codeGenResult);
6836
+ // Generate playwright.config.ts (no runId for project runs)
6837
+ await this.generatePlaywrightConfig(projectDir, includeReporter, undefined);
6838
+ // Generate custom reporter file for live progress streaming (only if requested)
6839
+ if (includeReporter) {
6840
+ await this.generateProboReporter(projectDir);
6841
+ }
6842
+ }
6843
+ /**
6844
+ * Run all scenarios in a project
6845
+ * Generates files, installs dependencies, and executes tests
6846
+ * Does not create run records in the database (CLI mode)
6847
+ */
6848
+ static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
6849
+ const { outputDir, includeReporter = false, // CLI mode - no custom reporter
6850
+ playwrightArgs = [], onStdout, onStderr, } = options;
6851
+ const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
6852
+ try {
6853
+ // Generate all files
6854
+ console.log(`๐Ÿ“ Generating test files for project ${projectId}...`);
6855
+ await this.generateProjectFiles(projectId, apiToken, apiUrl, projectDir, projectName, includeReporter);
6856
+ // Count total tests
6857
+ const testsTotal = countTotalTests(projectDir);
6858
+ if (testsTotal === 0) {
6859
+ throw new Error(`No test files generated for project ${projectId}`);
6860
+ }
6861
+ // Install dependencies
6862
+ console.log(`๐Ÿ“ฆ Installing dependencies in ${projectDir}...`);
6863
+ try {
6864
+ const { stdout: installStdout, stderr: installStderr } = await execAsync('npm install', { cwd: projectDir, timeout: 300000 } // 5 minute timeout for install
6865
+ );
6866
+ console.log('โœ… Dependencies installed successfully');
6867
+ if (installStdout)
6868
+ console.log(installStdout);
6869
+ if (installStderr)
6870
+ console.warn(installStderr);
6871
+ }
6872
+ catch (installError) {
6873
+ console.error('โŒ Failed to install dependencies:', installError);
6874
+ const errorMsg = `Failed to install dependencies: ${installError.message}`;
6875
+ return {
6876
+ success: false,
6877
+ exitCode: installError.code || 1,
6878
+ stdout: installError.stdout || '',
6879
+ stderr: installError.stderr || installError.message || '',
6880
+ error: errorMsg,
6881
+ };
6882
+ }
6883
+ // Run Playwright tests with streaming output
6884
+ console.log(`๐Ÿš€ Running Playwright tests in ${projectDir}...`);
6885
+ if (playwrightArgs.length > 0) {
6886
+ console.log(`๐Ÿงช Playwright extra args: ${playwrightArgs.join(' ')}`);
6887
+ }
6888
+ return new Promise(async (resolve) => {
6889
+ var _a, _b;
6890
+ let stdout = '';
6891
+ let stderr = '';
6892
+ let hasResolved = false;
6893
+ let stdoutLineBuffer = '';
6894
+ // Use spawn for streaming output
6895
+ const testProcess = spawn('npx', ['playwright', 'test', ...playwrightArgs], {
6896
+ cwd: projectDir,
6897
+ shell: true,
6898
+ stdio: ['ignore', 'pipe', 'pipe'],
6899
+ });
6900
+ // Stream stdout line by line
6901
+ (_a = testProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', async (data) => {
6902
+ var _a;
6903
+ const chunk = data.toString();
6904
+ stdoutLineBuffer += chunk;
6905
+ // Process complete lines
6906
+ const parts = stdoutLineBuffer.split(/\n/);
6907
+ stdoutLineBuffer = (_a = parts.pop()) !== null && _a !== void 0 ? _a : '';
6908
+ for (const rawLine of parts) {
6909
+ const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
6910
+ stdout += rawLine + '\n';
6911
+ // Call stdout callback if provided
6912
+ if (onStdout) {
6913
+ await onStdout(rawLine + '\n');
6914
+ }
6915
+ }
6916
+ });
6917
+ // Stream stderr line by line
6918
+ (_b = testProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', async (data) => {
6919
+ const chunk = data.toString();
6920
+ stderr += chunk;
6921
+ // Call stderr callback if provided
6922
+ if (onStderr) {
6923
+ await onStderr(chunk);
6924
+ }
6925
+ });
6926
+ // Handle process completion
6927
+ testProcess.on('close', async (code) => {
6928
+ if (hasResolved)
6929
+ return;
6930
+ hasResolved = true;
6931
+ // Flush any remaining buffered stdout
6932
+ if (stdoutLineBuffer) {
6933
+ stdout += stdoutLineBuffer;
6934
+ if (onStdout) {
6935
+ await onStdout(stdoutLineBuffer);
6936
+ }
6937
+ }
6938
+ const exitCode = code !== null && code !== void 0 ? code : 1;
6939
+ const success = exitCode === 0;
6940
+ const isTestFailure = exitCode === 1 && stdout.length > 0;
6941
+ console.log(success ? 'โœ… Tests completed successfully' : (isTestFailure ? 'โš ๏ธ Tests completed with failures' : 'โŒ Test execution failed'));
6942
+ resolve({
6943
+ success: success,
6944
+ exitCode: exitCode,
6945
+ stdout: stdout,
6946
+ stderr: stderr,
6947
+ error: success || isTestFailure ? undefined : `Test execution failed with exit code ${exitCode}`,
6948
+ });
6949
+ });
6950
+ // Handle process errors
6951
+ testProcess.on('error', async (error) => {
6952
+ if (hasResolved)
6953
+ return;
6954
+ hasResolved = true;
6955
+ const errorMessage = error.message || String(error);
6956
+ stderr += errorMessage;
6957
+ console.error('โŒ Test execution failed:', error);
6958
+ resolve({
6959
+ success: false,
6960
+ exitCode: 1,
6961
+ stdout: stdout,
6962
+ stderr: stderr,
6963
+ error: `Test execution failed: ${errorMessage}`,
6964
+ });
6965
+ });
6966
+ // Set timeout (1 hour)
6967
+ setTimeout(async () => {
6968
+ if (!hasResolved) {
6969
+ hasResolved = true;
6970
+ testProcess.kill();
6971
+ const errorMessage = 'Test execution timed out after 1 hour';
6972
+ stderr += errorMessage;
6973
+ resolve({
6974
+ success: false,
6975
+ exitCode: 1,
6976
+ stdout: stdout,
6977
+ stderr: stderr,
6978
+ error: errorMessage,
6979
+ });
6980
+ }
6981
+ }, 3600000); // 1 hour timeout
6982
+ });
6983
+ }
6984
+ catch (error) {
6985
+ console.error('โŒ Error running project scenarios:', error);
6986
+ const errorMsg = error.message || String(error);
6987
+ return {
6988
+ success: false,
6989
+ exitCode: 1,
6990
+ stdout: '',
6991
+ stderr: errorMsg,
6992
+ error: errorMsg,
6993
+ };
6994
+ }
6995
+ }
6664
6996
  /**
6665
6997
  * Upload artifacts for a test suite run
6666
6998
  *