@putdotio/rokit 1.2.0 → 1.4.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/README.md +21 -2
- package/dist/index.d.mts +7 -1
- package/dist/index.mjs +2 -2
- package/dist/rokit.mjs +174 -53
- package/dist/{roku-DxlYPbY1.mjs → roku-7hiM97p0.mjs} +32 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,12 +42,19 @@ pnpm exec rokit press --delay-ms 250 Right Select
|
|
|
42
42
|
pnpm exec rokit query /query/active-app
|
|
43
43
|
pnpm exec rokit wait-node videoPlayerScreen visible
|
|
44
44
|
pnpm exec rokit screenshot artifacts/live/player.png
|
|
45
|
+
pnpm exec rokit --json active-app
|
|
45
46
|
```
|
|
46
47
|
|
|
47
48
|
App-specific scenario scripts can also import the generic helpers:
|
|
48
49
|
|
|
49
50
|
```ts
|
|
50
|
-
import {
|
|
51
|
+
import {
|
|
52
|
+
assertSceneGraphNode,
|
|
53
|
+
pressKey,
|
|
54
|
+
querySceneGraph,
|
|
55
|
+
readNamedNodeTranslation,
|
|
56
|
+
type RokuContext,
|
|
57
|
+
} from "@putdotio/rokit";
|
|
51
58
|
|
|
52
59
|
const target = process.env.ROKIT_TARGET;
|
|
53
60
|
|
|
@@ -62,8 +69,9 @@ const context: RokuContext = {
|
|
|
62
69
|
};
|
|
63
70
|
|
|
64
71
|
await pressKey(context, "Info");
|
|
72
|
+
const xml = await querySceneGraph(context, { attempts: 3 });
|
|
65
73
|
await assertSceneGraphNode(context, "videoPlayerScreen", { state: "visible" });
|
|
66
|
-
|
|
74
|
+
console.log(readNamedNodeTranslation(xml, "videoPlayerScreen"));
|
|
67
75
|
```
|
|
68
76
|
|
|
69
77
|
## Commands
|
|
@@ -84,6 +92,16 @@ rokit install <zip-path>
|
|
|
84
92
|
rokit --version
|
|
85
93
|
```
|
|
86
94
|
|
|
95
|
+
Global options:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
rokit --json <command>
|
|
99
|
+
rokit --output json <command>
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
JSON mode wraps command output as `{ "status": "ok", "command": "...", ... }`
|
|
103
|
+
and reports failures as `{ "status": "failed", "error": { "message": "..." } }`.
|
|
104
|
+
|
|
87
105
|
- `check` confirms the Roku ECP endpoint responds and the developer installer
|
|
88
106
|
is reachable.
|
|
89
107
|
- `device-info` prints enhanced Roku device metadata as JSON.
|
|
@@ -130,6 +148,7 @@ tokens, and app-specific media identifiers do not belong in git.
|
|
|
130
148
|
- remote keypresses
|
|
131
149
|
- raw ECP queries
|
|
132
150
|
- SceneGraph state queries and named-node assertions
|
|
151
|
+
- SceneGraph attribute, numeric geometry, bounds, and translation readers
|
|
133
152
|
- screenshots
|
|
134
153
|
|
|
135
154
|
App repositories should keep their own scenario commands for product behavior,
|
package/dist/index.d.mts
CHANGED
|
@@ -2,6 +2,8 @@ import * as rokuDeploy from "roku-deploy";
|
|
|
2
2
|
|
|
3
3
|
//#region src/scenegraph.d.ts
|
|
4
4
|
type NodeState = "absent" | "hidden" | "visible";
|
|
5
|
+
type SceneGraphBounds = readonly [x: number, y: number, width: number, height: number];
|
|
6
|
+
type SceneGraphPoint = readonly [x: number, y: number];
|
|
5
7
|
type NodeExpectation = {
|
|
6
8
|
readonly state: NodeState;
|
|
7
9
|
readonly text?: string;
|
|
@@ -11,10 +13,14 @@ type NodeExpectation = {
|
|
|
11
13
|
};
|
|
12
14
|
declare const readNamedNodeAttributes: (xml: string, nodeName: string) => string | undefined;
|
|
13
15
|
declare const readNamedNodeAttribute: (xml: string, nodeName: string, attributeName: string) => string | undefined;
|
|
16
|
+
declare const readNamedNodeNumber: (xml: string, nodeName: string, attributeName: string) => number | undefined;
|
|
17
|
+
declare const readNamedNodeBounds: (xml: string, nodeName: string) => SceneGraphBounds | undefined;
|
|
18
|
+
declare const readNamedNodeTranslation: (xml: string, nodeName: string) => SceneGraphPoint | undefined;
|
|
14
19
|
declare const isNamedNodeVisible: (xml: string, nodeName: string) => boolean;
|
|
15
20
|
declare const assertNamedNode: (xml: string, nodeName: string, expectation: NodeExpectation) => void;
|
|
16
21
|
declare const assertNamedNodeState: (xml: string, nodeName: string, state: NodeState) => void;
|
|
17
22
|
declare const assertNamedNodeText: (xml: string, nodeName: string, expectedText: string) => void;
|
|
23
|
+
declare const parseSceneGraphNumberList: (value: string) => number[];
|
|
18
24
|
//#endregion
|
|
19
25
|
//#region src/xml.d.ts
|
|
20
26
|
type ActiveApp = {
|
|
@@ -64,4 +70,4 @@ declare const takeScreenshot: (context: RokuContext & {
|
|
|
64
70
|
}, outputPath: string) => Promise<string>;
|
|
65
71
|
declare const validateRemoteKey: (key: string) => void;
|
|
66
72
|
//#endregion
|
|
67
|
-
export { type ActiveApp, type DeviceSummary, type NodeExpectation, type NodeState, type RemoteKey, type RetryOptions, type RokuContext, assertNamedNode, assertNamedNodeState, assertNamedNodeText, assertSceneGraphNode, checkDevice, getDeviceInfo, installPackage, isNamedNodeVisible, launchApp, pressKey, queryActiveApp, queryEcp, querySceneGraph, readActiveApp, readNamedNodeAttribute, readNamedNodeAttributes, readXmlAttribute, readXmlTag, takeScreenshot, validateRemoteKey, waitForActiveApp, waitForSceneGraphNode };
|
|
73
|
+
export { type ActiveApp, type DeviceSummary, type NodeExpectation, type NodeState, type RemoteKey, type RetryOptions, type RokuContext, type SceneGraphBounds, type SceneGraphPoint, assertNamedNode, assertNamedNodeState, assertNamedNodeText, assertSceneGraphNode, checkDevice, getDeviceInfo, installPackage, isNamedNodeVisible, launchApp, parseSceneGraphNumberList, pressKey, queryActiveApp, queryEcp, querySceneGraph, readActiveApp, readNamedNodeAttribute, readNamedNodeAttributes, readNamedNodeBounds, readNamedNodeNumber, readNamedNodeTranslation, readXmlAttribute, readXmlTag, takeScreenshot, validateRemoteKey, waitForActiveApp, waitForSceneGraphNode };
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { assertNamedNode, assertNamedNodeState, assertNamedNodeText, assertSceneGraphNode, checkDevice, getDeviceInfo, installPackage, isNamedNodeVisible, launchApp, pressKey, queryActiveApp, queryEcp, querySceneGraph, readActiveApp, readNamedNodeAttribute, readNamedNodeAttributes, readXmlAttribute, readXmlTag, takeScreenshot, validateRemoteKey, waitForActiveApp, waitForSceneGraphNode };
|
|
1
|
+
import { C as readNamedNodeTranslation, E as readXmlTag, S as readNamedNodeNumber, T as readXmlAttribute, _ as isNamedNodeVisible, a as launchApp, b as readNamedNodeAttributes, c as queryEcp, d as validateRemoteKey, f as waitForActiveApp, g as assertNamedNodeText, h as assertNamedNodeState, i as installPackage, l as querySceneGraph, m as assertNamedNode, n as checkDevice, o as pressKey, p as waitForSceneGraphNode, r as getDeviceInfo, s as queryActiveApp, t as assertSceneGraphNode, u as takeScreenshot, v as parseSceneGraphNumberList, w as readActiveApp, x as readNamedNodeBounds, y as readNamedNodeAttribute } from "./roku-7hiM97p0.mjs";
|
|
2
|
+
export { assertNamedNode, assertNamedNodeState, assertNamedNodeText, assertSceneGraphNode, checkDevice, getDeviceInfo, installPackage, isNamedNodeVisible, launchApp, parseSceneGraphNumberList, pressKey, queryActiveApp, queryEcp, querySceneGraph, readActiveApp, readNamedNodeAttribute, readNamedNodeAttributes, readNamedNodeBounds, readNamedNodeNumber, readNamedNodeTranslation, readXmlAttribute, readXmlTag, takeScreenshot, validateRemoteKey, waitForActiveApp, waitForSceneGraphNode };
|
package/dist/rokit.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as launchApp, c as queryEcp, f as waitForActiveApp, i as installPackage, l as querySceneGraph, n as checkDevice, o as pressKey, p as waitForSceneGraphNode, r as getDeviceInfo, s as queryActiveApp, t as assertSceneGraphNode, u as takeScreenshot } from "./roku-
|
|
2
|
+
import { a as launchApp, c as queryEcp, f as waitForActiveApp, i as installPackage, l as querySceneGraph, n as checkDevice, o as pressKey, p as waitForSceneGraphNode, r as getDeviceInfo, s as queryActiveApp, t as assertSceneGraphNode, u as takeScreenshot } from "./roku-7hiM97p0.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { existsSync, mkdirSync } from "node:fs";
|
|
@@ -23,9 +23,9 @@ const requirePassword = (env) => {
|
|
|
23
23
|
if (!password) return fail("ROKIT_PASSWORD is not set");
|
|
24
24
|
return password;
|
|
25
25
|
};
|
|
26
|
+
var RokitCliError = class extends Error {};
|
|
26
27
|
const fail = (message) => {
|
|
27
|
-
|
|
28
|
-
process.exit(1);
|
|
28
|
+
throw new RokitCliError(message);
|
|
29
29
|
};
|
|
30
30
|
const formatErrorMessage = (error) => {
|
|
31
31
|
if (error instanceof Error) return error.message;
|
|
@@ -42,100 +42,212 @@ const parseTimeout = (value) => {
|
|
|
42
42
|
//#region src/cli.ts
|
|
43
43
|
const packageJson = createRequire(import.meta.url)("../package.json");
|
|
44
44
|
const main = async (argv = process.argv.slice(2)) => {
|
|
45
|
-
|
|
46
|
-
if (!firstArg || firstArg === "--help" || firstArg === "-h") {
|
|
47
|
-
printHelp();
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
if (firstArg === "--version" || firstArg === "-v") {
|
|
51
|
-
console.log(packageJson.version);
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
loadLocalEnv();
|
|
55
|
-
const env = loadEnv();
|
|
56
|
-
const target = requireTarget(env);
|
|
57
|
-
const context = {
|
|
58
|
-
password: env.password,
|
|
59
|
-
target,
|
|
60
|
-
timeoutMs: env.timeoutMs,
|
|
61
|
-
username: env.username
|
|
62
|
-
};
|
|
63
|
-
const command = parseCommand(argv);
|
|
45
|
+
let outputMode = inferOutputMode(argv);
|
|
64
46
|
try {
|
|
65
|
-
|
|
47
|
+
const options = parseGlobalOptions(argv);
|
|
48
|
+
outputMode = options.outputMode;
|
|
49
|
+
const firstArg = options.args[0];
|
|
50
|
+
if (!firstArg || firstArg === "--help" || firstArg === "-h") {
|
|
51
|
+
printHelp();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (firstArg === "--version" || firstArg === "-v") {
|
|
55
|
+
console.log(packageJson.version);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
loadLocalEnv();
|
|
59
|
+
const env = loadEnv();
|
|
60
|
+
const target = requireTarget(env);
|
|
61
|
+
const result = await runCommand({
|
|
62
|
+
password: env.password,
|
|
63
|
+
target,
|
|
64
|
+
timeoutMs: env.timeoutMs,
|
|
65
|
+
username: env.username
|
|
66
|
+
}, parseCommand(options.args));
|
|
67
|
+
printResult(options.outputMode, result);
|
|
66
68
|
} catch (error) {
|
|
67
|
-
|
|
69
|
+
printError(outputMode, formatErrorMessage(error));
|
|
70
|
+
process.exitCode = 1;
|
|
68
71
|
}
|
|
69
72
|
};
|
|
70
73
|
const runCommand = async (context, command) => {
|
|
71
74
|
if (command.name === "check") {
|
|
72
75
|
const summary = await checkDevice(context);
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
76
|
+
return {
|
|
77
|
+
command: command.name,
|
|
78
|
+
data: summary,
|
|
79
|
+
message: [
|
|
80
|
+
`device: ${summary.name} (${summary.model})`,
|
|
81
|
+
`ecp: ${summary.ecp}`,
|
|
82
|
+
`developer installer HTTP status: ${summary.installerStatus}`
|
|
83
|
+
].join("\n"),
|
|
84
|
+
status: "ok"
|
|
85
|
+
};
|
|
81
86
|
}
|
|
87
|
+
if (command.name === "device-info") return {
|
|
88
|
+
command: command.name,
|
|
89
|
+
data: await getDeviceInfo(context),
|
|
90
|
+
status: "ok"
|
|
91
|
+
};
|
|
82
92
|
if (command.name === "active-app") {
|
|
83
93
|
const app = await queryActiveApp(context);
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
return {
|
|
95
|
+
command: command.name,
|
|
96
|
+
data: app,
|
|
97
|
+
message: `active app: ${app.id} ${app.name} ${app.version}`.trim(),
|
|
98
|
+
status: "ok"
|
|
99
|
+
};
|
|
86
100
|
}
|
|
87
101
|
if (command.name === "wait-active") {
|
|
88
102
|
const app = await waitForActiveApp(context, command.appId, command.timeoutMs);
|
|
89
|
-
|
|
90
|
-
|
|
103
|
+
return {
|
|
104
|
+
command: command.name,
|
|
105
|
+
data: app,
|
|
106
|
+
message: `active app: ${app.id} ${app.name} ${app.version}`.trim(),
|
|
107
|
+
status: "ok"
|
|
108
|
+
};
|
|
91
109
|
}
|
|
92
110
|
if (command.name === "launch") {
|
|
93
111
|
const app = await launchApp(context, command.args.appId, command.args.params);
|
|
94
|
-
|
|
95
|
-
|
|
112
|
+
return {
|
|
113
|
+
command: command.name,
|
|
114
|
+
data: app,
|
|
115
|
+
message: `launched: ${app.id} ${app.name} ${app.version}`.trim(),
|
|
116
|
+
status: "ok"
|
|
117
|
+
};
|
|
96
118
|
}
|
|
97
119
|
if (command.name === "press") {
|
|
120
|
+
const pressed = [];
|
|
98
121
|
for (const [index, key] of command.args.keys.entries()) {
|
|
99
122
|
if (index > 0 && command.args.delayMs > 0) await sleep(command.args.delayMs);
|
|
100
123
|
await pressKey(context, key);
|
|
101
|
-
|
|
124
|
+
pressed.push(key);
|
|
102
125
|
}
|
|
103
|
-
return
|
|
126
|
+
return {
|
|
127
|
+
command: command.name,
|
|
128
|
+
data: {
|
|
129
|
+
delayMs: command.args.delayMs,
|
|
130
|
+
keys: pressed
|
|
131
|
+
},
|
|
132
|
+
message: pressed.map((key) => `pressed: ${key}`).join("\n"),
|
|
133
|
+
status: "ok"
|
|
134
|
+
};
|
|
104
135
|
}
|
|
105
136
|
if (command.name === "query") {
|
|
106
|
-
|
|
107
|
-
return
|
|
137
|
+
const body = await queryEcp(context, command.path);
|
|
138
|
+
return {
|
|
139
|
+
command: command.name,
|
|
140
|
+
data: {
|
|
141
|
+
body,
|
|
142
|
+
path: command.path
|
|
143
|
+
},
|
|
144
|
+
message: body,
|
|
145
|
+
status: "ok"
|
|
146
|
+
};
|
|
108
147
|
}
|
|
109
148
|
if (command.name === "sgnodes") {
|
|
110
|
-
|
|
111
|
-
return
|
|
149
|
+
const body = await querySceneGraph(context);
|
|
150
|
+
return {
|
|
151
|
+
command: command.name,
|
|
152
|
+
data: { body },
|
|
153
|
+
message: body,
|
|
154
|
+
status: "ok"
|
|
155
|
+
};
|
|
112
156
|
}
|
|
113
157
|
if (command.name === "assert-node") {
|
|
114
158
|
await assertSceneGraphNode(context, command.args.nodeName, command.args.expectation);
|
|
115
|
-
|
|
116
|
-
|
|
159
|
+
return {
|
|
160
|
+
command: command.name,
|
|
161
|
+
data: formatNodeData(command.args),
|
|
162
|
+
message: `asserted node: ${formatNodeCondition(command.args)}`,
|
|
163
|
+
status: "ok"
|
|
164
|
+
};
|
|
117
165
|
}
|
|
118
166
|
if (command.name === "wait-node") {
|
|
119
167
|
await waitForSceneGraphNode(context, command.args.nodeName, command.args.expectation, command.args.timeoutMs);
|
|
120
|
-
|
|
121
|
-
|
|
168
|
+
return {
|
|
169
|
+
command: command.name,
|
|
170
|
+
data: formatNodeData(command.args),
|
|
171
|
+
message: `matched node: ${formatNodeCondition(command.args)}`,
|
|
172
|
+
status: "ok"
|
|
173
|
+
};
|
|
122
174
|
}
|
|
123
175
|
if (command.name === "screenshot") {
|
|
124
176
|
const password = requirePassword(context);
|
|
125
177
|
mkdirSync(dirname(command.outputPath), { recursive: true });
|
|
126
|
-
|
|
178
|
+
const path = await takeScreenshot({
|
|
127
179
|
...context,
|
|
128
180
|
password
|
|
129
|
-
}, command.outputPath)
|
|
130
|
-
return
|
|
181
|
+
}, command.outputPath);
|
|
182
|
+
return {
|
|
183
|
+
command: command.name,
|
|
184
|
+
data: { path },
|
|
185
|
+
message: `screenshot: ${path}`,
|
|
186
|
+
status: "ok"
|
|
187
|
+
};
|
|
131
188
|
}
|
|
132
189
|
if (command.name === "install") {
|
|
133
190
|
const password = requirePassword(context);
|
|
134
|
-
|
|
191
|
+
const message = await installPackage({
|
|
135
192
|
...context,
|
|
136
193
|
password
|
|
137
|
-
}, command.zipPath)
|
|
194
|
+
}, command.zipPath);
|
|
195
|
+
return {
|
|
196
|
+
command: command.name,
|
|
197
|
+
data: { message },
|
|
198
|
+
message,
|
|
199
|
+
status: "ok"
|
|
200
|
+
};
|
|
138
201
|
}
|
|
202
|
+
throw new Error(`unsupported command: ${command.name}`);
|
|
203
|
+
};
|
|
204
|
+
const parseGlobalOptions = (argv) => {
|
|
205
|
+
const args = [];
|
|
206
|
+
let outputMode = "text";
|
|
207
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
208
|
+
const arg = argv[index];
|
|
209
|
+
if (arg === "--json") {
|
|
210
|
+
outputMode = "json";
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (arg === "--output") {
|
|
214
|
+
const value = argv[index + 1];
|
|
215
|
+
if (value !== "json" && value !== "text") fail("usage: rokit [--json|--output json|--output text] <command>");
|
|
216
|
+
outputMode = value === "json" ? "json" : "text";
|
|
217
|
+
index += 1;
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
args.push(arg);
|
|
221
|
+
}
|
|
222
|
+
return {
|
|
223
|
+
args,
|
|
224
|
+
outputMode
|
|
225
|
+
};
|
|
226
|
+
};
|
|
227
|
+
const inferOutputMode = (argv) => {
|
|
228
|
+
if (argv.includes("--json")) return "json";
|
|
229
|
+
return argv[argv.indexOf("--output") + 1] === "json" ? "json" : "text";
|
|
230
|
+
};
|
|
231
|
+
const printResult = (outputMode, result) => {
|
|
232
|
+
if (outputMode === "json") {
|
|
233
|
+
console.log(JSON.stringify(result, null, 2));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (result.message !== void 0) {
|
|
237
|
+
console.log(result.message);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
241
|
+
};
|
|
242
|
+
const printError = (outputMode, message) => {
|
|
243
|
+
if (outputMode === "json") {
|
|
244
|
+
console.error(JSON.stringify({
|
|
245
|
+
error: { message },
|
|
246
|
+
status: "failed"
|
|
247
|
+
}, null, 2));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
console.error(message);
|
|
139
251
|
};
|
|
140
252
|
const parseCommand = (argv) => {
|
|
141
253
|
const [name, ...args] = argv;
|
|
@@ -265,6 +377,11 @@ const formatNodeCondition = ({ expectation, nodeName }) => {
|
|
|
265
377
|
const suffix = expectation.text === void 0 ? "" : ` text=${expectation.text}`;
|
|
266
378
|
return `${nodeName} ${expectation.state}${suffix}`;
|
|
267
379
|
};
|
|
380
|
+
const formatNodeData = ({ expectation, nodeName, timeoutMs }) => ({
|
|
381
|
+
expectation,
|
|
382
|
+
nodeName,
|
|
383
|
+
timeoutMs
|
|
384
|
+
});
|
|
268
385
|
const sleep = (ms) => new Promise((resolve) => {
|
|
269
386
|
setTimeout(resolve, ms);
|
|
270
387
|
});
|
|
@@ -305,6 +422,10 @@ usage:
|
|
|
305
422
|
rokit install <zip-path>
|
|
306
423
|
rokit --version
|
|
307
424
|
|
|
425
|
+
global options:
|
|
426
|
+
--json
|
|
427
|
+
--output json | text
|
|
428
|
+
|
|
308
429
|
environment:
|
|
309
430
|
ROKIT_TARGET=<roku-ip>
|
|
310
431
|
ROKIT_PASSWORD=<developer-mode-password>
|
|
@@ -28,6 +28,36 @@ const readNamedNodeAttribute = (xml, nodeName, attributeName) => {
|
|
|
28
28
|
if (!attributes) return;
|
|
29
29
|
return readXmlAttribute(attributes, attributeName);
|
|
30
30
|
};
|
|
31
|
+
const readNamedNodeNumber = (xml, nodeName, attributeName) => {
|
|
32
|
+
const value = readNamedNodeAttribute(xml, nodeName, attributeName);
|
|
33
|
+
if (value !== void 0) {
|
|
34
|
+
const parsed = Number(value);
|
|
35
|
+
return Number.isFinite(parsed) ? parsed : void 0;
|
|
36
|
+
}
|
|
37
|
+
if (attributeName !== "width" && attributeName !== "height") return;
|
|
38
|
+
const bounds = readNamedNodeBounds(xml, nodeName);
|
|
39
|
+
if (!bounds) return;
|
|
40
|
+
return attributeName === "width" ? bounds[2] : bounds[3];
|
|
41
|
+
};
|
|
42
|
+
const readNamedNodeBounds = (xml, nodeName) => {
|
|
43
|
+
const bounds = readNamedNodeAttribute(xml, nodeName, "bounds");
|
|
44
|
+
if (!bounds) return;
|
|
45
|
+
const parts = parseSceneGraphNumberList(bounds);
|
|
46
|
+
if (parts.length < 4) return;
|
|
47
|
+
return [
|
|
48
|
+
parts[0],
|
|
49
|
+
parts[1],
|
|
50
|
+
parts[2],
|
|
51
|
+
parts[3]
|
|
52
|
+
];
|
|
53
|
+
};
|
|
54
|
+
const readNamedNodeTranslation = (xml, nodeName) => {
|
|
55
|
+
const translation = readNamedNodeAttribute(xml, nodeName, "translation");
|
|
56
|
+
if (!translation) return;
|
|
57
|
+
const parts = parseSceneGraphNumberList(translation);
|
|
58
|
+
if (parts.length < 2) return;
|
|
59
|
+
return [parts[0], parts[1]];
|
|
60
|
+
};
|
|
31
61
|
const isNamedNodeVisible = (xml, nodeName) => {
|
|
32
62
|
const attributes = readNamedNodeAttributes(xml, nodeName);
|
|
33
63
|
return attributes !== void 0 && !attributes.includes("visible=\"false\"");
|
|
@@ -59,6 +89,7 @@ const assertNamedNodeAttribute = (xml, nodeName, attributeName, expectedValue) =
|
|
|
59
89
|
if (value !== expectedValue) throw new Error(`expected "${nodeName}" ${attributeName} "${expectedValue}", got "${value ?? "missing"}"`);
|
|
60
90
|
};
|
|
61
91
|
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
92
|
+
const parseSceneGraphNumberList = (value) => value.replace(/[[\]{}]/g, "").split(",").map((part) => Number(part.trim())).filter((part) => Number.isFinite(part));
|
|
62
93
|
//#endregion
|
|
63
94
|
//#region src/roku.ts
|
|
64
95
|
const ecpPort = 8060;
|
|
@@ -227,4 +258,4 @@ const sleep = (ms) => new Promise((resolve) => {
|
|
|
227
258
|
});
|
|
228
259
|
const formatErrorMessage = (error) => error instanceof Error ? error.message : String(error);
|
|
229
260
|
//#endregion
|
|
230
|
-
export { readXmlTag as S, isNamedNodeVisible as _, launchApp as a,
|
|
261
|
+
export { readNamedNodeTranslation as C, readXmlTag as E, readNamedNodeNumber as S, readXmlAttribute as T, isNamedNodeVisible as _, launchApp as a, readNamedNodeAttributes as b, queryEcp as c, validateRemoteKey as d, waitForActiveApp as f, assertNamedNodeText as g, assertNamedNodeState as h, installPackage as i, querySceneGraph as l, assertNamedNode as m, checkDevice as n, pressKey as o, waitForSceneGraphNode as p, getDeviceInfo as r, queryActiveApp as s, assertSceneGraphNode as t, takeScreenshot as u, parseSceneGraphNumberList as v, readActiveApp as w, readNamedNodeBounds as x, readNamedNodeAttribute as y };
|