@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.
Files changed (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +144 -2
  3. package/docs/baselines.md +221 -0
  4. package/docs/configuration.md +258 -0
  5. package/docs/dom-assertions.md +264 -0
  6. package/docs/errors.md +525 -0
  7. package/docs/examples/deep-link-cold-start.md +85 -0
  8. package/docs/examples/focus-navigation.md +81 -0
  9. package/docs/examples/log-driven-assertion.md +99 -0
  10. package/docs/examples/precondition-skips.md +150 -0
  11. package/docs/examples/screenshot-regression.md +103 -0
  12. package/docs/examples/smoke-test.md +69 -0
  13. package/docs/getting-started.md +195 -0
  14. package/docs/jest-integration.md +331 -0
  15. package/docs/log-assertions.md +314 -0
  16. package/docs/preconditions.md +317 -0
  17. package/docs/rkclient.md +655 -0
  18. package/docs/screenshot-assertions.md +316 -0
  19. package/internal/constants.ts +54 -0
  20. package/internal/deploy.ts +60 -0
  21. package/internal/paths.ts +15 -0
  22. package/package.json +80 -3
  23. package/src/assertions/assertions.ts +661 -0
  24. package/src/assertions/baseline-env-guard.ts +43 -0
  25. package/src/assertions/baselines.ts +178 -0
  26. package/src/assertions/screenshot-artifacts.ts +39 -0
  27. package/src/assertions/screenshot-diff.ts +447 -0
  28. package/src/client/build-cache.ts +292 -0
  29. package/src/client/client.ts +1656 -0
  30. package/src/client/deploy.ts +402 -0
  31. package/src/client/ecp-queue.ts +112 -0
  32. package/src/client/ecp-retry.ts +94 -0
  33. package/src/client/factory.ts +147 -0
  34. package/src/client/health.ts +214 -0
  35. package/src/client/parsers.ts +155 -0
  36. package/src/client/server-url.ts +137 -0
  37. package/src/constants.ts +471 -0
  38. package/src/device/device-class.ts +57 -0
  39. package/src/device/device-env-sync.ts +58 -0
  40. package/src/device/device-resolver-sync.ts +43 -0
  41. package/src/device/device-resolver.ts +141 -0
  42. package/src/device/lease-provisioning.ts +54 -0
  43. package/src/errors.ts +773 -0
  44. package/src/index.ts +186 -0
  45. package/src/internal/constants.ts +3 -0
  46. package/src/jest/artifacts.ts +87 -0
  47. package/src/jest/device-test-environment.ts +420 -0
  48. package/src/jest/failure-artifacts.ts +169 -0
  49. package/src/jest/global-setup.ts +133 -0
  50. package/src/jest/global-teardown.ts +132 -0
  51. package/src/jest/install-hooks.ts +488 -0
  52. package/src/jest/jest-setup.ts +53 -0
  53. package/src/jest/preconditions.ts +239 -0
  54. package/src/jest/telemetry.ts +331 -0
  55. package/src/logs/device-log-sink.ts +69 -0
  56. package/src/logs/log-buffer.ts +265 -0
  57. package/src/logs/log-session.ts +374 -0
  58. package/src/logs/log-stream-client.ts +296 -0
  59. package/src/paths.ts +65 -0
  60. package/src/types.ts +443 -0
package/src/index.ts ADDED
@@ -0,0 +1,186 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * `@roku-sdk/device-test-client` — public surface.
5
+ *
6
+ * SemVer major 2.0.0 trimmed the barrel to the symbols real
7
+ * consumers actually import. Log primitives, failure-bundle writers,
8
+ * XML parsers, low-level baseline/screenshot-artifact setters, async
9
+ * device resolvers, and sync device-env helpers are no longer part
10
+ * of the documented API.
11
+ *
12
+ * **Internal symbols (not on the public surface):**
13
+ * the `package.json` `exports` map no longer carries a `./src/*`
14
+ * wildcard. Symbols deliberately kept off this barrel are reachable
15
+ * ONLY via the dedicated, documented internal subpaths:
16
+ *
17
+ * - `@roku-sdk/device-test-client/internal/deploy` — `resolveAppPath`
18
+ * for the integration-test deploy guard's directory-wipe path.
19
+ * - `@roku-sdk/device-test-client/internal/constants` — the small
20
+ * subset of `src/constants.ts` symbols (currently `ARTIFACT_FILE`)
21
+ * that `*.config.ts` files load at parse time. The public barrel
22
+ * is unreachable from CommonJS-mode config loaders because it
23
+ * transitively pulls in `@roku-sdk/tooling` ESM (`import.meta.url`);
24
+ * this subpath bypasses the offending chain.
25
+ * - `@roku-sdk/device-test-client/internal/paths` — load-safe
26
+ * `test-results/` path helpers used by multi-device test setup.
27
+ *
28
+ * If a future internal symbol needs the same escape hatch, prefer
29
+ * extending one of the existing `internal/<name>` subpaths (grouped
30
+ * by concern: deploy-side helpers under `deploy`, config-load
31
+ * constants under `constants`) rather than reintroducing the
32
+ * wildcard or minting a parallel subpath. The wildcard was tech
33
+ * debt that compounded — every `./src/<x>` deep import bound the
34
+ * (already-internal) module topology into the published API surface
35
+ * for consumers using `moduleResolution: "Node16"` or `"Bundler"`.
36
+ * Named subpaths are grep-able, audit-able, and break loudly at
37
+ * install time when a partner reaches for a symbol that has moved.
38
+ *
39
+ * Constants surface (M14): `index.ts` no longer re-exports every
40
+ * value in `constants.ts`. The curated set below covers the env
41
+ * vars, file/global keys, default budgets, and bundle-layout
42
+ * names that consumers legitimately read; pure-implementation
43
+ * magic numbers (ECP queue gaps, log-buffer caps, pixelmatch YIQ
44
+ * cutoff, error-formatter character caps, readiness-gate poll
45
+ * mechanics, SSE accumulator limits) are intentionally NOT
46
+ * re-exported.
47
+ *
48
+ * Errors and types (`errors.ts`, `types.ts`) re-export wholesale —
49
+ * the entire error catalogue must stay reachable for `instanceof`
50
+ * checks, and the type surface is read-only at runtime.
51
+ *
52
+ * The trimmed surface is snapshot-guarded by
53
+ * `__tests__/public-api-surface.test.ts`; any add/remove/typeof flip
54
+ * requires an explicit snapshot update + reviewer sign-off.
55
+ *
56
+ * See `README.md` (Versioning) for the public API contract and
57
+ * `docs/getting-started.md` for setup migration guidance.
58
+ */
59
+
60
+ export * from "./types";
61
+ export * from "./errors";
62
+
63
+ // Curated public constants. KEEP in this list any constant a real
64
+ // consumer needs to align to (env-var names, file/global keys,
65
+ // default budgets that overlap with public option types, server
66
+ // URL / port, bundle layout names CI pipelines parse). DO NOT add
67
+ // pure-implementation magic numbers (queue mechanics, formatter
68
+ // caps, parser limits) — those stay file-internal. If a partner
69
+ // truly needs one, promote it through this barrel via a snapshot
70
+ // update (the `./src/*` deep-import escape hatch was removed in
71
+ // to keep the published exports map auditable).
72
+ export {
73
+ // Env vars + file/global keys.
74
+ ARTIFACT_FILE,
75
+ DEVICE_ENV_GLOBAL_KEY,
76
+ ENV_VAR,
77
+ // Server URL / port.
78
+ DEFAULT_RK_SERVER_URL,
79
+ ROKU_LOG_PORT,
80
+ // Default budgets (overlap with `RkClientOptions` / `WaitOptions`
81
+ // / `BuildAndDeployOptions` / `CaptureStableOptions`).
82
+ BUILD_SSE_IDLE_TIMEOUT_DEFAULT_MS,
83
+ DEFAULT_KEY_DELAY,
84
+ DEFAULT_REQUEST_TIMEOUT_MS,
85
+ DEVICE_ONLINE_DEFAULT_TIMEOUT_MS,
86
+ DEVICE_ONLINE_POLL_INTERVAL_MS,
87
+ DEVICE_RECOVERY_DEFAULT_RETRIES,
88
+ INSTALLER_PORT_PROBE_DEFAULT_TIMEOUT_MS,
89
+ INSTALLER_PORT_PROBE_INTERVAL_MS,
90
+ READINESS_TIMEOUT_MS,
91
+ SCREENSHOT_STABLE_DEFAULT_INTERVAL_MS,
92
+ WAIT_FOR_LOG_DEFAULT_TIMEOUT_MS,
93
+ WAIT_FOR_NODE_DEFAULT_INTERVAL_MS,
94
+ // Public threshold + alignment cap (overlap with `expectScreenshot` opts).
95
+ ALIGN_TOLERANCE_MAX_PX,
96
+ DEFAULT_SCREENSHOT_THRESHOLD,
97
+ // Validation sets consumers can read.
98
+ SUPPORTED_VIDEO_MODES,
99
+ SYSTEM_DIALOG_TAGS,
100
+ // Bundle / artifact layout — CI pipelines key off these names.
101
+ BASELINE_RESOLUTION_DIR,
102
+ FAILURE_ARTIFACT_FILE,
103
+ FAILURE_RECENT_LOG_LINES,
104
+ FAILURES_DIR,
105
+ SCREENSHOT_DIFF_DIR
106
+ } from "./constants";
107
+
108
+ export { RkClient } from "./client/client";
109
+ export { createDefaultClient } from "./client/factory";
110
+ // Exported so tests asserting the home-vs-channel contract check it the
111
+ // same way crash-detection does, instead of pinning field values that
112
+ // differ per firmware.
113
+ export { isHomeScreenActiveApp } from "./client/parsers";
114
+ export {
115
+ getFatalReason,
116
+ getUnhealthyReason,
117
+ isDeviceFatal,
118
+ isDeviceHealthy,
119
+ markDeviceFatal,
120
+ markDeviceUnhealthy,
121
+ resetDeviceHealth
122
+ } from "./client/health";
123
+ export { createGlobalSetup } from "./jest/global-setup";
124
+ export type { GlobalSetupOptions } from "./jest/global-setup";
125
+ export { createGlobalTeardown } from "./jest/global-teardown";
126
+ export type { CreateGlobalTeardownOptions } from "./jest/global-teardown";
127
+ export { installDeviceTestHooks } from "./jest/install-hooks";
128
+ export type { DeviceRecoveryOptions, InstallDeviceTestHooksOptions } from "./jest/install-hooks";
129
+ export { describeOnDevice, itOnDevice, requirePrecondition } from "./jest/preconditions";
130
+ export type { OnDeviceDescribeOptions, OnDeviceTestOptions } from "./jest/preconditions";
131
+ // Device-lease telemetry helpers for the parallel-device rollout.
132
+ // Curated for partners writing their own `globalSetup` /
133
+ // `globalTeardown`. Each export earns its keep:
134
+ //
135
+ // - `resolveTelemetry`: bootstrap entry point — turns a
136
+ // known device id + env into the fully-resolved triple.
137
+ // - `emitDeviceLeaseLog`: the side-effecting "emit to stdout"
138
+ // that the dashboard log-aggregator consumes.
139
+ // - `writeJUnitTelemetryPropertiesFile`: the side-effecting
140
+ // "write the CJS file `jest-junit` requires".
141
+ // - `formatDeviceLeaseLog`: pure formatter, exposed so partners
142
+ // piping through structured loggers (e.g. pino) can re-emit
143
+ // the canonical line without duplicating the wire contract.
144
+ // - `buildAcquireEvent` / `buildReleaseEvent` /
145
+ // `buildJUnitTelemetryProperties`: pure builders so partners
146
+ // can construct events without memorising the field shape.
147
+ // - `DEVICE_LEASE_LOG_PREFIX`: surfaced for log-parser code.
148
+ // - `DEFAULT_PROVIDER_KIND` / `DEFAULT_SHARD_NAME`: the
149
+ // documented fallback values when env vars are unset; surfaced
150
+ // so partner tests can assert against them by name.
151
+ //
152
+ // Anything NOT in this list (e.g. internal type guards) is file-
153
+ // internal. The `./src/*` deep-import escape hatch was removed in
154
+ // promote the symbol through this barrel (with a
155
+ // public-API snapshot update) if a partner needs it.
156
+ export {
157
+ buildAcquireEvent,
158
+ buildJUnitTelemetryProperties,
159
+ buildReleaseEvent,
160
+ DEFAULT_PROVIDER_KIND,
161
+ DEFAULT_SHARD_NAME,
162
+ DEVICE_LEASE_LOG_PREFIX,
163
+ emitDeviceLeaseLog,
164
+ formatDeviceLeaseLog,
165
+ resolveTelemetry,
166
+ writeJUnitTelemetryPropertiesFile
167
+ } from "./jest/telemetry";
168
+ export type {
169
+ DeviceLeaseAcquireEvent,
170
+ DeviceLeaseEvent,
171
+ DeviceLeaseReleaseEvent,
172
+ DeviceLeaseTelemetry,
173
+ JUnitTelemetryProperties
174
+ } from "./jest/telemetry";
175
+ export { getDeviceClass } from "./device/device-class";
176
+ export type { DeviceClass } from "./device/device-class";
177
+ export {
178
+ expectFocused,
179
+ expectScreenshot,
180
+ expectTree,
181
+ findAllNodes,
182
+ findFocusedNode,
183
+ findNode,
184
+ matchesPredicate
185
+ } from "./assertions/assertions";
186
+ export type { ScreenshotAssertion, TreeAssertion } from "./assertions/assertions";
@@ -0,0 +1,3 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+ export * from "../constants";
@@ -0,0 +1,87 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Artifact helpers shared between `globalSetup` and install hooks.
5
+ * Extracted so orchestration files focus on lifecycle wiring;
6
+ * `jest-setup.ts` re-exports every symbol for API compatibility.
7
+ */
8
+
9
+ import { mkdirSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
10
+ import { join } from "node:path";
11
+
12
+ import { ARTIFACT_FILE, SUPPORTED_VIDEO_MODES } from "../constants";
13
+ import type { LeaseProvisioning } from "../device/lease-provisioning";
14
+ import type { DeviceInfo } from "../types";
15
+
16
+ /**
17
+ * Removes everything inside `testResultsDir` except a `.gitkeep` marker, then
18
+ * ensures the directory itself exists. Safe to call when the directory is
19
+ * missing.
20
+ */
21
+ export function cleanArtifacts(testResultsDir: string): void {
22
+ mkdirSync(testResultsDir, { recursive: true });
23
+ for (const entry of readdirSync(testResultsDir, { withFileTypes: true })) {
24
+ if (entry.name === ".gitkeep") continue;
25
+ rmSync(join(testResultsDir, entry.name), { recursive: true, force: true });
26
+ }
27
+ }
28
+
29
+ export function assertSupportedVideoMode(videoMode: string): void {
30
+ if (!SUPPORTED_VIDEO_MODES.has(videoMode)) {
31
+ throw new Error(
32
+ `Unrecognized device videoMode "${videoMode}". ` + `Supported: ${[...SUPPORTED_VIDEO_MODES].join(", ")}.`
33
+ );
34
+ }
35
+ }
36
+
37
+ /**
38
+ * On-disk shape of `device-env.json`: the ECP `DeviceInfo` plus whatever
39
+ * provisioning provenance the lease carried.
40
+ *
41
+ * A superset rather than extra `DeviceInfo` fields, because `DeviceInfo` is
42
+ * the return type of `queryDeviceInfo()` and must keep meaning "what ECP
43
+ * reported". Readers of the artifact stay valid either way: the snapshot
44
+ * validator in `client/factory.ts` checks required fields only, and
45
+ * `readCachedDeviceInfoSync` passes extras through untouched.
46
+ */
47
+ export type DeviceEnvSnapshot = DeviceInfo & LeaseProvisioning;
48
+
49
+ /**
50
+ * Atomic write of `device-env.json` (write-temp + rename) so
51
+ * concurrent readers never see a torn JSON payload.
52
+ *
53
+ * `provisioning` keys with an `undefined` value are dropped by
54
+ * `JSON.stringify`, so an unknown field is absent from the artifact rather
55
+ * than present-and-null — see `lease-provisioning.ts` for why absence is
56
+ * the correct encoding of "unknown".
57
+ */
58
+ export function writeDeviceEnvSnapshot(
59
+ testResultsDir: string,
60
+ info: DeviceInfo,
61
+ provisioning: LeaseProvisioning = {}
62
+ ): void {
63
+ const file = join(testResultsDir, ARTIFACT_FILE.DEVICE_ENV);
64
+ const snapshot: DeviceEnvSnapshot = { ...info, ...provisioning };
65
+ // Per-pid+timestamp tmp name so concurrent globalSetups don't
66
+ // clobber each other's in-flight tmp files.
67
+ const tmp = `${file}.${process.pid}.${Date.now()}.tmp`;
68
+ writeFileSync(tmp, `${JSON.stringify(snapshot, null, 2)}\n`, "utf8");
69
+ renameSync(tmp, file);
70
+ }
71
+
72
+ export function logDeviceDiagnostic(info: DeviceInfo, provisioning: LeaseProvisioning = {}): void {
73
+ // The rootfs is appended to the SAME line rather than logged separately
74
+ // so a `grep "\[device-test-client\] device:"` over a CI log still yields
75
+ // one self-contained record per job.
76
+ const rootfs = provisioning.rootfsArtifactoryPath;
77
+ const rootfsSuffix = rootfs === undefined ? "" : `, rootfs ${rootfs}`;
78
+ console.log(
79
+ `[device-test-client] device: ${info.modelName} (${info.modelNumber}), ` +
80
+ `firmware ${info.firmwareVersion}, videoMode ${info.videoMode}, locale ${info.locale}${rootfsSuffix}`
81
+ );
82
+ }
83
+
84
+ /** Trim a single trailing `/` — shared helper for `rk server` URL composition. */
85
+ export function trimTrailingSlash(s: string): string {
86
+ return s.endsWith("/") ? s.slice(0, -1) : s;
87
+ }