From cb598daf9e5fe84ca8ac19e5a926cfea99e28012 Mon Sep 17 00:00:00 2001 From: Daniel Rentz Date: Thu, 5 Nov 2026 08:28:37 +0100 Subject: [PATCH] Fixed: FileSystem.waitForFile: do not crash if directory does not exist yet (#5420) fs.watch() throws for a missing directory, and the timeout callback then accesses the uninitialized watcher, which kills the process with a ReferenceError. Poll with fileExists() instead, like seeFile() does. --- lib/helper/FileSystem.js | 42 ++++------------------------- test/unit/helper/FileSystem_test.js | 29 ++++++++++++++++++++ 2 files changed, 34 insertions(+), 37 deletions(-) diff --git a/lib/helper/FileSystem.js b/lib/helper/FileSystem.js index 109bdb3a8..1edb597b2 100644 --- a/lib/helper/FileSystem.js +++ b/lib/helper/FileSystem.js @@ -100,7 +100,6 @@ class FileSystem extends Helper { */ async waitForFile(name, sec = 1) { if (sec === 0) assert.fail('Use `seeFile` instead of waiting 0 seconds!') - const waitTimeout = sec * 1000 this.file = path.join(this.dir, name) try { this.debugSection('File', this.file) @@ -108,9 +107,11 @@ class FileSystem extends Helper { // Fallback debug for ESM transition console.log(`[File] ${this.file}`) } - return isFileExists(this.file, waitTimeout).catch(() => { - throw new Error(`file (${name}) still not present in directory ${this.dir} after ${waitTimeout / 1000} sec`) - }) + const endTime = Date.now() + sec * 1000 + while (!fileExists(this.file)) { + if (endTime < Date.now()) throw new Error(`file (${name}) still not present in directory ${this.dir} after ${sec} sec`) + await new Promise(resolve => setTimeout(resolve, 200)) + } } /** @@ -213,36 +214,3 @@ function getFileContents(file, encoding = 'utf8') { if (encoding === '') assert.fail('Encoding is an empty string, please set a valid encoding') return fs.readFileSync(file, encoding) } - -/** - * @param {string} file - * @param {number} timeout - * @private - * @returns {Promise} - */ -function isFileExists(file, timeout) { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - watcher.close() - reject(new Error('File did not exists and was not created during the timeout.')) - }, timeout) - - const dir = path.dirname(file) - const basename = path.basename(file) - const watcher = fs.watch(dir, (eventType, filename) => { - if (eventType === 'rename' && filename === basename) { - clearTimeout(timer) - watcher.close() - resolve() - } - }) - - fs.access(file, fs.constants.R_OK, err => { - if (!err) { - clearTimeout(timer) - watcher.close() - resolve() - } - }) - }) -} diff --git a/test/unit/helper/FileSystem_test.js b/test/unit/helper/FileSystem_test.js index d015810d3..ba2530d22 100644 --- a/test/unit/helper/FileSystem_test.js +++ b/test/unit/helper/FileSystem_test.js @@ -77,4 +77,33 @@ describe('FileSystem', () => { fs.seeFile(outputFilePath) fs.seeInThisFile(text) }) + + describe('#waitForFile', () => { + const waitDir = path.join('data', 'output', 'wait_for_file') + + beforeEach(() => { + nodeFs.rmSync(path.join(global.codecept_dir, waitDir), { recursive: true, force: true }) + fs.amInPath(waitDir) + }) + + it('should wait for a file in a directory that does not exist yet', async () => { + setTimeout(() => { + nodeFs.mkdirSync(fs.dir, { recursive: true }) + nodeFs.writeFileSync(path.join(fs.dir, 'file.txt'), 'content') + }, 300) + await fs.waitForFile('file.txt', 3) + }) + + it('should fail after the timeout if the directory does not exist', async () => { + const start = Date.now() + let error + try { + await fs.waitForFile('file.txt', 1) + } catch (err) { + error = err + } + expect(error?.message).to.include('file (file.txt) still not present') + expect(Date.now() - start).to.be.at.least(1000) + }) + }) })