| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent e097c0c commit 38e3e01
168 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,9 +8,24 @@ on: | |||
| 8 | 8 | workflow_dispatch: | |
| 9 | 9 | ||
| 10 | 10 | jobs: | |
| 11 | + coverage: | ||
| 12 | + # Runs first; fast static check that gates the long device jobs so | ||
| 13 | + # missing demos / flows fail before the emulator boots. | ||
| 14 | + runs-on: ubuntu-latest | ||
| 15 | + steps: | ||
| 16 | + - name: Checkout | ||
| 17 | + uses: actions/checkout@v4 | ||
| 18 | + - name: Set up Python | ||
| 19 | + uses: actions/setup-python@v5 | ||
| 20 | + with: | ||
| 21 | + python-version: '3.11' | ||
| 22 | + - name: Check E2E coverage of pythonnative.__all__ | ||
| 23 | + run: python scripts/check-e2e-coverage.py | ||
| 24 | + | ||
| 11 | 25 | e2e-android: | |
| 26 | + needs: coverage | ||
| 12 | 27 | runs-on: ubuntu-latest | |
| 13 | - timeout-minutes: 30 | ||
| 28 | + timeout-minutes: 45 | ||
| 14 | 29 | ||
| 15 | 30 | steps: | |
| 16 | 31 | - name: Checkout | |
@@ -46,12 +61,12 @@ jobs: | |||
| 46 | 61 | with: | |
| 47 | 62 | api-level: 31 | |
| 48 | 63 | arch: x86_64 | |
| 49 | - script: >- | ||
| 50 | - bash -lc "cd examples/hello-world && pn run android --no-logs && cd ../.. && maestro test tests/e2e/android.yaml" | ||
| 64 | + script: bash -lc "./scripts/run-e2e.sh android" | ||
| 51 | 65 | ||
| 52 | 66 | e2e-ios: | |
| 67 | + needs: coverage | ||
| 53 | 68 | runs-on: macos-latest | |
| 54 | - timeout-minutes: 30 | ||
| 69 | + timeout-minutes: 45 | ||
| 55 | 70 | ||
| 56 | 71 | steps: | |
| 57 | 72 | - name: Checkout | |
@@ -71,11 +86,7 @@ jobs: | |||
| 71 | 86 | echo "$HOME/.maestro/bin" >> $GITHUB_PATH | |
| 72 | 87 | brew tap facebook/fb && brew install idb-companion | |
| 73 | 88 | ||
| 74 | - - name: Build and launch iOS app | ||
| 75 | - working-directory: examples/hello-world | ||
| 76 | - run: pn run ios --no-logs | ||
| 89 | + - name: Build and run E2E tests | ||
| 90 | + run: ./scripts/run-e2e.sh ios | ||
| 77 | 91 | env: | |
| 78 | 92 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| 79 | - | ||
| 80 | - - name: Run E2E tests | ||
| 81 | - run: maestro --platform ios test tests/e2e/ios.yaml | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,10 +35,13 @@ cd examples/hello-world && pn run android | |||
| 35 | 35 | - `src/pythonnative/` – installable library and CLI | |
| 36 | 36 | - `pythonnative/` – core cross‑platform UI components and utilities | |
| 37 | 37 | - `cli/` – `pn` command | |
| 38 | - - `tests/` – unit tests for the library | ||
| 38 | + - `tests/` – unit tests for the library, plus the Maestro E2E suite | ||
| 39 | + - `e2e/` – the comprehensive E2E suite (see [E2E tests](#e2e-tests-maestro) below and `tests/e2e/AGENTS.md`) | ||
| 39 | 40 | - `templates/` – Android/iOS project templates and zips | |
| 40 | 41 | - `examples/` – runnable example apps | |
| 41 | - - `hello-world/` – minimal demo app using the library | ||
| 42 | + - `hello-world/` – minimal marketing demo | ||
| 43 | + - `e2e-suite/` – comprehensive feature catalog that drives the Maestro E2E suite | ||
| 44 | + - `scripts/` – helper scripts (`check.sh`, `run-e2e.sh`, `check-e2e-coverage.py`) | ||
| 42 | 45 | - `README.md`, `pyproject.toml` – repo docs and packaging | |
| 43 | 46 | ||
| 44 | 47 | ## Coding guidelines | |
@@ -269,7 +272,9 @@ fix/cli-regression | |||
| 269 | 272 | ||
| 270 | 273 | ### E2E tests (Maestro) | |
| 271 | 274 | ||
| 272 | - End-to-end tests use [Maestro](https://maestro.dev/) to drive the hello-world example on real emulators and simulators. | ||
| 275 | + End-to-end tests use [Maestro](https://maestro.dev/) to drive the dedicated `examples/e2e-suite` app on real emulators and simulators. That app contains one screen per public symbol in `pythonnative.__all__`; every flow under `tests/e2e/flows/<category>/` exercises one symbol. | ||
| 276 | + | ||
| 277 | + The dedicated `examples/hello-world` app is left in place as a small marketing demo; it is **not** the E2E target. | ||
| 273 | 278 | ||
| 274 | 279 | ```bash | |
| 275 | 280 | # Install Maestro (one-time) | |
@@ -279,21 +284,36 @@ curl -Ls "https://get.maestro.mobile.dev" | bash | |||
| 279 | 284 | brew tap facebook/fb && brew install idb-companion | |
| 280 | 285 | ``` | |
| 281 | 286 | ||
| 282 | - Build and launch the app first, then run the tests: | ||
| 287 | + Build and run everything via the convenience script: | ||
| 283 | 288 | ||
| 284 | 289 | ```bash | |
| 285 | - cd examples/hello-world | ||
| 286 | - | ||
| 287 | 290 | # Android (emulator must be running) | |
| 288 | - pn run android | ||
| 289 | - maestro test ../../tests/e2e/android.yaml | ||
| 291 | + ./scripts/run-e2e.sh android | ||
| 292 | + | ||
| 293 | + # iOS (simulator must be running) | ||
| 294 | + ./scripts/run-e2e.sh ios | ||
| 295 | + ``` | ||
| 296 | + | ||
| 297 | + For tight iteration, run a single category instead of the full pass: | ||
| 290 | 298 | ||
| 291 | - # iOS (simulator must be running; --platform ios needed when an Android emulator is also connected) | ||
| 292 | - pn run ios | ||
| 293 | - maestro --platform ios test ../../tests/e2e/ios.yaml | ||
| 299 | + ```bash | ||
| 300 | + ./scripts/run-e2e.sh android hooks | ||
| 301 | + ./scripts/run-e2e.sh ios components | ||
| 294 | 302 | ``` | |
| 295 | 303 | ||
| 296 | - Test flows live in `tests/e2e/flows/` and cover the main screen rendering, counter interaction, and multi-screen navigation. The `e2e.yml` workflow runs these automatically on pushes to `main` and PRs. | ||
| 304 | + Available categories: `components`, `hooks`, `navigation`, `layout`, `styling`, `animations`, `misc`. | ||
| 305 | + | ||
| 306 | + A coverage checker, `scripts/check-e2e-coverage.py`, gates CI: every name in `pythonnative.__all__` must be covered by a demo + flow, or listed in `INTENTIONAL_EXEMPTIONS` with a justification. | ||
| 307 | + | ||
| 308 | + When you add a new public symbol you must also: | ||
| 309 | + | ||
| 310 | + 1. Add a demo screen under `examples/e2e-suite/app/screens/<category>/`. | ||
| 311 | + 2. Append a `DemoEntry` in `examples/e2e-suite/app/registry.py`. | ||
| 312 | + 3. Add a Maestro flow at `tests/e2e/flows/<category>/<name>.yaml`. | ||
| 313 | + 4. Append the flow to the top-level `tests/e2e/android.yaml`, `tests/e2e/ios.yaml`, and the matching `tests/e2e/suites/<category>.yaml`. | ||
| 314 | + 5. Confirm `python scripts/check-e2e-coverage.py` exits 0. | ||
| 315 | + | ||
| 316 | + `tests/e2e/AGENTS.md` is the deeper reference (label conventions, failure triage, naming rules); AI agents should read it before touching the suite. The `e2e.yml` workflow runs the suite automatically on pushes to `main` and PRs. | ||
| 297 | 317 | ||
| 298 | 318 | ### CI | |
| 299 | 319 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + build/ | ||
| 2 | + __pycache__/ | ||
| 3 | + *.pyc | ||
| 4 | + .DS_Store | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,37 @@ | |||
| 1 | + # PythonNative E2E Suite | ||
| 2 | + | ||
| 3 | + A comprehensive demo app that exercises every public feature in `pythonnative`. It is the target of the top-level Maestro E2E suite and doubles as a living reference for the framework's surface area. | ||
| 4 | + | ||
| 5 | + Unlike `examples/hello-world`, this app is not a marketing demo: it is structured for automated testing. Each PythonNative feature gets a dedicated screen that: | ||
| 6 | + | ||
| 7 | + - Renders a stable, unique title (so Maestro can wait for the screen to appear). | ||
| 8 | + - Exposes interactive controls with stable, unique labels (so Maestro can tap them). | ||
| 9 | + - Prints a "Result:" line that reflects the feature's state (so Maestro can assert behavior, not just rendering). | ||
| 10 | + | ||
| 11 | + ## Running locally | ||
| 12 | + | ||
| 13 | + From the repo root: | ||
| 14 | + | ||
| 15 | + ```bash | ||
| 16 | + cd examples/e2e-suite | ||
| 17 | + pn run android # or: pn run ios | ||
| 18 | + ``` | ||
| 19 | + | ||
| 20 | + Then, in another shell: | ||
| 21 | + | ||
| 22 | + ```bash | ||
| 23 | + # Android | ||
| 24 | + maestro test tests/e2e/android.yaml | ||
| 25 | + | ||
| 26 | + # iOS (use --platform ios if Android is also connected) | ||
| 27 | + maestro --platform ios test tests/e2e/ios.yaml | ||
| 28 | + ``` | ||
| 29 | + | ||
| 30 | + ## Adding a new feature demo | ||
| 31 | + | ||
| 32 | + 1. Add a screen module under `app/screens/<category>/<feature>.py` exporting a `pn.component`-decorated function. | ||
| 33 | + 2. Register it in `app/registry.py` with a unique `id`. | ||
| 34 | + 3. Add a Maestro flow at `tests/e2e/flows/<category>/<feature>.yaml` that opens the screen via its registry `id` and asserts the expected behavior. | ||
| 35 | + 4. Re-run `scripts/check-e2e-coverage.py` to make sure every public symbol in `pythonnative.__all__` is covered by a flow. | ||
| 36 | + | ||
| 37 | + See `tests/e2e/AGENTS.md` for a deeper tour of how AI agents should interact with this suite. | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,39 @@ | |||
| 1 | + """E2E suite entry point. | ||
| 2 | + | ||
| 3 | + Wires every demo screen from :mod:`app.registry` into the root | ||
| 4 | + [`Stack.Navigator`][pythonnative.create_stack_navigator]. The first | ||
| 5 | + route, ``"Home"``, is a categorized list of buttons that opens the | ||
| 6 | + rest of the demos. Each demo screen owns its own back navigation via | ||
| 7 | + [`use_navigation().go_back()`][pythonnative.use_navigation]. | ||
| 8 | + | ||
| 9 | + The stack-only architecture keeps the navigation surface flat and | ||
| 10 | + predictable for automated tests: every demo is reachable in exactly | ||
| 11 | + one push, and every back press lands the user back on ``"Home"``. | ||
| 12 | + """ | ||
| 13 | + | ||
| 14 | + from __future__ import annotations | ||
| 15 | + | ||
| 16 | + import pythonnative as pn | ||
| 17 | + from app.registry import DEMOS | ||
| 18 | + from app.screens.category import CategoryListScreen | ||
| 19 | + from app.screens.home import HomeScreen | ||
| 20 | + | ||
| 21 | + print("[e2e-suite] main module imported") | ||
| 22 | + | ||
| 23 | + Stack = pn.create_stack_navigator() | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + @pn.component | ||
| 27 | + def App() -> pn.Element: | ||
| 28 | + """Root component: a Stack with Home, every Category screen, and every demo.""" | ||
| 29 | + return pn.NavigationContainer( | ||
| 30 | + Stack.Navigator( | ||
| 31 | + Stack.Screen("Home", component=HomeScreen, options={"title": "PythonNative E2E Suite"}), | ||
| 32 | + Stack.Screen( | ||
| 33 | + "Category", | ||
| 34 | + component=CategoryListScreen, | ||
| 35 | + options={"title": "Category"}, | ||
| 36 | + ), | ||
| 37 | + *(Stack.Screen(demo.id, component=demo.component, options={"title": demo.title}) for demo in DEMOS), | ||
| 38 | + ) | ||
| 39 | + ) | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments