Skip to content

Commit 8bc5b46

Browse files
authoredAug 10, 2020
fix(client): avoid race between execute and clearContext (#3452)
Add a delay in execute to ensure that reload events and clear context events are completed first. Fixes #3424
1 parent 6cd5a3b commit 8bc5b46

File tree

3 files changed

+112
-90
lines changed

3 files changed

+112
-90
lines changed
 

‎client/karma.js

+30-28
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ function Karma (socket, iframe, opener, navigator, location, document) {
131131
// TODO(vojta): show what test (with explanation about jasmine.UPDATE_INTERVAL)
132132
self.error('Some of your tests did a full page reload!')
133133
}
134+
reloadingContext = false
135+
}
136+
137+
function clearContext () {
138+
reloadingContext = true
139+
140+
navigateContextTo('about:blank')
134141
}
135142

136143
this.log = function (type, args) {
@@ -145,12 +152,6 @@ function Karma (socket, iframe, opener, navigator, location, document) {
145152

146153
this.stringify = stringify
147154

148-
function clearContext () {
149-
reloadingContext = true
150-
151-
navigateContextTo('about:blank')
152-
}
153-
154155
function getLocation (url, lineno, colno) {
155156
var location = ''
156157

@@ -234,11 +235,10 @@ function Karma (socket, iframe, opener, navigator, location, document) {
234235
}
235236

236237
if (self.config.clearContext) {
237-
// give the browser some time to breath, there could be a page reload, but because a bunch of
238-
// tests could run in the same event loop, we wouldn't notice.
239-
setTimeout(function () {
240-
clearContext()
241-
}, 0)
238+
// A test could have incorrectly issued a navigate. To clear the context
239+
// we will navigate the iframe. Delay ours to ensure the error from an
240+
// incorrect navigate is processed.
241+
setTimeout(clearContext)
242242
}
243243

244244
socket.emit('complete', result || {}, function () {
@@ -259,24 +259,26 @@ function Karma (socket, iframe, opener, navigator, location, document) {
259259
}
260260

261261
socket.on('execute', function (cfg) {
262-
// reset startEmitted and reload the iframe
263-
startEmitted = false
264-
self.config = cfg
265-
// if not clearing context, reloadingContext always true to prevent beforeUnload error
266-
reloadingContext = !self.config.clearContext
267-
navigateContextTo(constant.CONTEXT_URL)
268-
269-
if (self.config.clientDisplayNone) {
270-
[].forEach.call(document.querySelectorAll('#banner, #browsers'), function (el) {
271-
el.style.display = 'none'
272-
})
273-
}
262+
// Delay our navigation to the next event in case the clearContext has not completed.
263+
setTimeout(function allowClearContextToComplete () {
264+
// reset startEmitted and reload the iframe
265+
startEmitted = false
266+
self.config = cfg
267+
268+
navigateContextTo(constant.CONTEXT_URL)
269+
270+
if (self.config.clientDisplayNone) {
271+
[].forEach.call(document.querySelectorAll('#banner, #browsers'), function (el) {
272+
el.style.display = 'none'
273+
})
274+
}
274275

275-
// clear the console before run
276-
// works only on FF (Safari, Chrome do not allow to clear console from js source)
277-
if (window.console && window.console.clear) {
278-
window.console.clear()
279-
}
276+
// clear the console before run
277+
// works only on FF (Safari, Chrome do not allow to clear console from js source)
278+
if (window.console && window.console.clear) {
279+
window.console.clear()
280+
}
281+
})
280282
})
281283
socket.on('stop', function () {
282284
this.complete()

‎static/karma.js

+30-28
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ function Karma (socket, iframe, opener, navigator, location, document) {
141141
// TODO(vojta): show what test (with explanation about jasmine.UPDATE_INTERVAL)
142142
self.error('Some of your tests did a full page reload!')
143143
}
144+
reloadingContext = false
145+
}
146+
147+
function clearContext () {
148+
reloadingContext = true
149+
150+
navigateContextTo('about:blank')
144151
}
145152

146153
this.log = function (type, args) {
@@ -155,12 +162,6 @@ function Karma (socket, iframe, opener, navigator, location, document) {
155162

156163
this.stringify = stringify
157164

158-
function clearContext () {
159-
reloadingContext = true
160-
161-
navigateContextTo('about:blank')
162-
}
163-
164165
function getLocation (url, lineno, colno) {
165166
var location = ''
166167

@@ -244,11 +245,10 @@ function Karma (socket, iframe, opener, navigator, location, document) {
244245
}
245246

246247
if (self.config.clearContext) {
247-
// give the browser some time to breath, there could be a page reload, but because a bunch of
248-
// tests could run in the same event loop, we wouldn't notice.
249-
setTimeout(function () {
250-
clearContext()
251-
}, 0)
248+
// A test could have incorrectly issued a navigate. To clear the context
249+
// we will navigate the iframe. Delay ours to ensure the error from an
250+
// incorrect navigate is processed.
251+
setTimeout(clearContext)
252252
}
253253

254254
socket.emit('complete', result || {}, function () {
@@ -269,24 +269,26 @@ function Karma (socket, iframe, opener, navigator, location, document) {
269269
}
270270

271271
socket.on('execute', function (cfg) {
272-
// reset startEmitted and reload the iframe
273-
startEmitted = false
274-
self.config = cfg
275-
// if not clearing context, reloadingContext always true to prevent beforeUnload error
276-
reloadingContext = !self.config.clearContext
277-
navigateContextTo(constant.CONTEXT_URL)
278-
279-
if (self.config.clientDisplayNone) {
280-
[].forEach.call(document.querySelectorAll('#banner, #browsers'), function (el) {
281-
el.style.display = 'none'
282-
})
283-
}
272+
// Delay our navigation to the next event in case the clearContext has not completed.
273+
setTimeout(function allowClearContextToComplete () {
274+
// reset startEmitted and reload the iframe
275+
startEmitted = false
276+
self.config = cfg
277+
278+
navigateContextTo(constant.CONTEXT_URL)
279+
280+
if (self.config.clientDisplayNone) {
281+
[].forEach.call(document.querySelectorAll('#banner, #browsers'), function (el) {
282+
el.style.display = 'none'
283+
})
284+
}
284285

285-
// clear the console before run
286-
// works only on FF (Safari, Chrome do not allow to clear console from js source)
287-
if (window.console && window.console.clear) {
288-
window.console.clear()
289-
}
286+
// clear the console before run
287+
// works only on FF (Safari, Chrome do not allow to clear console from js source)
288+
if (window.console && window.console.clear) {
289+
window.console.clear()
290+
}
291+
})
290292
})
291293
socket.on('stop', function () {
292294
this.complete()

‎test/client/karma.spec.js

+52-34
Original file line numberDiff line numberDiff line change
@@ -44,31 +44,40 @@ describe('Karma', function () {
4444
assert(startSpy.calledWith(config))
4545
})
4646

47-
it('should open a new window when useIFrame is false', function () {
47+
it('should open a new window when useIFrame is false', function (done) {
4848
var config = ck.config = {
4949
useIframe: false,
5050
runInParent: false
5151
}
5252

5353
socket.emit('execute', config)
54-
assert(!ck.start.called)
54+
setTimeout(function nextEventLoop () {
55+
assert(!ck.start.called)
5556

56-
ck.loaded()
57-
assert(startSpy.calledWith(config))
58-
assert(windowStub.calledWith('context.html'))
57+
ck.loaded()
58+
assert(startSpy.calledWith(config))
59+
assert(windowStub.calledWith('context.html'))
60+
done()
61+
})
5962
})
6063

61-
it('should not set style on elements', function () {
64+
it('should not set style on elements', function (done) {
6265
var config = {}
6366
socket.emit('execute', config)
64-
assert(Object.keys(elements[0].style).length === 0)
67+
setTimeout(function nextEventLoop () {
68+
assert(Object.keys(elements[0].style).length === 0)
69+
done()
70+
})
6571
})
6672

67-
it('should set display none on elements if clientDisplayNone', function () {
73+
it('should set display none on elements if clientDisplayNone', function (done) {
6874
var config = { clientDisplayNone: true }
6975
socket.emit('execute', config)
70-
assert(elements[0].style.display === 'none')
71-
assert(elements[1].style.display === 'none')
76+
setTimeout(function nextEventLoop () {
77+
assert(elements[0].style.display === 'none')
78+
assert(elements[1].style.display === 'none')
79+
done()
80+
})
7281
})
7382

7483
it('should stop execution', function () {
@@ -97,55 +106,65 @@ describe('Karma', function () {
97106
assert.notStrictEqual(k.start, ADAPTER_START_FN)
98107
})
99108

100-
it('should not set up context if there was an error', function () {
109+
it('should not set up context if there was an error', function (done) {
101110
var config = ck.config = {
102111
clearContext: true
103112
}
104113

105114
socket.emit('execute', config)
106115

107-
var mockWindow = {}
116+
setTimeout(function nextEventLoop () {
117+
var mockWindow = {}
108118

109-
ck.error('page reload')
110-
ck.setupContext(mockWindow)
119+
ck.error('page reload')
120+
ck.setupContext(mockWindow)
111121

112-
assert(mockWindow.onbeforeunload == null)
113-
assert(mockWindow.onerror == null)
122+
assert(mockWindow.onbeforeunload == null)
123+
assert(mockWindow.onerror == null)
124+
done()
125+
})
114126
})
115127

116-
it('should setup context if there was error but clearContext config is false', function () {
128+
it('should setup context if there was error but clearContext config is false', function (done) {
117129
var config = ck.config = {
118130
clearContext: false
119131
}
120132

121133
socket.emit('execute', config)
122134

123-
var mockWindow = {}
135+
setTimeout(function nextEventLoop () {
136+
var mockWindow = {}
124137

125-
ck.error('page reload')
126-
ck.setupContext(mockWindow)
138+
ck.error('page reload')
139+
ck.setupContext(mockWindow)
127140

128-
assert(mockWindow.onbeforeunload != null)
129-
assert(mockWindow.onerror != null)
141+
assert(mockWindow.onbeforeunload != null)
142+
assert(mockWindow.onerror != null)
143+
done()
144+
})
130145
})
131146

132-
it('should error out if a script attempted to reload the browser after setup', function () {
147+
it('should error out if a script attempted to reload the browser after setup', function (done) {
133148
// Perform setup
134149
var config = ck.config = {
135150
clearContext: true
136151
}
137152
socket.emit('execute', config)
138-
var mockWindow = {}
139-
ck.setupContext(mockWindow)
140153

141-
// Spy on our error handler
142-
sinon.spy(k, 'error')
154+
setTimeout(function nextEventLoop () {
155+
var mockWindow = {}
156+
ck.setupContext(mockWindow)
157+
158+
// Spy on our error handler
159+
sinon.spy(k, 'error')
143160

144-
// Emulate an unload event
145-
mockWindow.onbeforeunload()
161+
// Emulate an unload event
162+
mockWindow.onbeforeunload()
146163

147-
// Assert our spy was called
148-
assert(k.error.calledWith('Some of your tests did a full page reload!'))
164+
// Assert our spy was called
165+
assert(k.error.calledWith('Some of your tests did a full page reload!'))
166+
done()
167+
})
149168
})
150169

151170
it('should report navigator name', function () {
@@ -439,12 +458,10 @@ describe('Karma', function () {
439458
}
440459

441460
socket.emit('execute', config)
461+
clock.tick(1)
442462
var CURRENT_URL = iframe.src
443-
444463
ck.complete()
445-
446464
clock.tick(1)
447-
448465
assert.strictEqual(iframe.src, CURRENT_URL)
449466
})
450467

@@ -455,6 +472,7 @@ describe('Karma', function () {
455472
}
456473

457474
socket.emit('execute', config)
475+
clock.tick(1)
458476
assert(!startSpy.called)
459477

460478
ck.loaded()

0 commit comments

Comments
 (0)
Please sign in to comment.