{"record":{"id":"e5483a2ffcc167e2","repo":"Hmbown/CodeWhale","slug":"select-text-is-not-exposed-by-uitest-dumplayout-uiinput-on","errorCode":null,"errorMessage":"select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend","messagePattern":"select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend","errorType":"exception","errorClass":"ExecError","httpStatus":null,"severity":"error","filePath":"crates/tui/plugins/computer-use/src/backends/harmonyos.mjs","lineNumber":263,"sourceCode":"    key: ({ text }) => {\n      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\" };\n      const k = KEYMAP[String(text).toLowerCase()] ?? String(text);\n      if (!/^[A-Za-z0-9_]+$/.test(k)) throw new ExecError(`unsupported key \"${text}\" on harmony backend`);\n      return uiInput([\"keyEvent\", k]);\n    },\n    hold_key: async ({ text, duration }) => {\n      if (String(text).toLowerCase() !== \"click\") throw new ExecError('hold_key on harmony supports only hold_key({\"text\":\"click\"}) = longClick');\n      const d = Math.max(1, Math.min(5, Number(duration) || 1));\n      return uiInput([\"longClick\", \"300\", \"300\"]);\n    },\n    set_value: async ({ target, value }) => {\n      const b = await centerOf(target);\n      await uiInput([\"click\", String(b.cx), String(b.cy)]);\n      await new Promise((r) => setTimeout(r, 300));\n      await uiInput([\"inputText\", String(b.cx), String(b.cy), escDeviceText(String(value))]);\n      return { action_sent: true, strategy: \"uitest-element\" };\n    },\n    select_text: async () => { throw new ExecError(\"select_text is not exposed by uitest dumpLayout/uiInput on the harmony backend\"); },\n    perform_action: async ({ target, action }) => {\n      const b = await centerOf(target);\n      if (action === \"longClick\") return uiInput([\"longClick\", String(b.cx), String(b.cy)]);\n      return uiInput([\"click\", String(b.cx), String(b.cy)]);\n    },\n    read_clipboard: async () => { throw new ExecError(\"clipboard read is not exposed by hdc on current HarmonyOS builds\"); },\n    write_clipboard: async () => { throw new ExecError(\"clipboard write is not exposed by hdc on current HarmonyOS builds\"); },\n    cursor_position: async () => { throw new ExecError(\"cursor position does not exist on touch devices\"); },\n    recordingStart: async ({ intervalMs = 400 } = {}) => {\n      if (recording) throw new ExecError(`recording ${recording.id} already running`);\n      if (!(await have(\"ffmpeg\"))) throw new ExecError(\"ffmpeg is required on the host to mux harmony snapshot-series recordings\");\n      const id = crypto.randomBytes(4).toString(\"hex\");\n      const dir = fs.mkdtempSync(path.join(os.tmpdir(), `cu-rec-${id}-`));\n      const startedAt = new Date().toISOString();\n      const rec = recording = { id, dir, startedAt, intervalMs, seq: 0, controller: new AbortController() };\n      const tick = () => {\n        if (rec.stopped || rec.pending) return rec.pending;\n        rec.pending = (async () => {","sourceCodeStart":245,"sourceCodeEnd":281,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/plugins/computer-use/src/backends/harmonyos.mjs#L245-L281","documentation":"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.","triggerScenarios":"Calling select_text({target, start, end}) (any arguments) on the harmonyos backend.","commonSituations":"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.","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"],"exampleFix":"// before\nawait backend.select_text({ target: \"#field\", start: 0, end: 5 });\n// after\nawait backend.set_value({ target: \"#field\", value: \"replacement text\" });","handlingStrategy":"fallback","validationCode":"const SUPPORTS_SELECT = { harmonyos: false }; // check per backend before calling\nif (SUPPORTS_SELECT[backend.name] === false) throw new Error(\"select_text unsupported on \" + backend.name);","typeGuard":null,"tryCatchPattern":"try {\n  await backend.select_text({ target, start, end });\n} catch (e) {\n  if (String(e.message).includes(\"select_text is not exposed\")) await backend.set_value({ target, value: replacement });\n  else throw e;\n}","preventionTips":["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"],"tags":["harmonyos","select-text","not-implemented"],"backgroundTag":"operation-not-supported","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}