Merge pull request #3125 from deepseek-harness/worktree/composer-editable-gate

test(web): gate composer gestures on the editable attribute
This commit is contained in:
Yichen Jiang
2026-08-26 16:54:37 +08:00
committed by GitHub
5 changed files with 71 additions and 5 deletions
@@ -15,7 +15,7 @@ import {
assertFinalWorkspaceSnapshot, assertFixtureInventory, fixtureUserPrompts, launchWebScaffold, recordFixture,
watchConsole, webSnapshotMode, type WebScaffold,
} from './scaffold.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot } from './support.ts'
import { connectFreshWorkspace, newEnglishPage, saveFailureShot, writeComposerDraft } from './support.ts'
const SNAPSHOT_DIR = fileURLToPath(new URL('../../../snapshots/web/permission-policy-context', import.meta.url))
const FIXTURE = fileURLToPath(new URL('../../../snapshots/web/permission-policy-context/session.jsonl', import.meta.url))
@@ -97,23 +97,23 @@ describe('web e2e: current sandbox policy reaches the model before tools', () =>
const input = page.locator('[data-composer-input][contenteditable="true"]').first()
let sessionId: Awaited<ReturnType<WebScaffold['whenTurnSettled']>> | undefined
for (const [index, preset] of ['read-only', 'danger-full-access', 'workspace-write'].entries()) {
await input.fill(`/permission ${preset}`)
await writeComposerDraft(page, input, `/permission ${preset}`)
await input.press('Enter')
await page.getByRole('button', { name: `Access mode, current: ${PRESET_LABELS[index]}` })
.waitFor({ timeout: 10_000 })
const settled = scaffold.whenTurnSettled()
await input.fill(PROMPTS[index] as string)
await writeComposerDraft(page, input, PROMPTS[index] as string)
await input.press('Enter')
sessionId = await settled
await input.waitFor({ timeout: 10_000 })
}
await input.fill('/permission read-only')
await writeComposerDraft(page, input, '/permission read-only')
await input.press('Enter')
await page.getByRole('button', { name: 'Access mode, current: Read Only' }).waitFor({ timeout: 10_000 })
const settled = scaffold.whenTurnSettled()
await input.fill(PROMPTS[3])
await writeComposerDraft(page, input, PROMPTS[3])
await input.press('Enter')
sessionId = await settled
+10
View File
@@ -117,6 +117,15 @@ export async function connectFreshWorkspaceZh(page: Page, root: string, name = '
* selection, and the batched edit lands on a null selection and is silently
* 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. 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
@@ -127,6 +136,7 @@ export async function writeComposerDraft(
input: ReturnType<Page['locator']>,
text: string,
): Promise<void> {
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')