Skip to content

Commit 7801380

Browse files
committed
Add Android emulator startup args support
1 parent 2b96df9 commit 7801380

17 files changed

Lines changed: 449 additions & 73 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ simdeck --device <other-udid> describe --format agent --max-depth 2
111111
simdeck list
112112
simdeck use <udid>
113113
simdeck boot <udid>
114+
simdeck boot android:<avd-name> --android-emulator-arg=-no-snapshot
114115
simdeck shutdown
115116
simdeck erase
116117
simdeck install /path/to/App.app

actions/run-android-comment-session/action.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ inputs:
8282
description: Android SDK build-tools version used to inspect APK metadata.
8383
required: false
8484
default: "36.0.0"
85+
android_emulator_args:
86+
description: Extra Android emulator startup arguments, one argument per line.
87+
required: false
88+
default: ""
8589
public_health_check:
8690
description: Verify the public Cloudflare Tunnel health endpoint before continuing.
8791
required: false
@@ -123,6 +127,7 @@ runs:
123127
INPUT_ANDROID_TARGET_VALUE: ${{ inputs.android_target }}
124128
INPUT_ANDROID_ARCH_VALUE: ${{ inputs.android_arch }}
125129
INPUT_ANDROID_BUILD_TOOLS_VALUE: ${{ inputs.android_build_tools }}
130+
INPUT_ANDROID_EMULATOR_ARGS_VALUE: ${{ inputs.android_emulator_args }}
126131
INPUT_PUBLIC_HEALTH_CHECK_VALUE: ${{ inputs.public_health_check }}
127132
INPUT_CI_PROXY_URL_VALUE: ${{ inputs.ci_proxy_url }}
128133
INPUT_PROXY_LINKS_VALUE: ${{ inputs.proxy_links }}
@@ -163,6 +168,7 @@ runs:
163168
write_env "SIMDECK_ANDROID_TARGET" "${INPUT_ANDROID_TARGET_VALUE}"
164169
write_env "SIMDECK_ANDROID_ARCH" "${INPUT_ANDROID_ARCH_VALUE}"
165170
write_env "SIMDECK_ANDROID_BUILD_TOOLS" "${INPUT_ANDROID_BUILD_TOOLS_VALUE}"
171+
write_env "SIMDECK_ANDROID_EMULATOR_ARGS" "${INPUT_ANDROID_EMULATOR_ARGS_VALUE}"
166172
write_env "SIMDECK_CI_PROXY_URL" "${INPUT_CI_PROXY_URL_VALUE}"
167173
write_env "SIMDECK_PROXY_LINKS" "${INPUT_PROXY_LINKS_VALUE}"
168174
write_env "SIMDECK_SESSION_PASSWORD" "${INPUT_SESSION_PASSWORD_VALUE}"
@@ -656,7 +662,15 @@ runs:
656662
echo "ANDROID_UDID=${udid}" >> "${GITHUB_ENV}"
657663
echo "udid=${udid}" >> "${GITHUB_OUTPUT}"
658664
echo "Booting ${udid}"
659-
simdeck --server-url "http://127.0.0.1:${SIMDECK_PORT}" boot "${udid}"
665+
boot_args=()
666+
if [[ -n "${SIMDECK_ANDROID_EMULATOR_ARGS:-}" ]]; then
667+
while IFS= read -r arg; do
668+
if [[ -n "${arg//[[:space:]]/}" ]]; then
669+
boot_args+=(--android-emulator-arg="${arg}")
670+
fi
671+
done <<< "${SIMDECK_ANDROID_EMULATOR_ARGS}"
672+
fi
673+
simdeck --server-url "http://127.0.0.1:${SIMDECK_PORT}" boot "${udid}" "${boot_args[@]}"
660674
date +%s > /tmp/sim-boot-end
661675
662676
- name: Update status comment with booted emulator URL

docs/api/rest.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,17 @@ Device IDs come from `/api/simulators`. Android IDs use the `android:` prefix.
7070
Booted devices are listed first. Paired iPhone and Apple Watch entries include
7171
`pairedWatchUDID` or `pairedPhoneUDID` when CoreSimulator reports a pairing.
7272

