{"id":13772051,"url":"https://github.com/pyodide/pytest-pyodide","last_synced_at":"2025-12-30T00:21:20.485Z","repository":{"id":45842520,"uuid":"514846676","full_name":"pyodide/pytest-pyodide","owner":"pyodide","description":"Pytest plugin for testing applications that use Pyodide","archived":false,"fork":false,"pushed_at":"2025-04-07T18:17:43.000Z","size":299,"stargazers_count":27,"open_issues_count":18,"forks_count":17,"subscribers_count":7,"default_branch":"main","last_synced_at":"2025-05-07T22:16:29.836Z","etag":null,"topics":["pyodide","pytest","pytest-plugin"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pyodide.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":null,"open_collective":"pyodide"}},"created_at":"2022-07-17T13:09:26.000Z","updated_at":"2025-03-16T08:26:02.000Z","dependencies_parsed_at":"2024-01-01T19:26:24.399Z","dependency_job_id":"af78617c-2b2d-40a3-b770-de15350145ac","html_url":"https://github.com/pyodide/pytest-pyodide","commit_stats":{"total_commits":110,"total_committers":12,"mean_commits":9.166666666666666,"dds":0.5909090909090908,"last_synced_commit":"0b78ac0e85d6402b445e06aae0039771c753a476"},"previous_names":[],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyodide%2Fpytest-pyodide","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyodide%2Fpytest-pyodide/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyodide%2Fpytest-pyodide/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pyodide%2Fpytest-pyodide/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pyodide","download_url":"https://codeload.github.com/pyodide/pytest-pyodide/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253518941,"owners_count":21921074,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["pyodide","pytest","pytest-plugin"],"created_at":"2024-08-03T17:00:59.317Z","updated_at":"2025-12-30T00:21:20.472Z","avatar_url":"https://github.com/pyodide.png","language":"Python","funding_links":["https://opencollective.com/pyodide"],"categories":["Plugins"],"sub_categories":[],"readme":"# pytest-pyodide\n\n[![PyPI Latest Release](https://img.shields.io/pypi/v/pytest-pyodide.svg)](https://pypi.org/project/pytest-pyodide/)\n[![main](https://github.com/pyodide/pytest-pyodide/actions/workflows/build.yaml/badge.svg)](https://github.com/pyodide/pytest-pyodide/actions/workflows/build.yaml)\n[![codecov](https://codecov.io/gh/pyodide/pytest-pyodide/branch/main/graph/badge.svg?token=U7tWHpJj5c)](https://codecov.io/gh/pyodide/pytest-pyodide)\n\n\nPytest plugin for testing applications that use Pyodide\n\n## Installation\n\npytest-pyodide requires Python 3.10+ and can be installed with\n```\npip install pytest-pyodide\n```\nYou would also need one at least one of the following runtimes:\n - Chrome and chromedriver\n - Firefox and geckodriver\n - Safari and safaridriver\n - node v18+\n\n## Github Reusable workflow\n\n`pytest-pyodide` also supports testing on GitHub Action by means of a reusable workflow in [/.github/workflows/main.yaml](/.github/workflows/main.yaml) This allows you to test on a range of browser/OS combinations without having to install all the testing stuff, and integrate it easily into your CI process.\n\nIn your github actions workflow, call it with as a separate job. To pass in your built wheel, use an `upload-artifact` step in your build step.\n\nThis will run your tests on the given browser/Pyodide version/OS configuration. It runs pytest in the root of your repo, which should catch any test_\\*.py files in subfolders.\n\n\u003e [!NOTE]\n\u003e The following workflow might be out of date. Please refer to the latest version from the [Pyodide documentation](https://pyodide.org/en/stable/development/building-and-testing-packages.html).\n```\njobs:\n  # Build for pyodide 0.27\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v4\n    - uses: actions/setup-python@v5\n      with:\n        python-version: 3.12 # same as the version in Pyodide\n    - uses: mymindstorm/setup-emsdk@v14\n      with:\n        version: 3.1.58\n    - run: pip install pyodide-build\u003e=0.27.0\n    - run: pyodide build\n    - uses: actions/upload-artifact@v4\n      with:\n        name: pyodide_wheel\n        path: dist\n  # this is the job which you add to run pyodide-test\n  test:\n    needs: build\n    uses: pyodide/pytest-pyodide/.github/workflows/main.yaml@main\n    with:\n      build-artifact-name: pyodide_wheel\n      build-artifact-path: dist\n      browser: firefox\n      runner: selenium\n      pyodide-version: 0.27.2\n```\n\nIf you want to run on multiple browsers / pyodide versions etc., you can either use a matrix strategy and run main.yaml as above, or you can use testall.yaml. This by default tests on all browsers (and node) with multiple configurations. If you want to reduce the configurations you can filter with lists of browsers, runners, pyodide-versions as shown below.\n```\n  test:\n    needs: build\n    uses: pyodide/pytest-pyodide/.github/workflows/testall.yaml@main\n    with:\n      build-artifact-name: pyodide wheel\n      build-artifact-path: dist\n      pyodide-versions: \"0.26.4, 0.27.2\"\n      runners: \"selenium, playwright\"\n      browsers: \"firefox, chrome, node\"\n      os: \"ubuntu-latest, macos-latest\"\n```\n\n## Usage\n\n1. First you need a compatible version of Pyodide. You can download the Pyodide build artifacts from releases with,\n   ```bash\n   wget https://github.com/pyodide/pyodide/releases/download/0.27.2/pyodide-0.27.2.tar.bz2\n   tar xjf pyodide-0.27.2.tar.bz2\n   mv pyodide dist/\n   ```\n\n2. You can then use the provided fixtures (`selenium`, `selenium_standalone`) in tests,\n   ```py\n   def test_a(selenium):\n       selenium.run(\"assert 1+1 == 2\")   # run Python with Pyodide\n\n   ```\n   which you can run with\n   ```bash\n   pytest --dist-dir=./dist/\n   ```\n\n## `run_in_pyodide`\n\nSome tests simply involve running a chunk of code in Pyodide and ensuring it\ndoesn't error. In this case, one can use the `run_in_pyodide` decorate from\n`pytest_pyodide`, e.g.\n\n```python\nfrom pytest_pyodide import run_in_pyodide\n@run_in_pyodide\ndef test_add(selenium):\n    assert 1 + 1 == 2\n```\n\nIn this case, the body of the function will automatically be run in Pyodide. The\ndecorator can also be called with a `packages` argument to load packages before\nrunning the test. For example:\n\n```python\nfrom pytest_pyodide import run_in_pyodide\n@run_in_pyodide(packages = [\"regex\"])\ndef test_regex(selenium_standalone):\n    import regex\n    assert regex.search(\"o\", \"foo\").end() == 2\n```\n\nYou can also use `@run_in_pyodide` with\n`pytest.mark.parametrize`, with `hypothesis`, etc. `@run_in_pyodide` MUST be the\ninnermost decorator. Any decorators inside of `@run_in_pyodide` will be have no\neffect on the behavior of the test.\n\n```python\nfrom pytest_pyodide import run_in_pyodide\n@pytest.mark.parametrize(\"x\", [1, 2, 3])\n@run_in_pyodide(packages = [\"regex\"])\ndef test_type_of_int(selenium, x):\n    assert type(x) is int\n```\n\nThe first argument to a `@run_in_pyodide` function must be a browser runner,\ngenerally a `selenium` fixture. The remaining arguments and the return value of\nthe `@run_in_pyodide` function must be picklable. The arguments will be pickled\nin the host Python and unpickled in the Pyodide Python. The reverse will happen\nto the return value. The first `selenium` argument will be `None` inside the\nbody of the function (it is used internally by the fixture). Note that a\nconsequence of this is that the received arguments are copies. Changes made to\nan argument will not be reflected in the host Python:\n```py\n@run_in_pyodide\ndef mutate_dict(selenium, x):\n    x[\"a\"] = -1\n    return x\n\ndef test_mutate_dict():\n    d = {\"a\" : 9, \"b\" : 7}\n    assert mutate_dict(d) == { \"a\" : -1, \"b\" : 7 }\n    # d is unchanged because it was implicitly copied into the Pyodide runtime!\n    assert d == {\"a\" : 9, \"b\" : 7}\n```\n\nYou can also use fixtures as long as the return values of the fixtures are\npicklable (most commonly, if they are `None`). As a special case, the function\nwill see the `selenium` fixture as `None` inside the test.\n\nIf you need to return a persistent reference to a Pyodide Python object, you can\nuse the special `PyodideHandle` class:\n```py\n@run_in_pyodide\ndef get_pyodide_handle(selenium):\n    from pytest_pyodide.decorator import PyodideHandle\n    d = { \"a\" : 2 }\n    return PyodideHandle(d)\n\n@run_in_pyodide\ndef set_value(selenium, h, key, value):\n    h[key] = value\n\n@run_in_pyodide\ndef get_value(selenium, h, key):\n    return h[key]\n\ndef test_pyodide_handle(selenium):\n    h = get_pyodide_handle(selenium)\n    assert get_value(selenium, h, \"a\") == 2\n    set_value(selenium, h, \"a\", 3)\n    assert get_value(selenium, h, \"a\") == 3\n```\nThis can be used to create fixtures for use with `@run_in_pyodide`.\n\nIt is possible to use `run_in_pyodide` as an inner function:\n\n```py\ndef test_inner_function(selenium):\n    @run_in_pyodide\n    def inner_function(selenium, x):\n        assert x == 6\n        return 7\n    assert inner_function(selenium_mock, 6) == 7\n```\nHowever, the function will not see closure variables at all:\n\n```py\ndef test_inner_function_closure(selenium):\n    x = 6\n    @run_in_pyodide\n    def inner_function(selenium):\n        assert x == 6\n        return 7\n    # Raises `NameError: 'x' is not defined`\n    assert inner_function(selenium_mock) == 7\n```\nThus, the only value of inner `@run_in_pyodide` functions is to limit the scope\nof the function definition. If you need a closure, you will have to wrap it in a\nsecond function call.\n\n## Copying files to Pyodide\n\nYou can copy files to the pyodide filesystem using the `copy_files_to_pyodide` decorator. This takes two arguments - a list of `(src,destination)` pairs. These can be any of: 1) A filename, 2) A folder name, which is copied to the destination path (along with all subdirectories if `recurse_directories` is True), 3) A glob pattern, which will fetch all files matching the pattern and copy them to a destination directory, whilst preserving the folder structure.\n\nIf you set `install_wheels` to True, any `.whl` files will be installed on pyodide. This is useful for installing your package.\n\n```py\nfrom pytest_pyodide.decorator import copy_files_to_pyodide\n\n@copy_files_to_pyodide(file_list=[(src,dest)],install_wheels=True,recurse_directories=True)\n```\n\n\n## Running non-pyodide tests in Pyodide\n\nThis plugin also supports running standard pytest tests on pyodide in a browser. So if you have an existing codebase and\nyou want to check if your pyodide build works, just run it like this:\n```\n# Make the emscripten/wasm32  wheel in the dist folder\npyodide build\n# the following code\n# a) copies the test_path directory and subfolders to a Pyodide instance, and\n# b) installs any wheels in the dist subfolder so that this package is available on the Pyodide VM\npytest --run-in-pyodide test_path --runtime \u003cruntime\u003e --dist-dir=\u003cpyodide/dist\u003e\n```\n\n## Specifying a browser\n\nYou can specify a browser runtime using `--runtime` (`--rt`) commandline option.\n\nPossible options for `--runtime` are:\n\n- node (default)\n- firefox\n- chrome\n- safari\n- host (do not run browser-based tests)\n\n```sh\npytest --runtime firefox\npytest --runtime firefox --runtime chrome\n\n# Adding -no-host suffix will disable running host tests\npytest --runtime chrome-no-host\n```\n\n## Running tests with Playwright (optional)\n\nBy default, the tests will be run with Selenium.\nIt is possible to run tests with [playwright](https://github.com/microsoft/playwright-python) instead as follows.\n\nFirst install playwright browsers\n\n```sh\npython -m playwright install --with-deps\n```\n\nThen use the `--runner` argument to specify to run tests with playwright.\n\n```\npytest --runner playwright\n```\n\n\n### Custom test marks\n\nCustom test marks supported by `pytest-pyodide`:\n\n`pytest.mark.driver_timeout(timeout)`: Set script timeout in WebDriver. If the\ntest is known to take a long time, you can extend the deadline with this marker.\n\n`pytest.mark.xfail_browsers(chrome=\"why chrome fails\")`: xfail a test in\nspecific browsers.\n\n## Examples\n\nSee [`examples`](./examples).\n\n\n## Compatible Pyodide versions\n\nSee [`compatibility table`](./COMPATIBILITY.md).\n\n## License\n\npytest-pyodide uses the [Mozilla Public License Version\n2.0](https://choosealicense.com/licenses/mpl-2.0/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyodide%2Fpytest-pyodide","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpyodide%2Fpytest-pyodide","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpyodide%2Fpytest-pyodide/lists"}