Skip to content

Add mobile replay harness#60

Open
maciejfs wants to merge 2 commits into
mainfrom
maciej/mobile-harness
Open

Add mobile replay harness#60
maciejfs wants to merge 2 commits into
mainfrom
maciej/mobile-harness

Conversation

@maciejfs
Copy link
Copy Markdown
Collaborator

@maciejfs maciejfs commented May 8, 2026

Adds scripts/mobile/: a set of Node scripts that drive any iOS app on a physical device and then check whether the FS replay looks right.

iOS only for now. Android is not supported yet and will come in a follow-up.

How it works

You write a goal JSON file that describes what screen to navigate to, what taps to perform, and what the replay should contain. The runner reads that file plus env config, connects to the device through Lidar live tools (or Appium directly), drives the goal, and writes snapshots and a report. Separate scripts then fetch the FS replay, pull out observations, and validate them against the goal.

Nothing is hardcoded for any particular app. All app-specific config comes from the goal JSON and .env.local.

What's in here

  • run-lidar-live-ios.mjs: drives the app through Lidar MCP live tools
  • run-goal.mjs: drives the app through Appium/WebDriverIO
  • run-local-lidar-ios.mjs: builds and starts a local Lidar from your mn checkout, starts Appium, runs the goal, and writes the FS replay URL when the run finishes
  • extract-session-url.mjs: pulls the iOS NSUserDefaults plist through Appium and writes the FS replay URL (used by the runner above, also runnable on its own)
  • validate-goal-artifacts.mjs: checks device artifacts match the goal
  • fetch-subtext-review-evidence.mjs: fetches replay evidence from Subtext MCP
  • capture-subtext-review-observations.mjs: extracts structured observations from replay evidence
  • validate-replay-observations.mjs: compares observations to goal expectations
  • goals/example.json: documented goal skeleton you copy and edit for your app
  • appium-layer.mjs, device-e2e-common.mjs: shared Appium/device plumbing

How to test

  1. Copy scripts/mobile/mobile.env.example to scripts/mobile/.env.local and fill in your values.
  2. Write a goal JSON for your app (or use an existing one locally).
  3. Make sure node is on your PATH:
cd ~/src/subtext
export PATH="$HOME/src/fsdev/tools/node/bin:$PATH"
  1. Run:
node scripts/mobile/run-local-lidar-ios.mjs

That builds Lidar, starts Appium, connects to the device, runs the goal, and writes output to tmp/. Takes about 3 minutes.

  1. Wait ~90s for the replay to ingest, then validate:
export MOBILE_OUT_DIR=$(cat scripts/mobile/tmp/.last-run-dir)
node scripts/mobile/validate-goal-artifacts.mjs
node scripts/mobile/fetch-subtext-review-evidence.mjs
node scripts/mobile/capture-subtext-review-observations.mjs
node scripts/mobile/validate-replay-observations.mjs

For a remote Lidar server, set LIDAR_IOS_MCP_URL and run node scripts/mobile/run-lidar-live-ios.mjs.

See README.md in scripts/mobile/ for the full details.

Example .env.local (for internal FullStoryTestingApp testing)

FULLSTORY_API_KEY=<your key from app.fullstory.com>
MOBILE_BUNDLE_ID=com.fullstory.FullStoryTestingApp
MOBILE_UDID=<your device udid, from: xcrun devicectl list devices>
MOBILE_DEVICE_NAME=<your device name, e.g. "Maciej's iPhone 14">
LOCAL_MCP_ORG_ID=KWH
LOCAL_MCP_EMAIL=you@fullstory.com
MOBILE_GOAL_EXPECTATIONS=./scripts/mobile/goals/images.json
MOBILE_FULLSTORY_APP_HOST=https://app.staging.fullstory.com

The images.json goal file is not committed (gitignored), but you can create one by copying goals/example.json and filling in the navigation steps for the Images screen in FullStoryTestingApp.

MOBILE_FULLSTORY_APP_HOST is only needed for non-prod orgs (staging or EU). It defaults to https://app.fullstory.com.

Related

Lidar iOS live tool support: https://github.com/cowpaths/mn/pull/104208

Generic harness for driving any iOS app through Lidar live tools or
Appium, then validating the matching FullStory replay. Navigation,
target screen, scroll counts, and replay heuristics all come from a
goal JSON manifest. No app-specific defaults in committed code.

Scripts: run-lidar-live-ios, run-goal, run-local-lidar-ios,
validate-goal-artifacts, validate-replay-observations,
fetch-subtext-review-evidence, capture-subtext-review-observations,
capture-replay-observations-from-snapshot, prepare-subtext-review.
@maciejfs maciejfs force-pushed the maciej/mobile-harness branch from 77b1926 to dad1627 Compare May 8, 2026 19:14
Adds extract-session-url.mjs. It opens an Appium session, pulls the
iOS NSUserDefaults plist for the target bundle, reads the FullStory
previous-session and previous-user IDs, and writes the replay URL to
fullstory-session-url.txt. The local Lidar runner now calls this when
the goal finishes so the validate-* and Subtext review scripts have a
real session URL to work with. No app code changes needed for any
customer.

The runner also calls mobile: terminateApp before each run so the SDK
starts a fresh FullStory session instead of reusing the previous one.
The replay URL uses MOBILE_FULLSTORY_APP_HOST so staging and EU orgs
land on the right host. README and mobile.env.example are updated to
match, including a note that this is iOS only for now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant