@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/bin/probo.js +2 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/cli.js +408 -22
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +352 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +41 -1
- package/dist/index.js +352 -20
- package/dist/index.js.map +1 -1
- package/dist/types/cli.d.ts.map +1 -1
- package/dist/types/codegen-api.d.ts.map +1 -1
- package/dist/types/test-suite-runner.d.ts.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -5761,6 +5761,7 @@ export default class ProboReporter implements Reporter {
|
|
|
5761
5761
|
*/
|
|
5762
5762
|
static async fetchScenarioData(scenarioId, apiToken, apiUrl) {
|
|
5763
5763
|
const normalizedUrl = this.normalizeApiUrl(apiUrl);
|
|
5764
|
+
// Scenario interactions endpoint is under /api/ (special endpoint)
|
|
5764
5765
|
const url = `${normalizedUrl}/api/scenarios/${scenarioId}/interactions`;
|
|
5765
5766
|
const response = await fetch(url, {
|
|
5766
5767
|
method: 'GET',
|
|
@@ -5795,6 +5796,54 @@ export default class ProboReporter implements Reporter {
|
|
|
5795
5796
|
}
|
|
5796
5797
|
return response.json();
|
|
5797
5798
|
}
|
|
5799
|
+
/**
|
|
5800
|
+
* Fetch project data from the backend API
|
|
5801
|
+
*/
|
|
5802
|
+
static async fetchProjectData(projectId, apiToken, apiUrl) {
|
|
5803
|
+
const normalizedUrl = this.normalizeApiUrl(apiUrl);
|
|
5804
|
+
// Projects endpoint is at root level, not under /api/
|
|
5805
|
+
const url = `${normalizedUrl}/projects/${projectId}/`;
|
|
5806
|
+
const response = await fetch(url, {
|
|
5807
|
+
method: 'GET',
|
|
5808
|
+
headers: {
|
|
5809
|
+
'Authorization': `Token ${apiToken}`,
|
|
5810
|
+
'Content-Type': 'application/json',
|
|
5811
|
+
},
|
|
5812
|
+
});
|
|
5813
|
+
if (!response.ok) {
|
|
5814
|
+
const errorText = await this.readResponseErrorText(response);
|
|
5815
|
+
throw new Error(`Failed to fetch project ${projectId}: ${response.status} ${errorText}`);
|
|
5816
|
+
}
|
|
5817
|
+
return response.json();
|
|
5818
|
+
}
|
|
5819
|
+
/**
|
|
5820
|
+
* Fetch all scenarios for a project from the backend API
|
|
5821
|
+
*/
|
|
5822
|
+
static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
|
|
5823
|
+
const normalizedUrl = this.normalizeApiUrl(apiUrl);
|
|
5824
|
+
// Scenarios endpoint is at root level, not under /api/
|
|
5825
|
+
const url = `${normalizedUrl}/scenarios/?project_id=${projectId}`;
|
|
5826
|
+
const response = await fetch(url, {
|
|
5827
|
+
method: 'GET',
|
|
5828
|
+
headers: {
|
|
5829
|
+
'Authorization': `Token ${apiToken}`,
|
|
5830
|
+
'Content-Type': 'application/json',
|
|
5831
|
+
},
|
|
5832
|
+
});
|
|
5833
|
+
if (!response.ok) {
|
|
5834
|
+
const errorText = await this.readResponseErrorText(response);
|
|
5835
|
+
throw new Error(`Failed to fetch scenarios for project ${projectId}: ${response.status} ${errorText}`);
|
|
5836
|
+
}
|
|
5837
|
+
const data = await response.json();
|
|
5838
|
+
// Handle both array and paginated responses
|
|
5839
|
+
if (Array.isArray(data)) {
|
|
5840
|
+
return data;
|
|
5841
|
+
}
|
|
5842
|
+
if (data.results && Array.isArray(data.results)) {
|
|
5843
|
+
return data.results;
|
|
5844
|
+
}
|
|
5845
|
+
return [];
|
|
5846
|
+
}
|
|
5798
5847
|
/**
|
|
5799
5848
|
* Convert backend interaction format to Interaction[] format
|
|
5800
5849
|
*/
|
|
@@ -5945,6 +5994,48 @@ export default class ProboReporter implements Reporter {
|
|
|
5945
5994
|
scenarios: scenarioResults,
|
|
5946
5995
|
};
|
|
5947
5996
|
}
|
|
5997
|
+
/**
|
|
5998
|
+
* Generate code for all scenarios in a project
|
|
5999
|
+
* Returns map of scenario names to generated code
|
|
6000
|
+
*/
|
|
6001
|
+
static async generateCodeForProject(projectId, apiToken, apiUrl, options) {
|
|
6002
|
+
// Validate inputs
|
|
6003
|
+
if (!apiToken) {
|
|
6004
|
+
throw new Error('API token is required');
|
|
6005
|
+
}
|
|
6006
|
+
if (!apiUrl) {
|
|
6007
|
+
throw new Error('API URL is required');
|
|
6008
|
+
}
|
|
6009
|
+
// Fetch project data and scenarios
|
|
6010
|
+
const [projectData, scenarios] = await Promise.all([
|
|
6011
|
+
this.fetchProjectData(projectId, apiToken, apiUrl),
|
|
6012
|
+
this.fetchProjectScenarios(projectId, apiToken, apiUrl),
|
|
6013
|
+
]);
|
|
6014
|
+
if (scenarios.length === 0) {
|
|
6015
|
+
throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
|
|
6016
|
+
}
|
|
6017
|
+
// Generate code for each scenario
|
|
6018
|
+
const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
|
|
6019
|
+
try {
|
|
6020
|
+
const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, options);
|
|
6021
|
+
return {
|
|
6022
|
+
scenarioId: scenario.id,
|
|
6023
|
+
scenarioName: scenario.name,
|
|
6024
|
+
code: code,
|
|
6025
|
+
};
|
|
6026
|
+
}
|
|
6027
|
+
catch (error) {
|
|
6028
|
+
// Log error but continue with other scenarios
|
|
6029
|
+
console.error(`Failed to generate code for scenario ${scenario.id}: ${error.message}`);
|
|
6030
|
+
throw error; // Re-throw to fail fast for now
|
|
6031
|
+
}
|
|
6032
|
+
}));
|
|
6033
|
+
return {
|
|
6034
|
+
projectId: projectData.id,
|
|
6035
|
+
projectName: projectData.name,
|
|
6036
|
+
scenarios: scenarioResults,
|
|
6037
|
+
};
|
|
6038
|
+
}
|
|
5948
6039
|
}
|
|
5949
6040
|
|
|
5950
6041
|
const execAsync = util.promisify(child_process.exec);
|
|
@@ -6067,11 +6158,12 @@ export default class ProboReporter implements Reporter {
|
|
|
6067
6158
|
*/
|
|
6068
6159
|
class TestSuiteRunner {
|
|
6069
6160
|
/**
|
|
6070
|
-
* Lookup
|
|
6161
|
+
* Lookup project ID by name
|
|
6071
6162
|
*/
|
|
6072
|
-
static async
|
|
6163
|
+
static async lookupProjectByName(projectName, apiToken, apiUrl) {
|
|
6073
6164
|
const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
|
|
6074
|
-
|
|
6165
|
+
// Projects endpoint is at root level, not under /api/
|
|
6166
|
+
const url = `${baseUrl}/projects/?name=${encodeURIComponent(projectName)}`;
|
|
6075
6167
|
const response = await fetch$1(url, {
|
|
6076
6168
|
method: 'GET',
|
|
6077
6169
|
headers: {
|
|
@@ -6081,25 +6173,35 @@ export default class ProboReporter implements Reporter {
|
|
|
6081
6173
|
});
|
|
6082
6174
|
if (!response.ok) {
|
|
6083
6175
|
const errorText = await response.text();
|
|
6084
|
-
throw new Error(`Failed to lookup
|
|
6176
|
+
throw new Error(`Failed to lookup project: ${response.status} ${errorText}`);
|
|
6085
6177
|
}
|
|
6086
6178
|
const data = await response.json();
|
|
6087
|
-
// TEMPORARY DEBUG: Output the full response
|
|
6088
|
-
console.log('๐ DEBUG: API Response URL:', url);
|
|
6089
|
-
console.log('๐ DEBUG: API Response Status:', response.status);
|
|
6090
|
-
console.log('๐ DEBUG: API Response Data:', JSON.stringify(data, null, 2));
|
|
6091
|
-
console.log('๐ DEBUG: Is Array?', Array.isArray(data));
|
|
6092
|
-
console.log('๐ DEBUG: Array length:', Array.isArray(data) ? data.length : 'N/A');
|
|
6093
|
-
console.log('๐ DEBUG: Has results?', !!data.results);
|
|
6094
|
-
console.log('๐ DEBUG: Results is Array?', Array.isArray(data.results));
|
|
6095
|
-
console.log('๐ DEBUG: Results length:', Array.isArray(data.results) ? data.results.length : 'N/A');
|
|
6096
6179
|
if (Array.isArray(data) && data.length > 0) {
|
|
6097
|
-
|
|
6180
|
+
return data[0].id;
|
|
6098
6181
|
}
|
|
6099
6182
|
if (data.results && Array.isArray(data.results) && data.results.length > 0) {
|
|
6100
|
-
|
|
6183
|
+
return data.results[0].id;
|
|
6101
6184
|
}
|
|
6102
|
-
|
|
6185
|
+
throw new Error(`Project "${projectName}" not found`);
|
|
6186
|
+
}
|
|
6187
|
+
/**
|
|
6188
|
+
* Lookup test suite ID by name and project
|
|
6189
|
+
*/
|
|
6190
|
+
static async lookupTestSuiteByName(testSuiteName, projectName, apiToken, apiUrl) {
|
|
6191
|
+
const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
|
|
6192
|
+
const url = `${baseUrl}/test-suites/?name=${encodeURIComponent(testSuiteName)}&project=${encodeURIComponent(projectName)}`;
|
|
6193
|
+
const response = await fetch$1(url, {
|
|
6194
|
+
method: 'GET',
|
|
6195
|
+
headers: {
|
|
6196
|
+
'Authorization': `Token ${apiToken}`,
|
|
6197
|
+
'Content-Type': 'application/json',
|
|
6198
|
+
},
|
|
6199
|
+
});
|
|
6200
|
+
if (!response.ok) {
|
|
6201
|
+
const errorText = await response.text();
|
|
6202
|
+
throw new Error(`Failed to lookup test suite: ${response.status} ${errorText}`);
|
|
6203
|
+
}
|
|
6204
|
+
const data = await response.json();
|
|
6103
6205
|
if (Array.isArray(data) && data.length > 0) {
|
|
6104
6206
|
return data[0].id;
|
|
6105
6207
|
}
|
|
@@ -6217,7 +6319,10 @@ export default class ProboReporter implements Reporter {
|
|
|
6217
6319
|
}
|
|
6218
6320
|
}
|
|
6219
6321
|
}
|
|
6220
|
-
|
|
6322
|
+
// Type guard: check for projectId to distinguish ProjectCodeGenResult from TestSuiteCodeGenResult
|
|
6323
|
+
const sanitizedName = 'projectId' in codeGenResult && 'projectName' in codeGenResult
|
|
6324
|
+
? slugify(codeGenResult.projectName)
|
|
6325
|
+
: slugify(codeGenResult.testSuiteName);
|
|
6221
6326
|
const packageJsonContent = generatePackageJson({
|
|
6222
6327
|
name: sanitizedName,
|
|
6223
6328
|
hasSecrets: hasSecrets,
|
|
@@ -6250,12 +6355,12 @@ export default class ProboReporter implements Reporter {
|
|
|
6250
6355
|
* Generates files, installs dependencies, and executes tests
|
|
6251
6356
|
*/
|
|
6252
6357
|
static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
|
|
6253
|
-
const { outputDir, includeReporter = true, playwrightArgs = [], onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
|
|
6358
|
+
const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
|
|
6254
6359
|
const testSuiteDir = outputDir || getDefaultTestSuiteDir(testSuiteId, testSuiteName);
|
|
6255
6360
|
let currentRunId = runId;
|
|
6256
6361
|
try {
|
|
6257
|
-
// Create run record if not provided
|
|
6258
|
-
if (!currentRunId) {
|
|
6362
|
+
// Create run record if not provided and not skipping run creation
|
|
6363
|
+
if (!currentRunId && !skipRunCreation) {
|
|
6259
6364
|
try {
|
|
6260
6365
|
const createResponse = await fetch$1(`${apiUrl}/test-suites/${testSuiteId}/runs/`, {
|
|
6261
6366
|
method: 'POST',
|
|
@@ -6680,6 +6785,233 @@ export default class ProboReporter implements Reporter {
|
|
|
6680
6785
|
};
|
|
6681
6786
|
}
|
|
6682
6787
|
}
|
|
6788
|
+
/**
|
|
6789
|
+
* Generate all files for a project (all scenarios)
|
|
6790
|
+
*/
|
|
6791
|
+
static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
|
|
6792
|
+
const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
|
|
6793
|
+
// Generate code for all scenarios
|
|
6794
|
+
const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
|
|
6795
|
+
if (codeGenResult.scenarios.length === 0) {
|
|
6796
|
+
throw new Error(`No scenarios found in project "${codeGenResult.projectName}" (ID: ${projectId}). Cannot generate test files.`);
|
|
6797
|
+
}
|
|
6798
|
+
// Delete everything in the project directory except node_modules
|
|
6799
|
+
if (fs__namespace.existsSync(projectDir)) {
|
|
6800
|
+
try {
|
|
6801
|
+
const nodeModulesPath = path__namespace.join(projectDir, 'node_modules');
|
|
6802
|
+
const hasNodeModules = fs__namespace.existsSync(nodeModulesPath);
|
|
6803
|
+
// Temporarily move node_modules out of the way if it exists
|
|
6804
|
+
let tempNodeModulesPath = null;
|
|
6805
|
+
if (hasNodeModules) {
|
|
6806
|
+
tempNodeModulesPath = path__namespace.join(projectDir, '..', `node_modules.temp.${projectId}`);
|
|
6807
|
+
// Remove temp directory if it exists from a previous failed run
|
|
6808
|
+
if (fs__namespace.existsSync(tempNodeModulesPath)) {
|
|
6809
|
+
fs__namespace.rmSync(tempNodeModulesPath, { recursive: true, force: true });
|
|
6810
|
+
}
|
|
6811
|
+
fs__namespace.renameSync(nodeModulesPath, tempNodeModulesPath);
|
|
6812
|
+
console.log(`๐ฆ Preserved node_modules temporarily`);
|
|
6813
|
+
}
|
|
6814
|
+
// Delete everything in the directory
|
|
6815
|
+
const entries = fs__namespace.readdirSync(projectDir, { withFileTypes: true });
|
|
6816
|
+
for (const entry of entries) {
|
|
6817
|
+
const entryPath = path__namespace.join(projectDir, entry.name);
|
|
6818
|
+
try {
|
|
6819
|
+
if (entry.isDirectory()) {
|
|
6820
|
+
fs__namespace.rmSync(entryPath, { recursive: true, force: true });
|
|
6821
|
+
}
|
|
6822
|
+
else {
|
|
6823
|
+
fs__namespace.unlinkSync(entryPath);
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
catch (error) {
|
|
6827
|
+
console.warn(`โ ๏ธ Failed to delete ${entry.name}:`, error);
|
|
6828
|
+
}
|
|
6829
|
+
}
|
|
6830
|
+
console.log(`๐๏ธ Cleaned project directory (preserved node_modules)`);
|
|
6831
|
+
// Move node_modules back
|
|
6832
|
+
if (hasNodeModules && tempNodeModulesPath) {
|
|
6833
|
+
fs__namespace.renameSync(tempNodeModulesPath, nodeModulesPath);
|
|
6834
|
+
console.log(`๐ฆ Restored node_modules`);
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6837
|
+
catch (error) {
|
|
6838
|
+
console.warn(`โ ๏ธ Failed to clean project directory ${projectDir}:`, error);
|
|
6839
|
+
// Continue anyway - we'll overwrite files as needed
|
|
6840
|
+
}
|
|
6841
|
+
}
|
|
6842
|
+
// Ensure directories exist (will recreate after deletion)
|
|
6843
|
+
ensureDirectoryExists(projectDir);
|
|
6844
|
+
const testsDir = path__namespace.join(projectDir, 'tests');
|
|
6845
|
+
ensureDirectoryExists(testsDir);
|
|
6846
|
+
// Save each scenario's code to a .spec.ts file
|
|
6847
|
+
for (const scenario of codeGenResult.scenarios) {
|
|
6848
|
+
const fileName = `${slugify(scenario.scenarioName)}.spec.ts`;
|
|
6849
|
+
const filePath = path__namespace.join(testsDir, fileName);
|
|
6850
|
+
fs__namespace.writeFileSync(filePath, scenario.code, 'utf-8');
|
|
6851
|
+
console.log(`โ
Generated test file: ${filePath}`);
|
|
6852
|
+
}
|
|
6853
|
+
// Generate package.json
|
|
6854
|
+
await this.generatePackageJson(projectDir, codeGenResult);
|
|
6855
|
+
// Generate playwright.config.ts (no runId for project runs)
|
|
6856
|
+
await this.generatePlaywrightConfig(projectDir, includeReporter, undefined);
|
|
6857
|
+
// Generate custom reporter file for live progress streaming (only if requested)
|
|
6858
|
+
if (includeReporter) {
|
|
6859
|
+
await this.generateProboReporter(projectDir);
|
|
6860
|
+
}
|
|
6861
|
+
}
|
|
6862
|
+
/**
|
|
6863
|
+
* Run all scenarios in a project
|
|
6864
|
+
* Generates files, installs dependencies, and executes tests
|
|
6865
|
+
* Does not create run records in the database (CLI mode)
|
|
6866
|
+
*/
|
|
6867
|
+
static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
|
|
6868
|
+
const { outputDir, includeReporter = false, // CLI mode - no custom reporter
|
|
6869
|
+
playwrightArgs = [], onStdout, onStderr, } = options;
|
|
6870
|
+
const projectDir = outputDir || getDefaultTestSuiteDir(projectId, projectName || `project-${projectId}`);
|
|
6871
|
+
try {
|
|
6872
|
+
// Generate all files
|
|
6873
|
+
console.log(`๐ Generating test files for project ${projectId}...`);
|
|
6874
|
+
await this.generateProjectFiles(projectId, apiToken, apiUrl, projectDir, projectName, includeReporter);
|
|
6875
|
+
// Count total tests
|
|
6876
|
+
const testsTotal = countTotalTests(projectDir);
|
|
6877
|
+
if (testsTotal === 0) {
|
|
6878
|
+
throw new Error(`No test files generated for project ${projectId}`);
|
|
6879
|
+
}
|
|
6880
|
+
// Install dependencies
|
|
6881
|
+
console.log(`๐ฆ Installing dependencies in ${projectDir}...`);
|
|
6882
|
+
try {
|
|
6883
|
+
const { stdout: installStdout, stderr: installStderr } = await execAsync('npm install', { cwd: projectDir, timeout: 300000 } // 5 minute timeout for install
|
|
6884
|
+
);
|
|
6885
|
+
console.log('โ
Dependencies installed successfully');
|
|
6886
|
+
if (installStdout)
|
|
6887
|
+
console.log(installStdout);
|
|
6888
|
+
if (installStderr)
|
|
6889
|
+
console.warn(installStderr);
|
|
6890
|
+
}
|
|
6891
|
+
catch (installError) {
|
|
6892
|
+
console.error('โ Failed to install dependencies:', installError);
|
|
6893
|
+
const errorMsg = `Failed to install dependencies: ${installError.message}`;
|
|
6894
|
+
return {
|
|
6895
|
+
success: false,
|
|
6896
|
+
exitCode: installError.code || 1,
|
|
6897
|
+
stdout: installError.stdout || '',
|
|
6898
|
+
stderr: installError.stderr || installError.message || '',
|
|
6899
|
+
error: errorMsg,
|
|
6900
|
+
};
|
|
6901
|
+
}
|
|
6902
|
+
// Run Playwright tests with streaming output
|
|
6903
|
+
console.log(`๐ Running Playwright tests in ${projectDir}...`);
|
|
6904
|
+
if (playwrightArgs.length > 0) {
|
|
6905
|
+
console.log(`๐งช Playwright extra args: ${playwrightArgs.join(' ')}`);
|
|
6906
|
+
}
|
|
6907
|
+
return new Promise(async (resolve) => {
|
|
6908
|
+
var _a, _b;
|
|
6909
|
+
let stdout = '';
|
|
6910
|
+
let stderr = '';
|
|
6911
|
+
let hasResolved = false;
|
|
6912
|
+
let stdoutLineBuffer = '';
|
|
6913
|
+
// Use spawn for streaming output
|
|
6914
|
+
const testProcess = child_process.spawn('npx', ['playwright', 'test', ...playwrightArgs], {
|
|
6915
|
+
cwd: projectDir,
|
|
6916
|
+
shell: true,
|
|
6917
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
6918
|
+
});
|
|
6919
|
+
// Stream stdout line by line
|
|
6920
|
+
(_a = testProcess.stdout) === null || _a === void 0 ? void 0 : _a.on('data', async (data) => {
|
|
6921
|
+
var _a;
|
|
6922
|
+
const chunk = data.toString();
|
|
6923
|
+
stdoutLineBuffer += chunk;
|
|
6924
|
+
// Process complete lines
|
|
6925
|
+
const parts = stdoutLineBuffer.split(/\n/);
|
|
6926
|
+
stdoutLineBuffer = (_a = parts.pop()) !== null && _a !== void 0 ? _a : '';
|
|
6927
|
+
for (const rawLine of parts) {
|
|
6928
|
+
const line = rawLine.endsWith('\r') ? rawLine.slice(0, -1) : rawLine;
|
|
6929
|
+
stdout += rawLine + '\n';
|
|
6930
|
+
// Call stdout callback if provided
|
|
6931
|
+
if (onStdout) {
|
|
6932
|
+
await onStdout(rawLine + '\n');
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
});
|
|
6936
|
+
// Stream stderr line by line
|
|
6937
|
+
(_b = testProcess.stderr) === null || _b === void 0 ? void 0 : _b.on('data', async (data) => {
|
|
6938
|
+
const chunk = data.toString();
|
|
6939
|
+
stderr += chunk;
|
|
6940
|
+
// Call stderr callback if provided
|
|
6941
|
+
if (onStderr) {
|
|
6942
|
+
await onStderr(chunk);
|
|
6943
|
+
}
|
|
6944
|
+
});
|
|
6945
|
+
// Handle process completion
|
|
6946
|
+
testProcess.on('close', async (code) => {
|
|
6947
|
+
if (hasResolved)
|
|
6948
|
+
return;
|
|
6949
|
+
hasResolved = true;
|
|
6950
|
+
// Flush any remaining buffered stdout
|
|
6951
|
+
if (stdoutLineBuffer) {
|
|
6952
|
+
stdout += stdoutLineBuffer;
|
|
6953
|
+
if (onStdout) {
|
|
6954
|
+
await onStdout(stdoutLineBuffer);
|
|
6955
|
+
}
|
|
6956
|
+
}
|
|
6957
|
+
const exitCode = code !== null && code !== void 0 ? code : 1;
|
|
6958
|
+
const success = exitCode === 0;
|
|
6959
|
+
const isTestFailure = exitCode === 1 && stdout.length > 0;
|
|
6960
|
+
console.log(success ? 'โ
Tests completed successfully' : (isTestFailure ? 'โ ๏ธ Tests completed with failures' : 'โ Test execution failed'));
|
|
6961
|
+
resolve({
|
|
6962
|
+
success: success,
|
|
6963
|
+
exitCode: exitCode,
|
|
6964
|
+
stdout: stdout,
|
|
6965
|
+
stderr: stderr,
|
|
6966
|
+
error: success || isTestFailure ? undefined : `Test execution failed with exit code ${exitCode}`,
|
|
6967
|
+
});
|
|
6968
|
+
});
|
|
6969
|
+
// Handle process errors
|
|
6970
|
+
testProcess.on('error', async (error) => {
|
|
6971
|
+
if (hasResolved)
|
|
6972
|
+
return;
|
|
6973
|
+
hasResolved = true;
|
|
6974
|
+
const errorMessage = error.message || String(error);
|
|
6975
|
+
stderr += errorMessage;
|
|
6976
|
+
console.error('โ Test execution failed:', error);
|
|
6977
|
+
resolve({
|
|
6978
|
+
success: false,
|
|
6979
|
+
exitCode: 1,
|
|
6980
|
+
stdout: stdout,
|
|
6981
|
+
stderr: stderr,
|
|
6982
|
+
error: `Test execution failed: ${errorMessage}`,
|
|
6983
|
+
});
|
|
6984
|
+
});
|
|
6985
|
+
// Set timeout (1 hour)
|
|
6986
|
+
setTimeout(async () => {
|
|
6987
|
+
if (!hasResolved) {
|
|
6988
|
+
hasResolved = true;
|
|
6989
|
+
testProcess.kill();
|
|
6990
|
+
const errorMessage = 'Test execution timed out after 1 hour';
|
|
6991
|
+
stderr += errorMessage;
|
|
6992
|
+
resolve({
|
|
6993
|
+
success: false,
|
|
6994
|
+
exitCode: 1,
|
|
6995
|
+
stdout: stdout,
|
|
6996
|
+
stderr: stderr,
|
|
6997
|
+
error: errorMessage,
|
|
6998
|
+
});
|
|
6999
|
+
}
|
|
7000
|
+
}, 3600000); // 1 hour timeout
|
|
7001
|
+
});
|
|
7002
|
+
}
|
|
7003
|
+
catch (error) {
|
|
7004
|
+
console.error('โ Error running project scenarios:', error);
|
|
7005
|
+
const errorMsg = error.message || String(error);
|
|
7006
|
+
return {
|
|
7007
|
+
success: false,
|
|
7008
|
+
exitCode: 1,
|
|
7009
|
+
stdout: '',
|
|
7010
|
+
stderr: errorMsg,
|
|
7011
|
+
error: errorMsg,
|
|
7012
|
+
};
|
|
7013
|
+
}
|
|
7014
|
+
}
|
|
6683
7015
|
/**
|
|
6684
7016
|
* Upload artifacts for a test suite run
|
|
6685
7017
|
*
|