@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
@@ -0,0 +1,316 @@
1
+ # Screenshot assertions
2
+
3
+ Screenshot assertions commit a rendered frame to disk as the ground
4
+ truth for a UI state and verify future runs stay within a configurable
5
+ mismatch budget. They share the baseline layout, resolution routing,
6
+ and `UPDATE_BASELINES=1` workflow with
7
+ [DOM assertions](dom-assertions.md), but the comparison engine is
8
+ [pixelmatch](https://github.com/mapbox/pixelmatch) and failures write
9
+ an annotated diff PNG to `test-results/diffs/`.
10
+
11
+ - [Core types](#core-types)
12
+ - [Stable capture](#stable-capture)
13
+ - `[expectScreenshot().toMatchBaseline(...)](#expectscreenshottomatchbaseline)`
14
+ - [Thresholds — aggregate vs per-pixel](#thresholds--aggregate-vs-per-pixel)
15
+ - [Masks](#masks)
16
+ - [Alignment tolerance](#alignment-tolerance)
17
+ - [Diff PNGs](#diff-pngs)
18
+ - [Diff-root override](#diff-root-override)
19
+ - `[UPDATE_BASELINES=1` refresh](#update_baselines1-refresh)
20
+ - [Errors thrown](#errors-thrown)
21
+
22
+ ## Core types
23
+
24
+ ```ts
25
+ interface ScreenshotMatchOptions {
26
+ /** Mismatch budget as a fraction of UNMASKED pixels. Default 0.01 (1%). */
27
+ threshold?: number;
28
+ /** Regions zeroed out in BOTH images before comparison. */
29
+ mask?: MaskRegion[];
30
+ /** Whole-pixel slack for device-dependent re-centering. Default none. */
31
+ alignTolerance?: AlignTolerance;
32
+ }
33
+
34
+ interface MaskRegion {
35
+ x: number;
36
+ y: number;
37
+ width: number;
38
+ height: number;
39
+ }
40
+
41
+ interface AlignTolerance {
42
+ /** Max horizontal translation, 0..ALIGN_TOLERANCE_MAX_PX. Default 0. */
43
+ x?: number;
44
+ /** Max vertical translation, 0..ALIGN_TOLERANCE_MAX_PX. Default 0. */
45
+ y?: number;
46
+ }
47
+
48
+ interface CaptureStableOptions {
49
+ /** Overall wait budget (ms). */
50
+ timeout: number;
51
+ /** Minimum gap between captures (ms). Default 500. */
52
+ interval?: number;
53
+ /** Regions ignored when deciding "two frames are identical". */
54
+ mask?: MaskRegion[];
55
+ }
56
+ ```
57
+
58
+ ## Stable capture
59
+
60
+ Raw `client.captureScreenshot()` returns whatever is on screen at that
61
+ instant — typically a mid-animation frame. For baseline comparison you
62
+ want a **settled** frame: that's `captureStableScreenshot`.
63
+
64
+ ```ts
65
+ captureStableScreenshot(opts: CaptureStableOptions): Promise<Buffer>;
66
+ ```
67
+
68
+ Polls until two successive captures are pixel-identical outside `mask`
69
+ regions and returns the newer PNG. `assertAppAlive` runs before each
70
+ capture so a crash during polling surfaces as `AppCrashedError`
71
+ instead of a timeout.
72
+
73
+ **Example.**
74
+
75
+ ```ts
76
+ const png = await client.captureStableScreenshot({
77
+ timeout: 5_000,
78
+ mask: [{ x: 0, y: 0, width: 1920, height: 80 }] // hide the clock/status bar
79
+ });
80
+ ```
81
+
82
+ Throws `ScreenshotNotStableError` with `iterations`, `lastDiffCount`,
83
+ `firstDiffPoint`, `maskedRegions`, and `lastPng` on timeout.
84
+
85
+ ## `expectScreenshot().toMatchBaseline()`
86
+
87
+ ```ts
88
+ function expectScreenshot(png: Buffer): ScreenshotAssertion;
89
+
90
+ interface ScreenshotAssertion {
91
+ toMatchBaseline(baselinePath: string, opts?: ScreenshotMatchOptions): Promise<void>;
92
+ }
93
+ ```
94
+
95
+ Resolves `baselinePath` against the active baseline root + resolution
96
+ subdir (`hd/` or `fhd/`), reads the stored PNG, applies masks, runs
97
+ pixelmatch, then either:
98
+
99
+ - **equal within threshold** → assertion passes;
100
+ - **missing baseline + `UPDATE_BASELINES=1`** → atomically writes the
101
+ actual PNG at the baseline path and passes;
102
+ - **missing baseline, normal run** → throws
103
+ `ScreenshotBaselineMissingError`;
104
+ - **dimensions differ** → throws `ScreenshotDimensionsMismatchError`
105
+ (no diff PNG — pixelmatch cannot compare at different sizes);
106
+ - **mismatch above threshold** → writes a diff PNG and throws
107
+ `ScreenshotMismatchError`.
108
+
109
+ **Minimal example.**
110
+
111
+ ```ts
112
+ import { expectScreenshot } from "@roku-sdk/device-test-client";
113
+
114
+ const png = await client.captureStableScreenshot({ timeout: 5_000 });
115
+ await expectScreenshot(png).toMatchBaseline("home/initial.png");
116
+ ```
117
+
118
+ **Realistic example — mask, explicit threshold.**
119
+
120
+ ```ts
121
+ const png = await client.captureStableScreenshot({
122
+ timeout: 5_000,
123
+ mask: [{ x: 0, y: 0, width: 1920, height: 80 }]
124
+ });
125
+
126
+ await expectScreenshot(png).toMatchBaseline("home/initial.png", {
127
+ threshold: 0.005, // 0.5% of unmasked pixels may differ
128
+ mask: [{ x: 0, y: 0, width: 1920, height: 80 }]
129
+ });
130
+ ```
131
+
132
+ Note that `mask` is applied in **both** the capture step and the
133
+ assertion. The capture mask prevents animating content from ever
134
+ being considered "stable"; the assertion mask makes sure the same
135
+ region is ignored during comparison.
136
+
137
+ ## Thresholds — aggregate vs per-pixel
138
+
139
+ The SDK decouples two thresholds on purpose:
140
+
141
+ - **Aggregate ratio** (`opts.threshold`, default `0.01`) — the
142
+ fraction of unmasked pixels that may differ for the assertion to
143
+ pass.
144
+ - **Per-pixel YIQ color distance** — fixed internal constant
145
+ (`PIXELMATCH_COLOR_THRESHOLD = 0.1`), not exposed. Tuning this
146
+ would quietly shift what counts as a "different" pixel, which is
147
+ not a knob most tests actually want.
148
+
149
+ So `{ threshold: 0.5 }` means "up to 50% of pixels may differ before
150
+ I fail" — it does **not** mean "also loosen per-pixel sensitivity."
151
+
152
+ ## Masks
153
+
154
+ Masks are validated and clamped against the image canvas:
155
+
156
+ - Zero-width / zero-height → `ScreenshotMaskError`.
157
+ - Fully out-of-bounds → `ScreenshotMaskError`.
158
+ - Partially out-of-bounds → silently clamped to canvas.
159
+
160
+ The SDK zeroes masked pixels in **both** images before invoking
161
+ pixelmatch and excludes them from the ratio denominator, so a large
162
+ mask does not inflate the effective threshold.
163
+
164
+ **Example — masking the player clock.**
165
+
166
+ ```ts
167
+ await expectScreenshot(png).toMatchBaseline("player/playing.png", {
168
+ mask: [
169
+ { x: 1600, y: 1000, width: 300, height: 60 }, // time counter
170
+ { x: 0, y: 1040, width: 1920, height: 40 } // progress bar
171
+ ]
172
+ });
173
+ ```
174
+
175
+ ## Alignment tolerance
176
+
177
+ Some surfaces are drawn by firmware rather than by your app — the
178
+ system OSD raised by `StandardKeyboardDialog` and
179
+ `StandardPinPadDialog`, for example. Their internal row heights round
180
+ differently per device and firmware, so the whole surface re-centers by
181
+ 1-3px between otherwise-identical runs. Every separator line and text
182
+ row then lands a pixel or two off, which is easily enough to blow a 1%
183
+ budget even though nothing about the app changed.
184
+
185
+ `alignTolerance` lets the comparator translate the capture by a few
186
+ whole pixels before scoring it:
187
+
188
+ ```ts
189
+ await expectScreenshot(png).toMatchBaseline("dialogs/keyboard-open.png", {
190
+ threshold: 0.01,
191
+ mask: KEYBOARD_MASK,
192
+ alignTolerance: { y: 3 } // absorb vertical re-centering
193
+ });
194
+ ```
195
+
196
+ Three properties make this safe to reach for:
197
+
198
+ - **It can only rescue a failing assertion, never relax a passing
199
+ one.** The untranslated comparison runs first; the search happens only
200
+ if that already exceeded `threshold`. A test that passes today keeps
201
+ passing with byte-identical semantics, and the happy path does no
202
+ extra work.
203
+ - **`threshold` still applies in full** at the winning offset. The
204
+ tolerance buys positional slack, not a bigger mismatch budget, so a
205
+ missing row or a wrong color still fails.
206
+ - **The window is capped** at `ALIGN_TOLERANCE_MAX_PX` (4) per axis.
207
+ Needing more than that means the content genuinely differs and the
208
+ baseline should be re-captured, not slid into place.
209
+
210
+ Only the overlapping region is compared: at `dy=2` the top or bottom
211
+ two rows have no counterpart and are dropped from both the mismatch
212
+ count and the denominator.
213
+
214
+ Use this only for firmware-drawn chrome. App-rendered content is
215
+ positionally deterministic, and reaching for `alignTolerance` there
216
+ hides real layout regressions.
217
+
218
+ When the assertion fails even after aligning, the error names the best
219
+ offset it found and the untranslated ratio, which distinguishes "the
220
+ surface drifted further than the tolerance" from "the content is
221
+ genuinely different":
222
+
223
+ ```
224
+ ... (unmasked ratio 0.00412 > threshold 0.001). Best alignment dx=0, dy=2
225
+ (untranslated ratio was 0.01027). Diff: .../keyboard-open.diff.png.
226
+ ```
227
+
228
+ A malformed tolerance (non-integer, negative, or over the cap) throws
229
+ `ScreenshotAlignToleranceError`. Validation runs up front, so a typo
230
+ surfaces on a passing run too rather than lying dormant until the day
231
+ the assertion drifts.
232
+
233
+ ## Diff PNGs
234
+
235
+ On mismatch, the SDK renders a side-by-side diff PNG under
236
+ `getScreenshotDiffRoot()` (default `<cwd>/test-results/diffs/`). The
237
+ filename flattens the baseline path:
238
+
239
+ - `home/initial.png` → `home--initial.diff.png`
240
+ - `grid/after-select.png` → `grid--after-select.diff.png`
241
+
242
+ This keeps every failure's artifact in a single flat directory, easy
243
+ for CI reporters to upload. The `ScreenshotMismatchError` exposes the
244
+ full path in its `diffPath` field.
245
+
246
+ Diff PNGs are always full canvas size with baseline-aligned
247
+ coordinates, so a pixel in the diff names the same position as that
248
+ pixel in the baseline. When an `alignTolerance` search won, the rows or
249
+ columns that had no counterpart to compare are left transparent.
250
+
251
+ ## Diff-root override
252
+
253
+ The diff-root is configured through `installDeviceTestHooks`:
254
+
255
+ ```ts
256
+ import {
257
+ createDefaultClient,
258
+ installDeviceTestHooks
259
+ } from "@roku-sdk/device-test-client";
260
+ import { resolve } from "node:path";
261
+
262
+ const client = createDefaultClient();
263
+
264
+ installDeviceTestHooks({
265
+ client,
266
+ diffRoot: resolve(__dirname, "../../test-results/diffs")
267
+ });
268
+ ```
269
+
270
+ **Internal / deep-import.** The underlying
271
+ `setScreenshotDiffRoot(absPath)` / `getScreenshotDiffRoot()`
272
+ functions are no longer re-exported from the barrel in v2.0.0 — the
273
+ public path is `installDeviceTestHooks({ diffRoot })`. If a bespoke
274
+ runner needs to set the diff root without using
275
+ `installDeviceTestHooks`, the functions are reachable via
276
+ `@roku-sdk/device-test-client/src/assertions/screenshot-artifacts`
277
+ (deep import, not SemVer-covered).
278
+
279
+ ## `UPDATE_BASELINES=1` refresh
280
+
281
+ Re-run the suite with the env var set to regenerate baselines after a
282
+ legitimate UI change:
283
+
284
+ ```bash
285
+ UPDATE_BASELINES=1 pnpm jest path/to/file.test.ts
286
+ ```
287
+
288
+ - Missing baseline → created from the actual PNG.
289
+ - Mismatched baseline → overwritten with the actual PNG.
290
+ **No diff PNG is written in the update path.**
291
+ - Dimensions mismatch → overwritten with the actual PNG.
292
+ - CI guard still applies — `CI=true` blocks writes.
293
+
294
+ ## Errors thrown
295
+
296
+ - `ScreenshotBaselineMissingError` — no baseline + no
297
+ `UPDATE_BASELINES=1`. Fields: `baselinePath`, `resolution`.
298
+ - `ScreenshotDimensionsMismatchError` — baseline and actual disagree
299
+ on size. Fields: `baselinePath`, `resolution`, `baseline.w/h`,
300
+ `actual.w/h`.
301
+ - `ScreenshotMismatchError` — pixel diff over threshold. Fields:
302
+ `baselinePath`, `resolution`, `actual.w/h`, `mismatchedPixels`,
303
+ `totalPixels`, `ratio`, `threshold`, `diffPath`, and `alignment`
304
+ (`{ dx, dy }`, present only when an `alignTolerance` search improved on
305
+ the untranslated comparison).
306
+ - `ScreenshotNotStableError` — `captureStableScreenshot` timed out.
307
+ Fields: `iterations`, `lastDiffCount`, `firstDiffPoint`,
308
+ `maskedRegions`, `lastPng`.
309
+ - `ScreenshotMaskError` — invalid `MaskRegion`. Fields: `mask`,
310
+ `reason`.
311
+ - `ScreenshotAlignToleranceError` — invalid `alignTolerance` axis.
312
+ Fields: `axis`, `value`, `max`, `reason`.
313
+ - `AppCrashedError` — crash probe during stable-capture polling.
314
+
315
+ Full field lists and recovery advice in
316
+ `[errors.md](errors.md)`.
@@ -0,0 +1,54 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /**
5
+ * `@roku-sdk/device-test-client/internal/constants` — config-load-safe
6
+ * entry point for the small subset of `src/constants.ts` symbols that
7
+ * Jest configs (and other CommonJS-mode loaders) legitimately need at
8
+ * **parse time**.
9
+ *
10
+ * Why this exists, and why the public barrel is not enough:
11
+ *
12
+ * `src/index.ts` re-exports `RkClient` from `src/client/client.ts`,
13
+ * which imports `@roku-sdk/tooling/src/device-test-helpers.ts`. The
14
+ * `@roku-sdk/tooling` package declares `"type": "module"` and uses
15
+ * `import.meta.url`. Loaders that parse `*.config.ts` in CommonJS
16
+ * mode (ts-node default, esbuild-register, the loader Jest invokes
17
+ * from `jest-config/readConfigFileAndSetRootDir`) blow up on the
18
+ * first hop with `ERR_REQUIRE_ESM` — *before* any test, hook, or
19
+ * transform runs. The stack trace points at the loader, not at the
20
+ * import that pulled the ESM file in, so the failure is opaque to
21
+ * the reader of `jest.config.ts`.
22
+ *
23
+ * `src/constants.ts` itself has only `type` imports (all erased by
24
+ * TypeScript) and value-level constants, so a file that imports
25
+ * exclusively from it is parse-safe under CJS. This subpath is the
26
+ * one sanctioned "deep import past the barrel" surface for that
27
+ * purpose.
28
+ *
29
+ * Why a re-export shim instead of a direct entry in `exports`:
30
+ * `device-tests/tsconfig.json` uses `moduleResolution: "Node"`
31
+ * (classic), which falls back to a filesystem walk and does NOT honor
32
+ * the package `exports` map. A physical file at this path resolves
33
+ * under every resolution mode; the `exports` map entry covers the
34
+ * publish-time / `Node16` / `Bundler` case symmetrically. The same
35
+ * pattern is used by `./internal/deploy`.
36
+ *
37
+ * SemVer / stability contract:
38
+ * Symbols here are reachable, typed, and stable across patch releases
39
+ * — but explicitly NOT covered by SemVer (`/internal/` in the path is
40
+ * the tell). Partner code MUST NOT import from this subpath; the
41
+ * public, SemVer-covered surface for these constants stays
42
+ * `@roku-sdk/device-test-client` (curated barrel in `src/index.ts`).
43
+ *
44
+ * Adding new symbols:
45
+ * Only constants that a config-loader truly needs at parse time
46
+ * belong here (file-name constants, env-var names, default budgets
47
+ * referenced by `jest.config.ts` reporters / global setup). Pure-
48
+ * implementation magic numbers stay file-internal in
49
+ * `src/constants.ts`. Prefer extending this barrel over minting a
50
+ * parallel `/internal/<other>` subpath — keeps the internal contract
51
+ * grep-able.
52
+ */
53
+
54
+ export { ARTIFACT_FILE } from "../src/constants";
@@ -0,0 +1,60 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /**
5
+ * `@roku-sdk/device-test-client/internal/deploy` — explicit internal
6
+ * subpath for the **deploy-side** tooling helpers that the curated
7
+ * public API intentionally does NOT carry.
8
+ *
9
+ * Currently exposes:
10
+ *
11
+ * - `resolveAppPath` — integration-test deploy-guard directory-wipe
12
+ * path resolution.
13
+ * - `invalidateBuildCacheForApp` — release the in-process
14
+ * `RkClient.buildAndDeploy` build memoisation entries scoped to
15
+ * a single `absRoot`. Pairs with the deploy-guard's
16
+ * `wipeIntegrationBuildOutput` so a "we rebuilt because the
17
+ * previous bundle was dirty" recovery path also invalidates the
18
+ * "we already built this" memoisation. Both calls must run in
19
+ * lockstep; calling one without the other is a bug.
20
+ *
21
+ * Symbols here are reachable, typed, and stable across patch
22
+ * releases — but explicitly NOT covered by SemVer (`/internal/` in
23
+ * the path is the tell). Partner code MUST NOT import from this
24
+ * subpath; downstream consumers should treat it as private.
25
+ *
26
+ * Why a re-export shim instead of a direct entry in `exports`:
27
+ * `device-tests/tsconfig.json` uses `moduleResolution: "Node"`
28
+ * (classic), which falls back to a filesystem walk and does NOT honor
29
+ * the package `exports` map. A physical file at this path resolves
30
+ * under every resolution mode; the `exports` map entry covers the
31
+ * publish-time / `Node16` / `Bundler` case symmetrically.
32
+ *
33
+ * Adding new symbols (load-chain caveat):
34
+ * This barrel re-exports from `../src/client/deploy`, which imports
35
+ * `@roku-sdk/tooling/src/device-test-helpers` — an ESM module
36
+ * (`"type": "module"`, `import.meta.url`). Anything importing this
37
+ * subpath therefore pulls that ESM chain in transitively. That is
38
+ * FINE for runtime callers (Jest transforms `@roku-sdk/*` via babel,
39
+ * see `device-tests/jest.config.ts → transform`) but it is FATAL
40
+ * for parse-time loaders such as ts-node in CJS mode that the IDE
41
+ * and Jest itself use to read `*.config.ts`; those crash with
42
+ * `ERR_REQUIRE_ESM` before any transform runs.
43
+ *
44
+ * Group new `@internal` symbols by **load-time safety**, not just
45
+ * by concern:
46
+ * - Runtime-only helpers (need the live client / build pipeline /
47
+ * tooling shim) → add here.
48
+ * - Symbols a `*.config.ts` legitimately reads at parse time →
49
+ * add to `./internal/constants` (or another sibling subpath
50
+ * whose source file imports only types and value-level
51
+ * literals). Re-confirm by tracing the new file's imports to
52
+ * leaves; one accidental hop into `@roku-sdk/tooling` re-breaks
53
+ * the config-loader path.
54
+ *
55
+ * Prefer extending an existing `/internal/<name>` subpath over
56
+ * minting a parallel one — keeps the internal contract grep-able.
57
+ */
58
+
59
+ export { resolveAppPath } from "../src/client/deploy";
60
+ export { invalidateBuildCacheForApp } from "../src/client/build-cache";
@@ -0,0 +1,15 @@
1
+ // Copyright (c) 2026 Roku, Inc.
2
+ // SPDX-License-Identifier: MIT
3
+
4
+ /**
5
+ * `@roku-sdk/device-test-client/internal/paths` — explicit internal
6
+ * subpath for load-safe path constants/helpers used by test config and
7
+ * setup code.
8
+ *
9
+ * This subpath intentionally re-exports only from `src/paths.ts`, whose
10
+ * runtime imports are Node built-ins. It is safe for CommonJS-mode config
11
+ * loaders (`jest.config.ts`, ts-node) and must stay free of `RkClient`,
12
+ * `@roku-sdk/tooling`, or other ESM-heavy runtime chains.
13
+ */
14
+
15
+ export { TEST_RESULTS_DIR_ENV_VAR, TEST_RESULTS_DIR_NAME, defaultTestResultsDir } from "../src/paths";
package/package.json CHANGED
@@ -1,6 +1,83 @@
1
1
  {
2
2
  "name": "@roku-sdk/device-test-client",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "2.18.0",
4
+ "description": "Roku SDK on-device integration test client (RkClient, DOM/screenshot/log assertions, Jest wiring).",
5
+ "main": "src/index.ts",
6
+ "types": "src/index.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./src/index.ts",
10
+ "default": "./src/index.ts"
11
+ },
12
+ "./environment": {
13
+ "types": "./src/jest/device-test-environment.ts",
14
+ "default": "./src/jest/device-test-environment.ts"
15
+ },
16
+ "./internal/deploy": {
17
+ "types": "./internal/deploy.ts",
18
+ "default": "./internal/deploy.ts"
19
+ },
20
+ "./internal/constants": {
21
+ "types": "./internal/constants.ts",
22
+ "default": "./internal/constants.ts"
23
+ },
24
+ "./internal/paths": {
25
+ "types": "./internal/paths.ts",
26
+ "default": "./internal/paths.ts"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "files": [
31
+ "src/**/*",
32
+ "internal/**/*",
33
+ "docs/**/*",
34
+ "README.md"
35
+ ],
36
+ "engines": {
37
+ "node": ">=24.0.0"
38
+ },
39
+ "author": "Roku Inc",
40
+ "license": "MIT",
41
+ "repository": {
42
+ "type": "git",
43
+ "url": "git+https://gitlab.com/rokudev/rsg-sdk.git",
44
+ "directory": "packages/device-test-client"
45
+ },
46
+ "homepage": "https://gitlab.com/rokudev/rsg-sdk/-/tree/main/packages/device-test-client#readme",
47
+ "bugs": {
48
+ "url": "https://gitlab.com/rokudev/rsg-sdk/-/issues"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ },
53
+ "dependencies": {
54
+ "pixelmatch": "^5.3.0",
55
+ "pngjs": "^7.0.0",
56
+ "semver": "^7.6.0",
57
+ "@roku-sdk/tooling": "2.18.0"
58
+ },
59
+ "peerDependencies": {
60
+ "jest-environment-node": ">=29.7.0",
61
+ "@roku-sdk/tooling": "^2.18.0"
62
+ },
63
+ "devDependencies": {
64
+ "@types/jest": "^30.0.0",
65
+ "@types/node": "^20.0.0",
66
+ "@types/pixelmatch": "^5.2.6",
67
+ "@types/pngjs": "^6.0.5",
68
+ "@types/semver": "^7.5.8",
69
+ "fast-check": "^4.7.0",
70
+ "jest": "^30.4.1",
71
+ "jest-environment-node": "^30.4.1",
72
+ "ts-jest": "^29.4.6",
73
+ "typescript": "5.5.4",
74
+ "ulid": "^2.3.0",
75
+ "@roku-sdk/dev-tools": "2.18.0",
76
+ "@roku-sdk/tooling": "2.18.0"
77
+ },
78
+ "scripts": {
79
+ "test": "jest",
80
+ "lint": "eslint --no-error-on-unmatched-pattern \"src/**/*.{ts,tsx,mjs,cjs,js}\" \"__tests__/**/*.{ts,tsx,mjs,cjs,js}\"",
81
+ "typecheck": "tsc --noEmit"
82
+ }
6
83
  }