Compare commits

...

28 Commits

Author SHA1 Message Date
filtered
782d93a7a0 Add awaits to various tests 2024-11-15 01:28:48 +11:00
filtered
7be14c5189 Add nodeTemplate tests
Test failure confirmed when links are not connected
2024-11-15 01:28:18 +11:00
Chenlei Hu
ee5c127146 1.3.43 (#1536) 2024-11-13 19:01:44 -05:00
Chenlei Hu
acba6097e0 Replace electron API mocks with actual electron API impl (#1535)
* link electron types locally

* Update electronAPI calls

* Fix source validation

* Payload to raw

* nit

* Update electron types
2024-11-13 17:20:18 -05:00
filtered
82d00a1bcf Update Template copy & paste (#1533)
* Split original clipboard functions out

* Add version check for templates

* Fix regression in use template undo steps
2024-11-13 17:04:31 -05:00
Chenlei Hu
b9224464c0 Fix reverse proxy (#1532) 2024-11-13 15:36:35 -05:00
Chenlei Hu
fba9a03df3 Lazy load setting dialog tabs (#1530) 2024-11-13 10:56:48 -05:00
Chenlei Hu
2fd624cd3d [skip ci] Update README.md (#1529)
Replace screenshot with actual logs for better accessibility.
2024-11-13 10:37:12 -05:00
Chenlei Hu
095fe2a175 Allow access of dev server in LAN for touch device testing (#1528) 2024-11-13 10:34:36 -05:00
Lasse Lauwerys
d838777e04 Touch support bug fixes (#1527)
* Improved touch support

* Fix touch support scaling error

* Fix touch scaling precision on all zoom levels

* Improved touch experiene, fixed zooming on textarea elements and fixed context menu.

* Minor bug fix
2024-11-13 10:14:11 -05:00
filtered
7e0d1d441d Flaky tests and observable state (#1526)
* Fix missing await

* Fix flaky tests - keyboard combos

Old code is causing playwright &/ changeTracker to add an undo step.  Using combo mode resolves flakiness until that can be investigated thoroughly.

* Restore skipped tests

* Fix flaky tests

* Async clean up

* Fix test always fails on retry

* Add TS types (tests)

* Fix flaky test

* Add observable busy state to workflow store

* Add workflow store busy wait to tests

* Rename test for clarity

* Fix flaky tests - use press() from locator API

Ref: https://playwright.dev/docs/api/class-keyboard#keyboard-press

* Fix flaky test - wait next frame

* Add delay between mouse events

Litegraph pointer handling is all custom coded, so a adding a delay between events for a bit of reality is actually beneficial.
2024-11-13 09:35:22 -05:00
Chenlei Hu
ddab149f16 1.3.42 (#1524) 2024-11-12 23:13:49 -05:00
Chenlei Hu
a73fdcd3bd Fix sidebar splitter state (#1523) 2024-11-12 23:12:56 -05:00
filtered
d6e0c197bd Decouple group node from Litegraph copy & paste (#1522)
* nit - Refactor

* Add old clipboard code to groupNode

* [Refactor] groupNode copy / paste functions

* Clarify function name
2024-11-12 23:11:04 -05:00
Chenlei Hu
3117d0fdc1 Fix loading of model library in non-electron env (#1521) 2024-11-12 22:38:29 -05:00
Chenlei Hu
96fda64b70 Fix queue button overlaped by pysssss.ImageFeed (#1520) 2024-11-12 21:35:14 -05:00
oto-ciulis-tt
e3d2c3a814 feat: Add download progress to sidebar (#1490)
* feat: Add download progress to sidebar

* Removing console log

* Lint fixes

* Updating UI

* Fixing lint error

* Fixing lint error

* Fixing lint error

* PR comments

* Reverting change

---------

Co-authored-by: Oto Ciulis <oto.ciulis@gmail.com>
2024-11-12 16:28:55 -05:00
Lasse Lauwerys
1a8900de1f Improved touch support (#1519)
* Improved touch support

* Fix touch support scaling error
2024-11-12 16:19:59 -05:00
Chenlei Hu
05ba526388 Type DOMWidget and DOMWidgetOptions (#1517)
* Type DOMWidget and DOMWidgetOptions

* Annotate widget value type
2024-11-12 13:35:24 -05:00
Chenlei Hu
4bc79181ae Move DOMClippingEnabled to coreSettings.ts (#1516)
* Move DOMClippingEnabled to coreSettings.ts

* nit
2024-11-12 12:01:44 -05:00
filtered
feafbf9cbf Litegraph Reroute Beta (#1421)
* Add Reroute support - ConnectingLinkImpl

Bonus: TS strict

* Add Reroute support

* Remove unused TS expect error

* Add reroute beta opt-in option

* Add settings option: Middle-click reroute node

* Add settings: Link Markers

* Move settings

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-11-12 11:46:14 -05:00
Chenlei Hu
40f9b881f3 1.3.41 (#1514) 2024-11-12 10:48:36 -05:00
Chenlei Hu
8236163fea Enable New UI by default (#1515)
nit

Add playwright test

nit

nit

nit
2024-11-12 10:48:26 -05:00
filtered
59b555b448 Fix multiline text input alignment & clipping (#1513)
* Simplify multiline widget scaling

* Fix multiline widget clipping

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-11-12 10:38:03 -05:00
Chenlei Hu
71eeee6744 Less padding on sidebar tabs for small screens (#1511)
* Reduce searchbox and tree padding for small screen

* Smaller buttons
2024-11-11 20:15:34 -05:00
Chenlei Hu
1ff6e27d9c Manage widget definitions with Pinia store (#1510)
* Fix compile

* nit

* Remove extensions.test

* nit
2024-11-11 17:23:52 -05:00
Chenlei Hu
64ef0f18b1 Fix welcome page welcome text selection (#1508) 2024-11-11 11:13:59 -05:00
Chenlei Hu
73bdbddf90 Fix rename open/bookmark workflow (#1507)
* Fix rename open/bookmark workflow

* nit

* Fix save as

* Add browser test
2024-11-11 11:06:41 -05:00
218 changed files with 1265 additions and 672 deletions

View File

@@ -425,9 +425,29 @@ hook is used to auto-format code on commit.
Note: The dev server will NOT load any extension from the ComfyUI server. Only
core extensions will be loaded.
- Run `npm install` to install the necessary packages
- Start local ComfyUI backend at `localhost:8188`
- Run `npm run dev` to start the dev server
- Run `npm run dev:electron` to start the dev server with electron API mocked
#### Access dev server on touch devices
After you start the dev server, you should see following logs:
```
> comfyui-frontend@1.3.42 dev
> vite
VITE v5.4.6 ready in 488 ms
➜ Local: http://localhost:5173/
➜ Network: http://172.21.80.1:5173/
➜ Network: http://192.168.2.20:5173/
➜ press h + enter to show help
```
Make sure your desktop machine and touch device are on the same network. On your touch device,
navigate to `http://<server_ip>:5173` (e.g. `http://192.168.2.20:5173` here), to access the ComfyUI frontend.
### Unit Test

View File

@@ -0,0 +1,10 @@
[
{
"name": "Three Nodes Template",
"data": "{\"nodes\":[{\"id\":7,\"type\":\"CLIPTextEncode\",\"pos\":[413,389],\"size\":[425.27801513671875,180.6060791015625],\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"text, watermark\"]},{\"id\":6,\"type\":\"CLIPTextEncode\",\"pos\":[415,186],\"size\":[422.84503173828125,164.31304931640625],\"flags\":{},\"order\":2,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"]},{\"id\":4,\"type\":\"CheckpointLoaderSimple\",\"pos\":[26,474],\"size\":[315,98],\"flags\":{},\"order\":1,\"mode\":0,\"inputs\":[],\"outputs\":[{\"name\":\"MODEL\",\"type\":\"MODEL\",\"links\":[],\"slot_index\":0},{\"name\":\"CLIP\",\"type\":\"CLIP\",\"links\":[],\"slot_index\":1},{\"name\":\"VAE\",\"type\":\"VAE\",\"links\":[],\"slot_index\":2}],\"properties\":{\"Node name for S&R\":\"CheckpointLoaderSimple\"},\"widgets_values\":[\"v1-5-pruned-emaonly.ckpt\"]}],\"groups\":[],\"reroutes\":[],\"links\":[{\"id\":5,\"origin_id\":4,\"origin_slot\":1,\"target_id\":7,\"target_slot\":0,\"type\":\"CLIP\"},{\"id\":3,\"origin_id\":4,\"origin_slot\":1,\"target_id\":6,\"target_slot\":0,\"type\":\"CLIP\"}]}"
},
{
"name": "Completely empty template",
"data": "{\"nodes\":[],\"groups\":[],\"reroutes\":[],\"links\":[]}"
}
]

View File

@@ -0,0 +1,6 @@
[
{
"name": "vintageClipboard Template",
"data": "{\"nodes\":[{\"id\":-1,\"type\":\"CheckpointLoaderSimple\",\"pos\":[26,474],\"size\":[315,98],\"flags\":{},\"order\":1,\"mode\":0,\"inputs\":[],\"outputs\":[{\"name\":\"MODEL\",\"type\":\"MODEL\",\"links\":[],\"slot_index\":0},{\"name\":\"CLIP\",\"type\":\"CLIP\",\"links\":[],\"slot_index\":1},{\"name\":\"VAE\",\"type\":\"VAE\",\"links\":[],\"slot_index\":2}],\"properties\":{\"Node name for S&R\":\"CheckpointLoaderSimple\"},\"widgets_values\":[\"v1-5-pruned-emaonly.ckpt\"]},{\"id\":-1,\"type\":\"CLIPTextEncode\",\"pos\":[415,186],\"size\":[422.84503173828125,164.31304931640625],\"flags\":{},\"order\":2,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,\"]},{\"id\":-1,\"type\":\"CLIPTextEncode\",\"pos\":[413,389],\"size\":[425.27801513671875,180.6060791015625],\"flags\":{},\"order\":3,\"mode\":0,\"inputs\":[{\"name\":\"clip\",\"type\":\"CLIP\",\"link\":null}],\"outputs\":[{\"name\":\"CONDITIONING\",\"type\":\"CONDITIONING\",\"links\":[],\"slot_index\":0}],\"properties\":{\"Node name for S&R\":\"CLIPTextEncode\"},\"widgets_values\":[\"text, watermark\"]}],\"links\":[[0,1,1,0,4],[0,1,2,0,4]]}"
}
]

View File

@@ -3,6 +3,9 @@ import {
comfyPageFixture as test,
comfyExpect as expect
} from './fixtures/ComfyPage'
import type { useWorkspaceStore } from '../src/stores/workspaceStore'
type WorkspaceStore = ReturnType<typeof useWorkspaceStore>
async function beforeChange(comfyPage: ComfyPage) {
await comfyPage.page.evaluate(() => {
@@ -19,38 +22,40 @@ test.describe('Change Tracker', () => {
test.describe('Undo/Redo', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.setupWorkflowsDirectory({})
})
test('Can undo multiple operations', async ({ comfyPage }) => {
function isModified() {
return comfyPage.page.evaluate(async () => {
return window['app'].extensionManager.workflow.activeWorkflow
.isModified
return !!(window['app'].extensionManager as WorkspaceStore).workflow
.activeWorkflow?.isModified
})
}
function getUndoQueueSize() {
return comfyPage.page.evaluate(() => {
const workflow =
window['app'].extensionManager.workflow.activeWorkflow
return workflow.changeTracker.undoQueue.length
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.undoQueue.length
})
}
function getRedoQueueSize() {
return comfyPage.page.evaluate(() => {
const workflow =
window['app'].extensionManager.workflow.activeWorkflow
return workflow.changeTracker.redoQueue.length
const workflow = (window['app'].extensionManager as WorkspaceStore)
.workflow.activeWorkflow
return workflow?.changeTracker.redoQueue.length
})
}
expect(await getUndoQueueSize()).toBe(0)
expect(await getRedoQueueSize()).toBe(0)
// Save, confirm no errors & workflow modified flag removed
await comfyPage.menu.topbar.saveWorkflow('undo-redo-test')
await comfyPage.page.waitForFunction(
() => !window['app'].extensionManager.workflow.activeWorkflow.isModified
)
expect(await comfyPage.getToastErrorCount()).toBe(0)
expect(await isModified()).toBe(false)
// TODO(huchenlei): Investigate why saving the workflow is causing the
// undo queue to be triggered.
expect(await getUndoQueueSize()).toBe(1)
@@ -75,13 +80,11 @@ test.describe('Change Tracker', () => {
expect(await getUndoQueueSize()).toBe(2)
expect(await getRedoQueueSize()).toBe(1)
// TODO(huchenlei): Following assertion is flaky.
// Seems like ctrlZ() is not triggered correctly.
// await comfyPage.ctrlZ()
// await expect(node).not.toBeCollapsed()
// expect(await isModified()).toBe(false)
// expect(await getUndoQueueSize()).toBe(1)
// expect(await getRedoQueueSize()).toBe(2)
await comfyPage.ctrlZ()
await expect(node).not.toBeCollapsed()
expect(await isModified()).toBe(false)
expect(await getUndoQueueSize()).toBe(1)
expect(await getRedoQueueSize()).toBe(2)
})
})
@@ -109,7 +112,7 @@ test.describe('Change Tracker', () => {
await expect(node).not.toBeCollapsed()
// Run again, but within a change transaction
beforeChange(comfyPage)
await beforeChange(comfyPage)
await node.click('collapse')
await comfyPage.ctrlB()
@@ -117,7 +120,7 @@ test.describe('Change Tracker', () => {
await expect(node).toBeBypassed()
// End transaction
afterChange(comfyPage)
await afterChange(comfyPage)
// Ensure undo reverts both changes
await comfyPage.ctrlZ()
@@ -125,7 +128,7 @@ test.describe('Change Tracker', () => {
await expect(node).not.toBeCollapsed()
})
test('Can group multiple transaction calls into a single one', async ({
test('Can nest multiple change transactions without adding undo steps', async ({
comfyPage
}) => {
const node = (await comfyPage.getFirstNodeRef())!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 158 KiB

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 152 KiB

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 140 KiB

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 134 KiB

After

Width:  |  Height:  |  Size: 134 KiB

View File

@@ -15,9 +15,9 @@ test.describe('Copy Paste', () => {
await textBox.click()
const originalString = await textBox.inputValue()
await textBox.selectText()
await comfyPage.ctrlC()
await comfyPage.ctrlV()
await comfyPage.ctrlV()
await comfyPage.ctrlC(null)
await comfyPage.ctrlV(null)
await comfyPage.ctrlV(null)
const resultString = await textBox.inputValue()
expect(resultString).toBe(originalString + originalString)
})
@@ -31,7 +31,7 @@ test.describe('Copy Paste', () => {
y: 643
}
})
await comfyPage.ctrlC()
await comfyPage.ctrlC(null)
// KSampler's seed
await comfyPage.canvas.click({
position: {
@@ -39,7 +39,7 @@ test.describe('Copy Paste', () => {
y: 281
}
})
await comfyPage.ctrlV()
await comfyPage.ctrlV(null)
await comfyPage.page.keyboard.press('Enter')
await expect(comfyPage.canvas).toHaveScreenshot('copied-widget-value.png')
})
@@ -51,14 +51,14 @@ test.describe('Copy Paste', () => {
comfyPage
}) => {
await comfyPage.clickEmptyLatentNode()
await comfyPage.ctrlC()
await comfyPage.ctrlC(null)
const textBox = comfyPage.widgetTextBox
await textBox.click()
await textBox.inputValue()
await textBox.selectText()
await comfyPage.ctrlC()
await comfyPage.ctrlV()
await comfyPage.ctrlV()
await comfyPage.ctrlC(null)
await comfyPage.ctrlV(null)
await comfyPage.ctrlV(null)
await expect(comfyPage.canvas).toHaveScreenshot(
'paste-in-text-area-with-node-previously-copied.png'
)
@@ -69,10 +69,10 @@ test.describe('Copy Paste', () => {
await textBox.click()
await textBox.inputValue()
await textBox.selectText()
await comfyPage.ctrlC()
await comfyPage.ctrlC(null)
// Unfocus textbox.
await comfyPage.page.mouse.click(10, 10)
await comfyPage.ctrlV()
await comfyPage.ctrlV(null)
await expect(comfyPage.canvas).toHaveScreenshot('no-node-copied.png')
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

View File

@@ -36,6 +36,7 @@ test.describe('Execution error', () => {
}) => {
await comfyPage.loadWorkflow('execution_error')
await comfyPage.queueButton.click()
await comfyPage.nextFrame()
// Wait for the element with the .comfy-execution-error selector to be visible
const executionError = comfyPage.page.locator('.comfy-error-report')
@@ -93,7 +94,7 @@ test.describe('Settings', () => {
test('Can change canvas zoom speed setting', async ({ comfyPage }) => {
const maxSpeed = 2.5
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', maxSpeed)
test.step('Setting should persist', async () => {
await test.step('Setting should persist', async () => {
expect(await comfyPage.getSetting('Comfy.Graph.ZoomSpeed')).toBe(maxSpeed)
})
})

View File

@@ -48,7 +48,7 @@ test.describe('Topbar commands', () => {
})
})
const menuItem: Locator = await comfyPage.menu.topbar.getMenuItem('ext')
const menuItem = comfyPage.menu.topbar.getMenuItem('ext')
expect(await menuItem.count()).toBe(0)
})

View File

@@ -77,6 +77,7 @@ export class ComfyPage {
// All canvas position operations are based on default view of canvas.
public readonly canvas: Locator
public readonly widgetTextBox: Locator
public readonly contextMenu: Locator
// Buttons
public readonly resetViewButton: Locator
@@ -107,6 +108,7 @@ export class ComfyPage {
this.url = process.env.PLAYWRIGHT_TEST_URL || 'http://localhost:8188'
this.canvas = page.locator('#graph-canvas')
this.widgetTextBox = page.getByPlaceholder('text').nth(1)
this.contextMenu = page.locator('.litegraph.litecontextmenu')
this.resetViewButton = page.getByRole('button', { name: 'Reset View' })
this.queueButton = page.getByRole('button', { name: 'Queue Prompt' })
this.workflowUploadInput = page.locator('#comfy-file-input')
@@ -148,6 +150,12 @@ export class ComfyPage {
})
}
async getGraphSelectedItemsCount(): Promise<number | undefined> {
return await this.page.evaluate(() => {
return window['app']?.canvas?.selectedItems?.size
})
}
async setupWorkflowsDirectory(structure: FolderStructure) {
const resp = await this.request.post(
`${this.url}/api/devtools/setup_folder_structure`,
@@ -191,6 +199,39 @@ export class ComfyPage {
return await resp.json()
}
async clearNodeTemplates() {
const resp = await this.request.delete(
`${this.url}/api/userdata/comfy.templates.json`,
{
headers: { 'Comfy-User': this.id }
}
)
const status = resp.status()
if (status !== 204 && status !== 404)
throw new Error(`Failed to delete node templates: ${await resp.text()}`)
}
async setNodeTemplates(fileName: string) {
const path = this.assetPath(fileName)
const data = fs.readFileSync(path, 'utf-8')
const resp = await this.request.post(
`${this.url}/api/userdata/comfy.templates.json`,
{
headers: {
'Comfy-User': this.id,
overwrite: 'true',
full_info: 'true'
},
data
}
)
if (resp.status() !== 200)
throw new Error(`Failed to upload node templates: ${await resp.text()}`)
}
async setupSettings(settings: Record<string, any>) {
const resp = await this.request.post(
`${this.url}/api/devtools/set_settings`,
@@ -349,6 +390,12 @@ export class ComfyPage {
await this.nextFrame()
}
async getToastErrorCount() {
return await this.page
.locator('.p-toast-message.p-toast-message-error')
.count()
}
async getVisibleToastCount() {
return await this.page.locator('.p-toast:visible').count()
}
@@ -393,11 +440,17 @@ export class ComfyPage {
await this.nextFrame()
}
async dragAndDrop(source: Position, target: Position) {
async dragAndDrop(
source: Position,
target: Position,
modifierKey?: 'ControlOrMeta' | 'Control' | 'Alt' | 'Shift'
) {
if (modifierKey) await this.page.keyboard.down(modifierKey)
await this.page.mouse.move(source.x, source.y)
await this.page.mouse.down()
await this.page.mouse.move(target.x, target.y)
await this.page.mouse.up()
if (modifierKey) await this.page.keyboard.up(modifierKey)
await this.nextFrame()
}
@@ -545,12 +598,15 @@ export class ComfyPage {
}
async rightClickCanvas() {
await this.page.mouse.click(10, 10, { button: 'right' })
await this.nextFrame()
await this.canvas.click({
position: { x: 10, y: 10 },
button: 'right'
})
await expect(this.contextMenu).toBeVisible()
}
async doubleClickCanvas() {
await this.page.mouse.dblclick(10, 10)
await this.page.mouse.dblclick(10, 10, { delay: 5 })
await this.nextFrame()
}
@@ -561,7 +617,7 @@ export class ComfyPage {
y: 625
}
})
this.page.mouse.move(10, 10)
await this.page.mouse.move(10, 10)
await this.nextFrame()
}
@@ -573,10 +629,14 @@ export class ComfyPage {
},
button: 'right'
})
this.page.mouse.move(10, 10)
await this.page.mouse.move(10, 10)
await this.nextFrame()
}
async clickContextMenuItem(name: string): Promise<void> {
await this.page.getByRole('menuitem', { name }).click()
}
async select2Nodes() {
// Select 2 CLIP nodes.
await this.page.keyboard.down('Control')
@@ -586,43 +646,42 @@ export class ComfyPage {
await this.nextFrame()
}
async ctrlSend(keyToPress: string) {
await this.page.keyboard.down('Control')
await this.page.keyboard.press(keyToPress)
await this.page.keyboard.up('Control')
async ctrlSend(keyToPress: string, locator: Locator | null = this.canvas) {
const target = locator ?? this.page.keyboard
await target.press(`Control+${keyToPress}`)
await this.nextFrame()
}
async ctrlA() {
await this.ctrlSend('KeyA')
async ctrlA(locator?: Locator | null) {
await this.ctrlSend('KeyA', locator)
}
async ctrlB() {
await this.ctrlSend('KeyB')
async ctrlB(locator?: Locator | null) {
await this.ctrlSend('KeyB', locator)
}
async ctrlC() {
await this.ctrlSend('KeyC')
async ctrlC(locator?: Locator | null) {
await this.ctrlSend('KeyC', locator)
}
async ctrlV() {
await this.ctrlSend('KeyV')
async ctrlV(locator?: Locator | null) {
await this.ctrlSend('KeyV', locator)
}
async ctrlZ() {
await this.ctrlSend('KeyZ')
async ctrlZ(locator?: Locator | null) {
await this.ctrlSend('KeyZ', locator)
}
async ctrlY() {
await this.ctrlSend('KeyY')
async ctrlY(locator?: Locator | null) {
await this.ctrlSend('KeyY', locator)
}
async ctrlArrowUp() {
await this.ctrlSend('ArrowUp')
async ctrlArrowUp(locator?: Locator | null) {
await this.ctrlSend('ArrowUp', locator)
}
async ctrlArrowDown() {
await this.ctrlSend('ArrowDown')
async ctrlArrowDown(locator?: Locator | null) {
await this.ctrlSend('ArrowDown', locator)
}
async closeMenu() {
@@ -778,6 +837,7 @@ export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
comfyPage.userIds[parallelIndex] = userId
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,

View File

@@ -1,4 +1,4 @@
import { Page } from '@playwright/test'
import { Locator, Page } from '@playwright/test'
class SidebarTab {
constructor(
@@ -110,11 +110,30 @@ export class WorkflowsSidebarTab extends SidebarTab {
}
async switchToWorkflow(workflowName: string) {
const workflowLocator = this.page.locator(
'.comfyui-workflows-open .node-label',
{ hasText: workflowName }
)
const workflowLocator = this.getOpenedItem(workflowName)
await workflowLocator.click()
await this.page.waitForTimeout(300)
}
getOpenedItem(name: string) {
return this.page.locator('.comfyui-workflows-open .node-label', {
hasText: name
})
}
getPersistedItem(name: string) {
return this.page.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')
await this.page.waitForTimeout(300)
}
}

View File

@@ -13,35 +13,46 @@ export class Topbar {
await this.page.locator('.p-menubar-mobile .p-menubar-button').click()
}
async getMenuItem(itemLabel: string): Promise<Locator> {
getMenuItem(itemLabel: string): Locator {
return this.page.locator(`.p-menubar-item-label:text-is("${itemLabel}")`)
}
async getWorkflowTab(tabName: string): Promise<Locator> {
getWorkflowTab(tabName: string): Locator {
return this.page
.locator(`.workflow-tabs .workflow-label:has-text("${tabName}")`)
.locator('..')
}
async closeWorkflowTab(tabName: string) {
const tab = await this.getWorkflowTab(tabName)
const tab = this.getWorkflowTab(tabName)
await tab.locator('.close-button').click({ force: true })
}
async saveWorkflow(workflowName: string) {
await this.triggerTopbarCommand(['Workflow', 'Save'])
await this.page.locator('.p-dialog-content input').fill(workflowName)
await this.page.keyboard.press('Enter')
// Wait for the dialog to close.
await this.page.waitForTimeout(300)
getSaveDialog(): Locator {
return this.page.locator('.p-dialog-content input')
}
async saveWorkflowAs(workflowName: string) {
await this.triggerTopbarCommand(['Workflow', 'Save As'])
await this.page.locator('.p-dialog-content input').fill(workflowName)
saveWorkflow(workflowName: string): Promise<void> {
return this._saveWorkflow(workflowName, 'Save')
}
saveWorkflowAs(workflowName: string): Promise<void> {
return this._saveWorkflow(workflowName, 'Save As')
}
async _saveWorkflow(workflowName: string, command: 'Save' | 'Save As') {
await this.triggerTopbarCommand(['Workflow', command])
await this.getSaveDialog().fill(workflowName)
await this.page.keyboard.press('Enter')
// Wait for workflow service to finish saving
await this.page.waitForFunction(
() => !window['app'].extensionManager.workflow.isBusy,
undefined,
{ timeout: 3000 }
)
// Wait for the dialog to close.
await this.page.waitForTimeout(300)
await this.getSaveDialog().waitFor({ state: 'hidden', timeout: 500 })
}
async triggerTopbarCommand(path: string[]) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

View File

@@ -285,7 +285,8 @@ test.describe('Node Interaction', () => {
position: {
x: 50,
y: 10
}
},
delay: 5
})
await comfyPage.page.keyboard.type('Hello World')
await comfyPage.page.keyboard.press('Enter')
@@ -300,7 +301,8 @@ test.describe('Node Interaction', () => {
position: {
x: 50,
y: 50
}
},
delay: 5
})
expect(await comfyPage.page.locator('.node-title-editor').count()).toBe(0)
})
@@ -352,7 +354,8 @@ test.describe('Group Interaction', () => {
position: {
x: 50,
y: 10
}
},
delay: 5
})
await comfyPage.page.keyboard.type('Hello World')
await comfyPage.page.keyboard.press('Enter')
@@ -504,7 +507,7 @@ test.describe('Widget Interaction', () => {
await expect(textBox).toHaveValue('')
await textBox.fill('Hello World')
await expect(textBox).toHaveValue('Hello World')
await comfyPage.ctrlZ()
await comfyPage.ctrlZ(null)
await expect(textBox).toHaveValue('')
})
@@ -515,9 +518,9 @@ test.describe('Widget Interaction', () => {
await textBox.fill('1girl')
await expect(textBox).toHaveValue('1girl')
await textBox.selectText()
await comfyPage.ctrlArrowUp()
await comfyPage.ctrlArrowUp(null)
await expect(textBox).toHaveValue('(1girl:1.05)')
await comfyPage.ctrlZ()
await comfyPage.ctrlZ(null)
await expect(textBox).toHaveValue('1girl')
})
})

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 107 KiB

After

Width:  |  Height:  |  Size: 107 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 101 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 71 KiB

Some files were not shown because too many files have changed in this diff Show More