@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.d.ts
CHANGED
|
@@ -432,6 +432,8 @@ interface CodeGenOptions {
|
|
|
432
432
|
width: number;
|
|
433
433
|
height: number;
|
|
434
434
|
};
|
|
435
|
+
/** Test suite names for Playwright tags (from api/scenarios/light/) */
|
|
436
|
+
testSuiteTags?: string[];
|
|
435
437
|
}
|
|
436
438
|
/**
|
|
437
439
|
* Result of generating code for a test suite
|
|
@@ -455,6 +457,7 @@ interface ProjectCodeGenResult {
|
|
|
455
457
|
scenarioId: number;
|
|
456
458
|
scenarioName: string;
|
|
457
459
|
code: string;
|
|
460
|
+
testSuiteTags?: string[];
|
|
458
461
|
}>;
|
|
459
462
|
}
|
|
460
463
|
/**
|
|
@@ -479,7 +482,7 @@ declare class ProboCodeGenerator {
|
|
|
479
482
|
*/
|
|
480
483
|
private static fetchProjectData;
|
|
481
484
|
/**
|
|
482
|
-
* Fetch all scenarios for a project from the backend API
|
|
485
|
+
* Fetch all scenarios for a project from the backend API (light endpoint includes test_suite_tags)
|
|
483
486
|
*/
|
|
484
487
|
private static fetchProjectScenarios;
|
|
485
488
|
/**
|
|
@@ -558,6 +561,11 @@ interface TestSuiteRunOptions {
|
|
|
558
561
|
onStderr?: (chunk: string) => void | Promise<void>;
|
|
559
562
|
onReporterEvent?: (event: any) => void | Promise<void>;
|
|
560
563
|
}
|
|
564
|
+
/**
|
|
565
|
+
* Gets the project directory path (unified for test suite runs and export).
|
|
566
|
+
* Path: ~/.probium/projects/{projectId}-{projectName-slugified}/
|
|
567
|
+
*/
|
|
568
|
+
declare function getProjectDir(projectId: number, projectName?: string): string;
|
|
561
569
|
/**
|
|
562
570
|
* TestSuiteRunner - Handles test suite file generation and execution
|
|
563
571
|
*/
|
|
@@ -571,9 +579,18 @@ declare class TestSuiteRunner {
|
|
|
571
579
|
*/
|
|
572
580
|
static lookupTestSuiteByName(testSuiteName: string, projectName: string, apiToken: string, apiUrl: string): Promise<number>;
|
|
573
581
|
/**
|
|
574
|
-
*
|
|
582
|
+
* Fetch test suite details (project id, project name, test suite name)
|
|
583
|
+
*/
|
|
584
|
+
private static fetchTestSuiteDetails;
|
|
585
|
+
/**
|
|
586
|
+
* Generate all files for a test suite (writes to project-level directory)
|
|
575
587
|
*/
|
|
576
588
|
static generateTestSuiteFiles(testSuiteId: number, apiToken: string, apiUrl: string, outputDir?: string, testSuiteName?: string, includeReporter?: boolean, runId?: number): Promise<void>;
|
|
589
|
+
/**
|
|
590
|
+
* Generate files only for the scenarios that participate in the test suite.
|
|
591
|
+
* Used by the recorder app test-suite runner so only suite specs are written; then we run npx playwright test normally.
|
|
592
|
+
*/
|
|
593
|
+
static generateTestSuiteOnlyFiles(testSuiteId: number, apiToken: string, apiUrl: string, outputDir: string, testSuiteName?: string, includeReporter?: boolean, runId?: number): Promise<void>;
|
|
577
594
|
/**
|
|
578
595
|
* Generate package.json file
|
|
579
596
|
*/
|
|
@@ -595,6 +612,12 @@ declare class TestSuiteRunner {
|
|
|
595
612
|
* Generate all files for a project (all scenarios)
|
|
596
613
|
*/
|
|
597
614
|
static generateProjectFiles(projectId: number, apiToken: string, apiUrl: string, outputDir?: string, projectName?: string, includeReporter?: boolean): Promise<void>;
|
|
615
|
+
/**
|
|
616
|
+
* Export project code to a directory.
|
|
617
|
+
* If outputDir is provided, creates a subfolder {outputDir}/{projectId}-{projectNameSlug}/ and writes there.
|
|
618
|
+
* Otherwise writes to the default project dir (~/.probium/projects/...).
|
|
619
|
+
*/
|
|
620
|
+
static exportProjectCode(projectId: number, apiToken: string, apiUrl: string, outputDir?: string, projectName?: string, includeReporter?: boolean): Promise<string>;
|
|
598
621
|
/**
|
|
599
622
|
* Run all scenarios in a project
|
|
600
623
|
* Generates files, installs dependencies, and executes tests
|
|
@@ -674,5 +697,5 @@ declare class Probo {
|
|
|
674
697
|
askAIHelper(page: Page, question: string, options: AskAIOptions, assertAnswer?: string): Promise<ServerResponse>;
|
|
675
698
|
}
|
|
676
699
|
|
|
677
|
-
export { Highlighter, NavTracker, OTP, Probo, ProboCodeGenerator, ProboPlaywright, TestSuiteRunner, buildInterpolationContext, findClosestVisibleElement, interpolateWithParams };
|
|
700
|
+
export { Highlighter, NavTracker, OTP, Probo, ProboCodeGenerator, ProboPlaywright, TestSuiteRunner, buildInterpolationContext, findClosestVisibleElement, getProjectDir, interpolateWithParams };
|
|
678
701
|
export type { AskAIOptions, CodeGenOptions, ElementTagType, MailinatorMessage, RunStepOptions, TestStatistics, TestSuiteCodeGenResult, TestSuiteRunOptions, TestSuiteRunResult };
|
package/dist/index.js
CHANGED
|
@@ -1296,6 +1296,16 @@ const DEFAULT_RECORDER_SETTINGS = {
|
|
|
1296
1296
|
};
|
|
1297
1297
|
|
|
1298
1298
|
// --- Code generation utilities for Probo Labs Playwright scripts ---
|
|
1299
|
+
/**
|
|
1300
|
+
* Converts a test suite name to a Playwright tag (slugify + @ prefix).
|
|
1301
|
+
* Playwright tags must start with @.
|
|
1302
|
+
*/
|
|
1303
|
+
function slugifyTag(tag) {
|
|
1304
|
+
if (!tag || typeof tag !== 'string')
|
|
1305
|
+
return '@untagged';
|
|
1306
|
+
const slug = slugify(tag);
|
|
1307
|
+
return slug ? `@${slug}` : '@untagged';
|
|
1308
|
+
}
|
|
1299
1309
|
/**
|
|
1300
1310
|
* Extracts environment variable names from parameter table rows
|
|
1301
1311
|
* by parsing ${process.env.VAR_NAME} patterns from parameter values
|
|
@@ -1604,6 +1614,13 @@ function scriptTemplate(options, settings, viewPort) {
|
|
|
1604
1614
|
}).join(',\n ')}
|
|
1605
1615
|
];
|
|
1606
1616
|
` : '';
|
|
1617
|
+
// Playwright tags for test suite filtering (e.g. npx playwright test --grep @sanity)
|
|
1618
|
+
const playwrightTags = options.testSuiteTags && options.testSuiteTags.length > 0
|
|
1619
|
+
? options.testSuiteTags.map(slugifyTag).filter((t) => t !== '@untagged')
|
|
1620
|
+
: [];
|
|
1621
|
+
const describeTagOption = playwrightTags.length > 0
|
|
1622
|
+
? `{ tag: [${playwrightTags.map((t) => `'${t}'`).join(', ')}] }, `
|
|
1623
|
+
: '';
|
|
1607
1624
|
const proboConstructor = hasAiInteractions ? `
|
|
1608
1625
|
const probo = new Probo({
|
|
1609
1626
|
scenarioName: '${options.scenarioName}',
|
|
@@ -1680,7 +1697,7 @@ ${hasSecrets ? `import { config } from 'dotenv';
|
|
|
1680
1697
|
}
|
|
1681
1698
|
});
|
|
1682
1699
|
|
|
1683
|
-
test.describe('${options.scenarioName}', () => {
|
|
1700
|
+
test.describe('${options.scenarioName}', ${describeTagOption}() => {
|
|
1684
1701
|
test.beforeEach(async ({ page }) => {
|
|
1685
1702
|
// set the ProboPlaywright instance to the current page
|
|
1686
1703
|
ppw.setPage(page);
|
|
@@ -1756,7 +1773,7 @@ const DEFAULT_PLAYWRIGHT_TEST_VERSION = '^1.57.0';
|
|
|
1756
1773
|
* Generate package.json content for a Playwright test project
|
|
1757
1774
|
*/
|
|
1758
1775
|
function generatePackageJson(options) {
|
|
1759
|
-
const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION } = options;
|
|
1776
|
+
const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION, probolabsPlaywrightVersion = 'latest' } = options;
|
|
1760
1777
|
return `{
|
|
1761
1778
|
"name": "${name}",
|
|
1762
1779
|
"version": "1.0.0",
|
|
@@ -1767,7 +1784,7 @@ function generatePackageJson(options) {
|
|
|
1767
1784
|
},
|
|
1768
1785
|
"dependencies": {
|
|
1769
1786
|
"@playwright/test": "${playwrightTestVersion}",
|
|
1770
|
-
"@probolabs/playwright": "
|
|
1787
|
+
"@probolabs/playwright": "${probolabsPlaywrightVersion}"${hasSecrets ? ',\n "dotenv": "latest"' : ''}
|
|
1771
1788
|
}
|
|
1772
1789
|
}`;
|
|
1773
1790
|
}
|
|
@@ -5106,16 +5123,21 @@ class ProboPlaywright {
|
|
|
5106
5123
|
this.nextTabIndex = 1;
|
|
5107
5124
|
this.params = {};
|
|
5108
5125
|
this.isCanceled = null;
|
|
5126
|
+
proboLogger.setLogLevel(debugLevel);
|
|
5109
5127
|
this.enableSmartSelectors = enableSmartSelectors;
|
|
5110
5128
|
this.timeoutConfig = mergeTimeoutConfigWithEnv({
|
|
5111
5129
|
...DEFAULT_PLAYWRIGHT_TIMEOUT_CONFIG,
|
|
5112
5130
|
...timeoutConfig
|
|
5113
5131
|
});
|
|
5132
|
+
proboLogger.debug(`ProboPlaywright timeout config: playwrightActionTimeout=${this.timeoutConfig.playwrightActionTimeout}ms, ` +
|
|
5133
|
+
`playwrightNavigationTimeout=${this.timeoutConfig.playwrightNavigationTimeout}ms, ` +
|
|
5134
|
+
`playwrightLocatorTimeout=${this.timeoutConfig.playwrightLocatorTimeout}ms, ` +
|
|
5135
|
+
`waitForStabilityQuietTimeout=${this.timeoutConfig.waitForStabilityQuietTimeout}ms, ` +
|
|
5136
|
+
`waitForStabilityGlobalTimeout=${this.timeoutConfig.waitForStabilityGlobalTimeout}ms`);
|
|
5114
5137
|
this.isCanceled = isCanceled || null;
|
|
5115
5138
|
this.highlighter = new Highlighter(enableSmartSelectors, false, debugLevel);
|
|
5116
5139
|
this.context = context !== null && context !== void 0 ? context : null;
|
|
5117
5140
|
this.setPage(page);
|
|
5118
|
-
proboLogger.setLogLevel(debugLevel);
|
|
5119
5141
|
}
|
|
5120
5142
|
/**
|
|
5121
5143
|
* Sets the Playwright page instance for this ProboPlaywright instance.
|
|
@@ -6106,12 +6128,12 @@ class ProboCodeGenerator {
|
|
|
6106
6128
|
return response.json();
|
|
6107
6129
|
}
|
|
6108
6130
|
/**
|
|
6109
|
-
* Fetch all scenarios for a project from the backend API
|
|
6131
|
+
* Fetch all scenarios for a project from the backend API (light endpoint includes test_suite_tags)
|
|
6110
6132
|
*/
|
|
6111
6133
|
static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
|
|
6112
6134
|
const normalizedUrl = this.normalizeApiUrl(apiUrl);
|
|
6113
|
-
//
|
|
6114
|
-
const url = `${normalizedUrl}/scenarios/?project_id=${projectId}`;
|
|
6135
|
+
// Use api/scenarios/light/ to get test_suite_tags for Playwright tagging
|
|
6136
|
+
const url = `${normalizedUrl}/api/scenarios/light/?project_id=${projectId}`;
|
|
6115
6137
|
const response = await fetch(url, {
|
|
6116
6138
|
method: 'GET',
|
|
6117
6139
|
headers: {
|
|
@@ -6249,6 +6271,7 @@ class ProboCodeGenerator {
|
|
|
6249
6271
|
scenarioName: scenarioData.name,
|
|
6250
6272
|
interactions: interactions,
|
|
6251
6273
|
rows: rows,
|
|
6274
|
+
...((options === null || options === void 0 ? void 0 : options.testSuiteTags) && options.testSuiteTags.length > 0 ? { testSuiteTags: options.testSuiteTags } : {}),
|
|
6252
6275
|
};
|
|
6253
6276
|
return generateCode(codeOptions, settings, viewPort);
|
|
6254
6277
|
}
|
|
@@ -6308,14 +6331,15 @@ class ProboCodeGenerator {
|
|
|
6308
6331
|
if (scenarios.length === 0) {
|
|
6309
6332
|
throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
|
|
6310
6333
|
}
|
|
6311
|
-
// Generate code for each scenario
|
|
6334
|
+
// Generate code for each scenario (with test_suite_tags for Playwright tagging)
|
|
6312
6335
|
const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
|
|
6313
6336
|
try {
|
|
6314
|
-
const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, options);
|
|
6337
|
+
const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, { ...options, testSuiteTags: scenario.test_suite_tags });
|
|
6315
6338
|
return {
|
|
6316
6339
|
scenarioId: scenario.id,
|
|
6317
6340
|
scenarioName: scenario.name,
|
|
6318
6341
|
code: code,
|
|
6342
|
+
testSuiteTags: scenario.test_suite_tags,
|
|
6319
6343
|
};
|
|
6320
6344
|
}
|
|
6321
6345
|
catch (error) {
|
|
@@ -6342,15 +6366,13 @@ function ensureDirectoryExists(dir) {
|
|
|
6342
6366
|
}
|
|
6343
6367
|
}
|
|
6344
6368
|
/**
|
|
6345
|
-
* Gets the
|
|
6369
|
+
* Gets the project directory path (unified for test suite runs and export).
|
|
6370
|
+
* Path: ~/.probium/projects/{projectId}-{projectName-slugified}/
|
|
6346
6371
|
*/
|
|
6347
|
-
function
|
|
6348
|
-
const baseDir = path.join(os.homedir(), '.probium', '
|
|
6349
|
-
|
|
6350
|
-
|
|
6351
|
-
return path.join(baseDir, `${testSuiteId}-${sanitizedName}`);
|
|
6352
|
-
}
|
|
6353
|
-
return path.join(baseDir, testSuiteId.toString());
|
|
6372
|
+
function getProjectDir(projectId, projectName) {
|
|
6373
|
+
const baseDir = path.join(os.homedir(), '.probium', 'projects');
|
|
6374
|
+
const sanitizedName = projectName ? slugify(projectName) : `project-${projectId}`;
|
|
6375
|
+
return path.join(baseDir, `${projectId}-${sanitizedName}`);
|
|
6354
6376
|
}
|
|
6355
6377
|
/**
|
|
6356
6378
|
* Count total tests by scanning spec files
|
|
@@ -6545,36 +6567,67 @@ class TestSuiteRunner {
|
|
|
6545
6567
|
throw new Error(`Test suite "${testSuiteName}" not found in project "${projectName}"`);
|
|
6546
6568
|
}
|
|
6547
6569
|
/**
|
|
6548
|
-
*
|
|
6570
|
+
* Fetch test suite details (project id, project name, test suite name)
|
|
6571
|
+
*/
|
|
6572
|
+
static async fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl) {
|
|
6573
|
+
var _a, _b, _c;
|
|
6574
|
+
const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
|
|
6575
|
+
const response = await fetch$1(`${baseUrl}/test-suites/${testSuiteId}/`, {
|
|
6576
|
+
method: 'GET',
|
|
6577
|
+
headers: {
|
|
6578
|
+
'Authorization': `Token ${apiToken}`,
|
|
6579
|
+
'Content-Type': 'application/json',
|
|
6580
|
+
},
|
|
6581
|
+
});
|
|
6582
|
+
if (!response.ok) {
|
|
6583
|
+
const errorText = await response.text();
|
|
6584
|
+
throw new Error(`Failed to fetch test suite ${testSuiteId}: ${response.status} ${errorText}`);
|
|
6585
|
+
}
|
|
6586
|
+
const data = await response.json();
|
|
6587
|
+
const projectId = (_a = data.project) !== null && _a !== void 0 ? _a : data.project_id;
|
|
6588
|
+
const projectName = (_b = data.project_name) !== null && _b !== void 0 ? _b : `project-${projectId}`;
|
|
6589
|
+
const testSuiteName = (_c = data.name) !== null && _c !== void 0 ? _c : `suite-${testSuiteId}`;
|
|
6590
|
+
if (!projectId) {
|
|
6591
|
+
throw new Error(`Test suite ${testSuiteId} has no project`);
|
|
6592
|
+
}
|
|
6593
|
+
return { projectId, projectName, testSuiteName };
|
|
6594
|
+
}
|
|
6595
|
+
/**
|
|
6596
|
+
* Generate all files for a test suite (writes to project-level directory)
|
|
6549
6597
|
*/
|
|
6550
6598
|
static async generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
|
|
6551
|
-
const
|
|
6552
|
-
|
|
6599
|
+
const { projectId, projectName, testSuiteName: resolvedSuiteName } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
|
|
6600
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName);
|
|
6601
|
+
await this.generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName !== null && testSuiteName !== void 0 ? testSuiteName : resolvedSuiteName, includeReporter, runId);
|
|
6602
|
+
}
|
|
6603
|
+
/**
|
|
6604
|
+
* Generate files only for the scenarios that participate in the test suite.
|
|
6605
|
+
* Used by the recorder app test-suite runner so only suite specs are written; then we run npx playwright test normally.
|
|
6606
|
+
*/
|
|
6607
|
+
static async generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
|
|
6608
|
+
var _a;
|
|
6609
|
+
const { projectId } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
|
|
6553
6610
|
const codeGenResult = await ProboCodeGenerator.generateCodeForTestSuite(testSuiteId, apiToken, apiUrl);
|
|
6554
6611
|
if (codeGenResult.scenarios.length === 0) {
|
|
6555
|
-
throw new Error(`No scenarios found in test suite ${testSuiteId}. Cannot generate test files.`);
|
|
6612
|
+
throw new Error(`No scenarios found in test suite "${codeGenResult.testSuiteName}" (ID: ${testSuiteId}). Cannot generate test files.`);
|
|
6556
6613
|
}
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
if (fs.existsSync(testSuiteDir)) {
|
|
6614
|
+
const projectDir = outputDir;
|
|
6615
|
+
if (fs.existsSync(projectDir)) {
|
|
6560
6616
|
try {
|
|
6561
|
-
const nodeModulesPath = path.join(
|
|
6617
|
+
const nodeModulesPath = path.join(projectDir, 'node_modules');
|
|
6562
6618
|
const hasNodeModules = fs.existsSync(nodeModulesPath);
|
|
6563
|
-
// Temporarily move node_modules out of the way if it exists
|
|
6564
6619
|
let tempNodeModulesPath = null;
|
|
6565
6620
|
if (hasNodeModules) {
|
|
6566
|
-
tempNodeModulesPath = path.join(
|
|
6567
|
-
// Remove temp directory if it exists from a previous failed run
|
|
6621
|
+
tempNodeModulesPath = path.join(projectDir, '..', `node_modules.temp.${projectId}`);
|
|
6568
6622
|
if (fs.existsSync(tempNodeModulesPath)) {
|
|
6569
6623
|
fs.rmSync(tempNodeModulesPath, { recursive: true, force: true });
|
|
6570
6624
|
}
|
|
6571
6625
|
fs.renameSync(nodeModulesPath, tempNodeModulesPath);
|
|
6572
6626
|
console.log(`๐ฆ Preserved node_modules temporarily`);
|
|
6573
6627
|
}
|
|
6574
|
-
|
|
6575
|
-
const entries = fs.readdirSync(testSuiteDir, { withFileTypes: true });
|
|
6628
|
+
const entries = fs.readdirSync(projectDir, { withFileTypes: true });
|
|
6576
6629
|
for (const entry of entries) {
|
|
6577
|
-
const entryPath = path.join(
|
|
6630
|
+
const entryPath = path.join(projectDir, entry.name);
|
|
6578
6631
|
try {
|
|
6579
6632
|
if (entry.isDirectory()) {
|
|
6580
6633
|
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
@@ -6587,36 +6640,33 @@ class TestSuiteRunner {
|
|
|
6587
6640
|
console.warn(`โ ๏ธ Failed to delete ${entry.name}:`, error);
|
|
6588
6641
|
}
|
|
6589
6642
|
}
|
|
6590
|
-
console.log(`๐๏ธ Cleaned
|
|
6591
|
-
// Move node_modules back
|
|
6643
|
+
console.log(`๐๏ธ Cleaned project directory (preserved node_modules)`);
|
|
6592
6644
|
if (hasNodeModules && tempNodeModulesPath) {
|
|
6593
6645
|
fs.renameSync(tempNodeModulesPath, nodeModulesPath);
|
|
6594
6646
|
console.log(`๐ฆ Restored node_modules`);
|
|
6595
6647
|
}
|
|
6596
6648
|
}
|
|
6597
6649
|
catch (error) {
|
|
6598
|
-
console.warn(`โ ๏ธ Failed to clean
|
|
6599
|
-
// Continue anyway - we'll overwrite files as needed
|
|
6650
|
+
console.warn(`โ ๏ธ Failed to clean project directory ${projectDir}:`, error);
|
|
6600
6651
|
}
|
|
6601
6652
|
}
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
const testsDir = path.join(testSuiteDir, 'tests');
|
|
6653
|
+
ensureDirectoryExists(projectDir);
|
|
6654
|
+
const testsDir = path.join(projectDir, 'tests');
|
|
6605
6655
|
ensureDirectoryExists(testsDir);
|
|
6606
|
-
|
|
6656
|
+
const seenSlugs = new Map();
|
|
6607
6657
|
for (const scenario of codeGenResult.scenarios) {
|
|
6608
|
-
const
|
|
6658
|
+
const slug = slugify(scenario.scenarioName);
|
|
6659
|
+
const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
|
|
6660
|
+
seenSlugs.set(slug, count + 1);
|
|
6661
|
+
const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
|
|
6609
6662
|
const filePath = path.join(testsDir, fileName);
|
|
6610
6663
|
fs.writeFileSync(filePath, scenario.code, 'utf-8');
|
|
6611
6664
|
console.log(`โ
Generated test file: ${filePath}`);
|
|
6612
6665
|
}
|
|
6613
|
-
|
|
6614
|
-
await this.
|
|
6615
|
-
// Generate playwright.config.ts with runId if available
|
|
6616
|
-
await this.generatePlaywrightConfig(testSuiteDir, includeReporter, runId);
|
|
6617
|
-
// Generate custom reporter file for live progress streaming (only if requested)
|
|
6666
|
+
await this.generatePackageJson(projectDir, codeGenResult);
|
|
6667
|
+
await this.generatePlaywrightConfig(projectDir, includeReporter, runId);
|
|
6618
6668
|
if (includeReporter) {
|
|
6619
|
-
await this.generateProboReporter(
|
|
6669
|
+
await this.generateProboReporter(projectDir);
|
|
6620
6670
|
}
|
|
6621
6671
|
}
|
|
6622
6672
|
/**
|
|
@@ -6660,7 +6710,8 @@ class TestSuiteRunner {
|
|
|
6660
6710
|
const packageJsonContent = generatePackageJson({
|
|
6661
6711
|
name: sanitizedName,
|
|
6662
6712
|
hasSecrets: hasSecrets,
|
|
6663
|
-
testScript: 'npx playwright test'
|
|
6713
|
+
testScript: 'npx playwright test',
|
|
6714
|
+
probolabsPlaywrightVersion: process.env.PROBO_PLAYWRIGHT_VERSION || 'latest',
|
|
6664
6715
|
});
|
|
6665
6716
|
const filePath = path.join(outputDir, 'package.json');
|
|
6666
6717
|
fs.writeFileSync(filePath, packageJsonContent, 'utf-8');
|
|
@@ -6690,7 +6741,8 @@ class TestSuiteRunner {
|
|
|
6690
6741
|
*/
|
|
6691
6742
|
static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
|
|
6692
6743
|
const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
|
|
6693
|
-
const
|
|
6744
|
+
const details = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
|
|
6745
|
+
const projectDir = outputDir || getProjectDir(details.projectId, details.projectName);
|
|
6694
6746
|
let currentRunId = runId;
|
|
6695
6747
|
try {
|
|
6696
6748
|
// Create run record if not provided and not skipping run creation
|
|
@@ -6718,9 +6770,9 @@ class TestSuiteRunner {
|
|
|
6718
6770
|
}
|
|
6719
6771
|
// Generate all files (with runId if available for run-specific report directories)
|
|
6720
6772
|
console.log(`๐ Generating test suite files for test suite ${testSuiteId}...`);
|
|
6721
|
-
await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl,
|
|
6773
|
+
await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName, includeReporter, currentRunId);
|
|
6722
6774
|
// Count total tests
|
|
6723
|
-
const testsTotal = countTotalTests(
|
|
6775
|
+
const testsTotal = countTotalTests(projectDir);
|
|
6724
6776
|
if (currentRunId && testsTotal > 0) {
|
|
6725
6777
|
await updateRunStatus(currentRunId, testSuiteId, { tests_total: testsTotal }, apiToken, apiUrl);
|
|
6726
6778
|
if (onStatusUpdate) {
|
|
@@ -6728,10 +6780,10 @@ class TestSuiteRunner {
|
|
|
6728
6780
|
}
|
|
6729
6781
|
}
|
|
6730
6782
|
// Install dependencies
|
|
6731
|
-
console.log(`๐ฆ Installing dependencies in ${
|
|
6783
|
+
console.log(`๐ฆ Installing dependencies in ${projectDir}...`);
|
|
6732
6784
|
try {
|
|
6733
6785
|
const npmExecutable = findNpmExecutable();
|
|
6734
|
-
const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd:
|
|
6786
|
+
const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for install
|
|
6735
6787
|
);
|
|
6736
6788
|
console.log('โ
Dependencies installed successfully');
|
|
6737
6789
|
if (installStdout)
|
|
@@ -6771,7 +6823,7 @@ class TestSuiteRunner {
|
|
|
6771
6823
|
console.log(`๐ Installing Playwright Chromium browser...`);
|
|
6772
6824
|
try {
|
|
6773
6825
|
const npxExecutable = findNpxExecutable();
|
|
6774
|
-
const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd:
|
|
6826
|
+
const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for browser install
|
|
6775
6827
|
);
|
|
6776
6828
|
console.log('โ
Playwright Chromium browser installed successfully');
|
|
6777
6829
|
if (browserStdout)
|
|
@@ -6785,7 +6837,7 @@ class TestSuiteRunner {
|
|
|
6785
6837
|
// or the error will be caught when trying to run tests
|
|
6786
6838
|
}
|
|
6787
6839
|
// Run Playwright tests with streaming output
|
|
6788
|
-
console.log(`๐ Running Playwright tests in ${
|
|
6840
|
+
console.log(`๐ Running Playwright tests in ${projectDir}...`);
|
|
6789
6841
|
if (playwrightArgs.length > 0) {
|
|
6790
6842
|
console.log(`๐งช Playwright extra args: ${playwrightArgs.join(' ')}`);
|
|
6791
6843
|
}
|
|
@@ -6807,7 +6859,7 @@ class TestSuiteRunner {
|
|
|
6807
6859
|
// Use spawn for streaming output
|
|
6808
6860
|
const npxExecutable = findNpxExecutable();
|
|
6809
6861
|
const testProcess = spawn(npxExecutable, ['playwright', 'test', ...playwrightArgs], {
|
|
6810
|
-
cwd:
|
|
6862
|
+
cwd: projectDir,
|
|
6811
6863
|
shell: true,
|
|
6812
6864
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
6813
6865
|
env: {
|
|
@@ -6993,8 +7045,8 @@ class TestSuiteRunner {
|
|
|
6993
7045
|
console.log(success ? 'โ
Tests completed successfully' : (isTestFailure ? 'โ ๏ธ Tests completed with failures' : 'โ Test execution failed'));
|
|
6994
7046
|
// Parse final statistics from metadata/stdout as fallback
|
|
6995
7047
|
let parsedStats = null;
|
|
6996
|
-
if (fs.existsSync(path.join(
|
|
6997
|
-
parsedStats = parseStatisticsFromMetadata(
|
|
7048
|
+
if (fs.existsSync(path.join(projectDir, 'playwright-report'))) {
|
|
7049
|
+
parsedStats = parseStatisticsFromMetadata(projectDir);
|
|
6998
7050
|
}
|
|
6999
7051
|
if (!parsedStats) {
|
|
7000
7052
|
parsedStats = parsePlaywrightStatistics(stdout);
|
|
@@ -7196,7 +7248,8 @@ class TestSuiteRunner {
|
|
|
7196
7248
|
* Generate all files for a project (all scenarios)
|
|
7197
7249
|
*/
|
|
7198
7250
|
static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
|
|
7199
|
-
|
|
7251
|
+
var _a;
|
|
7252
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
|
|
7200
7253
|
// Generate code for all scenarios
|
|
7201
7254
|
const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
|
|
7202
7255
|
if (codeGenResult.scenarios.length === 0) {
|
|
@@ -7250,9 +7303,12 @@ class TestSuiteRunner {
|
|
|
7250
7303
|
ensureDirectoryExists(projectDir);
|
|
7251
7304
|
const testsDir = path.join(projectDir, 'tests');
|
|
7252
7305
|
ensureDirectoryExists(testsDir);
|
|
7253
|
-
|
|
7306
|
+
const seenSlugs = new Map();
|
|
7254
7307
|
for (const scenario of codeGenResult.scenarios) {
|
|
7255
|
-
const
|
|
7308
|
+
const slug = slugify(scenario.scenarioName);
|
|
7309
|
+
const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
|
|
7310
|
+
seenSlugs.set(slug, count + 1);
|
|
7311
|
+
const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
|
|
7256
7312
|
const filePath = path.join(testsDir, fileName);
|
|
7257
7313
|
fs.writeFileSync(filePath, scenario.code, 'utf-8');
|
|
7258
7314
|
console.log(`โ
Generated test file: ${filePath}`);
|
|
@@ -7266,6 +7322,18 @@ class TestSuiteRunner {
|
|
|
7266
7322
|
await this.generateProboReporter(projectDir);
|
|
7267
7323
|
}
|
|
7268
7324
|
}
|
|
7325
|
+
/**
|
|
7326
|
+
* Export project code to a directory.
|
|
7327
|
+
* If outputDir is provided, creates a subfolder {outputDir}/{projectId}-{projectNameSlug}/ and writes there.
|
|
7328
|
+
* Otherwise writes to the default project dir (~/.probium/projects/...).
|
|
7329
|
+
*/
|
|
7330
|
+
static async exportProjectCode(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = false) {
|
|
7331
|
+
const targetDir = outputDir
|
|
7332
|
+
? path.join(outputDir, `${projectId}-${slugify(projectName || `project-${projectId}`)}`)
|
|
7333
|
+
: getProjectDir(projectId, projectName);
|
|
7334
|
+
await this.generateProjectFiles(projectId, apiToken, apiUrl, targetDir, projectName, includeReporter);
|
|
7335
|
+
return targetDir;
|
|
7336
|
+
}
|
|
7269
7337
|
/**
|
|
7270
7338
|
* Run all scenarios in a project
|
|
7271
7339
|
* Generates files, installs dependencies, and executes tests
|
|
@@ -7274,7 +7342,7 @@ class TestSuiteRunner {
|
|
|
7274
7342
|
static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
|
|
7275
7343
|
const { outputDir, includeReporter = false, // CLI mode - no custom reporter
|
|
7276
7344
|
playwrightArgs = [], onStdout, onStderr, } = options;
|
|
7277
|
-
const projectDir = outputDir ||
|
|
7345
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
|
|
7278
7346
|
try {
|
|
7279
7347
|
// Generate all files
|
|
7280
7348
|
console.log(`๐ Generating test files for project ${projectId}...`);
|
|
@@ -7513,6 +7581,11 @@ class Probo {
|
|
|
7513
7581
|
this.timeoutConfig = mergeTimeoutConfigWithEnv({ ...DEFAULT_PLAYWRIGHT_TIMEOUT_CONFIG, ...timeoutConfig });
|
|
7514
7582
|
// set the log level for the api client
|
|
7515
7583
|
apiLogger.setLogLevel(this.debugLevel);
|
|
7584
|
+
proboLogger.debug(`Timeout config: playwrightActionTimeout=${this.timeoutConfig.playwrightActionTimeout}ms, ` +
|
|
7585
|
+
`playwrightNavigationTimeout=${this.timeoutConfig.playwrightNavigationTimeout}ms, ` +
|
|
7586
|
+
`playwrightLocatorTimeout=${this.timeoutConfig.playwrightLocatorTimeout}ms, ` +
|
|
7587
|
+
`waitForStabilityQuietTimeout=${this.timeoutConfig.waitForStabilityQuietTimeout}ms, ` +
|
|
7588
|
+
`waitForStabilityGlobalTimeout=${this.timeoutConfig.waitForStabilityGlobalTimeout}ms`);
|
|
7516
7589
|
proboLogger.info(`Initializing: scenario=${scenarioName}, apiUrl=${apiEndPoint}, ` +
|
|
7517
7590
|
`enableConsoleLogs=${enableConsoleLogs}, debugLevel=${debugLevel}, aiModel=${aiModel}`);
|
|
7518
7591
|
}
|
|
@@ -7849,5 +7922,5 @@ class Probo {
|
|
|
7849
7922
|
}
|
|
7850
7923
|
}
|
|
7851
7924
|
|
|
7852
|
-
export { AIModel, Highlighter, NavTracker, OTP, PlaywrightAction, Probo, ProboCodeGenerator, ProboLogLevel, ProboPlaywright, TestSuiteRunner, buildInterpolationContext, createUrlSlug, extractRequiredEnvVars, findClosestVisibleElement, generateCode, getRequiredEnvVars, interpolateWithParams };
|
|
7925
|
+
export { AIModel, Highlighter, NavTracker, OTP, PlaywrightAction, Probo, ProboCodeGenerator, ProboLogLevel, ProboPlaywright, TestSuiteRunner, buildInterpolationContext, createUrlSlug, extractRequiredEnvVars, findClosestVisibleElement, generateCode, getProjectDir, getRequiredEnvVars, interpolateWithParams };
|
|
7853
7926
|
//# sourceMappingURL=index.js.map
|