mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-19 22:09:37 +00:00
Compare commits
11 Commits
proxy-widg
...
e2e/proper
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ff153693e | ||
|
|
be2693f3b5 | ||
|
|
1d3d64c0eb | ||
|
|
8cecf0ffe9 | ||
|
|
32694573a9 | ||
|
|
337e71dce2 | ||
|
|
dca7c218a8 | ||
|
|
4d11359bea | ||
|
|
40961820d0 | ||
|
|
71324f2408 | ||
|
|
610165cde9 |
@@ -13,6 +13,7 @@ import { ComfyTemplates } from '../helpers/templates'
|
|||||||
import { ComfyMouse } from './ComfyMouse'
|
import { ComfyMouse } from './ComfyMouse'
|
||||||
import { VueNodeHelpers } from './VueNodeHelpers'
|
import { VueNodeHelpers } from './VueNodeHelpers'
|
||||||
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||||
|
import { ComfyPropertiesPanel } from './components/ComfyPropertiesPanel'
|
||||||
import { SettingDialog } from './components/SettingDialog'
|
import { SettingDialog } from './components/SettingDialog'
|
||||||
import {
|
import {
|
||||||
NodeLibrarySidebarTab,
|
NodeLibrarySidebarTab,
|
||||||
@@ -26,18 +27,6 @@ dotenv.config()
|
|||||||
|
|
||||||
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
|
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
|
||||||
|
|
||||||
class ComfyPropertiesPanel {
|
|
||||||
readonly root: Locator
|
|
||||||
readonly panelTitle: Locator
|
|
||||||
readonly searchBox: Locator
|
|
||||||
|
|
||||||
constructor(readonly page: Page) {
|
|
||||||
this.root = page.getByTestId('properties-panel')
|
|
||||||
this.panelTitle = this.root.locator('h3')
|
|
||||||
this.searchBox = this.root.getByPlaceholder('Search...')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ComfyMenu {
|
class ComfyMenu {
|
||||||
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
|
private _nodeLibraryTab: NodeLibrarySidebarTab | null = null
|
||||||
private _workflowsTab: WorkflowsSidebarTab | null = null
|
private _workflowsTab: WorkflowsSidebarTab | null = null
|
||||||
@@ -170,6 +159,7 @@ export class ComfyPage {
|
|||||||
public readonly settingDialog: SettingDialog
|
public readonly settingDialog: SettingDialog
|
||||||
public readonly confirmDialog: ConfirmDialog
|
public readonly confirmDialog: ConfirmDialog
|
||||||
public readonly vueNodes: VueNodeHelpers
|
public readonly vueNodes: VueNodeHelpers
|
||||||
|
public readonly propertiesPanel: ComfyPropertiesPanel
|
||||||
|
|
||||||
/** Worker index to test user ID */
|
/** Worker index to test user ID */
|
||||||
public readonly userIds: string[] = []
|
public readonly userIds: string[] = []
|
||||||
@@ -202,6 +192,7 @@ export class ComfyPage {
|
|||||||
this.settingDialog = new SettingDialog(page, this)
|
this.settingDialog = new SettingDialog(page, this)
|
||||||
this.confirmDialog = new ConfirmDialog(page)
|
this.confirmDialog = new ConfirmDialog(page)
|
||||||
this.vueNodes = new VueNodeHelpers(page)
|
this.vueNodes = new VueNodeHelpers(page)
|
||||||
|
this.propertiesPanel = new ComfyPropertiesPanel(page)
|
||||||
}
|
}
|
||||||
|
|
||||||
convertLeafToContent(structure: FolderStructure): FolderStructure {
|
convertLeafToContent(structure: FolderStructure): FolderStructure {
|
||||||
@@ -1614,7 +1605,7 @@ export class ComfyPage {
|
|||||||
(
|
(
|
||||||
await this.page.evaluate((title) => {
|
await this.page.evaluate((title) => {
|
||||||
return window['app'].graph.nodes
|
return window['app'].graph.nodes
|
||||||
.filter((n: LGraphNode) => n.title === title)
|
.filter((n: LGraphNode) => n.getTitle() === title)
|
||||||
.map((n: LGraphNode) => n.id)
|
.map((n: LGraphNode) => n.id)
|
||||||
}, title)
|
}, title)
|
||||||
).map((id: NodeId) => this.getNodeRefById(id))
|
).map((id: NodeId) => this.getNodeRefById(id))
|
||||||
|
|||||||
84
browser_tests/fixtures/components/ComfyPropertiesPanel.ts
Normal file
84
browser_tests/fixtures/components/ComfyPropertiesPanel.ts
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import type { Locator, Page } from '@playwright/test'
|
||||||
|
|
||||||
|
export class ComfyPropertiesPanel {
|
||||||
|
readonly root: Locator
|
||||||
|
readonly header: Locator
|
||||||
|
readonly panelTitle: Locator
|
||||||
|
readonly nodeTitleInput: Locator
|
||||||
|
readonly closeButton: Locator
|
||||||
|
readonly searchBox: Locator
|
||||||
|
readonly tabList: Locator
|
||||||
|
|
||||||
|
constructor(readonly page: Page) {
|
||||||
|
this.root = page.getByTestId('properties-panel')
|
||||||
|
this.header = this.root.locator('section').first()
|
||||||
|
this.panelTitle = this.root.locator('h3')
|
||||||
|
this.nodeTitleInput = this.root.getByTestId('node-title-input')
|
||||||
|
this.closeButton = this.root.getByRole('button', { name: 'Close' })
|
||||||
|
this.searchBox = this.root.getByPlaceholder('Search...')
|
||||||
|
this.tabList = this.root.locator('[role="tablist"]')
|
||||||
|
}
|
||||||
|
|
||||||
|
getTab(tabName: string): Locator {
|
||||||
|
return this.tabList.getByRole('tab', { name: tabName })
|
||||||
|
}
|
||||||
|
|
||||||
|
async clickTab(tabName: string) {
|
||||||
|
await this.getTab(tabName).click()
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
await this.closeButton.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
async editTitle(newTitle: string) {
|
||||||
|
await this.panelTitle.click()
|
||||||
|
await this.nodeTitleInput.fill(newTitle)
|
||||||
|
await this.nodeTitleInput.press('Enter')
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelTitleEdit() {
|
||||||
|
await this.panelTitle.click()
|
||||||
|
await this.nodeTitleInput.press('Escape')
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeSection(nodeTitle: string): Locator {
|
||||||
|
return this.root.locator(`[data-testid="node-section-${nodeTitle}"]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccordionItem(label: string): Locator {
|
||||||
|
return this.root.locator('.border-b', { hasText: label })
|
||||||
|
}
|
||||||
|
|
||||||
|
get globalSettingsSection() {
|
||||||
|
return {
|
||||||
|
nodes: this.getAccordionItem('Nodes'),
|
||||||
|
canvas: this.getAccordionItem('Canvas'),
|
||||||
|
connectionLinks: this.getAccordionItem('Connection Links'),
|
||||||
|
viewAllSettingsButton: this.root.getByRole('button', {
|
||||||
|
name: 'View all settings'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async toggleSwitch(switchLabel: string) {
|
||||||
|
const switchContainer = this.root.locator('label', {
|
||||||
|
hasText: switchLabel
|
||||||
|
})
|
||||||
|
const toggle = switchContainer.locator('button[role="switch"]')
|
||||||
|
await toggle.click()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSwitchState(switchLabel: string): Promise<boolean> {
|
||||||
|
const switchContainer = this.root.locator('label', {
|
||||||
|
hasText: switchLabel
|
||||||
|
})
|
||||||
|
const toggle = switchContainer.locator('button[role="switch"]')
|
||||||
|
const ariaChecked = await toggle.getAttribute('aria-checked')
|
||||||
|
return ariaChecked === 'true'
|
||||||
|
}
|
||||||
|
|
||||||
|
get noResultsMessage(): Locator {
|
||||||
|
return this.root.getByText('No results', { exact: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,24 +10,18 @@ test.describe('Properties panel', () => {
|
|||||||
|
|
||||||
await expect(propertiesPanel.panelTitle).toContainText('Workflow Overview')
|
await expect(propertiesPanel.panelTitle).toContainText('Workflow Overview')
|
||||||
|
|
||||||
await comfyPage.selectNodes(['KSampler', 'CLIP Text Encode (Prompt)'])
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
|
||||||
await expect(propertiesPanel.panelTitle).toContainText('3 items selected')
|
await expect(propertiesPanel.panelTitle).toContainText('2 items selected')
|
||||||
await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1)
|
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
|
||||||
await expect(
|
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
|
||||||
propertiesPanel.root.getByText('CLIP Text Encode (Prompt)')
|
|
||||||
).toHaveCount(2)
|
|
||||||
|
|
||||||
await propertiesPanel.searchBox.fill('seed')
|
await propertiesPanel.searchBox.fill('seed')
|
||||||
await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1)
|
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
|
||||||
await expect(
|
await expect(propertiesPanel.root.getByText('VAE Decode')).not.toBeVisible()
|
||||||
propertiesPanel.root.getByText('CLIP Text Encode (Prompt)')
|
|
||||||
).toHaveCount(0)
|
|
||||||
|
|
||||||
await propertiesPanel.searchBox.fill('')
|
await propertiesPanel.searchBox.fill('')
|
||||||
await expect(propertiesPanel.root.getByText('KSampler')).toHaveCount(1)
|
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
|
||||||
await expect(
|
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
|
||||||
propertiesPanel.root.getByText('CLIP Text Encode (Prompt)')
|
|
||||||
).toHaveCount(2)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
149
browser_tests/tests/propertiesPanel/propertiesPanelBasic.spec.ts
Normal file
149
browser_tests/tests/propertiesPanel/propertiesPanelBasic.spec.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Properties panel basic functionality', { tag: ['@ui'] }, () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.actionbar.propertiesButton.click()
|
||||||
|
await expect(comfyPage.propertiesPanel.root).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Panel visibility and toggle', () => {
|
||||||
|
test('opens panel via actionbar button', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await propertiesPanel.close()
|
||||||
|
await expect(propertiesPanel.root).not.toBeVisible()
|
||||||
|
|
||||||
|
await comfyPage.actionbar.propertiesButton.click()
|
||||||
|
await expect(propertiesPanel.root).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('closes panel via close button', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root).toBeVisible()
|
||||||
|
await propertiesPanel.closeButton.click()
|
||||||
|
await expect(propertiesPanel.root).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('persists open state after page reload', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root).toBeVisible()
|
||||||
|
|
||||||
|
await comfyPage.page.reload()
|
||||||
|
await comfyPage.setup()
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Workflow overview (no selection)', () => {
|
||||||
|
test('shows "Workflow Overview" title when nothing selected', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(propertiesPanel.panelTitle).toContainText(
|
||||||
|
'Workflow Overview'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows Parameters, Nodes, and Global Settings tabs when nothing selected', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Global Settings')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Nodes tab displays workflow nodes', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await propertiesPanel.clickTab('Nodes')
|
||||||
|
await expect(propertiesPanel.root).toContainText('KSampler')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Single node selection', () => {
|
||||||
|
test('updates title to node name when single node selected', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.panelTitle).not.toContainText(
|
||||||
|
'Workflow Overview'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows Parameters, Info, and Settings tabs for single node', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Info')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Nodes')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('Info tab shows node documentation', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
await propertiesPanel.clickTab('Info')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root).toContainText('KSampler')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows widget inputs in Parameters tab', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root.getByText('seed')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.root.getByText('steps')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.root.getByText('cfg')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Multiple node selection', () => {
|
||||||
|
test('shows item count in title for multiple selections', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.panelTitle).toContainText('items selected')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows Nodes and Settings tabs for multiple selection', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('lists all selected nodes in Nodes tab', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Properties panel global settings', { tag: ['@ui'] }, () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.actionbar.propertiesButton.click()
|
||||||
|
await expect(comfyPage.propertiesPanel.root).toBeVisible()
|
||||||
|
await comfyPage.propertiesPanel.clickTab('Global Settings')
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Global settings sections', () => {
|
||||||
|
test('displays Nodes section', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByRole('button', { name: 'NODES' })
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('displays Canvas section', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByRole('button', { name: 'CANVAS' })
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('displays Connection Links section', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByRole('button', { name: 'CONNECTION LINKS' })
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('has View all settings button', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const viewAllButton = propertiesPanel.root.getByRole('button', {
|
||||||
|
name: /View all settings/i
|
||||||
|
})
|
||||||
|
await expect(viewAllButton).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Nodes section settings', () => {
|
||||||
|
test('can toggle Show advanced parameters', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const toggle = propertiesPanel.root
|
||||||
|
.locator('label', { hasText: 'Show advanced parameters' })
|
||||||
|
.locator('button[role="switch"]')
|
||||||
|
|
||||||
|
await expect(toggle).toBeVisible()
|
||||||
|
|
||||||
|
const initialState = await toggle.getAttribute('aria-checked')
|
||||||
|
await toggle.click()
|
||||||
|
|
||||||
|
const newState = await toggle.getAttribute('aria-checked')
|
||||||
|
expect(newState).not.toBe(initialState)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can toggle Nodes 2.0 setting', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const toggle = propertiesPanel.root
|
||||||
|
.locator('label', { hasText: 'Nodes 2.0' })
|
||||||
|
.locator('button[role="switch"]')
|
||||||
|
|
||||||
|
await expect(toggle).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Canvas section settings', () => {
|
||||||
|
test('can adjust grid spacing', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const gridSpacingLabel = propertiesPanel.root.getByText('Grid Spacing')
|
||||||
|
await expect(gridSpacingLabel).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can toggle Snap nodes to grid', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const toggle = propertiesPanel.root
|
||||||
|
.locator('label', { hasText: 'Snap nodes to grid' })
|
||||||
|
.locator('button[role="switch"]')
|
||||||
|
|
||||||
|
await expect(toggle).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Connection Links section settings', () => {
|
||||||
|
test('has link shape selector', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const linkShapeLabel = propertiesPanel.root.getByText('Link Shape')
|
||||||
|
await expect(linkShapeLabel).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('can toggle Show connected links', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const toggle = propertiesPanel.root
|
||||||
|
.locator('label', { hasText: 'Show connected links' })
|
||||||
|
.locator('button[role="switch"]')
|
||||||
|
|
||||||
|
await expect(toggle).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('View all settings navigation', () => {
|
||||||
|
test('opens full settings dialog when clicking View all settings', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
const viewAllButton = propertiesPanel.root.getByRole('button', {
|
||||||
|
name: /View all settings/i
|
||||||
|
})
|
||||||
|
|
||||||
|
await viewAllButton.click()
|
||||||
|
|
||||||
|
await expect(comfyPage.settingDialog.root).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Properties panel search functionality', { tag: ['@ui'] }, () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.actionbar.propertiesButton.click()
|
||||||
|
await expect(comfyPage.propertiesPanel.root).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Search with multiple nodes selected', () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('filters widgets by search query', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await propertiesPanel.searchBox.fill('seed')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByText('VAE Decode')
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows all nodes when search is cleared', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await propertiesPanel.searchBox.fill('seed')
|
||||||
|
await propertiesPanel.searchBox.fill('')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.root.getByText('VAE Decode')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('shows no results message when search matches nothing', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await propertiesPanel.searchBox.fill('nonexistent_widget_xyz')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.noResultsMessage).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Search with single node selected', () => {
|
||||||
|
test('filters widgets within single node', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
await propertiesPanel.searchBox.fill('cfg')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.root.getByText('cfg')).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByText('seed', { exact: true })
|
||||||
|
).not.toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Search in workflow overview mode', () => {
|
||||||
|
test('Nodes tab has search functionality', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await propertiesPanel.clickTab('Nodes')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.searchBox).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
118
browser_tests/tests/propertiesPanel/propertiesPanelTabs.spec.ts
Normal file
118
browser_tests/tests/propertiesPanel/propertiesPanelTabs.spec.ts
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Properties panel tab navigation', { tag: ['@ui'] }, () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.actionbar.propertiesButton.click()
|
||||||
|
await expect(comfyPage.propertiesPanel.root).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Tab switching', () => {
|
||||||
|
test('switches between tabs in workflow overview mode', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
|
||||||
|
|
||||||
|
await propertiesPanel.clickTab('Nodes')
|
||||||
|
await expect(propertiesPanel.root.getByText('KSampler')).toBeVisible()
|
||||||
|
|
||||||
|
await propertiesPanel.clickTab('Global Settings')
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByRole('button', { name: 'NODES' })
|
||||||
|
).toBeVisible()
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByRole('button', { name: 'CANVAS' })
|
||||||
|
).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('switches between tabs in single node mode', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Info')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
|
||||||
|
|
||||||
|
await propertiesPanel.clickTab('Info')
|
||||||
|
await expect(propertiesPanel.root).toContainText('KSampler')
|
||||||
|
|
||||||
|
await propertiesPanel.clickTab('Settings')
|
||||||
|
await expect(
|
||||||
|
propertiesPanel.root.getByText('Color').first()
|
||||||
|
).toBeVisible()
|
||||||
|
|
||||||
|
await propertiesPanel.clickTab('Parameters')
|
||||||
|
await expect(propertiesPanel.root.getByText('seed')).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Tab availability based on selection', () => {
|
||||||
|
test('shows different tabs based on selection state', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Global Settings')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Info')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Nodes')).not.toBeVisible()
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
await expect(propertiesPanel.getTab('Nodes')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('first tab updates for multiple selection', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
await expect(propertiesPanel.getTab('Parameters')).toBeVisible()
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
const firstTab = propertiesPanel.tabList.locator('[role="tab"]').first()
|
||||||
|
await expect(firstTab).toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Tab persistence', () => {
|
||||||
|
test('remembers active tab when selection changes', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
await propertiesPanel.clickTab('Settings')
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['VAE Decode'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Settings')).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('falls back to default tab when current tab not available', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
await propertiesPanel.clickTab('Info')
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
|
||||||
|
await expect(propertiesPanel.getTab('Info')).not.toBeVisible()
|
||||||
|
const firstTab = propertiesPanel.tabList.locator('[role="tab"]').first()
|
||||||
|
await expect(firstTab).toHaveAttribute('aria-selected', 'true')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
import { expect } from '@playwright/test'
|
||||||
|
|
||||||
|
import { comfyPageFixture as test } from '../../fixtures/ComfyPage'
|
||||||
|
|
||||||
|
test.describe('Properties panel title editing', { tag: ['@ui'] }, () => {
|
||||||
|
test.beforeEach(async ({ comfyPage }) => {
|
||||||
|
await comfyPage.actionbar.propertiesButton.click()
|
||||||
|
await expect(comfyPage.propertiesPanel.root).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Single node title editing', () => {
|
||||||
|
test('shows editable title for single node selection', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
await propertiesPanel.panelTitle.click()
|
||||||
|
|
||||||
|
await expect(propertiesPanel.nodeTitleInput).toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('edits node title successfully', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
|
||||||
|
const newTitle = 'My Custom KSampler'
|
||||||
|
|
||||||
|
await propertiesPanel.panelTitle.click()
|
||||||
|
await propertiesPanel.nodeTitleInput.fill(newTitle)
|
||||||
|
await propertiesPanel.nodeTitleInput.press('Enter')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.panelTitle).toContainText(newTitle)
|
||||||
|
|
||||||
|
const renamedNodes = await comfyPage.getNodeRefsByTitle(newTitle)
|
||||||
|
expect(renamedNodes.length).toBeGreaterThan(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('cancels title edit with Escape', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
|
||||||
|
const originalTitle = await propertiesPanel.panelTitle.innerText()
|
||||||
|
|
||||||
|
await propertiesPanel.panelTitle.click()
|
||||||
|
await propertiesPanel.nodeTitleInput.fill('Should Not Be Saved')
|
||||||
|
await propertiesPanel.nodeTitleInput.press('Escape')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.panelTitle).toContainText(originalTitle)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not save empty title', async ({ comfyPage }) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler'])
|
||||||
|
|
||||||
|
const originalTitle = await propertiesPanel.panelTitle.innerText()
|
||||||
|
|
||||||
|
await propertiesPanel.panelTitle.click()
|
||||||
|
await propertiesPanel.nodeTitleInput.fill('')
|
||||||
|
await propertiesPanel.nodeTitleInput.press('Enter')
|
||||||
|
|
||||||
|
await expect(propertiesPanel.panelTitle).toContainText(originalTitle)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test.describe('Title editing restrictions', () => {
|
||||||
|
test('does not allow title editing for multiple selection', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await comfyPage.selectNodes(['KSampler', 'VAE Decode'])
|
||||||
|
await propertiesPanel.panelTitle.click()
|
||||||
|
|
||||||
|
await expect(propertiesPanel.nodeTitleInput).not.toBeVisible()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('does not allow title editing for workflow overview', async ({
|
||||||
|
comfyPage
|
||||||
|
}) => {
|
||||||
|
const { propertiesPanel } = comfyPage
|
||||||
|
|
||||||
|
await propertiesPanel.panelTitle.click()
|
||||||
|
|
||||||
|
await expect(propertiesPanel.nodeTitleInput).not.toBeVisible()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
Reference in New Issue
Block a user