@roku-sdk/device-test-client 0.0.0-stage → 2.18.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/LICENSE +21 -0
- package/README.md +144 -2
- package/docs/baselines.md +221 -0
- package/docs/configuration.md +258 -0
- package/docs/dom-assertions.md +264 -0
- package/docs/errors.md +525 -0
- package/docs/examples/deep-link-cold-start.md +85 -0
- package/docs/examples/focus-navigation.md +81 -0
- package/docs/examples/log-driven-assertion.md +99 -0
- package/docs/examples/precondition-skips.md +150 -0
- package/docs/examples/screenshot-regression.md +103 -0
- package/docs/examples/smoke-test.md +69 -0
- package/docs/getting-started.md +195 -0
- package/docs/jest-integration.md +331 -0
- package/docs/log-assertions.md +314 -0
- package/docs/preconditions.md +317 -0
- package/docs/rkclient.md +655 -0
- package/docs/screenshot-assertions.md +316 -0
- package/internal/constants.ts +54 -0
- package/internal/deploy.ts +60 -0
- package/internal/paths.ts +15 -0
- package/package.json +80 -3
- package/src/assertions/assertions.ts +661 -0
- package/src/assertions/baseline-env-guard.ts +43 -0
- package/src/assertions/baselines.ts +178 -0
- package/src/assertions/screenshot-artifacts.ts +39 -0
- package/src/assertions/screenshot-diff.ts +447 -0
- package/src/client/build-cache.ts +292 -0
- package/src/client/client.ts +1656 -0
- package/src/client/deploy.ts +402 -0
- package/src/client/ecp-queue.ts +112 -0
- package/src/client/ecp-retry.ts +94 -0
- package/src/client/factory.ts +147 -0
- package/src/client/health.ts +214 -0
- package/src/client/parsers.ts +155 -0
- package/src/client/server-url.ts +137 -0
- package/src/constants.ts +471 -0
- package/src/device/device-class.ts +57 -0
- package/src/device/device-env-sync.ts +58 -0
- package/src/device/device-resolver-sync.ts +43 -0
- package/src/device/device-resolver.ts +141 -0
- package/src/device/lease-provisioning.ts +54 -0
- package/src/errors.ts +773 -0
- package/src/index.ts +186 -0
- package/src/internal/constants.ts +3 -0
- package/src/jest/artifacts.ts +87 -0
- package/src/jest/device-test-environment.ts +420 -0
- package/src/jest/failure-artifacts.ts +169 -0
- package/src/jest/global-setup.ts +133 -0
- package/src/jest/global-teardown.ts +132 -0
- package/src/jest/install-hooks.ts +488 -0
- package/src/jest/jest-setup.ts +53 -0
- package/src/jest/preconditions.ts +239 -0
- package/src/jest/telemetry.ts +331 -0
- package/src/logs/device-log-sink.ts +69 -0
- package/src/logs/log-buffer.ts +265 -0
- package/src/logs/log-session.ts +374 -0
- package/src/logs/log-stream-client.ts +296 -0
- package/src/paths.ts +65 -0
- package/src/types.ts +443 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
// Copyright (c) 2026 Roku, Inc.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
export interface DeviceInfo {
|
|
4
|
+
modelName: string;
|
|
5
|
+
modelNumber: string;
|
|
6
|
+
modelFamily: string;
|
|
7
|
+
deviceClass: string;
|
|
8
|
+
firmwareVersion: string;
|
|
9
|
+
videoMode: string;
|
|
10
|
+
locale: string;
|
|
11
|
+
country: string;
|
|
12
|
+
libraries: Record<string, string>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface AppUiNode {
|
|
16
|
+
/** XML tag name (e.g. `"Label"`, `"RowList"`). */
|
|
17
|
+
tag: string;
|
|
18
|
+
/**
|
|
19
|
+
* Raw ECP attributes verbatim (all values are strings; numeric
|
|
20
|
+
* comparisons require parsing). `bounds`/`focused` are also kept
|
|
21
|
+
* here as strings so attribute iteration is exhaustive.
|
|
22
|
+
*/
|
|
23
|
+
attrs: Record<string, string>;
|
|
24
|
+
bounds: { x: number; y: number; width: number; height: number };
|
|
25
|
+
focused: boolean;
|
|
26
|
+
children: AppUiNode[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface NodePredicate {
|
|
30
|
+
tag?: string;
|
|
31
|
+
attr?: Record<string, string>;
|
|
32
|
+
focused?: boolean;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LibraryRequirement {
|
|
36
|
+
/** Library identifier as reported by Roku ECP (`rida`, etc.). */
|
|
37
|
+
name: string;
|
|
38
|
+
/** Semver range (e.g. `">=2.0"`) — matched against the library's reported version. */
|
|
39
|
+
version: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Test-declared requirements for `requirePrecondition` / `itOnDevice` /
|
|
44
|
+
* `describeOnDevice`.
|
|
45
|
+
*
|
|
46
|
+
* Match strategy: `firmwareVersion` semver range (4-part Roku versions
|
|
47
|
+
* coerced to MAJOR.MINOR.PATCH); `modelFamily` exact;
|
|
48
|
+
* `deviceClass` exact against a derived perf tier (NOT `DeviceInfo.deviceClass`,
|
|
49
|
+
* which is form-factor); `videoMode` prefix; `locale`/`country` exact.
|
|
50
|
+
* `library` throws `PreconditionNotImplementedError` (no ECP query path).
|
|
51
|
+
*
|
|
52
|
+
* Evaluation is in key-insertion order, so the caller controls which
|
|
53
|
+
* field produces the error when multiple mismatch.
|
|
54
|
+
*/
|
|
55
|
+
export interface Precondition {
|
|
56
|
+
firmwareVersion?: string;
|
|
57
|
+
modelFamily?: string;
|
|
58
|
+
deviceClass?: string;
|
|
59
|
+
videoMode?: string;
|
|
60
|
+
locale?: string;
|
|
61
|
+
country?: string;
|
|
62
|
+
library?: LibraryRequirement;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MaskRegion {
|
|
66
|
+
x: number;
|
|
67
|
+
y: number;
|
|
68
|
+
width: number;
|
|
69
|
+
height: number;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface WaitOptions {
|
|
73
|
+
timeout: number;
|
|
74
|
+
interval?: number;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface CaptureStableOptions {
|
|
78
|
+
timeout: number;
|
|
79
|
+
interval?: number;
|
|
80
|
+
mask?: MaskRegion[];
|
|
81
|
+
/**
|
|
82
|
+
* Maximum number of differing pixels (outside `mask`) between two
|
|
83
|
+
* consecutive captures that still counts as "stable". Defaults to
|
|
84
|
+
* `0` — strict pixel equality, the historical contract.
|
|
85
|
+
*
|
|
86
|
+
* Use a small positive value when the firmware's anti-aliasing
|
|
87
|
+
* pipeline emits sub-pixel jitter on static text (~50–500 pixels
|
|
88
|
+
* per frame on FHD) even after the user-visible content has
|
|
89
|
+
* settled. Pair with `expectScreenshot(...).toMatchBaseline(..., {
|
|
90
|
+
* threshold })` whose `threshold` covers the same tolerance — the
|
|
91
|
+
* stability gate must not be stricter than the assertion gate,
|
|
92
|
+
* otherwise `captureStableScreenshot` can time out on a screen
|
|
93
|
+
* the baseline would have accepted.
|
|
94
|
+
*/
|
|
95
|
+
tolerance?: number;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface KeySequenceOptions {
|
|
99
|
+
keyDelay?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export interface WaitForDeviceOnlineOptions {
|
|
103
|
+
/** Total wait budget; defaults to `DEVICE_ONLINE_DEFAULT_TIMEOUT_MS` (180s). */
|
|
104
|
+
timeoutMs?: number;
|
|
105
|
+
/** Probe interval; defaults to `DEVICE_ONLINE_POLL_INTERVAL_MS` (2s). */
|
|
106
|
+
intervalMs?: number;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Options for `RkClient.waitForDeviceFullyReady`. The method gates on
|
|
111
|
+
* BOTH the ECP responder (port 8060, via `/query/device-info`) AND the
|
|
112
|
+
* sideload installer (port 80, via TCP connect). Each phase has an
|
|
113
|
+
* independent budget so a slow installer cannot starve the ECP probe
|
|
114
|
+
* (and vice versa); the overall wall time is bounded by their sum.
|
|
115
|
+
*
|
|
116
|
+
* Why two phases: the original `waitForDeviceOnline` only proves the
|
|
117
|
+
* device is responding on ECP. After a crash/reboot the installer (the
|
|
118
|
+
* port `rk` POSTs the sideload package to) lags ECP recovery by several
|
|
119
|
+
* seconds; retrying a deploy in that window surfaces the misleading
|
|
120
|
+
* "Device is not reachable on the network" diagnostic from
|
|
121
|
+
* `sideload-diagnostics`. Probing both before the next deploy attempt
|
|
122
|
+
* eliminates that whole failure class — see `installDeployGuard`.
|
|
123
|
+
*/
|
|
124
|
+
export interface WaitForDeviceFullyReadyOptions {
|
|
125
|
+
/**
|
|
126
|
+
* Budget for the ECP `/query/device-info` arm. Defaults to
|
|
127
|
+
* `DEVICE_ONLINE_DEFAULT_TIMEOUT_MS` (180s). Forwarded verbatim to
|
|
128
|
+
* the underlying `waitForDeviceOnline` call.
|
|
129
|
+
*/
|
|
130
|
+
ecpTimeoutMs?: number;
|
|
131
|
+
/**
|
|
132
|
+
* Probe interval for the ECP arm. Defaults to
|
|
133
|
+
* `DEVICE_ONLINE_POLL_INTERVAL_MS` (2s).
|
|
134
|
+
*/
|
|
135
|
+
ecpIntervalMs?: number;
|
|
136
|
+
/**
|
|
137
|
+
* Budget for the installer-port (TCP/80) arm, applied AFTER the ECP
|
|
138
|
+
* arm resolves. Defaults to `INSTALLER_PORT_PROBE_DEFAULT_TIMEOUT_MS`
|
|
139
|
+
* (60s).
|
|
140
|
+
*/
|
|
141
|
+
installerTimeoutMs?: number;
|
|
142
|
+
/**
|
|
143
|
+
* Probe interval for the installer-port arm. Defaults to
|
|
144
|
+
* `INSTALLER_PORT_PROBE_INTERVAL_MS` (1s).
|
|
145
|
+
*/
|
|
146
|
+
installerIntervalMs?: number;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type LogPosition = string;
|
|
150
|
+
|
|
151
|
+
export type LogLine = string;
|
|
152
|
+
|
|
153
|
+
export interface LogQueryOptions {
|
|
154
|
+
since: LogPosition;
|
|
155
|
+
timeout?: number;
|
|
156
|
+
window?: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface LaunchOptions {
|
|
160
|
+
/**
|
|
161
|
+
* ECP channel id to launch. Defaults to `"dev"` (the currently
|
|
162
|
+
* sideloaded development channel). Published channels use their
|
|
163
|
+
* numeric id (e.g. `"12345"`).
|
|
164
|
+
*/
|
|
165
|
+
channelId?: string;
|
|
166
|
+
/**
|
|
167
|
+
* Deep-link params serialized into the launch URL. Reliable on
|
|
168
|
+
* `launch()` (the runtime is already subscribed); cold sideloads
|
|
169
|
+
* must use `buildAndLaunchWithParams` instead.
|
|
170
|
+
*/
|
|
171
|
+
deepLink?: Record<string, string>;
|
|
172
|
+
/**
|
|
173
|
+
* Post-launch readiness budget; defaults to `READINESS_TIMEOUT_MS`.
|
|
174
|
+
* On timeout: `DeployFailedError`, device flagged unhealthy.
|
|
175
|
+
*/
|
|
176
|
+
readinessTimeout?: number;
|
|
177
|
+
/**
|
|
178
|
+
* Semantic readiness gate. See `BuildAndDeployOptions.readyNode`
|
|
179
|
+
* for picking-the-predicate guidance.
|
|
180
|
+
*/
|
|
181
|
+
readyNode?: NodePredicate;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export interface BuildAndDeployOptions {
|
|
185
|
+
/**
|
|
186
|
+
* Deep-link params for the post-sideload `/launch/dev?…` POST.
|
|
187
|
+
*
|
|
188
|
+
* Cold-mount race: the POST frequently lands before the runtime
|
|
189
|
+
* subscribes to `setLaunchParamsUpdateHandler`, and firmware
|
|
190
|
+
* silently drops it. Readiness passing is NOT evidence the params
|
|
191
|
+
* arrived. Use this only for apps that ignore params, read them
|
|
192
|
+
* synchronously on mount, or gate on a fixture-emitted log line.
|
|
193
|
+
* For cold sideloads that must land in a deep-linked state, use
|
|
194
|
+
* `buildAndLaunchWithParams` instead.
|
|
195
|
+
*/
|
|
196
|
+
deepLink?: Record<string, string>;
|
|
197
|
+
/** Readiness budget; defaults to `READINESS_TIMEOUT_MS` (10s). */
|
|
198
|
+
readinessTimeout?: number;
|
|
199
|
+
/** ECP channel id to launch after sideload; defaults to `"dev"`. */
|
|
200
|
+
channelId?: string;
|
|
201
|
+
/** Override device selector; defaults to active `.roku/` device. */
|
|
202
|
+
deviceSelector?: string;
|
|
203
|
+
/**
|
|
204
|
+
* Semantic readiness gate: a predicate matching a user-rendered
|
|
205
|
+
* element. Without it, readiness fires on the SceneGraph skeleton
|
|
206
|
+
* (mounted before user code), so `buildAndDeploy` can return
|
|
207
|
+
* "success" while `main()` is still compiling or crashing during
|
|
208
|
+
* mount (`EXIT_APP_ERROR` 0.3–1.0s after splash). Pick a stable
|
|
209
|
+
* Label with a known id+text; avoid wrapper tags like `Group`.
|
|
210
|
+
*
|
|
211
|
+
* Once matched, readiness still requires two consecutive passing
|
|
212
|
+
* polls — catches "mounted then immediately crashed" as
|
|
213
|
+
* `DeployFailedError({ reason: "crashed-after-ready" })`.
|
|
214
|
+
* Omitting falls back to `tree.children.length > 0`.
|
|
215
|
+
*/
|
|
216
|
+
readyNode?: NodePredicate;
|
|
217
|
+
/**
|
|
218
|
+
* Override esbuild's `drop: ["console", "debugger"]` pass:
|
|
219
|
+
* `true` always drops (perf profiling), `false` never drops (for
|
|
220
|
+
* fixtures that exercise console behaviour, e.g. polyfill
|
|
221
|
+
* verifiers), `undefined` falls back to the historical default
|
|
222
|
+
* (drop on `rk build`, keep on `rk dev`).
|
|
223
|
+
*/
|
|
224
|
+
dropConsole?: boolean;
|
|
225
|
+
/**
|
|
226
|
+
* Build with development artifacts — no minification, readable identifiers,
|
|
227
|
+
* source maps. Equivalent to `rk build --debug`. Useful for profiling sessions
|
|
228
|
+
* where signal/effect names and stack frames remain human-readable.
|
|
229
|
+
*/
|
|
230
|
+
dev?: boolean;
|
|
231
|
+
/**
|
|
232
|
+
* Enable SolidJS dev mode — imports the `solid-js/dev` build so signal
|
|
233
|
+
* and effect names remain readable in Perfetto tracepoints. Equivalent to
|
|
234
|
+
* `rk build --solid-dev-mode` or `buildVariables: { __SIGNAL__: true }` in
|
|
235
|
+
* `roku-config.ts`.
|
|
236
|
+
*/
|
|
237
|
+
solidDevMode?: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Build-time variable overrides merged on top of the app's `roku-config.ts`
|
|
240
|
+
* `buildVariables`. Pass `{ __SIGNAL__: true }` to enable SolidJS dev mode
|
|
241
|
+
* (readable signal/effect names in Perfetto tracepoints).
|
|
242
|
+
*/
|
|
243
|
+
buildVariables?: Record<string, boolean | string | number>;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Options for `buildAndLaunchWithParams`. Both log regexes are
|
|
248
|
+
* required: omitting either re-introduces the cold-mount race the
|
|
249
|
+
* primitive exists to avoid.
|
|
250
|
+
*/
|
|
251
|
+
export interface BuildAndLaunchWithParamsOptions {
|
|
252
|
+
/** Device selector for phase-1 `buildAndDeploy`. */
|
|
253
|
+
deviceSelector?: string;
|
|
254
|
+
/** ECP channel id for both phases; defaults to `"dev"`. */
|
|
255
|
+
channelId?: string;
|
|
256
|
+
/** Readiness timeout forwarded to BOTH phases. */
|
|
257
|
+
readinessTimeout?: number;
|
|
258
|
+
/** Phase-1 semantic readiness gate; see `BuildAndDeployOptions.readyNode`. */
|
|
259
|
+
readyNode?: NodePredicate;
|
|
260
|
+
/**
|
|
261
|
+
* Required. Log regex emitted once the runtime's launch-params
|
|
262
|
+
* subscription is registered (e.g. an effect reading
|
|
263
|
+
* `useLaunchConfig()` for the first time). Gating phase 2 on this
|
|
264
|
+
* prevents the params POST from racing handler registration.
|
|
265
|
+
*/
|
|
266
|
+
runtimeReadyLogPattern: RegExp;
|
|
267
|
+
/** "Runtime ready" gate timeout; defaults to 5s. */
|
|
268
|
+
runtimeReadyTimeout?: number;
|
|
269
|
+
/**
|
|
270
|
+
* Required. Log regex emitted when phase-2 params are observed by
|
|
271
|
+
* the reactive graph. Gating on this separates signal-delivery
|
|
272
|
+
* failures from renderer regressions in subsequent DOM timeouts.
|
|
273
|
+
*/
|
|
274
|
+
paramsAckLogPattern: RegExp;
|
|
275
|
+
/** "Params ack" gate timeout; defaults to 5s. */
|
|
276
|
+
paramsAckTimeout?: number;
|
|
277
|
+
/** Forwarded to phase-1; see `BuildAndDeployOptions.dropConsole`. */
|
|
278
|
+
dropConsole?: boolean;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Narrow projection of `AppUiNode` for system-dialog surfacing.
|
|
283
|
+
* `tag`/`title` are top-level for reporter convenience; `node` keeps
|
|
284
|
+
* the full underlying tree for deeper inspection.
|
|
285
|
+
*/
|
|
286
|
+
export interface SystemDialogNode {
|
|
287
|
+
/** XML tag that matched the `SYSTEM_DIALOG_TAGS` heuristic. */
|
|
288
|
+
tag: string;
|
|
289
|
+
/** `attrs.title` on the node, if present (common on StandardMessageDialog). */
|
|
290
|
+
title: string;
|
|
291
|
+
/** Full underlying AppUiNode, for reporters that want context. */
|
|
292
|
+
node: AppUiNode;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Max whole-pixel translation the comparator may apply to the capture
|
|
297
|
+
* before scoring it against the baseline. Both axes default to 0
|
|
298
|
+
* (exact positional match) and are capped at
|
|
299
|
+
* `ALIGN_TOLERANCE_MAX_PX` — needing more than a few pixels means the
|
|
300
|
+
* baseline content is genuinely different, not merely offset.
|
|
301
|
+
*
|
|
302
|
+
* Intended for firmware-drawn surfaces (system OSD dialogs) whose
|
|
303
|
+
* internal row heights round differently per device, re-centering the
|
|
304
|
+
* whole surface by 1-3px. App-rendered content is positionally
|
|
305
|
+
* deterministic and must not use this.
|
|
306
|
+
*/
|
|
307
|
+
export interface AlignTolerance {
|
|
308
|
+
x?: number;
|
|
309
|
+
y?: number;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
export interface ScreenshotMatchOptions {
|
|
313
|
+
/**
|
|
314
|
+
* Max fraction of unmasked pixels that may differ; defaults to
|
|
315
|
+
* 0.01 (1%). Per-pixel color sensitivity (YIQ 0.1) is internal
|
|
316
|
+
* and decoupled — widening this does not loosen per-pixel matching.
|
|
317
|
+
*/
|
|
318
|
+
threshold?: number;
|
|
319
|
+
mask?: MaskRegion[];
|
|
320
|
+
/**
|
|
321
|
+
* Allow the capture to be translated by up to this many pixels on
|
|
322
|
+
* each axis to absorb device-dependent re-centering. Only consulted
|
|
323
|
+
* after an untranslated comparison has already failed, so it can
|
|
324
|
+
* rescue a failing assertion but never relax a passing one.
|
|
325
|
+
*/
|
|
326
|
+
alignTolerance?: AlignTolerance;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
export interface TreeMatchOptions {
|
|
330
|
+
/** Attribute keys stripped recursively before serialization. */
|
|
331
|
+
exclude?: readonly string[];
|
|
332
|
+
/**
|
|
333
|
+
* Node tags serialized as leaves: the node itself is asserted, its
|
|
334
|
+
* children are not. Use for native nodes whose internal chrome the
|
|
335
|
+
* app does not own and firmware restructures between builds.
|
|
336
|
+
*/
|
|
337
|
+
excludeChildrenOf?: readonly string[];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* `/query/active-app` result. Every field stays faithful to the XML, so
|
|
342
|
+
* decide "is this the home screen?" with `isHomeScreenActiveApp` rather
|
|
343
|
+
* than comparing fields directly — firmware ships several distinct home
|
|
344
|
+
* surfaces and no single field identifies all of them.
|
|
345
|
+
*/
|
|
346
|
+
export interface ActiveAppInfo {
|
|
347
|
+
/**
|
|
348
|
+
* `"dev"` for sideloaded, channel id for published. Empty on the
|
|
349
|
+
* classic home screen, but newer firmware reports a real id for its
|
|
350
|
+
* home surfaces (`"562859"` for Roku Dynamic Menu).
|
|
351
|
+
*/
|
|
352
|
+
id: string;
|
|
353
|
+
/**
|
|
354
|
+
* User-facing title (text node of `<app>`). `"Roku"` on the classic
|
|
355
|
+
* home screen; other home surfaces use their own name, e.g.
|
|
356
|
+
* `"Roku Dynamic Menu"`.
|
|
357
|
+
*/
|
|
358
|
+
title: string;
|
|
359
|
+
/** ECP `type` attribute (`"appl"`, `"home"`, `"tvin"`, …); `""` when absent. */
|
|
360
|
+
type: string;
|
|
361
|
+
/**
|
|
362
|
+
* ECP `ui-location` attribute: `"home"` for the home experience, and
|
|
363
|
+
* the channel's own id while that channel is foregrounded. `""` when
|
|
364
|
+
* absent, as on firmware that predates the attribute.
|
|
365
|
+
*/
|
|
366
|
+
uiLocation: string;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** ECP request categories. Keys in `ECP_INTERVAL_MS` must match. */
|
|
370
|
+
export type EcpCategory = "query-appui" | "query-device-info" | "query-active-app" | "keypress" | "launch" | "capture";
|
|
371
|
+
|
|
372
|
+
export interface RkClientOptions {
|
|
373
|
+
/** Device IPv4. Required directly; `createDefaultClient()` resolves from env/`.roku/`. */
|
|
374
|
+
deviceIp?: string;
|
|
375
|
+
/** Default inter-key delay for `keysequence` (ms). */
|
|
376
|
+
keyDelay?: number;
|
|
377
|
+
/** Per-request ECP timeout (ms); capture uses its own hard-coded longer timeout. */
|
|
378
|
+
requestTimeoutMs?: number;
|
|
379
|
+
/** Override per-category minimum intervals (ms). For tests only. */
|
|
380
|
+
intervals?: Partial<Record<EcpCategory, number>>;
|
|
381
|
+
/**
|
|
382
|
+
* Override `rk server` base URL. Precedence:
|
|
383
|
+
* `opts.serverUrl` → `RK_SERVER_URL` → `DEFAULT_RK_SERVER_URL`.
|
|
384
|
+
*/
|
|
385
|
+
serverUrl?: string;
|
|
386
|
+
/** Test-only: inject a fake log session. Ignored by normal callers. */
|
|
387
|
+
logSession?: LogSessionLike;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/** Structural type for `LogSession`; kept here to avoid a types→class cycle. */
|
|
391
|
+
export interface LogSessionLike {
|
|
392
|
+
ensureStarted(): Promise<void>;
|
|
393
|
+
dispose(): void;
|
|
394
|
+
readonly buffer: LogBufferLike;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Structural type for `LogBuffer`'s public surface. */
|
|
398
|
+
export interface LogBufferLike {
|
|
399
|
+
append(line: LogLine): { seq: number; line: LogLine; ts: number };
|
|
400
|
+
currentPosition(): LogPosition;
|
|
401
|
+
linesSince(pos: LogPosition): LogLine[];
|
|
402
|
+
entriesSince(pos: LogPosition): Array<{ seq: number; line: LogLine; ts: number }>;
|
|
403
|
+
hasGapWithin(from: LogPosition, to?: LogPosition): { start: number; end: number } | null;
|
|
404
|
+
/**
|
|
405
|
+
* Return the tracked gap that contains `seq`, or `null`. Used by
|
|
406
|
+
* the per-match line-integrity check in `waitForLog`.
|
|
407
|
+
*/
|
|
408
|
+
findGapContaining(seq: number): { start: number; end: number } | null;
|
|
409
|
+
subscribe(cb: (e: { seq: number; line: LogLine; ts: number }) => void): () => void;
|
|
410
|
+
markGapStart(): void;
|
|
411
|
+
markGapEnd(): void;
|
|
412
|
+
/** Lifetime stream-health stats. */
|
|
413
|
+
getStats(): {
|
|
414
|
+
gapCount: number;
|
|
415
|
+
totalGapSeqSpan: number;
|
|
416
|
+
lastGapAt: number | null;
|
|
417
|
+
openGap: boolean;
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Stream-health snapshot from `getLogStreamHealth()`. `serverMetrics`
|
|
423
|
+
* is `null` on server fetch failure, `undefined` when the server has
|
|
424
|
+
* no matching session, otherwise the matched session metrics.
|
|
425
|
+
*/
|
|
426
|
+
export interface LogStreamHealth {
|
|
427
|
+
/** Closed gaps recorded on the client buffer since construction. */
|
|
428
|
+
gapCount: number;
|
|
429
|
+
/** Sum of `(end - start)` across closed client-side gaps (seq units). */
|
|
430
|
+
totalGapSeqSpan: number;
|
|
431
|
+
/** `Date.now()` of the most recent gap close, or `null` if none. */
|
|
432
|
+
lastGapAt: number | null;
|
|
433
|
+
/** Whether the client thinks a gap is currently open (reconnecting). */
|
|
434
|
+
openGap: boolean;
|
|
435
|
+
/** Server-side metrics; see class docstring for tri-state semantics. */
|
|
436
|
+
serverMetrics?: {
|
|
437
|
+
reconnectCount: number;
|
|
438
|
+
totalDowntime: number;
|
|
439
|
+
uptime: number;
|
|
440
|
+
connectedAt: string | null;
|
|
441
|
+
lastActivity: string | null;
|
|
442
|
+
} | null;
|
|
443
|
+
}
|