Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ simdeck --device <other-udid> describe --format agent --max-depth 2
simdeck list
simdeck use <udid>
simdeck boot <udid>
simdeck boot android:<avd-name> --android-emulator-arg=-no-snapshot
simdeck shutdown
simdeck erase
simdeck install /path/to/App.app
Expand Down
16 changes: 15 additions & 1 deletion actions/run-android-comment-session/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ inputs:
description: Android SDK build-tools version used to inspect APK metadata.
required: false
default: "36.0.0"
android_emulator_args:
description: Extra Android emulator startup arguments, one argument per line.
required: false
default: ""
public_health_check:
description: Verify the public Cloudflare Tunnel health endpoint before continuing.
required: false
Expand Down Expand Up @@ -123,6 +127,7 @@ runs:
INPUT_ANDROID_TARGET_VALUE: ${{ inputs.android_target }}
INPUT_ANDROID_ARCH_VALUE: ${{ inputs.android_arch }}
INPUT_ANDROID_BUILD_TOOLS_VALUE: ${{ inputs.android_build_tools }}
INPUT_ANDROID_EMULATOR_ARGS_VALUE: ${{ inputs.android_emulator_args }}
INPUT_PUBLIC_HEALTH_CHECK_VALUE: ${{ inputs.public_health_check }}
INPUT_CI_PROXY_URL_VALUE: ${{ inputs.ci_proxy_url }}
INPUT_PROXY_LINKS_VALUE: ${{ inputs.proxy_links }}
Expand Down Expand Up @@ -163,6 +168,7 @@ runs:
write_env "SIMDECK_ANDROID_TARGET" "${INPUT_ANDROID_TARGET_VALUE}"
write_env "SIMDECK_ANDROID_ARCH" "${INPUT_ANDROID_ARCH_VALUE}"
write_env "SIMDECK_ANDROID_BUILD_TOOLS" "${INPUT_ANDROID_BUILD_TOOLS_VALUE}"
write_env "SIMDECK_ANDROID_EMULATOR_ARGS" "${INPUT_ANDROID_EMULATOR_ARGS_VALUE}"
write_env "SIMDECK_CI_PROXY_URL" "${INPUT_CI_PROXY_URL_VALUE}"
write_env "SIMDECK_PROXY_LINKS" "${INPUT_PROXY_LINKS_VALUE}"
write_env "SIMDECK_SESSION_PASSWORD" "${INPUT_SESSION_PASSWORD_VALUE}"
Expand Down Expand Up @@ -656,7 +662,15 @@ runs:
echo "ANDROID_UDID=${udid}" >> "${GITHUB_ENV}"
echo "udid=${udid}" >> "${GITHUB_OUTPUT}"
echo "Booting ${udid}"
simdeck --server-url "http://127.0.0.1:${SIMDECK_PORT}" boot "${udid}"
boot_args=()
if [[ -n "${SIMDECK_ANDROID_EMULATOR_ARGS:-}" ]]; then
while IFS= read -r arg; do
if [[ -n "${arg//[[:space:]]/}" ]]; then
boot_args+=(--android-emulator-arg="${arg}")
fi
done <<< "${SIMDECK_ANDROID_EMULATOR_ARGS}"
fi
simdeck --server-url "http://127.0.0.1:${SIMDECK_PORT}" boot "${udid}" "${boot_args[@]}"
date +%s > /tmp/sim-boot-end

- name: Update status comment with booted emulator URL
Expand Down
14 changes: 13 additions & 1 deletion docs/api/rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,17 @@ Device IDs come from `/api/simulators`. Android IDs use the `android:` prefix.
Booted devices are listed first. Paired iPhone and Apple Watch entries include
`pairedWatchUDID` or `pairedPhoneUDID` when CoreSimulator reports a pairing.

Android emulator boot accepts optional startup arguments:

```json
{
"androidEmulatorArgs": ["-no-snapshot"]
}
```

SimDeck appends its own selected AVD and gRPC port flags around those arguments;
`-avd`, `@AVD`, and `-grpc` are reserved.

