|
15 | 15 | """Port of tests/page/page-event-pageerror.spec.ts.""" |
16 | 16 |
|
17 | 17 |
|
18 | | -from typing import Optional |
| 18 | +import asyncio |
| 19 | +from typing import Any, Callable, List, Optional |
19 | 20 |
|
20 | 21 | import pytest |
21 | 22 |
|
| 23 | +from playwright._impl._greenlets import wait_for_future |
22 | 24 | from playwright.sync_api import Page |
23 | | -from tests.server import Server, TestServerRequest |
| 25 | +from tests.server import Server, TestServerRequest, reactor |
24 | 26 |
|
25 | 27 |
|
26 | 28 | def expect_failure(request: pytest.FixtureRequest, reason: str) -> None: |
@@ -349,3 +351,59 @@ def handle(request: TestServerRequest) -> None: |
349 | 351 | assert "Invalid character" in error.message |
350 | 352 | else: |
351 | 353 | assert "illegal character" in error.message |
| 354 | + |
| 355 | + |
| 356 | +def test_ER_022_should_not_fire_for_a_caught_fetch_rejected_by_a_pending_navigation( |
| 357 | + page: Page, server: Server, sync_gather: Callable[..., List[Any]] |
| 358 | +) -> None: |
| 359 | + page.goto(server.EMPTY_PAGE) |
| 360 | + errors: List[str] = [] |
| 361 | + page.on("pageerror", lambda error: errors.append(error.message)) |
| 362 | + loop = page._loop |
| 363 | + wait_response: asyncio.Future[TestServerRequest] = loop.create_future() |
| 364 | + replacement_response: asyncio.Future[TestServerRequest] = loop.create_future() |
| 365 | + # Python: the server handlers run on the reactor thread. |
| 366 | + server.set_route( |
| 367 | + "/wait", |
| 368 | + lambda request: loop.call_soon_threadsafe(wait_response.set_result, request), |
| 369 | + ) |
| 370 | + server.set_route( |
| 371 | + "/replacement", |
| 372 | + lambda request: loop.call_soon_threadsafe( |
| 373 | + replacement_response.set_result, request |
| 374 | + ), |
| 375 | + ) |
| 376 | + with page.expect_event( |
| 377 | + "console", predicate=lambda message: message.text == "fetch done" |
| 378 | + ) as fetch_done: |
| 379 | + # Chromium does not run evaluate during a pending navigation, so the page starts the second fetch. |
| 380 | + page.evaluate( |
| 381 | + """() => { |
| 382 | + void fetch('/wait').catch(() => {}).then(() => fetch('/empty.html')).catch(() => {}).then(() => console.log('fetch done')); |
| 383 | + }""" |
| 384 | + ) |
| 385 | + |
| 386 | + def finish_navigation() -> None: |
| 387 | + wait_for_future(loop, page._dispatcher_fiber, replacement_response) |
| 388 | + response = replacement_response.result() |
| 389 | + # WebKit rejects the second fetch, because the document is about to go away. |
| 390 | + wait_for_future(loop, page._dispatcher_fiber, wait_response) |
| 391 | + reactor.callFromThread(wait_response.result().finish) |
| 392 | + fetch_done.value |
| 393 | + |
| 394 | + def finish() -> None: |
| 395 | + response.write(b"replacement") |
| 396 | + response.finish() |
| 397 | + |
| 398 | + reactor.callFromThread(finish) |
| 399 | + |
| 400 | + sync_gather( |
| 401 | + lambda: page.goto(server.PREFIX + "/replacement"), finish_navigation |
| 402 | + ) |
| 403 | + with page.expect_event("pageerror") as error_info: |
| 404 | + page.evaluate( |
| 405 | + "() => setTimeout(() => { throw new Error('genuine error'); }, 0)" |
| 406 | + ) |
| 407 | + error = error_info.value |
| 408 | + assert error.message == "genuine error" |
| 409 | + assert errors == ["genuine error"] |
0 commit comments