mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-14 04:01:35 +00:00
test(web): finish the e2e migration to the composer surface
textContent/data-placeholder probes replace inputValue/placeholder reads, evaluate-string selectors move to the composer anchor, queued fills wait out the submit round-trip's read-only span, and the refreshed aria goldens drop the hover tooltip the old interaction order happened to capture.
This commit is contained in:
@@ -222,10 +222,10 @@ describe('web e2e: continuous conversation grown through the composer', () => {
|
||||
|
||||
for (const spec of specs) {
|
||||
const eventStart = sessionEvents.length
|
||||
expect(await composer.inputValue()).toBe('')
|
||||
expect(await composer.textContent()).toBe('')
|
||||
expect(await composer.isEnabled()).toBe(true)
|
||||
await composer.fill(spec.prompt)
|
||||
expect(await composer.inputValue()).toBe(spec.prompt)
|
||||
expect(await composer.textContent()).toBe(spec.prompt)
|
||||
|
||||
const settled = scaffold.whenTurnSettled(60_000)
|
||||
await page.getByRole('button', { name: 'Send message', exact: true }).click()
|
||||
@@ -257,7 +257,7 @@ describe('web e2e: continuous conversation grown through the composer', () => {
|
||||
|
||||
await expect.poll(() => page.locator('[data-streaming="true"]').count(), { timeout: 15_000 }).toBe(0)
|
||||
await page.getByText(spec.doneMarker, { exact: false }).last().waitFor({ timeout: 15_000 })
|
||||
await expect.poll(() => composer.inputValue(), { timeout: 10_000 }).toBe('')
|
||||
await expect.poll(() => composer.textContent(), { timeout: 10_000 }).toBe('')
|
||||
await expect.poll(() => composer.isEnabled(), { timeout: 10_000 }).toBe(true)
|
||||
|
||||
const turnEvents = sessionEvents.slice(eventStart)
|
||||
|
||||
@@ -279,7 +279,7 @@ describe('web e2e: long Chat interaction contract', () => {
|
||||
expect(await settled).toBe(child.session.id)
|
||||
await page.getByText(CONTINUE_DONE, { exact: false }).last().waitFor({ timeout: 15_000 })
|
||||
await expect.poll(() => page.locator('[data-streaming="true"]').count(), { timeout: 15_000 }).toBe(0)
|
||||
expect(await composer.inputValue()).toBe('')
|
||||
expect(await composer.textContent()).toBe('')
|
||||
expect(await composer.isEnabled()).toBe(true)
|
||||
expect(source.session.events.some(event => carries(event, CONTINUE_PROMPT))).toBe(false)
|
||||
expect(child.session.events.filter(event => (
|
||||
|
||||
@@ -947,8 +947,8 @@ async function continueConversation(
|
||||
const spec = options.turnSpec(index)
|
||||
const composerFill = await measure(cdp, async () => {
|
||||
await composer.fill(spec.prompt)
|
||||
await expect.poll(() => composer.inputValue()).toBe(spec.prompt)
|
||||
return (await composer.inputValue()).length
|
||||
await expect.poll(() => composer.textContent()).toBe(spec.prompt)
|
||||
return (await composer.textContent()).length
|
||||
})
|
||||
expect(composerFill.value).toBe(spec.prompt.length)
|
||||
|
||||
@@ -1067,8 +1067,8 @@ async function measurePostSoakUserRender(
|
||||
const composer = world.page.locator('[data-composer-input][contenteditable="true"]').last()
|
||||
const composerFill = await measure(cdp, async () => {
|
||||
await composer.fill(spec.prompt)
|
||||
await expect.poll(() => composer.inputValue()).toBe(spec.prompt)
|
||||
return (await composer.inputValue()).length
|
||||
await expect.poll(() => composer.textContent()).toBe(spec.prompt)
|
||||
return (await composer.textContent()).length
|
||||
})
|
||||
expect(composerFill.value).toBe(spec.prompt.length)
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ describe('web e2e: the composer model switch is the default for later sessions',
|
||||
await scaffold.ctx.settings.replace(settingsNamespace('llm-pi-ai'), { providers: {} })
|
||||
|
||||
await expect.poll(async () => box.isEnabled(), { timeout: 15_000 }).toBe(false)
|
||||
expect(await box.getAttribute('placeholder')).toBe('当前模型不可用,请先选择模型')
|
||||
expect(await box.getAttribute('data-placeholder')).toBe('当前模型不可用,请先选择模型')
|
||||
|
||||
// The block is an affordance; the refusal is the Host's. A client that
|
||||
// never disabled anything still cannot start a turn on a dead route.
|
||||
|
||||
@@ -43,7 +43,7 @@ describe('web e2e: goal bar clear convergence', () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-goal-bar-clear'))
|
||||
// Startup reuses the fixture workspace's blank session, keeping this
|
||||
// command independent of alpha's running replay and pending question.
|
||||
const input = page.getByPlaceholder('Describe what you want to build')
|
||||
const input = page.locator('[data-composer-input][data-placeholder="Describe what you want to build"]')
|
||||
await input.waitFor({ timeout: 10_000 })
|
||||
await input.fill('/goal guard rapid clear clicks')
|
||||
await input.press('Enter')
|
||||
|
||||
@@ -51,7 +51,7 @@ describe('web e2e: /goal human transcript presentation', () => {
|
||||
const input = page.locator('[data-composer-input]').first()
|
||||
await input.fill('/goal')
|
||||
await input.press('Enter')
|
||||
await expect.poll(() => input.inputValue()).toBe('/goal ')
|
||||
await expect.poll(() => input.textContent()).toBe('/goal ')
|
||||
await input.press('Enter')
|
||||
|
||||
const commandInput = page.locator('[data-command-input]')
|
||||
|
||||
@@ -108,14 +108,14 @@ describe('web e2e: lifecycle & chrome (workspace flow / reload / dark mode)', ()
|
||||
const menu = activePage.getByRole('listbox', { name: 'Trigger suggestions' })
|
||||
await menu.waitFor({ timeout: 10_000 })
|
||||
await menu.getByRole('option', { name: 'plan Enter or leave plan mode' }).click()
|
||||
await expect.poll(() => input.inputValue()).toBe('/plan ')
|
||||
await expect.poll(() => input.textContent()).toBe('/plan ')
|
||||
await input.press('Enter')
|
||||
const planButton = activePage.getByRole('button', { name: 'Plan mode on, press to turn off' })
|
||||
await planButton.waitFor({ timeout: 10_000 })
|
||||
// The golden encodes an empty composer, and the button arriving does not
|
||||
// mean the submitted text is gone yet: under load the capture can catch
|
||||
// a textbox still holding `/plan`.
|
||||
await expect.poll(() => input.inputValue(), { timeout: 10_000 }).toBe('')
|
||||
await expect.poll(() => input.textContent(), { timeout: 10_000 }).toBe('')
|
||||
const planSnapshot = await captureStableAria(activePage, '[class*="frame"]', activeScaffold.workspaceCwd)
|
||||
await compareOrRefreshGolden(PLAN_ACTIVE_EXPECTED, planSnapshot, MODE)
|
||||
const planStyle = await planButton.evaluate((element) => {
|
||||
|
||||
@@ -93,6 +93,8 @@ describe('web e2e: queue row actions', () => {
|
||||
await expect.poll(() => existsSync(readyFile), { timeout: 15_000 }).toBe(true)
|
||||
|
||||
for (const text of [REMOVE, EDIT]) {
|
||||
// A just-submitted composer is read-only for the prompt round-trip.
|
||||
await page.locator('[data-composer-input][contenteditable="true"]').first().waitFor({ timeout: 10_000 })
|
||||
await input.fill(text)
|
||||
await input.press('Enter')
|
||||
}
|
||||
@@ -198,6 +200,7 @@ describe('web e2e: queue row actions', () => {
|
||||
|
||||
const input = page.locator('[data-composer-input]').first()
|
||||
const settled = scaffold.whenTurnSettled()
|
||||
await page.locator('[data-composer-input][contenteditable="true"]').first().waitFor({ timeout: 10_000 })
|
||||
await input.fill('/goal Keep the composer context panels aligned')
|
||||
await input.press('Enter')
|
||||
await expect.poll(() => existsSync(readyFile), { timeout: 15_000 }).toBe(true)
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
# Composer draft scrolling (14-line cap, two text layers, one scrollport)
|
||||
# Composer draft scrolling (14-line cap, one editable surface, one scrollport)
|
||||
|
||||
## At the start of the draft
|
||||
|
||||
- draft overflows the capped box: true
|
||||
- visible lines: 14
|
||||
- the textarea holds no scroll offset of its own: true
|
||||
- all three layers wrap at one width: true
|
||||
- the surface holds no scroll offset of its own: true
|
||||
- scroll offset: 0px
|
||||
- caret and glyphs stay level when the offset changes: true
|
||||
- first draft line is on screen: true
|
||||
- last draft line is on screen: false
|
||||
|
||||
## Scrolled to the end of the draft
|
||||
|
||||
- offset moved: true
|
||||
- caret sits on its own glyphs: true
|
||||
- caret and glyphs stay level when the offset changes: true
|
||||
- the surface holds no scroll offset of its own: true
|
||||
- first draft line has scrolled out above: true
|
||||
- last draft line is on screen: true
|
||||
|
||||
## Draft ending in a newline, scrolled to the end
|
||||
|
||||
- caret sits on its own glyphs: true
|
||||
- the draft's own last line is on screen: true
|
||||
|
||||
## Right after pasting a long block at the end
|
||||
|
||||
- the composer scrolled to the caret it left: true
|
||||
- caret and glyphs stay level when the offset changes: true
|
||||
- the pasted block's last line is on screen: true
|
||||
|
||||
@@ -29,7 +29,8 @@
|
||||
- img
|
||||
- text: Standard mode
|
||||
- img
|
||||
- textbox "Describe what you want to build"
|
||||
- textbox "Describe what you want to build":
|
||||
- paragraph
|
||||
- button "Commands":
|
||||
- img
|
||||
- tooltip "Commands"
|
||||
|
||||
@@ -81,7 +81,7 @@ describe('web e2e: startup auto-selection', () => {
|
||||
workspaceChip: document.querySelector('[aria-label="Choose workspace"]'),
|
||||
scrollBody: document.querySelector('[data-conversation-scroll]'),
|
||||
composerSeat: document.querySelector('[data-composer-seat]'),
|
||||
textarea: document.querySelector('textarea'),
|
||||
composer: document.querySelector('[data-composer-input]'),
|
||||
}
|
||||
if (Object.values(refs).some(node => node === null)) throw new Error('incomplete initial Hero tree')
|
||||
;(window as unknown as { __heroTree: typeof refs }).__heroTree = refs
|
||||
@@ -99,8 +99,8 @@ describe('web e2e: startup auto-selection', () => {
|
||||
workspaceChip: document.querySelector('[aria-label="Choose workspace"]') === before.workspaceChip,
|
||||
scrollBody: document.querySelector('[data-conversation-scroll]') === before.scrollBody,
|
||||
composerSeat: document.querySelector('[data-composer-seat]') === before.composerSeat,
|
||||
textarea: document.querySelector('textarea') === before.textarea,
|
||||
textareaEnabled: !(document.querySelector('textarea') as HTMLTextAreaElement).disabled,
|
||||
composer: document.querySelector('[data-composer-input]') === before.composer,
|
||||
composerEnabled: document.querySelector('[data-composer-input]')?.getAttribute('aria-disabled') !== 'true',
|
||||
}
|
||||
})).toEqual({
|
||||
phase: 'hero',
|
||||
@@ -108,8 +108,8 @@ describe('web e2e: startup auto-selection', () => {
|
||||
workspaceChip: true,
|
||||
scrollBody: true,
|
||||
composerSeat: true,
|
||||
textarea: true,
|
||||
textareaEnabled: true,
|
||||
composer: true,
|
||||
composerEnabled: true,
|
||||
})
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
}, 120_000)
|
||||
@@ -156,7 +156,7 @@ describe('web e2e: startup auto-selection', () => {
|
||||
expect(await page.locator('[data-composer-input]').first().isVisible()).toBe(true)
|
||||
|
||||
releaseHistory()
|
||||
await page.locator('textarea:enabled[placeholder="Describe what you want to build"]')
|
||||
await page.locator('[data-composer-input][contenteditable="true"][data-placeholder="Describe what you want to build"]')
|
||||
.waitFor({ timeout: 15_000 })
|
||||
acknowledgeReloadConnectionLoss(tripwire, warningsBefore)
|
||||
|
||||
|
||||
@@ -209,7 +209,7 @@ describe('web e2e: composer shortcut steers directly', () => {
|
||||
|
||||
await input.fill(STEER)
|
||||
await input.press('Meta+Enter')
|
||||
await expect.poll(() => input.inputValue(), { timeout: 5_000 }).toBe('')
|
||||
await expect.poll(() => input.textContent(), { timeout: 5_000 }).toBe('')
|
||||
expect(await page.locator('[data-queue-dock]').count()).toBe(0)
|
||||
|
||||
const composer = page.locator('[data-question-key]')
|
||||
|
||||
Reference in New Issue
Block a user