Skip to content

WASM: add a WebAssembly/JupyterLite smoke-test job to CI #933

Description

@mmcky

Note

Updated 2026-08-21. The seed is now on main via #938 (ci/wasm/environment.yml, ci/wasm/smoke_test.py, the Playwright harness ci/wasm/test_jupyterlite.py, and the native gate in ci_wasm_smoke.yml), so the Approach below names it instead of "the config from #928", and a task list for the runner PR has been added. What that list corrects: the parked job (2026-08-19 comment) still needs triggers, the gini expectation in both suite files is inverted now that numba 0.67.0 build 1 serialises parallel=True (patch 0010, emscripten-forge/recipes#6293), the two jitted-generator tests will hang without a real guard, test_simplex_grid fails on a warm cache (#944), and smoke_test.py is not yet shipped into the kernel. The original text is preserved in the edit history.

Part of #925 (Phase 3 — keep it working).

Goal

Browser support must not regress once established: add a wasm job to GitHub Actions that runs a smoke suite in the real browser stack.

Approach

This job is also the natural watchpoint for upstreaming: the blog post signals intent to merge the WASM engine into Numba mainline, and once that ships, the patched-recipe caveats (the recipe now carries ten patches, e.g. the serial fallback for parallel=True added by patch 0010 and the ufunc caching disabled by patch 0007) may start dissolving. Until then the job tests the released conda-forge noarch quantecon, not repo source, so per-PR triggering is not useful long-term.

Tasks for the runner PR

  1. Add the parked wasm job with its own triggers. The snippet in the 2026-08-19 comment is a job block and has no on:; ci_wasm_smoke.yml's on: is push/pull_request to main. Either add schedule + workflow_dispatch to that shared on: block and guard the job with if: github.event_name != 'pull_request', or move it to its own workflow file — in which case needs: native no longer resolves (it only sees jobs in the same workflow) and must be dropped or replaced. Iterate under pull_request first, as the 2026-08-19 comment describes, then flip.
  2. Flip the gini expectation. numba 0.67.0 build 1 on emscripten-forge-4x (patch 0010, Fallback Numba parallel JIT to serial on Emscripten emscripten-forge/recipes#6293, merged 2026-08-18) demotes @njit(parallel=True) to the serial pipeline, and the bare numba pin resolves to it. test_gini_coefficient's xfail(IS_EMSCRIPTEN, strict=True) in smoke_test.py would XPASS and fail; test_gini_fails_on_emscripten in test_jupyterlite.py asserts an error and would fail. Both become plain pass assertions (browser-unverified; inferred from the merged patch). PR Rewrite Gini coefficient in O(n log n) #937's O(n log n) rewrite (WASM: remove @njit(parallel=True) from gini_coefficient via an O(n log n) rewrite #926) is a performance change on top of this, not a prerequisite.
  3. Guard the two jitted-generator tests. test_support_enumeration and test_vertex_enumeration in smoke_test.py call @njit generators (support_enumeration.py:96, vertex_enumeration.py:118) that never return on Emscripten (WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927, WASM: support_enumeration and vertex_enumeration hang in the browser — root cause is jitted generators (numba.np.linalg._LAPACK verified working) #927 (comment)). Use pytest.mark.skipif(IS_EMSCRIPTEN, ...) or xfail(IS_EMSCRIPTEN, run=False, strict=True) — a plain xfail still executes the test, and pytest-timeout (SIGALRM / threading.Timer) cannot interrupt a native loop in the single-threaded kernel. In test_jupyterlite.py, skip test_support_enumeration or run it last: Playwright's EXEC_MS (180 s) bounds it host-side, but the module-scoped kernel stays wedged and the following test fails spuriously. Add the minimal bare @njit generator test (for i in range(n): yield i) alongside, guarded the same way, as the isolated upstream repro and regression watch.
  4. Mark test_simplex_grid as expected to fail on a warm cache and cite WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944. Its comment currently says "(WASM: audit 32-bit intp behaviour on wasm32 (overflow guards, simplex_index wrapping, dtypes) #929)"; the actual failure is RuntimeError: no compiled object yet for comb_jit (WASM: cache=True functions that link a cache-restored callee fail with "no compiled object yet" (simplex_grid → num_compositions_jit → comb_jit) #944, reported upstream as Numba: RuntimeError: no compiled object yet emscripten-forge/recipes#6309), which triggers only when a cache=True caller links a callee restored from the persistent cache — a cold single-session run passes. A regression test needs a second session or a pre-seeded /drive/.cache/numba.
  5. Ship smoke_test.py into the kernel and run it in-kernel. The parked job runs only the Playwright harness (6 cells: kernel boot, import, tauchen, np.linalg.solve, support_enumeration, gini), so a green run fills at most 5 of WASM: JupyterLite proof-of-concept deployment and browser smoke suite #928's 13 rows. WASM: JupyterLite environment config and browser smoke suite #938 assigned in-kernel pytest execution of smoke_test.py (16 tests) here (commit 1e0c598 also dropped pytest from environment.yml, so it has to be added back). Copy the file into the site contents, invoke pytest inside the xeus-python kernel, and surface per-test results.
  6. Bump mamba-org/setup-micromamba@v2 to @v3 in the parked YAML (v3.0.0 moved to Node 24; the inputs used — micromamba-binary-path, init-shell, generate-run-shell — are unchanged). actions/checkout@v7 and actions/setup-python@v7 are current.

Expected outcome of the first browser run once 1–6 are done, on numba 0.67.0 build 1: test_gini_coefficient passes; test_support_enumeration, test_vertex_enumeration and the bare-generator test are skipped/xfail(run=False) pending the upstream generator report (#927); test_simplex_grid passes cold and fails warm (#944); everything else is expected to pass. Record the per-function table in #928.

Acceptance criteria

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    CIinfrastructureCI, build, packaging, and repo tooling

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions