microsoft / playwright-pytest

Pytest plugin to write end-to-end browser tests with Playwright.
https://playwright.dev/python/docs/test-runners
Apache License 2.0
440 stars 69 forks source link

Playwright does not play right with asyncio #240

Open Xowap opened 3 months ago

Xowap commented 3 months ago

Summary

This issue has been previously reported in issues microsoft/playwright-pytest#167, microsoft/playwright-pytest#46, and microsoft/playwright-pytest#29, but has not been fully resolved as the underlying root cause remains unaddressed.

Problem Description

I have observed that the playwright fixture installs a running event loop in the current thread. This behavior leads to subsequent calls to asyncio's run or similar functions becoming unsuccessful. Due to the abstract nature of the Playwright code, I have not yet pinpointed the exact source of the problem.

Additionally, I am unable to reproduce this issue by directly invoking the asyncio API within basic Python scripts not involving Playwright, so I don't really understand the mechanics at play here.

Reproduction

I have created a minimal reproduction repository to demonstrate the issue. Here is a sample test file that highlights the problem:

import asyncio
import pytest

def get_true():
    return True

async def a_get_true():
    return get_true()

@pytest.mark.playwright
def test_pw_true(playwright):
    pass

def test_true():
    assert get_true()

def test_a_true():
    assert asyncio.run(a_get_true())

@pytest.mark.asyncio
async def test_pa_true():
    assert await a_get_true()

In this script, any asynchronous test that follows a test using the playwright fixture results in an error. Changing the test order or skipping the test_pw_true test, which is currently empty, prevents the issue from occurring.

Suggested Solution

One potential solution could be to initialize Playwright in a separate thread or process, with the Pytest API offering a wrapper to handle inter-thread communication. However, this approach might be overly simplistic and may need further refinement.

Temporary Workaround

While awaiting a more permanent and clean solution, I will continue using the hacks mentioned in the above issues. Nonetheless, a robust and canonical fix would be greatly appreciated.

Thank you!

bartfeenstra commented 2 weeks ago

I have run into this as well. Please add a warning to the documentation, as this means the package is outright incompatible with anything that requires an async test method, because the system under test is asynchronous. It appears that this is the sole reason I cannot use this package and will have to resort to using the JS version of Playwright instead (which comes with the additional overhead of not being able to reuse existing Python fixtures) (not a rant, just an explanation of why this has a bigger impact than it may seem at first).

mxschmitt commented 3 days ago

We are in the process of providing async fixtures - would the following work for you? https://github.com/microsoft/playwright-pytest/issues/74#issuecomment-2476207495 - I think the problem is when you mix that pytest-playwright creates its own loop while the pytest-asyncio does as well. They don't work together that well. When going fully sync or fully async things should work.

bartfeenstra commented 3 days ago

I took a quick look at that code and so far it looks good! I'll test is out over the coming days.

mxschmitt commented 3 days ago

Appreciate a ton! Do you have a opinion about betting on asyncio vs. anyio? I saw a community plugin using anyio but not sure how common it is in the testing pytest world.

bartfeenstra commented 3 days ago

I personally only ever use asyncio directly, although some tools I use use AnyIO, but even that is just a wrapper around asyncio and Trio. When authoring libraries, I find it's usually enough to not start one's own loops (unless in a dedicated, synchronous entry point), and allow any API functions to be used through async/await (e.g. API methods return coroutines). So if pytest-playwright(-asyncio) provides asynchronous text fixtures and APIs, then those should work in any testing environment that has a running event loop already (e.g. pytest-asyncio).