mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 14:45:36 +00:00
Default search box settings are a little inconvenient to work with. This PR introduces a new `addNode` utility function to the V2 search fixture that handles all the steps of opening search and adding a node that a user would perform. It then migrates several PRs I have recently written to use this new fixture. See also #12029 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12382-Use-utility-function-to-add-node-with-V2-search-3666d73d3650817c8c73c9104b1113bf) by [Unito](https://www.unito.io)
126 lines
4.4 KiB
TypeScript
126 lines
4.4 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
import type { Locator } from '@playwright/test'
|
|
|
|
import type { RootCategoryId } from '@/components/searchbox/v2/rootCategories'
|
|
import type { ComfyPage } from '@e2e/fixtures/ComfyPage'
|
|
import { TestIds } from '@e2e/fixtures/selectors'
|
|
import type { Position } from '@e2e/fixtures/types'
|
|
|
|
const { searchBoxV2 } = TestIds
|
|
|
|
export type { RootCategoryId }
|
|
|
|
export class ComfyNodeSearchBoxV2 {
|
|
readonly dialog: Locator
|
|
readonly input: Locator
|
|
readonly filterSearch: Locator
|
|
readonly results: Locator
|
|
readonly filterOptions: Locator
|
|
readonly filterChips: Locator
|
|
readonly noResults: Locator
|
|
readonly nodeIdBadge: Locator
|
|
readonly sidebarToggle: Locator
|
|
readonly sidebarBackdrop: Locator
|
|
readonly filterChipsScroll: Locator
|
|
|
|
constructor(private comfyPage: ComfyPage) {
|
|
const page = comfyPage.page
|
|
this.dialog = page.getByRole('search')
|
|
this.input = this.dialog.getByRole('combobox')
|
|
this.filterSearch = this.dialog.getByRole('textbox', { name: 'Search' })
|
|
this.results = this.dialog.getByTestId(searchBoxV2.resultItem)
|
|
this.filterOptions = this.dialog.getByTestId(searchBoxV2.filterOption)
|
|
this.filterChips = this.dialog.getByTestId(searchBoxV2.filterChip)
|
|
this.noResults = this.dialog.getByTestId(searchBoxV2.noResults)
|
|
this.nodeIdBadge = this.dialog.getByTestId(searchBoxV2.nodeIdBadge)
|
|
this.sidebarToggle = this.dialog.getByTestId(searchBoxV2.sidebarToggle)
|
|
this.sidebarBackdrop = this.dialog.getByTestId(searchBoxV2.sidebarBackdrop)
|
|
this.filterChipsScroll = this.dialog.getByTestId(
|
|
searchBoxV2.filterChipsScroll
|
|
)
|
|
}
|
|
|
|
/** Sidebar category tree button (e.g. `sampling`, `sampling/custom_sampling`). */
|
|
categoryButton(categoryId: string): Locator {
|
|
return this.dialog.getByTestId(searchBoxV2.category(categoryId))
|
|
}
|
|
|
|
/** Top filter-bar root category chip (e.g. `comfy`, `essentials`). */
|
|
rootCategoryButton(id: RootCategoryId): Locator {
|
|
return this.dialog.getByTestId(searchBoxV2.rootCategory(id))
|
|
}
|
|
|
|
/** Top filter-bar input/output type popover trigger. */
|
|
typeFilterButton(key: 'input' | 'output'): Locator {
|
|
return this.dialog.getByTestId(searchBoxV2.typeFilter(key))
|
|
}
|
|
|
|
async applyTypeFilter(
|
|
key: 'input' | 'output',
|
|
typeName: string
|
|
): Promise<void> {
|
|
const trigger = this.typeFilterButton(key)
|
|
await trigger.click()
|
|
await this.filterOptions.first().waitFor({ state: 'visible' })
|
|
await this.filterSearch.fill(typeName)
|
|
await this.filterOptions.filter({ hasText: typeName }).first().click()
|
|
// The popover does not auto-close on selection — toggle the trigger.
|
|
await trigger.click()
|
|
await this.filterOptions.first().waitFor({ state: 'hidden' })
|
|
}
|
|
|
|
async removeFilterChip(index = 0): Promise<void> {
|
|
await this.filterChips
|
|
.nth(index)
|
|
.getByTestId(searchBoxV2.chipDelete)
|
|
.click()
|
|
}
|
|
|
|
async toggle(): Promise<void> {
|
|
await this.comfyPage.command.executeCommand('Workspace.SearchBox.Toggle')
|
|
}
|
|
|
|
async open(): Promise<void> {
|
|
if (await this.input.isVisible()) return
|
|
await this.toggle()
|
|
await this.input.waitFor({ state: 'visible' })
|
|
}
|
|
|
|
async openByDoubleClickCanvas(position?: Position) {
|
|
const { x, y } = position ?? { x: 200, y: 200 }
|
|
// Use page.mouse.dblclick (not canvas.dblclick) so the z-999 Vue overlay
|
|
// does not intercept; coords target a viewport spot that is on the canvas
|
|
// and clear of both the side toolbar and any default-graph nodes.
|
|
await this.comfyPage.page.mouse.dblclick(x, y, { delay: 5 })
|
|
}
|
|
|
|
async ensureV2Search(): Promise<void> {
|
|
await this.comfyPage.settings.setSetting(
|
|
'Comfy.NodeSearchBoxImpl',
|
|
'default'
|
|
)
|
|
}
|
|
|
|
async setup(): Promise<void> {
|
|
await this.ensureV2Search()
|
|
await this.comfyPage.settings.setSetting(
|
|
'Comfy.LinkRelease.Action',
|
|
'search box'
|
|
)
|
|
await this.comfyPage.settings.setSetting(
|
|
'Comfy.LinkRelease.ActionShift',
|
|
'search box'
|
|
)
|
|
}
|
|
|
|
async addNode(query: string, options: { position?: Position } = {}) {
|
|
const position = options.position ?? { x: 200, y: 200 }
|
|
await this.openByDoubleClickCanvas(position)
|
|
await this.input.fill(query)
|
|
await expect(this.results.first()).toContainText(query)
|
|
await this.comfyPage.page.keyboard.press('Enter')
|
|
await expect(this.dialog).toBeHidden()
|
|
await this.comfyPage.page.mouse.click(position.x, position.y)
|
|
}
|
|
}
|