{"_id":"@danecodes/roku-deeplink","name":"@danecodes/roku-deeplink","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@danecodes/roku-deeplink","version":"0.1.0","description":"Deep link test runner and validator for Roku channels","type":"module","bin":{"roku-deeplink":"dist/cli.js"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"scripts":{"build":"tsc","test":"node --experimental-vm-modules node_modules/.bin/vitest run","test:watch":"node --experimental-vm-modules node_modules/.bin/vitest"},"engines":{"node":">=22"},"dependencies":{"@danecodes/roku-ecp":"^0.5.0","commander":"^13.0.0"},"devDependencies":{"@types/node":"^25.6.0","typescript":"^5.7.0","vitest":"^3.0.0"},"keywords":["roku","deep-link","testing","certification","ecp"],"license":"MIT","gitHead":"0cc299664bdcf4dd1200f4fa9fb01221e038118b","_id":"@danecodes/roku-deeplink@0.1.0","_nodeVersion":"25.6.0","_npmVersion":"11.8.0","dist":{"integrity":"sha512-qKRrZWzp8qDrHuiTSon9Ce2Xlu7MZE1K07FV2ijdELM2xGf5KbPnzWqn/hJ2FEaxmZpegV4MVmg8PdYYps88/w==","shasum":"83b2ea6b00e2aaafff8c4eefbc0806cd9242008d","tarball":"https://registry.npmjs.org/@danecodes/roku-deeplink/-/roku-deeplink-0.1.0.tgz","fileCount":14,"unpackedSize":32092,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBg3NSgPcpK6b08tTNy5boYpx3Hl+I+Vl1zyq8GWB/L5AiEAg/1+4yyEXpP3QLSaR5z9XbyvovKB88Scg9Vqw9IAvUg="}]},"_npmUser":{"name":"danecodes","email":"danehesseldahl@gmail.com"},"directories":{},"maintainers":[{"name":"danecodes","email":"danehesseldahl@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/roku-deeplink_0.1.0_1776223519635_0.662371483806719"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-15T03:25:19.548Z","0.1.0":"2026-04-15T03:25:19.786Z","modified":"2026-04-15T03:25:19.997Z"},"maintainers":[{"name":"danecodes","email":"danehesseldahl@gmail.com"}],"description":"Deep link test runner and validator for Roku channels","keywords":["roku","deep-link","testing","certification","ecp"],"license":"MIT","readme":"# @danecodes/roku-deeplink\n\nDeep link test runner and validator for Roku channels. Define test cases, run them against a device, and get a pass/fail report — one command to validate all your deep links before submitting for Roku certification.\n\nBuilt on [@danecodes/roku-ecp](https://www.npmjs.com/package/@danecodes/roku-ecp).\n\n## Install\n\n```bash\nnpm install @danecodes/roku-deeplink\n```\n\n## Quick start\n\nGenerate a template config:\n\n```bash\nroku-deeplink init\n```\n\nEdit `deeplinks.json` with your content IDs, then run:\n\n```bash\nroku-deeplink run deeplinks.json --device 192.168.0.30\n```\n\n```\nDeep Link Test Results\n═══════════════════════\n✓ Movie playback           (3.2s)\n✓ Series details page      (2.1s)\n✗ Episode direct play      (15.0s) — playback did not start within 15000ms\n✓ Short form content       (1.8s)\n✓ Invalid content ID       (2.5s)\n✓ Home screen              (1.2s)\n\n5/6 passed (25.8s total)\n```\n\n## Config file\n\n```json\n{\n  \"channelId\": \"dev\",\n  \"tests\": [\n    {\n      \"name\": \"Movie playback\",\n      \"contentId\": \"MOVIE_001\",\n      \"mediaType\": \"movie\",\n      \"expect\": {\n        \"playback\": true,\n        \"playbackTimeout\": 15000,\n        \"noErrors\": true\n      }\n    },\n    {\n      \"name\": \"Series details page\",\n      \"contentId\": \"SERIES_042\",\n      \"mediaType\": \"series\",\n      \"expect\": {\n        \"element\": \"DetailsScreen\",\n        \"elementTimeout\": 10000,\n        \"noErrors\": true\n      }\n    },\n    {\n      \"name\": \"Invalid content ID\",\n      \"contentId\": \"DOES_NOT_EXIST\",\n      \"mediaType\": \"movie\",\n      \"expect\": {\n        \"noPlayback\": true,\n        \"noErrors\": true\n      }\n    },\n    {\n      \"name\": \"Home screen (no content ID)\",\n      \"contentId\": \"\",\n      \"expect\": {\n        \"element\": \"HomePage\",\n        \"elementTimeout\": 10000\n      }\n    }\n  ]\n}\n```\n\nConfig file resolution order:\n1. Path passed as CLI argument\n2. `deeplinks.json` in cwd\n3. `.roku-deeplinks.json` in cwd\n4. `roku-deeplink` key in `package.json`\n\n### Test case options\n\n| Field | Type | Description |\n|---|---|---|\n| `name` | string | Test name for reports |\n| `contentId` | string | Content ID to deep link to (empty string for home) |\n| `mediaType` | string? | Media type (`movie`, `series`, `episode`, `shortFormVideo`, etc.) |\n| `params` | object? | Extra key-value params passed to the deep link |\n| `coldStart` | boolean? | Force-close the app before deep linking |\n\n### Expectations\n\n| Field | Type | Description |\n|---|---|---|\n| `playback` | boolean | Expect media playback to start |\n| `playbackTimeout` | number | Max ms to wait for playback (default 15000) |\n| `noPlayback` | boolean | Expect playback to NOT be active |\n| `element` | string | UI element selector to wait for |\n| `text` | string | Text content to wait for (uses `element` as container) |\n| `focused` | string | Element selector that should have focus |\n| `elementTimeout` | number | Max ms to wait for element/text/focus (default 10000) |\n| `noErrors` | boolean | Check console for BrightScript errors |\n\n## CLI\n\n```bash\n# Run all tests from config\nroku-deeplink run ./deeplinks.json --device 192.168.0.30\n\n# Override channel ID\nroku-deeplink run ./deeplinks.json --device 192.168.0.30 --channel 12345\n\n# Output formats\nroku-deeplink run ./deeplinks.json --format json\nroku-deeplink run ./deeplinks.json --format junit\n\n# Save results to file\nroku-deeplink run ./deeplinks.json --output results.xml --format junit\n\n# Adjust timing\nroku-deeplink run ./deeplinks.json --settle 3000 --timeout 30000\n\n# Skip optional checks\nroku-deeplink run ./deeplinks.json --no-home --no-console\n\n# Run a single quick test\nroku-deeplink test --content-id MOVIE_001 --media-type movie --expect-playback --device 192.168.0.30\n\n# Generate template config\nroku-deeplink init\nroku-deeplink init --output my-tests.json\n```\n\n### Environment variables\n\n| Variable | Description |\n|---|---|\n| `ROKU_DEVICE_IP` | Default device IP (used when `--device` is omitted) |\n\n## Library API\n\n```typescript\nimport { DeepLinkRunner } from '@danecodes/roku-deeplink';\n\nconst runner = new DeepLinkRunner('192.168.0.30', {\n  channelId: 'dev',\n  playbackTimeout: 15000,\n  elementTimeout: 10000,\n  settleDuration: 2000,\n  goHomeBetween: true,\n  consoleCheck: true,\n});\n\n// Run all tests\nconst results = await runner.runAll(tests);\n\n// Run a single test\nconst result = await runner.run({\n  name: 'Movie playback',\n  contentId: 'MOVIE_001',\n  mediaType: 'movie',\n  expect: { playback: true },\n});\n\n// Format results\nconsole.log(runner.formatReport(results));  // human-readable text\nrunner.toJSON(results);                     // JSON string\nrunner.toJUnit(results);                    // JUnit XML string\n```\n\n### Config utilities\n\n```typescript\nimport { loadConfig, validateConfig, generateTemplate } from '@danecodes/roku-deeplink';\n\n// Load from file (with fallback resolution)\nconst config = await loadConfig('./deeplinks.json');\n\n// Validate a raw object\nconst validated = validateConfig(someObject);\n\n// Generate a starter config\nconst template = generateTemplate();\n```\n\n## Test execution flow\n\nFor each test case:\n\n1. **Go home** — `closeApp()`, wait 1s (skipped if `goHomeBetween: false`)\n2. **Deep link** — `deepLink(channelId, contentId, mediaType)`\n3. **Settle** — wait `settleDuration` ms for the app to load\n4. **Check expectations** in order:\n   - `playback` — poll `queryMediaPlayer()` until state is `\"play\"`\n   - `noPlayback` — verify player is NOT in `\"play\"` state\n   - `element` — `waitForElement()` with the given selector\n   - `text` — `waitForText()` for content within a container\n   - `focused` — `waitForFocus()` on the given selector\n5. **Console check** — `readConsole()` + `parseConsoleForIssues()`, fail if errors found\n\nIf any step fails, the test stops early and reports the failure.\n\n## Roku certification context\n\nRoku requires these deep link behaviors for certification:\n\n- App launch must complete within **15 seconds** of deep link\n- Playback must initiate within **8 seconds** of app launch for playable content\n- Deep links to invalid content must show an error/fallback, **not crash**\n- Deep links with no content ID should launch to the **home screen**\n- App must handle deep links from **cold start** and while **already running**\n- Must support `contentId` and `mediaType` parameters at minimum\n\nThe default timeouts in this tool reflect these requirements. Use the `coldStart` option on individual test cases to verify both cold and warm start behavior.\n\n## CI integration\n\nThe CLI exits with code 1 when any test fails, and supports JUnit XML output for CI systems:\n\n```bash\nroku-deeplink run deeplinks.json --device $ROKU_IP --format junit --output results.xml\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-8feee39b1e3c022c6ee9aad0378abacd"}