@oniroproject/core 0.11.0 → 0.13.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 +12 -6
- package/dist/index.cjs +463 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +217 -6
- package/dist/index.d.ts +217 -6
- package/dist/index.js +447 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -171,7 +171,8 @@ var ALL_SDKS = [
|
|
|
171
171
|
{ version: "5.1.0", api: "18", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
172
172
|
{ version: "5.1.1", api: "19", tarballStrip: { linuxWindows: 1, darwin: 3 } },
|
|
173
173
|
{ version: "6.0", api: "20", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
174
|
-
{ version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } }
|
|
174
|
+
{ version: "6.1", api: "23", tarballStrip: { linuxWindows: 0, darwin: 3 } },
|
|
175
|
+
{ version: "7.0", api: "26", tarballStrip: { linuxWindows: 1, darwin: 3 }, archiveSuffix: "_20260829" }
|
|
175
176
|
];
|
|
176
177
|
var OHOS_URL_BASE = "https://repo.huaweicloud.com/openharmony/os";
|
|
177
178
|
|
|
@@ -187,18 +188,20 @@ function getOsFolder() {
|
|
|
187
188
|
function getSdkFilename(version) {
|
|
188
189
|
const platform7 = os2.platform();
|
|
189
190
|
const v = version ?? ALL_SDKS[ALL_SDKS.length - 1].version;
|
|
190
|
-
const
|
|
191
|
+
const release = ALL_SDKS.find((r) => r.version === v);
|
|
192
|
+
const strips = release?.tarballStrip ?? { linuxWindows: 1, darwin: 3 };
|
|
193
|
+
const archive = (base) => `${base}${release?.archiveSuffix ?? ""}.tar.gz`;
|
|
191
194
|
if (platform7 === "linux") {
|
|
192
|
-
return { filename: "ohos-sdk-windows_linux-public
|
|
195
|
+
return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "linux", strip: strips.linuxWindows };
|
|
193
196
|
}
|
|
194
197
|
if (platform7 === "darwin") {
|
|
195
198
|
if (os2.arch() === "arm64") {
|
|
196
|
-
return { filename: "L2-SDK-MAC-M1-PUBLIC
|
|
199
|
+
return { filename: archive("L2-SDK-MAC-M1-PUBLIC"), osFolder: "darwin", strip: strips.darwin };
|
|
197
200
|
}
|
|
198
|
-
return { filename: "ohos-sdk-mac-public
|
|
201
|
+
return { filename: archive("ohos-sdk-mac-public"), osFolder: "darwin", strip: strips.darwin };
|
|
199
202
|
}
|
|
200
203
|
if (platform7 === "win32") {
|
|
201
|
-
return { filename: "ohos-sdk-windows_linux-public
|
|
204
|
+
return { filename: archive("ohos-sdk-windows_linux-public"), osFolder: "windows", strip: strips.linuxWindows };
|
|
202
205
|
}
|
|
203
206
|
throw new UnsupportedPlatformError(platform7);
|
|
204
207
|
}
|
|
@@ -2260,19 +2263,39 @@ async function isReachable(config, deviceSerial) {
|
|
|
2260
2263
|
}
|
|
2261
2264
|
}
|
|
2262
2265
|
var TCP_SERIAL_RE = /^.+:\d+$/;
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2266
|
+
var WEDGED_POLLS_BEFORE_RESTART = 3;
|
|
2267
|
+
function tcpReconnector(config, deviceSerial, logger) {
|
|
2268
|
+
let wedgedPolls = 0;
|
|
2269
|
+
return async () => {
|
|
2270
|
+
if (!deviceSerial || !TCP_SERIAL_RE.test(deviceSerial)) return;
|
|
2271
|
+
let claimsConnected = false;
|
|
2272
|
+
try {
|
|
2273
|
+
const res = await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
|
|
2274
|
+
claimsConnected = /already connected|repeat operation/i.test(`${res.stdout}
|
|
2275
|
+
${res.stderr}`);
|
|
2276
|
+
} catch {
|
|
2277
|
+
}
|
|
2278
|
+
if (!claimsConnected || await isReachable(config, deviceSerial)) {
|
|
2279
|
+
wedgedPolls = 0;
|
|
2280
|
+
return;
|
|
2281
|
+
}
|
|
2282
|
+
if (++wedgedPolls < WEDGED_POLLS_BEFORE_RESTART) return;
|
|
2283
|
+
wedgedPolls = 0;
|
|
2284
|
+
logger.warn(`hdc reports ${deviceSerial} as connected but it does not answer; restarting the hdc server.`);
|
|
2285
|
+
try {
|
|
2286
|
+
await hdcExec({ config, args: ["start", "-r"], timeoutMs: 15e3, logger });
|
|
2287
|
+
await hdcExec({ config, args: ["tconn", deviceSerial], timeoutMs: 5e3, logger });
|
|
2288
|
+
} catch {
|
|
2289
|
+
}
|
|
2290
|
+
};
|
|
2269
2291
|
}
|
|
2270
2292
|
async function waitForBundle(opts) {
|
|
2271
2293
|
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2272
2294
|
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
2295
|
+
const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
|
|
2273
2296
|
await waitForCondition({
|
|
2274
2297
|
probe: async () => {
|
|
2275
|
-
await reconnectIfTcp(
|
|
2298
|
+
await reconnectIfTcp();
|
|
2276
2299
|
return await findRunningProcess({ config: opts.config, bundle: opts.bundle, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
2277
2300
|
},
|
|
2278
2301
|
timeoutMs,
|
|
@@ -2285,9 +2308,10 @@ async function waitForBundle(opts) {
|
|
|
2285
2308
|
async function waitForBoot(opts) {
|
|
2286
2309
|
const logger = scopedLogger(opts.logger ?? noopLogger, "hdc");
|
|
2287
2310
|
const timeoutMs = opts.timeoutMs ?? 18e4;
|
|
2311
|
+
const reconnectIfTcp = tcpReconnector(opts.config, opts.deviceSerial, logger);
|
|
2288
2312
|
await waitForCondition({
|
|
2289
2313
|
probe: async () => {
|
|
2290
|
-
await reconnectIfTcp(
|
|
2314
|
+
await reconnectIfTcp();
|
|
2291
2315
|
if (!await isReachable(opts.config, opts.deviceSerial)) return false;
|
|
2292
2316
|
if (!opts.untilPidOf) return true;
|
|
2293
2317
|
return await findRunningProcess({ config: opts.config, bundle: opts.untilPidOf, deviceSerial: opts.deviceSerial, timeoutMs: 8e3 }) !== null;
|
|
@@ -2391,6 +2415,55 @@ async function captureBurst(opts) {
|
|
|
2391
2415
|
}
|
|
2392
2416
|
|
|
2393
2417
|
// src/hdc/input.ts
|
|
2418
|
+
var UITEST_KEY_NAMES = ["Back", "Home", "Power"];
|
|
2419
|
+
var KEY_IDS = {
|
|
2420
|
+
back: 2,
|
|
2421
|
+
home: 1,
|
|
2422
|
+
power: 18,
|
|
2423
|
+
volumeup: 16,
|
|
2424
|
+
volumedown: 17,
|
|
2425
|
+
up: 2012,
|
|
2426
|
+
down: 2013,
|
|
2427
|
+
left: 2014,
|
|
2428
|
+
right: 2015,
|
|
2429
|
+
alt: 2045,
|
|
2430
|
+
shift: 2047,
|
|
2431
|
+
tab: 2049,
|
|
2432
|
+
space: 2050,
|
|
2433
|
+
enter: 2054,
|
|
2434
|
+
delete: 2055,
|
|
2435
|
+
backspace: 2055,
|
|
2436
|
+
pageup: 2068,
|
|
2437
|
+
pagedown: 2069,
|
|
2438
|
+
escape: 2070,
|
|
2439
|
+
esc: 2070,
|
|
2440
|
+
forwarddelete: 2071,
|
|
2441
|
+
ctrl: 2072,
|
|
2442
|
+
meta: 2076,
|
|
2443
|
+
movehome: 2081,
|
|
2444
|
+
moveend: 2082
|
|
2445
|
+
};
|
|
2446
|
+
function keyId(name) {
|
|
2447
|
+
const k = name.trim().toLowerCase();
|
|
2448
|
+
if (/^\d+$/.test(k)) return Number(k);
|
|
2449
|
+
if (/^[a-z]$/.test(k)) return 2017 + (k.charCodeAt(0) - 97);
|
|
2450
|
+
const id = KEY_IDS[k];
|
|
2451
|
+
if (id === void 0) {
|
|
2452
|
+
throw new OniroError(
|
|
2453
|
+
`Unknown key "${name}". Use a numeric KeyCode id, a letter, or one of: ${Object.keys(KEY_IDS).join(", ")}. Combine up to three with "+", e.g. Ctrl+A.`
|
|
2454
|
+
);
|
|
2455
|
+
}
|
|
2456
|
+
return id;
|
|
2457
|
+
}
|
|
2458
|
+
function resolveKeyEvent(key) {
|
|
2459
|
+
const spec = String(key).trim();
|
|
2460
|
+
if (spec === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
2461
|
+
const native = UITEST_KEY_NAMES.find((n) => n.toLowerCase() === spec.toLowerCase());
|
|
2462
|
+
if (native) return native;
|
|
2463
|
+
const parts = spec.includes("+") ? spec.split("+") : spec.split(/\s+/);
|
|
2464
|
+
if (parts.length > 3) throw new OniroError(`uitest injects at most three keys at once; got "${spec}".`);
|
|
2465
|
+
return parts.map(keyId).join(" ");
|
|
2466
|
+
}
|
|
2394
2467
|
function reqNum(value, name, type) {
|
|
2395
2468
|
if (value === void 0 || Number.isNaN(value)) {
|
|
2396
2469
|
throw new OniroError(`send input "${type}" requires a numeric ${name}.`);
|
|
@@ -2411,7 +2484,7 @@ function buildInputCommand(opts) {
|
|
|
2411
2484
|
}
|
|
2412
2485
|
case "keyEvent":
|
|
2413
2486
|
if (opts.key === void 0 || opts.key === "") throw new OniroError('send input "keyEvent" requires a key.');
|
|
2414
|
-
return `uitest uiInput keyEvent ${opts.key}`;
|
|
2487
|
+
return `uitest uiInput keyEvent ${resolveKeyEvent(opts.key)}`;
|
|
2415
2488
|
case "inputText": {
|
|
2416
2489
|
const t = String(opts.text ?? "").replace(/'/g, `'\\''`);
|
|
2417
2490
|
if (opts.pxX !== void 0 && opts.pxY !== void 0) {
|
|
@@ -2509,6 +2582,48 @@ import * as fs20 from "fs";
|
|
|
2509
2582
|
import * as os10 from "os";
|
|
2510
2583
|
import * as path20 from "path";
|
|
2511
2584
|
import { randomBytes as randomBytes3 } from "crypto";
|
|
2585
|
+
|
|
2586
|
+
// src/hdc/ime.ts
|
|
2587
|
+
var INPUT_METHOD_SA_ID = 3703;
|
|
2588
|
+
var IME_BUNDLE_FALLBACK = /inputmethod|keyboard|(^|\.)ime(\.|$)/i;
|
|
2589
|
+
function parseImeBundles(output) {
|
|
2590
|
+
const current = [];
|
|
2591
|
+
const others = [];
|
|
2592
|
+
const entry = /\{[^{}]*"ime"\s*:\s*"([^"/]+)\/[^"]*"[^{}]*\}/g;
|
|
2593
|
+
for (const m of output.matchAll(entry)) {
|
|
2594
|
+
const bundle = m[1];
|
|
2595
|
+
const list = /"isCurrentIme"\s*:\s*"true"/.test(m[0]) ? current : others;
|
|
2596
|
+
if (!current.includes(bundle) && !others.includes(bundle)) list.push(bundle);
|
|
2597
|
+
}
|
|
2598
|
+
return [...current, ...others];
|
|
2599
|
+
}
|
|
2600
|
+
async function getImeBundles(config, opts = {}) {
|
|
2601
|
+
try {
|
|
2602
|
+
const res = await shell({
|
|
2603
|
+
config,
|
|
2604
|
+
command: `hidumper -s ${INPUT_METHOD_SA_ID} -a '-a'`,
|
|
2605
|
+
deviceSerial: opts.deviceSerial,
|
|
2606
|
+
timeoutMs: 1e4,
|
|
2607
|
+
logger: opts.logger
|
|
2608
|
+
});
|
|
2609
|
+
if (res.code === 0) return parseImeBundles(res.stdout);
|
|
2610
|
+
} catch (err) {
|
|
2611
|
+
opts.logger?.debug(`could not list input methods: ${err instanceof Error ? err.message : String(err)}`);
|
|
2612
|
+
}
|
|
2613
|
+
return [];
|
|
2614
|
+
}
|
|
2615
|
+
function findKeyboardWindow(tree, imeBundles) {
|
|
2616
|
+
const isIme = (bundle) => imeBundles.length > 0 ? imeBundles.includes(bundle) : IME_BUNDLE_FALLBACK.test(bundle);
|
|
2617
|
+
const stack = [tree];
|
|
2618
|
+
while (stack.length > 0) {
|
|
2619
|
+
const n = stack.shift();
|
|
2620
|
+
if (n.bundle && isIme(n.bundle)) return n;
|
|
2621
|
+
stack.push(...n.children ?? []);
|
|
2622
|
+
}
|
|
2623
|
+
return null;
|
|
2624
|
+
}
|
|
2625
|
+
|
|
2626
|
+
// src/hdc/dump.ts
|
|
2512
2627
|
function parseBounds(s) {
|
|
2513
2628
|
const m = /^\[(-?\d+),(-?\d+)\]\[(-?\d+),(-?\d+)\]$/.exec(s ?? "");
|
|
2514
2629
|
if (!m) return null;
|
|
@@ -2543,7 +2658,46 @@ var FLAG_MAP = [
|
|
|
2543
2658
|
["scrollable", "scroll"],
|
|
2544
2659
|
["longClickable", "longClick"]
|
|
2545
2660
|
];
|
|
2546
|
-
function
|
|
2661
|
+
function colorAlpha(color) {
|
|
2662
|
+
const m = /^#([0-9a-f]{2})?[0-9a-f]{6}$/i.exec(color ?? "");
|
|
2663
|
+
if (!m) return 0;
|
|
2664
|
+
return m[1] === void 0 ? 255 : parseInt(m[1], 16);
|
|
2665
|
+
}
|
|
2666
|
+
function paintsFill(a) {
|
|
2667
|
+
if (a.visible === "false") return false;
|
|
2668
|
+
if (a.opacity !== void 0 && a.opacity !== "" && !(Number(a.opacity) > 0)) return false;
|
|
2669
|
+
return colorAlpha(a.backgroundColor) > 0;
|
|
2670
|
+
}
|
|
2671
|
+
function findCoveredNodes(root) {
|
|
2672
|
+
const order = [];
|
|
2673
|
+
const fills = [];
|
|
2674
|
+
const visit = (n) => {
|
|
2675
|
+
const a = n.attributes ?? {};
|
|
2676
|
+
const entry = { node: n, bounds: parseBounds(a.bounds), end: 0 };
|
|
2677
|
+
if (entry.bounds && paintsFill(a)) fills.push(order.length);
|
|
2678
|
+
order.push(entry);
|
|
2679
|
+
const kids = (n.children ?? []).map((c, i) => ({ c, i, z: Number(c.attributes?.zIndex) || 0 })).sort((p, q) => p.z - q.z || p.i - q.i);
|
|
2680
|
+
for (const k of kids) visit(k.c);
|
|
2681
|
+
entry.end = order.length;
|
|
2682
|
+
};
|
|
2683
|
+
visit(root);
|
|
2684
|
+
const covered = /* @__PURE__ */ new Set();
|
|
2685
|
+
for (const p of order) {
|
|
2686
|
+
if (!p.bounds) continue;
|
|
2687
|
+
const cx = (p.bounds[0] + p.bounds[2]) / 2;
|
|
2688
|
+
const cy = (p.bounds[1] + p.bounds[3]) / 2;
|
|
2689
|
+
for (const j of fills) {
|
|
2690
|
+
if (j < p.end) continue;
|
|
2691
|
+
const q = order[j].bounds;
|
|
2692
|
+
if (cx >= q[0] && cx < q[2] && cy >= q[1] && cy < q[3]) {
|
|
2693
|
+
covered.add(p.node);
|
|
2694
|
+
break;
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
return covered;
|
|
2699
|
+
}
|
|
2700
|
+
function pruneLayout(node, display, covered) {
|
|
2547
2701
|
const a = node.attributes ?? {};
|
|
2548
2702
|
const px = parseBounds(a.bounds);
|
|
2549
2703
|
const out = {};
|
|
@@ -2561,28 +2715,125 @@ function pruneLayout(node, display) {
|
|
|
2561
2715
|
if (a.type && !STRUCTURAL_TYPES.has(a.type)) out.type = a.type;
|
|
2562
2716
|
if (a.text) out.text = a.text;
|
|
2563
2717
|
if (a.description) out.desc = a.description;
|
|
2718
|
+
if (a.hint) out.hint = a.hint;
|
|
2564
2719
|
if (a.id) out.id = a.id;
|
|
2565
2720
|
if (a.key && a.key !== a.id) out.key = a.key;
|
|
2566
2721
|
if (a.bundleName) out.bundle = a.bundleName;
|
|
2567
2722
|
for (const [attr, short] of FLAG_MAP) {
|
|
2568
2723
|
if (a[attr] === "true") out[short] = true;
|
|
2569
2724
|
}
|
|
2725
|
+
if (a.enabled === "false") out.disabled = true;
|
|
2726
|
+
if (covered?.has(node)) out.covered = true;
|
|
2570
2727
|
const children = [];
|
|
2571
2728
|
for (const c of node.children ?? []) {
|
|
2572
|
-
const pc = pruneLayout(c, display);
|
|
2729
|
+
const pc = pruneLayout(c, display, covered);
|
|
2573
2730
|
if (pc) {
|
|
2574
2731
|
if (Array.isArray(pc)) children.push(...pc);
|
|
2575
2732
|
else children.push(pc);
|
|
2576
2733
|
}
|
|
2577
2734
|
}
|
|
2578
2735
|
const hasSignal = Boolean(
|
|
2579
|
-
out.text || out.desc || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
|
|
2736
|
+
out.text || out.desc || out.hint || out.id || out.key || out.click || out.scroll || out.check || out.longClick || out.bundle || out.type && out.type !== "root"
|
|
2580
2737
|
);
|
|
2581
2738
|
if (!hasSignal && children.length === 0) return null;
|
|
2582
2739
|
if (!hasSignal) return children.length === 1 ? children[0] : children;
|
|
2583
2740
|
if (children.length) out.children = children;
|
|
2584
2741
|
return out;
|
|
2585
2742
|
}
|
|
2743
|
+
function flattenLayout(tree) {
|
|
2744
|
+
const out = [];
|
|
2745
|
+
const visit = (n, window) => {
|
|
2746
|
+
const { children, ...rest } = n;
|
|
2747
|
+
const win = n.bundle ?? window;
|
|
2748
|
+
if (Object.keys(rest).length > 0) out.push({ ...rest, ...win ? { window: win } : {} });
|
|
2749
|
+
for (const c of children ?? []) visit(c, win);
|
|
2750
|
+
};
|
|
2751
|
+
visit(tree, void 0);
|
|
2752
|
+
return out;
|
|
2753
|
+
}
|
|
2754
|
+
function assertLayoutSelector(selector) {
|
|
2755
|
+
if (selector.text === void 0 && selector.id === void 0 && selector.type === void 0) {
|
|
2756
|
+
throw new OniroError("A layout selector needs a text, id, or type.");
|
|
2757
|
+
}
|
|
2758
|
+
if (selector.text !== void 0 && selector.text.trim() === "") {
|
|
2759
|
+
throw new OniroError("A layout selector text must not be blank.");
|
|
2760
|
+
}
|
|
2761
|
+
}
|
|
2762
|
+
function withoutWindow(tree, bundle) {
|
|
2763
|
+
const children = (tree.children ?? []).filter((c) => c.bundle !== bundle).map((c) => withoutWindow(c, bundle));
|
|
2764
|
+
return tree.children ? { ...tree, children } : tree;
|
|
2765
|
+
}
|
|
2766
|
+
function findLayoutNodes(tree, selector) {
|
|
2767
|
+
assertLayoutSelector(selector);
|
|
2768
|
+
const type = selector.type?.toLowerCase();
|
|
2769
|
+
const nodes = flattenLayout(tree).filter(
|
|
2770
|
+
(n) => (type === void 0 || n.type?.toLowerCase() === type) && (selector.id === void 0 || n.id === selector.id || n.key === selector.id)
|
|
2771
|
+
);
|
|
2772
|
+
if (selector.text === void 0) return nodes;
|
|
2773
|
+
const want = selector.text.trim().toLowerCase();
|
|
2774
|
+
const labels = (n) => [n.text, n.desc, n.hint].filter((s) => !!s).map((s) => s.trim().toLowerCase());
|
|
2775
|
+
const exact = nodes.filter((n) => labels(n).some((l) => l === want));
|
|
2776
|
+
if (exact.length > 0) return exact;
|
|
2777
|
+
return nodes.filter((n) => labels(n).some((l) => l.includes(want)));
|
|
2778
|
+
}
|
|
2779
|
+
function describeSelector(s) {
|
|
2780
|
+
const parts = [];
|
|
2781
|
+
if (s.text !== void 0) parts.push(`text "${s.text}"`);
|
|
2782
|
+
if (s.id !== void 0) parts.push(`id "${s.id}"`);
|
|
2783
|
+
if (s.type !== void 0) parts.push(`type ${s.type}`);
|
|
2784
|
+
return parts.join(", ");
|
|
2785
|
+
}
|
|
2786
|
+
function describeLayoutNode(n) {
|
|
2787
|
+
const label = n.text ?? n.desc ?? (n.hint !== void 0 ? `hint: ${n.hint}` : void 0);
|
|
2788
|
+
const at = n.c ? ` at ${n.c[0].toFixed(3)},${n.c[1].toFixed(3)}` : "";
|
|
2789
|
+
return `${n.type ?? "node"}${label !== void 0 ? ` "${label}"` : ""}${n.id ? ` #${n.id}` : ""}${at}`;
|
|
2790
|
+
}
|
|
2791
|
+
var area = (b) => b ? (b[2] - b[0]) * (b[3] - b[1]) : 0;
|
|
2792
|
+
function enclosesTarget(outer, inner) {
|
|
2793
|
+
const b = outer.b;
|
|
2794
|
+
const c = inner.c;
|
|
2795
|
+
if (!b || !c) return false;
|
|
2796
|
+
return c[0] >= b[0] && c[0] <= b[2] && c[1] >= b[1] && c[1] <= b[3] && area(b) > area(inner.b);
|
|
2797
|
+
}
|
|
2798
|
+
var TargetNotFoundError = class extends OniroError {
|
|
2799
|
+
constructor(message, reason) {
|
|
2800
|
+
super(message);
|
|
2801
|
+
this.reason = reason;
|
|
2802
|
+
this.name = "TargetNotFoundError";
|
|
2803
|
+
}
|
|
2804
|
+
reason;
|
|
2805
|
+
};
|
|
2806
|
+
function pickTarget(tree, selector, index) {
|
|
2807
|
+
const matches = findLayoutNodes(tree, selector).filter((n) => n.c !== void 0);
|
|
2808
|
+
const what = describeSelector(selector);
|
|
2809
|
+
if (matches.length === 0) {
|
|
2810
|
+
throw new TargetNotFoundError(`No on-screen element matches ${what}. Run \`oniro-app dump layout --flat\` to see what is there.`, "none");
|
|
2811
|
+
}
|
|
2812
|
+
const visible = matches.filter((n) => !n.covered);
|
|
2813
|
+
if (visible.length === 0) {
|
|
2814
|
+
throw new TargetNotFoundError(
|
|
2815
|
+
`Found ${matches.length} element(s) matching ${what}, but each is covered by an overlay (dialog, keyboard, or another layer): ${matches.map(describeLayoutNode).join("; ")}.`,
|
|
2816
|
+
"covered"
|
|
2817
|
+
);
|
|
2818
|
+
}
|
|
2819
|
+
const clickable = visible.filter((n) => n.click);
|
|
2820
|
+
const preferred = clickable.length > 0 ? clickable : visible;
|
|
2821
|
+
const candidates = preferred.filter((n) => !preferred.some((o) => o !== n && o.click && enclosesTarget(o, n)));
|
|
2822
|
+
if (index !== void 0) {
|
|
2823
|
+
const picked = candidates[index];
|
|
2824
|
+
if (!picked) {
|
|
2825
|
+
throw new TargetNotFoundError(`Index ${index} is out of range: ${candidates.length} element(s) match ${what}.`, "ambiguous");
|
|
2826
|
+
}
|
|
2827
|
+
return picked;
|
|
2828
|
+
}
|
|
2829
|
+
if (candidates.length > 1) {
|
|
2830
|
+
throw new TargetNotFoundError(
|
|
2831
|
+
`${candidates.length} elements match ${what}; pass an index to choose: ` + candidates.map((n, i) => `[${i}] ${describeLayoutNode(n)}`).join("; "),
|
|
2832
|
+
"ambiguous"
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
2835
|
+
return candidates[0];
|
|
2836
|
+
}
|
|
2586
2837
|
async function dumpLayout(opts) {
|
|
2587
2838
|
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
2588
2839
|
const bundleArg = opts.bundleName ? ` -b '${opts.bundleName.replace(/'/g, `'\\''`)}'` : "";
|
|
@@ -2600,9 +2851,13 @@ ${dump.stdout}`);
|
|
|
2600
2851
|
if (opts.prune === false) {
|
|
2601
2852
|
return { display, tree: parsed };
|
|
2602
2853
|
}
|
|
2603
|
-
const prunedRaw = pruneLayout(parsed, display);
|
|
2854
|
+
const prunedRaw = pruneLayout(parsed, display, findCoveredNodes(parsed));
|
|
2604
2855
|
const tree = Array.isArray(prunedRaw) ? { children: prunedRaw } : prunedRaw ?? { children: [] };
|
|
2605
|
-
return { display, tree };
|
|
2856
|
+
if (!opts.detectKeyboard || opts.bundleName) return { display, tree };
|
|
2857
|
+
const imeBundles = await getImeBundles(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
2858
|
+
const kb = findKeyboardWindow(tree, imeBundles);
|
|
2859
|
+
const keyboard = kb ? { shown: true, bundle: kb.bundle, ...kb.b ? { b: kb.b } : {} } : { shown: false };
|
|
2860
|
+
return { display, tree, keyboard };
|
|
2606
2861
|
} finally {
|
|
2607
2862
|
fs20.promises.unlink(local).catch(() => {
|
|
2608
2863
|
});
|
|
@@ -2611,6 +2866,161 @@ ${dump.stdout}`);
|
|
|
2611
2866
|
}
|
|
2612
2867
|
}
|
|
2613
2868
|
|
|
2869
|
+
// src/hdc/uiActions.ts
|
|
2870
|
+
var FOCUS_SETTLE_MS = 400;
|
|
2871
|
+
var KEYBOARD_SETTLE_MS = 400;
|
|
2872
|
+
var sleep = (ms) => new Promise((resolve6) => setTimeout(resolve6, ms));
|
|
2873
|
+
function targetTree(layout) {
|
|
2874
|
+
const kb = layout.keyboard;
|
|
2875
|
+
return kb?.shown && kb.bundle ? withoutWindow(layout.tree, kb.bundle) : layout.tree;
|
|
2876
|
+
}
|
|
2877
|
+
function fractionToPixel(f, size) {
|
|
2878
|
+
return Math.min(size - 1, Math.round(f * size));
|
|
2879
|
+
}
|
|
2880
|
+
function pressBack(opts) {
|
|
2881
|
+
return sendInput({ config: opts.config, type: "keyEvent", key: "Back", deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
2882
|
+
}
|
|
2883
|
+
async function hideKeyboard(opts) {
|
|
2884
|
+
const { keyboard } = await dumpLayout({ ...opts, detectKeyboard: true });
|
|
2885
|
+
if (!keyboard?.shown) return false;
|
|
2886
|
+
await pressBack(opts);
|
|
2887
|
+
await sleep(KEYBOARD_SETTLE_MS);
|
|
2888
|
+
return true;
|
|
2889
|
+
}
|
|
2890
|
+
async function locateTarget(opts) {
|
|
2891
|
+
assertLayoutSelector(opts.selector);
|
|
2892
|
+
const dumpOpts = { config: opts.config, detectKeyboard: true, deviceSerial: opts.deviceSerial, logger: opts.logger };
|
|
2893
|
+
let layout = await dumpLayout(dumpOpts);
|
|
2894
|
+
if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
|
|
2895
|
+
let keyboardHidden = false;
|
|
2896
|
+
let node;
|
|
2897
|
+
let failure;
|
|
2898
|
+
try {
|
|
2899
|
+
node = pickTarget(targetTree(layout), opts.selector, opts.index);
|
|
2900
|
+
} catch (err) {
|
|
2901
|
+
if (!(err instanceof TargetNotFoundError) || err.reason === "ambiguous") throw err;
|
|
2902
|
+
failure = err;
|
|
2903
|
+
}
|
|
2904
|
+
if ((!node || !node.click) && layout.keyboard?.shown && opts.hideKeyboard !== false) {
|
|
2905
|
+
opts.logger?.debug("target not reachable while the keyboard is up; dismissing it and looking again");
|
|
2906
|
+
await pressBack(opts);
|
|
2907
|
+
await sleep(KEYBOARD_SETTLE_MS);
|
|
2908
|
+
keyboardHidden = true;
|
|
2909
|
+
layout = await dumpLayout(dumpOpts);
|
|
2910
|
+
if (!layout.display) throw new OniroError("Could not read the display size to locate the element.");
|
|
2911
|
+
node = pickTarget(targetTree(layout), opts.selector, opts.index);
|
|
2912
|
+
}
|
|
2913
|
+
if (!node) throw failure;
|
|
2914
|
+
if (!node.c) throw new OniroError("The element has no on-screen position.");
|
|
2915
|
+
const x2 = fractionToPixel(node.c[0], layout.display.width);
|
|
2916
|
+
const y = fractionToPixel(node.c[1], layout.display.height);
|
|
2917
|
+
return { node, x: x2, y, display: layout.display, keyboardHidden };
|
|
2918
|
+
}
|
|
2919
|
+
function fraction(value, name) {
|
|
2920
|
+
if (value === void 0) return void 0;
|
|
2921
|
+
if (!(value >= 0 && value <= 1)) throw new OniroError(`${name} must be a fraction between 0 and 1; got ${value}.`);
|
|
2922
|
+
return value;
|
|
2923
|
+
}
|
|
2924
|
+
function pair(a, b, names) {
|
|
2925
|
+
if (a === void 0 && b === void 0) return void 0;
|
|
2926
|
+
if (a === void 0 || b === void 0) throw new OniroError(`Give both ${names}.`);
|
|
2927
|
+
return [a, b];
|
|
2928
|
+
}
|
|
2929
|
+
async function performInput(opts) {
|
|
2930
|
+
const device = { config: opts.config, deviceSerial: opts.deviceSerial, logger: opts.logger };
|
|
2931
|
+
const startFx = pair(fraction(opts.fx, "--fx"), fraction(opts.fy, "--fy"), "--fx and --fy");
|
|
2932
|
+
const startPx = pair(opts.pxX, opts.pxY, "--x and --y");
|
|
2933
|
+
const endFx = pair(fraction(opts.fx2, "--fx2"), fraction(opts.fy2, "--fy2"), "--fx2 and --fy2");
|
|
2934
|
+
const endPx = pair(opts.pxX2, opts.pxY2, "--x2 and --y2");
|
|
2935
|
+
if ([opts.target, startFx, startPx].filter((s) => s !== void 0).length > 1) {
|
|
2936
|
+
throw new OniroError("Give the start point one way: a target, fractions (--fx/--fy), or pixels (--x/--y).");
|
|
2937
|
+
}
|
|
2938
|
+
if (endFx && endPx) throw new OniroError("Give the end point one way: fractions (--fx2/--fy2) or pixels (--x2/--y2).");
|
|
2939
|
+
let display;
|
|
2940
|
+
const toPx = async ([fx, fy]) => {
|
|
2941
|
+
display ??= await getDisplaySize(opts.config, { deviceSerial: opts.deviceSerial, logger: opts.logger });
|
|
2942
|
+
if (!display) throw new OniroError("Could not read the display size to convert fractions to pixels.");
|
|
2943
|
+
return [fractionToPixel(fx, display.width), fractionToPixel(fy, display.height)];
|
|
2944
|
+
};
|
|
2945
|
+
const result = { keyboardHidden: false };
|
|
2946
|
+
let start = startPx;
|
|
2947
|
+
if (opts.target) {
|
|
2948
|
+
const located = await locateTarget({ ...device, selector: opts.target, index: opts.targetIndex });
|
|
2949
|
+
display = located.display;
|
|
2950
|
+
start = [located.x, located.y];
|
|
2951
|
+
result.target = located.node;
|
|
2952
|
+
result.keyboardHidden = located.keyboardHidden;
|
|
2953
|
+
} else if (startFx) {
|
|
2954
|
+
start = await toPx(startFx);
|
|
2955
|
+
}
|
|
2956
|
+
const end = endFx ? await toPx(endFx) : endPx;
|
|
2957
|
+
if (start) [result.x, result.y] = start;
|
|
2958
|
+
const tapToFocus = async () => {
|
|
2959
|
+
if (!start) return;
|
|
2960
|
+
await sendInput({ ...device, type: "click", pxX: start[0], pxY: start[1] });
|
|
2961
|
+
await sleep(FOCUS_SETTLE_MS);
|
|
2962
|
+
};
|
|
2963
|
+
const clearFocused = async () => {
|
|
2964
|
+
await sendInput({ ...device, type: "keyEvent", key: "Ctrl+A" });
|
|
2965
|
+
await sendInput({ ...device, type: "keyEvent", key: "Delete" });
|
|
2966
|
+
};
|
|
2967
|
+
switch (opts.type) {
|
|
2968
|
+
case "hideKeyboard":
|
|
2969
|
+
result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
|
|
2970
|
+
return result;
|
|
2971
|
+
case "clearText":
|
|
2972
|
+
await tapToFocus();
|
|
2973
|
+
await clearFocused();
|
|
2974
|
+
break;
|
|
2975
|
+
case "inputText":
|
|
2976
|
+
if (opts.clear) {
|
|
2977
|
+
await tapToFocus();
|
|
2978
|
+
await clearFocused();
|
|
2979
|
+
await sendInput({ ...device, type: "inputText", text: opts.text });
|
|
2980
|
+
} else {
|
|
2981
|
+
await sendInput({ ...device, type: "inputText", pxX: start?.[0], pxY: start?.[1], text: opts.text });
|
|
2982
|
+
}
|
|
2983
|
+
break;
|
|
2984
|
+
default:
|
|
2985
|
+
await sendInput({
|
|
2986
|
+
...device,
|
|
2987
|
+
type: opts.type,
|
|
2988
|
+
pxX: start?.[0],
|
|
2989
|
+
pxY: start?.[1],
|
|
2990
|
+
pxX2: end?.[0],
|
|
2991
|
+
pxY2: end?.[1],
|
|
2992
|
+
speed: opts.speed,
|
|
2993
|
+
key: opts.key,
|
|
2994
|
+
text: opts.text
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
if (opts.hideKeyboard) result.keyboardHidden = await hideKeyboard(device) || result.keyboardHidden;
|
|
2998
|
+
return result;
|
|
2999
|
+
}
|
|
3000
|
+
async function waitForTarget(opts) {
|
|
3001
|
+
const { selector } = opts;
|
|
3002
|
+
assertLayoutSelector(selector);
|
|
3003
|
+
let found = null;
|
|
3004
|
+
await waitForCondition({
|
|
3005
|
+
probe: async () => {
|
|
3006
|
+
const layout = await dumpLayout({
|
|
3007
|
+
config: opts.config,
|
|
3008
|
+
detectKeyboard: true,
|
|
3009
|
+
deviceSerial: opts.deviceSerial,
|
|
3010
|
+
logger: opts.logger
|
|
3011
|
+
});
|
|
3012
|
+
found = findLayoutNodes(targetTree(layout), selector).find((n) => !n.covered) ?? null;
|
|
3013
|
+
return opts.gone ? found === null : found !== null;
|
|
3014
|
+
},
|
|
3015
|
+
timeoutMs: opts.timeoutMs,
|
|
3016
|
+
pollMs: opts.pollMs ?? 500,
|
|
3017
|
+
abortSignal: opts.abortSignal,
|
|
3018
|
+
logger: opts.logger,
|
|
3019
|
+
timeoutMessage: opts.gone ? `An element matching the selector was still on screen after ${opts.timeoutMs}ms.` : `No element matching the selector appeared within ${opts.timeoutMs}ms.`
|
|
3020
|
+
});
|
|
3021
|
+
return opts.gone ? null : found;
|
|
3022
|
+
}
|
|
3023
|
+
|
|
2614
3024
|
// src/hdc/hilog.ts
|
|
2615
3025
|
function setHilogLevel(opts) {
|
|
2616
3026
|
const logger = opts.logger ?? noopLogger;
|
|
@@ -4743,7 +5153,7 @@ async function harmonyOsAutoSign(opts) {
|
|
|
4743
5153
|
}
|
|
4744
5154
|
|
|
4745
5155
|
// src/index.ts
|
|
4746
|
-
var VERSION = "0.
|
|
5156
|
+
var VERSION = "0.13.0";
|
|
4747
5157
|
export {
|
|
4748
5158
|
ALL_SDKS,
|
|
4749
5159
|
APL_VALUES,
|
|
@@ -4762,9 +5172,11 @@ export {
|
|
|
4762
5172
|
SIGNING_ERRORS,
|
|
4763
5173
|
SdkNotInstalledError,
|
|
4764
5174
|
TMP_DIR_HINT,
|
|
5175
|
+
TargetNotFoundError,
|
|
4765
5176
|
UnsupportedPlatformError,
|
|
4766
5177
|
VERSION,
|
|
4767
5178
|
applyChanges,
|
|
5179
|
+
assertLayoutSelector,
|
|
4768
5180
|
attemptHdcConnection,
|
|
4769
5181
|
buildCmdWrapper,
|
|
4770
5182
|
buildGestureCommands,
|
|
@@ -4787,6 +5199,7 @@ export {
|
|
|
4787
5199
|
dedupEntries,
|
|
4788
5200
|
defaultContext,
|
|
4789
5201
|
defaultPaths,
|
|
5202
|
+
describeLayoutNode,
|
|
4790
5203
|
detectAaStartFailure,
|
|
4791
5204
|
detectProjectSdkVersion,
|
|
4792
5205
|
detectRuntimeOs,
|
|
@@ -4812,8 +5225,12 @@ export {
|
|
|
4812
5225
|
extractZipWithProgress,
|
|
4813
5226
|
findAppProcessId,
|
|
4814
5227
|
findCmdToolsSourceDir,
|
|
5228
|
+
findCoveredNodes,
|
|
4815
5229
|
findHarmonyOsSdk,
|
|
5230
|
+
findKeyboardWindow,
|
|
5231
|
+
findLayoutNodes,
|
|
4816
5232
|
findRunningProcess,
|
|
5233
|
+
flattenLayout,
|
|
4817
5234
|
forceStop,
|
|
4818
5235
|
formatBytes,
|
|
4819
5236
|
generateSigningConfigs,
|
|
@@ -4828,6 +5245,7 @@ export {
|
|
|
4828
5245
|
getFreeSpaceBytes,
|
|
4829
5246
|
getHdcPath,
|
|
4830
5247
|
getHvigorwPath,
|
|
5248
|
+
getImeBundles,
|
|
4831
5249
|
getInstalledSdks,
|
|
4832
5250
|
getKey,
|
|
4833
5251
|
getMainAbility,
|
|
@@ -4840,6 +5258,7 @@ export {
|
|
|
4840
5258
|
harmonyOsAutoSign,
|
|
4841
5259
|
harmonyOsSdkLabelForApi,
|
|
4842
5260
|
hdcExec,
|
|
5261
|
+
hideKeyboard,
|
|
4843
5262
|
installApp,
|
|
4844
5263
|
installCmdTools,
|
|
4845
5264
|
installEmulator,
|
|
@@ -4856,6 +5275,7 @@ export {
|
|
|
4856
5275
|
listModules,
|
|
4857
5276
|
listRunningProcesses,
|
|
4858
5277
|
listTemplates,
|
|
5278
|
+
locateTarget,
|
|
4859
5279
|
needsCmdWrapper,
|
|
4860
5280
|
nonInteractivePrompter,
|
|
4861
5281
|
noopLogger,
|
|
@@ -4866,9 +5286,12 @@ export {
|
|
|
4866
5286
|
parseCodelinterFindings,
|
|
4867
5287
|
parseDeviceList,
|
|
4868
5288
|
parseHilogLine,
|
|
5289
|
+
parseImeBundles,
|
|
4869
5290
|
parseMainAbilityFromBmDump,
|
|
4870
5291
|
parseRemoteExit,
|
|
5292
|
+
performInput,
|
|
4871
5293
|
pickSigningKind,
|
|
5294
|
+
pickTarget,
|
|
4872
5295
|
prepareSigning,
|
|
4873
5296
|
pruneLayout,
|
|
4874
5297
|
readJson5File,
|
|
@@ -4881,6 +5304,7 @@ export {
|
|
|
4881
5304
|
removeSdk,
|
|
4882
5305
|
removeTempWorkDir,
|
|
4883
5306
|
requireHarmonyOsSdk,
|
|
5307
|
+
resolveKeyEvent,
|
|
4884
5308
|
resolveTmpRoot,
|
|
4885
5309
|
runCodelinter,
|
|
4886
5310
|
runHvigorw,
|
|
@@ -4910,8 +5334,10 @@ export {
|
|
|
4910
5334
|
waitForBundle,
|
|
4911
5335
|
waitForCondition,
|
|
4912
5336
|
waitForLog,
|
|
5337
|
+
waitForTarget,
|
|
4913
5338
|
watchLog,
|
|
4914
5339
|
waypointsToRawEvents,
|
|
5340
|
+
withoutWindow,
|
|
4915
5341
|
writeJson5File,
|
|
4916
5342
|
writeJsonFile
|
|
4917
5343
|
};
|