@roku-sdk/device-test-client 2.18.0 → 2.19.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.
@@ -0,0 +1,17 @@
1
+ # Breaking Changes
2
+
3
+ ## `./internal/*` subpaths removed
4
+
5
+ The `./internal/*` subpaths are removed. The harness helpers that were reachable only through `./internal/deploy`, `./internal/constants`
6
+ and `./internal/paths` have documented entry points with the same contents:
7
+
8
+ | Before | After |
9
+ |--------|-------|
10
+ | `@roku-sdk/device-test-client/internal/deploy` | `@roku-sdk/device-test-client/support/deploy` |
11
+ | `@roku-sdk/device-test-client/internal/constants` | `@roku-sdk/device-test-client/support/constants` |
12
+ | `@roku-sdk/device-test-client/internal/paths` | `@roku-sdk/device-test-client/support/paths` |
13
+
14
+ `ARTIFACT_FILE` is also exported from the package root.
15
+
16
+ **Migration.** Replace `/internal/` with `/support/` in these imports. The `support/constants`
17
+ and `support/paths` entry points stay safe to import from a CommonJS-mode `jest.config.ts`.
package/docs/errors.md CHANGED
@@ -132,12 +132,19 @@ Known `context.reason` values: `"readiness-timeout"`,
132
132
 
133
133
  `"never-left-home"` is special. It means the device answered ECP for
134
134
  the entire readiness budget but never foregrounded the sideloaded
