Compare commits

...

108 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
Chenlei Hu
a55833b3a6 1.3.40 (#1506) 2024-11-11 09:53:42 -05:00
filtered
43012eb1d1 Add settings option: Keep links on delete (#1504) 2024-11-10 21:33:48 -05:00
Chenlei Hu
d1e019589d [Electron] ComfyUI Desktop install wizard (#1503)
* Basic prototype

* Welcome screen animation

* nit

* Refactor structure

* Fix mocking

* Add tooltips

* i18n

* Add next button

* nit

* Stepper navigate

* Extract

* More i18n

* More i18n

* Polish MigrationPicker

* Polish settings step

* Add more i18n

* nit

* nit
2024-11-10 19:56:01 -05:00
filtered
7bc79edf3d Add back/forwards compatibility to schema validation (#1501)
* Allow future extensions of added schema objects

* Add explicit versioning to zod schemas

* Extend schema 0.4 with new fields in extras

- Allows Reroutes without using schema v1
- Reroute data is retained when using old versions - simply not displayed

* Add Reroute undo/redo support
2024-11-10 19:50:18 -05:00
Chenlei Hu
58ad01adfe Reland "Re-enable multiple-undo test" (#1499)
* Revert "Revert "Re-enable multiple-undo test (#1483)" (#1498)"

This reverts commit 5f1a9659e9.

* nit
2024-11-10 12:59:19 -05:00
Chenlei Hu
5f1a9659e9 Revert "Re-enable multiple-undo test (#1483)" (#1498)
This reverts commit 6c6c356c78.
2024-11-10 11:53:55 -05:00
Chenlei Hu
6c6c356c78 Re-enable multiple-undo test (#1483)
* Re-enable multiple-undo test

* nit
2024-11-10 10:57:15 -05:00
Chenlei Hu
893fd498df 1.3.39 (#1497) 2024-11-10 10:17:42 -05:00
Chenlei Hu
1ca388457d chore: update litegraph to 0.8.25 (#1496) 2024-11-10 10:16:35 -05:00
Chenlei Hu
69f0da06f8 Add update-litegraph script (#1495)
* Add update script

* nit
2024-11-10 10:15:46 -05:00
Chenlei Hu
d9a34872c3 [Electron] Add basic welcome screen (#1491)
* WIP

* Add LogTerminal

* Modify server startup view

* Add installView

* Add basic welcome screen and dev server setup

* nit

* nit

* nit

* nit

* nit
2024-11-10 09:41:32 -05:00
filtered
31fac3873c Litegraph: Reroute support (#1420)
* Add Litegraph schema v1 support

- LLink changed from array to object
- Add reroutes
- Graph state object
- Falls back to original schema if not validated
- Add version check to schema validation pass

* Fix test schema version detection

Resolves conflict with proposed schema v1
2024-11-09 15:46:34 -05:00
Chenlei Hu
8dc057517f 1.3.38 (#1488) 2024-11-09 10:55:35 -05:00
Chenlei Hu
4617e0fb1a Fix node badge on unknown color palette (#1487)
* Fix node badge on unknown color palette

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-11-09 10:55:25 -05:00
Chenlei Hu
f8ec87ddea Fix changeTracker modified state (#1481)
* Add jsondiffpatch

* Add logs

* Add graphDiff helper

* Fix changeTracker

* Add loglevel

* Add playwright test

* Fix jest test

* nit

* nit

* Fix test url

* nit
2024-11-08 22:24:35 -05:00
Chenlei Hu
c12f059940 Persist splitter state in localStorage (#1480) 2024-11-08 20:16:13 -05:00
Chenlei Hu
cc320e0f84 Change tooltip location to bottom for sidebar action buttons (#1479) 2024-11-08 18:56:44 -05:00
Chenlei Hu
acbc38ced4 Revert "Enable New UI by default (#1460)" (#1476)
This reverts commit f0b735f3dd.
2024-11-08 16:35:52 -05:00
oto-ciulis-tt
777a6d9ce3 feat:use electron api to download models (#1473)
* enh: Use electron API to download models

* Adding tooltips

* PR comments

---------

Co-authored-by: Oto Ciulis <oto.ciulis@gmail.com>
2024-11-08 15:59:35 -05:00
pythongosssss
7e0b87dd32 Live terminal output (#1347)
* Add live terminal output

* Fix scrolling

* Refactor loading

* Fallback to polling if endpoint fails

* Comment

* Move clientId to executionStore
Refactor types

* Remove polling
2024-11-08 15:38:21 -05:00
Zoltán Dócs
0161a670cf Fit view to bounds (#1474)
* fit view:
- fit view to canvas selection
- fit view to whole graph when nothing is selected
- add button to graph canvas menu
- assign default keybinding '.'

* Adjust on changed APIs

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-11-08 15:03:21 -05:00
Chenlei Hu
0eba49c536 Update litegraph (Animate to bounds) (#1475) 2024-11-08 14:12:58 -05:00
Chenlei Hu
1d9c3f00b7 Setting dialog responsive design for smaller screen size (screen width < 1536) (#1472)
* Smaller queue button

* Smaller dialog padding

* Adjust setting content

* Fix keybinding panel
2024-11-08 11:18:26 -05:00
Chenlei Hu
904408de01 1.3.37 (#1470) 2024-11-08 10:03:27 -05:00
Chenlei Hu
700336fcc7 Fix queue button icon layout shift (#1469) 2024-11-08 10:02:36 -05:00
Chenlei Hu
dd192777b7 Consistently use -1 for temporary file size (#1464) 2024-11-07 23:34:46 -05:00
Chenlei Hu
6b6992591b Update litegraph (#1462) 2024-11-07 20:20:05 -05:00
filtered
45380f7ca0 Fix TypeError thrown (#1461)
Missing node type + reroute linked
2024-11-07 20:17:16 -05:00
Chenlei Hu
f0b735f3dd Enable New UI by default (#1460)
* Enable New UI by default

* nit

* Add playwright test

* nit

* nit

* nit
2024-11-07 17:55:53 -05:00
Chenlei Hu
9568d63820 Add comfyui-electron-types dependency (#1459) 2024-11-07 16:05:26 -05:00
Chenlei Hu
073638672d Fix ('STRING',) input node (#1457)
* Fix ('STRING',) input node

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-11-07 13:03:54 -05:00
Chenlei Hu
8ae9210298 Always sort workflows tree (#1456)
* Always sort workflows tree

* nit

* nit

* Add tests

* nit

* nit
2024-11-07 11:29:29 -05:00
Chenlei Hu
daf94d74d5 1.3.36 (#1453) 2024-11-07 08:59:56 -05:00
Chenlei Hu
14b3d4c766 Fix loading of workflow bookmarks (#1452) 2024-11-07 08:58:28 -05:00
Chenlei Hu
40880dbb59 Move refresh button from actionbar to 'Edit' menu (#1451) 2024-11-07 08:47:46 -05:00
Chenlei Hu
aa4742e394 Update litegraph (#1447) 2024-11-06 21:51:03 -05:00
Chenlei Hu
0a7000328a Add menu button to toggle focus mode (#1446)
* Add focus mode toggle button

* handle menu position

* nit
2024-11-06 20:56:32 -05:00
dependabot[bot]
da7a49bb5c Bump happy-dom from 15.4.0 to 15.11.0 (#1445)
Bumps [happy-dom](https://github.com/capricorn86/happy-dom) from 15.4.0 to 15.11.0.
- [Release notes](https://github.com/capricorn86/happy-dom/releases)
- [Commits](https://github.com/capricorn86/happy-dom/compare/v15.4.0...v15.11.0)

---
updated-dependencies:
- dependency-name: happy-dom
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-06 19:59:13 -05:00
Chenlei Hu
5e4439b905 [Electron] Add electron-specific setup page (#1444)
* Add dummy server start view

* Do external nav

* nit

* nit

* nit

* nit
2024-11-06 19:39:09 -05:00
Chenlei Hu
ea0883271e 1.3.35 (#1443) 2024-11-06 13:49:12 -05:00
Chenlei Hu
3d303c7693 Fix save workflow binding on Ctrl + S (#1442)
* Fix save workflow binding on Ctrl + S

* nit
2024-11-06 13:46:56 -05:00
Chenlei Hu
9f14edaf2b 1.3.34 (#1440) 2024-11-06 08:53:38 -05:00
Chenlei Hu
d1738b50d2 Update litegraph (Remove hardcode +1 size) (#1438)
* Update litegraph (Remove hardcode +1 size)

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-11-05 19:07:38 -05:00
Chenlei Hu
c560628f1f [Extension API] Register about panel badge (#1436)
* Custom about panel badge

* Add playwright test

* Update README

* nit

* nit

* nit

* nit
2024-11-05 19:06:38 -05:00
Chenlei Hu
c56533bb23 Workflow Management Reworked (#1406)
* Merge temp userfile

Basic migration

Remove deprecated isFavourite

Rename

nit

nit

Rework open/load

Refactor save

Refactor delete

Remove workflow dep on manager

WIP

Change map to record

Fix directory

nit

isActive

Move

nit

Add unload

Add close workflow

Remove workflowManager.closeWorkflow

nit

Remove workflowManager.storePrompt

move from commandStore

move more from commandStore

nit

Use workflowservice

nit

nit

implement setWorkflow

nit

Remove workflows.ts

Fix strict errors

nit

nit

Resolves circular dep

nit

nit

Fix workflow switching

Add openworkflowPaths

Fix store

Fix key

Serialize by default

Fix proxy

nit

Update path

Proper sync

Fix tabs

WIP

nit

Resolve merge conflict

Fix userfile store tests

Update jest test

Update tabs

patch tests

Fix changeTracker init

Move insert to service

nit

Fix insert

nit

Handle bookmark rename

Refactor tests

Add delete workflow

Add test on deleting workflow

Add closeWorkflow tests

nit

* Fix path

* Move load next/previous

* Move logic from store to service

* nit

* nit

* nit

* nit

* nit

* Add ChangeTracker.initialState

* ChangeTracker load/unload

* Remove app.changeWorkflow

* Hook to app.ts

* Changetracker restore

* nit

* nit

* nit

* Add debug logs

* Remove unnecessary checkState on graphLoad

* nit

* Fix strict

* Fix temp workflow name

* Track ismodified

* Fix reactivity

* nit

* Fix graph equal

* nit

* update test

* nit

* nit

* Fix modified state

* nit

* Fix modified state

* Sidebar force close

* tabs force close

* Fix save

* Add load remote workflow test

* Force save

* Add save test

* nit

* Correctly handle delete last opened workflow

* nit

* Fix workflow rename

* Fix save

* Fix tests

* Fix strict

* Update playwright tests

* Fix filename conflict handling

* nit

* Merge temporary and persisted ref

* Update playwright expectations

* nit

* nit

* Fix saveAs

* Add playwright test

* nit
2024-11-05 11:03:27 -05:00
Chenlei Hu
1387d7e627 1.3.33 (#1435) 2024-11-05 10:06:51 -05:00
Chenlei Hu
16f2e56d8e Handle errors from top action menu commands (#1432) 2024-11-04 22:50:19 -05:00
Chenlei Hu
75ffab2160 Fix user stuck in title editing state (#1430)
* Fix user stuck in title editing state

* Fix test
2024-11-04 21:59:40 -05:00
filtered
4c19e1ba3a Speed up E2E tests using fully parallel (#1429)
With flaky tests / async bugs all dealt with, fullyParallel can be restored.
2024-11-04 20:33:50 -05:00
Chenlei Hu
2161ae4e5b Pin selected items (Nodes + Groups) (#1427)
* Pin selected items (Nodes + Groups)

* Update litegraph

* Add playwright test

* nit

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-11-04 17:36:33 -05:00
Chenlei Hu
3148c90e28 [skip ci] Update README.md (#1425) 2024-11-04 09:16:55 -05:00
Chenlei Hu
497b2fba8d 1.3.32 (#1424) 2024-11-04 09:03:27 -05:00
Chenlei Hu
09d5e29f01 Create new branch in release script (#1423) 2024-11-04 09:02:33 -05:00
filtered
56b63ebab5 Update Litegraph API: Group move / select / titlebar (#1418)
* Litegraph: Group move / select

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-11-03 18:45:20 -05:00
filtered
3ba776e6ca Add Litegraph multi-select & group nesting (#1416)
* Allow nested groups

Pass all selected items (new litegraph feature) instead of just selected nodes.

* Allow nested groups - context menus

* Update litegraph

* Update litegraph (Select all / Delete selected)

* Add playwright test

* nit

* Update test expectations [skip ci]

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
2024-11-03 18:08:42 -05:00
Chenlei Hu
0a784d9236 Highlight splitter gutter on resizing (#1414) 2024-11-03 12:38:39 -05:00
Chenlei Hu
00df7b428f Animate goto node (#1412)
* Animate goto node

* Update litegraph (animateToNode)
2024-11-03 10:57:17 -05:00
filtered
27bacc36d4 Update tests with precise node positions / sizes (#1408)
* Update tests with precise node positions / sizes

* Fix test flakiness - missing await

* Fix test failures - async not awaited

* Update action

* Update test expectations [skip ci]

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
Co-authored-by: github-actions <github-actions@github.com>
2024-11-03 10:42:54 -05:00
filtered
394df49208 Fix primitive size on load (#1407) 2024-11-03 09:29:39 -05:00
Chenlei Hu
38847e1079 1.3.31 (#1411) 2024-11-03 09:27:58 -05:00
Chenlei Hu
dd86417177 Disable flaky test on missing model download dialog (#1409) 2024-11-03 09:04:18 -05:00
filtered
1366c8cb44 Fix primitive resize when node size ref retained (#1405)
* Fix primitive resize when node size ref retained

Primitive assumes that setting node size property will replace the ref.  Minimal change.

* Use explicit variable names
2024-11-02 16:14:49 -04:00
Chenlei Hu
3a910f25e9 Track previous workflow name on Vue side (#1404) 2024-11-02 14:40:05 -04:00
Chenlei Hu
cc420b70a5 Add finally handler for rename tree node action (#1403)
* Add finally handler for rename tree node action

* nit
2024-11-02 11:39:15 -04:00
filtered
caa3ac2068 Add playwright concurrency - multi-user mode (#1400)
* Add playwright concurrency - multi-user mode

* Add extra server params

* nit

* Update to v2.1 action

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-11-02 09:51:24 -04:00
Chenlei Hu
8baaf380dc Split jest tests into fast and slow groups (#1401) 2024-11-01 22:39:42 -04:00
Chenlei Hu
d719a4e0fb Move exportWorkflow from menu to workflowService (#1399) 2024-11-01 19:44:21 -04:00
Chenlei Hu
d254559e20 [Refactor] Extract createTemporary (#1398)
* [Refactor] Extract createTemporary

* nit
2024-11-01 19:32:50 -04:00
pythongosssss
d701758663 Add support for hidden & advanced widgets (#1389)
* Add support for hidden & advanced widgets

* Fix

* Update package

* Remove ts-expect-error

* Fix test, tidy
2024-11-01 19:12:44 -04:00
Chenlei Hu
a11b78d1c3 Remove deprecated method isFavourite (#1397) 2024-11-01 19:12:04 -04:00
Chenlei Hu
dfb695be72 [Refactor] Rework userFileStore to match existing API on ComfyWorkflow (#1394)
* nit

* Move load

* nit

* nit

* Update store API

* nit

* nit

* Move api

* nit

* Update tests

* Add docs

* Add temp user file

* Implement save as

* Test saveAs
2024-10-31 21:58:00 -04:00
Chenlei Hu
2974b9257a 1.3.30 (#1393) 2024-10-31 19:17:19 -04:00
Chenlei Hu
d11d07334b Add npm release script to automatically create release PR (#1392)
* Add release script

* nit
2024-10-31 19:14:35 -04:00
Chenlei Hu
0c8fe41b84 Fix queue ResultItem schema (#1386) 2024-10-30 20:36:33 -04:00
filtered
ed0592d6e0 Update litegraph API - add @ts-expect-error (#1380)
* Update litegraph API - add @ts-expect-error

LG update removes some implicit any, exposing existing errors

* Update litegraph

---------

Co-authored-by: huchenlei <huchenlei@proton.me>
2024-10-30 20:05:04 -04:00
Chenlei Hu
94f4147f92 Fix double trigger of setting onChange callback (#1385)
* Fix onChange double trigger

* nit

* Add playwright test
2024-10-30 19:55:46 -04:00
Chenlei Hu
67ee8726ef 1.3.29 (#1383) 2024-10-30 16:34:49 -04:00
Chenlei Hu
e48c78541c Hide empty folders when searching in model library (#1382) 2024-10-30 16:27:35 -04:00
Chenlei Hu
bf7a9bf5eb Update litegraph (link snap to slot & highlight) (#1378)
* Update litegraph (link snap)

* Add settings

* nit

* Add playwright test

* Update test expectations [skip ci]

---------

Co-authored-by: github-actions <github-actions@github.com>
2024-10-30 15:23:58 -04:00
filtered
3fb2d423ba Update litegraph API - 237 (#1376) 2024-10-30 13:43:38 -04:00
329 changed files with 5647 additions and 2075 deletions

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
if: github.event.label.name == 'New Browser Test Expectations'
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend

View File

@@ -15,7 +15,7 @@ jobs:
jest-tests:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Run Jest tests
run: |
npm run test:generate
@@ -26,7 +26,7 @@ jobs:
playwright-tests-chromium:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
@@ -43,7 +43,7 @@ jobs:
playwright-tests-chromium-2x:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend
@@ -60,7 +60,7 @@ jobs:
playwright-tests-mobile-chrome:
runs-on: ubuntu-latest
steps:
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v2.1
- name: Install Playwright Browsers
run: npx playwright install chromium --with-deps
working-directory: ComfyUI_frontend

View File

@@ -62,6 +62,14 @@ There will be a 2-day feature freeze before each stable release. During this per
### Major features
<details>
<summary>v1.3.22: Integrated server terminal</summary>
Press Ctrl + ` to toggle integrated terminal.
https://github.com/user-attachments/assets/eddedc6a-07a3-4a83-9475-63b3977f6d94
</details>
<details>
<summary>v1.3.7: Keybinding customization</summary>
@@ -107,6 +115,18 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
### QoL changes
<details>
<summary>v1.3.32: **Litegraph** Nested group</summary>
https://github.com/user-attachments/assets/f51adeb1-028e-40af-81e4-0ac13075198a
</details>
<details>
<summary>v1.3.24: **Litegraph** Group selection</summary>
https://github.com/user-attachments/assets/e6230a94-411e-4fba-90cb-6c694200adaa
</details>
<details>
<summary>v1.3.6: **Litegraph** Toggle link visibility</summary>
@@ -195,6 +215,26 @@ https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
### Developer APIs
<details>
<summary>v1.3.34: Register about panel badges</summary>
```js
app.registerExtension({
name: 'TestExtension1',
aboutPageBadges: [
{
label: 'Test Badge',
url: 'https://example.com',
icon: 'pi pi-box'
}
]
})
```
![image](https://github.com/user-attachments/assets/099e77ee-16ad-4141-b2fc-5e9d5075188b)
</details>
<details>
<summary>v1.3.22: Register bottom panel tabs</summary>
@@ -385,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

@@ -52,7 +52,9 @@ test.describe('Actionbar', () => {
(n) => n.type === 'EmptyLatentImage'
)
node.widgets[0].value = value
window['app'].workflowManager.activeWorkflow.changeTracker.checkState()
window[
'app'
].extensionManager.workflow.activeWorkflow.changeTracker.checkState()
}, value)
}

View File

@@ -0,0 +1,227 @@
{
"last_node_id": 3,
"last_link_id": 0,
"nodes": [
{
"id": 2,
"type": "KSampler",
"pos": {
"0": 420,
"1": 130
},
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 3,
"type": "KSampler",
"pos": {
"0": 820,
"1": 130
},
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
},
{
"id": 1,
"type": "KSampler",
"pos": {
"0": 30,
"1": 130
},
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": null
},
{
"name": "positive",
"type": "CONDITIONING",
"link": null
},
{
"name": "negative",
"type": "CONDITIONING",
"link": null
},
{
"name": "latent_image",
"type": "LATENT",
"link": null
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": null
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
0,
"randomize",
20,
8,
"euler",
"normal",
1
]
}
],
"links": [],
"groups": [
{
"id": 0,
"title": "Group",
"bounding": [
406.9701232910156,
59.079444885253906,
335,
345.6000061035156
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
},
{
"id": 3,
"title": "Group Parent",
"bounding": [
796.9703979492188,
14.796443939208984,
355,
399.20001220703125
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
},
{
"id": 2,
"title": "Group Child",
"bounding": [
806.9703979492188,
58.39643096923828,
335,
345.6000061035156
],
"color": "#3f789e",
"font_size": 24,
"flags": {}
}
],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

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,42 @@
{
"last_node_id": 1,
"last_link_id": 0,
"nodes": [
{
"id": 1,
"type": "DevToolsNodeWithStringInput",
"pos": [
15,
48
],
"size": [
315,
58
],
"flags": {},
"order": 0,
"mode": 0,
"inputs": [],
"outputs": [],
"properties": {
"Node name for S&R": "DevToolsNodeWithStringInput"
},
"widgets_values": [
""
]
}
],
"links": [],
"groups": [],
"config": {},
"extra": {
"ds": {
"scale": 1,
"offset": [
0,
0
]
}
},
"version": 0.4
}

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

@@ -9,24 +9,22 @@ test.describe('Browser tab title', () => {
test('Can display workflow name', async ({ comfyPage }) => {
const workflowName = await comfyPage.page.evaluate(async () => {
return window['app'].workflowManager.activeWorkflow.name
return window['app'].extensionManager.workflow.activeWorkflow.filename
})
// Note: unsaved workflow name is always prepended with "*".
expect(await comfyPage.page.title()).toBe(`*${workflowName} - ComfyUI`)
expect(await comfyPage.page.title()).toBe(`${workflowName} - ComfyUI`)
})
// Broken by https://github.com/Comfy-Org/ComfyUI_frontend/pull/893
// Release blocker for v1.3.0
// Failing on CI
// Cannot reproduce locally
test.skip('Can display workflow name with unsaved changes', async ({
comfyPage
}) => {
const workflowName = await comfyPage.page.evaluate(async () => {
return window['app'].workflowManager.activeWorkflow.name
return window['app'].extensionManager.workflow.activeWorkflow.filename
})
// Note: unsaved workflow name is always prepended with "*".
expect(await comfyPage.page.title()).toBe(`*${workflowName} - ComfyUI`)
expect(await comfyPage.page.title()).toBe(`${workflowName} - ComfyUI`)
await comfyPage.menu.saveWorkflow('test')
await comfyPage.menu.topbar.saveWorkflow('test')
expect(await comfyPage.page.title()).toBe('test - ComfyUI')
const textBox = comfyPage.widgetTextBox
@@ -36,7 +34,7 @@ test.describe('Browser tab title', () => {
// Delete the saved workflow for cleanup.
await comfyPage.page.evaluate(async () => {
window['app'].workflowManager.activeWorkflow.delete()
return window['app'].extensionManager.workflow.activeWorkflow.delete()
})
})
})

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(() => {
@@ -16,6 +19,75 @@ async function afterChange(comfyPage: ComfyPage) {
}
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 as WorkspaceStore).workflow
.activeWorkflow?.isModified
})
}
function getUndoQueueSize() {
return comfyPage.page.evaluate(() => {
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 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')
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)
expect(await getRedoQueueSize()).toBe(0)
const node = (await comfyPage.getFirstNodeRef())!
await node.click('collapse')
await expect(node).toBeCollapsed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(2)
expect(await getRedoQueueSize()).toBe(0)
await comfyPage.ctrlB()
await expect(node).toBeBypassed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(3)
expect(await getRedoQueueSize()).toBe(0)
await comfyPage.ctrlZ()
await expect(node).not.toBeBypassed()
expect(await isModified()).toBe(true)
expect(await getUndoQueueSize()).toBe(2)
expect(await getRedoQueueSize()).toBe(1)
await comfyPage.ctrlZ()
await expect(node).not.toBeCollapsed()
expect(await isModified()).toBe(false)
expect(await getUndoQueueSize()).toBe(1)
expect(await getRedoQueueSize()).toBe(2)
})
})
test('Can group multiple change actions into a single transaction', async ({
comfyPage
}) => {
@@ -40,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()
@@ -48,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()
@@ -56,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())!

View File

@@ -211,7 +211,7 @@ test.describe('Node Color Adjustments', () => {
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
await comfyPage.setSetting('Comfy.Node.Opacity', 0.3)
const node = await comfyPage.getFirstNodeRef()
await node.clickContextMenuOption('Colors')
await node?.clickContextMenuOption('Colors')
})
test('should persist color adjustments when changing custom node colors', async ({

Binary file not shown.

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 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: 96 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: 138 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: 157 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: 96 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')
@@ -51,7 +52,9 @@ test.describe('Missing models warning', () => {
}, comfyPage.url)
})
test('Should display a warning when missing models are found', async ({
// Flaky test after parallelization
// https://github.com/Comfy-Org/ComfyUI_frontend/pull/1400
test.skip('Should display a warning when missing models are found', async ({
comfyPage
}) => {
// The fake_model.safetensors is served by
@@ -91,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)
})
@@ -80,22 +80,81 @@ test.describe('Topbar commands', () => {
)
})
test('Should allow adding settings', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window['app'].registerExtension({
name: 'TestExtension1',
settings: [
{
id: 'TestSetting',
name: 'Test Setting',
type: 'text',
defaultValue: 'Hello, world!'
}
]
test.describe('Settings', () => {
test('Should allow adding settings', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window['app'].registerExtension({
name: 'TestExtension1',
settings: [
{
id: 'TestSetting',
name: 'Test Setting',
type: 'text',
defaultValue: 'Hello, world!',
onChange: () => {
window['changeCount'] = (window['changeCount'] ?? 0) + 1
}
}
]
})
})
// onChange is called when the setting is first added
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(1)
expect(await comfyPage.getSetting('TestSetting')).toBe('Hello, world!')
await comfyPage.setSetting('TestSetting', 'Hello, universe!')
expect(await comfyPage.getSetting('TestSetting')).toBe('Hello, universe!')
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(2)
})
test('Should allow setting boolean settings', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window['app'].registerExtension({
name: 'TestExtension1',
settings: [
{
id: 'Comfy.TestSetting',
name: 'Test Setting',
type: 'boolean',
defaultValue: false,
onChange: () => {
window['changeCount'] = (window['changeCount'] ?? 0) + 1
}
}
]
})
})
expect(await comfyPage.getSetting('Comfy.TestSetting')).toBe(false)
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(1)
await comfyPage.settingDialog.open()
await comfyPage.settingDialog.toggleBooleanSetting('Comfy.TestSetting')
expect(await comfyPage.getSetting('Comfy.TestSetting')).toBe(true)
expect(await comfyPage.page.evaluate(() => window['changeCount'])).toBe(2)
})
})
test.describe('About panel', () => {
test('Should allow adding badges', async ({ comfyPage }) => {
await comfyPage.page.evaluate(() => {
window['app'].registerExtension({
name: 'TestExtension1',
aboutPageBadges: [
{
label: 'Test Badge',
url: 'https://example.com',
icon: 'pi pi-box'
}
]
})
})
await comfyPage.settingDialog.open()
await comfyPage.settingDialog.goToAboutPanel()
const badge = comfyPage.page.locator('.about-badge').last()
expect(badge).toBeDefined()
expect(await badge.textContent()).toContain('Test Badge')
})
expect(await comfyPage.getSetting('TestSetting')).toBe('Hello, world!')
await comfyPage.setSetting('TestSetting', 'Hello, universe!')
expect(await comfyPage.getSetting('TestSetting')).toBe('Hello, universe!')
})
})

View File

@@ -17,6 +17,7 @@ import {
import { Topbar } from './components/Topbar'
import { NodeReference } from './utils/litegraphUtils'
import type { Position, Size } from './types'
import { SettingDialog } from './components/SettingDialog'
class ComfyMenu {
public readonly sideToolbar: Locator
@@ -76,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
@@ -89,6 +91,15 @@ export class ComfyPage {
public readonly menu: ComfyMenu
public readonly actionbar: ComfyActionbar
public readonly templates: ComfyTemplates
public readonly settingDialog: SettingDialog
/** Worker index to test user ID */
public readonly userIds: string[] = []
/** Test user ID for the current context */
get id() {
return this.userIds[comfyPageFixture.info().parallelIndex]
}
constructor(
public readonly page: Page,
@@ -97,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')
@@ -104,6 +116,7 @@ export class ComfyPage {
this.menu = new ComfyMenu(page)
this.actionbar = new ComfyActionbar(page)
this.templates = new ComfyTemplates(page)
this.settingDialog = new SettingDialog(page)
}
convertLeafToContent(structure: FolderStructure): FolderStructure {
@@ -137,13 +150,19 @@ 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`,
{
data: {
tree_structure: this.convertLeafToContent(structure),
base_path: 'user/default/workflows'
base_path: `user/${this.id}/workflows`
}
}
)
@@ -155,6 +174,64 @@ export class ComfyPage {
}
}
async setupUser(username: string) {
const res = await this.request.get(`${this.url}/api/users`)
if (res.status() !== 200)
throw new Error(`Failed to retrieve users: ${await res.text()}`)
const apiRes = await res.json()
const user = Object.entries(apiRes?.users ?? {}).find(
([, name]) => name === username
)
const id = user?.[0]
return id ? id : await this.createUser(username)
}
async createUser(username: string) {
const resp = await this.request.post(`${this.url}/api/users`, {
data: { username }
})
if (resp.status() !== 200)
throw new Error(`Failed to create user: ${await resp.text()}`)
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`,
@@ -170,10 +247,13 @@ export class ComfyPage {
async setup() {
await this.goto()
await this.page.evaluate(() => {
await this.page.evaluate((id) => {
localStorage.clear()
sessionStorage.clear()
})
localStorage.setItem('Comfy.userId', id)
}, this.id)
await this.goto()
// Unify font for consistent screenshots.
await this.page.addStyleTag({
url: 'https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap'
@@ -310,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()
}
@@ -354,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()
}
@@ -506,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()
}
@@ -522,7 +617,7 @@ export class ComfyPage {
y: 625
}
})
this.page.mouse.move(10, 10)
await this.page.mouse.move(10, 10)
await this.nextFrame()
}
@@ -534,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')
@@ -547,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() {
@@ -611,10 +709,14 @@ export class ComfyPage {
x: nodePos.x + nodeSize.width * ratioX,
y: nodePos.y + nodeSize.height * ratioY
}
await this.dragAndDrop(bottomRight, target)
// -1 to be inside the node. -2 because nodes currently get an arbitrary +1 to width.
await this.dragAndDrop(
{ x: bottomRight.x - 2, y: bottomRight.y - 1 },
target
)
await this.nextFrame()
if (revertAfter) {
await this.dragAndDrop(target, bottomRight)
await this.dragAndDrop({ x: target.x - 2, y: target.y - 1 }, bottomRight)
await this.nextFrame()
}
}
@@ -625,14 +727,20 @@ export class ComfyPage {
revertAfter: boolean = false
) {
const ksamplerPos = {
x: 864,
y: 157
x: 863,
y: 156
}
const ksamplerSize = {
width: 315,
height: 292
}
this.resizeNode(ksamplerPos, ksamplerSize, percentX, percentY, revertAfter)
return this.resizeNode(
ksamplerPos,
ksamplerSize,
percentX,
percentY,
revertAfter
)
}
async resizeLoadCheckpointNode(
@@ -641,14 +749,14 @@ export class ComfyPage {
revertAfter: boolean = false
) {
const loadCheckpointPos = {
x: 25,
y: 440
x: 26,
y: 444
}
const loadCheckpointSize = {
width: 320,
height: 120
width: 315,
height: 127
}
this.resizeNode(
return this.resizeNode(
loadCheckpointPos,
loadCheckpointSize,
percentX,
@@ -663,14 +771,14 @@ export class ComfyPage {
revertAfter: boolean = false
) {
const emptyLatentPos = {
x: 475,
y: 580
x: 473,
y: 579
}
const emptyLatentSize = {
width: 303,
height: 132
width: 315,
height: 136
}
this.resizeNode(
return this.resizeNode(
emptyLatentPos,
emptyLatentSize,
percentX,
@@ -722,7 +830,14 @@ export class ComfyPage {
export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
comfyPage: async ({ page, request }, use) => {
const comfyPage = new ComfyPage(page, request)
const { parallelIndex } = comfyPageFixture.info()
const username = `playwright-test-${parallelIndex}`
const userId = await comfyPage.setupUser(username)
comfyPage.userIds[parallelIndex] = userId
await comfyPage.setupSettings({
'Comfy.UseNewMenu': 'Disabled',
// Hide canvas menu/info by default.
'Comfy.Graph.CanvasInfo': false,
'Comfy.Graph.CanvasMenu': false,
@@ -730,7 +845,8 @@ export const comfyPageFixture = base.extend<{ comfyPage: ComfyPage }>({
'Comfy.NodeBadge.NodeIdBadgeMode': NodeBadgeMode.None,
'Comfy.NodeBadge.NodeSourceBadgeMode': NodeBadgeMode.None,
// Disable tooltips by default to avoid flakiness.
'Comfy.EnableTooltips': false
'Comfy.EnableTooltips': false,
'Comfy.userId': userId
})
await comfyPage.setup()
await use(comfyPage)

View File

@@ -0,0 +1,40 @@
import { Page } from '@playwright/test'
export class SettingDialog {
constructor(public readonly page: Page) {}
async open() {
const button = this.page.locator('button.comfy-settings-btn:visible')
await button.click()
await this.page.waitForSelector('div.settings-container')
}
/**
* Set the value of a text setting
* @param id - The id of the setting
* @param value - The value to set
*/
async setStringSetting(id: string, value: string) {
const settingInputDiv = this.page.locator(
`div.settings-container div[id="${id}"]`
)
await settingInputDiv.locator('input').fill(value)
}
/**
* Toggle the value of a boolean setting
* @param id - The id of the setting
*/
async toggleBooleanSetting(id: string) {
const settingInputDiv = this.page.locator(
`div.settings-container div[id="${id}"]`
)
await settingInputDiv.locator('input').click()
}
async goToAboutPanel() {
const aboutButton = this.page.locator('li[aria-label="About"]')
await aboutButton.click()
await this.page.waitForSelector('div.about-container')
}
}

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,27 +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)
getSaveDialog(): Locator {
return this.page.locator('.p-dialog-content input')
}
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: 98 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View File

@@ -139,7 +139,9 @@ test.describe('Group Node', () => {
}) => {
await comfyPage.loadWorkflow('legacy_group_node')
expect(await comfyPage.getGraphNodesCount()).toBe(1)
expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
await expect(
comfyPage.page.locator('.comfy-missing-nodes')
).not.toBeVisible()
})
test.describe('Copy and paste', () => {

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

@@ -1,6 +1,27 @@
import { expect } from '@playwright/test'
import { comfyPageFixture as test } from './fixtures/ComfyPage'
test.describe('Item Interaction', () => {
test('Can select/delete all items', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('mixed_graph_items')
await comfyPage.canvas.press('Control+a')
await expect(comfyPage.canvas).toHaveScreenshot('selected-all.png')
await comfyPage.canvas.press('Delete')
await expect(comfyPage.canvas).toHaveScreenshot('deleted-all.png')
})
test('Can pin/unpin items with keyboard shortcut', async ({ comfyPage }) => {
await comfyPage.loadWorkflow('mixed_graph_items')
await comfyPage.canvas.press('Control+a')
await comfyPage.canvas.press('KeyP')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('pinned-all.png')
await comfyPage.canvas.press('KeyP')
await comfyPage.nextFrame()
await expect(comfyPage.canvas).toHaveScreenshot('unpinned-all.png')
})
})
test.describe('Node Interaction', () => {
test('Can enter prompt', async ({ comfyPage }) => {
const textBox = comfyPage.widgetTextBox
@@ -12,7 +33,7 @@ test.describe('Node Interaction', () => {
})
test.describe('Node Selection', () => {
const multiSelectModifiers = ['Control', 'Shift', 'Meta']
const multiSelectModifiers = ['Control', 'Shift', 'Meta'] as const
multiSelectModifiers.forEach((modifier) => {
test(`Can add multiple nodes to selection using ${modifier}+Click`, async ({
@@ -116,6 +137,24 @@ test.describe('Node Interaction', () => {
await comfyPage.page.keyboard.up('Shift')
await expect(comfyPage.canvas).toHaveScreenshot('copied-link.png')
})
test('Auto snap&highlight when dragging link over node', async ({
comfyPage
}) => {
await comfyPage.setSetting('Comfy.Node.AutoSnapLinkToSlot', true)
await comfyPage.setSetting('Comfy.Node.SnapHighlightsNode', true)
await comfyPage.page.mouse.move(
comfyPage.clipTextEncodeNode1InputSlot.x,
comfyPage.clipTextEncodeNode1InputSlot.y
)
await comfyPage.page.mouse.down()
await comfyPage.page.mouse.move(
comfyPage.clipTextEncodeNode2InputSlot.x,
comfyPage.clipTextEncodeNode2InputSlot.y
)
await expect(comfyPage.canvas).toHaveScreenshot('snapped-highlighted.png')
})
})
test('Can adjust widget value', async ({ comfyPage }) => {
@@ -246,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')
@@ -261,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)
})
@@ -313,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')
@@ -465,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('')
})
@@ -476,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')
})
})
@@ -488,6 +530,13 @@ test.describe('Load workflow', () => {
await comfyPage.loadWorkflow('string_node_id')
await expect(comfyPage.canvas).toHaveScreenshot('string_node_id.png')
})
test('Can load workflow with ("STRING",) input node', async ({
comfyPage
}) => {
await comfyPage.loadWorkflow('string_input')
await expect(comfyPage.canvas).toHaveScreenshot('string_input.png')
})
})
test.describe('Load duplicate workflow', () => {

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: 90 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: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 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: 99 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: 92 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: 103 KiB

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 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: 96 KiB

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 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: 72 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 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: 89 KiB

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 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: 96 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: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 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

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