Create requests use identifiers from `/api/simulators/create-options`. New
devices are booted before the response is returned. If an iOS simulator is
created with `pairedWatch`, the watch is created, paired, and booted too.
Expand Down Expand Up @@ -97,7 +108,8 @@ Android:
"platform": "android",
"name": "Pixel_8_API_36",
"deviceTypeIdentifier": "pixel_8",
"runtimeIdentifier": "system-images;android-36;google_apis;arm64-v8a"
"runtimeIdentifier": "system-images;android-36;google_apis;arm64-v8a",
"androidEmulatorArgs": ["-no-snapshot"]
}
```

Expand Down
5 changes: 5 additions & 0 deletions docs/cli/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ simdeck list
simdeck list --format json
simdeck use <udid>
simdeck boot <udid>
simdeck boot android:<avd-name> --android-emulator-arg=-no-snapshot
simdeck shutdown
simdeck erase
```
Expand All @@ -68,6 +69,10 @@ inventory, including paths and display metadata.
directory. After that, most device commands can omit `<udid>`; explicit UDIDs
still override the default.

For Android emulator startup flags, repeat `--android-emulator-arg=<arg>` on
`simdeck boot`. SimDeck still owns the AVD selector and gRPC port used for the
browser stream.

## Apps and URLs

```sh
Expand Down
9 changes: 9 additions & 0 deletions docs/cli/flags.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ Alias: `snapshot`.
| `--point <x>,<y>` | Describe the element at a screen point |
| `--direct` | Skip service and use native accessibility directly |

## Device lifecycle

| Command | Useful flags |
| ------- | ----------------------------------------------------------------------------------------------------------------------- |
| `boot` | `--android-emulator-arg=<arg>` for Android emulator startup flags; repeat once per argument, for example `-no-snapshot` |

SimDeck reserves Android emulator target and stream flags such as `-avd` and
`-grpc` so browser streaming remains attached to the selected emulator.

## Input

| Command | Useful flags |
Expand Down
31 changes: 16 additions & 15 deletions docs/guide/github-actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,21 +188,22 @@ Supported quality values include `tiny`, `low`, `economy`, `fast`, `smooth`, `ba

## Common inputs

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

## Password-protected links

Expand Down
31 changes: 31 additions & 0 deletions packages/client/src/api/controls.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { afterEach, describe, expect, it, vi } from "vitest";

import { bootSimulator } from "./controls";

describe("controls", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

it("posts Android emulator startup args when booting", async () => {
const fetchMock = vi.fn(async () => {
return new Response(JSON.stringify({ ok: true, simulator: null }), {
headers: { "content-type": "application/json" },
status: 200,
});
});
vi.stubGlobal("fetch", fetchMock);

await bootSimulator("android:Pixel_8_API_36", {
androidEmulatorArgs: ["-no-snapshot"],
});

expect(fetchMock).toHaveBeenCalledWith(
"/api/simulators/android:Pixel_8_API_36/boot",
expect.objectContaining({
body: JSON.stringify({ androidEmulatorArgs: ["-no-snapshot"] }),
method: "POST",
}),
);
});
});
7 changes: 4 additions & 3 deletions packages/client/src/api/controls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { accessTokenFromLocation, apiHeaders, apiRequest } from "./client";
import { apiUrl } from "./config";
import type {
ButtonPayload,
BootPayload,
CrownPayload,
EdgeTouchPayload,
InstallUploadResponse,
Expand Down Expand Up @@ -37,7 +38,7 @@ export interface ScreenRecordingStartResponse {
async function postSimulatorAction(
udid: string,
action: string,
payload?: LaunchPayload | OpenUrlPayload,
payload?: BootPayload | LaunchPayload | OpenUrlPayload,
): Promise<SimulatorMetadata | null> {
if (action === "launch" || action === "open-url") {
const response = await apiRequest<{
Expand All @@ -63,8 +64,8 @@ async function postSimulatorAction(
return "simulator" in response ? response.simulator : null;
}

export function bootSimulator(udid: string) {
return postSimulatorAction(udid, "boot");
export function bootSimulator(udid: string, payload?: BootPayload) {
return postSimulatorAction(udid, "boot", payload);
}

export function shutdownSimulator(udid: string) {
Expand Down
4 changes: 4 additions & 0 deletions packages/client/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ export interface SimulatorResponse {
simulator: SimulatorMetadata;
}

export interface BootPayload {
androidEmulatorArgs?: string[];
}

export interface InstallUploadResponse {
action: "install";
fileName: string;
Expand Down
Loading
Loading