@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/cli.js
CHANGED
|
@@ -321,6 +321,16 @@ const DEFAULT_RECORDER_SETTINGS = {
|
|
|
321
321
|
};
|
|
322
322
|
|
|
323
323
|
// --- Code generation utilities for Probo Labs Playwright scripts ---
|
|
324
|
+
/**
|
|
325
|
+
* Converts a test suite name to a Playwright tag (slugify + @ prefix).
|
|
326
|
+
* Playwright tags must start with @.
|
|
327
|
+
*/
|
|
328
|
+
function slugifyTag(tag) {
|
|
329
|
+
if (!tag || typeof tag !== 'string')
|
|
330
|
+
return '@untagged';
|
|
331
|
+
const slug = slugify(tag);
|
|
332
|
+
return slug ? `@${slug}` : '@untagged';
|
|
333
|
+
}
|
|
324
334
|
/**
|
|
325
335
|
* Extracts environment variable names from parameter table rows
|
|
326
336
|
* by parsing ${process.env.VAR_NAME} patterns from parameter values
|
|
@@ -620,6 +630,13 @@ function scriptTemplate(options, settings, viewPort) {
|
|
|
620
630
|
}).join(',\n ')}
|
|
621
631
|
];
|
|
622
632
|
` : '';
|
|
633
|
+
// Playwright tags for test suite filtering (e.g. npx playwright test --grep @sanity)
|
|
634
|
+
const playwrightTags = options.testSuiteTags && options.testSuiteTags.length > 0
|
|
635
|
+
? options.testSuiteTags.map(slugifyTag).filter((t) => t !== '@untagged')
|
|
636
|
+
: [];
|
|
637
|
+
const describeTagOption = playwrightTags.length > 0
|
|
638
|
+
? `{ tag: [${playwrightTags.map((t) => `'${t}'`).join(', ')}] }, `
|
|
639
|
+
: '';
|
|
623
640
|
const proboConstructor = hasAiInteractions ? `
|
|
624
641
|
const probo = new Probo({
|
|
625
642
|
scenarioName: '${options.scenarioName}',
|
|
@@ -696,7 +713,7 @@ ${hasSecrets ? `import { config } from 'dotenv';
|
|
|
696
713
|
}
|
|
697
714
|
});
|
|
698
715
|
|
|
699
|
-
test.describe('${options.scenarioName}', () => {
|
|
716
|
+
test.describe('${options.scenarioName}', ${describeTagOption}() => {
|
|
700
717
|
test.beforeEach(async ({ page }) => {
|
|
701
718
|
// set the ProboPlaywright instance to the current page
|
|
702
719
|
ppw.setPage(page);
|
|
@@ -735,7 +752,7 @@ const DEFAULT_PLAYWRIGHT_TEST_VERSION = '^1.57.0';
|
|
|
735
752
|
* Generate package.json content for a Playwright test project
|
|
736
753
|
*/
|
|
737
754
|
function generatePackageJson(options) {
|
|
738
|
-
const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION } = options;
|
|
755
|
+
const { name, hasSecrets = false, testScript = 'npx playwright test', playwrightTestVersion = DEFAULT_PLAYWRIGHT_TEST_VERSION, probolabsPlaywrightVersion = 'latest' } = options;
|
|
739
756
|
return `{
|
|
740
757
|
"name": "${name}",
|
|
741
758
|
"version": "1.0.0",
|
|
@@ -746,7 +763,7 @@ function generatePackageJson(options) {
|
|
|
746
763
|
},
|
|
747
764
|
"dependencies": {
|
|
748
765
|
"@playwright/test": "${playwrightTestVersion}",
|
|
749
|
-
"@probolabs/playwright": "
|
|
766
|
+
"@probolabs/playwright": "${probolabsPlaywrightVersion}"${hasSecrets ? ',\n "dotenv": "latest"' : ''}
|
|
750
767
|
}
|
|
751
768
|
}`;
|
|
752
769
|
}
|
|
@@ -1139,12 +1156,12 @@ class ProboCodeGenerator {
|
|
|
1139
1156
|
return response.json();
|
|
1140
1157
|
}
|
|
1141
1158
|
/**
|
|
1142
|
-
* Fetch all scenarios for a project from the backend API
|
|
1159
|
+
* Fetch all scenarios for a project from the backend API (light endpoint includes test_suite_tags)
|
|
1143
1160
|
*/
|
|
1144
1161
|
static async fetchProjectScenarios(projectId, apiToken, apiUrl) {
|
|
1145
1162
|
const normalizedUrl = this.normalizeApiUrl(apiUrl);
|
|
1146
|
-
//
|
|
1147
|
-
const url = `${normalizedUrl}/scenarios/?project_id=${projectId}`;
|
|
1163
|
+
// Use api/scenarios/light/ to get test_suite_tags for Playwright tagging
|
|
1164
|
+
const url = `${normalizedUrl}/api/scenarios/light/?project_id=${projectId}`;
|
|
1148
1165
|
const response = await fetch(url, {
|
|
1149
1166
|
method: 'GET',
|
|
1150
1167
|
headers: {
|
|
@@ -1282,6 +1299,7 @@ class ProboCodeGenerator {
|
|
|
1282
1299
|
scenarioName: scenarioData.name,
|
|
1283
1300
|
interactions: interactions,
|
|
1284
1301
|
rows: rows,
|
|
1302
|
+
...((options === null || options === void 0 ? void 0 : options.testSuiteTags) && options.testSuiteTags.length > 0 ? { testSuiteTags: options.testSuiteTags } : {}),
|
|
1285
1303
|
};
|
|
1286
1304
|
return generateCode(codeOptions, settings, viewPort);
|
|
1287
1305
|
}
|
|
@@ -1341,14 +1359,15 @@ class ProboCodeGenerator {
|
|
|
1341
1359
|
if (scenarios.length === 0) {
|
|
1342
1360
|
throw new Error(`No scenarios found in project "${projectData.name}" (ID: ${projectId})`);
|
|
1343
1361
|
}
|
|
1344
|
-
// Generate code for each scenario
|
|
1362
|
+
// Generate code for each scenario (with test_suite_tags for Playwright tagging)
|
|
1345
1363
|
const scenarioResults = await Promise.all(scenarios.map(async (scenario) => {
|
|
1346
1364
|
try {
|
|
1347
|
-
const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, options);
|
|
1365
|
+
const code = await this.generateCodeForScenario(scenario.id, apiToken, apiUrl, { ...options, testSuiteTags: scenario.test_suite_tags });
|
|
1348
1366
|
return {
|
|
1349
1367
|
scenarioId: scenario.id,
|
|
1350
1368
|
scenarioName: scenario.name,
|
|
1351
1369
|
code: code,
|
|
1370
|
+
testSuiteTags: scenario.test_suite_tags,
|
|
1352
1371
|
};
|
|
1353
1372
|
}
|
|
1354
1373
|
catch (error) {
|
|
@@ -1375,15 +1394,13 @@ function ensureDirectoryExists(dir) {
|
|
|
1375
1394
|
}
|
|
1376
1395
|
}
|
|
1377
1396
|
/**
|
|
1378
|
-
* Gets the
|
|
1397
|
+
* Gets the project directory path (unified for test suite runs and export).
|
|
1398
|
+
* Path: ~/.probium/projects/{projectId}-{projectName-slugified}/
|
|
1379
1399
|
*/
|
|
1380
|
-
function
|
|
1381
|
-
const baseDir = path.join(os.homedir(), '.probium', '
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
return path.join(baseDir, `${testSuiteId}-${sanitizedName}`);
|
|
1385
|
-
}
|
|
1386
|
-
return path.join(baseDir, testSuiteId.toString());
|
|
1400
|
+
function getProjectDir(projectId, projectName) {
|
|
1401
|
+
const baseDir = path.join(os.homedir(), '.probium', 'projects');
|
|
1402
|
+
const sanitizedName = projectName ? slugify(projectName) : `project-${projectId}`;
|
|
1403
|
+
return path.join(baseDir, `${projectId}-${sanitizedName}`);
|
|
1387
1404
|
}
|
|
1388
1405
|
/**
|
|
1389
1406
|
* Count total tests by scanning spec files
|
|
@@ -1578,36 +1595,67 @@ class TestSuiteRunner {
|
|
|
1578
1595
|
throw new Error(`Test suite "${testSuiteName}" not found in project "${projectName}"`);
|
|
1579
1596
|
}
|
|
1580
1597
|
/**
|
|
1581
|
-
*
|
|
1598
|
+
* Fetch test suite details (project id, project name, test suite name)
|
|
1599
|
+
*/
|
|
1600
|
+
static async fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl) {
|
|
1601
|
+
var _a, _b, _c;
|
|
1602
|
+
const baseUrl = apiUrl.endsWith('/') ? apiUrl.slice(0, -1) : apiUrl;
|
|
1603
|
+
const response = await fetch$1(`${baseUrl}/test-suites/${testSuiteId}/`, {
|
|
1604
|
+
method: 'GET',
|
|
1605
|
+
headers: {
|
|
1606
|
+
'Authorization': `Token ${apiToken}`,
|
|
1607
|
+
'Content-Type': 'application/json',
|
|
1608
|
+
},
|
|
1609
|
+
});
|
|
1610
|
+
if (!response.ok) {
|
|
1611
|
+
const errorText = await response.text();
|
|
1612
|
+
throw new Error(`Failed to fetch test suite ${testSuiteId}: ${response.status} ${errorText}`);
|
|
1613
|
+
}
|
|
1614
|
+
const data = await response.json();
|
|
1615
|
+
const projectId = (_a = data.project) !== null && _a !== void 0 ? _a : data.project_id;
|
|
1616
|
+
const projectName = (_b = data.project_name) !== null && _b !== void 0 ? _b : `project-${projectId}`;
|
|
1617
|
+
const testSuiteName = (_c = data.name) !== null && _c !== void 0 ? _c : `suite-${testSuiteId}`;
|
|
1618
|
+
if (!projectId) {
|
|
1619
|
+
throw new Error(`Test suite ${testSuiteId} has no project`);
|
|
1620
|
+
}
|
|
1621
|
+
return { projectId, projectName, testSuiteName };
|
|
1622
|
+
}
|
|
1623
|
+
/**
|
|
1624
|
+
* Generate all files for a test suite (writes to project-level directory)
|
|
1582
1625
|
*/
|
|
1583
1626
|
static async generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
|
|
1584
|
-
const
|
|
1585
|
-
|
|
1627
|
+
const { projectId, projectName, testSuiteName: resolvedSuiteName } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
|
|
1628
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName);
|
|
1629
|
+
await this.generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName !== null && testSuiteName !== void 0 ? testSuiteName : resolvedSuiteName, includeReporter, runId);
|
|
1630
|
+
}
|
|
1631
|
+
/**
|
|
1632
|
+
* Generate files only for the scenarios that participate in the test suite.
|
|
1633
|
+
* Used by the recorder app test-suite runner so only suite specs are written; then we run npx playwright test normally.
|
|
1634
|
+
*/
|
|
1635
|
+
static async generateTestSuiteOnlyFiles(testSuiteId, apiToken, apiUrl, outputDir, testSuiteName, includeReporter = true, runId) {
|
|
1636
|
+
var _a;
|
|
1637
|
+
const { projectId } = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
|
|
1586
1638
|
const codeGenResult = await ProboCodeGenerator.generateCodeForTestSuite(testSuiteId, apiToken, apiUrl);
|
|
1587
1639
|
if (codeGenResult.scenarios.length === 0) {
|
|
1588
|
-
throw new Error(`No scenarios found in test suite ${testSuiteId}. Cannot generate test files.`);
|
|
1640
|
+
throw new Error(`No scenarios found in test suite "${codeGenResult.testSuiteName}" (ID: ${testSuiteId}). Cannot generate test files.`);
|
|
1589
1641
|
}
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
if (fs.existsSync(testSuiteDir)) {
|
|
1642
|
+
const projectDir = outputDir;
|
|
1643
|
+
if (fs.existsSync(projectDir)) {
|
|
1593
1644
|
try {
|
|
1594
|
-
const nodeModulesPath = path.join(
|
|
1645
|
+
const nodeModulesPath = path.join(projectDir, 'node_modules');
|
|
1595
1646
|
const hasNodeModules = fs.existsSync(nodeModulesPath);
|
|
1596
|
-
// Temporarily move node_modules out of the way if it exists
|
|
1597
1647
|
let tempNodeModulesPath = null;
|
|
1598
1648
|
if (hasNodeModules) {
|
|
1599
|
-
tempNodeModulesPath = path.join(
|
|
1600
|
-
// Remove temp directory if it exists from a previous failed run
|
|
1649
|
+
tempNodeModulesPath = path.join(projectDir, '..', `node_modules.temp.${projectId}`);
|
|
1601
1650
|
if (fs.existsSync(tempNodeModulesPath)) {
|
|
1602
1651
|
fs.rmSync(tempNodeModulesPath, { recursive: true, force: true });
|
|
1603
1652
|
}
|
|
1604
1653
|
fs.renameSync(nodeModulesPath, tempNodeModulesPath);
|
|
1605
1654
|
console.log(`๐ฆ Preserved node_modules temporarily`);
|
|
1606
1655
|
}
|
|
1607
|
-
|
|
1608
|
-
const entries = fs.readdirSync(testSuiteDir, { withFileTypes: true });
|
|
1656
|
+
const entries = fs.readdirSync(projectDir, { withFileTypes: true });
|
|
1609
1657
|
for (const entry of entries) {
|
|
1610
|
-
const entryPath = path.join(
|
|
1658
|
+
const entryPath = path.join(projectDir, entry.name);
|
|
1611
1659
|
try {
|
|
1612
1660
|
if (entry.isDirectory()) {
|
|
1613
1661
|
fs.rmSync(entryPath, { recursive: true, force: true });
|
|
@@ -1620,36 +1668,33 @@ class TestSuiteRunner {
|
|
|
1620
1668
|
console.warn(`โ ๏ธ Failed to delete ${entry.name}:`, error);
|
|
1621
1669
|
}
|
|
1622
1670
|
}
|
|
1623
|
-
console.log(`๐๏ธ Cleaned
|
|
1624
|
-
// Move node_modules back
|
|
1671
|
+
console.log(`๐๏ธ Cleaned project directory (preserved node_modules)`);
|
|
1625
1672
|
if (hasNodeModules && tempNodeModulesPath) {
|
|
1626
1673
|
fs.renameSync(tempNodeModulesPath, nodeModulesPath);
|
|
1627
1674
|
console.log(`๐ฆ Restored node_modules`);
|
|
1628
1675
|
}
|
|
1629
1676
|
}
|
|
1630
1677
|
catch (error) {
|
|
1631
|
-
console.warn(`โ ๏ธ Failed to clean
|
|
1632
|
-
// Continue anyway - we'll overwrite files as needed
|
|
1678
|
+
console.warn(`โ ๏ธ Failed to clean project directory ${projectDir}:`, error);
|
|
1633
1679
|
}
|
|
1634
1680
|
}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
const testsDir = path.join(testSuiteDir, 'tests');
|
|
1681
|
+
ensureDirectoryExists(projectDir);
|
|
1682
|
+
const testsDir = path.join(projectDir, 'tests');
|
|
1638
1683
|
ensureDirectoryExists(testsDir);
|
|
1639
|
-
|
|
1684
|
+
const seenSlugs = new Map();
|
|
1640
1685
|
for (const scenario of codeGenResult.scenarios) {
|
|
1641
|
-
const
|
|
1686
|
+
const slug = slugify(scenario.scenarioName);
|
|
1687
|
+
const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
|
|
1688
|
+
seenSlugs.set(slug, count + 1);
|
|
1689
|
+
const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
|
|
1642
1690
|
const filePath = path.join(testsDir, fileName);
|
|
1643
1691
|
fs.writeFileSync(filePath, scenario.code, 'utf-8');
|
|
1644
1692
|
console.log(`โ
Generated test file: ${filePath}`);
|
|
1645
1693
|
}
|
|
1646
|
-
|
|
1647
|
-
await this.
|
|
1648
|
-
// Generate playwright.config.ts with runId if available
|
|
1649
|
-
await this.generatePlaywrightConfig(testSuiteDir, includeReporter, runId);
|
|
1650
|
-
// Generate custom reporter file for live progress streaming (only if requested)
|
|
1694
|
+
await this.generatePackageJson(projectDir, codeGenResult);
|
|
1695
|
+
await this.generatePlaywrightConfig(projectDir, includeReporter, runId);
|
|
1651
1696
|
if (includeReporter) {
|
|
1652
|
-
await this.generateProboReporter(
|
|
1697
|
+
await this.generateProboReporter(projectDir);
|
|
1653
1698
|
}
|
|
1654
1699
|
}
|
|
1655
1700
|
/**
|
|
@@ -1693,7 +1738,8 @@ class TestSuiteRunner {
|
|
|
1693
1738
|
const packageJsonContent = generatePackageJson({
|
|
1694
1739
|
name: sanitizedName,
|
|
1695
1740
|
hasSecrets: hasSecrets,
|
|
1696
|
-
testScript: 'npx playwright test'
|
|
1741
|
+
testScript: 'npx playwright test',
|
|
1742
|
+
probolabsPlaywrightVersion: process.env.PROBO_PLAYWRIGHT_VERSION || 'latest',
|
|
1697
1743
|
});
|
|
1698
1744
|
const filePath = path.join(outputDir, 'package.json');
|
|
1699
1745
|
fs.writeFileSync(filePath, packageJsonContent, 'utf-8');
|
|
@@ -1723,7 +1769,8 @@ class TestSuiteRunner {
|
|
|
1723
1769
|
*/
|
|
1724
1770
|
static async runTestSuite(testSuiteId, apiToken, apiUrl, testSuiteName, runId, options = {}) {
|
|
1725
1771
|
const { outputDir, includeReporter = true, playwrightArgs = [], skipRunCreation = false, onStatusUpdate, onStdout, onStderr, onReporterEvent, } = options;
|
|
1726
|
-
const
|
|
1772
|
+
const details = await this.fetchTestSuiteDetails(testSuiteId, apiToken, apiUrl);
|
|
1773
|
+
const projectDir = outputDir || getProjectDir(details.projectId, details.projectName);
|
|
1727
1774
|
let currentRunId = runId;
|
|
1728
1775
|
try {
|
|
1729
1776
|
// Create run record if not provided and not skipping run creation
|
|
@@ -1751,9 +1798,9 @@ class TestSuiteRunner {
|
|
|
1751
1798
|
}
|
|
1752
1799
|
// Generate all files (with runId if available for run-specific report directories)
|
|
1753
1800
|
console.log(`๐ Generating test suite files for test suite ${testSuiteId}...`);
|
|
1754
|
-
await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl,
|
|
1801
|
+
await this.generateTestSuiteFiles(testSuiteId, apiToken, apiUrl, projectDir, testSuiteName, includeReporter, currentRunId);
|
|
1755
1802
|
// Count total tests
|
|
1756
|
-
const testsTotal = countTotalTests(
|
|
1803
|
+
const testsTotal = countTotalTests(projectDir);
|
|
1757
1804
|
if (currentRunId && testsTotal > 0) {
|
|
1758
1805
|
await updateRunStatus(currentRunId, testSuiteId, { tests_total: testsTotal }, apiToken, apiUrl);
|
|
1759
1806
|
if (onStatusUpdate) {
|
|
@@ -1761,10 +1808,10 @@ class TestSuiteRunner {
|
|
|
1761
1808
|
}
|
|
1762
1809
|
}
|
|
1763
1810
|
// Install dependencies
|
|
1764
|
-
console.log(`๐ฆ Installing dependencies in ${
|
|
1811
|
+
console.log(`๐ฆ Installing dependencies in ${projectDir}...`);
|
|
1765
1812
|
try {
|
|
1766
1813
|
const npmExecutable = findNpmExecutable();
|
|
1767
|
-
const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd:
|
|
1814
|
+
const { stdout: installStdout, stderr: installStderr } = await execAsync(`${npmExecutable} install`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for install
|
|
1768
1815
|
);
|
|
1769
1816
|
console.log('โ
Dependencies installed successfully');
|
|
1770
1817
|
if (installStdout)
|
|
@@ -1804,7 +1851,7 @@ class TestSuiteRunner {
|
|
|
1804
1851
|
console.log(`๐ Installing Playwright Chromium browser...`);
|
|
1805
1852
|
try {
|
|
1806
1853
|
const npxExecutable = findNpxExecutable();
|
|
1807
|
-
const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd:
|
|
1854
|
+
const { stdout: browserStdout, stderr: browserStderr } = await execAsync(`${npxExecutable} playwright install chromium`, { cwd: projectDir, timeout: 300000 } // 5 minute timeout for browser install
|
|
1808
1855
|
);
|
|
1809
1856
|
console.log('โ
Playwright Chromium browser installed successfully');
|
|
1810
1857
|
if (browserStdout)
|
|
@@ -1818,7 +1865,7 @@ class TestSuiteRunner {
|
|
|
1818
1865
|
// or the error will be caught when trying to run tests
|
|
1819
1866
|
}
|
|
1820
1867
|
// Run Playwright tests with streaming output
|
|
1821
|
-
console.log(`๐ Running Playwright tests in ${
|
|
1868
|
+
console.log(`๐ Running Playwright tests in ${projectDir}...`);
|
|
1822
1869
|
if (playwrightArgs.length > 0) {
|
|
1823
1870
|
console.log(`๐งช Playwright extra args: ${playwrightArgs.join(' ')}`);
|
|
1824
1871
|
}
|
|
@@ -1840,7 +1887,7 @@ class TestSuiteRunner {
|
|
|
1840
1887
|
// Use spawn for streaming output
|
|
1841
1888
|
const npxExecutable = findNpxExecutable();
|
|
1842
1889
|
const testProcess = spawn(npxExecutable, ['playwright', 'test', ...playwrightArgs], {
|
|
1843
|
-
cwd:
|
|
1890
|
+
cwd: projectDir,
|
|
1844
1891
|
shell: true,
|
|
1845
1892
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
1846
1893
|
env: {
|
|
@@ -2026,8 +2073,8 @@ class TestSuiteRunner {
|
|
|
2026
2073
|
console.log(success ? 'โ
Tests completed successfully' : (isTestFailure ? 'โ ๏ธ Tests completed with failures' : 'โ Test execution failed'));
|
|
2027
2074
|
// Parse final statistics from metadata/stdout as fallback
|
|
2028
2075
|
let parsedStats = null;
|
|
2029
|
-
if (fs.existsSync(path.join(
|
|
2030
|
-
parsedStats = parseStatisticsFromMetadata(
|
|
2076
|
+
if (fs.existsSync(path.join(projectDir, 'playwright-report'))) {
|
|
2077
|
+
parsedStats = parseStatisticsFromMetadata(projectDir);
|
|
2031
2078
|
}
|
|
2032
2079
|
if (!parsedStats) {
|
|
2033
2080
|
parsedStats = parsePlaywrightStatistics(stdout);
|
|
@@ -2229,7 +2276,8 @@ class TestSuiteRunner {
|
|
|
2229
2276
|
* Generate all files for a project (all scenarios)
|
|
2230
2277
|
*/
|
|
2231
2278
|
static async generateProjectFiles(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = true) {
|
|
2232
|
-
|
|
2279
|
+
var _a;
|
|
2280
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
|
|
2233
2281
|
// Generate code for all scenarios
|
|
2234
2282
|
const codeGenResult = await ProboCodeGenerator.generateCodeForProject(projectId, apiToken, apiUrl);
|
|
2235
2283
|
if (codeGenResult.scenarios.length === 0) {
|
|
@@ -2283,9 +2331,12 @@ class TestSuiteRunner {
|
|
|
2283
2331
|
ensureDirectoryExists(projectDir);
|
|
2284
2332
|
const testsDir = path.join(projectDir, 'tests');
|
|
2285
2333
|
ensureDirectoryExists(testsDir);
|
|
2286
|
-
|
|
2334
|
+
const seenSlugs = new Map();
|
|
2287
2335
|
for (const scenario of codeGenResult.scenarios) {
|
|
2288
|
-
const
|
|
2336
|
+
const slug = slugify(scenario.scenarioName);
|
|
2337
|
+
const count = (_a = seenSlugs.get(slug)) !== null && _a !== void 0 ? _a : 0;
|
|
2338
|
+
seenSlugs.set(slug, count + 1);
|
|
2339
|
+
const fileName = count > 0 ? `${slug}-${scenario.scenarioId}.spec.ts` : `${slug}.spec.ts`;
|
|
2289
2340
|
const filePath = path.join(testsDir, fileName);
|
|
2290
2341
|
fs.writeFileSync(filePath, scenario.code, 'utf-8');
|
|
2291
2342
|
console.log(`โ
Generated test file: ${filePath}`);
|
|
@@ -2299,6 +2350,18 @@ class TestSuiteRunner {
|
|
|
2299
2350
|
await this.generateProboReporter(projectDir);
|
|
2300
2351
|
}
|
|
2301
2352
|
}
|
|
2353
|
+
/**
|
|
2354
|
+
* Export project code to a directory.
|
|
2355
|
+
* If outputDir is provided, creates a subfolder {outputDir}/{projectId}-{projectNameSlug}/ and writes there.
|
|
2356
|
+
* Otherwise writes to the default project dir (~/.probium/projects/...).
|
|
2357
|
+
*/
|
|
2358
|
+
static async exportProjectCode(projectId, apiToken, apiUrl, outputDir, projectName, includeReporter = false) {
|
|
2359
|
+
const targetDir = outputDir
|
|
2360
|
+
? path.join(outputDir, `${projectId}-${slugify(projectName || `project-${projectId}`)}`)
|
|
2361
|
+
: getProjectDir(projectId, projectName);
|
|
2362
|
+
await this.generateProjectFiles(projectId, apiToken, apiUrl, targetDir, projectName, includeReporter);
|
|
2363
|
+
return targetDir;
|
|
2364
|
+
}
|
|
2302
2365
|
/**
|
|
2303
2366
|
* Run all scenarios in a project
|
|
2304
2367
|
* Generates files, installs dependencies, and executes tests
|
|
@@ -2307,7 +2370,7 @@ class TestSuiteRunner {
|
|
|
2307
2370
|
static async runProjectScenarios(projectId, apiToken, apiUrl, projectName, options = {}) {
|
|
2308
2371
|
const { outputDir, includeReporter = false, // CLI mode - no custom reporter
|
|
2309
2372
|
playwrightArgs = [], onStdout, onStderr, } = options;
|
|
2310
|
-
const projectDir = outputDir ||
|
|
2373
|
+
const projectDir = outputDir || getProjectDir(projectId, projectName || `project-${projectId}`);
|
|
2311
2374
|
try {
|
|
2312
2375
|
// Generate all files
|
|
2313
2376
|
console.log(`๐ Generating test files for project ${projectId}...`);
|