Introduction
E2E testing for mini programs has long been stuck on one fact: the developer tool is desktop-only.
This is #247 in the "One Open Source Project a Day" series. Today's project is wxa-runner (open-wxa/wxa-runner).
Anyone who has automated WeChat Mini Program tests has likely hit this wall: the official developer tool exists only for Windows and macOS, so it can't go into Linux CI. And if you want a modern tool like Playwright, there's no mini-program runtime that lives in a browser. H5-style approaches run, but their rendering and components are reimplemented by a framework, so they always drift from a real device.
wxa-runner's idea is blunt: don't rewrite the base library — move the official WeChat base library into the browser as-is, and only supply the "native shell" of the WeChat client around it.
What You'll Learn
- What problem wxa-runner solves, and what it does not
- Its core idea: implement only the native layer, reuse the official base library and compiler
- The layered architecture and each package's role
- How the logic and render layers run in a browser, and how WXML/WXSS are compiled
- Its relationship to the developer tool, and how it runs on Linux CI
- Known limitations and where it fits
Prerequisites
- Familiarity with the basic structure of WeChat Mini Programs (logic layer / render layer, WXML / WXSS,
setData) - Experience with Playwright or similar browser automation tools
- Some knowledge of uni-app or Taro helps
Project Background
Overview
In one sentence: run mini programs in an ordinary browser on top of the official WeChat base library, for local debugging and E2E automation.
Its boundary first: it does not replace the WeChat developer tool. Preview, upload, and on-device debugging still need the developer tool.
Team
- Organization account: open-wxa
- Individual authors and contributors: not listed on the project page
- Acknowledgements: the WeChat team (official base library and compiler); WEPT (chemzqm/wept), which first proved that running mini programs in a browser is feasible
- Creation date: not stated
Project Stats
- ⭐ GitHub Stars: 556
- 🍴 Forks: 73
- 📝 Commits: 184
- 📦 Version: v0.8.0
- 📄 License: MIT (note: the bundled base library
wxa-baselibis official WeChat code, copyright Tencent, and is not covered by MIT) - 📚 Docs: open-wxa.github.io/wxa-runner
- 🏷️ Topics: e2e-testing, mini-program, playwright, simulator, taro, uni-app, weapp
Main Features
Core Purpose
Run a mini program's build output directly in a browser, with rendering, components, setData, and lifecycles all executed by the official base library rather than reimplemented by a framework as in H5. The behavior is closer to a real device, yet it can be driven by browser tooling.
Use Cases
-
Headless E2E testing
- Test inside the browser with Playwright, with no need to remote-control the developer tool; runs headless in CI.
-
Mini-program tests on Linux CI
- Runs on GitHub Actions Linux machines without installing the developer tool.
-
Local debugging
- Use Chrome DevTools for breakpoints, network requests, and the DOM; hot reload on save keeps the page stack and global state.
-
Getting past obstacles like login
- In setup, seed a login state, mock native APIs, and intercept requests (such as
wx.request) without touching business code.
- In setup, seed a login state, mock native APIs, and intercept requests (such as
-
Testing uni-app / Taro projects
- Supports the
mp-weixindirectory they compile to.
- Supports the
Quick Start
The launch command:
npx wxa-runner dev ./dist/dev/mp-weixin # native mini program dir, or uni-app / Taro build output
# → http://localhost:3000You can jump straight to a page with URL parameters, for example:
http://localhost:3000/?path=pages/detail/detail&id=1The docs home page doesn't give complete install steps or code samples for setup and Playwright. See the docs site's sub-pages — "setup before launch", "testing in Playwright", "running in CI", and "commands and environment variables". I won't invent sample code for them here.
Core Features
-
Device-faithful execution
- Runs build output directly; rendering, components,
setData, and lifecycles are executed by the official base library.
- Runs build output directly; rendering, components,
-
Headless E2E
- Built on Playwright; can assert on page data and outgoing requests.
-
Chrome DevTools debugging
- Breakpoints, network, and DOM are all visible, with hot reload on save.
-
Pre-launch setup
- Seed a login state, mock native APIs, intercept requests.
-
Borrowing the developer tool's login state
- On a machine with the developer tool installed, borrow its login state to get real codes and phone numbers and reach the service market and plugin pages.
-
Mainstream cross-platform output
- Native mini programs, uni-app, Taro.
Advantages
The docs site has a page on choosing between wxa-runner and miniprogram-automator, which the home page doesn't expand on. The table below is my own qualitative comparison based on its design, for reference only:
| Dimension | wxa-runner | Developer-tool-based approaches (e.g. miniprogram-automator) | H5 / framework reimplementation |
|---|---|---|---|
| Runtime | Ordinary browser | Needs the desktop developer tool | Browser |
| Linux CI | Runs headless, no developer tool needed | Limited by the tool being Windows / macOS only | Yes |
| Rendering fidelity | Executed by the official base library | Official | Framework reimplementation may differ |
| Driver | Playwright | Remote control of the developer tool | Varies |
Why it's worth watching
- It fills a practical gap in mini-program engineering: a runtime that fits in CI and stays close to a real device.
- The "reuse the official stack, add only the native layer" trade-off means it doesn't chase every base-library version with a rewrite.
- It is candid about its boundaries; the limitation list is very specific.
Deep Dive
Core idea: implement only the "native layer"
A mini-program runtime splits roughly in two: the base library, compiler, and bridge protocol WeChat provides, and the native layer the WeChat client provides (message bridge, page stack, native APIs, and native UI such as the navigation bar).
wxa-runner's choice: reuse the former entirely from the official source, and implement only the latter.
Layered architecture
Application: cli | playwright fixture
Service: core/server (dev server, proxy, watcher, hot-update routing)
Build: project / compiler / extractor
Runtime: core/runtime (host + injected frame scripts)
└─ official base library (WAService.js / WAWebview.js)What each package does
| Package | Responsibility |
|---|---|
| extractor | Finds the developer tool directory, parses .wxvpkg, reads app.asar, unpacks and caches the base library to ~/.wxa-runner/baselib |
| compiler | Wraps wcc (WXML / WXS) and wcsc (WXSS); prefers the developer tool's own, falls back to miniprogram-compiler |
| core | project (parses the project, produces artifacts, __wxConfig), server (DevServer, watching, hot update, proxy, plugins), runtime (browser side) |
| cli | The wxa-runner command line |
| playwright | Wraps the Playwright fixture, matchers, and Node-side mock replay |
The repo also has examples (showcase, hello-world, custom-tabbar, subpackages, worker, etc.) and tests (unit, integration, e2e, playwright). The stack includes TypeScript, pnpm workspace, Express, ws, chokidar, esbuild, tsup, Vitest, and Playwright.
How a mini program runs inside the browser
Logic and render layers. Each is a same-origin iframe: the logic layer is hidden, and there's one render layer per page, with a URL matching the page path so relative resource paths resolve correctly.
frame.js, injected before the base library, does the following:
- Presets global variables
- Provides
WeixinJSBridge(invoke / publish / subscribe / on) - Simulates device size and portrait orientation
- Converts mouse events into touch events
- Injects built-in component styles early
Iframes rather than Web Workers because the base library needs window / document, and a same-origin iframe can call the host directly, so synchronous APIs like getStorageSync can return synchronously.
The host only maintains the page stack and dispatches onAppRoute; page lifecycles (onLoad / onShow / onReady) are all driven by the base library. The page stack is capped at 10, routing operations are serialized, and tabBar page instances are cached.
Compiling WXML / WXSS.
wccruns as a subprocess, because the native modulewcc.noderandomly crashes withstd::bad_allocwhen called repeatedly in one process.- All wxml and wxs compile at once into a single
$gwx: the render layer uses the full build, the logic layer a trimmed one; omitting the trimmed build produces a node-creation mismatch error. - In
wcscoutput,commis the shared runtime, and each page or component gets onesetCssToHead; rpx conversion is done by the runtime wcsc generates. - Incremental builds: editing only JS doesn't recompile templates or styles, the three compilations run in parallel, and concurrent requests share a single build.
- Each JS file in the logic layer becomes one
definescript with line numbers matching the source; ES modules go through esbuild to CommonJS with source maps, so breakpoints line up with your source.
Simulating wx APIs and native components.
- Native APIs are managed by a registry covering storage, system, network, routes, ui, media, audio, map, picker, webView, fs, subpackages, misc, and more. Calls that can produce a result synchronously call back synchronously; unimplemented APIs return ok by default and warn once.
- The render layer reports its platform as
devtools, so most native components directly use the web implementations the base library prepares for the developer tool: canvas, video, map, camera, audio, and input work. The host pops up pickers, and web-view embeds via iframe. - Companion SDKs such as ads, cloud development, and payments have no loading mechanism; only stubs on the
wxlayer make the business logic take its failure branch. - Workers use the browser's Web Worker, with the host implementing both ends of
WeixinWorker.
Hot update. Editing only wxss, or a page or component's own wxml / js, hot-updates; other changes refresh the whole page and restore the page stack. Page() pages swap new methods onto the live instance, while Component() pages and components are remounted by the base library.
Startup order, for testing
The host mounts window.__wxa_test_api__, with this startup order: test API → setup → __wxa_before_launch__ → launch the mini program. Setup runs before the logic layer is created and passes through only environment variables starting with WXA_. This is the underlying mechanism behind "seed a login state and mock native APIs without touching business code".
Relationship to the developer tool
wxa-runner reuses the developer tool's base library (.wxvpkg), compilers (wcc / wcsc), and administrative-division data, and discards the desktop shell.
Without the developer tool (Linux, CI):
- The base library is the bundled
@open-wxa/wxa-baselib(3.7.0) - The compiler is
miniprogram-compiler, which supports x86-64 only, has an older core, and shows small template-runtime differences - The region picker is unavailable
For the same base-library version, the developer tool's copy is preferred. Paths can be overridden with WXA_DEVTOOLS_PATH, WXA_VENDOR_DIR, and WXA_WCC / WXA_WCSC.
On base-library versions: by default the latest compatible version up to 3.7.0 is used; from 3.7.1, business code runs in an isolated sub-context created by the native WeixinJSContext, so you must pass a version explicitly with --baselib or WXA_BASELIB; from 3.8, the framework splits into WAServiceMainContext.js / WASubContext.js.
Key design trade-offs
| Trade-off | Reason |
|---|---|
| Not using the developer tool directly | Windows / macOS only; hard to put into CI or a headless browser, and Playwright can't drive it |
| iframes instead of Web Workers | The base library needs window / document, and same-origin iframes also support synchronous APIs |
| Not rewriting the base library | Large effort and hard to keep consistent; the earlier self-simulated version supported very limited capabilities |
| Bundled base library in a separate repo | Copyright is Tencent's and not under MIT; installed as an optional dependency, and failure doesn't affect the core |
| Compiler in a subprocess | Avoids random crashes of the native module |
| Worker sync interfaces fail outright | The developer tool blocks on SharedArrayBuffer, which needs COOP / COEP cross-origin isolation and would break cross-origin resource loading |
Known limitations
The more specific this part is, the more honest the project's boundary is:
- Base-library versions above 3.7.0 are unverified
- Components not covered or with limited capability: live-player (can't play), live-pusher, voip-room, open-data (renders empty), ad (takes the failure branch), xr-frame
- web-view: sites that set X-Frame-Options or CSP frame-ancestors can't be embedded;
wx.miniProgramAPIs inside the page are unavailable;binderrordoesn't fire - map: needs network access; some methods (
toScreenLocation, ground overlays, indoor maps, etc.) don't work - File system:
saveFilewith afilePathfails,saveFileSyncis unimplemented; temp files live on the dev server and vanish on restart - The network proxy is for local development only; the base library's own requests return failure directly
- Without the developer tool, the compiler supports x86-64 only and differs slightly from the real-device compiler
How to judge its value
- It tests logic and interaction, not real-device pixels. Browser rendering and native components will always differ from a real device; it suits verifying business flows, page data, and requests, not visual regression.
- Coverage depends on which APIs your mini program uses. Pages that lean heavily on live streaming, AR, ads, or cloud development need your own assessment.
- Tracking versions is a long-term cost. Base-library versions above 3.7.0 are unverified, so the project must keep up as WeChat's base library evolves.
- The project page gives no roadmap, configuration details, or formal comparison with similar projects; deeper use relies on the docs site and
PROJECT_DESIGN.md.
Links and Resources
Official
- 🌟 GitHub: https://github.com/open-wxa/wxa-runner
- 📚 Docs: https://open-wxa.github.io/wxa-runner/
- 📦 Bundled base library: https://github.com/open-wxa/wxa-baselib
- 🐛 Issue Tracker: https://github.com/open-wxa/wxa-runner/issues
Related
- WEPT: first proved that running mini programs in a browser is feasible
PROJECT_DESIGN.mdin the repo: details on architecture and design trade-offs- The docs page on choosing between wxa-runner and miniprogram-automator
Summary and Outlook
Key Takeaways
- A clear idea: reuse the official base library, compiler, and bridge protocol, and implement only the native layer — far steadier than rewriting the base library.
- Built for CI: driven by Playwright, runs headless on Linux without the developer tool.
- Close to a real device: rendering,
setData, and lifecycles are executed by the official base library. - Candid boundaries: a specific limitation list, and an explicit statement that it doesn't replace the developer tool.
Who It's For
- Teams that want mini-program E2E tests in Linux CI
- Developers building mini programs with uni-app or Taro who need automated regression
- Readers interested in engineering problems like "how to reuse an official runtime inside a browser"
Suggested Path
Run npx wxa-runner dev on examples/hello-world first and get used to debugging in Chrome DevTools. Then read PROJECT_DESIGN.md to understand the division of labor among host, frame, and base library. Finally, following the docs site's setup and Playwright pages, write your project's first E2E case with a mocked login state and get it running headless in CI.
Explore PrimeSkills — a curated marketplace of AI agents and skills, each validated against real enterprise workflows. No hype, just what actually works.
Visit my personal site for more insights and interesting products.