Agent skill

E2E Test

by divinevideo in divinevideo/divine-mobile

Run and debug Flutter E2E integration tests that exercise the real app against a local Docker backend (no mocks).

MPL-2.0Auto-check: notesTesting & QA

Install E2E Test

skills CLI
$ npx skills add divinevideo/divine-mobile --skill e2e-test -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install divinevideo/divine-mobile e2e-test --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/divinevideo/divine-mobile.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.agents/skills/e2e-test .claude/skills/e2e-test && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
e2e-test
GitHub stars
266
Token cost
~3.8k tokens
SKILL.md length
1,710 words
Files
1
Skills in repo
103
Repo updated
First seen
Licence
MPL-2.0

At a glance

Run and debug Flutter E2E integration tests that exercise the real app against a local Docker backend (no mocks).

  • Running E2E tests
  • SKILL.md covers Run a test, Version pair, Stack and Emulator, plus 3 more sections
  • Calls mise, flutter and docker
  • Debugging failures

What it does

E2E Test is an agent skill from divinevideo/divine-mobile. Run and debug Flutter E2E integration tests that exercise the real app against a local Docker backend (no mocks). Use when running E2E tests, debugging failures, or working on the local harness.

Its SKILL.md is about 3.8k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

It sits in Testing & QA, covering End-to-end testing, Integration testing and Cross-platform mobile apps. It works with Docker and Flutter. The licence is MPL-2.0.

When your agent uses it

  • Running E2E tests
  • Debugging failures
  • Working on the local harness

Example prompts

  • “/e2e-test”

Requirements

  • Docker

What it can do on your machine

Read from SKILL.md and the folder at commit 4c622be. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • mise
    • flutter
    • docker
    • adb
    • bash
    • rg
    • dart

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Links to these hosts (documentation or services it may open):

    • patrol.leancode.co

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

E2E Test loads about 3.8k tokens when it runs. Until then it costs about 51 tokens; SKILL.md has 1,710 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~51
When it runs · the whole SKILL.md, loaded when a task matches
~3.8k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check: notes

The automated check noted patterns worth knowing about, such as sudo or a known installer.

  • NoteRuns commands with sudoSKILL.md:132
    find it: sudo lsof -nP -iTCP:45173 -sTCP:LISTEN

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from divinevideo/divine-mobile at commit 4c622be, republished under its MPL-2.0 licence (© divinevideo). 1,710 words, ~3,751 tokens.

Download SKILL.mdSave it as .claude/skills/e2e-test/SKILL.md (or your agent's skills folder).
name
e2e-test
description
Run and debug Flutter E2E integration tests that exercise the real app against a local Docker backend (no mocks). Use when running E2E tests, debugging failures, or working on the local harness.
author
Claude Code
version
1.3.0

E2E Integration Testing

Goal: run the real app against a real local backend, end-to-end. OAuth, relay subscriptions, and media uploads all hit local Docker services — no mocks anywhere. Tests live in mobile/integration_test/, backend in local_stack/.

Run a test

Two terminals, from mobile/:

bash
# Terminal 1 — emulator
mise run emulator

# Terminal 2 — tests
mise run e2e_test                                              # All auth tests
mise run e2e_test integration_test/auth/auth_journey_test.dart # Single test

e2e_test brings up the Docker stack, runs the suite, captures a merged docker+logcat+app timeline at test_reports/*.jsonl, and prints the native test XML path + failure excerpts when the APK fails to install. For e2e targets, never call patrol test or flutter test directly — you'll lose the timeline and the diagnostics.

Not every suite is a patrol suite. profile.sh recursively greps the target for patrolTest and dispatches: patrol suites go to patrol test, plain integration_test suites go to flutter test --device-id. Everything under integration_test/e2e/ is now the plain kind. The original four converted off patrol in #7005 because none used the native automator for anything load-bearing, and new suites should follow that pattern. The plain path pre-grants POST_NOTIFICATIONS, since without an automator nothing can dismiss that dialog.

Version pair

patrol (the package) and patrol_cli (the binary) ship as a matched pair, and patrol_cli enforces it at run time — a mismatch aborts the run before any test executes.

HalfVersionDeclared in
patrol package4.9.0mobile/pubspec.yaml (patrol: ">=4.9.0 <4.10.0")
patrol_cli binary4.7.0local_stack/profile.sh (PATROL_CLI_VERSION)

profile.sh checks the installed CLI and runs dart pub global activate patrol_cli <version> when it differs, so mise run e2e_test self-heals. That activation is machine-global: it switches the CLI for every checkout, including worktrees still on an older patrol, which will then fail the same compatibility check until they rebase. Change the two versions together — the compatibility table is at https://patrol.leancode.co/documentation/compatibility-table.

The package constraint pins a single minor rather than using a caret, because that table closes open-ended bands retroactively. A caret range lets flutter pub upgrade walk into a patrol the pinned CLI rejects, and the abort then surfaces at patrol test time, unrelated to whatever the upgrade was actually for.

Stack

ServicePortPurpose
Keycast43000OAuth + NIP-46 signer
FunnelCake Relay47777Nostr relay (WebSocket)
FunnelCake API47777REST API, under /api/ on the same proxy
Blossom43003Media server
Postgres15432Keycast DB

The app reaches these at 10.0.2.2 from the emulator. Cleartext to loopback hosts is permitted in every build type on both platforms.

Only start what your flow needs

Most services are irrelevant to any given test, and local_up failing on one does not mean you are blocked. Check what is actually healthy before debugging a service your flow never calls:

bash
docker compose -f local_stack/docker-compose.yml ps
bash
mise run local_up         # Start (auto-runs local_setup on fresh worktrees)
mise run local_up_cached  # Same, but reuse cached images (offline / rate-limited)
mise run local_down       # Stop
mise run local_reset      # Wipe data + restart
mise run local_status     # Health

If local_up fails only at e2e-seed and the services your test actually needs are healthy (auth tests don't need the indexer), bypass the seed:

bash
bash ../local_stack/profile.sh integration_test/<your_test>.dart

Any local_up failure prints the per-service status, the logs of whatever is down, and that same bypass command. Set E2E_TEST_PATH before the run and it prints the command for your test:

bash
E2E_TEST_PATH=integration_test/auth/auth_journey_test.dart mise run local_up
Port conflicts

up.sh pre-flights every host port in docker-compose.yml before starting anything. This machine runs several compose projects, and stale test containers days old are the normal case, so collisions are routine. The check names the service, the port, and the holder:

  port 43000  wanted by service "keycast"
            held by container "funnelcake-test-clickhouse-sim" — compose project "funnelcake-test"
            remedy: docker rm -f funnelcake-test-clickhouse-sim

  port 45173  wanted by service "keycast"
            held by a host process (not a container), listening on: 127.0.0.1:45173
            find it: sudo lsof -nP -iTCP:45173 -sTCP:LISTEN

Ports already published by our own containers are not conflicts — up.sh is idempotent. The raw daemon error it replaces (Bind for 0.0.0.0:16380 failed: port is already allocated) named neither the service nor the holder.

Listening sockets come from ss on Linux and lsof on macOS, and the find it: line names whichever of the two the machine has. With neither installed the run says so and falls back to container-held ports alone, which docker ps reports without either tool — and a stale container is the usual culprit anyway.

bash local_stack/test_stack_scripts.sh covers these paths against a stubbed docker/ss/lsof, so it needs no daemon and no free ports.

Startup races

Containers sometimes start before Docker's embedded DNS knows a dependency's alias: funnelcake-migrate dies with dial tcp: lookup funnelcake-clickhouse on 127.0.0.11:53: no such host, or keycast burns its DB connection attempts on Temporary failure in name resolution. Both succeed on an unchanged retry. up.sh re-runs the whole up (idempotent — it restarts whatever died) up to 3 attempts, 5s apart, only when it sees a name-resolution signature in the compose output or in the failed containers' logs. A port clash or a bad image fails straight through rather than retrying pointlessly.

Emulator

bash
mise run emulator           # Normal launch (auto-detects DISPLAY)
mise run emulator_headless  # Offscreen, no window
mise run emulator_wipe      # -wipe-data (storage exhausted)

Override AVD: AVD_NAME=<name> mise run emulator. Always uses -gpu host.

Debug builds render with Impeller, as release does. On an emulator the engine picks Impeller OpenGLES, never Vulkan. If an emulator vanishes or cannot render, e2e_test cannot pass a flag: it calls patrol test or flutter test with fixed arguments, and Patrol launches the app without intent extras. Build with the opt-out instead: ORG_GRADLE_PROJECT_divineDisableImpeller=true mise run e2e_test .... mobile/docs/ANDROID_LOCAL_SETUP.md ("Debug builds render with Impeller") lists it with the flags for flutter run and adb.

Skip the per-run reinstall with PATROL_NO_UNINSTALL=true mise run e2e_test ... when iterating fast and the APK hasn't changed. Stale-state debugging cost is yours.

Buffer auth-flow logs: adb logcat -G 16M (default 256 KB rotates mid-flow).

Storage exhaustion

Not just a Patrol problem — flutter run hits it too, and the error is on the install, not the build:

java.io.IOException: Requested internal only, but not enough space

A debug APK is ~289 MB and needs real headroom on top of that. adb shell pm trim-caches 1G often does not free enough; mise run emulator_wipe (emulator.sh --wipe) is usually the faster fix.

Patterns

Launching the app

pumpAndSettle hangs because of persistent polling timers — the app polls email verification every 3s, so the tree never reaches a quiescent frame and the call blocks until its 10-minute timeout. Use launchAppGuarded (from test_setup.dart) and a bounded pump instead of pumpAndSettle, and run the whole scenario inside runWithAppErrorHandlers (see below):

dart
await runWithAppErrorHandlers(() async {
  launchAppGuarded(app.main);

  await pumpUntilSettled(tester, maxSeconds: 3);
  // ...the scenario...

  drainAsyncErrors(tester);
});

When you need to stop as soon as something appears rather than pump a fixed budget, use waitForText / waitForWidget from navigation_helpers.dart — both poll and return early.

The tell that a suite has this bug: patrol logs PATROL_LOG {"type":"test",…,"status":"start"} and then no terminal status at all, while the app keeps logging. It reads like a crash; it is a hang.

runWithAppErrorHandlers is what lets a failed check fail the test. app.main() replaces FlutterError.onError with a handler that does not chain to flutter_test's, and flutter_test reports a failed expect through whichever handler is current. Outside the helper the failure never reaches the binding: the log shows the app's Flutter Error: Expected: … line, then Failed assertion: … '_pendingExceptionDetails != null', and the run sits there until it is killed (#9659). The helper puts the original handler back before the failure propagates, suppresses known relay and teardown noise, and restores ErrorWidget.builder, which flutter_test checks at the end of the test body. check_integration_test_error_restore_safety.sh fails a suite that imports main.dart without it.

Show full SKILL.md (605 more words)Show less
Async publish → relay query

UI navigates before publish/upload completes. Poll the relay:

dart
for (var i = 0; i < 120; i++) {
  await tester.pump(const Duration(milliseconds: 500));
  events = await queryRelay(filter);
  if (events.isNotEmpty) break;
}
Onboarding sheets blocking UI

New bottom sheets may cover the target widget:

dart
for (var i = 0; i < 20; i++) {
  await tester.pump(const Duration(milliseconds: 250));
  final gotIt = find.text('Got it!');
  if (gotIt.evaluate().isNotEmpty) {
    await tester.tap(gotIt);
    break;
  }
}
Android permission dialogs blocking UI

A fresh install has no runtime permissions, and Android's permission dialog sits in front of the app until it is answered. After sign-in the app asks for notification permission; while that dialog is up, a bottom sheet never finishes sliding in, so taps on its buttons miss or time out. Patrol suites answer it with dismissNotificationPermission($) before the step it would block, and call grantCameraAndMicrophone($) before anything that records. Both match the dialog by its button id, not by the text "Allow", which the notification dialog's title also contains. Plain testWidgets suites cannot answer a native dialog, so local_stack/profile.sh grants them the notification permission before the run.

Patrol false positives

Patrol bundles every file in a target dir into one APK. When file B runs, file A shows up as "not requested" [E] markers in logcat. Trust only the final ✅/❌ lines.

Never put / or # in a patrol test name

Patrol names each JUnit case MainActivityTest#runDartTest[<dart test name>], and the AndroidX orchestrator writes a per-test output file named after it. Android's ContextImpl.makeFilename rejects any filename containing a path separator, so a test called e.g. 'strips metadata via separate input/output paths' crashes the orchestrator:

FATAL EXCEPTION: AndroidTestOrchestrator
java.lang.IllegalArgumentException: File …input/output paths].txt
contains a path separator

The tell is a green summary with a non-zero exit: Gradle reports Instrumentation run failed due to Process crashed and exits 1, while patrol prints Failed: 0 — because the offending test never started and so was never counted. Compare Total: against the number of tests in the file when the exit code disagrees with the summary.

A # crashes nothing, but the orchestrator reads it as the class/method separator and cuts the JUnit id there, so tests whose names share the text before it collapse into one entry in the test report. The three Bug #2233 -- … tests in repro_log2_delete_test.dart reported as one.

Write input and output, not input/output, and Bug 2233, not Bug #2233. test/integration_test_helpers/patrol_test_names_test.dart fails CI when a group or patrolTest name in a Patrol suite contains either character. It reads names from source, so it also fails a name that is not a plain string literal: a variable, an interpolation, a raw string or a concatenation.

Provider error caching

Providers using requireIdentity (or similar non-nullable getters) crash during cold start and Riverpod caches the error forever. Use the nullable accessor (currentIdentity) and handle null.

Material ancestor

TextField in an overlay/transition without Scaffold needs:

dart
Material(color: Colors.transparent, child: TextField(...))

Helpers

integration_test/helpers/:

  • test_setup.dart — launchAppGuarded, error suppression, async-error drain
  • navigation_helpers.dart — register, login, tap tabs, wait for widgets
  • relay_helpers.dart — publish/query Nostr events
  • db_helpers.dart — Postgres (verification tokens, refresh tokens)
  • http_helpers.dart — Keycast API (verify email, forgot password)
  • permission_helpers.dart — answer Android permission dialogs through Patrol (camera and microphone, notifications)
  • constants.dart — ports + appPackage

Debugging

Never pipe a long-running command through tail/head
bash
flutter run ... | tail -40      # WRONG
flutter run ... > /tmp/run.log 2>&1   # then read/grep the file

Two separate failures. The pipe buffers until the command exits, so you watch a blank screen and lose everything if you kill it. And the pipeline's exit status is tail's, so a failed run reports success. Redirect to a file and read that instead.

bash
# Service logs
docker compose -f local_stack/docker-compose.yml logs keycast --tail=50
docker compose -f local_stack/docker-compose.yml logs blossom | grep -v 'path=/'

# Auth trace
adb logcat -d | grep 'flutter.*\[AUTH\]' | grep -v 'Router redirect'

# Last merged timeline
ls mobile/test_reports/*.jsonl

The timeline is where cross-service failures actually show up. A test can fail in teardown from an unhandled async error against a service that is down — Patrol's summary only says the test failed, while the timeline names the URL that was refused. Read it before writing a failure off as flaky.

bash
rg -o '.{0,60}logout.{0,50}' mobile/test_reports/<run>.jsonl

If patrol reports Total: 0 with Gradle exit 1, the runner auto-prints the native test XML path + failure excerpts — that's an APK install failure, not a missing test. Free space with adb shell pm trim-caches 1G or mise run emulator_wipe.

© divinevideo, MPL-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .agents/skills/e2e-test of divinevideo/divine-mobile.

Open the folder on GitHubat commit 4c622be

Compare with similar skills

E2E Test next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

E2E Test compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
E2E Test this skilldivinevideo/divine-mobile266—~3.8kAutomated safety check: NotesMPL-2.0
Mobile Testingirahardianto/awesome-agv157—~1.8kAutomated safety check: NotesMIT
Specialist Integration Test GeneratorHoangNguyen0403/agent-skills-standard571—~548Automated safety check: PassMIT
MongoDB Source Connector E2E Harnessairbytehq/airbyte22k—~1.9kAutomated safety check: PassCustom licence
Go Redis Client Test Runnerredis/go-redis22k—~786Automated safety check: PassBSD-2-Clause
Airbyte Postgres Source E2E Testsairbytehq/airbyte22k—~2.5kAutomated safety check: PassCustom licence

Similar skills

  • Mobile Testing

    irahardianto/awesome-agv

    Mobile E2E testing patterns — Flutter integrationtest, Patrol, Maestro, golden testing, device matrix, and test data management.

    157 GitHub stars~1.8k tokensUpdated 3 days ago
    Testing & QAAuto-check: notes
  • Specialist Integration Test Generator

    HoangNguyen0403/agent-skills-standard

    Generates one integration/E2E test from an approved test case spec using existing project patterns.

    571 GitHub stars~548 tokensUpdated today
    Testing & QAAuto-check passed
  • Official

    Starts a throwaway MongoDB 7.0 replica set and runs the Airbyte spec, check, discover and read commands against source-mongodb-v2 images for local end-to-end testing.

    22k GitHub stars~1.9k tokensUpdated today
    Testing & QAAuto-check passed
  • Official

    Explains how to run go-redis tests: the Docker Compose stack, make targets, focusing a single Ginkgo spec, the e2e suite and the version environment variables.

    22k GitHub stars~786 tokensUpdated today
    Testing & QAAuto-check passed
  • Official

    Starts a local PostgreSQL 16 container, loads SQL fixtures and runs the Airbyte spec, check, discover and read commands against a chosen source-postgres image.

    22k GitHub stars~2.5k tokensUpdated today
    Testing & QAAuto-check passed
  • Official

    Reproduces PostgreSQL logical-decoding CDC behavior for Airbyte's source-postgres connector on a local backend, with CDC fixtures, a catalog and smoke case scripts.

    22k GitHub stars~1.9k tokensUpdated today
    Testing & QAAuto-check passed

More from divinevideo/divine-mobile

All 103 skills in this repo
  • Fix ArgoCD ExternalSecret deployment failing with "namespace X is not permitted in project Y".

    266 GitHub stars~931 tokensUpdated today
    Auto-check passed
  • Art Direct

    divinevideo/divine-mobile

    Art direction for any content — reads text, PDF, Word, HTML, PPT, then proposes 2-3 creative directions with photography style, mood, and visual language.

    266 GitHub stars~4.8k tokensUpdated today
    Auto-check passed
  • Async Await Null Race Condition

    divinevideo/divine-mobile

    Fix "Null check operator used on a null value" errors when an object is set to null during an async await.

    266 GitHub stars~881 tokensUpdated today
    Auto-check passed
  • AWS V4 Signing Custom Headers Gcs

    divinevideo/divine-mobile

    Add custom metadata headers (x-amz-meta-) to AWS v4 signed requests for GCS S3-compatible API.

    266 GitHub stars~1k tokensUpdated today
    Auto-check passed
  • Bash Herestring Newline Secrets

    divinevideo/divine-mobile

    Fix password/secret authentication failures caused by trailing newlines when creating Google Cloud secrets (or similar) with bash here-strings.

    266 GitHub stars~791 tokensUpdated today
    Auto-check passed
  • Fix silent video/media processing failures caused by URL extraction code that filters on file extensions (.mp4, .webm, .webp).

    266 GitHub stars~1.1k tokensUpdated today
    Auto-check passed

Works with

Categories

Questions about E2E Test

What does E2E Test do?

Run and debug Flutter E2E integration tests that exercise the real app against a local Docker backend (no mocks). E2E Test is an agent skill from divinevideo/divine-mobile. Run and debug Flutter E2E integration tests that exercise the real app against a local Docker backend (no mocks).

When should I use E2E Test?

E2E Test fits situations like: running E2E tests; debugging failures; working on the local harness.

How do I install E2E Test in Claude Code?

Run `npx skills add divinevideo/divine-mobile --skill e2e-test -a claude-code`. Or copy the skill folder (.agents/skills/e2e-test in divinevideo/divine-mobile) into .claude/skills/e2e-test in your project. Claude Code loads it when a task matches its description.

How do I install E2E Test in Codex?

Run `npx skills add divinevideo/divine-mobile --skill e2e-test -a codex`. Or copy the skill folder (.agents/skills/e2e-test in divinevideo/divine-mobile) into .agents/skills/e2e-test in your project. Codex loads it when a task matches its description.

Can I use E2E Test in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add divinevideo/divine-mobile --skill e2e-test -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/e2e-test, .gemini/skills/e2e-test, .github/skills/e2e-test and .opencode/skills/e2e-test in your project.

What does E2E Test need to run?

Going by SKILL.md and its folder, E2E Test needs the command-line tools its instructions call (mise, flutter, docker, adb, bash and rg). Our summary lists: Docker.

Does E2E Test access the network?

SKILL.md names 1 domain. As links in the text: patrol.leancode.co. This is read from the text; nothing was executed.

Is E2E Test safe to install?

Our automated static check of SKILL.md found notes only (runs commands with sudo), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.

What licence does E2E Test use?

E2E Test is published under the MPL-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does E2E Test use?

About 3.8k tokens (SKILL.md is roughly 15k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to E2E Test?

Skills that share tags, products or a category with E2E Test: Mobile Testing (irahardianto/awesome-agv, 157 stars), Specialist Integration Test Generator (HoangNguyen0403/agent-skills-standard, 571 stars), MongoDB Source Connector E2E Harness (airbytehq/airbyte, 22k stars) and Go Redis Client Test Runner (redis/go-redis, 22k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains E2E Test?

divinevideo (a GitHub organization) maintains it in divinevideo/divine-mobile, which has 266 GitHub stars. The repository holds 103 skills in this directory. The repository was last updated on October 8, 2026.

Source: divinevideo/divine-mobile on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.