@@ -40,6 +40,18 @@ const templates = {}
4040 * npx codeceptjs run --plugins stepByStepReport
4141 * ```
4242 *
43+ * Run tests with workers:
44+ *
45+ * ```
46+ * npx codeceptjs run-workers 2 --plugins stepByStepReport
47+ * ```
48+ *
49+ * Run tests with multiple configurations:
50+ *
51+ * ```
52+ * npx codeceptjs run-multiple --all --plugins stepByStepReport
53+ * ```
54+ *
4355 * #### Configuration
4456 *
4557 * ```js
@@ -60,6 +72,20 @@ const templates = {}
6072 * * `screenshotsForAllureReport`: If Allure plugin is enabled this plugin attaches each saved screenshot to allure report. Default: false.
6173 * * `disableScreenshotOnFail : Disables the capturing of screeshots after the failed step. Default: true.
6274 *
75+ * #### Worker and Multiple Run Support
76+ *
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.
80+ *
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.
88+ *
6389 * @param {* } config
6490 */
6591
@@ -87,8 +113,12 @@ module.exports = function (config) {
87113
88114 const recordedTests = { }
89115 const pad = '0000'
116+
90117 const reportDir = config . output ? path . resolve ( global . codecept_dir , config . output ) : defaultConfig . output
91118
119+ // Ensure the report directory exists
120+ mkdirp . sync ( reportDir )
121+
92122 event . dispatcher . on ( event . suite . before , suite => {
93123 stepNum = - 1
94124 } )
@@ -137,11 +167,70 @@ module.exports = function (config) {
137167
138168 event . dispatcher . on ( event . workers . result , async ( ) => {
139169 await recorder . add ( ( ) => {
140- 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 ( )
141173 generateRecordsHtml ( recordedTests )
142174 } )
143175 } )
144176
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+
145234 function getRecordFoldersWithDetails ( dirPath ) {
146235 let results = { }
147236
@@ -186,9 +275,32 @@ module.exports = function (config) {
186275 records : links ,
187276 } )
188277
189- 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 )
190302
191- 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` ) } ` )
192304 }
193305
194306 async function persistStep ( step ) {
0 commit comments