diff --git a/.claude/skills/ios-sim-navigation/SKILL.md b/.claude/skills/ios-sim-navigation/SKILL.md index 6ec6393dc54b..ea0418bcabc6 100644 --- a/.claude/skills/ios-sim-navigation/SKILL.md +++ b/.claude/skills/ios-sim-navigation/SKILL.md @@ -104,6 +104,32 @@ Run these from the project root that should own the `.build/WebDriverAgent` cache. `wda-start.rb` resolves the path relative to its working directory and clones into it on first run. +## Launching the app with custom options (caller-supplied) + +By default you don't launch the app yourself — the first `tap.rb` binds a +session to whatever app is in the foreground. But some callers need the app +launched with **specific launch arguments or environment variables**: test +configuration, feature flags, or instrumentation that an external instrument +reads from the app's environment (a profiler, a leak detector, etc.). + +This skill is agnostic about *what* those options are. It just gives the caller +a way to inject them: launch the app through WDA with `scripts/wda-session.rb` +**before** any `tap.rb` call, so the instrumented process is the one WDA drives. + +```bash +# Launch arguments (order-preserving; a `-key value` pair is two --arg tokens). +ruby scripts/wda-session.rb --bundle com.example.app --arg -some-flag --arg value + +# Environment variables (e.g. to enable an instrument the caller cares about). +ruby scripts/wda-session.rb --bundle com.example.app --env SOME_INSTRUMENT_VAR=1 +``` + +Don't substitute `simctl launch` for this — its options are silently discarded +when WDA binds the session. Establish the `wda-session.rb` session first, then +drive normally; don't `simctl launch` again or delete the session file mid-run +(either relaunches the app without the options). `references/sessions.md` +explains why. + ## Tap — the default action **Use `scripts/tap.rb` for every tap.** It collapses session creation diff --git a/.claude/skills/ios-sim-navigation/references/sessions.md b/.claude/skills/ios-sim-navigation/references/sessions.md index 1491fbf6f623..3792fec855ae 100644 --- a/.claude/skills/ios-sim-navigation/references/sessions.md +++ b/.claude/skills/ios-sim-navigation/references/sessions.md @@ -35,24 +35,40 @@ Don't mint a fresh session with `alwaysMatch:{}` for these calls — that produces an unbound session whose `/actions` requests return HTTP 200 but never reach the UI. -## Launching with arguments: use `wda-session.rb`, not `simctl` +## Launching with arguments or environment: use `wda-session.rb`, not `simctl` Creating a session relaunches the target app even when it's already running -(`forceAppLaunch` defaults to `YES`), so any arguments passed via -`simctl launch -key value` are lost — they belong to the original process, and -the relaunch starts a new one. +(`forceAppLaunch` defaults to `YES`), so any arguments or environment passed via +`simctl launch` are lost — they belong to the original process, and the relaunch +starts a new one. -So when the app needs launch arguments, don't pass them with `simctl`. Let WDA -launch the app with them in the call that creates the session, using -`scripts/wda-session.rb`. It persists the session so `tap.rb` reuses it (no -further relaunch): +So when the app needs launch arguments or environment variables, don't pass them +with `simctl`. Let WDA launch the app with them in the call that creates the +session, using `scripts/wda-session.rb`. It persists the session so `tap.rb` +reuses it (no further relaunch): ```bash +# Launch arguments (order-preserving): ruby scripts/wda-session.rb --bundle com.example.app \ --arg -some-flag --arg value + +# Environment variables (--env is repeatable; split on the first '='): +ruby scripts/wda-session.rb --bundle com.example.app \ + --env MallocStackLogging=1 --env SOME_FLAG=on ``` -Once that session exists, avoid forcing another relaunch before the arguments +`--arg` populates the app's `launchArguments`; `--env` populates its +`launchEnvironment`. Both ride along as WDA session capabilities (`arguments` +and `environment` under `alwaysMatch`), so WDA's launch is the one that carries +them. + +The skill is agnostic about what these are — they're whatever the *caller* +needs. A common use of `--env` is enabling an external instrument that reads the +app's environment at launch (for example, a memory tool that needs malloc stack +logging turned on in the target process). The skill doesn't need to know which +instrument; the caller passes the variable. + +Once that session exists, avoid forcing another relaunch before the options are consumed (don't `simctl launch` again, don't delete the session file). `--wait-quiescence` is off by default because a screen with a spinner can keep the app from going quiescent and stall the call. diff --git a/.claude/skills/ios-sim-navigation/scripts/wda-session.rb b/.claude/skills/ios-sim-navigation/scripts/wda-session.rb index 8e13c80ef3b9..bfd8800a21d0 100755 --- a/.claude/skills/ios-sim-navigation/scripts/wda-session.rb +++ b/.claude/skills/ios-sim-navigation/scripts/wda-session.rb @@ -1,26 +1,40 @@ #!/usr/bin/env ruby # wda-session.rb — Create a WDA session bound to an app, launching it with -# launch arguments, and persist the session so tap.rb reuses it. +# caller-supplied launch arguments and/or environment variables, and persist +# the session so tap.rb reuses it. # # Why this exists: creating a WDA session relaunches the target app by default -# (forceAppLaunch defaults to YES), which discards any arguments passed via -# `simctl launch -key value` (they belong to the original process). This script -# instead lets WDA launch the app with the arguments, so the process WDA drives -# has them, and persists the session so tap.rb reuses it (no further relaunch). +# (forceAppLaunch defaults to YES), which discards any arguments or environment +# passed via `simctl launch` (they belong to the original process, and the +# relaunch starts a fresh one). This script instead lets WDA launch the app with +# the arguments and environment, so the process WDA drives has them, and persists +# the session so tap.rb reuses it (no further relaunch). +# +# This script is agnostic about WHAT the arguments and environment are — the +# caller decides. They might be test configuration, feature flags, or +# instrumentation an instrument needs in the app's environment (e.g. a profiler +# or leak detector toggled by an env var). See references/sessions.md. # # Usage: +# # Launch arguments (e.g. test configuration): # wda-session.rb --bundle com.automattic.jetpack \ # --arg -ui-test-site-url --arg https://example.com \ # --arg -ui-test-site-user --arg demo \ # --arg -ui-test-site-pass --arg secret # +# # Environment variables (e.g. enabling an instrument): +# wda-session.rb --bundle com.automattic.jetpack --env MallocStackLogging=1 +# # Each --arg contributes one token to launchArguments, in order. A # `-key value` pair is two --arg values, exactly as you'd pass them to -# `simctl launch`. +# `simctl launch`. Each --env contributes one KEY=VALUE pair to the app's +# launch environment (split on the first '='). # # Options: # --bundle ID Required. App bundle id to launch and bind to. # --arg VALUE Repeatable. One launch-argument token (order preserved). +# --env KEY=VALUE Repeatable. One environment variable for the app's +# launch environment. # --port PORT WDA port (default: 8100, or $WDA_PORT). # --wait-quiescence Wait for app quiescence after launch (default: off; # a spinning login screen can keep an app from going @@ -36,12 +50,21 @@ port = (ENV["WDA_PORT"] || 8100).to_i bundle = nil args = [] +env = {} wait_quiescence = false parser = OptionParser.new do |opts| - opts.banner = "Usage: wda-session.rb --bundle ID [--arg VALUE ...] [--port PORT] [--wait-quiescence]" + opts.banner = "Usage: wda-session.rb --bundle ID [--arg VALUE ...] [--env KEY=VALUE ...] [--port PORT] [--wait-quiescence]" opts.on("--bundle ID") { |v| bundle = v } opts.on("--arg VALUE") { |v| args << v } + opts.on("--env KEY=VALUE") do |v| + key, value = v.split("=", 2) + if key.nil? || key.empty? || value.nil? + $stderr.puts "error: --env expects KEY=VALUE, got: #{v}" + exit 2 + end + env[key] = value + end opts.on("--port PORT", Integer) { |v| port = v } opts.on("--wait-quiescence") { wait_quiescence = true } end @@ -52,13 +75,16 @@ exit 2 end -caps = { - "alwaysMatch" => { - "bundleId" => bundle, - "arguments" => args, - "shouldWaitForQuiescence" => wait_quiescence - } +always_match = { + "bundleId" => bundle, + "arguments" => args, + "shouldWaitForQuiescence" => wait_quiescence } +# Only include the environment key when set, so a plain session isn't given an +# empty launch environment. +always_match["environment"] = env unless env.empty? + +caps = { "alwaysMatch" => always_match } uri = URI("http://localhost:#{port}/session") req = Net::HTTP::Post.new(uri, "Content-Type" => "application/json")