refactor: add KeyboardHelper.press() and delete() with internalized nextFrame()

New convenience methods that press a key and wait one frame. Converted
40 canvas.press(key) + nextFrame() and page.keyboard.press(key) +
nextFrame() pairs across 13 spec files to use comfyPage.keyboard.press().

Phase 5 of nextFrame() internalization plan.

Amp-Thread-ID: https://ampcode.com/threads/T-019d7b5d-2961-76f1-b4f3-cfd557b8dc0e
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-04-11 00:20:01 -07:00
parent ab31fb7fad
commit 270aae0d54
14 changed files with 50 additions and 80 deletions

View File

@@ -10,6 +10,16 @@ export class KeyboardHelper {
await this.page.evaluate(() => new Promise<number>(requestAnimationFrame))
}
async press(key: string, locator?: Locator | null): Promise<void> {
const target = locator ?? this.canvas
await target.press(key)
await this.nextFrame()
}
async delete(locator?: Locator | null): Promise<void> {
await this.press('Delete', locator)
}
async ctrlSend(
keyToPress: string,
locator: Locator | null = this.canvas

View File

@@ -110,8 +110,7 @@ test.describe('Builder save flow', { tag: ['@ui'] }, () => {
await expect(comfyPage.appMode.steps.toolbar).toBeVisible()
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await expect(comfyPage.appMode.steps.toolbar).toBeHidden()
})

View File

@@ -224,8 +224,7 @@ test.describe('CanvasModeSelector', { tag: '@canvas' }, () => {
await comfyPage.canvasOps.isReadOnly(),
'Precondition: canvas starts unlocked'
).toBe(false)
await comfyPage.canvas.press('KeyH')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('KeyH')
expect(await comfyPage.canvasOps.isReadOnly()).toBe(true)
const { trigger } = getLocators(comfyPage.page)
const modeIcon = trigger.locator('i[aria-hidden="true"]').first()
@@ -240,8 +239,7 @@ test.describe('CanvasModeSelector', { tag: '@canvas' }, () => {
await comfyPage.canvasOps.isReadOnly(),
'Precondition: canvas starts locked'
).toBe(true)
await comfyPage.canvas.press('KeyV')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('KeyV')
expect(await comfyPage.canvasOps.isReadOnly()).toBe(false)
const { trigger } = getLocators(comfyPage.page)
const modeIcon = trigger.locator('i[aria-hidden="true"]').first()

View File

@@ -223,8 +223,7 @@ test.describe('Change Tracker', { tag: '@workflow' }, () => {
await beforeChange(comfyPage)
await comfyPage.keyboard.bypass()
await expect(node).toBeBypassed()
await comfyPage.page.keyboard.press('KeyP')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('KeyP')
await expect(node).toBePinned()
await afterChange(comfyPage)
}

View File

@@ -53,8 +53,7 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
test("'Alt+=' zooms in", async ({ comfyPage }) => {
const initialScale = await comfyPage.canvasOps.getScale()
await comfyPage.canvas.press('Alt+Equal')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Alt+Equal')
await expect
.poll(() => comfyPage.canvasOps.getScale())
@@ -64,8 +63,7 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
test("'Alt+-' zooms out", async ({ comfyPage }) => {
const initialScale = await comfyPage.canvasOps.getScale()
await comfyPage.canvas.press('Alt+Minus')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Alt+Minus')
await expect
.poll(() => comfyPage.canvasOps.getScale())
@@ -83,8 +81,7 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
await comfyPage.canvas.click({ position: { x: 400, y: 400 } })
await comfyPage.nextFrame()
await comfyPage.canvas.press('Period')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Period')
await expect
.poll(() => comfyPage.canvasOps.getScale())
@@ -94,8 +91,7 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
test("'h' locks canvas", async ({ comfyPage }) => {
await expect.poll(() => comfyPage.canvasOps.isReadOnly()).toBe(false)
await comfyPage.canvas.press('KeyH')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('KeyH')
await expect.poll(() => comfyPage.canvasOps.isReadOnly()).toBe(true)
})
@@ -105,8 +101,7 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
await comfyPage.command.executeCommand('Comfy.Canvas.Lock')
await expect.poll(() => comfyPage.canvasOps.isReadOnly()).toBe(true)
await comfyPage.canvas.press('KeyV')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('KeyV')
await expect.poll(() => comfyPage.canvasOps.isReadOnly()).toBe(false)
})
@@ -124,12 +119,10 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
await expect.poll(() => node.isCollapsed()).toBe(false)
await comfyPage.canvas.press('Alt+KeyC')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Alt+KeyC')
await expect.poll(() => node.isCollapsed()).toBe(true)
await comfyPage.canvas.press('Alt+KeyC')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Alt+KeyC')
await expect.poll(() => node.isCollapsed()).toBe(false)
})
@@ -148,13 +141,11 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
await expect.poll(() => getMode()).toBe(0)
await comfyPage.canvas.press('Control+KeyM')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+KeyM')
// NEVER (2) = muted
await expect.poll(() => getMode()).toBe(2)
await comfyPage.canvas.press('Control+KeyM')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+KeyM')
await expect.poll(() => getMode()).toBe(0)
})
})
@@ -242,16 +233,14 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
test("'Ctrl+s' triggers save workflow", async ({ comfyPage }) => {
// On a new unsaved workflow, Ctrl+s triggers Save As dialog.
// The dialog appearing proves the keybinding was intercepted by the app.
await comfyPage.page.keyboard.press('Control+s')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+s')
// The Save As dialog should appear (p-dialog overlay)
const dialogOverlay = comfyPage.page.locator('.p-dialog-mask')
await expect(dialogOverlay).toBeVisible()
// Dismiss the dialog
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
})
test("'Ctrl+o' triggers open workflow", async ({ comfyPage }) => {
@@ -268,8 +257,7 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
}
})
await comfyPage.page.keyboard.press('Control+o')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+o')
await expect
.poll(() => comfyPage.page.evaluate(() => window.TestCommand))
@@ -291,11 +279,9 @@ test.describe('Default Keybindings', { tag: '@keyboard' }, () => {
const initialCount = await comfyPage.nodeOps.getGraphNodesCount()
// Select all nodes
await comfyPage.canvas.press('Control+a')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+a')
await comfyPage.page.keyboard.press('Control+Shift+KeyE')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+Shift+KeyE')
// After conversion, node count should decrease
// (multiple nodes replaced by single subgraph node)

View File

@@ -31,11 +31,9 @@ test.describe('Item Interaction', { tag: ['@screenshot', '@node'] }, () => {
test('Can pin/unpin items with keyboard shortcut', async ({ comfyPage }) => {
await comfyPage.workflow.loadWorkflow('groups/mixed_graph_items')
await comfyPage.canvas.press('Control+a')
await comfyPage.canvas.press('KeyP')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('KeyP')
await expect(comfyPage.canvas).toHaveScreenshot('pinned-all.png')
await comfyPage.canvas.press('KeyP')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('KeyP')
await expect(comfyPage.canvas).toHaveScreenshot('unpinned-all.png')
})
})
@@ -512,8 +510,7 @@ test.describe('Node Interaction', () => {
await comfyPage.page.keyboard.up('Control')
await comfyPage.nextFrame()
// Confirm group title
await comfyPage.page.keyboard.press('Enter')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Enter')
await expect(comfyPage.canvas).toHaveScreenshot(
'group-selected-nodes.png'
)

View File

@@ -108,8 +108,7 @@ for (const mode of ['litegraph', 'vue'] as const) {
expect(before).not.toBeNull()
expect(before!.ghost).toBe(true)
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
const after = await getNodeById(comfyPage, nodeId)
expect(after).toBeNull()
@@ -122,8 +121,7 @@ for (const mode of ['litegraph', 'vue'] as const) {
expect(before).not.toBeNull()
expect(before!.ghost).toBe(true)
await comfyPage.page.keyboard.press('Delete')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Delete')
const after = await getNodeById(comfyPage, nodeId)
expect(after).toBeNull()
@@ -136,8 +134,7 @@ for (const mode of ['litegraph', 'vue'] as const) {
expect(before).not.toBeNull()
expect(before!.ghost).toBe(true)
await comfyPage.page.keyboard.press('Backspace')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Backspace')
const after = await getNodeById(comfyPage, nodeId)
expect(after).toBeNull()

View File

@@ -18,15 +18,13 @@ test.describe('@canvas Selection Rectangle', () => {
const totalCount = await comfyPage.vueNodes.getNodeCount()
// Use canvas press for keyboard shortcuts (doesn't need click target)
await comfyPage.canvas.press('Control+a')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+a')
await expect(comfyPage.vueNodes.selectedNodes).toHaveCount(totalCount)
})
test('Click empty space deselects all', async ({ comfyPage }) => {
await comfyPage.canvas.press('Control+a')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+a')
await expect(comfyPage.vueNodes.selectedNodes).not.toHaveCount(0)
// Deselect by Ctrl+clicking the already-selected node (reliable cross-env)
@@ -77,8 +75,7 @@ test.describe('@canvas Selection Rectangle', () => {
// Use Ctrl+A to select all, which is functionally equivalent to
// drag-selecting the entire canvas and more reliable in CI
await comfyPage.canvas.press('Control+a')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+a')
await expect
.poll(() => comfyPage.vueNodes.getNodeCount())

View File

@@ -267,8 +267,7 @@ test.describe('Selection Toolbox', { tag: ['@screenshot', '@ui'] }, () => {
.click()
// Undo the colorization
await comfyPage.page.keyboard.press('Control+Z')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Control+Z')
// Node should be uncolored again
const selectedNode = (

View File

@@ -53,8 +53,7 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
await expect(breadcrumb).toBeVisible({ timeout: 20_000 })
const initialBreadcrumbText = (await breadcrumb.textContent()) ?? ''
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await comfyPage.canvas.dblclick({
position: {
@@ -68,8 +67,7 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
await comfyPage.page.keyboard.press('Control+a')
await comfyPage.page.keyboard.type(UPDATED_SUBGRAPH_TITLE)
await comfyPage.page.keyboard.press('Enter')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Enter')
await subgraphNode.navigateIntoSubgraph()
await expect(breadcrumb).toBeVisible()
@@ -153,8 +151,7 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
await expect.poll(() => comfyPage.subgraph.isInSubgraph()).toBe(true)
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await expect
.poll(() => comfyPage.subgraph.isInSubgraph(), {
message:
@@ -162,8 +159,7 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
})
.toBe(true)
await comfyPage.page.keyboard.press('Alt+q')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Alt+q')
await expect.poll(() => comfyPage.subgraph.isInSubgraph()).toBe(false)
})
@@ -191,8 +187,7 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
comfyPage.page.getByTestId(TestIds.dialogs.settings)
).toBeVisible()
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await expect(
comfyPage.page.getByTestId(TestIds.dialogs.settings)
@@ -200,8 +195,7 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
await expect.poll(() => comfyPage.subgraph.isInSubgraph()).toBe(true)
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await expect.poll(() => comfyPage.subgraph.isInSubgraph()).toBe(false)
})
})
@@ -304,8 +298,7 @@ test.describe('Subgraph Navigation', { tag: ['@slow', '@subgraph'] }, () => {
await expect.poll(() => comfyPage.subgraph.isInSubgraph()).toBe(true)
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await expect
.poll(() =>

View File

@@ -39,11 +39,9 @@ test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => {
const nodeToClone = await comfyPage.nodeOps.getNodeRefById(String(nodeId))
await nodeToClone.click('title')
await comfyPage.page.keyboard.press('ControlOrMeta+c')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('ControlOrMeta+c')
await comfyPage.page.keyboard.press('ControlOrMeta+v')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('ControlOrMeta+v')
await expect
.poll(() => comfyPage.subgraph.getNodeCount())

View File

@@ -88,8 +88,7 @@ test.describe('Subgraph Promotion DOM', { tag: ['@subgraph'] }, () => {
await expect(subgraphTextarea).toHaveValue(TEST_WIDGET_CONTENT)
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
const backToParentTextarea = comfyPage.page.locator(DOM_WIDGET_SELECTOR)
await expect(backToParentTextarea).toBeVisible()

View File

@@ -15,8 +15,7 @@ async function exitSubgraphAndPublish(
subgraphNode: Awaited<ReturnType<typeof createSubgraphAndNavigateInto>>,
blueprintName: string
) {
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await subgraphNode.click('title')
await comfyPage.command.executeCommand('Comfy.PublishSubgraph', {

View File

@@ -59,8 +59,7 @@ test.describe('Subgraph Serialization', { tag: ['@subgraph'] }, () => {
await expect.poll(() => comfyPage.subgraph.isInSubgraph()).toBe(true)
await comfyPage.page.keyboard.press('Escape')
await comfyPage.nextFrame()
await comfyPage.keyboard.press('Escape')
await expect.poll(() => comfyPage.subgraph.isInSubgraph()).toBe(false)
})