codeceptjs 3.7.6-beta.3 → 3.7.6-beta.4

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.
@@ -72,11 +72,11 @@ class JSONResponse extends Helper {
72
72
  if (!this.helpers[this.options.requestHelper]) {
73
73
  throw new Error(`Error setting JSONResponse, helper ${this.options.requestHelper} is not enabled in config, helpers: ${Object.keys(this.helpers)}`)
74
74
  }
75
- const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse;
75
+ const origOnResponse = this.helpers[this.options.requestHelper].config.onResponse
76
76
  this.helpers[this.options.requestHelper].config.onResponse = response => {
77
- this.response = response;
78
- if (typeof origOnResponse === 'function') origOnResponse(response);
79
- };
77
+ this.response = response
78
+ if (typeof origOnResponse === 'function') origOnResponse(response)
79
+ }
80
80
  }
81
81
 
82
82
  _before() {
@@ -998,7 +998,7 @@ class WebDriver extends Helper {
998
998
  * {{ react }}
999
999
  */
1000
1000
  async click(locator, context = null) {
1001
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1001
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1002
1002
  const locateFn = prepareLocateFn.call(this, context)
1003
1003
 
1004
1004
  const res = await findClickable.call(this, locator, locateFn)
@@ -1217,7 +1217,7 @@ class WebDriver extends Helper {
1217
1217
  * {{> checkOption }}
1218
1218
  */
1219
1219
  async checkOption(field, context = null) {
1220
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1220
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1221
1221
  const locateFn = prepareLocateFn.call(this, context)
1222
1222
 
1223
1223
  const res = await findCheckable.call(this, field, locateFn)
@@ -1237,7 +1237,7 @@ class WebDriver extends Helper {
1237
1237
  * {{> uncheckOption }}
1238
1238
  */
1239
1239
  async uncheckOption(field, context = null) {
1240
- const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1240
+ const clickMethod = this.browser.isMobile && this.browser.capabilities.platformName !== 'android' ? 'touchClick' : 'elementClick'
1241
1241
  const locateFn = prepareLocateFn.call(this, context)
1242
1242
 
1243
1243
  const res = await findCheckable.call(this, field, locateFn)
@@ -79,14 +79,14 @@ module.exports = function () {
79
79
  return currentHook.steps.push(step)
80
80
  }
81
81
  if (!currentTest || !currentTest.steps) return
82
-
82
+
83
83
  // Check if we're in a session that should be excluded from main test steps
84
84
  const currentSessionId = recorder.getCurrentSessionId()
85
85
  if (currentSessionId && EXCLUDED_SESSIONS.includes(currentSessionId)) {
86
86
  // Skip adding this step to the main test steps
87
87
  return
88
88
  }
89
-
89
+
90
90
  currentTest.steps.push(step)
91
91
  })
92
92
 
@@ -1,3 +1,7 @@
1
+ // @ts-nocheck
2
+ // TypeScript: Import Node.js types for process, fs, path, etc.
3
+ /// <reference types="node" />
4
+
1
5
  const fs = require('fs')
2
6
  const path = require('path')
3
7
  const mkdirp = require('mkdirp')
@@ -11,7 +15,7 @@ const output = require('../output')
11
15
  const Codecept = require('../codecept')
12
16
 
13
17
  const defaultConfig = {
14
- output: global.output_dir || './output',
18
+ output: typeof global !== 'undefined' && global.output_dir ? global.output_dir : './output',
15
19
  reportFileName: 'report.html',
16
20
  includeArtifacts: true,
17
21
  showSteps: true,
@@ -60,6 +64,9 @@ const defaultConfig = {
60
64
  */
61
65
  module.exports = function (config) {
62
66
  const options = { ...defaultConfig, ...config }
67
+ /**
68
+ * TypeScript: Explicitly type reportData arrays as any[] to avoid 'never' errors
69
+ */
63
70
  let reportData = {
64
71
  stats: {},
65
72
  tests: [],
@@ -82,8 +89,8 @@ module.exports = function (config) {
82
89
 
83
90
  // Track overall test execution
84
91
  event.dispatcher.on(event.all.before, () => {
85
- reportData.startTime = new Date()
86
- output.plugin('htmlReporter', 'Starting HTML report generation...')
92
+ reportData.startTime = new Date().toISOString()
93
+ output.print('HTML Reporter: Starting HTML report generation...')
87
94
  })
88
95
 
89
96
  // Track test start to initialize steps and hooks collection
@@ -138,10 +145,30 @@ module.exports = function (config) {
138
145
  if (step.htmlReporterStartTime) {
139
146
  step.duration = Date.now() - step.htmlReporterStartTime
140
147
  }
148
+
149
+ // Serialize args immediately to preserve them through worker serialization
150
+ let serializedArgs = []
151
+ if (step.args && Array.isArray(step.args)) {
152
+ serializedArgs = step.args.map(arg => {
153
+ try {
154
+ // Try to convert to JSON-friendly format
155
+ if (typeof arg === 'string') return arg
156
+ if (typeof arg === 'number') return arg
157
+ if (typeof arg === 'boolean') return arg
158
+ if (arg === null || arg === undefined) return arg
159
+ // For objects, try to serialize them
160
+ return JSON.parse(JSON.stringify(arg))
161
+ } catch (e) {
162
+ // If serialization fails, convert to string
163
+ return String(arg)
164
+ }
165
+ })
166
+ }
167
+
141
168
  currentTestSteps.push({
142
169
  name: step.name,
143
170
  actor: step.actor,
144
- args: step.args || [],
171
+ args: serializedArgs,
145
172
  status: step.failed ? 'failed' : 'success',
146
173
  duration: step.duration || 0,
147
174
  })
@@ -211,19 +238,25 @@ module.exports = function (config) {
211
238
  // Debug logging
212
239
  output.debug(`HTML Reporter: Test finished - ${test.title}, State: ${test.state}, Retries: ${retryAttempts}`)
213
240
 
214
- // Detect if this is a BDD/Gherkin test
215
- const isBddTest = isBddGherkinTest(test, currentSuite)
241
+ // Detect if this is a BDD/Gherkin test - use test.parent directly instead of currentSuite
242
+ const suite = test.parent || test.suite || currentSuite
243
+ const isBddTest = isBddGherkinTest(test, suite)
216
244
  const steps = isBddTest ? currentBddSteps : currentTestSteps
217
- const featureInfo = isBddTest ? getBddFeatureInfo(test, currentSuite) : null
245
+ const featureInfo = isBddTest ? getBddFeatureInfo(test, suite) : null
218
246
 
219
247
  // Check if this test already exists in reportData.tests (from a previous retry)
220
248
  const existingTestIndex = reportData.tests.findIndex(t => t.id === testId)
221
- const hasFailedBefore = existingTestIndex >= 0 && reportData.tests[existingTestIndex].state === 'failed'
249
+ const hasFailedBefore = existingTestIndex >= 0 && reportData.tests[existingTestIndex] && reportData.tests[existingTestIndex].state === 'failed'
222
250
  const currentlyFailed = test.state === 'failed'
223
251
 
224
252
  // Debug artifacts collection (but don't process them yet - screenshots may not be ready)
225
253
  output.debug(`HTML Reporter: Test ${test.title} artifacts at test.finished: ${JSON.stringify(test.artifacts)}`)
226
254
 
255
+ // Extract parent/suite title before serialization (for worker mode)
256
+ // This ensures the feature name is preserved when test data is JSON stringified
257
+ const parentTitle = test.parent?.title || test.suite?.title || (suite && suite.title) || null
258
+ const suiteTitle = test.suite?.title || (suite && suite.title) || null
259
+
227
260
  const testData = {
228
261
  ...test,
229
262
  id: testId,
@@ -239,11 +272,14 @@ module.exports = function (config) {
239
272
  uid: test.uid,
240
273
  isBdd: isBddTest,
241
274
  feature: featureInfo,
275
+ // Store parent/suite titles as simple strings for worker mode serialization
276
+ parentTitle: parentTitle,
277
+ suiteTitle: suiteTitle,
242
278
  }
243
279
 
244
280
  if (existingTestIndex >= 0) {
245
281
  // Update existing test with final result (including failed state)
246
- reportData.tests[existingTestIndex] = testData
282
+ if (existingTestIndex >= 0) reportData.tests[existingTestIndex] = testData
247
283
  output.debug(`HTML Reporter: Updated existing test - ${test.title}, Final state: ${test.state}`)
248
284
  } else {
249
285
  // Add new test
@@ -288,14 +324,14 @@ module.exports = function (config) {
288
324
  finalState: test.state,
289
325
  duration: test.duration || 0,
290
326
  })
291
- output.debug(`HTML Reporter: Fallback retry detection for failed test ${test.title}, attempts: ${fallbackAttempts}`)
327
+ output.debug(`HTML Reporter: Fallback retry detection for failed test ${test.title}, attempts: ${fallbackAttempts}`)
292
328
  }
293
329
  })
294
330
 
295
331
  // Generate final report
296
- event.dispatcher.on(event.all.result, result => {
297
- reportData.endTime = new Date()
298
- reportData.duration = reportData.endTime - reportData.startTime
332
+ event.dispatcher.on(event.all.result, async result => {
333
+ reportData.endTime = new Date().toISOString()
334
+ reportData.duration = new Date(reportData.endTime).getTime() - new Date(reportData.startTime).getTime()
299
335
 
300
336
  // Process artifacts now that all async tasks (including screenshots) are complete
301
337
  output.debug(`HTML Reporter: Processing artifacts for ${reportData.tests.length} tests after all async tasks complete`)
@@ -345,7 +381,11 @@ module.exports = function (config) {
345
381
  .filter(t => t.state === 'failed')
346
382
  .map(t => {
347
383
  const testName = t.title || 'Unknown Test'
348
- const featureName = t.parent?.title || 'Unknown Feature'
384
+ // Try to get feature name from BDD, preserved titles (worker mode), or direct access
385
+ let featureName = t.feature?.name || t.parentTitle || t.suiteTitle || t.parent?.title || t.suite?.title || 'Unknown Feature'
386
+ if (featureName === 'Unknown Feature' && t.suite && t.suite.feature && t.suite.feature.name) {
387
+ featureName = t.suite.feature.name
388
+ }
349
389
 
350
390
  if (t.err) {
351
391
  const errorMessage = t.err.message || t.err.toString() || 'Test failed'
@@ -410,15 +450,20 @@ module.exports = function (config) {
410
450
  // Always overwrite the file with the latest complete data from this worker
411
451
  // This prevents double-counting when the event is triggered multiple times
412
452
  fs.writeFileSync(jsonPath, safeJsonStringify(reportData))
413
- output.print(`HTML Reporter: Generated worker JSON results: ${jsonFileName}`)
453
+ output.debug(`HTML Reporter: Generated worker JSON results: ${jsonFileName}`)
414
454
  } catch (error) {
415
- output.print(`HTML Reporter: Failed to write worker JSON: ${error.message}`)
455
+ output.debug(`HTML Reporter: Failed to write worker JSON: ${error.message}`)
416
456
  }
417
457
  return
418
458
  }
419
459
 
420
460
  // Single process mode - generate report normally
421
- generateHtmlReport(reportData, options)
461
+ try {
462
+ await generateHtmlReport(reportData, options)
463
+ } catch (error) {
464
+ output.print(`Failed to generate HTML report: ${error.message}`)
465
+ output.debug(`HTML Reporter error stack: ${error.stack}`)
466
+ }
422
467
 
423
468
  // Export stats if configured
424
469
  if (options.exportStats) {
@@ -542,8 +587,8 @@ module.exports = function (config) {
542
587
  const testName = replicateTestToFileName(originalTestName)
543
588
  const featureName = replicateTestToFileName(originalFeatureName)
544
589
 
545
- output.print(`HTML Reporter: Original test title: "${originalTestName}"`)
546
- output.print(`HTML Reporter: CodeceptJS filename: "${testName}"`)
590
+ output.debug(`HTML Reporter: Original test title: "${originalTestName}"`)
591
+ output.debug(`HTML Reporter: CodeceptJS filename: "${testName}"`)
547
592
 
548
593
  // Generate possible screenshot names based on CodeceptJS patterns
549
594
  const possibleNames = [
@@ -567,19 +612,19 @@ module.exports = function (config) {
567
612
  'failure.jpg',
568
613
  ]
569
614
 
570
- output.print(`HTML Reporter: Checking ${possibleNames.length} possible screenshot names for "${testName}"`)
615
+ output.debug(`HTML Reporter: Checking ${possibleNames.length} possible screenshot names for "${testName}"`)
571
616
 
572
617
  // Search for screenshots in possible directories
573
618
  for (const dir of possibleDirs) {
574
- output.print(`HTML Reporter: Checking directory: ${dir}`)
619
+ output.debug(`HTML Reporter: Checking directory: ${dir}`)
575
620
  if (!fs.existsSync(dir)) {
576
- output.print(`HTML Reporter: Directory does not exist: ${dir}`)
621
+ output.debug(`HTML Reporter: Directory does not exist: ${dir}`)
577
622
  continue
578
623
  }
579
624
 
580
625
  try {
581
626
  const files = fs.readdirSync(dir)
582
- output.print(`HTML Reporter: Found ${files.length} files in ${dir}`)
627
+ output.debug(`HTML Reporter: Found ${files.length} files in ${dir}`)
583
628
 
584
629
  // Look for exact matches first
585
630
  for (const name of possibleNames) {
@@ -587,7 +632,7 @@ module.exports = function (config) {
587
632
  const fullPath = path.join(dir, name)
588
633
  if (!screenshots.includes(fullPath)) {
589
634
  screenshots.push(fullPath)
590
- output.print(`HTML Reporter: Found screenshot: ${fullPath}`)
635
+ output.debug(`HTML Reporter: Found screenshot: ${fullPath}`)
591
636
  }
592
637
  }
593
638
  }
@@ -618,16 +663,16 @@ module.exports = function (config) {
618
663
  const fullPath = path.join(dir, file)
619
664
  if (!screenshots.includes(fullPath)) {
620
665
  screenshots.push(fullPath)
621
- output.print(`HTML Reporter: Found related screenshot: ${fullPath}`)
666
+ output.debug(`HTML Reporter: Found related screenshot: ${fullPath}`)
622
667
  }
623
668
  }
624
669
  } catch (error) {
625
670
  // Ignore directory read errors
626
- output.print(`HTML Reporter: Could not read directory ${dir}: ${error.message}`)
671
+ output.debug(`HTML Reporter: Could not read directory ${dir}: ${error.message}`)
627
672
  }
628
673
  }
629
674
  } catch (error) {
630
- output.print(`HTML Reporter: Error collecting screenshots: ${error.message}`)
675
+ output.debug(`HTML Reporter: Error collecting screenshots: ${error.message}`)
631
676
  }
632
677
 
633
678
  return screenshots
@@ -654,7 +699,7 @@ module.exports = function (config) {
654
699
  const statsPath = path.resolve(reportDir, config.exportStatsPath)
655
700
 
656
701
  const exportData = {
657
- timestamp: data.endTime.toISOString(),
702
+ timestamp: data.endTime, // Already an ISO string
658
703
  duration: data.duration,
659
704
  stats: data.stats,
660
705
  retries: data.retries,
@@ -698,7 +743,7 @@ module.exports = function (config) {
698
743
 
699
744
  // Add current run to history
700
745
  history.unshift({
701
- timestamp: data.endTime.toISOString(),
746
+ timestamp: data.endTime, // Already an ISO string
702
747
  duration: data.duration,
703
748
  stats: data.stats,
704
749
  retries: data.retries.length,
@@ -725,11 +770,11 @@ module.exports = function (config) {
725
770
  const jsonFiles = fs.readdirSync(reportDir).filter(file => file.startsWith('worker-') && file.endsWith('-results.json'))
726
771
 
727
772
  if (jsonFiles.length === 0) {
728
- output.print('HTML Reporter: No worker JSON results found to consolidate')
773
+ output.debug('HTML Reporter: No worker JSON results found to consolidate')
729
774
  return
730
775
  }
731
776
 
732
- output.print(`HTML Reporter: Found ${jsonFiles.length} worker JSON files to consolidate`)
777
+ output.debug(`HTML Reporter: Found ${jsonFiles.length} worker JSON files to consolidate`)
733
778
 
734
779
  // Initialize consolidated data structure
735
780
  const consolidatedData = {
@@ -821,9 +866,9 @@ module.exports = function (config) {
821
866
  saveTestHistory(consolidatedData, config)
822
867
  }
823
868
 
824
- output.print(`HTML Reporter: Successfully consolidated ${jsonFiles.length} worker reports`)
869
+ output.debug(`HTML Reporter: Successfully consolidated ${jsonFiles.length} worker reports`)
825
870
  } catch (error) {
826
- output.print(`HTML Reporter: Failed to consolidate worker reports: ${error.message}`)
871
+ output.debug(`HTML Reporter: Failed to consolidate worker reports: ${error.message}`)
827
872
  }
828
873
  }
829
874
 
@@ -844,7 +889,7 @@ module.exports = function (config) {
844
889
 
845
890
  // Add current run to history for chart display (before saving to file)
846
891
  const currentRun = {
847
- timestamp: data.endTime.toISOString(),
892
+ timestamp: data.endTime, // Already an ISO string
848
893
  duration: data.duration,
849
894
  stats: data.stats,
850
895
  retries: data.retries.length,
@@ -863,7 +908,7 @@ module.exports = function (config) {
863
908
 
864
909
  const html = template(getHtmlTemplate(), {
865
910
  title: `CodeceptJS Test Report v${Codecept.version()}`,
866
- timestamp: data.endTime.toISOString(),
911
+ timestamp: data.endTime, // Already an ISO string
867
912
  duration: formatDuration(data.duration),
868
913
  stats: JSON.stringify(data.stats),
869
914
  history: JSON.stringify(history),
@@ -929,7 +974,9 @@ module.exports = function (config) {
929
974
  return tests
930
975
  .map(test => {
931
976
  const statusClass = test.state || 'unknown'
932
- const feature = test.isBdd && test.feature ? test.feature.name : test.parent?.title || 'Unknown Feature'
977
+ // Use preserved parent/suite titles (for worker mode) or fallback to direct access
978
+ const feature = test.isBdd && test.feature ? test.feature.name : test.parentTitle || test.suiteTitle || test.parent?.title || test.suite?.title || 'Unknown Feature'
979
+ // Always try to show steps if available, even for unknown feature
933
980
  const steps = config.showSteps && test.steps ? (test.isBdd ? generateBddStepsHtml(test.steps) : generateStepsHtml(test.steps)) : ''
934
981
  const featureDetails = test.isBdd && test.feature ? generateBddFeatureHtml(test.feature) : ''
935
982
  const hooks = test.hooks && test.hooks.length > 0 ? generateHooksHtml(test.hooks) : ''
@@ -1135,19 +1182,19 @@ module.exports = function (config) {
1135
1182
 
1136
1183
  function generateArtifactsHtml(artifacts, isFailedTest = false) {
1137
1184
  if (!artifacts || artifacts.length === 0) {
1138
- output.print(`HTML Reporter: No artifacts found for test`)
1185
+ output.debug(`HTML Reporter: No artifacts found for test`)
1139
1186
  return ''
1140
1187
  }
1141
1188
 
1142
- output.print(`HTML Reporter: Processing ${artifacts.length} artifacts, isFailedTest: ${isFailedTest}`)
1143
- output.print(`HTML Reporter: Artifacts: ${JSON.stringify(artifacts)}`)
1189
+ output.debug(`HTML Reporter: Processing ${artifacts.length} artifacts, isFailedTest: ${isFailedTest}`)
1190
+ output.debug(`HTML Reporter: Artifacts: ${JSON.stringify(artifacts)}`)
1144
1191
 
1145
1192
  // Separate screenshots from other artifacts
1146
1193
  const screenshots = []
1147
1194
  const otherArtifacts = []
1148
1195
 
1149
1196
  artifacts.forEach(artifact => {
1150
- output.print(`HTML Reporter: Processing artifact: ${artifact} (type: ${typeof artifact})`)
1197
+ output.debug(`HTML Reporter: Processing artifact: ${artifact} (type: ${typeof artifact})`)
1151
1198
 
1152
1199
  // Handle different artifact formats
1153
1200
  let artifactPath = artifact
@@ -1162,14 +1209,14 @@ module.exports = function (config) {
1162
1209
  // Check if it's a screenshot file
1163
1210
  if (typeof artifactPath === 'string' && artifactPath.match(/\.(png|jpg|jpeg|gif|webp|bmp|svg)$/i)) {
1164
1211
  screenshots.push(artifactPath)
1165
- output.print(`HTML Reporter: Found screenshot: ${artifactPath}`)
1212
+ output.debug(`HTML Reporter: Found screenshot: ${artifactPath}`)
1166
1213
  } else {
1167
1214
  otherArtifacts.push(artifact)
1168
- output.print(`HTML Reporter: Found other artifact: ${artifact}`)
1215
+ output.debug(`HTML Reporter: Found other artifact: ${artifact}`)
1169
1216
  }
1170
1217
  })
1171
1218
 
1172
- output.print(`HTML Reporter: Found ${screenshots.length} screenshots and ${otherArtifacts.length} other artifacts`)
1219
+ output.debug(`HTML Reporter: Found ${screenshots.length} screenshots and ${otherArtifacts.length} other artifacts`)
1173
1220
 
1174
1221
  let artifactsHtml = ''
1175
1222
 
@@ -1198,7 +1245,7 @@ module.exports = function (config) {
1198
1245
  }
1199
1246
  }
1200
1247
 
1201
- output.print(`HTML Reporter: Screenshot ${filename} -> ${relativePath}`)
1248
+ output.debug(`HTML Reporter: Screenshot ${filename} -> ${relativePath}`)
1202
1249
 
1203
1250
  return `
1204
1251
  <div class="screenshot-container">
@@ -1242,7 +1289,7 @@ module.exports = function (config) {
1242
1289
  }
1243
1290
  }
1244
1291
 
1245
- output.print(`HTML Reporter: Screenshot ${filename} -> ${relativePath}`)
1292
+ output.debug(`HTML Reporter: Screenshot ${filename} -> ${relativePath}`)
1246
1293
  return `<img src="${relativePath}" alt="Screenshot" class="artifact-image" onclick="openImageModal(this.src)"/>`
1247
1294
  })
1248
1295
  .join('')
@@ -1556,7 +1603,7 @@ module.exports = function (config) {
1556
1603
  <section class="history-section" style="display: {{showHistory}};">
1557
1604
  <h2>Test History</h2>
1558
1605
  <div class="history-chart-container">
1559
- <canvas id="historyChart" width="800" height="300"></canvas>
1606
+ <canvas id="historyChart" width="1600" height="600"></canvas>
1560
1607
  </div>
1561
1608
  </section>
1562
1609
 
@@ -2457,6 +2504,10 @@ body {
2457
2504
 
2458
2505
  function getJsScripts() {
2459
2506
  return `
2507
+ // Go to Top button
2508
+ function scrollToTop() {
2509
+ window.scrollTo({ top: 0, behavior: 'smooth' });
2510
+ }
2460
2511
  function toggleTestDetails(testId) {
2461
2512
  const details = document.getElementById('details-' + testId);
2462
2513
  if (details.style.display === 'none' || details.style.display === '') {
@@ -2939,9 +2990,26 @@ function drawHistoryChart() {
2939
2990
  // Initialize charts and filters
2940
2991
  document.addEventListener('DOMContentLoaded', function() {
2941
2992
 
2942
- // Draw charts
2943
- drawPieChart();
2944
- drawHistoryChart();
2993
+ // Draw charts
2994
+ drawPieChart();
2995
+ drawHistoryChart();
2996
+ // Add Go to Top button
2997
+ const goTopBtn = document.createElement('button');
2998
+ goTopBtn.innerText = '↑ Top';
2999
+ goTopBtn.id = 'goTopBtn';
3000
+ goTopBtn.style.position = 'fixed';
3001
+ goTopBtn.style.bottom = '30px';
3002
+ goTopBtn.style.right = '30px';
3003
+ goTopBtn.style.zIndex = '9999';
3004
+ goTopBtn.style.padding = '12px 18px';
3005
+ goTopBtn.style.borderRadius = '50%';
3006
+ goTopBtn.style.background = '#27ae60';
3007
+ goTopBtn.style.color = '#fff';
3008
+ goTopBtn.style.fontSize = '20px';
3009
+ goTopBtn.style.boxShadow = '0 2px 8px rgba(0,0,0,0.2)';
3010
+ goTopBtn.style.cursor = 'pointer';
3011
+ goTopBtn.onclick = scrollToTop;
3012
+ document.body.appendChild(goTopBtn);
2945
3013
 
2946
3014
  // Set up filter event listeners
2947
3015
  document.getElementById('statusFilter').addEventListener('change', applyFilters);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codeceptjs",
3
- "version": "3.7.6-beta.3",
3
+ "version": "3.7.6-beta.4",
4
4
  "description": "Supercharged End 2 End Testing Framework for NodeJS",
5
5
  "keywords": [
6
6
  "acceptance",
@@ -48,6 +48,9 @@
48
48
  "repository": "Codeception/codeceptjs",
49
49
  "scripts": {
50
50
  "test-server": "node bin/test-server.js test/data/rest/db.json --host 0.0.0.0 -p 8010",
51
+ "mock-server:start": "node test/mock-server/start-mock-server.js",
52
+ "mock-server:stop": "kill -9 $(lsof -t -i:3001)",
53
+ "test:with-mock-server": "npm run mock-server:start && npm test",
51
54
  "json-server:graphql": "node test/data/graphql/index.js",
52
55
  "lint": "eslint bin/ examples/ lib/ test/ translations/ runok.js",
53
56
  "lint-fix": "eslint bin/ examples/ lib/ test/ translations/ runok.js --fix",
@@ -138,7 +141,7 @@
138
141
  "@pollyjs/core": "6.0.6",
139
142
  "@types/chai": "5.2.2",
140
143
  "@types/inquirer": "9.0.9",
141
- "@types/node": "^24.6.0",
144
+ "@types/node": "^24.6.2",
142
145
  "@wdio/sauce-service": "9.12.5",
143
146
  "@wdio/selenium-standalone-service": "8.15.0",
144
147
  "@wdio/utils": "9.19.2",