@testsmith/testblocks 0.9.1 → 0.9.3

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.
@@ -32,7 +32,10 @@ class TestExecutor {
32
32
  this.browser = await playwright_1.chromium.launch({
33
33
  headless: this.options.headless,
34
34
  });
35
- this.browserContext = await this.browser.newContext();
35
+ // Use consistent viewport size for headless and headed modes
36
+ this.browserContext = await this.browser.newContext({
37
+ viewport: { width: 1920, height: 1080 },
38
+ });
36
39
  this.page = await this.browserContext.newPage();
37
40
  if (this.options.timeout) {
38
41
  this.page.setDefaultTimeout(this.options.timeout);
@@ -164,6 +167,7 @@ class TestExecutor {
164
167
  logger: this.createLogger(),
165
168
  plugins: this.plugins,
166
169
  procedures: this.procedures,
170
+ webTimeout: this.options.timeout,
167
171
  };
168
172
  }
169
173
  async runTestWithData(test, testFile, dataSet, dataIndex, sharedVariables) {
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.1',
457
+ '@testsmith/testblocks': '^0.9.3',
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: {