@run-cloud/sdk 0.12.1 → 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;
@@ -227,10 +427,34 @@ export interface Snapshot {
227
427
  state?: "pending" | "ready" | "failed" | string;
228
428
  [key: string]: unknown;
229
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
+ }
230
443
  export declare class RunCloudError extends Error {
231
444
  readonly status: number;
232
445
  readonly detail: string;
233
- 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);
234
458
  }
235
459
  export declare class Client {
236
460
  readonly ios: IosSimulatorClient;
@@ -248,9 +472,9 @@ export declare class Client {
248
472
  orgId?: string;
249
473
  }): Promise<UsageReport>;
250
474
  account(): Promise<Account>;
251
- 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>;
252
476
  uploadForm<T>(path: string, form: FormData): Promise<T>;
253
- requestBytes(path: string): Promise<Uint8Array>;
477
+ requestBytes(path: string, abortOptions?: RequestAbortOptions): Promise<Uint8Array>;
254
478
  requestTextStream(path: string, signal?: AbortSignal): AsyncGenerator<string>;
255
479
  putSigned(url: string, body: Blob, headers: Record<string, string>): Promise<void>;
256
480
  execStream(path: string, request: Record<string, unknown>, callbacks?: Pick<ExecOptions, "onStdout" | "onStderr" | "signal">): Promise<ExecResult>;
@@ -271,6 +495,26 @@ export declare class SimulatorClient {
271
495
  get(id: string): Promise<SimulatorSession>;
272
496
  delete(id: string): Promise<SimulatorSession>;
273
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>;
274
518
  logs(id: string, options?: {
275
519
  tail?: number;
276
520
  }): Promise<SimulatorLogSnapshot>;
@@ -280,7 +524,6 @@ export declare class SimulatorClient {
280
524
  }
281
525
  export declare class IosSimulatorClient extends SimulatorClient {
282
526
  constructor(client: Client);
283
- screenshot(id: string): Promise<Uint8Array>;
284
527
  uploadVideo(id: string, video: Blob, options?: {
285
528
  filename?: string;
286
529
  name?: string;
@@ -313,6 +556,26 @@ export declare class SimulatorsClient {
313
556
  openUrl(id: string, url: string, options?: {
314
557
  platform?: SimulatorPlatform;
315
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>;
316
579
  }
317
580
  export declare class SandboxClient {
318
581
  private readonly client;
@@ -416,3 +679,4 @@ export declare class AssetsClient {
416
679
  deleted: boolean;
417
680
  }>;
418
681
  }
682
+ export {};