Compare commits
349 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d2b3e325a4 | ||
|
|
c99ca004b4 | ||
|
|
fa9a415c62 | ||
|
|
da3271fe57 | ||
|
|
358c0ce83c | ||
|
|
110c007912 | ||
|
|
fdb01c06f2 | ||
|
|
ca6bf7d054 | ||
|
|
14f5019556 | ||
|
|
80ca1808f0 | ||
|
|
f2a30ec197 | ||
|
|
b8bdba0bcc | ||
|
|
baf0bc8de4 | ||
|
|
8ce7b515a3 | ||
|
|
06a05cb283 | ||
|
|
15758101aa | ||
|
|
05b3ad2f59 | ||
|
|
90abf9744c | ||
|
|
0e01bb3c07 | ||
|
|
8b77dde55a | ||
|
|
a41de30dc5 | ||
|
|
534ea17816 | ||
|
|
913582c7cd | ||
|
|
3779878b57 | ||
|
|
023299cf1a | ||
|
|
23796d9040 | ||
|
|
21c3883cc7 | ||
|
|
616e295262 | ||
|
|
c201e86b97 | ||
|
|
61ee43aa6f | ||
|
|
08a1fd0056 | ||
|
|
56f3842045 | ||
|
|
81bc0fd9cb | ||
|
|
38c957d3a9 | ||
|
|
9d855d637e | ||
|
|
743683c01d | ||
|
|
720e7e112d | ||
|
|
ce157afeac | ||
|
|
95701ab761 | ||
|
|
060e61f0db | ||
|
|
6c7fb5041d | ||
|
|
25a3c30fef | ||
|
|
287bd7ddd0 | ||
|
|
b396d1a9fe | ||
|
|
ada8500d21 | ||
|
|
0f32ab334a | ||
|
|
36cdebcad1 | ||
|
|
974a7ef63f | ||
|
|
b49b19c9b0 | ||
|
|
a5d93f6910 | ||
|
|
8a99124470 | ||
|
|
4a230f720e | ||
|
|
4ad1e67ebf | ||
|
|
80e4384644 | ||
|
|
51b7467012 | ||
|
|
9d69db6db7 | ||
|
|
e733b87f22 | ||
|
|
adcef7d2f4 | ||
|
|
8ba5da14bc | ||
|
|
c181bf1f55 | ||
|
|
d9a7537169 | ||
|
|
75e91137f0 | ||
|
|
a4a298924e | ||
|
|
14da8433f7 | ||
|
|
ff2d160230 | ||
|
|
b0b5f92940 | ||
|
|
d04dbcd2c1 | ||
|
|
5383f97eba | ||
|
|
bc7da487e8 | ||
|
|
86e7c12e27 | ||
|
|
50f1ca8eaf | ||
|
|
280b43fd58 | ||
|
|
488f0d82b4 | ||
|
|
bc3ec65967 | ||
|
|
61342edba0 | ||
|
|
9247aec03a | ||
|
|
0e88308571 | ||
|
|
380cbdd5fc | ||
|
|
68d6b1f172 | ||
|
|
70d5e98c73 | ||
|
|
9009e784f9 | ||
|
|
877e500510 | ||
|
|
aee2afee36 | ||
|
|
f42609c966 | ||
|
|
aaea05a37b | ||
|
|
d0067719b8 | ||
|
|
e59ed85cc0 | ||
|
|
d5b4311e24 | ||
|
|
6fed739402 | ||
|
|
b25c5259bd | ||
|
|
fb5bb57b0d | ||
|
|
986827cb91 | ||
|
|
6f9d2bfa17 | ||
|
|
4e8acf6c77 | ||
|
|
493805d0ee | ||
|
|
2c174b5956 | ||
|
|
5c2cb00cd6 | ||
|
|
968f417061 | ||
|
|
fef9395a2c | ||
|
|
698754b835 | ||
|
|
29d21348ca | ||
|
|
09d8f2a502 | ||
|
|
50b418113c | ||
|
|
6ab92f28db | ||
|
|
b19cbd9111 | ||
|
|
9cdefca481 | ||
|
|
84662ada9e | ||
|
|
26b02979a1 | ||
|
|
0795c3041c | ||
|
|
c604209f40 | ||
|
|
96d129e8a6 | ||
|
|
a69858c87a | ||
|
|
03ac6eea19 | ||
|
|
f9ae5aaa0f | ||
|
|
a5cdebe1a8 | ||
|
|
298a4744d8 | ||
|
|
090fda2f22 | ||
|
|
f36c934d37 | ||
|
|
87ef469d1c | ||
|
|
a1e40b14c7 | ||
|
|
b863c11e67 | ||
|
|
7a7188aeb0 | ||
|
|
2079a54ffa | ||
|
|
74baf2af12 | ||
|
|
5dec86861b | ||
|
|
fc05029f4e | ||
|
|
98064f301d | ||
|
|
bff1dc91fa | ||
|
|
f4242f8a66 | ||
|
|
845ab88d55 | ||
|
|
6c557eaa58 | ||
|
|
2fdaabd2c9 | ||
|
|
a2143d9120 | ||
|
|
f2b02dd10b | ||
|
|
b831a82360 | ||
|
|
d232e38c33 | ||
|
|
1a3cf4c3f3 | ||
|
|
31d172d4d9 | ||
|
|
92ce064ebf | ||
|
|
3d041dd742 | ||
|
|
af378262f4 | ||
|
|
57c5a78af3 | ||
|
|
233fd1347e | ||
|
|
60221254d9 | ||
|
|
7434691bed | ||
|
|
fbdc9d430b | ||
|
|
3e457f812d | ||
|
|
0466c79725 | ||
|
|
8b989c6415 | ||
|
|
2e51122778 | ||
|
|
8f8eac038a | ||
|
|
4b3bad4bc5 | ||
|
|
17c7f57d8f | ||
|
|
5542845710 | ||
|
|
f2de9b0d3c | ||
|
|
71fa71e82c | ||
|
|
77ba201367 | ||
|
|
6edfc9bc1b | ||
|
|
743dc4879a | ||
|
|
2c1bd662e1 | ||
|
|
7051d86ba7 | ||
|
|
7487c565c8 | ||
|
|
a86d10b02d | ||
|
|
3d89c245e5 | ||
|
|
9dd6da3dc2 | ||
|
|
269e468425 | ||
|
|
c3ef716d53 | ||
|
|
bd7bbd9e95 | ||
|
|
447d1c95ef | ||
|
|
c4bc0e8430 | ||
|
|
f3ab9cfb8e | ||
|
|
52c8c8194e | ||
|
|
9dbc114ae9 | ||
|
|
556edea299 | ||
|
|
d5584a1d39 | ||
|
|
628f2afc34 | ||
|
|
ea01fde607 | ||
|
|
b8a3e6b1ad | ||
|
|
cfad3cd918 | ||
|
|
339e201920 | ||
|
|
c227a8af9a | ||
|
|
6b47162606 | ||
|
|
a4c5a2a3d1 | ||
|
|
45a47be7c0 | ||
|
|
2252f0a134 | ||
|
|
0dfbcfb2d6 | ||
|
|
b46036f25d | ||
|
|
f5ce42d5d5 | ||
|
|
ce75a29202 | ||
|
|
6a8a68a240 | ||
|
|
f9adaadc7d | ||
|
|
727992048e | ||
|
|
dd1e3f087d | ||
|
|
f1c1a3dab7 | ||
|
|
4a43dfe6b9 | ||
|
|
8576d3797b | ||
|
|
22e2628479 | ||
|
|
4e1f14139b | ||
|
|
0c53ab9177 | ||
|
|
eb5f4d9bc7 | ||
|
|
19681fd43d | ||
|
|
add2f9baa0 | ||
|
|
ec5f1152da | ||
|
|
17aa44d9f6 | ||
|
|
d8887a434d | ||
|
|
9d3ca763d0 | ||
|
|
a45851d7a6 | ||
|
|
266336104a | ||
|
|
8c40f83b35 | ||
|
|
5ba524fd94 | ||
|
|
966b1dd057 | ||
|
|
069766337a | ||
|
|
a1a6eeed0f | ||
|
|
b33874db2b | ||
|
|
05c6193a1d | ||
|
|
73f7889f81 | ||
|
|
c73915e31e | ||
|
|
7626d08c98 | ||
|
|
f10554d914 | ||
|
|
238225dd12 | ||
|
|
9cea54686a | ||
|
|
d1715972e3 | ||
|
|
49a910d7b0 | ||
|
|
479ca63e3c | ||
|
|
7468555c06 | ||
|
|
14a395a0ce | ||
|
|
0a99a2bd53 | ||
|
|
955c703fde | ||
|
|
ef8b952d79 | ||
|
|
e6d29656fa | ||
|
|
9e3dffd7fd | ||
|
|
429e44f74d | ||
|
|
73b0ecc8cb | ||
|
|
775f536d30 | ||
|
|
1ff7a70579 | ||
|
|
51233b4be3 | ||
|
|
5c4d1c2cec | ||
|
|
711205b499 | ||
|
|
00fe3515b9 | ||
|
|
d8ee0b4584 | ||
|
|
fc1b0b3e53 | ||
|
|
a68f7c680b | ||
|
|
c6b6bdcb67 | ||
|
|
6993a56c2d | ||
|
|
4eb56a19ba | ||
|
|
784947fdea | ||
|
|
64ee131e64 | ||
|
|
1b59e3ab6d | ||
|
|
37b414d1b2 | ||
|
|
7245eee85b | ||
|
|
9f3696e70f | ||
|
|
d5deb6d2a0 | ||
|
|
41889c3cfe | ||
|
|
e8602e88fa | ||
|
|
b5f2d334d9 | ||
|
|
c459698956 | ||
|
|
f91e335ca7 | ||
|
|
1fc173b48f | ||
|
|
4fe44339fd | ||
|
|
f987f4f5f3 | ||
|
|
91e21b1387 | ||
|
|
a5be1f6072 | ||
|
|
d5f30be06d | ||
|
|
d607f6c7f7 | ||
|
|
d9df0328c5 | ||
|
|
aff059b98b | ||
|
|
cf53e8df6a | ||
|
|
d1d4324e57 | ||
|
|
c86abd3dc6 | ||
|
|
281ed0c5d1 | ||
|
|
edf0396619 | ||
|
|
2a5b2e8d12 | ||
|
|
9385014799 | ||
|
|
cfce1c6037 | ||
|
|
480679aa32 | ||
|
|
e2141a81e2 | ||
|
|
0f3b58b610 | ||
|
|
7ef8e56c25 | ||
|
|
9ffdf768b1 | ||
|
|
23fcdd3e44 | ||
|
|
7ce7490bc3 | ||
|
|
3e7b0a4907 | ||
|
|
a095e7ecae | ||
|
|
fe0d63e16c | ||
|
|
3c76554bec | ||
|
|
ce2a2dd2b6 | ||
|
|
d6c304690c | ||
|
|
e1bc2708d3 | ||
|
|
95dc6ff5de | ||
|
|
4b83ee3918 | ||
|
|
078df413b5 | ||
|
|
3fc85f1fb6 | ||
|
|
ce14c1c071 | ||
|
|
0d31dc5b4c | ||
|
|
42c1e4b5e1 | ||
|
|
5490ccf4f0 | ||
|
|
a5f0d2b201 | ||
|
|
02d7f91e9e | ||
|
|
eb1c66c90a | ||
|
|
6b1776450b | ||
|
|
7804b25d5f | ||
|
|
3249bbf4ab | ||
|
|
baa1e54fc0 | ||
|
|
968a1da227 | ||
|
|
a013d83fc0 | ||
|
|
564ec887f2 | ||
|
|
79469bd2b1 | ||
|
|
6fe2297cc1 | ||
|
|
e162d0007c | ||
|
|
5419865740 | ||
|
|
b90b1194d6 | ||
|
|
1eb45ddc55 | ||
|
|
2f1df2c6ce | ||
|
|
3269b54aae | ||
|
|
50c4c87e62 | ||
|
|
1b96c16cca | ||
|
|
31ca016055 | ||
|
|
cf9d95aa97 | ||
|
|
7a980f46c9 | ||
|
|
c48f68e53e | ||
|
|
0210c7f438 | ||
|
|
0384cf96c4 | ||
|
|
4bebcb408e | ||
|
|
b5a919e8b2 | ||
|
|
7e669b0f68 | ||
|
|
244cd3c920 | ||
|
|
0cf5e647af | ||
|
|
f0f867481d | ||
|
|
d02b074fa3 | ||
|
|
e14d84526a | ||
|
|
2aa9166079 | ||
|
|
3baa07e0a9 | ||
|
|
c494cd211e | ||
|
|
c00e2fd208 | ||
|
|
d77343da83 | ||
|
|
c611c15d40 | ||
|
|
269686eebb | ||
|
|
0e3590d017 | ||
|
|
7d2d6df57b | ||
|
|
4462dabc63 | ||
|
|
53bfc0c95a | ||
|
|
b78682689e | ||
|
|
6d1dce8255 | ||
|
|
73f4e5143d | ||
|
|
7d75cc99ba | ||
|
|
0aa7d0b99a | ||
|
|
66b690e5c8 | ||
|
|
6e27b884fc | ||
|
|
561162fb3e |
@@ -13,4 +13,7 @@ DEV_SERVER_COMFYUI_URL=http://127.0.0.1:8188
|
||||
DEPLOY_COMFYUI_DIR=/home/ComfyUI/web
|
||||
|
||||
# The directory containing the ComfyUI_examples repo used to extract test workflows.
|
||||
EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples
|
||||
EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples
|
||||
|
||||
# Whether to enable minification of the frontend code.
|
||||
ENABLE_MINIFY=true
|
||||
7
.gitattributes
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
# Default
|
||||
* text=auto
|
||||
|
||||
# Force TS to LF to make the unixy scripts not break on Windows
|
||||
*.ts text eol=lf
|
||||
*.vue text eol=lf
|
||||
*.js text eol=lf
|
||||
72
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Bug Report
|
||||
description: "Something is not behaving as expected."
|
||||
title: "[Bug]: "
|
||||
labels: ['Potential Bug']
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Before submitting a **Bug Report**, please ensure the following:
|
||||
|
||||
- **1:** You are running the latest version of ComfyUI.
|
||||
- **2:** You have looked at the existing bug reports and made sure this isn't already reported.
|
||||
- **3:** You confirmed that the bug is not caused by a custom node. You can disable all custom nodes by passing
|
||||
`--disable-all-custom-nodes` command line argument.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Frontend Version
|
||||
description: 'What is the frontend version you are using? You can check this in the settings dialog'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected Behavior
|
||||
description: 'What you expected to happen.'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Actual Behavior
|
||||
description: 'What actually happened. Please include a screenshot / video clip of the issue if possible.'
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to Reproduce
|
||||
description: "Describe how to reproduce the issue. Please be sure to attach a workflow JSON or PNG, ideally one that doesn't require custom nodes to test. If the bug open happens when certain custom nodes are used, most likely that custom node is what has the bug rather than ComfyUI, in which case it should be reported to the node's author."
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Debug Logs
|
||||
description: 'Please copy the output from your terminal logs here.'
|
||||
render: powershell
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Browser Logs
|
||||
description: 'Please copy the output from your browser logs here. You can access this by pressing F12 to toggle the developer tools, then navigating to the Console tab.'
|
||||
validations:
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: browsers
|
||||
attributes:
|
||||
label: What browsers do you use to access the UI ?
|
||||
multiple: true
|
||||
options:
|
||||
- Mozilla Firefox
|
||||
- Google Chrome
|
||||
- Brave
|
||||
- Apple Safari
|
||||
- Microsoft Edge
|
||||
- Android
|
||||
- iOS
|
||||
- Other
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Other
|
||||
description: 'Any other additional information you think might be helpful.'
|
||||
validations:
|
||||
required: false
|
||||
40
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
name: Feature Request
|
||||
description: Suggest an idea for this project
|
||||
title: "[Feature Request]: "
|
||||
labels: ["enhancement"]
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the feature you want, and that it's not implemented in a recent build/commit.
|
||||
options:
|
||||
- label: I have searched the existing issues and checked the recent builds/commits
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
*Please fill this form with as much information as possible, provide screenshots and/or illustrations of the feature if possible*
|
||||
- type: textarea
|
||||
id: feature
|
||||
attributes:
|
||||
label: What would your feature do ?
|
||||
description: Tell us about your feature in a very clear and simple way, and what problem it would solve
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: workflow
|
||||
attributes:
|
||||
label: Proposed workflow
|
||||
description: Please provide us with step by step information on how you'd like the feature to be accessed and used
|
||||
value: |
|
||||
1. Go to ....
|
||||
2. Press ....
|
||||
3. ...
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: misc
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Add any other context or screenshots about the feature request here.
|
||||
20
.github/workflows/eslint.yaml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: ESLint
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- 'dev*'
|
||||
|
||||
jobs:
|
||||
eslint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
- run: npm ci
|
||||
- run: npm run lint
|
||||
23
.github/workflows/format.yaml
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
name: Prettier Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main, master, dev* ]
|
||||
|
||||
jobs:
|
||||
prettier:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run Prettier check
|
||||
run: npx prettier --check './**/*.{js,ts,tsx,vue}'
|
||||
40
.github/workflows/test-browser-exp.yaml
vendored
@@ -12,43 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'New Browser Test Expectations'
|
||||
steps:
|
||||
- name: Checkout ComfyUI
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "comfyanonymous/ComfyUI"
|
||||
path: "ComfyUI"
|
||||
ref: master
|
||||
- name: Checkout ComfyUI_frontend
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "huchenlei/ComfyUI_frontend"
|
||||
path: "ComfyUI_frontend"
|
||||
ref: ${{ github.head_ref }}
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
pip install -r requirements.txt
|
||||
pip install wait-for-it
|
||||
working-directory: ComfyUI
|
||||
- name: Build & Install ComfyUI_frontend
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
rm -rf ../ComfyUI/web/*
|
||||
mv dist/* ../ComfyUI/web/
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Start ComfyUI server
|
||||
run: |
|
||||
python main.py --cpu &
|
||||
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||
working-directory: ComfyUI
|
||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: ComfyUI_frontend
|
||||
@@ -72,6 +36,8 @@ jobs:
|
||||
run: |
|
||||
git config --global user.name 'github-actions'
|
||||
git config --global user.email 'github-actions@github.com'
|
||||
git fetch origin ${{ github.head_ref }}
|
||||
git checkout -B ${{ github.head_ref }} origin/${{ github.head_ref }}
|
||||
git add browser_tests
|
||||
git commit -m "Update test expectations [skip ci]"
|
||||
git push origin HEAD:${{ github.head_ref }}
|
||||
|
||||
102
.github/workflows/test-ui.yaml
vendored
@@ -5,7 +5,6 @@ on:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- 'dev*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
@@ -13,75 +12,64 @@ on:
|
||||
- 'dev*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
jest-tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout ComfyUI
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "comfyanonymous/ComfyUI"
|
||||
path: "ComfyUI"
|
||||
ref: master
|
||||
- name: Checkout ComfyUI_frontend
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "Comfy-Org/ComfyUI_frontend"
|
||||
path: "ComfyUI_frontend"
|
||||
- name: Get commit message
|
||||
id: commit-message
|
||||
run: echo "::set-output name=message::$(git log -1 --pretty=%B)"
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Checkout ComfyUI_examples
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "comfyanonymous/ComfyUI_examples"
|
||||
path: "ComfyUI_frontend/tests-ui/ComfyUI_examples"
|
||||
ref: master
|
||||
- name: Skip CI
|
||||
if: contains(steps.commit-message.outputs.message, '[skip ci]')
|
||||
run: echo "Skipping CI as commit contains '[skip ci]'"
|
||||
continue-on-error: true
|
||||
working-directory: ComfyUI_frontend
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
pip install -r requirements.txt
|
||||
pip install wait-for-it
|
||||
working-directory: ComfyUI
|
||||
- name: Build & Install ComfyUI_frontend
|
||||
run: |
|
||||
npm ci
|
||||
npm run build
|
||||
rm -rf ../ComfyUI/web/*
|
||||
mv dist/* ../ComfyUI/web/
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Start ComfyUI server
|
||||
run: |
|
||||
python main.py --cpu &
|
||||
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||
working-directory: ComfyUI
|
||||
- name: Run UI tests
|
||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
|
||||
- name: Run Jest tests
|
||||
run: |
|
||||
npm run test:generate
|
||||
npm run test:generate:examples
|
||||
npm test -- --verbose
|
||||
working-directory: ComfyUI_frontend
|
||||
|
||||
playwright-tests-chromium:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Run Playwright tests
|
||||
run: npx playwright test
|
||||
- name: Run Playwright tests (Chromium)
|
||||
run: npx playwright test --project=chromium
|
||||
working-directory: ComfyUI_frontend
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
name: playwright-report-chromium
|
||||
path: ComfyUI_frontend/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
playwright-tests-chromium-2x:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Run Playwright tests (Chromium 2x)
|
||||
run: npx playwright test --project=chromium-2x
|
||||
working-directory: ComfyUI_frontend
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-chromium-2x
|
||||
path: ComfyUI_frontend/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
playwright-tests-mobile-chrome:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Comfy-Org/ComfyUI_frontend_setup_action@v1
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install chromium --with-deps
|
||||
working-directory: ComfyUI_frontend
|
||||
- name: Run Playwright tests (Mobile Chrome)
|
||||
run: npx playwright test --project=mobile-chrome
|
||||
working-directory: ComfyUI_frontend
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report-mobile-chrome
|
||||
path: ComfyUI_frontend/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
53
.github/workflows/update-main.yaml
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
name: Update Main Repo from PR
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
update-main-repo:
|
||||
if: github.event.label.name == 'Update Main Repo'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout frontend repo PR
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build project
|
||||
run: npm run build
|
||||
|
||||
- name: Checkout ComfyUI
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: "comfyanonymous/ComfyUI"
|
||||
path: ComfyUI
|
||||
ref: master
|
||||
|
||||
- name: Copy compiled assets
|
||||
run: |
|
||||
rm -rf ./ComfyUI/web/*
|
||||
cp -R dist/* ./ComfyUI/web/
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
token: ${{ secrets.PAT }}
|
||||
commit-message: 'Update frontend assets from PR #${{ github.event.pull_request.number }}'
|
||||
title: 'Update frontend assets from PR #${{ github.event.pull_request.number }}'
|
||||
body: |
|
||||
This PR updates the compiled frontend assets from PR #${{ github.event.pull_request.number }} in the frontend repo.
|
||||
|
||||
Frontend PR: ${{ github.event.pull_request.html_url }}
|
||||
branch: update-frontend-assets-pr-${{ github.event.pull_request.number }}
|
||||
base: main
|
||||
path: ComfyUI
|
||||
31
.github/workflows/vitest.yaml
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
name: Vitest Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- 'dev*'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
- 'dev*'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Run Vitest tests
|
||||
run: npm run test:component
|
||||
2
.gitignore
vendored
@@ -23,6 +23,7 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
components.d.ts
|
||||
|
||||
# Ignore test data.
|
||||
tests-ui/data/*
|
||||
@@ -34,6 +35,7 @@ tests-ui/workflows/examples
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
browser_tests/*/*-win32.png
|
||||
|
||||
.env
|
||||
|
||||
|
||||
133
README.md
@@ -1,19 +1,63 @@
|
||||
<div align="center">
|
||||
|
||||
# ComfyUI_frontend
|
||||
|
||||
Front-end of [ComfyUI](https://github.com/comfyanonymous/ComfyUI) modernized. This repo is fully compatible with the existing extension system.
|
||||
**Official front-end implementation of [ComfyUI](https://github.com/comfyanonymous/ComfyUI).**
|
||||
|
||||
## How To Use
|
||||
[![Website][website-shield]][website-url]
|
||||
[![Discord][discord-shield]][discord-url]
|
||||
[![Matrix][matrix-shield]][matrix-url]
|
||||
<br>
|
||||
[![][github-release-shield]][github-release-link]
|
||||
[![][github-release-date-shield]][github-release-link]
|
||||
[![][github-downloads-shield]][github-downloads-link]
|
||||
[![][github-downloads-latest-shield]][github-downloads-link]
|
||||
|
||||
Add command line argument `--front-end-version Comfy-Org/ComfyUI_frontend@latest` to your
|
||||
ComfyUI launch script.
|
||||
|
||||
For Windows stand-alone build users, please edit the `run_cpu.bat` / `run_nvidia_gpu.bat` file as following
|
||||
[github-release-shield]: https://img.shields.io/github/v/release/Comfy-Org/ComfyUI_frontend?style=flat&sort=semver
|
||||
[github-release-link]: https://github.com/Comfy-Org/ComfyUI_frontend/releases
|
||||
[github-release-date-shield]: https://img.shields.io/github/release-date/Comfy-Org/ComfyUI_frontend?style=flat
|
||||
[github-downloads-shield]: https://img.shields.io/github/downloads/Comfy-Org/ComfyUI_frontend/total?style=flat
|
||||
[github-downloads-latest-shield]: https://img.shields.io/github/downloads/Comfy-Org/ComfyUI_frontend/latest/total?style=flat&label=downloads%40latest
|
||||
[github-downloads-link]: https://github.com/Comfy-Org/ComfyUI_frontend/releases
|
||||
[matrix-shield]: https://img.shields.io/badge/Matrix-000000?style=flat&logo=matrix&logoColor=white
|
||||
[matrix-url]: https://app.element.io/#/room/%23comfyui_space%3Amatrix.org
|
||||
[website-shield]: https://img.shields.io/badge/ComfyOrg-4285F4?style=flat
|
||||
[website-url]: https://www.comfy.org/
|
||||
[discord-shield]: https://img.shields.io/discord/1218270712402415686?style=flat&logo=discord&logoColor=white&label=Discord
|
||||
[discord-url]: https://www.comfy.org/discord
|
||||
|
||||
</div>
|
||||
|
||||
## Release Schedule
|
||||
|
||||
### Nightly Release
|
||||
|
||||
Nightly releases are published daily at [https://github.com/Comfy-Org/ComfyUI_frontend/releases](https://github.com/Comfy-Org/ComfyUI_frontend/releases).
|
||||
|
||||
To use the latest nightly release, add the following command line argument to your ComfyUI launch script:
|
||||
|
||||
```
|
||||
--front-end-version Comfy-Org/ComfyUI_frontend@latest
|
||||
```
|
||||
|
||||
#### For Windows Stand-alone Build Users
|
||||
|
||||
Edit your `run_cpu.bat` or `run_nvidia_gpu.bat` file as follows:
|
||||
|
||||
```bat
|
||||
.\python_embeded\python.exe -s ComfyUI\main.py --windows-standalone-build --front-end-version Comfy-Org/ComfyUI_frontend@latest
|
||||
pause
|
||||
```
|
||||
|
||||
### Stable Release
|
||||
|
||||
Stable releases are published weekly in the ComfyUI main repository, aligned with ComfyUI backend's stable release schedule.
|
||||
|
||||
#### Feature Freeze
|
||||
|
||||
There will be a 2-day feature freeze before each stable release. During this period, no new major features will be merged.
|
||||
|
||||
## Release Summary
|
||||
|
||||
### Major features
|
||||
@@ -46,6 +90,36 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
|
||||
|
||||
### QoL changes
|
||||
|
||||
<details>
|
||||
<summary>v1.2.44: **Litegraph** Double click group title to edit</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/5bf0e2b6-8b3a-40a7-b44f-f0879e9ad26f
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.2.39: **Litegraph** Group selected nodes with Ctrl + G</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/7805dc54-0854-4a28-8bcd-4b007fa01151
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.2.38: **Litegraph** Double click node title to edit</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/d61d5d0e-f200-4153-b293-3e3f6a212b30
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.2.7: **Litegraph** drags multiple links with shift pressed</summary>
|
||||
|
||||
https://github.com/user-attachments/assets/68826715-bb55-4b2a-be6e-675cfc424afe
|
||||
|
||||
https://github.com/user-attachments/assets/c142c43f-2fe9-4030-8196-b3bfd4c6977d
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.2.2: **Litegraph** auto connects to correct slot</summary>
|
||||
|
||||
@@ -64,9 +138,9 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
|
||||
|
||||
### Node developers API
|
||||
<details>
|
||||
<summary>v1.2.4: Extension API to register custom side bar tab</summary>
|
||||
<summary>v1.2.4: Extension API to register custom sidebar tab</summary>
|
||||
|
||||
Extensions now can call following API to register a sidebar tab.
|
||||
Extensions now can call the following API to register a sidebar tab.
|
||||
|
||||
```js
|
||||
app.extensionManager.registerSidebarTab({
|
||||
@@ -81,20 +155,37 @@ https://github.com/user-attachments/assets/4bbca3ee-318f-4cf0-be32-a5a5541066cf
|
||||
});
|
||||
```
|
||||
|
||||
The list of supported icons can be find here: https://primevue.org/icons/#list
|
||||
The list of supported icons can be found here: <https://primevue.org/icons/#list>
|
||||
|
||||
We will support custom icon later.
|
||||
We will support custom icons later.
|
||||
|
||||

|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v1.2.27: Extension API to add toast message</summary>
|
||||
|
||||
Extensions can call the following API to add toast messages.
|
||||
|
||||
```js
|
||||
app.extensionManager.toast.add({
|
||||
severity: 'info',
|
||||
summary: 'Loaded!',
|
||||
detail: 'Extension loaded!',
|
||||
life: 3000
|
||||
})
|
||||
```
|
||||
Documentation of all supported options can be found here: <https://primevue.org/toast/#api.toast.interfaces.ToastMessageOptions>
|
||||
|
||||

|
||||
</details>
|
||||
|
||||
## Road Map
|
||||
|
||||
### What has been done
|
||||
|
||||
- Migrate all code to TypeScript with minimal change modification to the original logic.
|
||||
- Bundle all code with vite's rollup build.
|
||||
- Bundle all code with Vite's rollup build.
|
||||
- Added a shim layer to be backward compatible with the existing extension system. <https://github.com/huchenlei/ComfyUI_frontend/pull/15>
|
||||
- Front-end dev server.
|
||||
- Zod schema for input validation on ComfyUI workflow.
|
||||
@@ -102,18 +193,17 @@ We will support custom icon later.
|
||||
- Introduce Vue to start managing part of the UI.
|
||||
- Easy install and version management (<https://github.com/comfyanonymous/ComfyUI/pull/3897>).
|
||||
- Better node management. Sherlock <https://github.com/Nuked88/ComfyUI-N-Sidebar>.
|
||||
- Replace the existing ComfyUI front-end implementation. <https://github.com/comfyanonymous/ComfyUI/pull/4379>
|
||||
|
||||
|
||||
### What to be done
|
||||
|
||||
- Replace the existing ComfyUI front-end impl
|
||||
- Remove `@ts-ignore`s.
|
||||
- Turn on `strict` on `tsconfig.json`.
|
||||
- Add more widget types for node developers.
|
||||
- LLM streaming node.
|
||||
- Linear mode (Similar to InvokeAI's linear mode).
|
||||
- Keybinding settings management. Register keybindings API for custom nodes.
|
||||
- New extensions API for adding UI-related features.
|
||||
|
||||
## Development
|
||||
|
||||
@@ -139,11 +229,26 @@ core extensions will be loaded.
|
||||
- `npm run test:generate:examples` to extract the example workflows
|
||||
- `npm run test` to execute all unit tests.
|
||||
|
||||
### LiteGraph
|
||||
|
||||
This repo is using litegraph package hosted on <https://github.com/Comfy-Org/litegraph.js>. Any changes to litegraph should be submitted in that repo instead.
|
||||
|
||||
### Test litegraph changes
|
||||
|
||||
- Run `npm link` in the local litegraph repo.
|
||||
- Run `npm uninstall @comfyorg/litegraph` in this repo.
|
||||
- Run `npm link @comfyorg/litegraph` in this repo.
|
||||
|
||||
This will replace the litegraph package in this repo with the local litegraph repo.
|
||||
|
||||
## Deploy
|
||||
|
||||
- Option 1: Set `DEPLOY_COMFYUI_DIR` in `.env` and run `npm run deploy`.
|
||||
- Option 2: Copy everything under `dist/` to `ComfyUI/web/` in your ComfyUI checkout manually.
|
||||
|
||||
## Breaking changes
|
||||
## Publish release to ComfyUI main repo
|
||||
|
||||
- api.api_url now adds a prefix `api/` to every url going through the method. If the custom node registers a new api endpoint but does not offer the `api/` prefixed alt endpoint, it will have issue. Luckily there aren't many extensions that do that. We can perform an audit before launching to resolve this issue.
|
||||
Run following command to publish a release to ComfyUI main repo. The script will create a new branch and do a commit to `web/` folder by checkout `dist.zip`
|
||||
from GitHub release.
|
||||
|
||||
- `python scripts/main_repo_release.py <path_to_comfyui_main_repo> <version>`
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { Page, Locator } from '@playwright/test'
|
||||
import { test as base } from '@playwright/test'
|
||||
import dotenv from 'dotenv'
|
||||
dotenv.config()
|
||||
import * as fs from 'fs'
|
||||
import { NodeBadgeMode } from '../src/types/nodeSource'
|
||||
|
||||
interface Position {
|
||||
x: number
|
||||
@@ -26,18 +28,24 @@ class ComfyNodeSearchBox {
|
||||
)
|
||||
}
|
||||
|
||||
async fillAndSelectFirstNode(nodeName: string) {
|
||||
async fillAndSelectFirstNode(
|
||||
nodeName: string,
|
||||
options?: { suggestionIndex: number }
|
||||
) {
|
||||
await this.input.waitFor({ state: 'visible' })
|
||||
await this.input.fill(nodeName)
|
||||
await this.dropdown.waitFor({ state: 'visible' })
|
||||
// Wait for some time for the auto complete list to update.
|
||||
// The auto complete list is debounced and may take some time to update.
|
||||
await this.page.waitForTimeout(500)
|
||||
await this.dropdown.locator('li').nth(0).click()
|
||||
await this.dropdown
|
||||
.locator('li')
|
||||
.nth(options?.suggestionIndex || 0)
|
||||
.click()
|
||||
}
|
||||
}
|
||||
|
||||
class NodeLibrarySideBarTab {
|
||||
class NodeLibrarySidebarTab {
|
||||
public readonly tabId: string = 'node-library'
|
||||
constructor(public readonly page: Page) {}
|
||||
|
||||
@@ -51,14 +59,26 @@ class NodeLibrarySideBarTab {
|
||||
)
|
||||
}
|
||||
|
||||
get nodeLibrarySearchBoxInput() {
|
||||
return this.page.locator('.node-lib-search-box input[type="text"]')
|
||||
}
|
||||
|
||||
get nodeLibraryTree() {
|
||||
return this.page.locator('.node-lib-tree')
|
||||
return this.page.locator('.node-lib-tree-explorer')
|
||||
}
|
||||
|
||||
get nodePreview() {
|
||||
return this.page.locator('.node-lib-node-preview')
|
||||
}
|
||||
|
||||
get tabContainer() {
|
||||
return this.page.locator('.sidebar-content-container')
|
||||
}
|
||||
|
||||
get newFolderButton() {
|
||||
return this.tabContainer.locator('.new-folder-button')
|
||||
}
|
||||
|
||||
async open() {
|
||||
if (await this.selectedTabButton.isVisible()) {
|
||||
return
|
||||
@@ -68,22 +88,34 @@ class NodeLibrarySideBarTab {
|
||||
await this.nodeLibraryTree.waitFor({ state: 'visible' })
|
||||
}
|
||||
|
||||
async toggleFirstFolder() {
|
||||
await this.page.locator('.p-tree-node-toggle-button').nth(0).click()
|
||||
folderSelector(folderName: string) {
|
||||
return `.p-tree-node-content:has(> .tree-explorer-node-label:has(.tree-folder .node-label:has-text("${folderName}")))`
|
||||
}
|
||||
|
||||
getFolder(folderName: string) {
|
||||
return this.page.locator(this.folderSelector(folderName))
|
||||
}
|
||||
|
||||
nodeSelector(nodeName: string) {
|
||||
return `.p-tree-node-content:has(> .tree-explorer-node-label:has(.tree-leaf .node-label:has-text("${nodeName}")))`
|
||||
}
|
||||
|
||||
getNode(nodeName: string) {
|
||||
return this.page.locator(this.nodeSelector(nodeName))
|
||||
}
|
||||
}
|
||||
|
||||
class ComfyMenu {
|
||||
public readonly sideToolBar: Locator
|
||||
public readonly sideToolbar: Locator
|
||||
public readonly themeToggleButton: Locator
|
||||
|
||||
constructor(public readonly page: Page) {
|
||||
this.sideToolBar = page.locator('.side-tool-bar-container')
|
||||
this.sideToolbar = page.locator('.side-tool-bar-container')
|
||||
this.themeToggleButton = page.locator('.comfy-vue-theme-toggle')
|
||||
}
|
||||
|
||||
get nodeLibraryTab() {
|
||||
return new NodeLibrarySideBarTab(this.page)
|
||||
return new NodeLibrarySidebarTab(this.page)
|
||||
}
|
||||
|
||||
async toggleTheme() {
|
||||
@@ -118,6 +150,7 @@ export class ComfyPage {
|
||||
|
||||
// Buttons
|
||||
public readonly resetViewButton: Locator
|
||||
public readonly queueButton: Locator
|
||||
|
||||
// Inputs
|
||||
public readonly workflowUploadInput: Locator
|
||||
@@ -131,6 +164,7 @@ export class ComfyPage {
|
||||
this.canvas = page.locator('#graph-canvas')
|
||||
this.widgetTextBox = page.getByPlaceholder('text').nth(1)
|
||||
this.resetViewButton = page.getByRole('button', { name: 'Reset View' })
|
||||
this.queueButton = page.getByRole('button', { name: 'Queue Prompt' })
|
||||
this.workflowUploadInput = page.locator('#comfy-file-input')
|
||||
this.searchBox = new ComfyNodeSearchBox(page)
|
||||
this.menu = new ComfyMenu(page)
|
||||
@@ -168,9 +202,35 @@ export class ComfyPage {
|
||||
// Reset view to force re-rendering of canvas. So that info fields like fps
|
||||
// become hidden.
|
||||
await this.resetView()
|
||||
|
||||
// Hide all badges by default.
|
||||
await this.setSetting('Comfy.NodeBadge.NodeIdBadgeMode', NodeBadgeMode.None)
|
||||
await this.setSetting(
|
||||
'Comfy.NodeBadge.NodeSourceBadgeMode',
|
||||
NodeBadgeMode.None
|
||||
)
|
||||
}
|
||||
|
||||
async realod() {
|
||||
public assetPath(fileName: string) {
|
||||
return `./browser_tests/assets/${fileName}`
|
||||
}
|
||||
|
||||
async setSetting(settingId: string, settingValue: any) {
|
||||
return await this.page.evaluate(
|
||||
async ({ id, value }) => {
|
||||
await window['app'].ui.settings.setSettingValueAsync(id, value)
|
||||
},
|
||||
{ id: settingId, value: settingValue }
|
||||
)
|
||||
}
|
||||
|
||||
async getSetting(settingId: string) {
|
||||
return await this.page.evaluate(async (id) => {
|
||||
return await window['app'].ui.settings.getSettingValue(id)
|
||||
}, settingId)
|
||||
}
|
||||
|
||||
async reload() {
|
||||
await this.page.reload({ timeout: 15000 })
|
||||
await this.setup()
|
||||
}
|
||||
@@ -185,9 +245,13 @@ export class ComfyPage {
|
||||
})
|
||||
}
|
||||
|
||||
async delay(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
async loadWorkflow(workflowName: string) {
|
||||
await this.workflowUploadInput.setInputFiles(
|
||||
`./browser_tests/assets/${workflowName}.json`
|
||||
this.assetPath(`${workflowName}.json`)
|
||||
)
|
||||
await this.nextFrame()
|
||||
}
|
||||
@@ -211,6 +275,16 @@ export class ComfyPage {
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async clickTextEncodeNodeToggler() {
|
||||
await this.canvas.click({
|
||||
position: {
|
||||
x: 430,
|
||||
y: 171
|
||||
}
|
||||
})
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async clickTextEncodeNode2() {
|
||||
await this.canvas.click({
|
||||
position: {
|
||||
@@ -239,6 +313,54 @@ export class ComfyPage {
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async dragAndDropFile(fileName: string) {
|
||||
const filePath = this.assetPath(fileName)
|
||||
|
||||
// Read the file content
|
||||
const buffer = fs.readFileSync(filePath)
|
||||
|
||||
// Get file type
|
||||
const getFileType = (fileName: string) => {
|
||||
if (fileName.endsWith('.png')) return 'image/png'
|
||||
if (fileName.endsWith('.webp')) return 'image/webp'
|
||||
if (fileName.endsWith('.json')) return 'application/json'
|
||||
return 'application/octet-stream'
|
||||
}
|
||||
|
||||
const fileType = getFileType(fileName)
|
||||
|
||||
await this.page.evaluate(
|
||||
async ({ buffer, fileName, fileType }) => {
|
||||
const file = new File([new Uint8Array(buffer)], fileName, {
|
||||
type: fileType
|
||||
})
|
||||
const dataTransfer = new DataTransfer()
|
||||
dataTransfer.items.add(file)
|
||||
|
||||
const dropEvent = new DragEvent('drop', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer
|
||||
})
|
||||
|
||||
Object.defineProperty(dropEvent, 'preventDefault', {
|
||||
value: () => {},
|
||||
writable: false
|
||||
})
|
||||
|
||||
Object.defineProperty(dropEvent, 'stopPropagation', {
|
||||
value: () => {},
|
||||
writable: false
|
||||
})
|
||||
|
||||
document.dispatchEvent(dropEvent)
|
||||
},
|
||||
{ buffer: [...new Uint8Array(buffer)], fileName, fileType }
|
||||
)
|
||||
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async dragNode2() {
|
||||
await this.dragAndDrop({ x: 622, y: 400 }, { x: 622, y: 300 })
|
||||
await this.nextFrame()
|
||||
@@ -280,16 +402,22 @@ export class ComfyPage {
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async zoom(deltaY: number) {
|
||||
async zoom(deltaY: number, steps: number = 1) {
|
||||
await this.page.mouse.move(10, 10)
|
||||
await this.page.mouse.wheel(0, deltaY)
|
||||
for (let i = 0; i < steps; i++) {
|
||||
await this.page.mouse.wheel(0, deltaY)
|
||||
}
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async pan(offset: Position) {
|
||||
await this.page.mouse.move(10, 10)
|
||||
async pan(offset: Position, safeSpot?: Position) {
|
||||
safeSpot = safeSpot || { x: 10, y: 10 }
|
||||
await this.page.mouse.move(safeSpot.x, safeSpot.y)
|
||||
await this.page.mouse.down()
|
||||
await this.page.mouse.move(offset.x, offset.y)
|
||||
// TEMPORARY HACK: Multiple pans open the search menu, so cheat and keep it closed.
|
||||
// TODO: Fix that (double-click at not-the-same-coordinations should not open the menu)
|
||||
await this.page.keyboard.press('Escape')
|
||||
await this.page.mouse.move(offset.x + safeSpot.x, offset.y + safeSpot.y)
|
||||
await this.page.mouse.up()
|
||||
await this.nextFrame()
|
||||
}
|
||||
@@ -350,6 +478,34 @@ export class ComfyPage {
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async ctrlZ() {
|
||||
await this.page.keyboard.down('Control')
|
||||
await this.page.keyboard.press('KeyZ')
|
||||
await this.page.keyboard.up('Control')
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async ctrlY() {
|
||||
await this.page.keyboard.down('Control')
|
||||
await this.page.keyboard.press('KeyY')
|
||||
await this.page.keyboard.up('Control')
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async ctrlArrowUp() {
|
||||
await this.page.keyboard.down('Control')
|
||||
await this.page.keyboard.press('ArrowUp')
|
||||
await this.page.keyboard.up('Control')
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async ctrlArrowDown() {
|
||||
await this.page.keyboard.down('Control')
|
||||
await this.page.keyboard.press('ArrowDown')
|
||||
await this.page.keyboard.up('Control')
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async closeMenu() {
|
||||
await this.page.click('button.comfy-close-menu-btn')
|
||||
await this.nextFrame()
|
||||
|
||||
41
browser_tests/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Playwright Testing for ComfyUI_frontend
|
||||
|
||||
This document outlines the setup and usage of Playwright for testing the ComfyUI_frontend project.
|
||||
|
||||
## Setup
|
||||
|
||||
Ensure you have Node.js v20 or later installed. Then, set up the Chromium test driver:
|
||||
|
||||
```bash
|
||||
npx playwright install chromium --with-deps
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
There are two ways to run the tests:
|
||||
|
||||
1. **Headless mode with report generation:**
|
||||
```bash
|
||||
npx playwright test
|
||||
```
|
||||
This runs all tests without a visible browser and generates a comprehensive test report.
|
||||
|
||||
2. **UI mode for interactive testing:**
|
||||
```bash
|
||||
npx playwright test --ui
|
||||
```
|
||||
This opens a user interface where you can select specific tests to run and inspect the test execution timeline.
|
||||
|
||||

|
||||
|
||||
## Screenshot Expectations
|
||||
|
||||
Due to variations in system font rendering, screenshot expectations are platform-specific. Please note:
|
||||
|
||||
- We maintain Linux screenshot expectations as our GitHub Action runner operates in a Linux environment.
|
||||
- To set new test expectations:
|
||||
1. Create a pull request from a `Comfy-Org/ComfyUI_frontend` branch.
|
||||
2. Add the `New Browser Test Expectation` tag to your pull request.
|
||||
3. This will trigger a GitHub action to update the screenshot expectations automatically.
|
||||
|
||||
> **Note:** If you're making a pull request from a forked repository, the GitHub action won't be able to commit updated screenshot expectations directly to your PR branch.
|
||||
193
browser_tests/assets/batch_move_links.json
Normal file
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"last_node_id": 10,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
0,
|
||||
92
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
3,
|
||||
5
|
||||
],
|
||||
"slot_index": 1
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [],
|
||||
"slot_index": 2
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"3Guofeng3_v32Light.safetensors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
460,
|
||||
92
|
||||
],
|
||||
"size": {
|
||||
"0": 422.84503173828125,
|
||||
"1": 164.31304931640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"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": 7,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [
|
||||
460,
|
||||
368
|
||||
],
|
||||
"size": {
|
||||
"0": 425.27801513671875,
|
||||
"1": 180.6060791015625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 5
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": [
|
||||
0,
|
||||
276
|
||||
],
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"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": [
|
||||
"3Guofeng3_v32Light.safetensors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
3,
|
||||
4,
|
||||
1,
|
||||
6,
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
5,
|
||||
4,
|
||||
1,
|
||||
7,
|
||||
0,
|
||||
"CLIP"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
BIN
browser_tests/assets/edited_workflow.webp
Normal file
|
After Width: | Height: | Size: 200 KiB |
84
browser_tests/assets/execution_error.json
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"last_node_id": 17,
|
||||
"last_link_id": 15,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 14,
|
||||
"type": "PreviewImage",
|
||||
"pos": {
|
||||
"0": 300,
|
||||
"1": 60
|
||||
},
|
||||
"size": {
|
||||
"0": 213.8594970703125,
|
||||
"1": 50.65289306640625
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 15
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "PreviewImage"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 17,
|
||||
"type": "DevToolsErrorRaiseNode",
|
||||
"pos": {
|
||||
"0": 20,
|
||||
"1": 60
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 26
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"slot_index": 0,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DevToolsErrorRaiseNode"
|
||||
}
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
15,
|
||||
17,
|
||||
0,
|
||||
14,
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
117.20766722169206,
|
||||
472.69035116826046
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
BIN
browser_tests/assets/large_workflow.webp
Normal file
|
After Width: | Height: | Size: 316 KiB |
25
browser_tests/assets/missing_models.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"last_node_id": 0,
|
||||
"last_link_id": 0,
|
||||
"nodes": [],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"models": [
|
||||
{
|
||||
"name": "fake_model.safetensors",
|
||||
"url": "http://localhost:8188/api/devtools/fake_model.safetensors",
|
||||
"directory": "clip"
|
||||
}
|
||||
],
|
||||
"version": 0.4
|
||||
}
|
||||
61
browser_tests/assets/missing_nodes.json
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"last_node_id": 1,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "UNKNOWN NODE",
|
||||
"pos": [
|
||||
48,
|
||||
86
|
||||
],
|
||||
"size": {
|
||||
"0": 358.80780029296875,
|
||||
"1": 314.7989501953125
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "image",
|
||||
"type": "IMAGE",
|
||||
"link": null,
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [],
|
||||
"slot_index": 0,
|
||||
"shape": 6
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "UNKNOWN NODE"
|
||||
},
|
||||
"widgets_values": [
|
||||
"wd-v1-4-moat-tagger-v2",
|
||||
0.35,
|
||||
0.85,
|
||||
false,
|
||||
false,
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0, 0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
BIN
browser_tests/assets/no_workflow.webp
Normal file
|
After Width: | Height: | Size: 195 KiB |
145
browser_tests/assets/primitive_node.json
Normal file
@@ -0,0 +1,145 @@
|
||||
{
|
||||
"last_node_id": 2,
|
||||
"last_link_id": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "KSampler",
|
||||
"pos": {
|
||||
"0": 521.0906982421875,
|
||||
"1": 40.999996185302734,
|
||||
"2": 0,
|
||||
"3": 0,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0,
|
||||
"7": 0,
|
||||
"8": 0,
|
||||
"9": 0
|
||||
},
|
||||
"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
|
||||
},
|
||||
{
|
||||
"name": "steps",
|
||||
"type": "INT",
|
||||
"link": 1,
|
||||
"widget": {
|
||||
"name": "steps"
|
||||
}
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": null,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "PrimitiveNode",
|
||||
"pos": {
|
||||
"0": 15,
|
||||
"1": 46,
|
||||
"2": 0,
|
||||
"3": 0,
|
||||
"4": 0,
|
||||
"5": 0,
|
||||
"6": 0,
|
||||
"7": 0,
|
||||
"8": 0,
|
||||
"9": 0
|
||||
},
|
||||
"size": [
|
||||
446.96645387135936,
|
||||
108.34243389566905
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
1
|
||||
],
|
||||
"slot_index": 0,
|
||||
"widget": {
|
||||
"name": "steps"
|
||||
}
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Run widget replace on values": false
|
||||
},
|
||||
"widgets_values": [
|
||||
20,
|
||||
"fixed"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
2,
|
||||
4,
|
||||
"INT"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
88
browser_tests/assets/single_group.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 13,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": {
|
||||
"0": 10.321063995361328,
|
||||
"1": 73.14462280273438
|
||||
},
|
||||
"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": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
156680208700286,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [
|
||||
{
|
||||
"title": "Group",
|
||||
"bounding": [
|
||||
0,
|
||||
0,
|
||||
335,
|
||||
346
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
30
browser_tests/assets/single_group_only.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 13,
|
||||
"nodes": [],
|
||||
"links": [],
|
||||
"groups": [
|
||||
{
|
||||
"title": "Group",
|
||||
"bounding": [
|
||||
0,
|
||||
0,
|
||||
335,
|
||||
346
|
||||
],
|
||||
"color": "#3f789e",
|
||||
"font_size": 24
|
||||
}
|
||||
],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.2100000000000006,
|
||||
"offset": [
|
||||
104.34159172650945,
|
||||
241.35965953210126
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
76
browser_tests/assets/single_ksampler.json
Normal file
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 13,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 3,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
0,
|
||||
30
|
||||
],
|
||||
"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": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
156680208700286,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
46
browser_tests/assets/single_save_image_node.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"last_node_id": 9,
|
||||
"last_link_id": 9,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SaveImage",
|
||||
"pos": {
|
||||
"0": 64,
|
||||
"1": 104
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
377
browser_tests/assets/string_node_id.json
Normal file
@@ -0,0 +1,377 @@
|
||||
{
|
||||
"last_node_id": 0,
|
||||
"last_link_id": 18,
|
||||
"nodes": [
|
||||
{
|
||||
"id": "CheckpointLoaderSimple.0",
|
||||
"type": "CheckpointLoaderSimple",
|
||||
"pos": {
|
||||
"0": 100,
|
||||
"1": 130
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 98
|
||||
},
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "MODEL",
|
||||
"type": "MODEL",
|
||||
"links": [
|
||||
12
|
||||
],
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "CLIP",
|
||||
"type": "CLIP",
|
||||
"links": [
|
||||
10,
|
||||
11
|
||||
],
|
||||
"shape": 3
|
||||
},
|
||||
{
|
||||
"name": "VAE",
|
||||
"type": "VAE",
|
||||
"links": [
|
||||
17
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CheckpointLoaderSimple"
|
||||
},
|
||||
"widgets_values": [
|
||||
"v1-5-pruned-emaonly.ckpt"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "CLIPTextEncode.0",
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": {
|
||||
"0": 515,
|
||||
"1": 130
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 2,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 10
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
13
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "CLIPTextEncode.1",
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": {
|
||||
"0": 515,
|
||||
"1": 460
|
||||
},
|
||||
"size": {
|
||||
"0": 400,
|
||||
"1": 200
|
||||
},
|
||||
"flags": {},
|
||||
"order": 3,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": 11
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": [
|
||||
14
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [
|
||||
"text, watermark"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "EmptyLatentImage.0",
|
||||
"type": "EmptyLatentImage",
|
||||
"pos": {
|
||||
"0": 100,
|
||||
"1": 358
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 106
|
||||
},
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
15
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "EmptyLatentImage"
|
||||
},
|
||||
"widgets_values": [
|
||||
512,
|
||||
512,
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "KSampler.0",
|
||||
"type": "KSampler",
|
||||
"pos": {
|
||||
"0": 1015,
|
||||
"1": 130
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 262
|
||||
},
|
||||
"flags": {},
|
||||
"order": 4,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "model",
|
||||
"type": "MODEL",
|
||||
"link": 12
|
||||
},
|
||||
{
|
||||
"name": "positive",
|
||||
"type": "CONDITIONING",
|
||||
"link": 13
|
||||
},
|
||||
{
|
||||
"name": "negative",
|
||||
"type": "CONDITIONING",
|
||||
"link": 14
|
||||
},
|
||||
{
|
||||
"name": "latent_image",
|
||||
"type": "LATENT",
|
||||
"link": 15
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": [
|
||||
16
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
3,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "VAEDecode.0",
|
||||
"type": "VAEDecode",
|
||||
"pos": {
|
||||
"0": 1430,
|
||||
"1": 130
|
||||
},
|
||||
"size": {
|
||||
"0": 210,
|
||||
"1": 46
|
||||
},
|
||||
"flags": {},
|
||||
"order": 5,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "samples",
|
||||
"type": "LATENT",
|
||||
"link": 16
|
||||
},
|
||||
{
|
||||
"name": "vae",
|
||||
"type": "VAE",
|
||||
"link": 17
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [
|
||||
18
|
||||
],
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "VAEDecode"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "SaveImage.0",
|
||||
"type": "SaveImage",
|
||||
"pos": {
|
||||
"0": 1740,
|
||||
"1": 130
|
||||
},
|
||||
"size": {
|
||||
"0": 315,
|
||||
"1": 58
|
||||
},
|
||||
"flags": {},
|
||||
"order": 6,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 18
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": [
|
||||
"ComfyUI"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
10,
|
||||
"CheckpointLoaderSimple.0",
|
||||
1,
|
||||
"CLIPTextEncode.0",
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
11,
|
||||
"CheckpointLoaderSimple.0",
|
||||
1,
|
||||
"CLIPTextEncode.1",
|
||||
0,
|
||||
"CLIP"
|
||||
],
|
||||
[
|
||||
12,
|
||||
"CheckpointLoaderSimple.0",
|
||||
0,
|
||||
"KSampler.0",
|
||||
0,
|
||||
"MODEL"
|
||||
],
|
||||
[
|
||||
13,
|
||||
"CLIPTextEncode.0",
|
||||
0,
|
||||
"KSampler.0",
|
||||
1,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
14,
|
||||
"CLIPTextEncode.1",
|
||||
0,
|
||||
"KSampler.0",
|
||||
2,
|
||||
"CONDITIONING"
|
||||
],
|
||||
[
|
||||
15,
|
||||
"EmptyLatentImage.0",
|
||||
0,
|
||||
"KSampler.0",
|
||||
3,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
16,
|
||||
"KSampler.0",
|
||||
0,
|
||||
"VAEDecode.0",
|
||||
0,
|
||||
"LATENT"
|
||||
],
|
||||
[
|
||||
17,
|
||||
"CheckpointLoaderSimple.0",
|
||||
2,
|
||||
"VAEDecode.0",
|
||||
1,
|
||||
"VAE"
|
||||
],
|
||||
[
|
||||
18,
|
||||
"VAEDecode.0",
|
||||
0,
|
||||
"SaveImage.0",
|
||||
0,
|
||||
"IMAGE"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
149.9747408641311,
|
||||
383.8593224280729
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
BIN
browser_tests/assets/workflow.webp
Normal file
|
After Width: | Height: | Size: 200 KiB |
@@ -22,6 +22,28 @@ test.describe('Copy Paste', () => {
|
||||
expect(resultString).toBe(originalString + originalString)
|
||||
})
|
||||
|
||||
test('Can copy and paste widget value', async ({ comfyPage }) => {
|
||||
// Copy width value (512) from empty latent node to KSampler's seed.
|
||||
// Empty latent node's width
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 718,
|
||||
y: 643
|
||||
}
|
||||
})
|
||||
await comfyPage.ctrlC()
|
||||
// KSampler's seed
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 1005,
|
||||
y: 281
|
||||
}
|
||||
})
|
||||
await comfyPage.ctrlV()
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-widget-value.png')
|
||||
})
|
||||
|
||||
/**
|
||||
* https://github.com/Comfy-Org/ComfyUI_frontend/issues/98
|
||||
*/
|
||||
@@ -53,4 +75,15 @@ test.describe('Copy Paste', () => {
|
||||
await comfyPage.ctrlV()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('no-node-copied.png')
|
||||
})
|
||||
|
||||
test('Copy node by dragging + alt', async ({ comfyPage }) => {
|
||||
// TextEncodeNode1
|
||||
await comfyPage.page.mouse.move(618, 191)
|
||||
await comfyPage.page.keyboard.down('Alt')
|
||||
await comfyPage.page.mouse.down()
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
await comfyPage.page.mouse.up()
|
||||
await comfyPage.page.keyboard.up('Alt')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('drag-copy-copied-node.png')
|
||||
})
|
||||
})
|
||||
|
||||
|
After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 103 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 106 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 95 KiB |
106
browser_tests/dialog.spec.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from './ComfyPage'
|
||||
|
||||
test.describe('Load workflow warning', () => {
|
||||
test('Should display a warning when loading a workflow with missing nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('missing_nodes')
|
||||
|
||||
// Wait for the element with the .comfy-missing-nodes selector to be visible
|
||||
const missingNodesWarning = comfyPage.page.locator('.comfy-missing-nodes')
|
||||
await expect(missingNodesWarning).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test('Does not report warning when switching between opened workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.loadWorkflow('missing_nodes')
|
||||
await comfyPage.page.locator('.p-dialog-close-button').click()
|
||||
|
||||
// Load default workflow
|
||||
const workflowSelector = comfyPage.page.locator(
|
||||
'button.comfyui-workflows-button'
|
||||
)
|
||||
await workflowSelector.hover()
|
||||
await workflowSelector.click()
|
||||
await comfyPage.page.locator('button[title="Load default workflow"]').click()
|
||||
|
||||
// Switch back to the missing_nodes workflow
|
||||
await workflowSelector.click()
|
||||
await comfyPage.page.locator('span:has-text("missing_nodes")').first().click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test('Does not report warning on undo/redo', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('missing_nodes')
|
||||
await comfyPage.page.locator('.p-dialog-close-button').click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Make a change to the graph
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
await comfyPage.page.waitForTimeout(256)
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||
|
||||
// Undo and redo the change
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
|
||||
await comfyPage.ctrlY()
|
||||
await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test.describe('Execution error', () => {
|
||||
test('Should display an error message when an execution error occurs', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('execution_error')
|
||||
await comfyPage.queueButton.click()
|
||||
|
||||
// Wait for the element with the .comfy-execution-error selector to be visible
|
||||
const executionError = comfyPage.page.locator('.comfy-error-report')
|
||||
await expect(executionError).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Missing models warning', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate((url: string) => {
|
||||
return fetch(`${url}/api/devtools/cleanup_fake_model`)
|
||||
}, comfyPage.url)
|
||||
await comfyPage.setSetting('Comfy.Workflow.ModelDownload.AllowedSources', [
|
||||
'http://localhost:8188'
|
||||
])
|
||||
await comfyPage.setSetting('Comfy.Workflow.ModelDownload.AllowedSuffixes', [
|
||||
'.safetensors'
|
||||
])
|
||||
})
|
||||
|
||||
test('Should display a warning when missing models are found', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Workflow.ShowMissingModelsWarning', true)
|
||||
|
||||
// The fake_model.safetensors is served by
|
||||
// https://github.com/Comfy-Org/ComfyUI_devtools/blob/main/__init__.py
|
||||
await comfyPage.loadWorkflow('missing_models')
|
||||
|
||||
// Wait for the element with the .comfy-missing-models selector to be visible
|
||||
const missingModelsWarning = comfyPage.page.locator('.comfy-missing-models')
|
||||
await expect(missingModelsWarning).toBeVisible()
|
||||
|
||||
// Click the download button
|
||||
const downloadButton = comfyPage.page.getByLabel('Download')
|
||||
await expect(downloadButton).toBeVisible()
|
||||
await downloadButton.click()
|
||||
|
||||
// Wait for the element with the .download-complete selector to be visible
|
||||
const downloadComplete = comfyPage.page.locator('.download-complete')
|
||||
await expect(downloadComplete).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,5 @@
|
||||
import { expect } from '@playwright/test'
|
||||
import { ComfyPage, comfyPageFixture as test } from './ComfyPage'
|
||||
import { comfyPageFixture as test } from './ComfyPage'
|
||||
|
||||
test.describe('Node Interaction', () => {
|
||||
test('Can enter prompt', async ({ comfyPage }) => {
|
||||
@@ -19,13 +19,13 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-node2.png')
|
||||
})
|
||||
|
||||
// Flaky. See https://github.com/comfyanonymous/ComfyUI/issues/3866
|
||||
test.skip('Can drag node', async ({ comfyPage }) => {
|
||||
test('Can drag node', async ({ comfyPage }) => {
|
||||
await comfyPage.dragNode2()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('dragged-node1.png')
|
||||
})
|
||||
|
||||
test('Can disconnect/connect edge', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'no action')
|
||||
await comfyPage.disconnectEdge()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'disconnected-edge-with-menu.png'
|
||||
@@ -58,6 +58,174 @@ test.describe('Node Interaction', () => {
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('snap_to_slot_linked.png')
|
||||
})
|
||||
|
||||
test('Can batch move links by drag with shift', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('batch_move_links')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('batch_move_links.png')
|
||||
|
||||
const outputSlot1Pos = {
|
||||
x: 304,
|
||||
y: 127
|
||||
}
|
||||
const outputSlot2Pos = {
|
||||
x: 307,
|
||||
y: 310
|
||||
}
|
||||
|
||||
await comfyPage.page.keyboard.down('Shift')
|
||||
await comfyPage.dragAndDrop(outputSlot1Pos, outputSlot2Pos)
|
||||
await comfyPage.page.keyboard.up('Shift')
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'batch_move_links_moved.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can batch disconnect links with ctrl+alt+click', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointClipSlotPos = {
|
||||
x: 332,
|
||||
y: 508
|
||||
}
|
||||
await comfyPage.canvas.click({
|
||||
modifiers: ['Control', 'Alt'],
|
||||
position: loadCheckpointClipSlotPos
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'batch-disconnect-links-disconnected.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can toggle dom widget node open/closed', async ({ comfyPage }) => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default.png')
|
||||
await comfyPage.clickTextEncodeNodeToggler()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'text-encode-toggled-off.png'
|
||||
)
|
||||
await comfyPage.delay(1000)
|
||||
await comfyPage.clickTextEncodeNodeToggler()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'text-encode-toggled-back-open.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can close prompt dialog with canvas click (number widget)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const numberWidgetPos = {
|
||||
x: 724,
|
||||
y: 645
|
||||
}
|
||||
await comfyPage.canvas.click({
|
||||
position: numberWidgetPos
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('prompt-dialog-opened.png')
|
||||
// Wait for 1s so that it does not trigger the search box by double click.
|
||||
await comfyPage.page.waitForTimeout(1000)
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 10,
|
||||
y: 10
|
||||
}
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('prompt-dialog-closed.png')
|
||||
})
|
||||
|
||||
test('Can close prompt dialog with canvas click (text widget)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const textWidgetPos = {
|
||||
x: 167,
|
||||
y: 143
|
||||
}
|
||||
await comfyPage.loadWorkflow('single_save_image_node')
|
||||
await comfyPage.canvas.click({
|
||||
position: textWidgetPos
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'prompt-dialog-opened-text.png'
|
||||
)
|
||||
await comfyPage.page.waitForTimeout(1000)
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 10,
|
||||
y: 10
|
||||
}
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'prompt-dialog-closed-text.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can double click node title to edit', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('single_ksampler')
|
||||
await comfyPage.canvas.dblclick({
|
||||
position: {
|
||||
x: 50,
|
||||
y: 10
|
||||
}
|
||||
})
|
||||
await comfyPage.page.keyboard.type('Hello World')
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-title-edited.png')
|
||||
})
|
||||
|
||||
test('Double click node body does not trigger edit', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('single_ksampler')
|
||||
await comfyPage.canvas.dblclick({
|
||||
position: {
|
||||
x: 50,
|
||||
y: 50
|
||||
}
|
||||
})
|
||||
expect(await comfyPage.page.locator('.node-title-editor').count()).toBe(0)
|
||||
})
|
||||
|
||||
test('Can group selected nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.GroupSelectedNodes.Padding', 10)
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
await comfyPage.page.keyboard.press('KeyG')
|
||||
await comfyPage.page.keyboard.up('Control')
|
||||
await comfyPage.nextFrame()
|
||||
// Confirm group title
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('group-selected-nodes.png')
|
||||
})
|
||||
|
||||
// Somehow this test fails on GitHub Actions. It works locally.
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/pull/736
|
||||
test.skip('Can pin/unpin nodes with keyboard shortcut', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.canvas.press('KeyP')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-pinned.png')
|
||||
await comfyPage.canvas.press('KeyP')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-unpinned.png')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Group Interaction', () => {
|
||||
test('Can double click group title to edit', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('single_group')
|
||||
await comfyPage.canvas.dblclick({
|
||||
position: {
|
||||
x: 50,
|
||||
y: 10
|
||||
}
|
||||
})
|
||||
await comfyPage.page.keyboard.type('Hello World')
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('group-title-edited.png')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Canvas Interaction', () => {
|
||||
@@ -68,8 +236,111 @@ test.describe('Canvas Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-out.png')
|
||||
})
|
||||
|
||||
test('Can zoom very far out', async ({ comfyPage }) => {
|
||||
await comfyPage.zoom(100, 12)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-very-far-out.png')
|
||||
await comfyPage.zoom(-100, 12)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-back-in.png')
|
||||
})
|
||||
|
||||
test('Can zoom in/out with ctrl+shift+vertical-drag', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
await comfyPage.page.keyboard.down('Shift')
|
||||
await comfyPage.dragAndDrop({ x: 10, y: 100 }, { x: 10, y: 40 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-in-ctrl-shift.png')
|
||||
await comfyPage.dragAndDrop({ x: 10, y: 40 }, { x: 10, y: 160 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-out-ctrl-shift.png')
|
||||
await comfyPage.dragAndDrop({ x: 10, y: 280 }, { x: 10, y: 220 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'zoomed-default-ctrl-shift.png'
|
||||
)
|
||||
await comfyPage.page.keyboard.up('Control')
|
||||
await comfyPage.page.keyboard.up('Shift')
|
||||
})
|
||||
|
||||
test('Can zoom in/out after decreasing canvas zoom speed setting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.05)
|
||||
await comfyPage.zoom(-100, 4)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'zoomed-in-low-zoom-speed.png'
|
||||
)
|
||||
await comfyPage.zoom(100, 8)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'zoomed-out-low-zoom-speed.png'
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.1)
|
||||
})
|
||||
|
||||
test('Can zoom in/out after increasing canvas zoom speed', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.5)
|
||||
await comfyPage.zoom(-100, 4)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'zoomed-in-high-zoom-speed.png'
|
||||
)
|
||||
await comfyPage.zoom(100, 8)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'zoomed-out-high-zoom-speed.png'
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.Graph.ZoomSpeed', 1.1)
|
||||
})
|
||||
|
||||
test('Can pan', async ({ comfyPage }) => {
|
||||
await comfyPage.pan({ x: 200, y: 200 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned.png')
|
||||
})
|
||||
|
||||
test('Can pan very far and back', async ({ comfyPage }) => {
|
||||
// intentionally slice the edge of where the clip text encode dom widgets are
|
||||
await comfyPage.pan({ x: -800, y: -300 }, { x: 1000, y: 10 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-step-one.png')
|
||||
await comfyPage.pan({ x: -200, y: 0 }, { x: 1000, y: 10 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-step-two.png')
|
||||
await comfyPage.pan({ x: -2200, y: -2200 }, { x: 1000, y: 10 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-far-away.png')
|
||||
await comfyPage.pan({ x: 2200, y: 2200 }, { x: 1000, y: 10 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-back-from-far.png')
|
||||
await comfyPage.pan({ x: 200, y: 0 }, { x: 1000, y: 10 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-back-to-two.png')
|
||||
await comfyPage.pan({ x: 800, y: 300 }, { x: 1000, y: 10 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('panned-back-to-one.png')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Widget Interaction', () => {
|
||||
test('Undo text input', async ({ comfyPage }) => {
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.fill('')
|
||||
await expect(textBox).toHaveValue('')
|
||||
await textBox.fill('Hello World')
|
||||
await expect(textBox).toHaveValue('Hello World')
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(textBox).toHaveValue('')
|
||||
})
|
||||
|
||||
test('Undo attention edit', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.EditAttention.Delta', 0.05)
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.fill('1girl')
|
||||
await expect(textBox).toHaveValue('1girl')
|
||||
await textBox.selectText()
|
||||
await comfyPage.ctrlArrowUp()
|
||||
await expect(textBox).toHaveValue('(1girl:1.05)')
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(textBox).toHaveValue('1girl')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Load workflow', () => {
|
||||
test('Can load workflow with string node id', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('string_node_id')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('string_node_id.png')
|
||||
})
|
||||
})
|
||||
|
||||
|
After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 90 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 93 KiB |
|
Before Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 91 KiB |
|
After Width: | Height: | Size: 107 KiB |
|
After Width: | Height: | Size: 103 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 72 KiB |
|
Before Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 55 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 64 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 70 KiB |