@tantawowa/hosanna-tools 3.9.0 → 3.10.1

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 (82) hide show
  1. package/README.md +100 -18
  2. package/dist/build-info.json +3 -3
  3. package/dist/cli.js +32 -140
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.d.ts +2 -8
  6. package/dist/index.js +2 -12
  7. package/dist/index.js.map +1 -1
  8. package/dist/run/run-controller.js +10 -1
  9. package/dist/run/run-controller.js.map +1 -1
  10. package/dist/support-tools/build-config-resolver.d.ts +1 -0
  11. package/dist/support-tools/build-config-resolver.js +30 -5
  12. package/dist/support-tools/build-config-resolver.js.map +1 -1
  13. package/dist/support-tools/command-debugger.d.ts +1 -0
  14. package/dist/support-tools/command-debugger.js +6 -0
  15. package/dist/support-tools/command-debugger.js.map +1 -1
  16. package/dist/support-tools/dev-runner.d.ts +1 -0
  17. package/dist/support-tools/dev-runner.js +3 -3
  18. package/dist/support-tools/dev-runner.js.map +1 -1
  19. package/dist/support-tools/mcp-server/{cli-test-recorder.d.ts → rasp-capture.d.ts} +7 -7
  20. package/dist/support-tools/mcp-server/{cli-test-recorder.js → rasp-capture.js} +58 -58
  21. package/dist/support-tools/mcp-server/rasp-capture.js.map +1 -0
  22. package/dist/support-tools/mcp-server/rasp-exporter.d.ts +12 -12
  23. package/dist/support-tools/mcp-server/rasp-exporter.js +23 -23
  24. package/dist/support-tools/mcp-server/rasp-exporter.js.map +1 -1
  25. package/dist/support-tools/mcp-server/rasp-input-capture.d.ts +60 -0
  26. package/dist/support-tools/mcp-server/rasp-input-capture.js +100 -0
  27. package/dist/support-tools/mcp-server/rasp-input-capture.js.map +1 -0
  28. package/dist/support-tools/mcp-server/roku-device-client.d.ts +9 -0
  29. package/dist/support-tools/mcp-server/roku-device-client.js +17 -2
  30. package/dist/support-tools/mcp-server/roku-device-client.js.map +1 -1
  31. package/dist/support-tools/mcp-server/roku-log-client.d.ts +2 -0
  32. package/dist/support-tools/mcp-server/roku-log-client.js +16 -2
  33. package/dist/support-tools/mcp-server/roku-log-client.js.map +1 -1
  34. package/dist/support-tools/mcp-server/tools.js +30 -679
  35. package/dist/support-tools/mcp-server/tools.js.map +1 -1
  36. package/dist/support-tools/mcp-server/types.d.ts +4 -1
  37. package/dist/support-tools/mcp-server/types.js +3 -1
  38. package/dist/support-tools/mcp-server/types.js.map +1 -1
  39. package/dist/testing/client.d.ts +215 -0
  40. package/dist/testing/client.js +1029 -0
  41. package/dist/testing/client.js.map +1 -0
  42. package/dist/testing/config.d.ts +5 -0
  43. package/dist/testing/config.js +55 -0
  44. package/dist/testing/config.js.map +1 -0
  45. package/dist/testing/global-setup.d.ts +10 -0
  46. package/dist/testing/global-setup.js +39 -0
  47. package/dist/testing/global-setup.js.map +1 -0
  48. package/dist/testing/index.d.ts +7 -0
  49. package/dist/testing/index.js +26 -0
  50. package/dist/testing/index.js.map +1 -0
  51. package/dist/testing/launcher.d.ts +23 -0
  52. package/dist/testing/launcher.js +675 -0
  53. package/dist/testing/launcher.js.map +1 -0
  54. package/dist/testing/target.d.ts +16 -0
  55. package/dist/testing/target.js +40 -0
  56. package/dist/testing/target.js.map +1 -0
  57. package/dist/testing/types.d.ts +266 -0
  58. package/dist/testing/types.js +3 -0
  59. package/dist/testing/types.js.map +1 -0
  60. package/dist/testing/vitest-fixture.d.mts +28 -0
  61. package/dist/testing/vitest-fixture.mjs +87 -0
  62. package/dist/testing/vitest-fixture.mjs.map +1 -0
  63. package/dist/testing/vitest-reporter.d.mts +35 -0
  64. package/dist/testing/vitest-reporter.mjs +176 -0
  65. package/dist/testing/vitest-reporter.mjs.map +1 -0
  66. package/dist/testing/vitest.d.ts +1 -0
  67. package/dist/testing/vitest.js +8 -0
  68. package/dist/testing/vitest.js.map +1 -0
  69. package/dist/vite.d.ts +7 -0
  70. package/dist/vite.js +58 -0
  71. package/dist/vite.js.map +1 -1
  72. package/package.json +28 -3
  73. package/dist/support-tools/mcp-server/cli-test-recorder.js.map +0 -1
  74. package/dist/support-tools/mcp-server/cli-test-runner.d.ts +0 -37
  75. package/dist/support-tools/mcp-server/cli-test-runner.js +0 -332
  76. package/dist/support-tools/mcp-server/cli-test-runner.js.map +0 -1
  77. package/dist/support-tools/mcp-server/test-reporter.d.ts +0 -19
  78. package/dist/support-tools/mcp-server/test-reporter.js +0 -248
  79. package/dist/support-tools/mcp-server/test-reporter.js.map +0 -1
  80. package/dist/support-tools/mcp-server/test-runner.d.ts +0 -222
  81. package/dist/support-tools/mcp-server/test-runner.js +0 -686
  82. package/dist/support-tools/mcp-server/test-runner.js.map +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## Documentation
