-
Notifications
You must be signed in to change notification settings - Fork 122
feat: kitten-lynx, a puppeteer like, android emulator based, testing infrastructure #2272
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
PupilTong
merged 31 commits into
lynx-family:main
from
PupilTong:p/hw/based-on-emulator-testing
Mar 18, 2026
Merged
Changes from all commits
Commits
Show all changes
31 commits
Select commit
Hold shift + click to select a range
db0962b
test(kitten-lynx): impl vitest test suite, tap interaction, and AGENT…
PupilTong e327783
feat(kitten-lynx): impl core testing API and finish test runner
PupilTong 5912ee3
feat: Automatically restart the Lynx app on all connected ADB devices…
PupilTong d1e79b3
feat: Introduce configurable container images for reusable workflows …
PupilTong 013788f
refactor: switch Lynx connection to devtool-connector and add configu…
PupilTong 161619f
feat: Migrate Android testing documentation to Waydroid and enhance a…
PupilTong 9d6ef89
ci: Replace Docker-based Android emulator with Waydroid for CI tests.
PupilTong c542281
feat: Install Waydroid and initialize it with a vanilla image.
PupilTong 6a5c0d5
chore: Add kmod package to Waydroid installation in the test workflow.
PupilTong d17cc1b
ci: Install Waydroid kernel modules and their dependencies in the tes…
PupilTong 5030f5c
+ fix
PupilTong 5bf624f
fix: Explicitly set ANDROID_HOME and use `--sdk_root` with `sdkmanage…
PupilTong 634592f
+ fix
PupilTong 2ea97fb
+ remove install stage
PupilTong 8958529
+ fix url
PupilTong 69e6d66
+ waiting time
PupilTong fdce55f
+ fix
PupilTong e1ca7fe
ci: Start and verify Lynx Explorer application before running tests i…
PupilTong b3290b9
+ log
PupilTong 30c8fda
+ fix
PupilTong 57aca0c
+ essential infos
PupilTong 51a1019
docs: Correct typo "chould" to "could" in AGENTS.md.
PupilTong cea23be
+ fix dedupe
PupilTong 53fadb5
docs: Add comprehensive JSDoc comments and a new README to clarify Ki…
PupilTong da748a5
Remove `container-image` parameter from workflows and adjust test exc…
PupilTong f264485
refactor: rename `@lynx-test/kitten-lynx` to `@lynx-js/kitten-lynx-te…
PupilTong b92aa16
refactor: Migrate ADB interactions to `@yume-chan/adb` and adjust tou…
PupilTong 32ebdfe
chore: update pnpm lockfile
PupilTong 1d38cc8
+ report coverage
PupilTong c3b1fd7
feat: Improve `kitten-lynx` test stability by implementing local serv…
PupilTong 1f6a24b
feat: Implement `cmd package resolve-activity` for Android app launch…
PupilTong File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@lynx-js/kitten-lynx-test-infra": patch | ||
| --- | ||
|
|
||
| feat: initial commit | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,58 @@ | ||
| # @lynx-js/kitten-lynx-test-infra | ||
|
|
||
| This document provides context, architecture guidelines, and workflows for agents interacting with the `kitten-lynx` framework. | ||
|
|
||
| ## Overview | ||
|
|
||
| `kitten-lynx` is a Puppeteer-like testing library designed for interacting with the Lynx browser engine and Lynx Explorer Android application. It utilizes the `@lynx-js/devtool-connector` (stateless, short-lived connection architecture) to communicate with Lynx apps running on Android devices via ADB. | ||
|
|
||
|
PupilTong marked this conversation as resolved.
|
||
| Through the Chrome DevTools Protocol (CDP), `kitten-lynx` enables: | ||
|
|
||
| - Starting and tearing down `LynxView` instances. | ||
| - Navigating to Lynx bundle URLs and reading the DOM structure. | ||
| - Querying elements via `DOM.querySelector`. | ||
| - Reading styles, attributes, and precise boundary boxes of elements. | ||
| - Simulating native touches through `Input.emulateTouchFromMouseEvent`. | ||
|
|
||
| ## Architecture Details | ||
|
|
||
| ### Connections & Sessions | ||
|
|
||
| 1. **Lynx.ts**: The entry point. Initializes `Connector` with `AndroidTransport`, discovers ADB devices, restarts the target app, and polls `listClients()` to find the Lynx client. Accepts `ConnectOptions` to target a specific device and app package. | ||
| 2. **LynxView.ts**: Manages individual pages. Attaches to a CDP session via `sendListSessionMessage()`, sends `Page.navigate` to load a Lynx bundle, then polls sessions by URL to find and re-attach to the correct session (apps may have multiple Lynx views). | ||
| 3. **CDPChannel.ts**: A stateless wrapper that sends CDP commands via `connector.sendCDPMessage()`. Each call is a short-lived request/response — no persistent connection is maintained. | ||
| 4. **ElementNode.ts**: A wrapper around `nodeId`s matching an element. Implements interactive methods like `getAttribute()`, `computedStyleMap()`, and `tap()`. | ||
|
|
||
| ### Key Design Patterns | ||
|
|
||
| - **Stateless connector**: The `devtool-connector` does not maintain persistent WebSocket connections. Each `sendCDPMessage` / `sendListSessionMessage` call is a self-contained request through ADB/USB transport. | ||
| - **Retry-based initialization**: After restarting the app, polling loops handle the delay before the devtool server is ready. `onAttachedToTarget()` only assigns `_channel` after all CDP domain enables succeed, making the whole operation retryable. | ||
| - **Session URL matching**: After `Page.navigate`, the Lynx runtime creates a new session for the navigated URL. `goto()` polls `sendListSessionMessage()` and matches sessions by URL (full URL, filename, or suffix) to find the correct one. | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| For the library to interact successfully: | ||
|
|
||
| - The host machine (or CI environment) must have an Android environment (emulator or real device) running with ADB enabled and authorized. | ||
| - The Lynx Explorer APK must be installed on the device (e.g., `adb install /path/to/LynxExplorer.apk`). The latest apk could be found here `https://github.com/lynx-family/lynx/releases` | ||
| - Typical commands use `pnpm run test` starting `vitest` logic inside the Node wrapper. | ||
|
|
||
| ### Known Gotchas | ||
|
|
||
| - **`Page.navigate` does not work like Chrome**: In Lynx, `Page.navigate` tells the runtime to load a new bundle, which creates a **new session** rather than updating the current one in place. You must poll `sendListSessionMessage()` to find the new session by URL and re-attach to it. | ||
| - **`App.openPage` is not implemented** in Lynx Explorer 3.6.0. Do not rely on `sendAppMessage('App.openPage')` for navigation. | ||
| - **Network access & Local Serving**: If the Android environment lacks direct internet access to your host machine's local server (common in some CI setups): | ||
| 1. Serve your Lynx bundles from the host (e.g., `python3 -m http.server 8080`). | ||
| 2. Use `adb reverse tcp:8080 tcp:8080` to map the host port to the device. | ||
| 3. Navigate to `http://localhost:8080/your.bundle`. | ||
| - **Multiple ADB targets**: When multiple ADB devices are connected (e.g. physical phone + emulator), use `ConnectOptions.deviceId` to target a specific one (e.g. `192.168.240.112:5555`). Otherwise the first available client is used, which may be on the wrong device. | ||
| - **CDP timeouts**: The connector uses a 5-second `AbortSignal.timeout`. Keep test operations tolerant of emulator boot/warm-up times. | ||
|
|
||
| ## Adding Features | ||
|
|
||
| When extending the `kitten-lynx` testing library, adhere to these rules: | ||
|
|
||
| 1. **Protocol Typings**: Only update `Protocol` types in `src/CDPChannel.ts` when implementing new standard CDP requests (e.g. `Page.reload`, `DOM.getOuterHTML`). | ||
| 2. **Puppeteer Equivalency**: Maintain an API design similar to Puppeteer/Playwright. Add element-level logic inside the `ElementNode` class (e.g., `type()`, `boundingBox()`) and page-level logic inside `LynxView` (e.g., `evaluate()`, `screenshot()`). | ||
| 3. **Session Reconnection**: Be mindful of device timeouts. CDP requests time out after 5000ms. Keep connection tests tolerant of emulator boot/warm-up times gracefully in test suites (`tests/lynx.spec.ts`). | ||
| 4. **Vitest Verification**: Before pushing feature changes, verify functionality using `pnpm run build && pnpm run test` inside the package root folder. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| # Kitten-Lynx (🐾 testing-library) | ||
|
|
||
| **Kitten-Lynx** is a Puppeteer-like / Playwright-like testing library. It is designed specifically for interacting with the **Lynx browser engine** and the **Lynx Explorer Android application**. | ||
|
|
||
| If you are an AI Agent (or a developer) reading this, this document is optimized to be as clear and straightforward as possible to help you write tests and understand the architecture without guessing. | ||
|
|
||
| --- | ||
|
|
||
| ## 🌟 What does it do? | ||
|
|
||
| Using the Chrome DevTools Protocol (CDP) over USB/ADB, `kitten-lynx` gives you the power to: | ||
|
|
||
| 1. Automatically open the Lynx Explorer app on an Android emulator or physical device. | ||
| 2. Navigate to `.lynx.bundle` URLs. | ||
| 3. Access the Lynx DOM (Document Object Model) tree. | ||
| 4. Find elements using CSS Selectors (e.g. `page.locator('#my-id')`). | ||
| 5. Read element styles and attributes. | ||
| 6. Simulate native touch gestures (like tapping on buttons). | ||
|
|
||
| --- | ||
|
|
||
| ## 🏗️ Architecture Explained (For Agents) | ||
|
|
||
| In standard Web Playwright/Puppeteer, you connect to a persistent browser WebSocket. **Lynx is different.** | ||
|
|
||
| 1. **Stateless Connector:** This library uses `@lynx-js/devtool-connector` which operates via Android Debug Bridge (ADB). It sends isolated Request/Response commands. There is no long-living socket. | ||
| 2. **Session Hopping:** When you tell Lynx to navigate to a new URL, Lynx creates an entirely **new debugging session**. | ||
| 3. **`Lynx.ts`**: Handles the physical device connection, force-stops the app, restarts it, and ensures the Master devtool switch is ON. | ||
| 4. **`KittenLynxView.ts`**: Represents a single "Page". When you call `goto(url)`, it sends the navigate command, and then intensely **polls** the ADB session list until it finds the new session matching your URL, and re-attaches to it. | ||
| 5. **`ElementNode.ts`**: Represents a physical tag (like `<view>` or `<text>`). Cached via `WeakRef` to save memory. Uses native coordinate math via `DOM.getBoxModel` to simulate real screen taps. | ||
|
|
||
| --- | ||
|
|
||
| ## 🚀 Quick Start Guide | ||
|
|
||
| ### Prerequisites | ||
|
|
||
| - You must have an Android Emulator or device running via `adb`. | ||
| - The Lynx Explorer APK must be installed (`adb install LynxExplorer.apk`). | ||
| - (In CI) Ensure your test runner can reach your local bundle dev server (you might need `adb reverse tcp:8080 tcp:8080`). | ||
|
|
||
| ### Example Test Script | ||
|
|
||
| Here is the blueprint for a standard test written using `kitten-lynx` and `vitest`: | ||
|
|
||
| ```typescript | ||
| import { expect, test, beforeAll, afterAll } from 'vitest'; | ||
| import { Lynx } from '@lynx-js/kitten-lynx-test-infra'; | ||
| import type { KittenLynxView } from '@lynx-js/kitten-lynx-test-infra'; | ||
|
|
||
| let lynx: Lynx; | ||
| let page: KittenLynxView; | ||
|
|
||
| // Setup: Connect to device | ||
| beforeAll(async () => { | ||
| // Connects to the first available ADB device and opens com.lynx.explorer | ||
| lynx = await Lynx.connect(); | ||
| page = await lynx.newPage(); | ||
| }, 60000); // Give ADB enough time to boot! | ||
|
|
||
| // Teardown: Clean up resources | ||
| afterAll(async () => { | ||
| await lynx.close(); | ||
| }); | ||
|
|
||
| test('Basic Navigation and Interaction', async () => { | ||
| // 1. Navigate to the bundle (Will poll until the session is found) | ||
| await page.goto('http://10.0.2.2:8080/dist/main.lynx.bundle'); | ||
|
|
||
| // 2. Locate an element by CSS Selector | ||
| const button = await page.locator('#submit-btn'); | ||
| expect(button).toBeDefined(); | ||
|
|
||
| // 3. Read an attribute. | ||
| // (Note: 'id' maps internally to Lynx's 'idSelector') | ||
| const idValue = await button!.getAttribute('id'); | ||
| expect(idValue).toBe('submit-btn'); | ||
|
|
||
| // 4. Read computed CSS styles | ||
| const styles = await button!.computedStyleMap(); | ||
| expect(styles.get('display')).toBe('flex'); | ||
|
|
||
| // 5. Simulate a native tap | ||
| await button!.tap(); | ||
|
|
||
| // 6. Assert DOM changes (re-query the new element) | ||
| const successText = await page.locator('.success-message'); | ||
| expect(successText).toBeDefined(); | ||
| }, 30000); | ||
| ``` | ||
|
|
||
| --- | ||
|
|
||
| ## ⚠️ Known Gotchas & Pitfalls | ||
|
|
||
| If you are writing scripts and tests, memorize these rules: | ||
|
|
||
| 1. **`goto()` implies a Session Change:** After `page.goto()`, the old node IDs are dead. Always query elements _after_ the navigation finishes. | ||
| 2. **Timeouts:** Android emulators take time to boot. The devtool ADB bridge takes time to synchronize. Always set high timeouts for setup hooks (`beforeAll(..., 60000)`). | ||
| 3. **No `App.openPage` locally:** Lynx Explorer 3.6.0 does not support `App.openPage` properly in some fallback layers. `kitten-lynx` falls back to a Custom OpenCard event automatically. You do not need to worry about this, but do not be alarmed by terminal warnings. | ||
| 4. **Id Selector:** Standard web writes `<view id="test">`. Lynx internally uses `idSelector="test"`. The `ElementNode.getAttribute('id')` handles this mapping automatically for you. Do not query `'idSelector'` directly. | ||
| 5. **DOM Snapshots:** You can call `await page.content()` to get a massive HTML-like string of the current Lynx DOM. This is extremely helpful for debugging what is actually rendering! | ||
|
|
||
| --- | ||
|
|
||
| ## 🛠️ Extending the Library | ||
|
|
||
| If you need to add a newly supported CDP command: | ||
|
|
||
| 1. Open `src/CDPChannel.ts`. | ||
| 2. Add the strictly-typed parameter and return shapes to the `Protocol` interface block at the top of the file. | ||
| 3. Call `await this._channel.send('Domain.methodName', params)` in `KittenLynxView` or `ElementNode`. | ||
| 4. Run `pnpm run build && pnpm run test` before committing. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| { | ||
| "name": "@lynx-js/kitten-lynx-test-infra", | ||
| "version": "0.1.0", | ||
| "description": "A testing framework executing the Lynx explorer Android application", | ||
| "keywords": [ | ||
| "Lynx", | ||
| "testing", | ||
| "android", | ||
| "emulator" | ||
| ], | ||
| "author": { | ||
| "name": "lynx-stack" | ||
| }, | ||
| "type": "module", | ||
| "exports": { | ||
| ".": { | ||
| "types": "./dist/index.d.ts", | ||
| "import": "./dist/index.js", | ||
| "default": "./dist/index.js" | ||
| }, | ||
| "./package.json": "./package.json" | ||
| }, | ||
| "main": "./dist/index.js", | ||
| "types": "./dist/index.d.ts", | ||
| "files": [ | ||
| "dist" | ||
| ], | ||
| "scripts": { | ||
| "build": "tsc", | ||
| "test": "vitest run" | ||
| }, | ||
| "dependencies": { | ||
| "@lynx-js/devtool-connector": "workspace:*", | ||
| "@yume-chan/adb": "catalog:adb", | ||
| "@yume-chan/adb-server-node-tcp": "catalog:adb" | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.