binderhub
binderhub copied to clipboard
Switch to using pytest-playwright-asyncio
As part of https://github.com/jupyterhub/binderhub/pull/1891, we're using the sync playwright api even in our async tests. playwright's sync api is written as a wrapper on top of its async api, so we have to use some hacks to use it given we have async tests.
so we currently have: async -> sync -> async
We can switch to pytest-playwright-asyncio and cut out the middle sync part.
I spent 30min trying this out as part of #1891, with this diff:
diff --git a/dev-requirements.txt b/dev-requirements.txt
index 13e76f2..73ad164 100644
--- a/dev-requirements.txt
+++ b/dev-requirements.txt
@@ -12,6 +12,6 @@ pytest
pytest-asyncio
pytest-cov
pytest-timeout
-pytest_playwright
+pytest-playwright-asyncio
requests
ruamel.yaml>=0.17.30
diff --git a/integration-tests/conftest.py b/integration-tests/conftest.py
index f5632ca..e69de29 100644
--- a/integration-tests/conftest.py
+++ b/integration-tests/conftest.py
@@ -1,6 +0,0 @@
-import nest_asyncio
-
-
-def pytest_configure(config):
- # Required for playwright to be run from within pytest
- nest_asyncio.apply()
diff --git a/integration-tests/test_ui.py b/integration-tests/test_ui.py
index 2e4e4f8..db4c359 100644
--- a/integration-tests/test_ui.py
+++ b/integration-tests/test_ui.py
@@ -9,9 +9,10 @@ import time
import pytest
import requests
import requests.exceptions
-from playwright.sync_api import Page
+from playwright.async_api import Page
from binderhub import __version__ as binder_version
+
from binderhub.tests.utils import async_requests, random_port
@@ -104,15 +105,15 @@ async def test_loading_page(
provider_spec = f"{provider_prefix}/{spec}"
query = f"{path_type}path={path}" if path else ""
uri = f"/v2/{provider_spec}?{query}"
- r = page.goto(local_hub_local_binder + uri)
+ r = await page.goto(local_hub_local_binder + uri)
assert r.status == status_code
if status_code == 200:
- assert page.query_selector("#log-container")
- iframe = page.query_selector("#nbviewer-preview iframe")
+ assert page.locator("#log-container")
+ iframe = page.locator("#nbviewer-preview iframe")
assert iframe is not None
- nbviewer_url = iframe.get_attribute("src")
+ nbviewer_url = await iframe.get_attribute("src")
r = await async_requests.get(nbviewer_url)
assert r.status_code == 200, f"{r.status_code} {nbviewer_url}"
@@ -153,35 +154,35 @@ async def test_loading_page(
async def test_main_page(
local_hub_local_binder, page: Page, repo, ref, path, path_type, shared_url
):
- resp = page.goto(local_hub_local_binder)
+ resp = await page.goto(local_hub_local_binder)
assert resp.status == 200
- page.get_by_placeholder("GitHub repository name or URL").type(repo)
+ await page.get_by_placeholder("GitHub repository name or URL").type(repo)
if ref:
- page.locator("#ref").type(ref)
+ await page.locator("#ref").type(ref)
if path_type:
- page.query_selector("#url-or-file-btn").click()
+ await page.locator("#url-or-file-btn").click()
if path_type == "file":
- page.locator("a:text-is('File')").click()
+ await page.locator("a:text-is('File')").click()
elif path_type == "url":
- page.locator("a:text-is('URL')").click()
+ await page.locator("a:text-is('URL')").click()
else:
raise ValueError(f"Unknown path_type {path_type}")
if path:
- page.locator("#filepath").type(path)
+ await page.locator("#filepath").type(path)
assert (
- page.query_selector("#basic-url-snippet").inner_text()
+ await page.locator("#basic-url-snippet").inner_text()
== f"{local_hub_local_binder}{shared_url}"
)
-
async def test_about_page(local_hub_local_binder, page: Page):
- r = page.goto(f"{local_hub_local_binder}about")
+ r = await page.goto(f"{local_hub_local_binder}about")
assert r.status == 200
- assert "This website is powered by" in page.content()
- assert binder_version.split("+")[0] in page.content()
+ content = await page.content()
+ assert "this website is powered by" in content
+ assert binder_version.split("+")[0] in content
However, I could not get it to work - things seem to get stuck in the await page.goto.
JupyterHub implements its own pytest fixture, however that means we don't get the ability to upload traces for debugging only on failure. We could implement that ourselves too, but using the upstream provided pytest plugin feels better longer term.