11const glob = require ( 'glob' ) ;
22const path = require ( 'path' ) ;
3+ const { isPromise } = require ( 'util/types' ) ;
34const { MetaStep } = require ( './step' ) ;
4- const { fileExists, isFunction, isAsyncFunction } = require ( './utils' ) ;
5+ const { fileExists, isFunction, isAsyncFunction, installedLocally } = require ( './utils' ) ;
56const Translation = require ( './translation' ) ;
67const MochaFactory = require ( './mochaFactory' ) ;
78const recorder = require ( './recorder' ) ;
@@ -155,45 +156,42 @@ module.exports = Container;
155156
156157function createHelpers ( config ) {
157158 const helpers = { } ;
158- let moduleName ;
159- for ( const helperName in config ) {
159+ for ( let helperName in config ) {
160160 try {
161- if ( config [ helperName ] . require ) {
162- if ( config [ helperName ] . require . startsWith ( '.' ) ) {
163- moduleName = path . resolve ( global . codecept_dir , config [ helperName ] . require ) ; // custom helper
164- } else {
165- moduleName = config [ helperName ] . require ; // plugin helper
166- }
167- } else {
168- moduleName = `./helper/${ helperName } ` ; // built-in helper
161+ let HelperClass ;
162+
163+ // ESM import
164+ if ( helperName ?. constructor === Function && helperName . prototype ) {
165+ HelperClass = helperName ;
166+ helperName = HelperClass . constructor . name ;
169167 }
170168
171- // @ts -ignore
172- let HelperClass ;
173- // check if the helper is the built-in, use the require() syntax.
174- if ( moduleName . startsWith ( './helper/' ) ) {
175- HelperClass = require ( moduleName ) ;
176- } else {
177- // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
178- HelperClass = require ( moduleName ) . default || require ( moduleName ) ;
169+ // classical require
170+ if ( ! HelperClass ) {
171+ HelperClass = requireHelperFromModule ( helperName , config ) ;
179172 }
180173
181- if ( HelperClass . _checkRequirements ) {
182- const requirements = HelperClass . _checkRequirements ( ) ;
183- if ( requirements ) {
184- let install ;
185- if ( require ( './utils' ) . installedLocally ( ) ) {
186- install = `npm install --save-dev ${ requirements . join ( ' ' ) } ` ;
187- } else {
188- console . log ( 'WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation' ) ;
189- install = `[sudo] npm install -g ${ requirements . join ( ' ' ) } ` ;
174+ // handle async CJS modules that use dynamic import
175+ if ( isPromise ( HelperClass ) && typeof HelperClass . then === 'function' ) {
176+ HelperClass = HelperClass . then ( ResolvedHelperClass => {
177+ // Check if ResolvedHelperClass is a constructor function
178+ if ( typeof ResolvedHelperClass !== 'function' || ! ResolvedHelperClass . prototype ) {
179+ throw new Error ( `Helper class from module '${ helperName } ' is not a constructor. Use CJS async module syntax.` ) ;
190180 }
191- throw new Error ( `Required modules are not installed.\n\nRUN: ${ install } ` ) ;
192- }
181+ try {
182+ helpers [ helperName ] = new ResolvedHelperClass ( config [ helperName ] ) ;
183+ } catch ( err ) {
184+ throw new Error ( `Could not load helper ${ helperName } as async module (${ err . message } )` ) ;
185+ }
186+ } ) ;
187+ continue ;
193188 }
189+
190+ checkHelperRequirements ( HelperClass ) ;
191+
194192 helpers [ helperName ] = new HelperClass ( config [ helperName ] ) ;
195193 } catch ( err ) {
196- throw new Error ( `Could not load helper ${ helperName } from module ' ${ moduleName } ':\n ${ err . message } \n ${ err . stack } ` ) ;
194+ throw new Error ( `Could not load helper ${ helperName } ( ${ err . message } ) ` ) ;
197195 }
198196 }
199197
@@ -203,6 +201,44 @@ function createHelpers(config) {
203201 return helpers ;
204202}
205203
204+ function checkHelperRequirements ( HelperClass ) {
205+ if ( HelperClass . _checkRequirements ) {
206+ const requirements = HelperClass . _checkRequirements ( ) ;
207+ if ( requirements ) {
208+ let install ;
209+ if ( installedLocally ( ) ) {
210+ install = `npm install --save-dev ${ requirements . join ( ' ' ) } ` ;
211+ } else {
212+ console . log ( 'WARNING: CodeceptJS is not installed locally. It is recommended to switch to local installation' ) ;
213+ install = `[sudo] npm install -g ${ requirements . join ( ' ' ) } ` ;
214+ }
215+ throw new Error ( `Required modules are not installed.\n\nRUN: ${ install } ` ) ;
216+ }
217+ }
218+ }
219+
220+ function requireHelperFromModule ( helperName , config , HelperClass ) {
221+ const moduleName = getHelperModuleName ( helperName , config ) ;
222+ if ( moduleName . startsWith ( './helper/' ) ) {
223+ HelperClass = require ( moduleName ) ;
224+ } else {
225+ // check if the new syntax export default HelperName is used and loads the Helper, otherwise loads the module that used old syntax export = HelperName.
226+ try {
227+ const mod = require ( moduleName ) ;
228+ if ( ! mod && ! mod . default ) {
229+ throw new Error ( `Helper module '${ moduleName } ' was not found. Make sure you have installed the package correctly.` ) ;
230+ }
231+ HelperClass = mod . default || mod ;
232+ } catch ( err ) {
233+ if ( err . code === 'MODULE_NOT_FOUND' ) {
234+ throw new Error ( `Helper module '${ moduleName } ' was not found. Make sure you have installed the package correctly.` ) ;
235+ }
236+ throw err ;
237+ }
238+ }
239+ return HelperClass ;
240+ }
241+
206242function createSupportObjects ( config ) {
207243 const objects = { } ;
208244
@@ -440,3 +476,21 @@ function loadTranslation(locale, vocabularies) {
440476
441477 return translation ;
442478}
479+
480+ function getHelperModuleName ( helperName , config ) {
481+ // classical require
482+ if ( config [ helperName ] . require ) {
483+ if ( config [ helperName ] . require . startsWith ( '.' ) ) {
484+ return path . resolve ( global . codecept_dir , config [ helperName ] . require ) ; // custom helper
485+ }
486+ return config [ helperName ] . require ; // plugin helper
487+ }
488+
489+ // built-in helpers
490+ if ( helperName . startsWith ( '@codeceptjs/' ) ) {
491+ return helperName ;
492+ }
493+
494+ // built-in helpers
495+ return `./helper/${ helperName } ` ;
496+ }
0 commit comments