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.
- package/lib/helper/JSONResponse.js +4 -4
- package/lib/helper/WebDriver.js +3 -3
- package/lib/listener/steps.js +2 -2
- package/lib/plugin/htmlReporter.js +117 -49
- package/package.json +5 -2
|
@@ -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() {
|
package/lib/helper/WebDriver.js
CHANGED
|
@@ -998,7 +998,7 @@ class WebDriver extends Helper {
|
|
|
998
998
|
* {{ react }}
|
|
999
999
|
*/
|
|
1000
1000
|
async click(locator, context = null) {
|
|
1001
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
package/lib/listener/steps.js
CHANGED
|
@@ -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
|
|
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.
|
|
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:
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
453
|
+
output.debug(`HTML Reporter: Generated worker JSON results: ${jsonFileName}`)
|
|
414
454
|
} catch (error) {
|
|
415
|
-
output.
|
|
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
|
-
|
|
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.
|
|
546
|
-
output.
|
|
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.
|
|
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.
|
|
619
|
+
output.debug(`HTML Reporter: Checking directory: ${dir}`)
|
|
575
620
|
if (!fs.existsSync(dir)) {
|
|
576
|
-
output.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
671
|
+
output.debug(`HTML Reporter: Could not read directory ${dir}: ${error.message}`)
|
|
627
672
|
}
|
|
628
673
|
}
|
|
629
674
|
} catch (error) {
|
|
630
|
-
output.
|
|
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
|
|
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
|
|
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.
|
|
773
|
+
output.debug('HTML Reporter: No worker JSON results found to consolidate')
|
|
729
774
|
return
|
|
730
775
|
}
|
|
731
776
|
|
|
732
|
-
output.
|
|
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.
|
|
869
|
+
output.debug(`HTML Reporter: Successfully consolidated ${jsonFiles.length} worker reports`)
|
|
825
870
|
} catch (error) {
|
|
826
|
-
output.
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
1185
|
+
output.debug(`HTML Reporter: No artifacts found for test`)
|
|
1139
1186
|
return ''
|
|
1140
1187
|
}
|
|
1141
1188
|
|
|
1142
|
-
output.
|
|
1143
|
-
output.
|
|
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.
|
|
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.
|
|
1212
|
+
output.debug(`HTML Reporter: Found screenshot: ${artifactPath}`)
|
|
1166
1213
|
} else {
|
|
1167
1214
|
otherArtifacts.push(artifact)
|
|
1168
|
-
output.
|
|
1215
|
+
output.debug(`HTML Reporter: Found other artifact: ${artifact}`)
|
|
1169
1216
|
}
|
|
1170
1217
|
})
|
|
1171
1218
|
|
|
1172
|
-
output.
|
|
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.
|
|
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.
|
|
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="
|
|
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
|
-
|
|
2943
|
-
|
|
2944
|
-
|
|
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
|
+
"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.
|
|
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",
|