4
4
 
5
- - **[MCP debugger & UI tests](src/support-tools/mcp-server/README.md)** — `hst mcp:start`, all MCP tools, commands by use case, examples, `hst test:ui` / `hst test:record`.
5
+ - **[MCP debugger](src/support-tools/mcp-server/README.md)** — `hst mcp:start`, debugger tools, commands by use case, examples, and RASP certification capture.
6
6
  - **[docs/README.md](docs/README.md)** — index of docs in this repo (MCP + source-map note).
7
7
  - **hosanna-ui** (separate repo): `docs/README.md` for agentic debugging and links back to this README.
8
8
 
@@ -94,7 +94,7 @@ Common build-script replacements:
94
94
  | `npx hst secrets:check` | `await secrets.check({ cwd: process.cwd() })` |
95
95
  | `npx hst env check` | `await env.check({ cwd: process.cwd() })` |
96
96
 
97
- Long-running APIs such as `dev.run`, `debugger.start`, `mcp.start`, `test.record`, and RASP capture-style workflows keep the same operational behavior as the corresponding CLI commands: they start services, attach to debuggers, or wait for user/session activity. `mcp.start`, `dev.run`, and `test.ui` are wired for programmatic no-exit behavior through the top-level API.
97
+ Long-running APIs such as `dev.run`, `debugger.start`, `mcp.start`, and RASP-owned certification capture workflows keep the same operational behavior as the corresponding CLI commands: they start services, attach to debuggers, or wait for user/session activity. `mcp.start` and `dev.run` are wired for programmatic no-exit behavior through the top-level API.
98
98
 
99
99
  # Getting started
100
100
 
@@ -386,9 +386,95 @@ silently break an install.
386
386
 
387
387
  **Cursor and MCP:** Cursor does not magically attach to a terminal `hst mcp:start`. Add Hosanna as a Command MCP server (project **`.cursor/mcp.json`** or **Cursor Settings → MCP → Add Custom MCP**) so Cursor spawns **`hosanna-mcp`** over stdio. Start the command debugger/app explicitly with `hst run --platform web --target web`, `hst run --platform ios --target web`, `hst run --platform android-tv --target web`, or `hst run --platform roku --target device`.
388
388
 