73+
Android emulator boot accepts optional startup arguments:
74+
75+
```json
76+
{
77+
"androidEmulatorArgs": ["-no-snapshot"]
78+
}
79+
```
80+
81+
SimDeck appends its own selected AVD and gRPC port flags around those arguments;
82+
`-avd`, `@AVD`, and `-grpc` are reserved.
83+
7384
Create requests use identifiers from `/api/simulators/create-options`. New
7485
devices are booted before the response is returned. If an iOS simulator is
7586
created with `pairedWatch`, the watch is created, paired, and booted too.
@@ -97,7 +108,8 @@ Android:
97108
"platform": "android",
98109
"name": "Pixel_8_API_36",
99110
"deviceTypeIdentifier": "pixel_8",
100-
"runtimeIdentifier": "system-images;android-36;google_apis;arm64-v8a"
111+
"runtimeIdentifier": "system-images;android-36;google_apis;arm64-v8a",
112+
"androidEmulatorArgs": ["-no-snapshot"]
101113
}
102114
```
103115

docs/cli/commands.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ simdeck list
5656
simdeck list --format json
5757
simdeck use <udid>
5858
simdeck boot <udid>
59+
simdeck boot android:<avd-name> --android-emulator-arg=-no-snapshot
5960
simdeck shutdown
6061
simdeck erase
6162
```
@@ -68,6 +69,10 @@ inventory, including paths and display metadata.
6869
directory. After that, most device commands can omit `<udid>`; explicit UDIDs
6970
still override the default.
7071

72+
For Android emulator startup flags, repeat `--android-emulator-arg=<arg>` on
73+
`simdeck boot`. SimDeck still owns the AVD selector and gRPC port used for the
74+
browser stream.
75+
7176
## Apps and URLs
7277

7378
```sh

docs/cli/flags.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ Alias: `snapshot`.
5353
| `--point <x>,<y>` | Describe the element at a screen point |
5454
| `--direct` | Skip service and use native accessibility directly |
5555

56+
## Device lifecycle
57+
58+
| Command | Useful flags |
59+
| ------- | ----------------------------------------------------------------------------------------------------------------------- |
60+
| `boot` | `--android-emulator-arg=<arg>` for Android emulator startup flags; repeat once per argument, for example `-no-snapshot` |
61+
62+
SimDeck reserves Android emulator target and stream flags such as `-avd` and
63+
`-grpc` so browser streaming remains attached to the selected emulator.
64+
5665
## Input
5766

5867
| Command | Useful flags |

docs/guide/github-actions.md

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -188,21 +188,22 @@ Supported quality values include `tiny`, `low`, `economy`, `fast`, `smooth`, `ba
188188

189189
## Common inputs
190190

