Repository: m9810223/playwright-async-pytest (language: python)

Issue:
Test suite freezes during teardown
Hi, 

ever since the recent update my test suite freezes on teardown. I'm not sure what goes wrong, but here is a simple test case to reproduce:

I set up a test environment as follows:

```sh
python3 -m venv env
source env/bin/activate
pip install pytest-playwright-async
playwright install firefox
```

pytest.ini
```ini
[pytest]
asyncio_mode = auto
```

test_playwright.py
```py
from playwright.async_api import Page as APage
from playwright.sync_api import Page as SPage


async def test_async_api(page: APage):

    # access webpage
    await page.goto('https://playwright.dev/')


def test_sync_api(page: SPage):

    # access webpage
    page.goto('https://playwright.dev/')
```

Run the tests like this
`python -m pytest -sv --setup-show  --browser firefox test_playwright.py::<test_name>`

`test_sync_api` runs without issue, `test_async_api` gets as far as shown below, then freezes and needs to be stopped using Ctrl-c

```
platform linux -- Python 3.11.2, pytest-8.2.1, pluggy-1.5.0 -- /tmp/test_test/env/bin/python
cachedir: .pytest_cache
rootdir: /tmp/test_test
configfile: pytest.ini
plugins: asyncio-0.23.7, playwright-0.5.0, pytest_playwright_async-0.17.0, base-url-2.1.0
asyncio: mode=Mode.AUTO
collected 1 item                                                                                                                                                                           

test_playwright.py::test_async_api[firefox] 
SETUP    S event_loop_policy
SETUP    S pytestconfig
SETUP    S delete_output_dir (fixtures used: pytestconfig)
SETUP    S base_url
SETUP    S _verify_url (fixtures used: base_url)
SETUP    S browser_type_launch_args (fixtures used: pytestconfig)
SETUP    S playwright
SETUP    S browser_name['firefox']
SETUP    S browser_type (fixtures used: browser_name, playwright)
SETUP    S launch_browser (fixtures used: browser_type, browser_type_launch_args)
SETUP    S browser (fixtures used: launch_browser)
SETUP    S device (fixtures used: pytestconfig)
SETUP    S _pw_artifacts_folder
SETUP    S browser_context_args (fixtures used: _pw_artifacts_folder, base_url, device, playwright, pytestconfig)
        SETUP    F _artifacts_recorder (fixtures used: _pw_artifacts_folder, playwright, pytestconfig)
        SETUP    F new_context (fixtures used: _artifacts_recorder, browser, browser_context_args)
        SETUP    F context (fixtures used: new_context)
        SETUP    F page (fixtures used: context)
        SETUP    F event_loop
        test_playwright.py::test_async_api[firefox] (fixtures used: _artifacts_recorder, _pw_artifacts_folder, _verify_url, base_url, browser, browser_context_args, browser_name, browser_type, browser_type_launch_args, context, delete_output_dir, device, event_loop, event_loop_policy, launch_browser, new_context, page, playwright, pytestconfig, request)FAILED
        TEARDOWN F event_loop
        TEARDOWN F page
        TEARDOWN F context
```




Discussion / hints:
Hi @m-reichle

After some debugging, I think you can use:

- pytest-asyncio==0.20.3
- pytest==8.0.0
- nest-asyncio

and add

```py
# conftest.py
import asyncio
import nest_asyncio
import pytest_asyncio
@pytest_asyncio.fixture(scope='session')
def event_loop(): 
    policy = asyncio.get_event_loop_policy()
    loop = policy.new_event_loop()
    nest_asyncio.apply(loop) # !!
    yield loop
    loop.close()
```

---

Please let me know if this helps!

Note that https://github.com/erdewit/nest_asyncio has been marked as archived recently, so it's not a long-term solution.
Thanks for your quick response! 

This does indeed fix the issue, but, as jluebbe mentioned, the nest_asyncio project has sadly been archived when its maintainer passed away in March. 

