@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.
@@ -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 testFile.tests) {
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: parseInt(options.timeout, 10),
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.0',
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 failed = results.filter(r => r.status !== 'passed').length;
53
- console.log(` ${passed} passed, ${failed} failed\n`);
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 failed = allResults.reduce((sum, r) => sum + r.results.filter(t => t.status !== 'passed').length, 0);
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
- const passRate = actualTests > 0
96
- ? ((actualPassed / actualTests) * 100).toFixed(1)
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 fileFailed = testResults.filter(r => r.status !== 'passed').length;
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 failed = results.filter(r => r.status !== 'passed').length;
53
- console.log(` ${passed} passed, ${failed} failed\n`);
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 !== 'passed' && result.error) {
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`;
@@ -18,6 +18,7 @@ export interface ReportData {
18
18
  totalTests: number;
19
19
  passed: number;
20
20
  failed: number;
21
+ skipped: number;
21
22
  duration: number;
22
23
  };
23
24
  testFiles: {