@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/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,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
|
+
}
|