389
- ### UI Test Commands (`test:*`) - UI Test Automation
390
- - `test:ui` - Replay UI test recordings against a running Hosanna app
391
- - `test:record` - Interactively record a UI test by navigating the app
389
+ ### Deterministic UI testing
390
+
391
+ Applications own regular Vitest suites. Global setup comes from `@tantawowa/hosanna-tools/testing/vitest`; worker fixtures and matchers come from `@tantawowa/hosanna-tools/testing/vitest/fixture`; shared lifecycle, types, and helpers come from `@tantawowa/hosanna-tools/testing`. There is no HST test CLI or recording/replay format.
392
+
393
+ Add `createHosannaReporter(config)` from `@tantawowa/hosanna-tools/testing/vitest/reporter` to the application's Vitest reporters. Every run then writes a self-contained JSON and HTML report under `test-results/<testRunId>/`, updates `test-results/latest.json` and `test-results/latest.html`, and includes every Vitest result plus any named visual proofs captured by the tests.
394
+
395
+ Browser targets can also record the complete owned browser session as WebM:
396
+
397
+ ```ts
398
+ defineHosannaTestConfig({
399
+ // ...
400
+ video: true,
401
+ // Or: video: { size: { width: 1280, height: 720 } },
402
+ });
403
+ ```
404
+
405
+ The finalized recording is written to
406
+ `test-results/<testRunId>/<platform>-web/run.webm` and embedded in the HTML
407
+ report. Recording is opt-in in the shared tools and ignored for device and
408
+ simulator targets. It records browser video only, without audio. This is a
409
+ review artifact, not a replay format or executable test source.
410
+
411
+ ```ts
412
+ import { defineConfig } from 'vitest/config';
413
+ import { createHosannaReporter } from '@tantawowa/hosanna-tools/testing/vitest/reporter';
414
+ import hosannaTestConfig from './integration/hosanna-test.config';
415
+
416
+ export default defineConfig({
417
+ reporters: ['default', createHosannaReporter(hosannaTestConfig)],
418
+ });
419
+ ```
420
+
421
+ Use `hs.proof.capture(...)` at meaningful acceptance moments. A proof contains the target screenshot and the inspected screen/focus/control state used to understand it; hierarchy and scoped logs are optional. Proofs are evidence in the report, not an assertion by themselves:
422
+
423
+ ```ts
424
+ const poster = hs.image.inCollectionCell({
425
+ collectionId: 'collectionView',
426
+ rowSettingsKey: 'rows.regular',
427
+ cellIndex: 0,
428
+ imageId: 'poster',
429
+ });
430
+ await poster.waitForLoaded({ requireBitmapSize: true });
431
+ await expect(poster).toBeLoadedImage({ requireBitmapSize: true });
432
+
433
+ await hs.proof.capture('programme-focused', {
434
+ controls: {
435
+ programmeCard: hs.control.byId('programmeCard'),
436
+ },
437
+ images: { poster },
438
+ includeLogs: true,
439
+ });
440
+ ```
441
+
442
+ Image readiness requires a non-empty URI, `loadStatus: "ready"`, positive rendered dimensions, and positive decoded bitmap dimensions by default. Explicit `loadWidth`/`loadHeight` assertions remain available for controls that request a fixed decode size; zero is valid when the runtime decodes at the source size. `hs.image.byId(...)`, `hs.image.within(...)`, and semantic `hs.image.inCollectionCell(...)` locators work across browser, Roku, and later native drivers.
443
+
444
+ Screenshots and proofs wait for the command-plane hierarchy and focus to remain stable for 300ms by default, then allow a short platform render-settle window. Consecutive screenshots are also spaced automatically. Override this only for known platform behaviour through the test config's `screenshot` settings or per-capture options; failure diagnostics bypass the stable-frame wait so they can still capture broken states.
445
+
446
+ Custom test ports must be unique. Keep the Vite and browser CDP ports outside the debugger's dynamic app allocation range; the launcher validates this before starting any processes and reports the conflicting range.
447
+
448
+ Keep structural and state assertions as the default correctness checks. Use `hs.screenshot.match(...)` only for an explicit visual regression whose target-specific baseline and tolerance are intentionally maintained. Failure diagnostics and proof artifacts are separated below `test-results/<testRunId>/<platform>-<target>/`.
449
+
450
+ `buildConfig` supplies a non-secret overlay that is merged last for the owned test launch. It may be a target-aware factory, so browser emulation can enable deterministic mocks while a physical Roku keeps its real platform services. The launcher and resolver both reject test overlays for `prod`:
451
+
452
+ ```ts
453
+ defineHosannaTestConfig({
454
+ // ...
455
+ environment: 'test',
456
+ buildConfig: ({ platform, target }) => ({
457
+ remoteDebug: { isEnabled: true },
458
+ ...(target === 'web'
459
+ ? { channelStore: { testMode: { enabled: true } } }
460
+ : {}),
461
+ }),
462
+ });
463
+ ```
464
+
465
+ Use `createHosannaTestTarget(config)` with Vitest's standard conditional APIs. Selectors accept one platform or a set, and distinguish browser, simulator, and physical-device targets:
466
+
467
+ ```ts
468
+ const target = createHosannaTestTarget(config);
469
+
470
+ test.runIf(target.matches({ platform: 'roku', target: 'device' }))('Roku only', async ({ hs }) => {});
471
+ test.runIf(target.matches({ platform: ['ios', 'android'], target: 'sim' }))('mobile simulators', async ({ hs }) => {});
472
+ test.runIf(target.matches({ target: 'web' }))('all browser flavours', async ({ hs }) => {});
473
+ ```
474
+
475
+ Browser Channel Store emulation supports per-test `success`, `cancelled`, and `failed` outcomes when `channelStore.testMode.enabled` is present in the test overlay. Set it from a named precondition with `hs.mocks.channelStore.setPurchaseOutcome(...)`. This API refuses physical-device targets. Roku purchase tests use Roku Pay [billing testing](https://developer.roku.com/dev/docs/billing-testing), a linked [Roku test user](https://developer.roku.com/dev/docs/test-users), and real Channel Store/OS purchase screens; keep them opt-in and serial.
476
+
477
+ For Roku device suites, the launcher builds and deploys before the test launch, then relaunches through ECP with the exact test run ID and a debugger address reachable from the device. Configure the device IP and developer password in the application test config/environment. `HS_TEST_DEBUG_HOST` overrides automatic LAN address selection when needed. Physical Roku input, held keys, deep links, screenshots, and logs use their device transports; the committed Vitest source remains the same API used by browser tests.
392
478
 
393
479
  ## Usage Examples
394
480
 
@@ -445,16 +531,15 @@ hst env prepare-gitignore # Update .gitignore
445
531
  hst debugger:start # Start debug proxy (required)
446
532
  hst mcp:start # Start MCP server for Cursor/Claude
447
533
 
448
- # UI test automation
449
- hst test:record --name "My test" # Record UI test interactively
450
- hst test:ui # Replay all recordings
451
- hst test:ui --suite regression.json # Run test suite
452
- hst test:ui --json --report-dir ./reports # CI mode with reports
534
+ # Application-owned UI tests
535
+ npm run test:ui
536
+ npm run test:ui -- auth/login.test.ts
537
+ HS_TEST_PLATFORM=roku HS_TEST_TARGET=device npm run test:ui
453
538
  ```
454
539
 
455
- ## MCP Debugging & UI Test Automation
540
+ ## MCP Debugging & Deterministic UI Tests
456
541
 
457
- Hosanna Tools includes a powerful MCP (Model Context Protocol) server for AI-assisted debugging and a UI test automation framework.
542
+ Hosanna Tools includes an MCP server for interactive AI-assisted debugging. Deterministic UI tests run in an application's Vitest process and use the separate `@tantawowa/hosanna-tools/testing` export; MCP is only an authoring aid.
458
543
 
459
544
  ### Cursor Settings → MCP (Hosanna debugger)
460
545
 
@@ -507,7 +592,7 @@ After saving, enable the server in the MCP list. **`hst mcp:start`** is still us
507
592
  ### Quick Start
508
593
 
509
594
  ```bash
510
- # 1. Start the debug proxy (required for both MCP and test automation)
595
+ # 1. Start the debug proxy for an interactive MCP debugging session
511
596
  hst debugger:start
512
597
 
513
598
  # 2. Start your Hosanna app with remote debugging enabled
@@ -515,11 +600,8 @@ hst debugger:start
515
600
  # 3. For AI debugging (Cursor, Claude Code):
516
601
  # Add MCP server (see "Cursor Settings → MCP" above) or hst mcp:start in a terminal
517
602
 
518
- # 4. For UI test recording:
519
- hst test:record --name "My test flow"
520
-
521
- # 5. For UI test replay:
522
- hst test:ui
603
+ # 4. Run committed deterministic tests from the application repository:
604
+ npm run test:ui
523
605
  ```
524
606
 
525
607
  ### See also
@@ -1,6 +1,6 @@
1
1
  {
2
- "buildDate": "2026-07-15T18:20:21+00:00",
3
- "buildDateISO": "2026-07-15T18:20:21.607Z",
2
+ "buildDate": "2026-07-17T08:33:15+00:00",
3
+ "buildDateISO": "2026-07-17T08:33:15.255Z",
4
4
  "timeZone": "UTC",
5
- "gitHash": "0a46a09"
5
+ "gitHash": "cb39ebb"
6
6
  }
package/dist/cli.js CHANGED
@@ -300,7 +300,7 @@ catch (err) {
300
300
  // Let yargs handle all commands including env
301
301
  (0, yargs_1.default)((0, helpers_1.hideBin)(process.argv))
302
302
  .version(`${buildVersion}\n git: ${buildGitHash}\n built: ${buildDate}`)
303
- .usage('$0 [command] [options]', `Hosanna Tools CLI\nVersion: ${buildVersion}\nBuild Date: ${buildDate}\n\nCommand Groups:\n compiler:* Compiler lifecycle (install, status, list)\n config hosanna.json management (wizard, show, set)\n env Environment checks and repair (check, fix, prepare-gitignore)\n dev:* Local development execution\n debugger:* Command debugger control\n mcp:* Hosanna MCP server control\n build Cross-platform build orchestration\n run Cross-platform launch orchestration\n native:* Native platform bootstrap and validation\n target:* Run target discovery and inventory\n roku:* Roku deployment, commands & packaging (run, command, package, map-stack)\n device:* Device discovery and run workflows\n generate:* Code and asset generation\n sdk:* Hosanna SDK/framework installation\n build-config:* Build/runtime configuration resolution\n ci:* Continuous integration helpers\n framework:* Framework distribution helpers\n license:* Hosanna framework licensing\n secrets:* Portable .secrets files\n test:* UI testing\n rasp:* RASP cert scripts`)
303
+ .usage('$0 [command] [options]', `Hosanna Tools CLI\nVersion: ${buildVersion}\nBuild Date: ${buildDate}\n\nCommand Groups:\n compiler:* Compiler lifecycle (install, status, list)\n config hosanna.json management (wizard, show, set)\n env Environment checks and repair (check, fix, prepare-gitignore)\n dev:* Local development execution\n debugger:* Command debugger control\n mcp:* Hosanna MCP server control\n build Cross-platform build orchestration\n run Cross-platform launch orchestration\n native:* Native platform bootstrap and validation\n target:* Run target discovery and inventory\n roku:* Roku deployment, commands & packaging (run, command, package, map-stack)\n device:* Device discovery and run workflows\n generate:* Code and asset generation\n sdk:* Hosanna SDK/framework installation\n build-config:* Build/runtime configuration resolution\n ci:* Continuous integration helpers\n framework:* Framework distribution helpers\n license:* Hosanna framework licensing\n secrets:* Portable .secrets files\n rasp:* RASP cert scripts`)
304
304
  .option('verbose', {
305
305
  type: 'boolean',
306
306
  default: false,
@@ -2056,121 +2056,13 @@ catch (err) {
2056
2056
  appName: args.appName,
2057
2057
  });
2058
2058
  })
2059
- .command('test:ui [recordings..]', 'Replay UI test recordings against a running Hosanna app and exit with pass/fail. ' +
2060
- 'Requires a running debug proxy (hst debugger:start) and a connected app.', yargs => yargs
2061
- .positional('recordings', {
2062
- describe: 'Recording IDs to replay (omit to run all)',
2063
- type: 'string',
2064
- array: true,
2065
- })
2066
- .option('suite', {
2067
- type: 'string',
2068
- describe: 'Path to a test-suite.json file (relative to recordings dir or absolute)',
2069
- })
2070
- .option('appId', {
2071
- type: 'string',
2072
- describe: 'Target app ID (defaults to the one stored in each recording)',
2073
- })
2074
- .option('managementPort', { type: 'number', default: 59150, describe: 'Debug proxy management port' })
2075
- .option('managementHost', { type: 'string', default: 'localhost', describe: 'Debug proxy host' })
2076
- .option('extensionPort', { type: 'number', default: 59153, describe: 'Debug proxy extension WebSocket port' })
2077
- .option('stop-on-failure', { type: 'boolean', default: true, describe: 'Stop each recording on first step failure' })
2078
- .option('step-delay-ms', { type: 'number', default: 500, describe: 'Delay after each action (ms)' })
2079
- .option('focus-assert-retry-ms', {
2080
- type: 'number',
2081
- default: 300,
2082
- describe: 'Delay (ms) between focus polls after a mismatch (0 = no polling)',
2083
- })
2084
- .option('focus-assert-max-retries', {
2085
- type: 'number',
2086
- default: 12,
2087
- describe: 'Max extra GetFocusedView polls per step (skeleton/load); 0 = single read only',
2088
- })
2089
- .option('screenshot-settle-ms', { type: 'number', default: 400, describe: 'Extra delay before screenshot capture (ms)' })
2090
- .option('compare-screenshots', { type: 'boolean', default: true, describe: 'Enable visual regression comparison' })
2091
- .option('screenshot-threshold', { type: 'number', default: 0.15, describe: 'Pixel diff tolerance (0-1)' })
2092
- .option('screenshot-fail-percent', { type: 'number', default: 2.0, describe: 'Max allowed diff % before failure' })
2093
- .option('roku-ip', { type: 'string', describe: 'Roku device IP (env: HOSANNA_ROKU_DEVICE_IP)' })
2094
- .option('roku-password', { type: 'string', describe: 'Roku dev password (env: HOSANNA_ROKU_DEV_PASSWORD)' })
2095
- .option('json', { type: 'boolean', default: false, describe: 'Output results as JSON only' })
2096
- .option('report-dir', { type: 'string', describe: 'Directory for HTML + JUnit XML reports' })
2097
- .option('update-baselines', { type: 'boolean', default: false, describe: 'Re-capture baseline screenshots without asserting (replays actions only)' })
2098
- .option('coverage', { type: 'boolean', default: false, describe: 'Analyze recordings and print navigation coverage (no running app needed)' }), async (args) => {
2099
- if (args.verbose) {
2100
- process.env.HOSANNA_VERBOSE = '1';
2101
- }
2102
- const { runTestUI } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/cli-test-runner.js')));
2103
- await runTestUI({
2104
- recordings: args.recordings,
2105
- suite: args.suite,
2106
- appId: args.appId,
2107
- managementPort: args.managementPort,
2108
- managementHost: args.managementHost,
2109
- extensionPort: args.extensionPort,
2110
- stopOnFailure: args.stopOnFailure,
2111
- stepDelayMs: args.stepDelayMs,
2112
- focusAssertRetryMs: args.focusAssertRetryMs,
2113
- focusAssertMaxRetries: args.focusAssertMaxRetries,
2114
- screenshotSettleMs: args.screenshotSettleMs,
2115
- compareScreenshots: args.compareScreenshots,
2116
- screenshotThreshold: args.screenshotThreshold,
2117
- screenshotFailPercent: args.screenshotFailPercent,
2118
- rokuIp: args.rokuIp,
2119
- rokuPassword: args.rokuPassword,
2120
- json: args.json,
2121
- verbose: args.verbose,
2122
- reportDir: args.reportDir,
2123
- updateBaselines: args.updateBaselines,
2124
- coverage: args.coverage,
2125
- });
2126
- })
2127
- .command('test:record', 'Interactively record a UI test by navigating the app. ' +
2128
- 'Key events from the running app are captured via the debug proxy.', yargs => yargs
2129
- .option('name', {
2130
- type: 'string',
2131
- demandOption: true,
2132
- describe: 'Name for this test recording',
2133
- })
2134
- .option('description', {
2135
- type: 'string',
2136
- describe: 'Optional description of what this test covers',
2137
- })
2138
- .option('appId', {
2139
- type: 'string',
2140
- describe: 'Target app ID (defaults to first connected app)',
2141
- })
2142
- .option('managementPort', { type: 'number', default: 59150, describe: 'Debug proxy management port' })
2143
- .option('managementHost', { type: 'string', default: 'localhost', describe: 'Debug proxy host' })
2144
- .option('extensionPort', { type: 'number', default: 59153, describe: 'Debug proxy extension WebSocket port' })
2145
- .option('roku-ip', { type: 'string', describe: 'Roku device IP for screenshots (env: HOSANNA_ROKU_DEVICE_IP)' })
2146
- .option('roku-password', { type: 'string', describe: 'Roku dev password for screenshots (env: HOSANNA_ROKU_DEV_PASSWORD)' })
2147
- .option('screenshot-on-select', { type: 'boolean', default: true, describe: 'Auto-capture screenshot on Select key press' })
2148
- .example('$0 test:record --name "Home screen navigation"', 'Record key presses while navigating')
2149
- .example('$0 test:record --name "Details test" --roku-ip 192.168.1.10', 'Record with Roku screenshot support'), async (args) => {
2150
- if (args.verbose) {
2151
- process.env.HOSANNA_VERBOSE = '1';
2152
- }
2153
- const { runTestRecord } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/cli-test-recorder.js')));
2154
- await runTestRecord({
2155
- name: args.name,
2156
- description: args.description,
2157
- appId: args.appId,
2158
- managementPort: args.managementPort,
2159
- managementHost: args.managementHost,
2160
- extensionPort: args.extensionPort,
2161
- rokuIp: args.rokuIp,
2162
- rokuPassword: args.rokuPassword,
2163
- screenshotOnSelect: args.screenshotOnSelect,
2164
- verbose: args.verbose,
2165
- });
2166
- })
2167
- .command('rasp:export <recordingId> <name>', 'Convert a saved test recording into a Roku Automation Scripting Platform (RASP) ' +
2059
+ .command('rasp:export <captureId> <name>', 'Convert a saved RASP input capture into a Roku Automation Scripting Platform (RASP) ' +
2168
2060
  'YAML script suitable for Roku certification testing. Output goes to ' +
2169
2061
  'rasp-scripts/<name>.rasp in the current project by default.', yargs => yargs
2170
- .positional('recordingId', {
2062
+ .positional('captureId', {
2171
2063
  type: 'string',
2172
2064
  demandOption: true,
2173
- describe: 'ID of the saved recording (filename without .json), e.g. "signin_1736888000000"',
2065
+ describe: 'ID of the saved capture (filename without .json), e.g. "signin_1736888000000"',
2174
2066
  })
2175
2067
  .positional('name', {
2176
2068
  type: 'string',
@@ -2180,11 +2072,11 @@ catch (err) {
2180
2072
  .option('out', {
2181
2073
  type: 'string',
2182
2074
  alias: 'o',
2183
- describe: 'Override the output path. Default: rasp-scripts/<name>.rasp next to recordings dir.',
2075
+ describe: 'Override the output path. Default: rasp-scripts/<name>.rasp next to the capture directory.',
2184
2076
  })
2185
2077
  .option('channel-name', {
2186
2078
  type: 'string',
2187
- describe: 'Channel name for the launch step (overrides recording.debugContext.channel.name).',
2079
+ describe: 'Channel name for the launch step (overrides capture.debugContext.channel.name).',
2188
2080
  })
2189
2081
  .option('channel-mode', {
2190
2082
  type: 'string',
@@ -2242,7 +2134,7 @@ catch (err) {
2242
2134
  describe: 'Master gate for credential auto-injection. When true (default), the exporter ' +
2243
2135
  'watches for OK presses landing on a TextInputView classified as email or password ' +
2244
2136
  '(via inputType / secureMode or field id) and injects the matching half of the RFI credential flow. ' +
2245
- 'Use --no-useWithRfi to disable injection entirely (recording flows through verbatim).',
2137
+ 'Use --no-useWithRfi to disable injection entirely (capture flows through verbatim).',
2246
2138
  })
2247
2139
  .option('login-text', {
2248
2140
  type: 'string',
@@ -2277,8 +2169,8 @@ catch (err) {
2277
2169
  }
2278
2170
  const path = await Promise.resolve().then(() => __importStar(require('path')));
2279
2171
  const fs = await Promise.resolve().then(() => __importStar(require('fs')));
2280
- const { getRecordingsDir } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/test-runner.js')));
2281
- const { exportRaspFromRecording, validateRaspYaml } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-exporter.js')));
2172
+ const { getRaspCapturesDir } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-input-capture.js')));
2173
+ const { exportRaspFromCapture, validateRaspYaml } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-exporter.js')));
2282
2174
  // Parses a minimal RASP YAML fragment containing only `- press: <key>` and
2283
2175
  // `- text: <value>` ops (with `LOGIN` / `PASSWORD` reserved tokens). Comments
2284
2176
  // (#…) and blank lines are allowed. Anything else throws.
@@ -2305,16 +2197,16 @@ catch (err) {
2305
2197
  }
2306
2198
  return ops;
2307
2199
  };
2308
- const recordingId = args.recordingId;
2200
+ const captureId = args.captureId;
2309
2201
  const scriptName = args.name.replace(/[^a-zA-Z0-9_-]/g, '_');
2310
- const recordingsDir = getRecordingsDir();
2311
- const recordingPath = path.join(recordingsDir, `${recordingId}.json`);
2312
- let recording;
2202
+ const capturesDir = getRaspCapturesDir();
2203
+ const capturePath = path.join(capturesDir, `${captureId}.json`);
2204
+ let capture;
2313
2205
  try {
2314
- recording = JSON.parse(fs.readFileSync(recordingPath, 'utf-8'));
2206
+ capture = JSON.parse(fs.readFileSync(capturePath, 'utf-8'));
2315
2207
  }
2316
2208
  catch (_c) {
2317
- console.error(`Recording "${recordingId}" not found in ${recordingsDir}.`);
2209
+ console.error(`RASP capture "${captureId}" not found in ${capturesDir}.`);
2318
2210
  process.exit(1);
2319
2211
  }
2320
2212
  // Parse repeatable --text-field flags into a map.
@@ -2332,7 +2224,7 @@ catch (err) {
2332
2224
  const credentialFlow = credentialFlowPath
2333
2225
  ? parseCredentialFlowFile(credentialFlowPath)
2334
2226
  : undefined;
2335
- const result = exportRaspFromRecording(recording, {
2227
+ const result = exportRaspFromCapture(capture, {
2336
2228
  channelName: args.channelName,
2337
2229
  channelMode: args.channelMode,
2338
2230
  contentId: args.contentId,
@@ -2357,7 +2249,7 @@ catch (err) {
2357
2249
  process.stdout.write(result.yaml);
2358
2250
  }
2359
2251
  console.error(`✅ Wrote ${outPath}`);
2360
- console.error(` ${result.stats.recordedSteps} recorded steps → ${result.stats.raspOps} RASP ops` +
2252
+ console.error(` ${result.stats.capturedSteps} captured steps → ${result.stats.raspOps} RASP ops` +
2361
2253
  ` (auto-pauses: ${result.stats.autoPausesInserted}, text injections: ${result.stats.textInjections}` +
2362
2254
  `, keyboard steps dropped: ${result.stats.keyboardStepsDropped}, verify ops: ${result.stats.verifyOpsEmitted})`);
2363
2255
  for (const d of result.rfiDetections) {
@@ -2380,7 +2272,7 @@ catch (err) {
2380
2272
  }
2381
2273
  })
2382
2274
  .command('rasp:capture <name>', 'Record a UI session and export it directly to a RASP script in one step. ' +
2383
- 'Navigate the app, press Ctrl+C to stop — the recording is saved and the RASP script is written automatically.', yargs => yargs
2275
+ 'Navigate the app, press Ctrl+C to stop — the capture is saved and the RASP script is written automatically.', yargs => yargs
2384
2276
  .positional('name', {
2385
2277
  type: 'string',
2386
2278
  demandOption: true,
@@ -2465,10 +2357,10 @@ catch (err) {
2465
2357
  default: false,
2466
2358
  describe: 'Print the YAML to stdout after writing.',
2467
2359
  })
2468
- .option('keep-recording', {
2360
+ .option('keep-capture', {
2469
2361
  type: 'boolean',
2470
2362
  default: false,
2471
- describe: 'Keep the temporary test-recordings JSON/screenshots after the RASP script is written.',
2363
+ describe: 'Keep the temporary RASP capture JSON/screenshots after the script is written.',
2472
2364
  })
2473
2365
  .example('$0 rasp:capture login --channel-name "Criterion"', 'Record and export to rasp-scripts/login.rasp')
2474
2366
  .example('$0 rasp:capture login --channel-name "Criterion" --login-text "user@example.com" --password-text "MyPass1!"', 'Default credential flow with custom text')
@@ -2476,13 +2368,13 @@ catch (err) {
2476
2368
  var _a, _b;
2477
2369
  const path = await Promise.resolve().then(() => __importStar(require('path')));
2478
2370
  const fs = await Promise.resolve().then(() => __importStar(require('fs')));
2479
- const { cleanupSavedRecordingArtifacts, runTestRecord } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/cli-test-recorder.js')));
2480
- const { getRecordingsDir } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/test-runner.js')));
2481
- const { exportRaspFromRecording, validateRaspYaml } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-exporter.js')));
2371
+ const { cleanupRaspCaptureArtifacts, runRaspCapture } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-capture.js')));
2372
+ const { getRaspCapturesDir } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-input-capture.js')));
2373
+ const { exportRaspFromCapture, validateRaspYaml } = await Promise.resolve().then(() => __importStar(require('./support-tools/mcp-server/rasp-exporter.js')));
2482
2374
  const scriptName = args.name.replace(/[^a-zA-Z0-9_-]/g, '_');
2483
2375
  const raspScriptsDir = path.join(process.cwd(), 'rasp-scripts');
2484
2376
  const outPath = (_a = args.out) !== null && _a !== void 0 ? _a : path.join(raspScriptsDir, `${scriptName}.rasp`);
2485
- const recordingsDir = getRecordingsDir();
2377
+ const capturesDir = getRaspCapturesDir();
2486
2378
  const textFields = {};
2487
2379
  const tfArgs = (_b = args['text-field']) !== null && _b !== void 0 ? _b : [];
2488
2380
  for (const tf of tfArgs) {
@@ -2520,7 +2412,7 @@ catch (err) {
2520
2412
  const credentialFlow = credentialFlowPath
2521
2413
  ? parseCredentialFlowFile(credentialFlowPath)
2522
2414
  : undefined;
2523
- await runTestRecord({
2415
+ await runRaspCapture({
2524
2416
  name: args.name,
2525
2417
  appId: args.appId,
2526
2418
  managementPort: args.managementPort,
@@ -2528,8 +2420,8 @@ catch (err) {
2528
2420
  extensionPort: args.extensionPort,
2529
2421
  rokuIp: args.rokuIp,
2530
2422
  rokuPassword: args.rokuPassword,
2531
- onSaved: async (recordingId, recording) => {
2532
- const result = exportRaspFromRecording(recording, {
2423
+ onSaved: async (captureId, capture) => {
2424
+ const result = exportRaspFromCapture(capture, {
2533
2425
  channelName: args.channelName,
2534
2426
  channelMode: args.channelMode,
2535
2427
  contentId: args.contentId,
@@ -2548,7 +2440,7 @@ catch (err) {
2548
2440
  if (args.print)
2549
2441
  process.stdout.write(result.yaml);
2550
2442
  console.log(`✅ RASP script written: ${outPath}`);
2551
- console.log(` ${result.stats.recordedSteps} steps → ${result.stats.raspOps} RASP ops` +
2443
+ console.log(` ${result.stats.capturedSteps} steps → ${result.stats.raspOps} RASP ops` +
2552
2444
  ` (pauses: ${result.stats.autoPausesInserted}, text injections: ${result.stats.textInjections})`);
2553
2445
  for (const d of result.rfiDetections) {
2554
2446
  const icon = d.type === 'login' ? '📧' : '🔑';
@@ -2567,15 +2459,15 @@ catch (err) {
2567
2459
  console.error(` Script has validation errors — review before submitting to Roku cert.`);
2568
2460
  process.exitCode = 1;
2569
2461
  }
2570
- if (!args.keepRecording) {
2462
+ if (!args.keepCapture) {
2571
2463
  try {
2572
- const cleanup = cleanupSavedRecordingArtifacts(recordingsDir, recordingId);
2464
+ const cleanup = cleanupRaspCaptureArtifacts(capturesDir, captureId);
2573
2465
  if (cleanup.jsonDeleted || cleanup.artifactDirDeleted) {
2574
- console.log(`🧹 Cleaned up temporary recording: ${recordingId}`);
2466
+ console.log(`🧹 Cleaned up temporary capture: ${captureId}`);
2575
2467
  }
2576
2468
  }
2577
2469
  catch (err) {
2578
- console.warn(`⚠ RASP script was written, but temporary recording cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
2470
+ console.warn(`⚠ RASP script was written, but temporary capture cleanup failed: ${err instanceof Error ? err.message : String(err)}`);
2579
2471
  }
2580
2472
  }
2581
2473
  },