Files
ComfyUI_frontend/browser_tests/helpers/builderTestUtils.ts
Alexander Brown f1bb756929 fix: remove redundant and counterproductive e2e timeout overrides (#11110)
## 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>
2026-04-10 19:47:20 +00:00

143 lines
4.3 KiB
TypeScript

import { expect } from '@playwright/test'
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
import type { AppModeHelper } from '@e2e/fixtures/helpers/AppModeHelper'
import type { NodeReference } from '@e2e/fixtures/utils/litegraphUtils'
import { comfyExpect } from '@e2e/fixtures/ComfyPage'
import { fitToViewInstant } from '@e2e/helpers/fitToView'
interface BuilderSetupResult {
inputNodeTitle: string
widgetNames: string[]
}
/**
* Enter builder on the default workflow and select I/O.
*
* Loads the default workflow, optionally transforms it (e.g. convert a node
* to subgraph), then enters builder mode and selects inputs + outputs.
*
* @param comfyPage - The page fixture.
* @param prepareGraph - Optional callback to transform the graph before
* entering builder. Receives the KSampler node ref and returns the
* input node title and widget names to select.
* Defaults to KSampler with its first widget.
* Mutually exclusive with widgetNames.
* @param widgetNames - Widget names to select from the KSampler node.
* Only used when prepareGraph is not provided.
* Mutually exclusive with prepareGraph.
*/
export async function setupBuilder(
comfyPage: ComfyPage,
prepareGraph?: (ksampler: NodeReference) => Promise<BuilderSetupResult>,
widgetNames?: string[]
): Promise<void> {
const { appMode } = comfyPage
await comfyPage.workflow.loadWorkflow('default')
const ksampler = await comfyPage.nodeOps.getNodeRefById('3')
const { inputNodeTitle, widgetNames: inputWidgets } = prepareGraph
? await prepareGraph(ksampler)
: { inputNodeTitle: 'KSampler', widgetNames: widgetNames ?? ['seed'] }
await fitToViewInstant(comfyPage)
await appMode.enterBuilder()
await appMode.steps.goToInputs()
for (const name of inputWidgets) {
await appMode.select.selectInputWidget(inputNodeTitle, name)
}
await appMode.steps.goToOutputs()
await appMode.select.selectOutputNode('Save Image')
}
/**
* Convert the KSampler to a subgraph, then enter builder with I/O selected.
*/
export async function setupSubgraphBuilder(
comfyPage: ComfyPage
): Promise<void> {
await setupBuilder(comfyPage, async (ksampler) => {
await ksampler.click('title')
await ksampler.convertToSubgraph()
await comfyPage.nextFrame()
return {
inputNodeTitle: 'New Subgraph',
widgetNames: ['seed']
}
})
}
/**
* Open the save-as dialog, fill name + view type, click save,
* and wait for the success dialog.
*/
export async function builderSaveAs(
appMode: AppModeHelper,
workflowName: string,
viewType: 'App' | 'Node graph' = 'App'
) {
await appMode.footer.saveAsButton.click()
await comfyExpect(appMode.saveAs.nameInput).toBeVisible()
await appMode.saveAs.fillAndSave(workflowName, viewType)
await comfyExpect(appMode.saveAs.successMessage).toBeVisible()
}
/**
* Load a different workflow, then reopen the named one from the sidebar.
* Caller must ensure the page is in graph mode (not builder or app mode)
* before calling.
*/
export async function openWorkflowFromSidebar(
comfyPage: ComfyPage,
name: string
) {
await comfyPage.workflow.loadWorkflow('default')
await comfyPage.nextFrame()
const { workflowsTab } = comfyPage.menu
await workflowsTab.open()
await workflowsTab.getPersistedItem(name).dblclick()
await comfyPage.nextFrame()
await expect
.poll(() => comfyPage.workflow.getActiveWorkflowPath())
.toContain(name)
}
/** Save the workflow, reopen it, and enter app mode. */
export async function saveAndReopenInAppMode(
comfyPage: ComfyPage,
workflowName: string
) {
await comfyPage.menu.topbar.saveWorkflow(workflowName)
const { workflowsTab } = comfyPage.menu
await workflowsTab.open()
await workflowsTab.getPersistedItem(workflowName).dblclick()
await comfyPage.nextFrame()
await comfyPage.appMode.toggleAppMode()
}
/**
* Enter builder, select the given widgets as inputs + SaveImage as output,
* save as an app, and close the success dialog.
*
* Returns on the builder arrange/preview step.
*/
export async function createAndSaveApp(
comfyPage: ComfyPage,
appName: string,
widgetNames: string[] = ['seed']
): Promise<void> {
await setupBuilder(comfyPage, undefined, widgetNames)
await comfyPage.appMode.steps.goToPreview()
await builderSaveAs(comfyPage.appMode, appName)
await comfyPage.appMode.saveAs.closeButton.click()
await comfyPage.nextFrame()
}