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)

`