Repository: microsoft/playwright-python

Issue:
[BUG] Task exception was never retrieved when continue_ race with page closed event
**Context:**
- Playwright Version: 1.19.0
- Operating System: Mac
- Python version: 3.9
- Browser: Chromium

**Code Snippet**

```
import asyncio
import logging

from playwright.async_api import async_playwright, Route, Error as PlaywrightApiError


async def on_request(route: Route):
    # simulate some time-consuming operations
    await asyncio.sleep(0.5)
    await route.continue_()


async def main():
    async with async_playwright() as p:
        browser = await p.chromium.launch()

        for _ in range(2):
            page = await browser.new_page()
            page.set_default_timeout(500)
            await page.route("**/*", on_request)
            try:
                await page.goto("https://playwright.dev")
            except PlaywrightApiError as exc:
                logging.error(f"exc: {exc}")
            finally:
                await page.close()

        await asyncio.sleep(1)

if __name__ == '__main__':
    logging.basicConfig(level=logging.DEBUG)
    asyncio.run(main(), debug=True)

```

OUTPUT:
```text
...
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-8' coro=<Channel.send() done, defined at /Users/x/PycharmProjects/playwright-python/playwright/_impl/_connection.py:38> exception=Error('Target page, context or browser has been closed') created at /usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/tasks.py:361>
source_traceback: Object created at (most recent call last):
  File "/Users/x/Library/Application Support/JetBrains/PyCharmCE2021.3/scratches/scratch.py", line 32, in <module>
    asyncio.run(main(), debug=True)
  File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/runners.py", line 44, in run
    return loop.run_until_complete(main)
  File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 629, in run_until_complete
    self.run_forever()
  File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 596, in run_forever
    self._run_once()
  File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 1882, in _run_once
    handle._run()
  File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/events.py", line 80, in _run
    self._context.run(self._callback, *self._args)
  File "/Users/x/Library/Application Support/JetBrains/PyCharmCE2021.3/scratches/scratch.py", line 10, in on_request
    await route.continue_()
  File "/Users/x/PycharmProjects/playwright-python/playwright/async_api/_generated.py", line 762, in continue_
    await self._async(
  File "/Users/x/PycharmProjects/playwright-python/playwright/_impl/_network.py", line 269, in continue_
    await self._race_with_page_close(
  File "/Users/x/PycharmProjects/playwright-python/playwright/_impl/_network.py", line 289, in _race_with_page_close
    [asyncio.create_task(future), page._closed_or_crashed_future],
  File "/usr/local/Cellar/python@3.9/3.9.9/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/tasks.py", line 361, in create_task
    task = loop.create_task(coro)
Traceback (most recent call last):
  File "/Users/x/PycharmProjects/playwright-python/playwright/_impl/_connection.py", line 39, in send
    return await self.inner_send(method, params, False)
  File "/Users/x/PycharmProjects/playwright-python/playwright/_impl/_connection.py", line 63, in inner_send
    result = next(iter(done)).result()
playwright._impl._api_types.Error: Target page, context or browser has been closed
INFO:asyncio:<_UnixReadPipeTransport fd=8 polling> was closed by peer
DEBUG:asyncio:process 98057 exited with returncode 0
INFO:asyncio:<_UnixSubprocessTransport pid=98057 running stdin=<_UnixWritePipeTransport closed fd=7 closed> stdout=<_UnixReadPipeTransport closed fd=8 closed>> exited with return code 0
DEBUG:asyncio:Close <_UnixSelectorEventLoop running=False closed=False debug=True>
...
```

**Describe the bug**

with a long-running browser, after page created, hook the route of page. and do some operation long than timeout, and will see some error log print.
maybe bug is at here.  fut task is created but without any exception handler.

https://github.com/microsoft/playwright-python/blob/54b4bdfaf5c0484b2e6cc4301c9c19a2cf5fcf0b/playwright/_impl/_network.py#L282-L291

**POC to fix**
```
    async def _race_with_page_close(self, future: Coroutine) -> None:
        if hasattr(self.request.frame, "_page"):
            page = self.request.frame._page
            # When page closes or crashes, we catch any potential rejects from this Route.
            # Note that page could be missing when routing popup's initial request that
            # does not have a Page initialized just yet.
            fut = asyncio.create_task(future)
            await asyncio.wait(
                [fut, page._closed_or_crashed_future],
                return_when=asyncio.FIRST_COMPLETED,
            )
            if page._closed_or_crashed_future.done():
                await asyncio.gather(fut, return_exceptions=True)
        else:
            await future
```


Discussion:
Sounds good! do you want to create a Pull Request for it?

Resolving patch:
diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py
--- a/playwright/_impl/_network.py
+++ b/playwright/_impl/_network.py
@@ -285,10 +285,13 @@ async def _race_with_page_close(self, future: Coroutine) -> None:
             # When page closes or crashes, we catch any potential rejects from this Route.
             # Note that page could be missing when routing popup's initial request that
             # does not have a Page initialized just yet.
+            fut = asyncio.create_task(future)
             await asyncio.wait(
-                [asyncio.create_task(future), page._closed_or_crashed_future],
+                [fut, page._closed_or_crashed_future],
                 return_when=asyncio.FIRST_COMPLETED,
             )
+            if page._closed_or_crashed_future.done():
+                await asyncio.gather(fut, return_exceptions=True)
         else:
             await future
 


Test patch: