An agent-runnable script that drives the whole push-notification pipeline on a
real device/emulator: a second identity publishes each notification kind through
`amy` to the account on the phone, and the script reads back the notification
shade over `adb` to assert the right notification on the right channel — then
exercises cold-push metadata enrichment (title flips from raw pubkey to display
name in place) and dismiss-on-read (opening the note clears the tray entry).
Covers mention, reply, reaction, repost, picture, and DM; prints PASS/WARN/FAIL
and exits non-zero on any hard failure. tools/notification-e2e/{run.sh,README.md}.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0122uQ8BLHLeHDni81RBP26r
Notification end-to-end test (Layer 3)
Drives the whole push-notification pipeline on a real device/emulator: a second
identity publishes each notification kind through amy to the account logged
into the phone, and the script reads the system notification shade back over
adb to assert the right notification landed on the right channel — then tests
the two behaviors unit tests can't reach: cold-push metadata enrichment and
dismiss-on-read.
It's written to be run unattended by an agent: every check prints
PASS / WARN / FAIL, and the script exits non-zero if any hard assertion
fails.
What it covers
| Check | How it's triggered | Asserted from the shade |
|---|---|---|
| Mention (+ liveness) | amy event --kind 1 p-tagging A |
MentionsID + "mentioned you" |
| Reply | kind 1 with e+p tags to A's note |
RepliesID + "replied" |
| Reaction | kind 7 to A's note | ReactionsID + "reacted" |
| Repost (new kind) | kind 6 to A's note | RepostsID + "reposted" |
| Picture (new kind) | kind 20 with imeta + p |
MediaID + "shared a photo" |
| DM | amy dm send |
PrivateMessagesID + the body |
| Enrichment | cold-start, then publish sender kind:0 | title flips from raw pubkey → display name in place |
| Dismiss-on-read | deep-link the note, then re-check | notification gone |
Zaps (need a Lightning wallet), git/badge/nutzap/onchain (need repo / badge
definition / cashu proofs) aren't automated here — they follow the same
amy event --kind … pattern with the appropriate tags if you want to extend it.
Prerequisites
-
A device/emulator on
adbwith the debug app installed and an account logged in — this is identity A, the receiver. -
amybuilt:./gradlew :cli:installDist(binary atcli/build/install/amy/bin/amy). -
jqonPATH. -
A relay both sides use. It must be in A's inbox/read relays and A's always-on notification service must be enabled, so events arrive while the app is backgrounded. For an emulator with a local relay:
amy serve --port 7777 & # embeds the geode relay adb reverse tcp:7777 tcp:7777 # phone's 127.0.0.1:7777 → host # then add ws://127.0.0.1:7777 to the account's relays in the app
Run
A_NPUB=npub1… RELAY=wss://relay.example ./tools/notification-e2e/run.sh
Reply/reaction/repost need a note authored by A on the relay; if A has none the script warns and skips those three — post one from the phone first for full coverage.
Knobs
| Env | Default | |
|---|---|---|
A_NPUB |
(required) | receiver npub (the phone's account) |
RELAY |
(required) | relay both sides use |
APP_PKG |
com.vitorpamplona.amethyst.debug |
app id |
ADB_SERIAL |
first device | adb -s target |
AMY |
cli/build/install/amy/bin/amy |
amy binary |
TIMEOUT |
40 |
seconds to wait for a notification |
IMG_URL |
a small public jpg | image for the media test |
Notes / limitations
- Verification greps
dumpsys notification --noredactfor the channel id and a content string; it doesn't bind them to the same record, so run it against a reasonably quiet device. - The dismiss-on-read step depends on the note deep-link format; if it can't
confirm the clear it
WARNs rather than failing, so eyeball it once. - It exercises the always-on relay delivery path (no FCM/UnifiedPush server needed). To test the real push transport, send through your push server and keep the rest of the flow identical.