Skip to content
Merged
17 changes: 17 additions & 0 deletions browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,20 @@ class ConfirmDialog {
const loc = this[locator]
await expect(loc).toBeVisible()
await loc.click()

// Wait for the dialog mask to disappear after confirming
const mask = this.page.locator('.p-dialog-mask')
const count = await mask.count()
if (count > 0) {
await mask.first().waitFor({ state: 'hidden', timeout: 3000 })
}

// Wait for workflow service to finish if it's busy
await this.page.waitForFunction(
() => !window['app']?.extensionManager?.workflow?.isBusy,
undefined,
{ timeout: 3000 }
)
}
}

Expand Down Expand Up @@ -242,6 +256,9 @@ export class ComfyPage {
await this.page.evaluate(async () => {
await window['app'].extensionManager.workflow.syncWorkflows()
})

// Wait for Vue to re-render the workflow list
await this.nextFrame()
}

async setupUser(username: string) {
Expand Down
7 changes: 7 additions & 0 deletions browser_tests/fixtures/components/SidebarTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,13 @@ export class WorkflowsSidebarTab extends SidebarTab {
.click()
await this.page.keyboard.type(newName)
await this.page.keyboard.press('Enter')

// Wait for workflow service to finish renaming
await this.page.waitForFunction(
() => !window['app']?.extensionManager?.workflow?.isBusy,
undefined,
{ timeout: 3000 }
)
}

async insertWorkflow(locator: Locator) {
Expand Down
46 changes: 42 additions & 4 deletions browser_tests/fixtures/components/Topbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,26 @@ export class Topbar {
)
// Wait for the dialog to close.
await this.getSaveDialog().waitFor({ state: 'hidden', timeout: 500 })

// Check if a confirmation dialog appeared (e.g., "Overwrite existing file?")
// If so, return early to let the test handle the confirmation
const confirmationDialog = this.page.locator(
'.p-dialog:has-text("Overwrite")'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

)
if (await confirmationDialog.isVisible()) {
return
}
}

async openTopbarMenu() {
// If menu is already open, close it first to reset state
const isAlreadyOpen = await this.menuLocator.isVisible()
if (isAlreadyOpen) {
// Click outside the menu to close it properly
await this.page.locator('body').click({ position: { x: 500, y: 300 } })
await this.menuLocator.waitFor({ state: 'hidden', timeout: 1000 })
}
Comment on lines 106 to +113
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using a more reliable method to close the menu.

Clicking on body at hardcoded coordinates { x: 500, y: 300 } works but may be fragile across different viewport sizes or layouts. This same pattern appears in the retry logic as well.

Consider one of these alternatives:

  • Click on a specific element known to be outside the menu (e.g., a canvas or sidebar element)
  • Use page.mouse.click() at a calculated position relative to the menu bounds
  • Press the Escape key if the menu supports keyboard dismissal

Example:

// If menu is already open, close it first to reset state
const isAlreadyOpen = await this.menuLocator.isVisible()
if (isAlreadyOpen) {
  // Press Escape to close the menu
  await this.page.keyboard.press('Escape')
  await this.menuLocator.waitFor({ state: 'hidden', timeout: 1000 })
}
🤖 Prompt for AI Agents
In browser_tests/fixtures/components/Topbar.ts around lines 106 to 113, the code
uses a hardcoded body.click at { x: 500, y: 300 } to close the menu which is
fragile; replace that with a reliable dismissal strategy (choose one): press the
Escape key via page.keyboard.press('Escape') and wait for menuLocator to be
hidden, or click a stable off-menu element (e.g., a known canvas or sidebar
locator) using its locator.click(), or compute an off-menu coordinate using
this.menuLocator.boundingBox() and use page.mouse.click() at a position
calculated relative to the menu bounds; apply the same replacement to the retry
logic elsewhere so all hardcoded-body clicks are removed and waiting for
menuLocator to be hidden is preserved.


await this.menuTrigger.click()
await this.menuLocator.waitFor({ state: 'visible' })
return this.menuLocator
Expand Down Expand Up @@ -146,6 +163,9 @@ export class Topbar {
throw new Error('Path cannot be empty')
}

// Ensure no dialog masks are blocking before menu interaction
// await this.waitForDialogMaskHidden()

const menu = await this.openTopbarMenu()
const tabName = path[0]
const topLevelMenuItem = this.getMenuItem(tabName)
Expand All @@ -162,15 +182,33 @@ export class Topbar {

await topLevelMenu.hover()

// Hover over top-level menu with retry logic for flaky submenu appearance
const submenu = this.getVisibleSubmenu()
Comment on lines +182 to +183
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the appearance nondeterministic?

try {
await submenu.waitFor({ state: 'visible', timeout: 1000 })
} catch {
// Click outside to reset, then reopen menu
await this.page.locator('body').click({ position: { x: 500, y: 300 } })
await this.menuLocator.waitFor({ state: 'hidden', timeout: 1000 })
await this.menuTrigger.click()
await this.menuLocator.waitFor({ state: 'visible' })
}

let currentMenu = topLevelMenu
for (let i = 1; i < path.length; i++) {
const commandName = path[i]
const menuItem = currentMenu
.locator(
`.p-tieredmenu-submenu .p-tieredmenu-item:has-text("${commandName}")`
)
const menuItem = submenu
.locator(`.p-tieredmenu-item:has-text("${commandName}")`)
.first()
await menuItem.waitFor({ state: 'visible' })

// For the last item, click it
if (i === path.length - 1) {
await menuItem.click()
return
}

// Otherwise, hover to open nested submenu
await menuItem.hover()
currentMenu = menuItem
}
Expand Down
10 changes: 10 additions & 0 deletions browser_tests/tests/sidebar/workflows.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,12 @@ test.describe('Workflows sidebar', () => {

await comfyPage.menu.workflowsTab.open()

// Wait for workflow to appear in Browse section after sync
const workflowItem = comfyPage.page.locator(
'.comfyui-workflows-browse .node-label:has-text("workflow1.json")'
)
await workflowItem.waitFor({ state: 'visible', timeout: 3000 })

const nodeCount = await comfyPage.getGraphNodesCount()

// Get the bounding box of the canvas element
Expand All @@ -358,6 +364,10 @@ test.describe('Workflows sidebar', () => {
'#graph-canvas',
{ targetPosition }
)

// Wait for nodes to be inserted after drag-drop
await comfyPage.nextFrame()

expect(await comfyPage.getGraphNodesCount()).toBe(nodeCount * 2)
})
})
Loading