Skip to content

Commit 28bfac6

Browse files
Copilotkobenguyent
andcommitted
Change stepByStepReport to keep screenshots in original directories instead of consolidating
Co-authored-by: kobenguyent <[email protected]>
1 parent 7dec84e commit 28bfac6

File tree

2 files changed

+290
-46
lines changed

2 files changed

+290
-46
lines changed

lib/plugin/stepByStepReport.js

Lines changed: 96 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,17 @@ const templates = {}
7474
*
7575
* #### Worker and Multiple Run Support
7676
*
77-
* When using `run-workers`, `run-multiple`, or combinations thereof, screenshots from all workers and runs
78-
* are automatically consolidated into a shared directory to ensure the final step-by-step report contains
79-
* all screenshots from all processes.
77+
* When using `run-workers`, `run-multiple`, or combinations thereof, the plugin automatically
78+
* detects all worker and run processes and creates a consolidated step-by-step report that
79+
* includes screenshots from all processes while keeping them in their original directories.
8080
*
81-
* The plugin automatically detects:
82-
* - **run-workers**: Consolidates screenshots from multiple worker processes
83-
* - **run-multiple**: Consolidates screenshots from multiple run configurations
84-
* - **Mixed scenarios**: Handles combinations of both workers and multiple runs
85-
* - **Custom output directories**: Works with both user-specified and default output directories
81+
* Screenshots remain in their respective process directories for traceability:
82+
* - **run-workers**: Screenshots saved in `/output/worker1/`, `/output/worker2/`, etc.
83+
* - **run-multiple**: Screenshots saved in `/output/config_name_hash/`, etc.
84+
* - **Mixed scenarios**: Screenshots saved in `/output/config_name_hash/worker1/`, etc.
85+
*
86+
* The final consolidated report links to all screenshots while preserving their original locations
87+
* and indicating which process or worker they came from.
8688
*
8789
* @param {*} config
8890
*/
@@ -112,41 +114,7 @@ module.exports = function (config) {
112114
const recordedTests = {}
113115
const pad = '0000'
114116

115-
// Determine if we're running in a multi-process scenario (workers or run-multiple)
116-
// and need to consolidate screenshots from multiple directories
117-
let reportDir
118-
119-
const isRunningWithWorkers = process.env.RUNS_WITH_WORKERS === 'true'
120-
const isRunMultipleChild = process.argv.some(arg => arg === '--child')
121-
const needsConsolidation = isRunningWithWorkers || isRunMultipleChild
122-
123-
if (needsConsolidation && global.codecept_dir) {
124-
// Extract the base output directory and create a shared location for screenshots
125-
const currentOutputDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output
126-
127-
let baseOutputDir = currentOutputDir
128-
129-
// For mixed scenario (run-multiple + workers), we need to strip both worker and run directory segments
130-
// For run-workers only, strip worker directory segment
131-
// For run-multiple only, strip run directory segment
132-
if (isRunningWithWorkers) {
133-
// Strip worker directory: /output/smoke_chrome_hash_1/worker1 -> /output/smoke_chrome_hash_1 or /output/worker1 -> /output
134-
const workerDirPattern = /[/\\][^/\\]+$/ // Match the last directory segment (worker name)
135-
baseOutputDir = baseOutputDir.replace(workerDirPattern, '')
136-
}
137-
138-
if (isRunMultipleChild) {
139-
// Strip run directory: /output/smoke_chrome_hash_1 -> /output
140-
const runDirPattern = /[/\\][^/\\]+$/ // Match the last directory segment (run name)
141-
baseOutputDir = baseOutputDir.replace(runDirPattern, '')
142-
}
143-
144-
// Create a shared directory for step-by-step screenshots
145-
reportDir = path.join(baseOutputDir, 'stepByStepReport')
146-
} else {
147-
// Regular run command: use the output directory as-is
148-
reportDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output
149-
}
117+
const reportDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output
150118

151119
// Ensure the report directory exists
152120
mkdirp.sync(reportDir)
@@ -199,11 +167,70 @@ module.exports = function (config) {
199167

200168
event.dispatcher.on(event.workers.result, async () => {
201169
await recorder.add(() => {
202-
const recordedTests = getRecordFoldersWithDetails(reportDir)
170+
// For workers and run-multiple scenarios, we need to search across multiple directories
171+
// to find all screenshot folders from different processes
172+
const recordedTests = getRecordFoldersFromAllDirectories()
203173
generateRecordsHtml(recordedTests)
204174
})
205175
})
206176

177+
function getRecordFoldersFromAllDirectories() {
178+
let results = {}
179+
180+
// Determine the base output directory to search from
181+
const baseOutputDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output
182+
183+
// Function to recursively search for record folders in a directory
184+
function searchForRecordFolders(searchDir, basePath = '') {
185+
try {
186+
if (!fs.existsSync(searchDir)) return
187+
188+
const items = fs.readdirSync(searchDir, { withFileTypes: true })
189+
190+
items.forEach(item => {
191+
if (item.isDirectory()) {
192+
const itemPath = path.join(searchDir, item.name)
193+
const relativePath = basePath ? path.join(basePath, item.name) : item.name
194+
195+
// If this is a record folder, process it
196+
if (item.name.startsWith('record_')) {
197+
const indexPath = path.join(itemPath, 'index.html')
198+
199+
let name = ''
200+
if (fs.existsSync(indexPath)) {
201+
try {
202+
const htmlContent = fs.readFileSync(indexPath, 'utf-8')
203+
const $ = cheerio.load(htmlContent)
204+
name = $('.navbar-brand').text().trim()
205+
} catch (err) {
206+
console.error(`Error reading index.html in ${itemPath}:`, err.message)
207+
}
208+
}
209+
210+
// Include the relative path to show which process/worker this came from
211+
const displayName = basePath ? `${name} (${basePath})` : name
212+
results[displayName || 'Unknown'] = path.join(relativePath, 'index.html')
213+
} else {
214+
// Continue searching in subdirectories (worker folders, run-multiple folders)
215+
searchForRecordFolders(itemPath, relativePath)
216+
}
217+
}
218+
})
219+
} catch (err) {
220+
console.error(`Error searching directory ${searchDir}:`, err.message)
221+
}
222+
}
223+
224+
// Start the search from the base output directory
225+
searchForRecordFolders(baseOutputDir)
226+
227+
// Also check the current reportDir for backwards compatibility
228+
const currentDirResults = getRecordFoldersWithDetails(reportDir)
229+
Object.assign(results, currentDirResults)
230+
231+
return results
232+
}
233+
207234
function getRecordFoldersWithDetails(dirPath) {
208235
let results = {}
209236

@@ -248,9 +275,32 @@ module.exports = function (config) {
248275
records: links,
249276
})
250277

251-
fs.writeFileSync(path.join(reportDir, 'records.html'), indexHTML)
278+
// Determine where to write the main records.html file
279+
// For worker/run-multiple scenarios, we want to write to the base output directory
280+
let recordsHtmlDir = reportDir
281+
282+
if (global.codecept_dir && (process.env.RUNS_WITH_WORKERS === 'true' || process.argv.some(arg => arg === '--child'))) {
283+
// Extract base output directory by removing worker/run-specific segments
284+
const baseOutputDir = config.output ? path.resolve(global.codecept_dir, config.output) : defaultConfig.output
285+
let actualBaseDir = baseOutputDir
286+
287+
// For workers: strip worker directory segment
288+
if (process.env.RUNS_WITH_WORKERS === 'true') {
289+
actualBaseDir = actualBaseDir.replace(/[/\\][^/\\]+$/, '')
290+
}
291+
292+
// For run-multiple: strip run directory segment
293+
if (process.argv.some(arg => arg === '--child')) {
294+
actualBaseDir = actualBaseDir.replace(/[/\\][^/\\]+$/, '')
295+
}
296+
297+
recordsHtmlDir = actualBaseDir
298+
mkdirp.sync(recordsHtmlDir)
299+
}
300+
301+
fs.writeFileSync(path.join(recordsHtmlDir, 'records.html'), indexHTML)
252302

253-
output.print(`${figures.circleFilled} Step-by-step preview: ${colors.white.bold(`file://${reportDir}/records.html`)}`)
303+
output.print(`${figures.circleFilled} Step-by-step preview: ${colors.white.bold(`file://${recordsHtmlDir}/records.html`)}`)
254304
}
255305

256306
async function persistStep(step) {
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
const path = require('path')
2+
const fs = require('fs')
3+
const { exec } = require('child_process')
4+
const { expect } = require('expect')
5+
6+
const runner = path.join(__dirname, '../../bin/codecept.js')
7+
const codecept_dir = path.join(__dirname, '../data/sandbox')
8+
9+
describe('stepByStepReport plugin with different run commands', function () {
10+
this.timeout(60000)
11+
12+
const outputDir = path.join(codecept_dir, 'output')
13+
14+
beforeEach(() => {
15+
// Clean up output directory before each test
16+
if (fs.existsSync(outputDir)) {
17+
fs.rmSync(outputDir, { recursive: true, force: true })
18+
}
19+
})
20+
21+
it('should keep screenshots in worker directories and create consolidated report for run-workers', function (done) {
22+
const config = `
23+
exports.config = {
24+
tests: './*_test.js',
25+
timeout: 10000,
26+
output: './output',
27+
helpers: {
28+
FakeDriver: {
29+
require: '../fake_driver',
30+
browser: 'dummy',
31+
},
32+
},
33+
plugins: {
34+
stepByStepReport: {
35+
enabled: true,
36+
deleteSuccessful: false,
37+
},
38+
},
39+
include: {},
40+
bootstrap: false,
41+
mocha: {},
42+
name: 'stepByStepTest',
43+
}
44+
`
45+
46+
const configPath = path.join(codecept_dir, 'codecept.stepbystep.js')
47+
fs.writeFileSync(configPath, config)
48+
49+
const command = `${runner} run-workers 2 --config ${configPath} --grep "@stepbystep"`
50+
51+
exec(command, (err, stdout, stderr) => {
52+
console.log('STDOUT:', stdout)
53+
console.log('STDERR:', stderr)
54+
55+
// Screenshots should remain in worker directories, not consolidated
56+
const worker1Dir = path.join(outputDir, 'worker1')
57+
const worker2Dir = path.join(outputDir, 'worker2')
58+
59+
// Check that worker directories exist (if tests ran)
60+
if (fs.existsSync(outputDir)) {
61+
const items = fs.readdirSync(outputDir)
62+
console.log('Output directory contents:', items)
63+
64+
// The consolidated records.html should be in the base output directory
65+
const recordsHtml = path.join(outputDir, 'records.html')
66+
67+
// If tests ran and created screenshots, we should see evidence of them
68+
console.log('Records.html exists:', fs.existsSync(recordsHtml))
69+
70+
// Screenshots should NOT be in a consolidated stepByStepReport directory
71+
const stepByStepDir = path.join(outputDir, 'stepByStepReport')
72+
expect(fs.existsSync(stepByStepDir)).toBe(false)
73+
}
74+
75+
// Clean up
76+
fs.unlinkSync(configPath)
77+
78+
done()
79+
})
80+
})
81+
82+
it('should keep screenshots in run-multiple directories and create consolidated report', function (done) {
83+
const multipleConfig = `
84+
exports.config = {
85+
tests: './*_test.js',
86+
timeout: 10000,
87+
output: './output',
88+
helpers: {
89+
FakeDriver: {
90+
require: '../fake_driver',
91+
browser: 'dummy',
92+
},
93+
},
94+
plugins: {
95+
stepByStepReport: {
96+
enabled: true,
97+
deleteSuccessful: false,
98+
},
99+
},
100+
include: {},
101+
bootstrap: false,
102+
mocha: {},
103+
name: 'stepByStepTest',
104+
multiple: {
105+
basic: {
106+
browsers: ['chrome']
107+
},
108+
smoke: {
109+
browsers: ['firefox']
110+
}
111+
}
112+
}
113+
`
114+
115+
const configPath = path.join(codecept_dir, 'codecept.multiple.js')
116+
fs.writeFileSync(configPath, multipleConfig)
117+
118+
const command = `${runner} run-multiple basic --config ${configPath} --grep "@stepbystep"`
119+
120+
exec(command, (err, stdout, stderr) => {
121+
console.log('STDOUT:', stdout)
122+
console.log('STDERR:', stderr)
123+
124+
if (fs.existsSync(outputDir)) {
125+
const items = fs.readdirSync(outputDir)
126+
console.log('Output directory contents:', items)
127+
128+
// Screenshots should NOT be in a consolidated stepByStepReport directory
129+
const stepByStepDir = path.join(outputDir, 'stepByStepReport')
130+
expect(fs.existsSync(stepByStepDir)).toBe(false)
131+
132+
// The consolidated records.html should be in the base output directory
133+
const recordsHtml = path.join(outputDir, 'records.html')
134+
console.log('Records.html exists:', fs.existsSync(recordsHtml))
135+
}
136+
137+
// Clean up
138+
fs.unlinkSync(configPath)
139+
140+
done()
141+
})
142+
})
143+
144+
it('should work with regular run command (backward compatibility)', function (done) {
145+
const config = `
146+
exports.config = {
147+
tests: './*_test.js',
148+
timeout: 10000,
149+
output: './output',
150+
helpers: {
151+
FakeDriver: {
152+
require: '../fake_driver',
153+
browser: 'dummy',
154+
},
155+
},
156+
plugins: {
157+
stepByStepReport: {
158+
enabled: true,
159+
deleteSuccessful: false,
160+
},
161+
},
162+
include: {},
163+
bootstrap: false,
164+
mocha: {},
165+
name: 'stepByStepTest',
166+
}
167+
`
168+
169+
const configPath = path.join(codecept_dir, 'codecept.regular.js')
170+
fs.writeFileSync(configPath, config)
171+
172+
const command = `${runner} run --config ${configPath} --grep "@stepbystep"`
173+
174+
exec(command, (err, stdout, stderr) => {
175+
console.log('STDOUT:', stdout)
176+
console.log('STDERR:', stderr)
177+
178+
// For regular run, everything should work as before in the main output directory
179+
if (fs.existsSync(outputDir)) {
180+
const items = fs.readdirSync(outputDir)
181+
console.log('Output directory contents:', items)
182+
183+
// Should NOT create a consolidated stepByStepReport directory for regular runs
184+
const stepByStepDir = path.join(outputDir, 'stepByStepReport')
185+
expect(fs.existsSync(stepByStepDir)).toBe(false)
186+
}
187+
188+
// Clean up
189+
fs.unlinkSync(configPath)
190+
191+
done()
192+
})
193+
})
194+
})

0 commit comments

Comments
 (0)