test(web): derive the editable gate from the gesture target

Review follow-ups: wait on the caller's own locator instead of assuming
it is the page's first composer, describe the actual read-only window
(submit adjudication and locked states — a running turn stays editable
for queueing), drop the pre-Lexical narration from the JSDoc, and record
the gesture-semantics trap as an Agent Note.
This commit is contained in:
Yichen Jiang
2026-08-26 16:35:49 +08:00
parent 10d1c603be
commit 21e5ee9071
4 changed files with 65 additions and 7 deletions
+9 -7
View File
@@ -118,12 +118,14 @@ export async function connectFreshWorkspaceZh(page: Page, root: string, name = '
* dropped, leaving the previous draft in place. Real keystrokes leave room for
* `selectionchange` between keys, which is also what a user's typing does.
*
* Waits for the surface to be editable first. A running turn disables the
* composer by setting `contenteditable="false"` on the same element, where
* `fill()` throws immediately instead of waiting (a disabled `<textarea>`
* held it back via actionability) and `isEnabled()` reports `true` for a
* `<div>` regardless — so a gesture directly after a turn settles must gate
* on the attribute, not on enablement.
* Waits for the surface to be editable first. While the input machine is
* adjudicating or submitting a send — and in every locked state (removed
* session, no workspace, an owner block) — the composer renders read-only
* with `contenteditable="false"` on the same element. `fill()` throws
* immediately on that element, and `isEnabled()` reports `true` for a
* `<div>` regardless of the attribute — so a gesture directly after a
* submit must gate on the attribute, not on enablement. A running turn by
* itself keeps the composer editable (that is what queueing types into).
* @param page - the page under test.
* @param input - the `[data-composer-input]` surface locator.
* @param text - the replacement draft; `''` clears the draft. Must not
@@ -134,7 +136,7 @@ export async function writeComposerDraft(
input: ReturnType<Page['locator']>,
text: string,
): Promise<void> {
await page.locator('[data-composer-input][contenteditable="true"]').first().waitFor({ timeout: 15_000 })
await input.and(page.locator('[contenteditable="true"]')).waitFor({ timeout: 15_000 })
await input.click()
await page.keyboard.press('ControlOrMeta+A')
if (text === '') await page.keyboard.press('Backspace')