mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Alright, alright, alright. These e2e tests have been runnin' around like they're late for somethin', settin' tight little timeouts like the world's gonna end in 250 milliseconds. Man, you gotta *breathe*. Let the framework do its thing. Go slow to go fast, that's what I always say. ## Changes - **What**: Removed ~120 redundant timeout overrides from auto-retrying Playwright assertions (`toBeVisible`, `toBeHidden`, `toHaveCount`, `toBeEnabled`, `toHaveAttribute`, `toContainText`, `expect.poll`) where 5000ms is already the default. Also removed sub-5s timeouts (1s, 2s, 3s) that were just *begging* for flaky failures — like wearin' a belt and suspenders and also holdin' your pants up with both hands. Raised the absurdly short timeouts in `customMatchers.ts` (250ms `toPass` → 5000ms, 256ms poll → default). Kept `timeout: 5000` on `.toPass()` calls (defaults to 0), `.waitFor()`, `waitForRequest`, `waitForFunction`, intentionally-short timeouts inside retry loops, and conditional `.isVisible()/.catch()` checks — those fellas actually need the help. ## Review Focus Every remaining timeout in the diff is there for a *reason*. The ones on `.toPass()` stay because that API defaults to zero — it won't retry at all without one. The ones on `.waitFor()` and `waitForRequest` stay because those are locator actions, not auto-retrying assertions. The intentionally-short ones inside `toPass` retry loops (`interaction.spec.ts`) and the negative assertions (`actionbar.spec.ts` confirming no response arrives) — those are *supposed* to be tight. The short timeouts on regular assertions were actively *encouragin'* flaky failures. That's like settin' your alarm for 4 AM and then gettin' mad you're tired. Just... don't do that, man. Let things take the time they need. 38 files, net -115 lines. Less code, more chill. That's livin'. --------- Co-authored-by: Amp <amp@ampcode.com>
248 lines
8.2 KiB
TypeScript
248 lines
8.2 KiB
TypeScript
import fs from 'node:fs'
|
|
import os from 'node:os'
|
|
import path from 'node:path'
|
|
|
|
import type { Page } from '@playwright/test'
|
|
import { expect } from '@playwright/test'
|
|
|
|
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
|
|
|
const TEST_PRESET = {
|
|
name: 'test-preset',
|
|
newBindings: [
|
|
{
|
|
commandId: 'Comfy.Canvas.SelectAll',
|
|
combo: { key: 'a', ctrl: true, shift: true },
|
|
targetElementId: 'graph-canvas-container'
|
|
}
|
|
],
|
|
unsetBindings: [
|
|
{
|
|
commandId: 'Comfy.Canvas.SelectAll',
|
|
combo: { key: 'a', ctrl: true },
|
|
targetElementId: 'graph-canvas-container'
|
|
}
|
|
]
|
|
}
|
|
|
|
async function importPreset(page: Page, preset: typeof TEST_PRESET) {
|
|
const menuButton = page.getByTestId('keybinding-preset-menu')
|
|
await menuButton.click()
|
|
|
|
const fileChooserPromise = page.waitForEvent('filechooser')
|
|
await page.getByRole('menuitem', { name: /Import preset/i }).click()
|
|
const fileChooser = await fileChooserPromise
|
|
|
|
const presetPath = path.join(os.tmpdir(), 'test-preset.json')
|
|
fs.writeFileSync(presetPath, JSON.stringify(preset))
|
|
await fileChooser.setFiles(presetPath)
|
|
}
|
|
|
|
test.beforeEach(async ({ comfyPage }) => {
|
|
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Disabled')
|
|
})
|
|
|
|
test.afterEach(async ({ comfyPage }) => {
|
|
await comfyPage.request.fetch(
|
|
`${comfyPage.url}/api/userdata/keybindings%2Ftest-preset.json`,
|
|
{ method: 'DELETE' }
|
|
)
|
|
await comfyPage.settings.setSetting(
|
|
'Comfy.Keybinding.CurrentPreset',
|
|
'default'
|
|
)
|
|
})
|
|
|
|
test.describe('Keybinding Presets', { tag: '@keyboard' }, () => {
|
|
test('Can import a preset, use remapped keybinding, and switch back to default', async ({
|
|
comfyPage
|
|
}) => {
|
|
test.setTimeout(30000)
|
|
const { page } = comfyPage
|
|
|
|
// Verify default Ctrl+A select-all works
|
|
await comfyPage.workflow.loadWorkflow('default')
|
|
await comfyPage.canvas.press('Control+a')
|
|
await comfyPage.canvas.press('Delete')
|
|
await expect.poll(() => comfyPage.nodeOps.getGraphNodesCount()).toBe(0)
|
|
|
|
// Open keybinding settings panel
|
|
await comfyPage.settingDialog.open()
|
|
await comfyPage.settingDialog.category('Keybinding').click()
|
|
|
|
await importPreset(page, TEST_PRESET)
|
|
|
|
// Verify active preset switched to test-preset
|
|
const presetTrigger = page
|
|
.locator('#keybinding-panel-actions')
|
|
.locator('button[role="combobox"]')
|
|
await expect(presetTrigger).toContainText('test-preset')
|
|
|
|
// Wait for toast to auto-dismiss, then close settings via Escape
|
|
await expect(comfyPage.toast.visibleToasts).toHaveCount(0)
|
|
await page.keyboard.press('Escape')
|
|
await comfyPage.settingDialog.waitForHidden()
|
|
|
|
// Load workflow again, use new keybind Ctrl+Shift+A
|
|
await comfyPage.workflow.loadWorkflow('default')
|
|
await comfyPage.canvas.press('Control+Shift+a')
|
|
await expect
|
|
.poll(() => comfyPage.nodeOps.getSelectedGraphNodesCount())
|
|
.toBeGreaterThan(0)
|
|
await comfyPage.canvas.press('Delete')
|
|
await expect.poll(() => comfyPage.nodeOps.getGraphNodesCount()).toBe(0)
|
|
|
|
// Switch back to default preset
|
|
await comfyPage.settingDialog.open()
|
|
await comfyPage.settingDialog.category('Keybinding').click()
|
|
|
|
await presetTrigger.click()
|
|
await page.getByRole('option', { name: /Default Preset/i }).click()
|
|
|
|
// Handle unsaved changes dialog if the preset was marked as modified
|
|
const discardButton = page.getByRole('button', {
|
|
name: /Discard and Switch/i
|
|
})
|
|
if (await discardButton.isVisible({ timeout: 2000 }).catch(() => false)) {
|
|
await discardButton.click()
|
|
}
|
|
|
|
await expect(presetTrigger).toContainText('Default Preset')
|
|
|
|
await page.keyboard.press('Escape')
|
|
await comfyPage.settingDialog.waitForHidden()
|
|
})
|
|
|
|
test('Can export a preset and re-import it', async ({ comfyPage }) => {
|
|
test.setTimeout(30000)
|
|
const { page } = comfyPage
|
|
const menuButton = page.getByTestId('keybinding-preset-menu')
|
|
|
|
// Open keybinding settings panel
|
|
await comfyPage.settingDialog.open()
|
|
await comfyPage.settingDialog.category('Keybinding').click()
|
|
|
|
await importPreset(page, TEST_PRESET)
|
|
|
|
// Verify active preset switched to test-preset
|
|
const presetTrigger = page
|
|
.locator('#keybinding-panel-actions')
|
|
.locator('button[role="combobox"]')
|
|
await expect(presetTrigger).toContainText('test-preset')
|
|
|
|
// Wait for toast to auto-dismiss
|
|
await expect(comfyPage.toast.visibleToasts).toHaveCount(0)
|
|
|
|
// Export via ellipsis menu
|
|
await menuButton.click()
|
|
const downloadPromise = page.waitForEvent('download')
|
|
await page.getByRole('menuitem', { name: /Export preset/i }).click()
|
|
const download = await downloadPromise
|
|
|
|
// Verify filename contains test-preset
|
|
expect(download.suggestedFilename()).toContain('test-preset')
|
|
|
|
// Close settings
|
|
await page.keyboard.press('Escape')
|
|
await comfyPage.settingDialog.waitForHidden()
|
|
|
|
// Verify the downloaded file is valid JSON with correct structure
|
|
const downloadPath = await download.path()
|
|
expect(downloadPath).toBeTruthy()
|
|
const content = fs.readFileSync(downloadPath!, 'utf-8')
|
|
const parsed = JSON.parse(content) as {
|
|
name: string
|
|
newBindings: unknown[]
|
|
unsetBindings: unknown[]
|
|
}
|
|
expect(parsed).toHaveProperty('name')
|
|
expect(parsed).toHaveProperty('newBindings')
|
|
expect(parsed).toHaveProperty('unsetBindings')
|
|
expect(parsed.name).toBe('test-preset')
|
|
})
|
|
|
|
test('Can delete an imported preset', async ({ comfyPage }) => {
|
|
test.setTimeout(30000)
|
|
const { page } = comfyPage
|
|
const menuButton = page.getByTestId('keybinding-preset-menu')
|
|
|
|
// Open keybinding settings panel
|
|
await comfyPage.settingDialog.open()
|
|
await comfyPage.settingDialog.category('Keybinding').click()
|
|
|
|
await importPreset(page, TEST_PRESET)
|
|
|
|
// Verify active preset switched to test-preset
|
|
const presetTrigger = page
|
|
.locator('#keybinding-panel-actions')
|
|
.locator('button[role="combobox"]')
|
|
await expect(presetTrigger).toContainText('test-preset')
|
|
|
|
// Wait for toast to auto-dismiss
|
|
await expect(comfyPage.toast.visibleToasts).toHaveCount(0)
|
|
|
|
// Delete via ellipsis menu
|
|
await menuButton.click()
|
|
await page.getByRole('menuitem', { name: /Delete preset/i }).click()
|
|
|
|
// Confirm deletion in the dialog
|
|
const confirmDialog = page.getByRole('dialog', {
|
|
name: /Delete the current preset/i
|
|
})
|
|
await confirmDialog.getByRole('button', { name: /Delete/i }).click()
|
|
|
|
// Verify preset trigger now shows Default Preset
|
|
await expect(presetTrigger).toContainText('Default Preset')
|
|
|
|
// Close settings
|
|
await page.keyboard.press('Escape')
|
|
await comfyPage.settingDialog.waitForHidden()
|
|
})
|
|
|
|
test('Can save modifications as a new preset', async ({ comfyPage }) => {
|
|
test.setTimeout(30000)
|
|
const { page } = comfyPage
|
|
const menuButton = page.getByTestId('keybinding-preset-menu')
|
|
|
|
// Open keybinding settings panel
|
|
await comfyPage.settingDialog.open()
|
|
await comfyPage.settingDialog.category('Keybinding').click()
|
|
|
|
await importPreset(page, TEST_PRESET)
|
|
|
|
// Verify active preset switched to test-preset
|
|
const presetTrigger = page
|
|
.locator('#keybinding-panel-actions')
|
|
.locator('button[role="combobox"]')
|
|
await expect(presetTrigger).toContainText('test-preset')
|
|
|
|
// Wait for toast to auto-dismiss
|
|
await expect(comfyPage.toast.visibleToasts).toHaveCount(0)
|
|
|
|
// Save as new preset via ellipsis menu
|
|
await menuButton.click()
|
|
await page.getByRole('menuitem', { name: /Save as new preset/i }).click()
|
|
|
|
// Fill in the preset name in the prompt dialog
|
|
const promptInput = page.locator('.prompt-dialog-content input')
|
|
await promptInput.fill('my-custom-preset')
|
|
await promptInput.press('Enter')
|
|
|
|
// Wait for toast to auto-dismiss
|
|
await expect(comfyPage.toast.visibleToasts).toHaveCount(0)
|
|
|
|
// Verify preset trigger shows my-custom-preset
|
|
await expect(presetTrigger).toContainText('my-custom-preset')
|
|
|
|
// Close settings
|
|
await page.keyboard.press('Escape')
|
|
await comfyPage.settingDialog.waitForHidden()
|
|
|
|
// Cleanup: delete the my-custom-preset file
|
|
await comfyPage.request.fetch(
|
|
`${comfyPage.url}/api/userdata/keybindings%2Fmy-custom-preset.json`,
|
|
{ method: 'DELETE' }
|
|
)
|
|
})
|
|
})
|