|
| 1 | +# ESM Migration Guide |
| 2 | + |
| 3 | +This guide covers the migration to ECMAScript Modules (ESM) format in CodeceptJS v4.x, including important changes in execution behavior and how to adapt your tests. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +CodeceptJS v4.x introduces support for ECMAScript Modules (ESM), which brings modern JavaScript module syntax and better compatibility with the Node.js ecosystem. While most tests will continue working without changes, there are some behavioral differences to be aware of. |
| 8 | + |
| 9 | +## Quick Migration |
| 10 | + |
| 11 | +For most users, migrating to ESM is straightforward: |
| 12 | + |
| 13 | +1. **Add `"type": "module"` to your `package.json`:** |
| 14 | + |
| 15 | +```json |
| 16 | +{ |
| 17 | + "name": "your-project", |
| 18 | + "type": "module", |
| 19 | + "dependencies": { |
| 20 | + "codeceptjs": "^4.0.0" |
| 21 | + } |
| 22 | +} |
| 23 | +``` |
| 24 | + |
| 25 | +2. **Update import syntax in configuration files:** |
| 26 | + |
| 27 | +```js |
| 28 | +// Before (CommonJS) |
| 29 | +const { setHeadlessWhen, setCommonPlugins } = require('@codeceptjs/configure') |
| 30 | + |
| 31 | +// After (ESM) |
| 32 | +import { setHeadlessWhen, setCommonPlugins } from '@codeceptjs/configure' |
| 33 | +``` |
| 34 | + |
| 35 | +3. **Update helper imports:** |
| 36 | + |
| 37 | +```js |
| 38 | +// Before (CommonJS) |
| 39 | +const Helper = require('@codeceptjs/helper') |
| 40 | + |
| 41 | +// After (ESM) |
| 42 | +import Helper from '@codeceptjs/helper' |
| 43 | +``` |
| 44 | + |
| 45 | +## Known Changes |
| 46 | + |
| 47 | +### Session and Within Block Execution Order |
| 48 | + |
| 49 | +**⚠️ Important:** ESM migration has changed the execution timing of `session()` and `within()` blocks. |
| 50 | + |
| 51 | +#### What Changed |
| 52 | + |
| 53 | +In CommonJS, session and within blocks executed synchronously, interleaved with main test steps: |
| 54 | + |
| 55 | +```js |
| 56 | +// CommonJS execution order |
| 57 | +Scenario('test', ({ I }) => { |
| 58 | + I.do('step1') // ← Executes first |
| 59 | + session('user', () => { |
| 60 | + I.do('session-step') // ← Executes second |
| 61 | + }) |
| 62 | + I.do('step2') // ← Executes third |
| 63 | +}) |
| 64 | +``` |
| 65 | + |
| 66 | +In ESM, session and within blocks execute after the main flow completes: |
| 67 | + |
| 68 | +```js |
| 69 | +// ESM execution order |
| 70 | +Scenario('test', ({ I }) => { |
| 71 | + I.do('step1') // ← Executes first |
| 72 | + session('user', () => { |
| 73 | + I.do('session-step') // ← Executes third (after step2) |
| 74 | + }) |
| 75 | + I.do('step2') // ← Executes second |
| 76 | +}) |
| 77 | +``` |
| 78 | + |
| 79 | +#### Impact on Your Tests |
| 80 | + |
| 81 | +**✅ No Impact (99% of cases):** Most tests will continue working correctly because: |
| 82 | + |
| 83 | +- All steps still execute completely |
| 84 | +- Browser interactions work as expected |
| 85 | +- Session isolation is maintained |
| 86 | +- Test assertions pass/fail correctly |
| 87 | +- Final test state is identical |
| 88 | + |
| 89 | +**⚠️ Potential Issues (rare edge cases):** |
| 90 | + |
| 91 | +1. **Cross-session dependencies on immediate state:** |
| 92 | + |
| 93 | +```js |
| 94 | +// POTENTIALLY PROBLEMATIC |
| 95 | +I.createUser('alice') |
| 96 | +session('alice', () => { |
| 97 | + I.login('alice') // May execute before user creation completes |
| 98 | +}) |
| 99 | +``` |
| 100 | + |
| 101 | +2. **Within blocks depending on immediate DOM changes:** |
| 102 | + |
| 103 | +```js |
| 104 | +// POTENTIALLY PROBLEMATIC |
| 105 | +I.click('Show Advanced Form') |
| 106 | +within('.advanced-form', () => { |
| 107 | + I.fillField('setting', 'value') // May execute before form appears |
| 108 | +}) |
| 109 | +``` |
| 110 | + |
| 111 | +#### Migration Solutions |
| 112 | + |
| 113 | +If you encounter timing-related issues in edge cases: |
| 114 | + |
| 115 | +1. **Use explicit waits for dependent operations:** |
| 116 | + |
| 117 | +```js |
| 118 | +// RECOMMENDED FIX |
| 119 | +I.createUser('alice') |
| 120 | +session('alice', () => { |
| 121 | + I.waitForElement('.login-form') // Ensure UI is ready |
| 122 | + I.login('alice') |
| 123 | +}) |
| 124 | +``` |
| 125 | + |
| 126 | +2. **Add explicit synchronization:** |
| 127 | + |
| 128 | +```js |
| 129 | +// RECOMMENDED FIX |
| 130 | +I.click('Show Advanced Form') |
| 131 | +I.waitForElement('.advanced-form') // Wait for form to appear |
| 132 | +within('.advanced-form', () => { |
| 133 | + I.fillField('setting', 'value') |
| 134 | +}) |
| 135 | +``` |
| 136 | + |
| 137 | +3. **Use async/await for complex flows:** |
| 138 | + |
| 139 | +```js |
| 140 | +// RECOMMENDED FIX |
| 141 | +await I.createUser('alice') |
| 142 | +await session('alice', async () => { |
| 143 | + await I.login('alice') |
| 144 | +}) |
| 145 | +``` |
| 146 | + |
| 147 | +## Best Practices for ESM |
| 148 | + |
| 149 | +### 1. File Extensions |
| 150 | + |
| 151 | +Use `.js` extension for ESM files (not `.mjs` unless specifically needed): |
| 152 | + |
| 153 | +```js |
| 154 | +// codecept.conf.js (ESM format) |
| 155 | +export default { |
| 156 | + tests: './*_test.js', |
| 157 | + // ... |
| 158 | +} |
| 159 | +``` |
| 160 | + |
| 161 | +### 2. Dynamic Imports |
| 162 | + |
| 163 | +For conditional imports, use dynamic import syntax: |
| 164 | + |
| 165 | +```js |
| 166 | +// Instead of require() conditions |
| 167 | +let helper |
| 168 | +if (condition) { |
| 169 | + helper = await import('./CustomHelper.js') |
| 170 | +} |
| 171 | +``` |
| 172 | + |
| 173 | +### 3. Configuration Export |
| 174 | + |
| 175 | +Use default export for configuration: |
| 176 | + |
| 177 | +```js |
| 178 | +// codecept.conf.js |
| 179 | +export default { |
| 180 | + tests: './*_test.js', |
| 181 | + output: './output', |
| 182 | + helpers: { |
| 183 | + Playwright: { |
| 184 | + url: 'http://localhost', |
| 185 | + }, |
| 186 | + }, |
| 187 | +} |
| 188 | +``` |
| 189 | + |
| 190 | +### 4. Helper Classes |
| 191 | + |
| 192 | +Export helper classes as default: |
| 193 | + |
| 194 | +```js |
| 195 | +// CustomHelper.js |
| 196 | +import { Helper } from 'codeceptjs' |
| 197 | + |
| 198 | +class CustomHelper extends Helper { |
| 199 | + // helper methods |
| 200 | +} |
| 201 | + |
| 202 | +export default CustomHelper |
| 203 | +``` |
| 204 | + |
| 205 | +## Troubleshooting |
| 206 | + |
| 207 | +### Common Issues |
| 208 | + |
| 209 | +1. **Module not found errors:** |
| 210 | + - Ensure `"type": "module"` is in package.json |
| 211 | + - Use complete file paths with extensions: `import './helper.js'` |
| 212 | + |
| 213 | +2. **Configuration not loading:** |
| 214 | + - Check that config uses `export default {}` |
| 215 | + - Verify all imports use ESM syntax |
| 216 | + |
| 217 | +3. **Timing issues in sessions/within:** |
| 218 | + - Add explicit waits as shown in the migration solutions above |
| 219 | + - Consider using async/await for complex flows |
| 220 | + |
| 221 | +4. **Plugin compatibility:** |
| 222 | + - Ensure all plugins support ESM |
| 223 | + - Update plugin imports to use ESM syntax |
| 224 | + |
| 225 | +### Getting Help |
| 226 | + |
| 227 | +If you encounter issues during ESM migration: |
| 228 | + |
| 229 | +1. Check the [example-esm](../example-esm/) directory for working examples |
| 230 | +2. Review error messages for import/export syntax issues |
| 231 | +3. Consider the execution order changes for session/within blocks |
| 232 | +4. Join the [CodeceptJS community](https://codecept.io/community/) for support |
| 233 | + |
| 234 | +## Conclusion |
| 235 | + |
| 236 | +ESM migration brings CodeceptJS into alignment with modern JavaScript standards while maintaining backward compatibility for most use cases. The execution order changes in sessions and within blocks represent internal timing adjustments that rarely affect real-world test functionality. |
| 237 | + |
| 238 | +The vast majority of CodeceptJS users will experience seamless migration with no functional differences in their tests. |
0 commit comments