@testsmith/testblocks 0.9.0 → 0.9.2
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/cli/executor.js +29 -18
- package/dist/cli/index.js +9 -2
- package/dist/cli/reporters/HTMLReporter.js +21 -8
- package/dist/cli/reporters/JUnitReporter.js +16 -8
- package/dist/cli/reporters/types.d.ts +1 -0
- package/dist/client/assets/{index-Ivy7T1Qk.js → index-CVn_B7zc.js} +71 -71
- package/dist/client/assets/{index-Ivy7T1Qk.js.map → index-CVn_B7zc.js.map} +1 -1
- package/dist/client/index.html +1 -1
- package/dist/core/blocks/playwright/assertions.js +10 -20
- package/dist/core/blocks/playwright/interactions.js +13 -15
- package/dist/core/blocks/playwright/navigation.js +2 -4
- package/dist/core/blocks/playwright/retrieval.js +3 -6
- package/dist/core/blocks/playwright/types.d.ts +4 -0
- package/dist/core/blocks/playwright/utils.d.ts +4 -0
- package/dist/core/blocks/playwright/utils.js +8 -0
- package/dist/core/types.d.ts +1 -0
- package/dist/server/executor.js +30 -17
- package/dist/server/globals.d.ts +4 -0
- package/dist/server/globals.js +7 -0
- package/dist/server/index.js +4 -2
- package/dist/server/startServer.js +6 -4
- package/package.json +1 -1
package/dist/cli/executor.js
CHANGED
|
@@ -93,29 +93,39 @@ class TestExecutor {
|
|
|
93
93
|
}
|
|
94
94
|
// Create base context for hooks
|
|
95
95
|
const baseContext = this.createBaseContext(testFile.variables);
|
|
96
|
+
// Check if there are any enabled tests
|
|
97
|
+
const enabledTests = testFile.tests.filter(t => !t.disabled);
|
|
98
|
+
const hasEnabledTests = enabledTests.length > 0;
|
|
99
|
+
// Add skipped results for disabled tests first
|
|
100
|
+
for (const test of testFile.tests) {
|
|
101
|
+
if (test.disabled) {
|
|
102
|
+
console.log(` Skipping (disabled): ${test.name}`);
|
|
103
|
+
results.push({
|
|
104
|
+
testId: test.id,
|
|
105
|
+
testName: test.name,
|
|
106
|
+
status: 'skipped',
|
|
107
|
+
duration: 0,
|
|
108
|
+
steps: [],
|
|
109
|
+
error: { message: 'Test is disabled' },
|
|
110
|
+
startedAt: new Date().toISOString(),
|
|
111
|
+
finishedAt: new Date().toISOString(),
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
// Only run hooks and tests if there are enabled tests
|
|
116
|
+
if (!hasEnabledTests) {
|
|
117
|
+
console.log(' All tests disabled, skipping hooks');
|
|
118
|
+
await this.cleanup();
|
|
119
|
+
return results;
|
|
120
|
+
}
|
|
96
121
|
try {
|
|
97
122
|
// Run beforeAll hooks
|
|
98
123
|
if (testFile.beforeAll) {
|
|
99
124
|
const steps = this.extractStepsFromBlocklyState(testFile.beforeAll);
|
|
100
125
|
await this.runSteps(steps, baseContext, 'beforeAll');
|
|
101
126
|
}
|
|
102
|
-
// Run each test - pass baseContext variables so beforeAll state persists
|
|
103
|
-
for (const test of
|
|
104
|
-
// Skip disabled tests
|
|
105
|
-
if (test.disabled) {
|
|
106
|
-
console.log(` Skipping (disabled): ${test.name}`);
|
|
107
|
-
results.push({
|
|
108
|
-
testId: test.id,
|
|
109
|
-
testName: test.name,
|
|
110
|
-
status: 'skipped',
|
|
111
|
-
duration: 0,
|
|
112
|
-
steps: [],
|
|
113
|
-
error: { message: 'Test is disabled' },
|
|
114
|
-
startedAt: new Date().toISOString(),
|
|
115
|
-
finishedAt: new Date().toISOString(),
|
|
116
|
-
});
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
127
|
+
// Run each enabled test - pass baseContext variables so beforeAll state persists
|
|
128
|
+
for (const test of enabledTests) {
|
|
119
129
|
// Check if test has data-driven sets
|
|
120
130
|
if (test.data && test.data.length > 0) {
|
|
121
131
|
// Run test for each data set
|
|
@@ -154,6 +164,7 @@ class TestExecutor {
|
|
|
154
164
|
logger: this.createLogger(),
|
|
155
165
|
plugins: this.plugins,
|
|
156
166
|
procedures: this.procedures,
|
|
167
|
+
webTimeout: this.options.timeout,
|
|
157
168
|
};
|
|
158
169
|
}
|
|
159
170
|
async runTestWithData(test, testFile, dataSet, dataIndex, sharedVariables) {
|
|
@@ -563,7 +574,7 @@ class TestExecutor {
|
|
|
563
574
|
let screenshot;
|
|
564
575
|
if (status === 'failed' && this.page) {
|
|
565
576
|
try {
|
|
566
|
-
const buffer = await this.page.screenshot({ type: 'png' });
|
|
577
|
+
const buffer = await this.page.screenshot({ type: 'png', fullPage: true });
|
|
567
578
|
screenshot = `data:image/png;base64,${buffer.toString('base64')}`;
|
|
568
579
|
}
|
|
569
580
|
catch (screenshotError) {
|
package/dist/cli/index.js
CHANGED
|
@@ -208,6 +208,7 @@ program
|
|
|
208
208
|
const testDir = path.dirname(files[0]);
|
|
209
209
|
globalsPath = findGlobalsFile(testDir) || globalsPath;
|
|
210
210
|
}
|
|
211
|
+
let globalTimeout;
|
|
211
212
|
if (fs.existsSync(globalsPath)) {
|
|
212
213
|
try {
|
|
213
214
|
const globalsContent = fs.readFileSync(globalsPath, 'utf-8');
|
|
@@ -218,6 +219,9 @@ program
|
|
|
218
219
|
if (globals.procedures && typeof globals.procedures === 'object') {
|
|
219
220
|
globalProcedures = globals.procedures;
|
|
220
221
|
}
|
|
222
|
+
if (typeof globals.timeout === 'number') {
|
|
223
|
+
globalTimeout = globals.timeout;
|
|
224
|
+
}
|
|
221
225
|
}
|
|
222
226
|
catch (e) {
|
|
223
227
|
console.warn(`Warning: Could not load globals from ${globalsPath}: ${e.message}`);
|
|
@@ -271,10 +275,13 @@ program
|
|
|
271
275
|
if (options.baseUrl) {
|
|
272
276
|
variables.baseUrl = options.baseUrl;
|
|
273
277
|
}
|
|
278
|
+
// Determine timeout: CLI option takes precedence, then globals.json, then default
|
|
279
|
+
const cliTimeout = parseInt(options.timeout, 10);
|
|
280
|
+
const effectiveTimeout = options.timeout !== '30000' ? cliTimeout : (globalTimeout ?? cliTimeout);
|
|
274
281
|
// Create executor options
|
|
275
282
|
const executorOptions = {
|
|
276
283
|
headless: !options.headed,
|
|
277
|
-
timeout:
|
|
284
|
+
timeout: effectiveTimeout,
|
|
278
285
|
baseUrl: options.baseUrl,
|
|
279
286
|
variables,
|
|
280
287
|
procedures: globalProcedures,
|
|
@@ -447,7 +454,7 @@ program
|
|
|
447
454
|
'test:ci': 'testblocks run tests/**/*.testblocks.json -r console,html,junit -o reports',
|
|
448
455
|
},
|
|
449
456
|
devDependencies: {
|
|
450
|
-
'@testsmith/testblocks': '^0.9.
|
|
457
|
+
'@testsmith/testblocks': '^0.9.2',
|
|
451
458
|
},
|
|
452
459
|
};
|
|
453
460
|
fs.writeFileSync(packagePath, JSON.stringify(packageJson, null, 2));
|
|
@@ -49,8 +49,9 @@ class HTMLReporter {
|
|
|
49
49
|
onTestFileComplete(file, testFile, results) {
|
|
50
50
|
this.allResults.push({ file, testFile, results });
|
|
51
51
|
const passed = results.filter(r => r.status === 'passed').length;
|
|
52
|
-
const
|
|
53
|
-
|
|
52
|
+
const skipped = results.filter(r => r.status === 'skipped').length;
|
|
53
|
+
const failed = results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length;
|
|
54
|
+
console.log(` ${passed} passed, ${failed} failed, ${skipped} skipped\n`);
|
|
54
55
|
}
|
|
55
56
|
onComplete(allResults) {
|
|
56
57
|
if (!fs.existsSync(this.outputDir)) {
|
|
@@ -60,11 +61,12 @@ class HTMLReporter {
|
|
|
60
61
|
const outputPath = path.join(this.outputDir, `report-${timestamp}.html`);
|
|
61
62
|
const totalTests = allResults.reduce((sum, r) => sum + r.results.length, 0);
|
|
62
63
|
const passed = allResults.reduce((sum, r) => sum + r.results.filter(t => t.status === 'passed').length, 0);
|
|
63
|
-
const
|
|
64
|
+
const skipped = allResults.reduce((sum, r) => sum + r.results.filter(t => t.status === 'skipped').length, 0);
|
|
65
|
+
const failed = allResults.reduce((sum, r) => sum + r.results.filter(t => t.status !== 'passed' && t.status !== 'skipped').length, 0);
|
|
64
66
|
const totalDuration = allResults.reduce((sum, r) => sum + r.results.reduce((s, t) => s + t.duration, 0), 0);
|
|
65
67
|
const html = generateHTMLReport({
|
|
66
68
|
timestamp: new Date().toISOString(),
|
|
67
|
-
summary: { totalTests, passed, failed, duration: totalDuration },
|
|
69
|
+
summary: { totalTests, passed, failed, skipped, duration: totalDuration },
|
|
68
70
|
testFiles: this.allResults,
|
|
69
71
|
});
|
|
70
72
|
fs.writeFileSync(outputPath, html);
|
|
@@ -81,19 +83,24 @@ function generateHTMLReport(data) {
|
|
|
81
83
|
let actualTests = 0;
|
|
82
84
|
let actualPassed = 0;
|
|
83
85
|
let actualFailed = 0;
|
|
86
|
+
let actualSkipped = 0;
|
|
84
87
|
for (const { results } of testFiles) {
|
|
85
88
|
for (const result of results) {
|
|
86
89
|
if (!result.isLifecycle) {
|
|
87
90
|
actualTests++;
|
|
88
91
|
if (result.status === 'passed')
|
|
89
92
|
actualPassed++;
|
|
93
|
+
else if (result.status === 'skipped')
|
|
94
|
+
actualSkipped++;
|
|
90
95
|
else
|
|
91
96
|
actualFailed++;
|
|
92
97
|
}
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
// Pass rate excludes skipped tests from calculation
|
|
101
|
+
const runTests = actualTests - actualSkipped;
|
|
102
|
+
const passRate = runTests > 0
|
|
103
|
+
? ((actualPassed / runTests) * 100).toFixed(1)
|
|
97
104
|
: '0';
|
|
98
105
|
let html = `<!DOCTYPE html>
|
|
99
106
|
<html lang="en">
|
|
@@ -123,6 +130,10 @@ ${getHTMLStyles()}
|
|
|
123
130
|
<div class="value">${actualFailed}</div>
|
|
124
131
|
<div class="label">Failed</div>
|
|
125
132
|
</div>
|
|
133
|
+
<div class="summary-card skipped">
|
|
134
|
+
<div class="value">${actualSkipped}</div>
|
|
135
|
+
<div class="label">Skipped</div>
|
|
136
|
+
</div>
|
|
126
137
|
<div class="summary-card">
|
|
127
138
|
<div class="value">${passRate}%</div>
|
|
128
139
|
<div class="label">Pass Rate</div>
|
|
@@ -150,12 +161,13 @@ function renderTestFile(file, testFile, results) {
|
|
|
150
161
|
const lifecycleResults = results.filter(r => r.isLifecycle);
|
|
151
162
|
const testResults = results.filter(r => !r.isLifecycle);
|
|
152
163
|
const filePassed = testResults.filter(r => r.status === 'passed').length;
|
|
153
|
-
const
|
|
164
|
+
const fileSkipped = testResults.filter(r => r.status === 'skipped').length;
|
|
165
|
+
const fileFailed = testResults.filter(r => r.status !== 'passed' && r.status !== 'skipped').length;
|
|
154
166
|
let html = `
|
|
155
167
|
<div class="test-file">
|
|
156
168
|
<div class="test-file-header">
|
|
157
169
|
<span>${(0, utils_1.escapeHtml)(testFile.name)}</span>
|
|
158
|
-
<span class="test-file-path">${(0, utils_1.escapeHtml)(file)} • ${filePassed} passed, ${fileFailed} failed</span>
|
|
170
|
+
<span class="test-file-path">${(0, utils_1.escapeHtml)(file)} • ${filePassed} passed, ${fileFailed} failed, ${fileSkipped} skipped</span>
|
|
159
171
|
</div>
|
|
160
172
|
`;
|
|
161
173
|
// Render lifecycle hooks (beforeAll)
|
|
@@ -336,6 +348,7 @@ function getHTMLStyles() {
|
|
|
336
348
|
.summary-card .label { font-size: 14px; color: var(--color-text-secondary); }
|
|
337
349
|
.summary-card.passed .value { color: var(--color-passed); }
|
|
338
350
|
.summary-card.failed .value { color: var(--color-failed); }
|
|
351
|
+
.summary-card.skipped .value { color: var(--color-skipped); }
|
|
339
352
|
.test-file {
|
|
340
353
|
background: var(--color-surface);
|
|
341
354
|
border: 1px solid var(--color-border);
|
|
@@ -49,8 +49,9 @@ class JUnitReporter {
|
|
|
49
49
|
onTestFileComplete(file, testFile, results) {
|
|
50
50
|
this.allResults.push({ file, testFile, results });
|
|
51
51
|
const passed = results.filter(r => r.status === 'passed').length;
|
|
52
|
-
const
|
|
53
|
-
|
|
52
|
+
const skipped = results.filter(r => r.status === 'skipped').length;
|
|
53
|
+
const failed = results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length;
|
|
54
|
+
console.log(` ${passed} passed, ${failed} failed, ${skipped} skipped\n`);
|
|
54
55
|
}
|
|
55
56
|
onComplete(allResults) {
|
|
56
57
|
if (!fs.existsSync(this.outputDir)) {
|
|
@@ -63,7 +64,8 @@ class JUnitReporter {
|
|
|
63
64
|
summary: {
|
|
64
65
|
totalTests: allResults.reduce((sum, r) => sum + r.results.length, 0),
|
|
65
66
|
passed: allResults.reduce((sum, r) => sum + r.results.filter(t => t.status === 'passed').length, 0),
|
|
66
|
-
failed: allResults.reduce((sum, r) => sum + r.results.filter(t => t.status !== 'passed').length, 0),
|
|
67
|
+
failed: allResults.reduce((sum, r) => sum + r.results.filter(t => t.status !== 'passed' && t.status !== 'skipped').length, 0),
|
|
68
|
+
skipped: allResults.reduce((sum, r) => sum + r.results.filter(t => t.status === 'skipped').length, 0),
|
|
67
69
|
duration: allResults.reduce((sum, r) => sum + r.results.reduce((s, t) => s + t.duration, 0), 0),
|
|
68
70
|
},
|
|
69
71
|
testFiles: this.allResults,
|
|
@@ -79,19 +81,25 @@ exports.JUnitReporter = JUnitReporter;
|
|
|
79
81
|
function generateJUnitXML(data) {
|
|
80
82
|
const { testFiles } = data;
|
|
81
83
|
const totalTests = testFiles.reduce((sum, f) => sum + f.results.length, 0);
|
|
82
|
-
const failures = testFiles.reduce((sum, f) => sum + f.results.filter(t => t.status !== 'passed').length, 0);
|
|
84
|
+
const failures = testFiles.reduce((sum, f) => sum + f.results.filter(t => t.status !== 'passed' && t.status !== 'skipped').length, 0);
|
|
85
|
+
const skipped = testFiles.reduce((sum, f) => sum + f.results.filter(t => t.status === 'skipped').length, 0);
|
|
83
86
|
const totalTime = testFiles.reduce((sum, f) => sum + f.results.reduce((s, t) => s + t.duration, 0), 0) / 1000;
|
|
84
87
|
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
|
|
85
|
-
xml += `<testsuites tests="${totalTests}" failures="${failures}" time="${totalTime.toFixed(3)}">\n`;
|
|
88
|
+
xml += `<testsuites tests="${totalTests}" failures="${failures}" skipped="${skipped}" time="${totalTime.toFixed(3)}">\n`;
|
|
86
89
|
for (const { file, testFile, results } of testFiles) {
|
|
87
90
|
const suiteTests = results.length;
|
|
88
|
-
const suiteFailures = results.filter(r => r.status !== 'passed').length;
|
|
91
|
+
const suiteFailures = results.filter(r => r.status !== 'passed' && r.status !== 'skipped').length;
|
|
92
|
+
const suiteSkipped = results.filter(r => r.status === 'skipped').length;
|
|
89
93
|
const suiteTime = results.reduce((s, t) => s + t.duration, 0) / 1000;
|
|
90
|
-
xml += ` <testsuite name="${(0, utils_1.escapeXml)(testFile.name)}" tests="${suiteTests}" failures="${suiteFailures}" time="${suiteTime.toFixed(3)}" file="${(0, utils_1.escapeXml)(file)}">\n`;
|
|
94
|
+
xml += ` <testsuite name="${(0, utils_1.escapeXml)(testFile.name)}" tests="${suiteTests}" failures="${suiteFailures}" skipped="${suiteSkipped}" time="${suiteTime.toFixed(3)}" file="${(0, utils_1.escapeXml)(file)}">\n`;
|
|
91
95
|
for (const result of results) {
|
|
92
96
|
const testTime = result.duration / 1000;
|
|
93
97
|
xml += ` <testcase name="${(0, utils_1.escapeXml)(result.testName)}" classname="${(0, utils_1.escapeXml)(testFile.name)}" time="${testTime.toFixed(3)}">\n`;
|
|
94
|
-
if (result.status
|
|
98
|
+
if (result.status === 'skipped') {
|
|
99
|
+
const message = result.error?.message || 'Test skipped';
|
|
100
|
+
xml += ` <skipped message="${(0, utils_1.escapeXml)(message)}"/>\n`;
|
|
101
|
+
}
|
|
102
|
+
else if (result.status !== 'passed' && result.error) {
|
|
95
103
|
xml += ` <failure message="${(0, utils_1.escapeXml)(result.error.message)}">\n`;
|
|
96
104
|
xml += `${(0, utils_1.escapeXml)(result.error.stack || result.error.message)}\n`;
|
|
97
105
|
xml += ` </failure>\n`;
|