An end-to-end test installs the real APK and drives it like a user, so it catches what no browser test can: a missing plugin, a permission, a WebView quirk. The mainstream stack is Appium 180,635 3.8.0 (Apache-2.0, github.com/appium/appium (https://github.com/appium/appium 22,014 )) with its UiAutomator2 driver 8.7.0, scripted from WebdriverIO 417,092 9.32.0 (MIT); Appium can switch from the native context into the WEBVIEW_... context and use CSS selectors there. Cypress 41,181 and Playwright 26,280 test the same web code in a desktop browser, which is faster but never touches the bridge.
BookNest's test uses the socket of Remote Debugging Android with no extra dependency: a before hook starts the app with adb, waits for the DevTools socket and opens a WebSocket, and run() sends Runtime.evaluate. (Playwright's connectOverCDP refused this WebView: "Browser context management is not supported".) The test:
test('a catalog tap opens the book page', { timeout: 60000 }, async () => {
const link = `document.querySelector('.page-current a[href="/books/3/"]')`;
await waitFor(`!!${link}`);
await run(`${link}.click()`);
await waitFor(`document.querySelector('.page-current .navbar .title')
?.textContent.includes('Salt and Saffron')`);
const details = await run(`document.querySelector('.page-current .page-content').innerText`);
assert.match(details, /\$24\.00/);
});npm 2,036 run e2e runs it with Node's built-in test runner (node --test --test-reporter=spec e2e/*.e2e.mjs):
> booknest@1.0.0 e2e > node --test --test-reporter=spec e2e/*.e2e.mjs ✔ a catalog tap opens the book page (24130.353352ms) ℹ tests 1 ℹ suites 0 ℹ pass 1 ℹ fail 0 ... ℹ duration_ms 28247.719768
The 24 seconds are mostly a cold start on a busy emulator. Keep such tests few, aimed at journeys that cross the bridge, waiting on conditions rather than sleeping, and run them in CI on an emulator (ReactiveCircus/android-emulator-runner v2.38.0 on GitHub Actions 29 ) or a device farm.