@run-cloud/sdk 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -15,14 +15,70 @@ const cloud = new Client();
15
15
 
16
16
  const ios = await cloud.ios.create({ displayName: "CI smoke" });
17
17
  await cloud.ios.openUrl(ios.id, "https://example.com");
18
- await new Promise((resolve) => setTimeout(resolve, 3000));
18
+ await cloud.ios.tap(ios.id, { x: 0.5, y: 0.75 });
19
+ await cloud.ios.typeText(ios.id, "Hello from iOS!");
19
20
  await writeFile("ios-screenshot.png", await cloud.ios.screenshot(ios.id));
20
21
  await cloud.ios.delete(ios.id);
21
22
 
22
23
  const android = await cloud.android.create({ displayName: "Android smoke" });
24
+ await cloud.android.swipe(
25
+ android.id,
26
+ { x: 0.5, y: 0.8 },
27
+ { x: 0.5, y: 0.2 },
28
+ { durationMs: 250 },
29
+ );
30
+ await cloud.android.pressKey(android.id, "enter");
31
+ await writeFile("android-screenshot.png", await cloud.android.screenshot(android.id));
23
32
  await cloud.android.delete(android.id);
24
33
  ```
25
34
 
35
+ ## Simulator interactions
36
+
37
+ Both `cloud.ios` and `cloud.android` expose the same interaction methods:
38
+ `tap`, `swipe`, `gesture`, `typeText`, `pressKey`, `pressButton`, `rotate`,
39
+ `reload`, `scroll`, `toggleSoftwareKeyboard`, `simulateMemoryWarning`,
40
+ `rotateDigitalCrown`, and `setRenderDebug`. Coordinates use inclusive normalized
41
+ display space: `(0, 0)` is the top-left corner and `(1, 1)` is the bottom-right.
42
+ `typeText` uses a US keyboard and accepts tabs, line feeds, and printable ASCII.
43
+ On iOS, `home`, `appSwitcher`, and `recents` are momentary navigation actions;
44
+ physical buttons honor `durationMs`.
45
+ Digital Crown input is not supported by current iOS Simulator or Android
46
+ Emulator sessions; the typed method returns an actionable API error.
47
+ Android Emulator sessions also report `capsLock`, `numLock`, and `scrollLock`
48
+ as unsupported semantic keys.
49
+
50
+ Every method returns a correlated, action-narrowed acknowledgement. Use
51
+ `interact` when the action is assembled dynamically:
52
+
53
+ ```ts
54
+ const result = await cloud.simulators.interact(
55
+ ios.id,
56
+ { action: "tap", x: 0.5, y: 0.5 },
57
+ { platform: "ios", requestId: "open-settings", timeoutMs: 15_000 },
58
+ );
59
+ console.log(result.requestId, result.action, result.status, result.result.pointCount);
60
+ ```
61
+
62
+ Pass an `AbortSignal` to stop waiting without releasing the session. The SDK
63
+ sends the requested timeout to run.cloud and leaves a short response margin so
64
+ the server's completion or timeout acknowledgement can arrive.
65
+
66
+ ```ts
67
+ const controller = new AbortController();
68
+ process.once("SIGINT", () => controller.abort());
69
+ await cloud.android.typeText(android.id, "cancel-safe", {
70
+ signal: controller.signal,
71
+ timeoutMs: 15_000,
72
+ });
73
+ ```
74
+
75
+ API failures throw `RunCloudError`. In addition to `status` and `detail`,
76
+ interaction failures preserve `code`, `retryable`, `details`, `requestId`,
77
+ `action`, and the accepted/completed timing fields when the API provides them.
78
+ Screenshot options also accept `requestId`; screenshot timeout, transport, and
79
+ invalid-image failures retain the same correlation fields with stable
80
+ `screenshot_*` codes.
81
+
26
82
  ## Linux sandboxes
27
83
 
28
84
  Firecracker microVMs. The default reservation is 0.125 vCPU / 128 MiB — CPU
@@ -162,7 +218,8 @@ For multipart archives, `cloud.assets.uploadBatch(parts, { uploadBatchId,
162
218
  uploadRunId })` numbers the parts, records per-part transfer telemetry, and
163
219
  limits direct storage transfers to six at a time.
164
220
 
165
- `cloud.ios.screenshot(sessionId)` returns PNG bytes as a `Uint8Array`. Save the
221
+ `cloud.ios.screenshot(sessionId)` and `cloud.android.screenshot(sessionId)`
222
+ return PNG bytes as a `Uint8Array`. Save the
166
223
  bytes directly with `node:fs/promises.writeFile`, attach them to test artifacts,
167
224
  or pass them to an image-processing library.
168
225
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,205 @@
1
1
  import WebSocket from "ws";
2
2
  export type SimulatorPlatform = "ios" | "android";
3
+ export interface SimulatorPoint {
4
+ /** Normalized display-space coordinate, inclusive from 0 (left) to 1 (right). */
5
+ x: number;
6
+ /** Normalized display-space coordinate, inclusive from 0 (top) to 1 (bottom). */
7
+ y: number;
8
+ }
9
+ export type SimulatorOrientation = "portrait" | "portrait_upside_down" | "landscape_left" | "landscape_right";
10
+ export declare const SIMULATOR_KEYS: readonly ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "enter", "escape", "backspace", "tab", "space", "minus", "equal", "bracketLeft", "bracketRight", "backslash", "semicolon", "quote", "backquote", "comma", "period", "slash", "capsLock", "f1", "f2", "f3", "f4", "f5", "f6", "f7", "f8", "f9", "f10", "f11", "f12", "printScreen", "scrollLock", "pause", "insert", "home", "pageUp", "delete", "end", "pageDown", "arrowRight", "arrowLeft", "arrowDown", "arrowUp", "numLock", "numpadDivide", "numpadMultiply", "numpadSubtract", "numpadAdd", "numpadEnter", "numpad0", "numpad1", "numpad2", "numpad3", "numpad4", "numpad5", "numpad6", "numpad7", "numpad8", "numpad9", "numpadDecimal"];
11
+ export type SimulatorKey = (typeof SIMULATOR_KEYS)[number];
12
+ export type SimulatorKeyModifier = "shift" | "control" | "alt" | "meta";
13
+ export declare const SIMULATOR_BUTTONS: readonly ["home", "back", "appSwitcher", "recents", "power", "volumeUp", "volumeDown", "menu", "sideButton", "actionButton", "digitalCrown"];
14
+ export type SimulatorButton = (typeof SIMULATOR_BUTTONS)[number];
15
+ export declare const SIMULATOR_RENDER_DEBUG_OPTIONS: readonly ["colorBlendedLayers", "colorCopiedImages", "colorMisalignedImages", "colorOffscreenRendered", "slowAnimations"];
16
+ export type SimulatorRenderDebugOption = (typeof SIMULATOR_RENDER_DEBUG_OPTIONS)[number];
17
+ export interface SimulatorGestureStep {
18
+ phase: "begin" | "move" | "end";
19
+ points: [SimulatorPoint] | [SimulatorPoint, SimulatorPoint];
20
+ /** Milliseconds to wait before the next step. The final end step must omit this or use 0. */
21
+ delayMs?: number;
22
+ }
23
+ export type SimulatorInteraction = {
24
+ action: "tap";
25
+ x: number;
26
+ y: number;
27
+ } | {
28
+ action: "swipe";
29
+ from: SimulatorPoint;
30
+ to: SimulatorPoint;
31
+ durationMs?: number;
32
+ } | {
33
+ action: "gesture";
34
+ steps: SimulatorGestureStep[];
35
+ } | {
36
+ action: "typeText";
37
+ text: string;
38
+ } | {
39
+ action: "pressKey";
40
+ key: SimulatorKey;
41
+ modifiers?: SimulatorKeyModifier[];
42
+ durationMs?: number;
43
+ } | {
44
+ action: "pressButton";
45
+ button: SimulatorButton;
46
+ durationMs?: number;
47
+ } | {
48
+ action: "rotate";
49
+ orientation: SimulatorOrientation;
50
+ } | {
51
+ action: "reload";
52
+ } | {
53
+ action: "scroll";
54
+ deltaX: number;
55
+ deltaY: number;
56
+ x?: number;
57
+ y?: number;
58
+ } | {
59
+ action: "toggleSoftwareKeyboard";
60
+ } | {
61
+ action: "simulateMemoryWarning";
62
+ } | {
63
+ action: "rotateDigitalCrown";
64
+ delta: number;
65
+ } | {
66
+ action: "setRenderDebug";
67
+ option: SimulatorRenderDebugOption;
68
+ enabled: boolean;
69
+ };
70
+ export type SimulatorInteractionAction = SimulatorInteraction["action"];
71
+ export type SimulatorOperation = SimulatorInteractionAction | "screenshot";
72
+ export declare const SIMULATOR_INTERACTION_ERROR_CODES: readonly ["invalid_interaction", "active_session_not_found", "simulator_capacity_unavailable", "unsupported_action", "duplicate_request", "interaction_cancelled", "interaction_timeout", "interaction_transport_error", "interaction_invalid_response", "interaction_failed"];
73
+ export type SimulatorInteractionErrorCode = (typeof SIMULATOR_INTERACTION_ERROR_CODES)[number];
74
+ export declare const SIMULATOR_SCREENSHOT_ERROR_CODES: readonly ["invalid_screenshot_request", "active_session_not_found", "simulator_capacity_unavailable", "screenshot_cancelled", "screenshot_timeout", "screenshot_transport_error", "screenshot_invalid_response", "screenshot_failed"];
75
+ export type SimulatorScreenshotErrorCode = (typeof SIMULATOR_SCREENSHOT_ERROR_CODES)[number];
76
+ export interface SimulatorInteractionOptions {
77
+ /** Correlates this request with logs and the returned acknowledgement. */
78
+ requestId?: string;
79
+ /** Cancels the request without cancelling or releasing the simulator session. */
80
+ signal?: AbortSignal;
81
+ /** Maximum time to wait for the simulator acknowledgement. Defaults to 15 seconds. */
82
+ timeoutMs?: number;
83
+ }
84
+ export interface SimulatorInteractionResultMap {
85
+ tap: {
86
+ pointCount: 1;
87
+ };
88
+ swipe: {
89
+ pointCount: 1;
90
+ durationMs: number;
91
+ };
92
+ gesture: {
93
+ pointCount: number;
94
+ stepCount: number;
95
+ };
96
+ typeText: {
97
+ characterCount: number;
98
+ };
99
+ pressKey: {
100
+ key: SimulatorKey;
101
+ modifierCount: number;
102
+ };
103
+ pressButton: {
104
+ button: SimulatorButton;
105
+ };
106
+ rotate: {
107
+ orientation: SimulatorOrientation;
108
+ };
109
+ reload: undefined;
110
+ scroll: {
111
+ deltaX: number;
112
+ deltaY: number;
113
+ };
114
+ toggleSoftwareKeyboard: undefined;
115
+ simulateMemoryWarning: undefined;
116
+ rotateDigitalCrown: {
117
+ delta: number;
118
+ };
119
+ setRenderDebug: {
120
+ option: SimulatorRenderDebugOption;
121
+ enabled: boolean;
122
+ };
123
+ }
124
+ type SimulatorInteractionResultField<Action extends SimulatorInteractionAction> = Action extends SimulatorInteractionAction ? SimulatorInteractionResultMap[Action] extends undefined ? {
125
+ result?: undefined;
126
+ } : {
127
+ result: SimulatorInteractionResultMap[Action];
128
+ } : never;
129
+ export type SimulatorInteractionResult<Action extends SimulatorInteractionAction = SimulatorInteractionAction> = {
130
+ ok: true;
131
+ requestId: string;
132
+ sessionId: string;
133
+ platform: SimulatorPlatform;
134
+ action: Action;
135
+ status: "completed";
136
+ acceptedAt: string;
137
+ completedAt: string;
138
+ durationMs: number;
139
+ } & SimulatorInteractionResultField<Action>;
140
+ export interface SimulatorInteractionFailure {
141
+ ok: false;
142
+ requestId?: string;
143
+ sessionId?: string;
144
+ platform?: SimulatorPlatform;
145
+ action?: SimulatorInteractionAction;
146
+ status?: "failed" | "cancelled" | "timed_out";
147
+ acceptedAt?: string;
148
+ completedAt?: string;
149
+ durationMs?: number;
150
+ error: {
151
+ code: SimulatorInteractionErrorCode;
152
+ message: string;
153
+ retryable: boolean;
154
+ details?: unknown;
155
+ };
156
+ }
157
+ export type SimulatorInteractionResultFor<Interaction extends SimulatorInteraction> = SimulatorInteractionResult<Interaction["action"]>;
158
+ export interface SimulatorScreenshotOptions {
159
+ /** Correlates this capture with API errors and logs. */
160
+ requestId?: string;
161
+ signal?: AbortSignal;
162
+ /** Maximum time to wait for image capture. Defaults to 15 seconds. */
163
+ timeoutMs?: number;
164
+ }
165
+ export interface SimulatorSwipeOptions extends SimulatorInteractionOptions {
166
+ durationMs?: number;
167
+ }
168
+ export interface SimulatorPressKeyOptions extends SimulatorInteractionOptions {
169
+ modifiers?: SimulatorKeyModifier[];
170
+ durationMs?: number;
171
+ }
172
+ export interface SimulatorPressButtonOptions extends SimulatorInteractionOptions {
173
+ durationMs?: number;
174
+ }
175
+ export interface AggregateSimulatorInteractionOptions extends SimulatorInteractionOptions {
176
+ platform?: SimulatorPlatform;
177
+ }
178
+ export interface AggregateSimulatorSwipeOptions extends SimulatorSwipeOptions {
179
+ platform?: SimulatorPlatform;
180
+ }
181
+ export interface AggregateSimulatorPressKeyOptions extends SimulatorPressKeyOptions {
182
+ platform?: SimulatorPlatform;
183
+ }
184
+ export interface AggregateSimulatorPressButtonOptions extends SimulatorPressButtonOptions {
185
+ platform?: SimulatorPlatform;
186
+ }
187
+ export interface AggregateSimulatorScreenshotOptions extends SimulatorScreenshotOptions {
188
+ platform?: SimulatorPlatform;
189
+ }
190
+ export declare const DEFAULT_SIMULATOR_INTERACTION_TIMEOUT_MS = 15000;
191
+ export declare function validateSimulatorInteraction(interaction: SimulatorInteraction, timeoutMs?: number): void;
192
+ interface RequestAbortOptions {
193
+ signal?: AbortSignal;
194
+ timeoutMs?: number;
195
+ timeoutMessage?: string;
196
+ timeoutCode?: string;
197
+ transportErrorCode?: string;
198
+ transportErrorMessage?: string;
199
+ headers?: Record<string, string>;
200
+ timeoutContext?: RunCloudErrorOptions;
201
+ errorContext?: RunCloudErrorOptions;
202
+ }
3
203
  export interface ClientOptions {
4
204
  apiKey?: string;
5
205
  apiUrl?: string;
@@ -81,6 +281,7 @@ export interface CreateSessionOptions {
81
281
  /** @deprecated Renamed to `tags`; removed in the next release. */
82
282
  labels?: Record<string, string>;
83
283
  installAssets?: string[];
284
+ /** Idle timeout duration. Pass null to disable inactivity auto-close. */
84
285
  inactivityTimeout?: string | null;
85
286
  hardTimeout?: string | null;
86
287
  codec?: "auto" | "mjpeg" | "webrtc";
@@ -226,10 +427,34 @@ export interface Snapshot {
226
427
  state?: "pending" | "ready" | "failed" | string;
227
428
  [key: string]: unknown;
228
429
  }
430
+ interface RunCloudErrorOptions {
431
+ code?: string;
432
+ retryable?: boolean;
433
+ details?: unknown;
434
+ requestId?: string;
435
+ sessionId?: string;
436
+ platform?: SimulatorPlatform;
437
+ action?: SimulatorOperation;
438
+ interactionStatus?: SimulatorInteractionFailure["status"];
439
+ acceptedAt?: string;
440
+ completedAt?: string;
441
+ durationMs?: number;
442
+ }
229
443
  export declare class RunCloudError extends Error {
230
444
  readonly status: number;
231
445
  readonly detail: string;
232
- constructor(status: number, detail: string);
446
+ readonly code?: string;
447
+ readonly retryable?: boolean;
448
+ readonly details?: unknown;
449
+ readonly requestId?: string;
450
+ readonly sessionId?: string;
451
+ readonly platform?: SimulatorPlatform;
452
+ readonly action?: SimulatorOperation;
453
+ readonly interactionStatus?: SimulatorInteractionFailure["status"];
454
+ readonly acceptedAt?: string;
455
+ readonly completedAt?: string;
456
+ readonly durationMs?: number;
457
+ constructor(status: number, detail: string, options?: RunCloudErrorOptions);
233
458
  }
234
459
  export declare class Client {
235
460
  readonly ios: IosSimulatorClient;
@@ -247,9 +472,9 @@ export declare class Client {
247
472
  orgId?: string;
248
473
  }): Promise<UsageReport>;
249
474
  account(): Promise<Account>;
250
- request<T>(method: string, path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
475
+ request<T>(method: string, path: string, body?: unknown, headers?: Record<string, string>, abortOptions?: RequestAbortOptions): Promise<T>;
251
476
  uploadForm<T>(path: string, form: FormData): Promise<T>;
252
- requestBytes(path: string): Promise<Uint8Array>;
477
+ requestBytes(path: string, abortOptions?: RequestAbortOptions): Promise<Uint8Array>;
253
478
  requestTextStream(path: string, signal?: AbortSignal): AsyncGenerator<string>;
254
479
  putSigned(url: string, body: Blob, headers: Record<string, string>): Promise<void>;
255
480
  execStream(path: string, request: Record<string, unknown>, callbacks?: Pick<ExecOptions, "onStdout" | "onStderr" | "signal">): Promise<ExecResult>;
@@ -270,6 +495,26 @@ export declare class SimulatorClient {
270
495
  get(id: string): Promise<SimulatorSession>;
271
496
  delete(id: string): Promise<SimulatorSession>;
272
497
  openUrl(id: string, url: string): Promise<Record<string, unknown>>;
498
+ interact<Interaction extends SimulatorInteraction>(id: string, interaction: Interaction, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResultFor<Interaction>>;
499
+ tap(id: string, point: SimulatorPoint, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"tap">>;
500
+ swipe(id: string, from: SimulatorPoint, to: SimulatorPoint, options?: SimulatorSwipeOptions): Promise<SimulatorInteractionResult<"swipe">>;
501
+ gesture(id: string, steps: SimulatorGestureStep[], options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"gesture">>;
502
+ typeText(id: string, text: string, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"typeText">>;
503
+ pressKey(id: string, key: SimulatorKey, options?: SimulatorPressKeyOptions): Promise<SimulatorInteractionResult<"pressKey">>;
504
+ pressButton(id: string, button: SimulatorButton, options?: SimulatorPressButtonOptions): Promise<SimulatorInteractionResult<"pressButton">>;
505
+ rotate(id: string, orientation: SimulatorOrientation, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"rotate">>;
506
+ reload(id: string, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"reload">>;
507
+ scroll(id: string, input: {
508
+ deltaX: number;
509
+ deltaY: number;
510
+ x?: number;
511
+ y?: number;
512
+ }, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"scroll">>;
513
+ toggleSoftwareKeyboard(id: string, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"toggleSoftwareKeyboard">>;
514
+ simulateMemoryWarning(id: string, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"simulateMemoryWarning">>;
515
+ rotateDigitalCrown(id: string, delta: number, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"rotateDigitalCrown">>;
516
+ setRenderDebug(id: string, option: SimulatorRenderDebugOption, enabled: boolean, options?: SimulatorInteractionOptions): Promise<SimulatorInteractionResult<"setRenderDebug">>;
517
+ screenshot(id: string, options?: SimulatorScreenshotOptions): Promise<Uint8Array>;
273
518
  logs(id: string, options?: {
274
519
  tail?: number;
275
520
  }): Promise<SimulatorLogSnapshot>;
@@ -279,7 +524,6 @@ export declare class SimulatorClient {
279
524
  }
280
525
  export declare class IosSimulatorClient extends SimulatorClient {
281
526
  constructor(client: Client);
282
- screenshot(id: string): Promise<Uint8Array>;
283
527
  uploadVideo(id: string, video: Blob, options?: {
284
528
  filename?: string;
285
529
  name?: string;
@@ -312,6 +556,26 @@ export declare class SimulatorsClient {
312
556
  openUrl(id: string, url: string, options?: {
313
557
  platform?: SimulatorPlatform;
314
558
  }): Promise<Record<string, unknown>>;
559
+ interact<Interaction extends SimulatorInteraction>(id: string, interaction: Interaction, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResultFor<Interaction>>;
560
+ tap(id: string, point: SimulatorPoint, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"tap">>;
561
+ swipe(id: string, from: SimulatorPoint, to: SimulatorPoint, options?: AggregateSimulatorSwipeOptions): Promise<SimulatorInteractionResult<"swipe">>;
562
+ gesture(id: string, steps: SimulatorGestureStep[], options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"gesture">>;
563
+ typeText(id: string, text: string, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"typeText">>;
564
+ pressKey(id: string, key: SimulatorKey, options?: AggregateSimulatorPressKeyOptions): Promise<SimulatorInteractionResult<"pressKey">>;
565
+ pressButton(id: string, button: SimulatorButton, options?: AggregateSimulatorPressButtonOptions): Promise<SimulatorInteractionResult<"pressButton">>;
566
+ rotate(id: string, orientation: SimulatorOrientation, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"rotate">>;
567
+ reload(id: string, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"reload">>;
568
+ scroll(id: string, input: {
569
+ deltaX: number;
570
+ deltaY: number;
571
+ x?: number;
572
+ y?: number;
573
+ }, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"scroll">>;
574
+ toggleSoftwareKeyboard(id: string, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"toggleSoftwareKeyboard">>;
575
+ simulateMemoryWarning(id: string, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"simulateMemoryWarning">>;
576
+ rotateDigitalCrown(id: string, delta: number, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"rotateDigitalCrown">>;
577
+ setRenderDebug(id: string, option: SimulatorRenderDebugOption, enabled: boolean, options?: AggregateSimulatorInteractionOptions): Promise<SimulatorInteractionResult<"setRenderDebug">>;
578
+ screenshot(id: string, options?: AggregateSimulatorScreenshotOptions): Promise<Uint8Array>;
315
579
  }
316
580
  export declare class SandboxClient {
317
581
  private readonly client;
@@ -415,3 +679,4 @@ export declare class AssetsClient {
415
679
  deleted: boolean;
416
680
  }>;
417
681
  }
682
+ export {};