135
- channel — an **unrecoverable-on-this-VM** condition (a wedged
136
- device host). It latches the device **fatal**
137
- (`markDeviceFatal`), not merely unhealthy, so downstream deploys
138
- fast-skip and the recovery `beforeEach` refuses to revive it. See
135
+ channel. On a device that has not been seen foregrounding a channel
136
+ at all, that is an **unrecoverable-on-this-VM** condition (a wedged
137
+ device host): it latches the device **fatal** (`markDeviceFatal`),
138
+ not merely unhealthy, so downstream deploys fast-skip and the
139
+ recovery `beforeEach` refuses to revive it. See
139
140
  [Fatal device wedge](#fatal-device-wedge) below.
140
141
 
142
+ Once any deploy has put a channel on screen, the same observation
143
+ carries the softer meaning — a launch slower than the budget looks
144
+ identical from inside one deploy — so it only flips soft health and
145
+ the deploy-guard retries that deploy. The `reason` tag reports what
146
+ was observed either way; only the verdict differs.
147
+
141
148
  ---
142
149
 
143
150
  ## Lifecycle health
@@ -190,7 +197,12 @@ There are two device-health states, exposed as separate helper sets:
190
197
 
191
198
  `markDeviceFatal` also flips the soft flag, so the existing cascade
192
199
  keeps firing and you never need to call both. Currently only
193
- `DeployFailedError({ reason: "never-left-home" })` latches fatal.
200
+ `DeployFailedError({ reason: "never-left-home" })` latches fatal, and
201
+ only on a device with no record of foregrounding a channel. A deploy
202
+ that put one on screen calls `markChannelForegrounded`, which keeps
203
+ later stalled launches on that VM soft — the latch costs the whole
204
+ shard plus a lease recycle, so it is reserved for the case the
205
+ device's own history does not contradict.
194
206
 
195
207
  Consequences once fatal is latched (single `--runInBand` process):
196
208
 
@@ -431,7 +443,14 @@ class WaitForLogTimeoutError extends Error {
431
443
  }
432
444
  ```
433
445
 
434
- Thrown by `waitForLog` on timeout.
446
+ Thrown by `waitForLog` on timeout. `linesChecked === 0` means no
447
+ output at all reached the window, re-attach included. That narrows the
448
+ cause but does not settle it: an app with nothing to log in those few
449
+ seconds and a stream stalled at its far end look the same from here,
450
+ and a short window around a quiet moment is the ordinary case. What it
451
+ does rule out is a line lost between the server and this process,
452
+ which the re-attach would have replayed. The message names both
453
+ readings.
435
454
 
436
455
  ### `UnexpectedLogError`
437
456
 
@@ -99,6 +99,9 @@ Resolves with the first line matching `pattern` whose seq >=
99
99
  - throws `LogStreamGapError` on timeout if any gap was recorded in
100
100
  the wait window — the missing lines might have included the
101
101
  marker, so the timeout is ambiguous, not a clean negative;
102
+ - re-attaches the stream once, and waits a further
103
+ `LOG_STREAM_RESYNC_GRACE_MS`, when the window received no line at
104
+ all (see [Silent windows](#silent-windows));
102
105
  - throws `WaitForLogTimeoutError` on timeout with `recentLines`,
103
106
  `position`, `timeout`.
104
107
 
@@ -234,6 +237,38 @@ reconnect because its truth value is sensitive to lines we did not
234
237
  see. `logs` is a bulk read with no soundness contract beyond "what
235
238
  is in the buffer right now"; it does not throw on gaps.
236
239
 
240
+ ### Silent windows
241
+
242
+ Gaps only describe reconnects the stream reported. A subscription can
243
+ also stop delivering while both ends still consider it attached — the
244
+ server's heartbeat probes the device over ECP, which keeps answering
245
+ — and then nothing is reported, nothing is closed, and no gap is
246
+ recorded. From inside a wait that is indistinguishable from a device
247
+ with nothing to say.
248
+
249
+ `waitForLog` treats a window in which **no line arrived at all** as
250
+ exactly that: no evidence either way. Before calling the marker
251
+ absent it asks `LogSession.resync()` for a fresh subscription and
252
+ waits `LOG_STREAM_RESYNC_GRACE_MS` longer. `/logs/tail` replays the
253
+ whole server-side buffer on attach and `lastServerSeq` drops the
254
+ duplicates, so a line only the subscription leg lost arrives through
255
+ the still-live subscription and resolves the wait. Nothing is
256
+ recorded as a gap — a replay fills the window rather than losing it,
257
+ and an open gap would mark the replayed lines themselves suspect.
258
+
259
+ If the re-attach also delivers nothing, the timeout stands. What that
260
+ rules out is a line lost between the server and this process, which
261
+ the replay would have carried; what it leaves is an app with nothing
262
+ to log in those few seconds and a stream stalled at its far end,
263
+ which are indistinguishable from here. A short window around a quiet
264
+ moment is much the commoner of the two, so the message names both
265
+ rather than reporting a transport fault. The re-attach is kept
266
+ regardless, so the next assertion in the file starts on a live
267
+ subscription instead of inheriting the silence.
268
+
269
+ `expectNoLog` does not yet do this, and a silent stream therefore
270
+ satisfies it vacuously.
271
+
237
272
  ### Why the asymmetry is sound
238
273
 
239
274
  `since` is captured before the action, so any seq `>= since` is
package/docs/rkclient.md CHANGED
@@ -646,7 +646,9 @@ advice live in `[errors.md](errors.md)`.
646
646
  The `never-left-home` reason latches the device **fatal**
647
647
  (`markDeviceFatal` / `isDeviceFatal` / `getFatalReason`) — an
648
648
  unrecoverable-on-this-VM wedge that disables recovery and fast-skips
649
- downstream deploys; see [errors.md](errors.md#fatal-device-wedge)
649
+ downstream deploys — but only while no deploy has put a channel on
650
+ screen; after that it stays soft and retryable. See
651
+ [errors.md](errors.md#fatal-device-wedge)
650
652
  - Polling — `WaitForNodeTimeoutError`, `AppCrashedError`,
651
653
  `ScreenshotNotStableError`
652
654
  - Logs (see [log-assertions.md](log-assertions.md)) —
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@roku-sdk/device-test-client",
3
- "version": "2.18.0",
3
+ "version": "2.19.0",
4
4
  "description": "Roku SDK on-device integration test client (RkClient, DOM/screenshot/log assertions, Jest wiring).",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -13,23 +13,24 @@
13
13
  "types": "./src/jest/device-test-environment.ts",
14
14
  "default": "./src/jest/device-test-environment.ts"
15
15
  },
16
- "./internal/deploy": {
17
- "types": "./internal/deploy.ts",
18
- "default": "./internal/deploy.ts"
16
+ "./support/deploy": {
17
+ "types": "./support/deploy.ts",
18
+ "default": "./support/deploy.ts"
19
19
  },
20
- "./internal/constants": {
21
- "types": "./internal/constants.ts",
22
- "default": "./internal/constants.ts"
20
+ "./support/constants": {
21
+ "types": "./support/constants.ts",
22
+ "default": "./support/constants.ts"
23
23
  },
24
- "./internal/paths": {
25
- "types": "./internal/paths.ts",
26
- "default": "./internal/paths.ts"
24
+ "./support/paths": {
25
+ "types": "./support/paths.ts",
26
+ "default": "./support/paths.ts"
27
27
  },
28
28
  "./package.json": "./package.json"
29
29
  },
30
30
  "files": [
31
31
  "src/**/*",
32
- "internal/**/*",
32
+ "support/**/*",
33
+ "BREAKING_CHANGES.md",
33
34
  "docs/**/*",
34
35
  "README.md"
35
36
  ],
@@ -54,11 +55,11 @@
54
55
  "pixelmatch": "^5.3.0",
55
56
  "pngjs": "^7.0.0",
56
57
  "semver": "^7.6.0",
57
- "@roku-sdk/tooling": "2.18.0"
58
+ "@roku-sdk/tooling": "2.19.0"
58
59
  },
59
60
  "peerDependencies": {
60
61
  "jest-environment-node": ">=29.7.0",
61
- "@roku-sdk/tooling": "^2.18.0"
62
+ "@roku-sdk/tooling": "^2.19.0"
62
63
  },
63
64
  "devDependencies": {
64
65
  "@types/jest": "^30.0.0",
@@ -72,8 +73,8 @@
72
73
  "ts-jest": "^29.4.6",
73
74
  "typescript": "5.5.4",
74
75
  "ulid": "^2.3.0",
75
- "@roku-sdk/dev-tools": "2.18.0",
76
- "@roku-sdk/tooling": "2.18.0"
76
+ "@roku-sdk/dev-tools": "2.19.0",
77
+ "@roku-sdk/tooling": "2.19.0"
77
78
  },
78
79
  "scripts": {
79
80
  "test": "jest",
@@ -77,10 +77,10 @@
77
77
  * to disk. Otherwise a later hit for the old key would skip rebuild
78
78
  * and sideload bytes produced by the newer sibling build.
79
79
  *
80
- * Exported via `./internal/deploy` (not the public barrel) because
81
- * it is an integration-test scaffolding helper, not a partner-
82
- * facing API. Treat any change here as a breaking change to that
83
- * internal subpath and the deploy-guard contract together.
80
+ * Exported via `./support/deploy` (not the public barrel) because
81
+ * it is a harness helper, not something test bodies call. Treat any
82
+ * change here as a change to that entry point and the deploy-guard
83
+ * contract together.
84
84
  */
85
85
 
86
86
  /**