191-
| Input | Default | Purpose |
192-
| ------------------- | ----------------------------------- | ------------------------------------------- |
193-
| `bundle_id` | empty | Bundle ID to launch |
194-
| `package_name` | empty | Android package name to launch |
195-
| `build_workflow` | `build-ios-simulator.yml` | Workflow file that uploads the app artifact |
196-
| `artifact_prefix` | `ios-simulator-app` / `android-apk` | Artifact prefix |
197-
| `simdeck_version` | `latest` | npm version or dist-tag |
198-
| `stream_profile` | `tiny` | Default stream quality |
199-
| `simulator_name` | `iPhone 17 Pro` | Preferred simulator |
200-
| `avd_name` | `SimDeck_Pixel_CI` | Preferred Android emulator |
201-
| `keepalive_seconds` | `1800` | Session lifetime after launch |
202-
| `simulator_cache` | `true` | Restore and save simulator cache |
203-
| `proxy_links` | `true` | Post SimDeck CI proxy links |
204-
| `ci_proxy_url` | `https://ci.simdeck.sh` | Optional SimDeck CI proxy URL |
205-
| `session_password` | empty | Optional password for proxy-gated sessions |
191+
| Input | Default | Purpose |
192+
| ----------------------- | ----------------------------------- | ---------------------------------------------- |
193+
| `bundle_id` | empty | Bundle ID to launch |
194+
| `package_name` | empty | Android package name to launch |
195+
| `build_workflow` | `build-ios-simulator.yml` | Workflow file that uploads the app artifact |
196+
| `artifact_prefix` | `ios-simulator-app` / `android-apk` | Artifact prefix |
197+
| `simdeck_version` | `latest` | npm version or dist-tag |
198+
| `stream_profile` | `tiny` | Default stream quality |
199+
| `simulator_name` | `iPhone 17 Pro` | Preferred simulator |
200+
| `avd_name` | `SimDeck_Pixel_CI` | Preferred Android emulator |
201+
| `android_emulator_args` | empty | Extra emulator startup arguments, one per line |
202+
| `keepalive_seconds` | `1800` | Session lifetime after launch |
203+
| `simulator_cache` | `true` | Restore and save simulator cache |
204+
| `proxy_links` | `true` | Post SimDeck CI proxy links |
205+
| `ci_proxy_url` | `https://ci.simdeck.sh` | Optional SimDeck CI proxy URL |
206+
| `session_password` | empty | Optional password for proxy-gated sessions |
206207

207208
## Password-protected links
208209

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { afterEach, describe, expect, it, vi } from "vitest";
2+
3+
import { bootSimulator } from "./controls";
4+
5+
describe("controls", () => {
6+
afterEach(() => {
7+
vi.unstubAllGlobals();
8+
});
9+
10+
it("posts Android emulator startup args when booting", async () => {
11+
const fetchMock = vi.fn(async () => {
12+
return new Response(JSON.stringify({ ok: true, simulator: null }), {
13+
headers: { "content-type": "application/json" },
14+
status: 200,
15+
});
16+
});
17+
vi.stubGlobal("fetch", fetchMock);
18+
19+
await bootSimulator("android:Pixel_8_API_36", {
20+
androidEmulatorArgs: ["-no-snapshot"],
21+
});
22+
23+
expect(fetchMock).toHaveBeenCalledWith(
24+
"/api/simulators/android:Pixel_8_API_36/boot",
25+
expect.objectContaining({
26+
body: JSON.stringify({ androidEmulatorArgs: ["-no-snapshot"] }),
27+
method: "POST",
28+
}),
29+
);
30+
});
31+
});

packages/client/src/api/controls.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { accessTokenFromLocation, apiHeaders, apiRequest } from "./client";
22
import { apiUrl } from "./config";
33
import type {
44
ButtonPayload,
5+
BootPayload,
56
CrownPayload,
67
EdgeTouchPayload,
78
InstallUploadResponse,
@@ -37,7 +38,7 @@ export interface ScreenRecordingStartResponse {
3738
async function postSimulatorAction(
3839
udid: string,
3940
action: string,
40-
payload?: LaunchPayload | OpenUrlPayload,
41+
payload?: BootPayload | LaunchPayload | OpenUrlPayload,
4142
): Promise<SimulatorMetadata | null> {
4243
if (action === "launch" || action === "open-url") {
4344
const response = await apiRequest<{
@@ -63,8 +64,8 @@ async function postSimulatorAction(
6364
return "simulator" in response ? response.simulator : null;
6465
}
6566

66-
export function bootSimulator(udid: string) {
67-
return postSimulatorAction(udid, "boot");
67+
export function bootSimulator(udid: string, payload?: BootPayload) {
68+
return postSimulatorAction(udid, "boot", payload);
6869
}
6970

7071
export function shutdownSimulator(udid: string) {

packages/client/src/api/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,10 @@ export interface SimulatorResponse {
211211
simulator: SimulatorMetadata;
212212
}
213213

214+
export interface BootPayload {
215+
androidEmulatorArgs?: string[];
216+
}
217+
214218
export interface InstallUploadResponse {
215219
action: "install";
216220
fileName: string;

0 commit comments

Comments
 (0)