@probolabs/playwright 1.5.0-rc.8 โ 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 +124 -60
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +26 -3
- package/dist/index.js +124 -61
- 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/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
|
}
|
|
@@ -6111,12 +6128,12 @@ class ProboCodeGenerator {
|
|
|
6111
6128
|
return response.json();
|
|
6112
6129
|
}
|
|
6113
6130
|
/**
|
|
6114
|
-
* 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)
|
|
6115
6132
|
*/
|
|
6116
6133
|
static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
|
|
6117
6134
|
const normalizedUrl = this.normalizeApiUrl(apiUrl);
|
|
6118
|
-
//
|
|
6119
|
-
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}`;
|
|
6120
6137
|
const response = await fetch(url, {
|
|
6121
6138
|
method: 'GET',
|
|
6122
6139
|
headers: {
|
|
@@ -6254,6 +6271,7 @@ class ProboCodeGenerator {
|
|
|
6254
6271
|
scenarioName: scenarioData.name,
|
|
6255
6272
|
interactions: interactions,
|
|
6256
6273
|
rows: rows,
|
|
6274
|
+
...((options === null || options === void 0 ? void 0 : options.testSuiteTags) && options.testSuiteTags.length > 0 ? { testSuiteTags: options.testSuiteTags } : {}),
|
|
6257
6275
|
};
|
|
6258
6276
|
return generateCode(codeOptions, settings, viewPort);
|
|
6259
6277
|
}
|
|
@@ -6313,14 +6331,15 @@ class ProboCodeGenerator {
|
|
|
6313
6331
|
if (scenarios.length === 0) {
|
|
6314
6332
|
throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
|
|
6315
6333
|
}
|
|
6316
|
-
// Generate code for each scenario
|
|
6334
|
+
// Generate code for each scenario (with test_suite_tags for Playwright tagging)
|
|
6317
6335
|
const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
|
|
6318
6336
|
try {
|
|
6319
|
-
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 });
|
|
6320
6338
|
return {
|
|
6321
6339
|
scenarioId: scenario.id,
|
|
6322
6340
|
scenarioName: scenario.name,
|
|
6323
6341
|
code: code,
|
|
6342
|
+
testSuiteTags: scenario.test_suite_tags,
|
|
6324
6343
|
};
|
|
6325
6344
|
}
|
|
6326
6345
|
catch (error) {
|
|
@@ -6347,15 +6366,13 @@ function ensureDirectoryExists(dir) {
|
|
|
6347
6366
|
}
|
|
6348
6367
|
}
|
|
6349
6368
|
/**
|
|
6350
|
-
* Gets the
|
|
6369
|
+
* Gets the project directory path (unified for test suite runs and export).
|
|
6370
|
+
* Path: ~/.probium/projects/{projectId}-{projectName-slugified}/
|
|
6351
6371
|
*/
|
|
6352
|
-
function
|
|
6353
|
-
const baseDir = path.join(os.homedir(), '.probium', '
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
return path.join(baseDir, `${testSuiteId}-${sanitizedName}`);
|
|
6357
|
-
}
|
|
6358
|
-
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}`);
|
|
6359
6376
|
}
|
|
6360
6377
|
/**
|
|
6361
6378
|
* Count total tests by scanning spec files
|
|
@@ -6550,36 +6567,67 @@ class TestSuiteRunner {
|
|
|
6550
6567
|
throw new Error(`Test suite "${testSuiteName}" not found in project "${projectName}"`);
|
|
6551
6568
|
}
|
|
6552
6569
|
/**
|
|
6553
|
-
*
|
|
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)
|
|
6554
6597
|
*/
|
|
6555
6598
|
static async generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
|
|
6556
|
-
const
|
|
6557
|
-
|
|
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);
|
|
6558
6610
|
const codeGenResult = await ProboCodeGenerator.generateCodeForTestSuite(testSuiteId, apiToken, apiUrl);
|
|
6559
6611
|
if (codeGenResult.scenarios.length === 0) {
|
|
6560
|
-
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.`);
|
|
6561
6613
|
}
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
if (fs.existsSync(testSuiteDir)) {
|
|
6614
|
+
const projectDir = outputDir;
|
|
6615
|
+
if (fs.existsSync(projectDir)) {
|
|
6565
6616
|
try {
|
|
6566
|
-
const nodeModulesPath = path.join(
|
|
6617
|
+
const nodeModulesPath = path.join(projectDir, 'node_modules');
|
|
6567
6618
|
const hasNodeModules = fs.existsSync(nodeModulesPath);
|
|
6568
|
-
// Temporarily move node_modules out of the way if it exists
|
|
6569
6619
|
let tempNodeModulesPath = null;
|
|
6570
6620
|
if (hasNodeModules) {
|
|
6571
|
-
tempNodeModulesPath = path.join(
|
|
6572
|
-
// Remove temp directory if it exists from a previous failed run
|
|
6621
|
+
tempNodeModulesPath = path.join(projectDir, '..', `node_modules.temp.${projectId}`);
|
|
6573
6622
|
if (fs.existsSync(tempNodeModulesPath)) {
|
|
6574
6623
|
fs.rmSync(tempNodeModulesPath, { recursive: true, force: true });
|
|
6575
6624
|
}
|
|
6576
6625
|
fs.renameSync(nodeModulesPath, tempNodeModulesPath);
|
|
6577
6626
|
console.log(`๐ฆ Preserved node_modules temporarily`);
|
|
6578
6627
|
}
|
|
6579
|
-
|
|
6580
|
-
const entries = fs.readdirSync(testSuiteDir, { withFileTypes: true });
|
|
6628
|
+
const entries = fs.readdirSync(projectDir, { withFileTypes: true });
|
|
6581
6629
|
for (const entry of entries) {
|
|
6582
|
-
const entryPath = path.join(
|
|
6630
|
+
const entryPath = path.join(projectDir, entry.name);
|
|
6583
6631
|
try {
|
|
6584
6632
|
if (entry.isDirectory()) {
|
|
6585
6633
|
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
@@ -6592,36 +6640,33 @@ class TestSuiteRunner {
|
|
|
6592
6640
|
console.warn(`โ ๏ธ Failed to delete ${entry.name}:`, error);
|
|
6593
6641
|
}
|
|
6594
6642
|
}
|
|
6595
|
-
console.log(`๐๏ธ Cleaned
|
|
6596
|
-
// Move node_modules back
|
|
6643
|
+
console.log(`๐๏ธ Cleaned project directory (preserved node_modules)`);
|
|
6597
6644
|
if (hasNodeModules && tempNodeModulesPath) {
|
|
6598
6645
|
fs.renameSync(tempNodeModulesPath, nodeModulesPath);
|
|
6599
6646
|
console.log(`๐ฆ Restored node_modules`);
|
|
6600
6647
|
}
|
|
6601
6648
|
}
|
|
6602
6649
|
catch (error) {
|
|
6603
|
-
console.warn(`โ ๏ธ Failed to clean
|
|
6604
|
-
// Continue anyway - we'll overwrite files as needed
|
|
6650
|
+
console.warn(`โ ๏ธ Failed to clean project directory ${projectDir}:`, error);
|
|
6605
6651
|
}
|
|
6606
6652
|
}
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
const testsDir = path.join(testSuiteDir, 'tests');
|
|
6653
|
+
ensureDirectoryExists(projectDir);
|
|
6654
|
+
const testsDir = path.join(projectDir, 'tests');
|
|
6610
6655
|
ensureDirectoryExists(testsDir);
|
|
6611
|
-
|
|
6656
|
+
const seenSlugs = new Map();
|
|
6612
6657
|
for (const scenario of codeGenResult.scenarios) {
|
|
6613
|
-
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`;
|
|
6614
6662
|
const filePath = path.join(testsDir, fileName);
|
|
6615
6663
|
fs.writeFileSync(filePath, scenario.code, 'utf-8');
|
|
6616
6664
|
console.log(`โ
Generated test file: ${filePath}`);
|
|
6617
6665
|
}
|
|
6618
|
-
|
|
6619
|
-
await this.
|
|
6620
|
-
// Generate playwright.config.ts with runId if available
|
|
6621
|
-
await this.generatePlaywrightConfig(testSuiteDir, includeReporter, runId);
|
|
6622
|
-
// Generate custom reporter file for live progress streaming (only if requested)
|
|
6666
|
+
await this.generatePackageJson(projectDir, codeGenResult);
|
|
6667
|
+
await this.generatePlaywrightConfig(projectDir, includeReporter, runId);
|
|
6623
6668
|
if (includeReporter) {
|
|
6624
|
-
await this.generateProboReporter(
|
|
6669
|
+
await this.generateProboReporter(projectDir);
|
|
6625
6670
|
}
|
|
6626
6671
|
}
|
|
6627
6672
|
/**
|
|
@@ -6665,7 +6710,8 @@ class TestSuiteRunner {
|
|
|
6665
6710
|
const packageJsonContent = generatePackageJson({
|
|
6666
6711
|
name: sanitizedName,
|
|
6667
6712
|
hasSecrets: hasSecrets,
|
|
6668
|
-
testScript: 'npx playwright test'
|
|
6713
|
+
testScript: 'npx playwright test',
|
|
6714
|
+
probolabsPlaywrightVersion: process.env.PROBO_PLAYWRIGHT_VERSION || 'latest',
|
|
6669
6715
|
});
|
|
6670
6716
|
const filePath = path.join(outputDir, 'package.json');
|
|
6671
6717
|
fs.writeFileSync(filePath, packageJsonContent, 'utf-8');
|
|
@@ -6695,7 +6741,8 @@ class TestSuiteRunner {
|
|
|
6695
6741
|
*/
|
|
6696
6742
|
static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
|
|
6697
6743
|
const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
|
|
6698
|
-
const
|
|
6744
|
+
const details = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
|
|
6745
|
+
const projectDir = outputDir || getProjectDir(details.projectId, details.projectName);
|
|
6699
6746
|
let currentRunId = runId;
|
|
6700
6747
|
try {
|
|
6701
6748
|
// Create run record if not provided and not skipping run creation
|
|
@@ -6723,9 +6770,9 @@ class TestSuiteRunner {
|
|
|
6723
6770
|
}
|
|
6724
6771
|
// Generate all files (with runId if available for run-specific report directories)
|
|
6725
6772
|
console.log(`๐ Generating test suite files for test suite ${testSuiteId}...`);
|
|
6726
|
-
await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl,
|
|
6773
|
+
await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName, includeReporter, currentRunId);
|
|
6727
6774
|
// Count total tests
|
|
6728
|
-
const testsTotal = countTotalTests(
|
|
6775
|
+
const testsTotal = countTotalTests(projectDir);
|
|
6729
6776
|
if (currentRunId && testsTotal > 0) {
|
|
6730
6777
|
await updateRunStatus(currentRunId, testSuiteId, { tests_total: testsTotal }, apiToken, apiUrl);
|
|
6731
6778
|
if (onStatusUpdate) {
|
|
@@ -6733,10 +6780,10 @@ class TestSuiteRunner {
|
|
|
6733
6780
|
}
|
|
6734
6781
|
}
|
|
6735
6782
|
// Install dependencies
|
|
6736
|
-
console.log(`๐ฆ Installing dependencies in ${
|
|
6783
|
+
console.log(`๐ฆ Installing dependencies in ${projectDir}...`);
|
|
6737
6784
|
try {
|
|
6738
6785
|
const npmExecutable = findNpmExecutable();
|
|
6739
|
-
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
|
|
6740
6787
|
);
|
|
6741
6788
|
console.log('โ
Dependencies installed successfully');
|
|
6742
6789
|
if (installStdout)
|
|
@@ -6776,7 +6823,7 @@ class TestSuiteRunner {
|
|
|
6776
6823
|
console.log(`๐ Installing Playwright Chromium browser...`);
|
|
6777
6824
|
try {
|
|
6778
6825
|
const npxExecutable = findNpxExecutable();
|
|
6779
|
-
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
|
|
6780
6827
|
);
|
|
6781
6828
|
console.log('โ
Playwright Chromium browser installed successfully');
|
|
6782
6829
|
if (browserStdout)
|
|
@@ -6790,7 +6837,7 @@ class TestSuiteRunner {
|
|
|
6790
6837
|
// or the error will be caught when trying to run tests
|
|
6791
6838
|
}
|
|
6792
6839
|
// Run Playwright tests with streaming output
|
|
6793
|
-
console.log(`๐ Running Playwright tests in ${
|
|
6840
|
+
console.log(`๐ Running Playwright tests in ${projectDir}...`);
|
|
6794
6841
|
if (playwrightArgs.length > 0) {
|
|
6795
6842
|
console.log(`๐งช Playwright extra args: ${playwrightArgs.join(' ')}`);
|
|
6796
6843
|
}
|
|
@@ -6812,7 +6859,7 @@ class TestSuiteRunner {
|
|
|
6812
6859
|
// Use spawn for streaming output
|
|
6813
6860
|
const npxExecutable = findNpxExecutable();
|
|
6814
6861
|
const testProcess = spawn(npxExecutable, ['playwright', 'test', ...playwrightArgs], {
|
|
6815
|
-
cwd:
|
|
6862
|
+
cwd: projectDir,
|
|
6816
6863
|
shell: true,
|
|
6817
6864
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
6818
6865
|
env: {
|
|
@@ -6998,8 +7045,8 @@ class TestSuiteRunner {
|
|
|
6998
7045
|
console.log(success ? 'โ
Tests completed successfully' : (isTestFailure ? 'โ ๏ธ Tests completed with failures' : 'โ Test execution failed'));
|
|
6999
7046
|
// Parse final statistics from metadata/stdout as fallback
|
|
7000
7047
|
let parsedStats = null;
|
|
7001
|
-
if (fs.existsSync(path.join(
|
|
7002
|
-
parsedStats = parseStatisticsFromMetadata(
|
|
7048
|
+
if (fs.existsSync(path.join(projectDir, 'playwright-report'))) {
|
|
7049
|
+
parsedStats = parseStatisticsFromMetadata(projectDir);
|
|
7003
7050
|
}
|
|
7004
7051
|
if (!parsedStats) {
|
|
7005
7052
|
parsedStats = parsePlaywrightStatistics(stdout);
|
|
@@ -7201,7 +7248,8 @@ class TestSuiteRunner {
|
|
|
7201
7248
|
* Generate all files for a project (all scenarios)
|
|
7202
7249
|
*/
|
|
7203
7250
|
static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
|
|
7204
|
-
|
|
7251
|
+
var _a;
|
|
7252
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
|
|
7205
7253
|
// Generate code for all scenarios
|
|
7206
7254
|
const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
|
|
7207
7255
|
if (codeGenResult.scenarios.length === 0) {
|
|
@@ -7255,9 +7303,12 @@ class TestSuiteRunner {
|
|
|
7255
7303
|
ensureDirectoryExists(projectDir);
|
|
7256
7304
|
const testsDir = path.join(projectDir, 'tests');
|
|
7257
7305
|
ensureDirectoryExists(testsDir);
|
|
7258
|
-
|
|
7306
|
+
const seenSlugs = new Map();
|
|
7259
7307
|
for (const scenario of codeGenResult.scenarios) {
|
|
7260
|
-
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`;
|
|
7261
7312
|
const filePath = path.join(testsDir, fileName);
|
|
7262
7313
|
fs.writeFileSync(filePath, scenario.code, 'utf-8');
|
|
7263
7314
|
console.log(`โ
Generated test file: ${filePath}`);
|
|
@@ -7271,6 +7322,18 @@ class TestSuiteRunner {
|
|
|
7271
7322
|
await this.generateProboReporter(projectDir);
|
|
7272
7323
|
}
|
|
7273
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
|
+
}
|
|
7274
7337
|
/**
|
|
7275
7338
|
* Run all scenarios in a project
|
|
7276
7339
|
* Generates files, installs dependencies, and executes tests
|
|
@@ -7279,7 +7342,7 @@ class TestSuiteRunner {
|
|
|
7279
7342
|
static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
|
|
7280
7343
|
const { outputDir, includeReporter = false, // CLI mode - no custom reporter
|
|
7281
7344
|
playwrightArgs = [], onStdout, onStderr, } = options;
|
|
7282
|
-
const projectDir = outputDir ||
|
|
7345
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
|
|
7283
7346
|
try {
|
|
7284
7347
|
// Generate all files
|
|
7285
7348
|
console.log(`๐ Generating test files for project ${projectId}...`);
|
|
@@ -7859,5 +7922,5 @@ class Probo {
|
|
|
7859
7922
|
}
|
|
7860
7923
|
}
|
|
7861
7924
|
|
|
7862
|
-
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 };
|
|
7863
7926
|
//# sourceMappingURL=index.js.map
|