diff --git a/CHANGELOG.md b/CHANGELOG.md index 4c60e89..7e0045e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## [0.58.3] - 2024-07-23 + +### Added + +- Added global config manager. + [#141](https://github.com/pyodide/pytest-pyodide/pull/141) + ## [0.58.1] - 2024-06-12 ### Fixed diff --git a/pytest_pyodide/__init__.py b/pytest_pyodide/__init__.py index bf147e3..57d68ea 100644 --- a/pytest_pyodide/__init__.py +++ b/pytest_pyodide/__init__.py @@ -1,5 +1,6 @@ from importlib.metadata import PackageNotFoundError, version +from .config import get_global_config from .decorator import copy_files_to_pyodide, run_in_pyodide from .runner import ( NodeRunner, @@ -30,4 +31,5 @@ "run_in_pyodide", "copy_files_to_pyodide", "spawn_web_server", + "get_global_config", ] diff --git a/pytest_pyodide/config.py b/pytest_pyodide/config.py new file mode 100644 index 0000000..217cfda --- /dev/null +++ b/pytest_pyodide/config.py @@ -0,0 +1,61 @@ +""" +Stores the global runtime configuration related to the pytest_pyodide package. +""" + +from typing import Literal + +RUNTIMES = Literal["chrome", "firefox", "node", "safari"] + +_global_load_pyodide_script = """ +let pyodide = await loadPyodide({ fullStdLib: false, jsglobals : self }); +""" + + +class Config: + def __init__(self): + # Flags to be passed to the browser or runtime. + self.flags: dict[RUNTIMES, list[str]] = { + "chrome": ["--js-flags=--expose-gc"], + "firefox": [], + "node": [], + "safari": [], + } + + # The script to be executed to load the Pyodide. + self.load_pyodide_script: dict[RUNTIMES, str] = { + "chrome": _global_load_pyodide_script, + "firefox": _global_load_pyodide_script, + "node": _global_load_pyodide_script, + "safari": _global_load_pyodide_script, + } + + # The script to be executed to initialize the runtime. + self.initialize_script: str = "pyodide.runPython('');" + + def set_flags(self, runtime: RUNTIMES, flags: list[str]): + self.flags[runtime] = flags + + def get_flags(self, runtime: RUNTIMES) -> list[str]: + return self.flags[runtime] + + def set_load_pyodide_script(self, runtime: RUNTIMES, load_pyodide_script: str): + self.load_pyodide_script[runtime] = load_pyodide_script + + def get_load_pyodide_script(self, runtime: RUNTIMES) -> str: + return self.load_pyodide_script[runtime] + + def set_initialize_script(self, initialize_script: str): + self.initialize_script = initialize_script + + def get_initialize_script(self) -> str: + return self.initialize_script + + +SINGLETON = Config() + + +def get_global_config() -> Config: + """ + Return the singleton config object. + """ + return SINGLETON diff --git a/pytest_pyodide/fixture.py b/pytest_pyodide/fixture.py index ea1485d..77dfee4 100644 --- a/pytest_pyodide/fixture.py +++ b/pytest_pyodide/fixture.py @@ -7,9 +7,8 @@ import pytest +from .config import get_global_config from .runner import ( - CHROME_FLAGS, - FIREFOX_FLAGS, NodeRunner, PlaywrightChromeRunner, PlaywrightFirefoxRunner, @@ -41,13 +40,14 @@ def _playwright_browsers(request): ) runtimes = pytest.pyodide_runtimes + cfg = get_global_config() with sync_playwright() as p: browsers: dict[str, Any] = {} supported_browsers: dict[str, tuple[str, list[str]]] = { # browser name: (attr_name, flags) - "firefox": ("firefox", FIREFOX_FLAGS), - "chrome": ("chromium", CHROME_FLAGS), + "firefox": ("firefox", cfg.get_flags("firefox")), + "chrome": ("chromium", cfg.get_flags("chrome")), # TODO: enable webkit # "webkit": (), } diff --git a/pytest_pyodide/runner.py b/pytest_pyodide/runner.py index e278134..dd7c9b7 100644 --- a/pytest_pyodide/runner.py +++ b/pytest_pyodide/runner.py @@ -5,10 +5,7 @@ import pexpect import pytest -CHROME_FLAGS: list[str] = ["--js-flags=--expose-gc"] -FIREFOX_FLAGS: list[str] = [] -NODE_FLAGS: list[str] = [] - +from .config import RUNTIMES, get_global_config TEST_SETUP_CODE = """ Error.stackTraceLimit = Infinity; @@ -89,8 +86,6 @@ }; """.strip() -INITIALIZE_SCRIPT = "pyodide.runPython('');" - class JavascriptException(Exception): def __init__(self, msg, stack): @@ -105,10 +100,17 @@ def __str__(self): class _BrowserBaseRunner: - browser = "" + browser: RUNTIMES = "" # type: ignore[assignment] script_timeout = 20 JavascriptException = JavascriptException + # A common script that runs after pyodide is loaded + POST_LOAD_PYODIDE_SCRIPT = """ + self.pyodide = pyodide; + globalThis.pyodide = pyodide; + pyodide._api.inTestHoist = true; // improve some error messages for tests + """ + def __init__( self, server_port, @@ -121,6 +123,8 @@ def __init__( *args, **kwargs, ): + self._config = get_global_config() + self.server_port = server_port self.server_hostname = server_hostname self.base_url = f"http://{self.server_hostname}:{self.server_port}" @@ -128,6 +132,7 @@ def __init__( self.script_type = script_type self.dist_dir = dist_dir self.driver = self.get_driver(jspi) + self.set_script_timeout(self.script_timeout) self.prepare_driver() self.javascript_setup() @@ -171,12 +176,8 @@ def javascript_setup(self): def load_pyodide(self): self.run_js( - """ - let pyodide = await loadPyodide({ fullStdLib: false, jsglobals : self }); - self.pyodide = pyodide; - globalThis.pyodide = pyodide; - pyodide._api.inTestHoist = true; // improve some error messages for tests - """ + self._config.get_load_pyodide_script(self.browser) + + self.POST_LOAD_PYODIDE_SCRIPT ) def initialize_pyodide(self): @@ -201,7 +202,7 @@ def initialize_pyodide(self): } """ ) - self.run_js(INITIALIZE_SCRIPT) + self.run_js(self._config.get_initialize_script()) from .decorator import initialize_decorator initialize_decorator(self) @@ -430,7 +431,7 @@ def get_driver(self, jspi=False): options = Options() options.add_argument("--headless") - for flag in FIREFOX_FLAGS: + for flag in self._config.get_flags("firefox"): options.add_argument(flag) return Firefox(service=Service(), options=options) @@ -449,7 +450,7 @@ def get_driver(self, jspi=False): if jspi: options.add_argument("--enable-features=WebAssemblyExperimentalJSPI") options.add_argument("--enable-experimental-webassembly-features") - for flag in CHROME_FLAGS: + for flag in self._config.get_flags("chrome"): options.add_argument(flag) return Chrome(options=options) @@ -544,7 +545,7 @@ def init_node(self, jspi=False): f"Node version {node_version} is too old, please use node >= 18" ) - extra_args = NODE_FLAGS[:] + extra_args = self._config.get_flags("node")[:] # Node v14 require the --experimental-wasm-bigint which # produces errors on later versions if jspi: diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..d038a53 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,25 @@ +from pytest_pyodide.config import Config, get_global_config + + +def test_config(): + c = Config() + + runtimes = ["chrome", "firefox", "node", "safari"] + + for runtime in runtimes: + c.get_flags(runtime) + c.get_load_pyodide_script(runtime) + + c.set_flags(runtime, ["--headless"]) + assert c.get_flags(runtime) == ["--headless"] + + c.set_load_pyodide_script(runtime, "console.log('hello')") + assert c.get_load_pyodide_script(runtime) == "console.log('hello')" + + c.get_initialize_script() + c.set_initialize_script("console.log('hello')") + assert c.get_initialize_script() == "console.log('hello')" + + +def test_global_config(): + assert get_global_config() is get_global_config()