Hmbown/CodeWhale · error · ExecError
select_text is not exposed by uitest dumpLayout/uiInput on…
Error message
select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend
What it means
select_text is intentionally unimplemented on the harmony backend: the hdc uitest surface used here (dumpLayout + uiInput: click, inputText, longClick, keyEvent) exposes no text-selection primitives. The backend throws unconditionally rather than silently failing to select.
Solutions
- Avoid select_text on HarmonyOS; restructure the workflow to use set_value to replace the whole field content
- Simulate selection with UI gestures (longClick then dpad/keys) if the app supports it, accepting it is heuristic
- Contribute a backend implementation if a newer hdc uitest API exposes selection
Example fix
// before
await backend.select_text({ target: "#field", start: 0, end: 5 });
// after
await backend.set_value({ target: "#field", value: "replacement text" }); Defensive patterns
Strategy: fallback
Validate before calling
const SUPPORTS_SELECT = { harmonyos: false }; // check per backend before calling
if (SUPPORTS_SELECT[backend.name] === false) throw new Error("select_text unsupported on " + backend.name); Try / catch
try {
await backend.select_text({ target, start, end });
} catch (e) {
if (String(e.message).includes("select_text is not exposed")) await backend.set_value({ target, value: replacement });
else throw e;
} Prevention
- Gate text-selection steps on backend capability
- Replace select+edit flows with set_value on HarmonyOS
- Keep an action-capability matrix for all backends used by the suite
When it happens
Trigger: Calling select_text({target, start, end}) (any arguments) on the harmonyos backend.
Common situations: Scripts copied from Android/desktop backends that rely on programmatic selection for copy/delete of text ranges; tests exercising the full cross-backend action matrix.
Related errors
- aa start failed
- clipboard read is not exposed by hdc on current HarmonyOS…
- clipboard write is not exposed by hdc on current HarmonyOS…
- cursor position does not exist on touch devices
- element_stale — re-run get_app_state; uitest indexes change…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/e5483a2ffcc167e2.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/plugins/computer-use/src/backends/harmonyos.mjs:263
key: ({ text }) => {
const KEYMAP = { enter: "Enter", return: "Enter", escape: "Esc", esc: "Esc", back: "Back", home: "Home", backspace: "Back", delete: "Del", tab: "Tab", left: "DPAD_LEFT", right: "DPAD_RIGHT", up: "DPAD_UP", down: "DPAD_DOWN", power: "Power", menu: "Menu" };
const k = KEYMAP[String(text).toLowerCase()] ?? String(text);
if (!/^[A-Za-z0-9_]+$/.test(k)) throw new ExecError(`unsupported key "${text}" on harmony backend`);
return uiInput(["keyEvent", k]);
},
hold_key: async ({ text, duration }) => {
if (String(text).toLowerCase() !== "click") throw new ExecError('hold_key on harmony supports only hold_key({"text":"click"}) = longClick');
const d = Math.max(1, Math.min(5, Number(duration) || 1));
return uiInput(["longClick", "300", "300"]);
},
set_value: async ({ target, value }) => {
const b = await centerOf(target);
await uiInput(["click", String(b.cx), String(b.cy)]);
await new Promise((r) => setTimeout(r, 300));
await uiInput(["inputText", String(b.cx), String(b.cy), escDeviceText(String(value))]);
return { action_sent: true, strategy: "uitest-element" };
},
select_text: async () => { throw new ExecError("select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend"); },
perform_action: async ({ target, action }) => {
const b = await centerOf(target);
if (action === "longClick") return uiInput(["longClick", String(b.cx), String(b.cy)]);
return uiInput(["click", String(b.cx), String(b.cy)]);
},
read_clipboard: async () => { throw new ExecError("clipboard read is not exposed by hdc on current HarmonyOS builds"); },
write_clipboard: async () => { throw new ExecError("clipboard write is not exposed by hdc on current HarmonyOS builds"); },
cursor_position: async () => { throw new ExecError("cursor position does not exist on touch devices"); },
recordingStart: async ({ intervalMs = 400 } = {}) => {
if (recording) throw new ExecError(`recording ${recording.id} already running`);
if (!(await have("ffmpeg"))) throw new ExecError("ffmpeg is required on the host to mux harmony snapshot-series recordings");
const id = crypto.randomBytes(4).toString("hex");
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `cu-rec-${id}-`));
const startedAt = new Date().toISOString();
const rec = recording = { id, dir, startedAt, intervalMs, seq: 0, controller: new AbortController() };
const tick = () => {
if (rec.stopped || rec.pending) return rec.pending;
rec.pending = (async () => {View on GitHub (pinned to 73e0f67d83)