From what I've read the python project isn't planning on including nested loops any time soon (https://github.com/python/cpython/issues/66435#issuecomment-2002071779). It seems https://github.com/oremanj/greenback does similar things, I've seen that mentioned in related discussions, not sure if it is applicable here. 
I have done some research and found no other solutions.
Until we find a suitable solution, this is how it will work for now.
If anyone finds a package or example that solves this problem, please let me know, I'd appreciate it.
Most of the packages I've found don't provide `event_loop`, which is what we need for `pytest-asyncio`.
The way to achieve this is to hack/modify `pytest-asyncio` with the packages.
Hi, thanks for this nice transfer to async.

I played last days with playwright and async. I need async because I'm using fastapi with uvicorn in parallel and mock some endpoints.

I'm wondering that @m-reichle is using `page: APage` instead of `page_async: APage`. Was this intended?

I need to vendor this project, because I don't use `pytest-asyncio`. Instead I'm using `anyio` which has pytest support and comes with `fastapi`. Do you tried this? I need to replace `@pytest_asyncio.fixture` to the normal decorator `@pytest.fixture`.
The  `page` in my example code is just the name of the parameter passed to function. It's not relevant to what it does. 
Hmmm. I don't understand. In pytest these arguments are normally fixtures and the fixture name for the async page in this pytest plugin ist `page_async`.
Apologies, I got that wrong. 

I created this issue a couple months ago and have since moved on to other topics, so I'm a bit hazy on what the details were. 

I've fixed the example code to this:

**test_playwright.py**
```
async def test_async_api(page_async):

    # access webpage
    await page_async.goto('https://playwright.dev/')


def test_sync_api(page):

    # access webpage
    page.goto('https://playwright.dev/')
```

However the actual issue seems to remain, the sync test runs without problems, while the async one gets stuck creating the context. 

From what I (vaguely) remember about the underlying problem it was that playwright-async-pytest cannot access the event_loop and tries to create one of its own from within the existing loop. @m9810223 can probably tell you more. 

I've deinstalled `pytest_asyncio`, installed `anyio` and patched this plugin with this:

<details>

<summary>anyio.diff</summary>

```diff
diff --git a/pytest_playwright_async.py b/pytest_playwright_async.py
index 858899a..15a6dbb 100644
--- a/pytest_playwright_async.py
+++ b/pytest_playwright_async.py
@@ -29,9 +29,8 @@ from playwright.async_api import StorageState
 from playwright.async_api import ViewportSize
 from playwright.async_api import async_playwright
 import pytest
-import pytest_asyncio
 from pytest_playwright.pytest_playwright import _build_artifact_test_folder
-from pytest_playwright.pytest_playwright import create_guid
+from pytest_playwright.pytest_playwright import _create_guid as create_guid
 from pytest_playwright.pytest_playwright import slugify
 
 
@@ -60,7 +59,7 @@ def browser_context_args_async(
     return context_args
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def _artifacts_recorder_async(
     request: pytest.FixtureRequest,
     playwright_async: Playwright,
@@ -77,7 +76,7 @@ async def _artifacts_recorder_async(
     await async_artifacts_recorder.did_finish_test(failed)
 
 
-@pytest_asyncio.fixture(scope='session')
+@pytest.fixture(scope='session')
 async def playwright_async() -> AsyncGenerator[Playwright, None]:
     apw = await async_playwright().start()
     yield apw
@@ -102,7 +101,7 @@ def launch_browser_async(
     return launch
 
 
-@pytest_asyncio.fixture(scope='session')
+@pytest.fixture(scope='session')
 async def browser_async(
     launch_browser_async: Callable[..., Awaitable[Browser]],
 ) -> AsyncGenerator[Browser, None]:
@@ -151,7 +150,7 @@ class AsyncCreateContextCallback(Protocol):
     ) -> BrowserContext: ...
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def new_context_async(
     browser_async: Browser,
     browser_context_args_async: dict,
@@ -183,12 +182,12 @@ async def new_context_async(
         await context_async.close()
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def context_async(new_context_async: AsyncCreateContextCallback) -> BrowserContext:
     return await new_context_async()
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def page_async(context_async: BrowserContext) -> Page:
     return await context_async.new_page()
 
@@ -303,4 +302,4 @@ class AsyncArtifactsRecorder:
                     )
                     self._screenshots.append(str(screenshot_path))
                 except Error:
-                    pass
\ No newline at end of file
+                    pass

```

</details>

Finally my **test_playwright.py**

```python
import anyio
from playwright.async_api import Page as APage
from playwright.sync_api import Page as SPage
import pytest


@pytest.fixture(scope="session", autouse=True)
def anyio_backend():
    return "asyncio"


async def test_async_api(page_async: APage):

    # access webpage
    await page_async.goto('https://playwright.dev/')


def test_sync_api(page: SPage):

    # access webpage
    page.goto('https://playwright.dev/')
```

Fix patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e4b0b47..60c6d77 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -24,25 +24,24 @@ repos:
   ### PYTHON
 
   - repo: https://github.com/psf/black
-    rev: 3702ba224ecffbcec30af640c149f231d90aebdb  # frozen: 24.4.2
+    rev: b965c2a5026f8ba399283ba3e01898b012853c79  # frozen: 24.8.0
     hooks: # https://black.readthedocs.io/en/stable/integrations/source_version_control.html
       - id: black
 
   - repo: https://github.com/charliermarsh/ruff-pre-commit
-    rev: fc609d3ce5e38f1d32423050196bf0ce4ecc59c1  # frozen: v0.4.5
+    rev: cc915bbf189077041f55bc59c4c0cf7f30cc921f  # frozen: v0.6.8
     hooks: # https://beta.ruff.rs/docs/installation-and-usage/#usage
       - id: ruff
 
   - repo: https://github.com/pre-commit/mirrors-mypy
-    rev: "e5ea6670624c24f8321f6328ef3176dbba76db46"  # frozen: v1.10.0
+    rev: "d4911cfb7f1010759fde68da196036feeb25b99d"  # frozen: v1.11.2
     hooks:
       - id: mypy
-        additional_dependencies: [types-all]
         args:
           - --explicit-package-bases
 
   - repo: https://github.com/asottile/pyupgrade
-    rev: 12af25eb252deaaecb6b259df40d01f42e716dc3  # frozen: v3.15.2
+    rev: a25bb536e10881d928ee824910cbaab8f4719bd1  # frozen: v3.17.0
     hooks:
       - id: pyupgrade
 
@@ -58,12 +57,12 @@ repos:
   ### MISC
 
   - repo: https://github.com/crate-ci/typos
-    rev: ee276ae87cb98d65004ebc0096d2c3a210ba9adc  # frozen: v1.21.0
+    rev: a0058970b777e6ddbbd92ed552885352ab4c873e  # frozen: v1.24.6
     hooks:
       - id: typos
 
   - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
-    rev: bdf9bad34ed8102dee37a1fabb11ebc40d51063f  # frozen: v2.13.0
+    rev: a6273196190bb0f68caf1dc68073cf62c719f725  # frozen: v2.14.0
     hooks:
       - id: pretty-format-yaml
         args:
@@ -80,15 +79,15 @@ repos:
       - id: check-builtin-literals # requires literal syntax when initializing empty or zero python builtin types.
       - id: check-case-conflict # checks for files that would conflict in case-insensitive filesystems.
       # - id: check-docstring-first # checks a common error of defining a docstring after code.
-      - id: check-executables-have-shebangs # ensures that (non-binary) executables have a shebang.
-      - id: check-json # checks json files for parseable syntax.
+      # - id: check-executables-have-shebangs # ensures that (non-binary) executables have a shebang.
+      # - id: check-json # checks json files for parseable syntax.
       - id: check-shebang-scripts-are-executable # ensures that (non-binary) files with a shebang are executable.
-      - id: pretty-format-json # sets a standard for formatting json files.
-        args:
-          - --autofix
-          - --indent=2
-          - --no-sort-keys
-          - --no-ensure-ascii
+      # - id: pretty-format-json # sets a standard for formatting json files.
+      #   args:
+      #     - --autofix
+      #     - --indent=2
+      #     - --no-sort-keys
+      #     - --no-ensure-ascii
       - id: check-merge-conflict # checks for files that contain merge conflict strings.
       # - id: check-symlinks # checks for symlinks which do not point to anything.
       - id: check-toml # checks toml files for parseable syntax.
diff --git a/README.md b/README.md
index f34508f..7c2d89b 100644
--- a/README.md
+++ b/README.md
@@ -19,25 +19,30 @@ pip install pytest-playwright-async
 # tests/conftest.py
 import asyncio
 
-import pytest_asyncio
+import nest_asyncio
+import pytest
 
 
-@pytest_asyncio.fixture(scope='session')
+@pytest.fixture(scope='session', autouse=True)
 def event_loop():  # https://pytest-asyncio.readthedocs.io/en/latest/reference/fixtures.html#fixtures
     policy = asyncio.get_event_loop_policy()
     loop = policy.new_event_loop()
+    nest_asyncio._patch_loop(loop)  # *
     yield loop
     loop.close()
 
+
+@pytest.fixture(scope='session', autouse=True)
+def anyio_backend():
+    return 'asyncio'
+
 ```
 
 ```py
 # tests/test_for_readme.py
 from playwright.async_api import Page
-import pytest
 
 
-@pytest.mark.asyncio
 async def test_page_async(page_async: Page):
     print(f'\n{page_async = }')
     await page_async.goto('https://playwright.dev/')
diff --git a/pdm.lock b/pdm.lock
index 085f758..51b4ae9 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -3,9 +3,28 @@
 
 [metadata]
 groups = ["default", "dev"]
-strategy = ["cross_platform"]
-lock_version = "4.4"
-content_hash = "sha256:e7d6cb114e204bce0c787f389da975161baf9e702ac1f42acb1c4946dcef3670"
+strategy = []
+lock_version = "4.5.0"
+content_hash = "sha256:0f35e4188f38dbbc45b67816b10d369cb0ed4241cce8a65b980093770f5845cb"
+
+[[metadata.targets]]
+requires_python = ">=3.8"
+
+[[package]]
+name = "anyio"
+version = "4.5.0"
+requires_python = ">=3.8"
+summary = "High level compatibility layer for multiple asynchronous event loop implementations"
+dependencies = [
+    "exceptiongroup>=1.0.2; python_version < \"3.11\"",
+    "idna>=2.8",
+    "sniffio>=1.1",
+    "typing-extensions>=4.1; python_version < \"3.11\"",
+]
+files = [
+    {file = "anyio-4.5.0-py3-none-any.whl", hash = "sha256:fdeb095b7cc5a5563175eedd926ec4ae55413bb4be5770c424af0ba46ccb4a78"},
+    {file = "anyio-4.5.0.tar.gz", hash = "sha256:c5a275fe5ca0afd788001f58fca1e69e29ce706d746e317d660e21f70c530ef9"},
+]
 
 [[package]]
 name = "appnope"
@@ -23,6 +42,7 @@ version = "2.4.1"
 summary = "Annotate AST trees with source code positions"
 dependencies = [
     "six>=1.12.0",
+    "typing; python_version < \"3.5\"",
 ]
 files = [
     {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"},
@@ -346,6 +366,9 @@ files = [
 name = "pickleshare"
 version = "0.7.5"
 summary = "Tiny 'shelve'-like database with concurrency support"
+dependencies = [
+    "pathlib2; python_version in \"2.6 2.7 3.2 3.3\"",
+]
 files = [
     {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"},
     {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"},
@@ -373,12 +396,12 @@ files = [
 
 [[package]]
 name = "pluggy"
-version = "1.4.0"
+version = "1.5.0"
 requires_python = ">=3.8"
 summary = "plugin and hook calling mechanisms for python"
 files = [
-    {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"},
-    {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"},
+    {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+    {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
 ]
 
 [[package]]
@@ -437,7 +460,7 @@ files = [
 
 [[package]]
 name = "pytest"
-version = "8.0.0"
+version = "8.3.3"
 requires_python = ">=3.8"
 summary = "pytest: simple powerful testing with Python"
 dependencies = [
@@ -445,25 +468,12 @@ dependencies = [
     "exceptiongroup>=1.0.0rc8; python_version < \"3.11\"",
     "iniconfig",
     "packaging",
-    "pluggy<2.0,>=1.3.0",
-    "tomli>=1.0.0; python_version < \"3.11\"",
+    "pluggy<2,>=1.5",
+    "tomli>=1; python_version < \"3.11\"",
 ]
 files = [
-    {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"},
-    {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"},
-]
-
-[[package]]
-name = "pytest-asyncio"
-version = "0.20.3"
-requires_python = ">=3.7"
-summary = "Pytest support for asyncio"
-dependencies = [
-    "pytest>=6.1.0",
-]
-files = [
-    {file = "pytest-asyncio-0.20.3.tar.gz", hash = "sha256:83cbf01169ce3e8eb71c6c278ccb0574d1a7a3bb8eaaf5e50e0ad342afb33b36"},
-    {file = "pytest_asyncio-0.20.3-py3-none-any.whl", hash = "sha256:f129998b209d04fcc65c96fc85c11e5316738358909a8399e93be553d7656442"},
+    {file = "pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2"},
+    {file = "pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181"},
 ]
 
 [[package]]
@@ -482,7 +492,7 @@ files = [
 
 [[package]]
 name = "pytest-playwright"
-version = "0.5.0"
+version = "0.5.2"
 requires_python = ">=3.8"
 summary = "A pytest wrapper with fixtures for Playwright to automate web browsers"
 dependencies = [
@@ -492,8 +502,8 @@ dependencies = [
     "python-slugify<9.0.0,>=6.0.0",
 ]
 files = [
-    {file = "pytest-playwright-0.5.0.tar.gz", hash = "sha256:f9f5ae8ade2f773e6e2cd85ec6bfff2ab287f7943108b3956fe5971324151622"},
-    {file = "pytest_playwright-0.5.0-py3-none-any.whl", hash = "sha256:b382c870384419c025d66aea14518bab71fb9e79917d4808692cde70d8c5216a"},
+    {file = "pytest_playwright-0.5.2-py3-none-any.whl", hash = "sha256:2c5720591364a1cdf66610b972ff8492512bc380953e043c85f705b78b2ed582"},
+    {file = "pytest_playwright-0.5.2.tar.gz", hash = "sha256:c6d603df9e6c50b35f057b0528e11d41c0963283e98c257267117f5ed6ba1924"},
 ]
 
 [[package]]
@@ -535,6 +545,16 @@ files = [
     {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
 ]
 
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+requires_python = ">=3.7"
+summary = "Sniff out which async library your code is running under"
+files = [
+    {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+    {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
+]
+
 [[package]]
 name = "stack-data"
 version = "0.6.3"
@@ -602,6 +622,9 @@ files = [
 name = "wcwidth"
 version = "0.2.13"
 summary = "Measures the displayed width of unicode strings in a terminal"
+dependencies = [
+    "backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"",
+]
 files = [
     {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"},
     {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"},
diff --git a/pyproject.toml b/pyproject.toml
index c71c417..555dcf5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,9 +5,10 @@ requires-python = ">=3.8"
 dependencies = [
     # https://pypi.org/project/pytest-playwright/
     # https://github.com/microsoft/playwright-pytest/blob/v0.5.0/pytest_playwright/pytest_playwright.py
-    "pytest-playwright>=0.5.0",
+    "pytest-playwright>=0.5.2", # https://github.com/microsoft/playwright-pytest/pull/236/files#diff-51d99e132c467c9f729b6b633a15ce5444e52872456e8e551146e0c87a694d31R582
     # https://github.com/microsoft/playwright-python/blob/main/local-requirements.txt
-    "pytest-asyncio",
+    "anyio",
+    "nest-asyncio",
 ]
 description = "ASYNC Pytest plugin for Playwright"
 license = { text = "MIT" }


Test patch:
diff --git a/src/pytest_playwright_async/pytest_playwright_async.py b/src/pytest_playwright_async/pytest_playwright_async.py
index a38c8b7..3d2a5ac 100644
--- a/src/pytest_playwright_async/pytest_playwright_async.py
+++ b/src/pytest_playwright_async/pytest_playwright_async.py
@@ -29,9 +29,8 @@
 from playwright.async_api import ViewportSize
 from playwright.async_api import async_playwright
 import pytest
-import pytest_asyncio
 from pytest_playwright.pytest_playwright import _build_artifact_test_folder
-from pytest_playwright.pytest_playwright import create_guid
+from pytest_playwright.pytest_playwright import _create_guid
 from pytest_playwright.pytest_playwright import slugify
 
 
@@ -60,7 +59,7 @@ def browser_context_args_async(
     return context_args
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def _artifacts_recorder_async(
     request: pytest.FixtureRequest,
     playwright_async: Playwright,
@@ -77,7 +76,7 @@ async def _artifacts_recorder_async(
     await async_artifacts_recorder.did_finish_test(failed)
 
 
-@pytest_asyncio.fixture(scope='session')
+@pytest.fixture(scope='session')
 async def playwright_async() -> AsyncGenerator[Playwright, None]:
     apw = await async_playwright().start()
     yield apw
@@ -85,7 +84,7 @@ async def playwright_async() -> AsyncGenerator[Playwright, None]:
 
 
 @pytest.fixture(scope='session')
-def browser_type_async(playwright_async: Playwright, browser_name: str) -> BrowserType:
+async def browser_type_async(playwright_async: Playwright, browser_name: str) -> BrowserType:
     return getattr(playwright_async, browser_name)
 
 
@@ -102,7 +101,7 @@ async def launch(**kwargs: Dict) -> Browser:
     return launch
 
 
-@pytest_asyncio.fixture(scope='session')
+@pytest.fixture(scope='session')
 async def browser_async(
     launch_browser_async: Callable[..., Awaitable[Browser]],
 ) -> AsyncGenerator[Browser, None]:
@@ -151,7 +150,7 @@ async def __call__(
     ) -> BrowserContext: ...
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def new_context_async(
     browser_async: Browser,
     browser_context_args_async: dict,
@@ -183,12 +182,12 @@ async def _close_wrapper(*args: Any, **kwargs: Any) -> None:
         await context_async.close()
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def context_async(new_context_async: AsyncCreateContextCallback) -> BrowserContext:
     return await new_context_async()
 
 
-@pytest_asyncio.fixture
+@pytest.fixture
 async def page_async(context_async: BrowserContext) -> Page:
     return await context_async.new_page()
 
@@ -286,7 +285,7 @@ async def on_did_create_browser_context(self, context_async: BrowserContext) ->
 
     async def on_will_close_browser_context(self, context_async: BrowserContext) -> None:
         if self._capture_trace:
-            trace_path = Path(self._pw_artifacts_folder.name) / create_guid()
+            trace_path = Path(self._pw_artifacts_folder.name) / _create_guid()
             await context_async.tracing.stop(path=trace_path)
             self._traces.append(str(trace_path))
         else:
@@ -295,7 +294,7 @@ async def on_will_close_browser_context(self, context_async: BrowserContext) ->
         if self._pytestconfig.getoption('--screenshot') in ['on', 'only-on-failure']:
             for page in context_async.pages:
                 try:
-                    screenshot_path = Path(self._pw_artifacts_folder.name) / create_guid()
+                    screenshot_path = Path(self._pw_artifacts_folder.name) / _create_guid()
                     await page.screenshot(
                         timeout=5000,
                         path=screenshot_path,
diff --git a/tests/conftest.py b/tests/conftest.py
index c5782f4..cc98c20 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1,11 +1,18 @@
 import asyncio
 
-import pytest_asyncio
+import nest_asyncio
+import pytest
 
 
-@pytest_asyncio.fixture(scope='session')
+@pytest.fixture(scope='session', autouse=True)
 def event_loop():  # https://pytest-asyncio.readthedocs.io/en/latest/reference/fixtures.html#fixtures
     policy = asyncio.get_event_loop_policy()
     loop = policy.new_event_loop()
+    nest_asyncio._patch_loop(loop)  # *
     yield loop
     loop.close()
+
+
+@pytest.fixture(scope='session', autouse=True)
+def anyio_backend():
+    return 'asyncio'
diff --git a/tests/test_for_readme.py b/tests/test_for_readme.py
index 2f238e5..1e10429 100644
--- a/tests/test_for_readme.py
+++ b/tests/test_for_readme.py
@@ -1,8 +1,6 @@
 from playwright.async_api import Page
-import pytest
 
 
-@pytest.mark.asyncio
 async def test_page_async(page_async: Page):
     print(f'\n{page_async = }')
     await page_async.goto('https://playwright.dev/')
diff --git a/tests/test_from_playwright_docs.py b/tests/test_from_playwright_docs.py
index 6cdca49..0bc5c32 100644
--- a/tests/test_from_playwright_docs.py
+++ b/tests/test_from_playwright_docs.py
@@ -6,10 +6,8 @@
 
 from playwright.async_api import Page
 from playwright.async_api import expect
-import pytest
 
 
-@pytest.mark.asyncio
 async def test_has_title(page_async: Page):
     await page_async.goto('https://playwright.dev/')
 
@@ -17,7 +15,6 @@ async def test_has_title(page_async: Page):
     await expect(page_async).to_have_title(re.compile('Playwright'))
 
 
-@pytest.mark.asyncio
 async def test_get_started_link(page_async: Page):
     await page_async.goto('https://playwright.dev/')
 
diff --git a/tests/test_playwright.py b/tests/test_playwright.py
index ea1c13e..78fc449 100644
--- a/tests/test_playwright.py
+++ b/tests/test_playwright.py
@@ -6,6 +6,12 @@
 from playwright.async_api import Page
 from playwright.async_api import Playwright
 from playwright.async_api import async_playwright
+from playwright.sync_api import Browser as SBrowser
+from playwright.sync_api import BrowserContext as SBrowserContext
+from playwright.sync_api import BrowserType as SBrowserType
+from playwright.sync_api import Page as SPage
+from playwright.sync_api import Playwright as SPlaywright
+from playwright.sync_api import sync_playwright
 import pytest
 
 
@@ -16,7 +22,6 @@ def check_title(title):
     assert 'Playwright' in title
 
 
-@pytest.mark.asyncio
 async def test_async_playwright():
     async with async_playwright() as playwright:
         async with await playwright.chromium.launch() as browser:
@@ -26,55 +31,92 @@ async def test_async_playwright():
                     check_title(await page.title())
 
 
+@pytest.mark.skip(reason='Not working')
+def test_sync_playwright():
+    with sync_playwright() as playwright:
+        with playwright.chromium.launch() as browser:
+            with browser.new_context() as context:
+                with context.new_page() as page:
+                    page.goto(URL)
+                    check_title(page.title())
+
+
 # https://playwright.dev/python/docs/test-runners#fixtures
 
 
-@pytest.mark.asyncio
 async def test_is_working_playwright_async(playwright_async: Playwright):
     print(f'\n{playwright_async = }')
-    assert type(playwright_async) == Playwright
+    assert isinstance(playwright_async, Playwright)
+
+
+def test_is_working_playwright_sync(playwright: SPlaywright):
+    print(f'\n{playwright = }')
+    assert isinstance(playwright, SPlaywright)
 
 
-@pytest.mark.asyncio
 async def test_is_working_browser_async(browser_async: Browser):
     print(f'\n{browser_async = }')
-    assert type(browser_async) == Browser
+    assert isinstance(browser_async, Browser)
+
 
+def test_is_working_browser_sync(browser: SBrowser):
+    print(f'\n{browser = }')
+    assert isinstance(browser, SBrowser)
 
-@pytest.mark.asyncio
-async def test_is_working_browser_name(browser_name: str):
+
+async def test_is_working_browser_name_async(browser_name: str):
     print(f'\n{browser_name = }')
     assert isinstance(browser_name, str)
 
 
-@pytest.mark.asyncio
-async def test_is_working_browser_channel(browser_channel: t.Optional[str]):
+def test_is_working_browser_name(browser_name: str):
+    print(f'\n{browser_name = }')
+    assert isinstance(browser_name, str)
+
+
+async def test_is_working_browser_channel_async(browser_channel: t.Optional[str]):
+    print(f'\n{browser_channel = }')
+    assert isinstance(browser_channel, str) or browser_channel is None
+
+
+def test_is_working_browser_channel(browser_channel: t.Optional[str]):
     print(f'\n{browser_channel = }')
     assert isinstance(browser_channel, str) or browser_channel is None
 
 
-@pytest.mark.asyncio
 async def test_is_working_context_async(context_async: BrowserContext):
     print(f'\n{context_async = }')
-    assert type(context_async) == BrowserContext
+    assert isinstance(context_async, BrowserContext)
+
+
+def test_is_working_context_sync(context: SBrowserContext):
+    print(f'\n{context = }')
+    assert isinstance(context, SBrowserContext)
 
 
-@pytest.mark.asyncio
 async def test_is_working_browser_context_args_async(browser_context_args_async: dict):
     print(f'\n{browser_context_args_async = }')
     assert isinstance(browser_context_args_async, dict)
 
 
-@pytest.mark.asyncio
+def test_is_working_browser_context_args_sync(browser_context_args: dict):
+    print(f'\n{browser_context_args = }')
+    assert isinstance(browser_context_args, dict)
+
+
 async def test_is_working_page_async(page_async: Page):
     print(f'\n{page_async = }')
-    assert type(page_async) == Page
+    assert isinstance(page_async, Page)
+
+
+def test_is_working_page_sync(page: SPage):
+    print(f'\n{page = }')
+    assert isinstance(page, SPage)
 
 
 ###
 
 
-@pytest.mark.asyncio
 async def test_browser_context_args_async(browser_context_args_async: t.Dict):
     print(f'\n{browser_context_args_async, type(browser_context_args_async) = }')
     async with async_playwright() as playwright:
@@ -85,7 +127,17 @@ async def test_browser_context_args_async(browser_context_args_async: t.Dict):
                     check_title(await page.title())
 
 
-@pytest.mark.asyncio
+@pytest.mark.skip(reason='Not working')
+def test_browser_context_args_sync(browser_context_args: t.Dict):
+    print(f'\n{browser_context_args, type(browser_context_args) = }')
+    with sync_playwright() as playwright:
+        with playwright.chromium.launch() as browser:
+            with browser.new_context(**browser_context_args) as context:
+                with context.new_page() as page:
+                    page.goto(URL)
+                    check_title(page.title())
+
+
 async def test_playwright_async(playwright_async: Playwright):
     print(f'\n{playwright_async, type(playwright_async) = }')
     async with await playwright_async.chromium.launch() as browser:
@@ -95,7 +147,15 @@ async def test_playwright_async(playwright_async: Playwright):
                 check_title(await page.title())
 
 
-@pytest.mark.asyncio
+def test_playwright_sync(playwright: SPlaywright):
+    print(f'\n{playwright, type(playwright) = }')
+    with playwright.chromium.launch() as browser:
+        with browser.new_context() as context:
+            with context.new_page() as page:
+                page.goto(URL)
+                check_title(page.title())
+
+
 async def test_browser_type_async(browser_type_async: BrowserType):
     print(f'\n{browser_type_async, type(browser_type_async) = }')
     async with await browser_type_async.launch() as browser:
@@ -105,7 +165,15 @@ async def test_browser_type_async(browser_type_async: BrowserType):
                 check_title(await page.title())
 
 
-@pytest.mark.asyncio
+def test_browser_type_sync(browser_type: SBrowserType):
+    print(f'\n{browser_type, type(browser_type) = }')
+    with browser_type.launch() as browser:
+        with browser.new_context() as context:
+            with context.new_page() as page:
+                page.goto(URL)
+                check_title(page.title())
+
+
 async def test_launch_browser_async(launch_browser_async: t.Callable[..., t.Awaitable[Browser]]):
     print(f'\n{launch_browser_async, type(launch_browser_async) = }')
     browser = await launch_browser_async()
@@ -115,7 +183,15 @@ async def test_launch_browser_async(launch_browser_async: t.Callable[..., t.Awai
             check_title(await page.title())
 
 
-@pytest.mark.asyncio
+def test_launch_browser_sync(launch_browser: t.Callable[..., SBrowser]):
+    print(f'\n{launch_browser, type(launch_browser) = }')
+    browser = launch_browser()
+    with browser.new_context() as context:
+        with context.new_page() as page:
+            page.goto(URL)
+            check_title(page.title())
+
+
 async def test_browser_async(browser_async: Browser):
     print(f'\n{browser_async, type(browser_async) = }')
     async with await browser_async.new_context() as context:
@@ -124,7 +200,14 @@ async def test_browser_async(browser_async: Browser):
             check_title(await page.title())
 
 
-@pytest.mark.asyncio
+def test_browser_sync(browser: SBrowser):
+    print(f'\n{browser, type(browser) = }')
+    with browser.new_context() as context:
+        with context.new_page() as page:
+            page.goto(URL)
+            check_title(page.title())
+
+
 async def test_context_async(context_async: BrowserContext):
     print(f'\n{context_async = }')
     async with await context_async.new_page() as page:
@@ -132,8 +215,20 @@ async def test_context_async(context_async: BrowserContext):
         check_title(await page.title())
 
 
-@pytest.mark.asyncio
+def test_context_sync(context: SBrowserContext):
+    print(f'\n{context = }')
+    with context.new_page() as page:
+        page.goto(URL)
+        check_title(page.title())
+
+
 async def test_page_async(page_async: Page):
     print(f'\n{page_async = }')
     await page_async.goto(URL)
     check_title(await page_async.title())
+
+
+def test_page_sync(page: SPage):
+    print(f'\n{page = }')
+    page.goto(URL)
+    check_title(page.title())