--- id: "ab39b279-9832-4766-ae8e-c093ebc5e389" name: "Gmail Compose Draft Authoring" micro_summary: "Open Gmail compose draft authoring: set subjectbox, insert body into visible Message Body contenteditable, dispatch save events, verify attachments from input[name=sa], never send." status: "resolved" see_also: [] scope: "Skill for authoring an already-open Gmail compose draft without sending it: fill subject/body, preserve attachments, and verify the human-review draft state." --- ## Open Compose Draft Fill For an already-open Gmail compose draft: - Fill the subject via `input[name="subjectbox"]`. - Fill the body via the visible `div[aria-label="Message Body"][contenteditable="true"]`. - Use `focus()`, select the editor contents, then `document.execCommand("insertText", false, body)` to make Gmail behave like typed input. - Dispatch `input` and `change` events after subject and body edits so Gmail saves the draft. - Verify attachments via `input[name="sa"]`; parse each JSON value and read index `1` for the filename. - Verify final visible draft state with `extract_text()`. ## Minimal Pattern ```python sid = switch_tab(target_id) res = js(""" (async () => { const subj = document.querySelector('input[name="subjectbox"]'); subj.focus(); subj.value = subject; subj.dispatchEvent(new InputEvent('input', {bubbles:true, inputType:'insertText', data:subject})); subj.dispatchEvent(new Event('change', {bubbles:true})); const editors = [...document.querySelectorAll('div[aria-label="Message Body"][contenteditable="true"]')] .filter(e => !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length)); const bodyEl = editors[editors.length - 1]; bodyEl.focus(); const sel = window.getSelection(); const range = document.createRange(); range.selectNodeContents(bodyEl); sel.removeAllRanges(); sel.addRange(range); document.execCommand('insertText', false, body); bodyEl.dispatchEvent(new InputEvent('input', {bubbles:true, inputType:'insertText', data:body})); bodyEl.dispatchEvent(new Event('change', {bubbles:true})); return { subjectValue: subj.value, bodyText: bodyEl.innerText, attachments: [...document.querySelectorAll('input[name="sa"]')] .map(i => JSON.parse(i.value)[1]) }; })() """) ``` ## Gotchas - Do not click Send. Leave final sending to the human. - Scope recipient checks to the compose dialog. Global `[data-hovercard-id]` returns unrelated inbox/calendar hovercards.