@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/.tsbuildinfo +1 -1
- package/dist/cli.js +123 -60
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +135 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +26 -3
- package/dist/index.js +135 -62
- package/dist/index.js.map +1 -1
- package/dist/types/codegen-api.d.ts.map +1 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/replay-utils.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
|
@@ -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": "
|
|
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
|
-
//
|
|
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
|
|
6388
|
+
* Gets the project directory path (unified for test suite runs and export).
|
|
6389
|
+
* Path: ~/.probium/projects/{projectId}-{projectName-slugified}/
|
|
6365
6390
|
*/
|
|
6366
|
-
function
|
|
6367
|
-
const baseDir = path__namespace.join(os__namespace.homedir(), '.probium', '
|
|
6368
|
-
|
|
6369
|
-
|
|
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
|
-
*
|
|
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
|
|
6571
|
-
|
|
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
|
-
|
|
6577
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
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
|
|
6618
|
-
// Continue anyway - we'll overwrite files as needed
|
|
6669
|
+
console.warn(`โ ๏ธ Failed to clean project directory ${projectDir}:`, error);
|
|
6619
6670
|
}
|
|
6620
6671
|
}
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
const testsDir = path__namespace.join(testSuiteDir, 'tests');
|
|
6672
|
+
ensureDirectoryExists(projectDir);
|
|
6673
|
+
const testsDir = path__namespace.join(projectDir, 'tests');
|
|
6624
6674
|
ensureDirectoryExists(testsDir);
|
|
6625
|
-
|
|
6675
|
+
const seenSlugs = new Map();
|
|
6626
6676
|
for (const scenario of codeGenResult.scenarios) {
|
|
6627
|
-
const
|
|
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
|
-
|
|
6633
|
-
await this.
|
|
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(
|
|
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
|
|
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,
|
|
6792
|
+
await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName, includeReporter, currentRunId);
|
|
6741
6793
|
// Count total tests
|
|
6742
|
-
const testsTotal = countTotalTests(
|
|
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 ${
|
|
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:
|
|
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:
|
|
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 ${
|
|
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:
|
|
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(
|
|
7016
|
-
parsedStats = parseStatisticsFromMetadata(
|
|
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
|
-
|
|
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
|
-
|
|
7325
|
+
const seenSlugs = new Map();
|
|
7273
7326
|
for (const scenario of codeGenResult.scenarios) {
|
|
7274
|
-
const
|
|
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 ||
|
|
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
|
|