fix(browser): wait for iframe tester readiness before preparing (#10497) · vitest-dev/vitest@f26552c (original) (raw)
1
1
`import type { Context as OTELContext } from '@opentelemetry/api'
`
2
``
`-
import type { GlobalChannelIncomingEvent, IframeChannelIncomingEvent, IframeChannelOutgoingEvent, IframeViewportDoneEvent, IframeViewportFailEvent } from '@vitest/browser/client'
`
``
2
`+
import type { GlobalChannelIncomingEvent, IframeChannelEvent, IframeChannelOutgoingEvent, IframeViewportDoneEvent, IframeViewportFailEvent } from '@vitest/browser/client'
`
3
3
`import type { BrowserTesterOptions, SerializedConfig } from 'vitest'
`
4
4
`import type { FileSpecification } from 'vitest/internal/browser'
`
5
5
`import { channel, client, globalChannel } from '@vitest/browser/client'
`
`@@ -16,6 +16,8 @@ export class IframeOrchestrator {
`
16
16
`private cancelled = false
`
17
17
`private recreateNonIsolatedIframe = false
`
18
18
`private iframes = new Map<string, HTMLIFrameElement>()
`
``
19
`+
private readyIframes = new Set()
`
``
20
`+
private readyWaiters = new Map<string, () => void>()
`
19
21
``
20
22
`public eventTarget: EventTarget = new EventTarget()
`
21
23
``
`@@ -91,6 +93,8 @@ export class IframeOrchestrator {
`
91
93
``
92
94
`this.iframes.forEach(iframe => iframe.remove())
`
93
95
`this.iframes.clear()
`
``
96
`+
this.readyIframes.clear()
`
``
97
`+
this.readyWaiters.clear()
`
94
98
``
95
99
`for (let i = 0; i < options.files.length; i++) {
`
96
100
`if (this.cancelled) {
`
`@@ -149,8 +153,7 @@ export class IframeOrchestrator {
`
149
153
`// because we called "cleanup" in the previous run
`
150
154
`// the iframe is not removed immediately to let the user see the last test
`
151
155
`this.recreateNonIsolatedIframe = false
`
152
``
`-
this.iframes.get(ID_ALL)!.remove()
`
153
``
`-
this.iframes.delete(ID_ALL)
`
``
156
`+
this.removeIframe(ID_ALL)
`
154
157
`debug('recreate non-isolated iframe')
`
155
158
`}
`
156
159
``
`@@ -191,8 +194,7 @@ export class IframeOrchestrator {
`
191
194
`const file = spec.filepath
`
192
195
``
193
196
`if (this.iframes.has(file)) {
`
194
``
`-
this.iframes.get(file)!.remove()
`
195
``
`-
this.iframes.delete(file)
`
``
197
`+
this.removeIframe(file)
`
196
198
`}
`
197
199
``
198
200
`await this.prepareIframe(
`
`@@ -257,12 +259,14 @@ export class IframeOrchestrator {
`
257
259
`}
`
258
260
`else {
`
259
261
`this.iframes.set(iframeId, iframe)
`
260
``
`-
this.sendEventToIframe({
`
261
``
`-
event: 'prepare',
`
262
``
`-
iframeId,
`
263
``
`-
startTime,
`
264
``
`-
otelCarrier: this.traces.getContextCarrier(otelContext),
`
265
``
`-
}).then(resolve, error => reject(this.dispatchIframeError(error)))
`
``
262
`+
this.waitForReady(iframeId)
`
``
263
`+
.then(() => this.sendEventToIframe({
`
``
264
`+
event: 'prepare',
`
``
265
`+
iframeId,
`
``
266
`+
startTime,
`
``
267
`+
otelCarrier: this.traces.getContextCarrier(otelContext),
`
``
268
`+
}))
`
``
269
`+
.then(resolve, error => reject(this.dispatchIframeError(error)))
`
266
270
`}
`
267
271
`}
`
268
272
`iframe.onerror = (e) => {
`
`@@ -280,6 +284,34 @@ export class IframeOrchestrator {
`
280
284
`return iframe
`
281
285
`}
`
282
286
``
``
287
`+
private markReady(iframeId: string) {
`
``
288
`+
this.readyIframes.add(iframeId)
`
``
289
+
``
290
`+
const waiter = this.readyWaiters.get(iframeId)
`
``
291
`+
if (waiter) {
`
``
292
`+
this.readyWaiters.delete(iframeId)
`
``
293
`+
waiter()
`
``
294
`+
}
`
``
295
`+
}
`
``
296
+
``
297
`+
private waitForReady(iframeId: string): Promise {
`
``
298
`+
if (this.readyIframes.has(iframeId)) {
`
``
299
`+
return Promise.resolve()
`
``
300
`+
}
`
``
301
+
``
302
`+
return new Promise((resolve) => {
`
``
303
`+
this.readyWaiters.set(iframeId, resolve)
`
``
304
`+
})
`
``
305
`+
}
`
``
306
+
``
307
`+
private removeIframe(iframeId: string) {
`
``
308
`+
const iframe = this.iframes.get(iframeId)
`
``
309
`+
this.iframes.delete(iframeId)
`
``
310
`+
this.readyIframes.delete(iframeId)
`
``
311
`+
this.readyWaiters.delete(iframeId)
`
``
312
`+
iframe?.remove()
`
``
313
`+
}
`
``
314
+
283
315
`private loggedIframe = new WeakSet()
`
284
316
``
285
317
`private createWarningMessage(iframeId: string, location: string) {
`
`@@ -369,9 +401,13 @@ export class IframeOrchestrator {
`
369
401
`}
`
370
402
`}
`
371
403
``
372
``
`-
private async onIframeEvent(e: MessageEvent) {
`
``
404
`+
private async onIframeEvent(e: MessageEvent) {
`
373
405
`debug('iframe event', JSON.stringify(e.data))
`
374
406
`switch (e.data.event) {
`
``
407
`+
case 'ready': {
`
``
408
`+
this.markReady(e.data.iframeId)
`
``
409
`+
break
`
``
410
`+
}
`
375
411
`case 'viewport': {
`
376
412
`const { width, height, iframeId: id } = e.data
`
377
413
`const iframe = this.iframes.get(id)
`