Skip to content

Commit 45ad5b0

Browse files
miraoclaude
andauthored
fix(pause): assign global.pause synchronously so it stops at the right step (#5652) (#5653)
global.pause was an async wrapper doing a lazy `await import('./pause.js')`. A scenario body runs synchronously to build the recorder queue, so the await deferred pause's `recorder.add('Start new session')` to a later microtask — after the following steps had already been queued. As a result pause() stopped *after* the next step instead of before it. initCodeceptGlobals is already async, so resolve the module up front and assign the real pause function directly, making it synchronous at call time and restoring queue order. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 7a5c4f9 commit 45ad5b0

2 files changed

Lines changed: 50 additions & 4 deletions

File tree

lib/globals.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,8 @@ export async function initCodeceptGlobals(dir, config, container) {
3030
// pause/inject/share stay global even under noGlobals — they're the everyday
3131
// debugging/wiring entry points and have no useful import alternative for
3232
// page-object code that runs before the container is available.
33-
global.pause = async (...args) => {
34-
const pauseModule = await import('./pause.js')
35-
return (pauseModule.default || pauseModule)(...args)
36-
}
33+
const pauseModule = await import('./pause.js')
34+
global.pause = pauseModule.default || pauseModule
3735
global.inject = () => container.support()
3836
global.share = container.share
3937

test/unit/pause_test.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import pause, { setPauseHandler, pauseNow } from '../../lib/pause.js'
44
import recorder from '../../lib/recorder.js'
55
import event from '../../lib/event.js'
66
import store from '../../lib/store.js'
7+
import { initCodeceptGlobals } from '../../lib/globals.js'
78

89
const settles = (promise, ms = 2000) =>
910
Promise.race([
@@ -100,3 +101,50 @@ describe('pause listener lifecycle', () => {
100101
expect(recorder.getCurrentSessionId()).to.equal(null)
101102
})
102103
})
104+
105+
describe('global pause() queue ordering (issue #5652)', () => {
106+
let savedPause
107+
let savedNoGlobals
108+
109+
beforeEach(async () => {
110+
savedPause = global.pause
111+
savedNoGlobals = store.noGlobals
112+
store.dryRun = false
113+
setPauseHandler(() => Promise.resolve())
114+
recorder.reset()
115+
recorder.stop()
116+
await initCodeceptGlobals(process.cwd(), { output: 'output', noGlobals: true }, { support() {}, share() {} })
117+
})
118+
119+
afterEach(() => {
120+
setPauseHandler(null)
121+
event.dispatcher.emit(event.test.finished)
122+
recorder.reset()
123+
global.pause = savedPause
124+
store.noGlobals = savedNoGlobals
125+
})
126+
127+
it('global.pause is a synchronous function, not an async wrapper', () => {
128+
expect(global.pause).to.equal(pause)
129+
expect(global.pause.constructor.name).to.equal('Function')
130+
})
131+
132+
it('queues the pause step between the steps surrounding it in the test body', () => {
133+
// Mirrors a scenario body that runs synchronously to build the recorder queue:
134+
// I.amOnPage(...); pause(); I.see(...);
135+
// The pause step must land *before* `see`, not after the rest of the body.
136+
recorder.start()
137+
recorder.add('amOnPage', () => {})
138+
global.pause()
139+
recorder.add('see', () => {})
140+
141+
const order = recorder.scheduled().split('\n')
142+
const amOnPageIdx = order.indexOf('amOnPage')
143+
const pauseIdx = order.indexOf('Start new session')
144+
const seeIdx = order.indexOf('see')
145+
146+
expect(pauseIdx, 'pause step must be queued synchronously').to.be.greaterThan(-1)
147+
expect(pauseIdx, 'pause must be queued after the preceding step').to.be.greaterThan(amOnPageIdx)
148+
expect(pauseIdx, 'pause must be queued before the following step').to.be.lessThan(seeIdx)
149+
})
150+
})

0 commit comments

Comments
 (0)