Files
ComfyUI_frontend/browser_tests/fixtures/components/SidebarTab.ts
Alexander Brown 5bd37bef99 fix: browser test fixes for flaky selectors and missing setup
- SidebarTab: Use placeholder selector instead of test ID for search input
- KeyboardHelper: Remove redundant canvas fallback (null is valid)
- groupNode: Use variable for category name instead of hardcoded string
- nodeLibrary: Ensure tab is open before filling search input
- subgraph: Reload workflow after page reload to restore state
- useSettingSearch: Use role selector and simplify selected class assertion

Amp-Thread-ID: https://ampcode.com/threads/T-019c18c2-667a-7179-bb2d-86f35cd1d7b0
Co-authored-by: Amp <amp@ampcode.com>
2026-02-01 02:39:59 -08:00

171 lines
4.1 KiB
TypeScript

import type { Locator, Page } from '@playwright/test'
import type { WorkspaceStore } from '../../types/globals'
import { TestIds } from '../selectors'
class SidebarTab {
constructor(
public readonly page: Page,
public readonly tabId: string
) {}
get tabButton() {
return this.page.locator(`.${this.tabId}-tab-button`)
}
get selectedTabButton() {
return this.page.locator(
`.${this.tabId}-tab-button.side-bar-button-selected`
)
}
async open() {
if (await this.selectedTabButton.isVisible()) {
return
}
await this.tabButton.click()
}
async close() {
if (!this.tabButton.isVisible()) {
return
}
await this.tabButton.click()
}
}
export class NodeLibrarySidebarTab extends SidebarTab {
constructor(public override readonly page: Page) {
super(page, 'node-library')
}
get nodeLibrarySearchBoxInput() {
return this.page.getByPlaceholder('Search Nodes...')
}
get nodeLibraryTree() {
return this.page.getByTestId(TestIds.sidebar.nodeLibrary)
}
get nodePreview() {
return this.page.locator('.node-lib-node-preview')
}
get tabContainer() {
return this.page.locator('.sidebar-content-container')
}
get newFolderButton() {
return this.tabContainer.locator('.new-folder-button')
}
override async open() {
await super.open()
await this.nodeLibraryTree.waitFor({ state: 'visible' })
}
override async close() {
if (!this.tabButton.isVisible()) {
return
}
await this.tabButton.click()
await this.nodeLibraryTree.waitFor({ state: 'hidden' })
}
getFolder(folderName: string) {
return this.page.locator(
`[data-testid="node-tree-folder"][data-folder-name="${folderName}"]`
)
}
getNode(nodeName: string) {
return this.page.locator(
`[data-testid="node-tree-leaf"][data-node-name="${nodeName}"]`
)
}
nodeSelector(nodeName: string): string {
return `[data-testid="node-tree-leaf"][data-node-name="${nodeName}"]`
}
folderSelector(folderName: string): string {
return `[data-testid="node-tree-folder"][data-folder-name="${folderName}"]`
}
getNodeInFolder(nodeName: string, folderName: string) {
return this.getFolder(folderName)
.locator('xpath=ancestor::li')
.locator(`[data-testid="node-tree-leaf"][data-node-name="${nodeName}"]`)
}
}
export class WorkflowsSidebarTab extends SidebarTab {
constructor(public override readonly page: Page) {
super(page, 'workflows')
}
get root() {
return this.page.getByTestId(TestIds.sidebar.workflows)
}
async getOpenedWorkflowNames() {
return await this.root
.locator('.comfyui-workflows-open .node-label')
.allInnerTexts()
}
async getActiveWorkflowName() {
return await this.root
.locator('.comfyui-workflows-open .p-tree-node-selected .node-label')
.innerText()
}
async getTopLevelSavedWorkflowNames() {
return await this.root
.locator('.comfyui-workflows-browse .node-label')
.allInnerTexts()
}
async switchToWorkflow(workflowName: string) {
const workflowLocator = this.getOpenedItem(workflowName)
await workflowLocator.click()
}
getOpenedItem(name: string) {
return this.root.locator('.comfyui-workflows-open .node-label', {
hasText: name
})
}
getPersistedItem(name: string) {
return this.root.locator('.comfyui-workflows-browse .node-label', {
hasText: name
})
}
async renameWorkflow(locator: Locator, newName: string) {
await locator.click({ button: 'right' })
await this.page
.locator('.p-contextmenu-item-content', { hasText: 'Rename' })
.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 as WorkspaceStore | undefined)?.workflow
?.isBusy,
undefined,
{ timeout: 3000 }
)
}
async insertWorkflow(locator: Locator) {
await locator.click({ button: 'right' })
await this.page
.locator('.p-contextmenu-item-content', { hasText: 'Insert' })
.click()
}
}