Compare commits
4 Commits
update-fix
...
create_fol
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e6e34cfd3 | ||
|
|
2a445f3f94 | ||
|
|
f8dcb915aa | ||
|
|
11925ce345 |
11
.cursorrules
@@ -8,15 +8,6 @@ const vue3CompositionApiBestPractices = [
|
||||
"Use watch and watchEffect for side effects",
|
||||
"Implement lifecycle hooks with onMounted, onUpdated, etc.",
|
||||
"Utilize provide/inject for dependency injection",
|
||||
"Use vue 3.5 style of default prop declaration. Example:
|
||||
|
||||
const { nodes, showTotal = true } = defineProps<{
|
||||
nodes: ApiNodeCost[]
|
||||
showTotal?: boolean
|
||||
}>()
|
||||
|
||||
",
|
||||
"Organize vue component in <template> <script> <style> order",
|
||||
]
|
||||
|
||||
// Folder structure
|
||||
@@ -49,6 +40,4 @@ const additionalInstructions = `
|
||||
7. Implement proper error handling
|
||||
8. Follow Vue 3 style guide and naming conventions
|
||||
9. Use Vite for fast development and building
|
||||
10. Use vue-i18n in composition API for any string literals. Place new translation
|
||||
entries in src/locales/en/main.json.
|
||||
`;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
# Local development playwright target
|
||||
# Note: Don't add a trailing / after the port
|
||||
PLAYWRIGHT_TEST_URL=http://localhost:5173
|
||||
# PLAYWRIGHT_TEST_URL=http://localhost:8188
|
||||
|
||||
@@ -18,10 +17,3 @@ TEST_COMFYUI_DIR=/home/ComfyUI
|
||||
|
||||
# Whether to enable minification of the frontend code.
|
||||
ENABLE_MINIFY=true
|
||||
|
||||
# Whether to disable proxying the `/templates` route. If true, allows you to
|
||||
# serve templates from the ComfyUI_frontend/public/templates folder (for
|
||||
# locally testing changes to templates). When false or nonexistent, the
|
||||
# templates are served via the normal method from the server's python site
|
||||
# packages.
|
||||
DISABLE_TEMPLATES_PROXY=false
|
||||
|
||||
37
.github/copilot-instructions.md
vendored
@@ -1,37 +0,0 @@
|
||||
Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
|
||||
|
||||
Use setup() function for component logic
|
||||
|
||||
Utilize ref and reactive for reactive state
|
||||
|
||||
Implement computed properties with computed()
|
||||
|
||||
Use watch and watchEffect for side effects
|
||||
|
||||
Implement lifecycle hooks with onMounted, onUpdated, etc.
|
||||
|
||||
Utilize provide/inject for dependency injection
|
||||
|
||||
Use vue 3.5 style of default prop declaration.
|
||||
|
||||
Use Tailwind CSS for styling
|
||||
|
||||
Leverage VueUse functions for performance-enhancing styles
|
||||
|
||||
Use lodash for utility functions
|
||||
|
||||
Use TypeScript for type safety
|
||||
|
||||
Implement proper props and emits definitions
|
||||
|
||||
Utilize Vue 3's Teleport component when needed
|
||||
|
||||
Use Suspense for async components
|
||||
|
||||
Implement proper error handling
|
||||
|
||||
Follow Vue 3 style guide and naming conventions
|
||||
|
||||
Use Vite for fast development and building
|
||||
|
||||
Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json.
|
||||
2
.github/workflows/release.yaml
vendored
@@ -29,9 +29,9 @@ jobs:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }}
|
||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||
USE_PROD_CONFIG: 'true'
|
||||
run: |
|
||||
npm ci
|
||||
npm run fetch-templates
|
||||
npm run build
|
||||
npm run zipdist
|
||||
- name: Upload dist artifact
|
||||
|
||||
3
.github/workflows/test-ui.yaml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
with:
|
||||
repository: 'Comfy-Org/ComfyUI_devtools'
|
||||
path: 'ComfyUI/custom_nodes/ComfyUI_devtools'
|
||||
ref: '9d2421fd3208a310e4d0f71fca2ea0c985759c33'
|
||||
ref: '080e6d4af809a46852d1c4b7ed85f06e8a3a72be'
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
@@ -39,6 +39,7 @@ jobs:
|
||||
- name: Build ComfyUI_frontend
|
||||
run: |
|
||||
npm ci
|
||||
npm run fetch-templates
|
||||
npm run build
|
||||
working-directory: ComfyUI_frontend
|
||||
|
||||
|
||||
2
.github/workflows/update-electron-types.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: '[chore] Update electron-types to ${{ steps.get-version.outputs.NEW_VERSION }}'
|
||||
title: '[chore] Update electron-types to ${{ steps.get-version.outputs.NEW_VERSION }}'
|
||||
body: |
|
||||
|
||||
5
.gitignore
vendored
@@ -38,7 +38,7 @@ tests-ui/workflows/examples
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
browser_tests/**/*-win32.png
|
||||
browser_tests/*/*-win32.png
|
||||
|
||||
.env
|
||||
|
||||
@@ -55,6 +55,3 @@ dist.zip
|
||||
|
||||
# Temporary repository directory
|
||||
templates_repo/
|
||||
|
||||
# Vite’s timestamped config modules
|
||||
vite.config.mts.timestamp-*.mjs
|
||||
|
||||
@@ -4,13 +4,13 @@
|
||||
const { defineConfig } = require('@lobehub/i18n-cli');
|
||||
|
||||
module.exports = defineConfig({
|
||||
modelName: 'gpt-4.1',
|
||||
modelName: 'gpt-4',
|
||||
splitToken: 1024,
|
||||
entry: 'src/locales/en',
|
||||
entryLocale: 'en',
|
||||
output: 'src/locales',
|
||||
outputLocales: ['zh', 'ru', 'ja', 'ko', 'fr', 'es'],
|
||||
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, stable zero, controlnet, lora, HiDream.
|
||||
outputLocales: ['zh', 'ru', 'ja', 'ko', 'fr'],
|
||||
reference: `Special names to keep untranslated: flux, photomaker, clip, vae, cfg, stable audio, stable cascade, controlnet, lora.
|
||||
'latent' is the short form of 'latent space'.
|
||||
'mask' is in the context of image processing.
|
||||
`
|
||||
|
||||
25
.vscode/extensions.json
vendored
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"austenc.tailwind-docs",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"eamodio.gitlens",
|
||||
"esbenp.prettier-vscode",
|
||||
"figma.figma-vscode-extension",
|
||||
"github.vscode-github-actions",
|
||||
"github.vscode-pull-request-github",
|
||||
"hbenl.vscode-test-explorer",
|
||||
"lokalise.i18n-ally",
|
||||
"ms-playwright.playwright",
|
||||
"vitest.explorer",
|
||||
"vue.volar",
|
||||
"sonarsource.sonarlint-vscode",
|
||||
"deque-systems.vscode-axe-linter",
|
||||
"kisstkondoros.vscode-codemetrics",
|
||||
"donjayamanne.githistory",
|
||||
"wix.vscode-import-cost",
|
||||
"prograhammer.tslint-vue",
|
||||
"antfu.vite"
|
||||
]
|
||||
}
|
||||
20
README.md
@@ -510,20 +510,6 @@ The selection toolbox will display the command button when items are selected:
|
||||
|
||||
</details>
|
||||
|
||||
## Contributing
|
||||
|
||||
We're building this frontend together and would love your help — no matter how you'd like to pitch in! You don't need to write code to make a difference.
|
||||
|
||||
Here are some ways to get involved:
|
||||
|
||||
- **Pull Requests:** Add features, fix bugs, or improve code health. Browse [issues](https://github.com/Comfy-Org/ComfyUI_frontend/issues) for inspiration.
|
||||
- **Vote on Features:** Give a 👍 to the feature requests you care about to help us prioritize.
|
||||
- **Verify Bugs:** Try reproducing reported issues and share your results (even if the bug doesn't occur!).
|
||||
- **Community Support:** Hop into our [Discord](https://www.comfy.org/discord) to answer questions or get help.
|
||||
- **Share & Advocate:** Tell your friends, tweet about us, or share tips to support the project.
|
||||
|
||||
Have another idea? Drop into Discord or open an issue, and let's chat!
|
||||
|
||||
## Development
|
||||
|
||||
### Tech Stack
|
||||
@@ -571,12 +557,6 @@ After you start the dev server, you should see following logs:
|
||||
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.
|
||||
|
||||
### Recommended Code Editor Configuration
|
||||
|
||||
This project includes `.vscode/launch.json.default` and `.vscode/settings.json.default` files with recommended launch and workspace settings for editors that use the `.vscode` directory (e.g., VS Code, Cursor, etc.).
|
||||
|
||||
We’ve also included a list of recommended extensions in `.vscode/extensions.json`. Your editor should detect this file and show a human friendly list in the Extensions panel, linking each entry to its marketplace page.
|
||||
|
||||
### Unit Test
|
||||
|
||||
- `npm i` to install all dependencies
|
||||
|
||||
@@ -9,26 +9,15 @@ If `TEST_COMFYUI_DIR` in `.env` isn't set to your `(Comfy Path)/ComfyUI` directo
|
||||
|
||||
## Setup
|
||||
|
||||
### ComfyUI devtools
|
||||
Clone <https://github.com/Comfy-Org/ComfyUI_devtools> to your `custom_nodes` directory.
|
||||
_ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing._
|
||||
Clone <https://github.com/Comfy-Org/ComfyUI_devtools> to your `custom_nodes` directory.
|
||||
ComfyUI_devtools adds additional API endpoints and nodes to ComfyUI for browser testing.
|
||||
|
||||
### Node.js & Playwright Prerequisites
|
||||
Ensure you have Node.js v20 or later installed. Then, set up the Chromium test driver:
|
||||
|
||||
```bash
|
||||
npx playwright install chromium --with-deps
|
||||
```
|
||||
|
||||
### Environment Variables
|
||||
Ensure the environment variables in `.env` are set correctly according to your setup.
|
||||
|
||||
The `.env` file will not exist until you create it yourself.
|
||||
|
||||
A template with helpful information can be found in `.env_example`.
|
||||
|
||||
### Multiple Tests
|
||||
If you are running Playwright tests in parallel or running the same test multiple times, the flag `--multi-user` must be added to the main ComfyUI process.
|
||||
|
||||
## Running Tests
|
||||
|
||||
There are two ways to run the tests:
|
||||
@@ -45,6 +34,8 @@ There are two ways to run the tests:
|
||||
```
|
||||
This opens a user interface where you can select specific tests to run and inspect the test execution timeline.
|
||||
|
||||
To run the same test multiple times in Playwright's UI mode, you must launch the main ComfyUI process with the `--multi-user` flag.
|
||||
|
||||

|
||||
|
||||
## Screenshot Expectations
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import type { Response } from '@playwright/test'
|
||||
import { expect, mergeTests } from '@playwright/test'
|
||||
|
||||
import type { StatusWsMessage } from '../../src/schemas/apiSchema.ts'
|
||||
import { comfyPageFixture } from '../fixtures/ComfyPage.ts'
|
||||
import { webSocketFixture } from '../fixtures/ws.ts'
|
||||
import type { StatusWsMessage } from '../src/schemas/apiSchema.ts'
|
||||
import { comfyPageFixture } from './fixtures/ComfyPage'
|
||||
import { webSocketFixture } from './fixtures/ws.ts'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB |
@@ -1,126 +0,0 @@
|
||||
{
|
||||
"id": "51b9b184-770d-40ac-a478-8cc31667ff23",
|
||||
"revision": 0,
|
||||
"last_node_id": 5,
|
||||
"last_link_id": 3,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 4,
|
||||
"type": "KSampler",
|
||||
"pos": [
|
||||
867.4669799804688,
|
||||
347.22369384765625
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
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",
|
||||
"widget": {
|
||||
"name": "steps"
|
||||
},
|
||||
"link": 3
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "LATENT",
|
||||
"type": "LATENT",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "PrimitiveInt",
|
||||
"pos": [
|
||||
443.0852355957031,
|
||||
441.131591796875
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "INT",
|
||||
"type": "INT",
|
||||
"links": [
|
||||
3
|
||||
]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PrimitiveInt"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"randomize"
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
[
|
||||
3,
|
||||
5,
|
||||
0,
|
||||
4,
|
||||
5,
|
||||
"INT"
|
||||
]
|
||||
],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.9487171000000016,
|
||||
"offset": [
|
||||
-325.57196748514497,
|
||||
-168.13150517966463
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
{
|
||||
"id": "9bcb9451-8319-492a-88d4-fb711d8c3d25",
|
||||
"revision": 0,
|
||||
"last_node_id": 6,
|
||||
"last_link_id": 0,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 6,
|
||||
"type": "DevToolsNodeWithDefaultInput",
|
||||
"pos": [
|
||||
8.39722728729248,
|
||||
29.727279663085938
|
||||
],
|
||||
"size": [
|
||||
315,
|
||||
82
|
||||
],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "float_input",
|
||||
"shape": 7,
|
||||
"type": "FLOAT",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {
|
||||
"Node name for S&R": "DevToolsNodeWithDefaultInput"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 2.1600300525920346,
|
||||
"offset": [
|
||||
63.071794466403446,
|
||||
75.18055335968394
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
{
|
||||
"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
|
||||
} ,
|
||||
{
|
||||
"name": "dynamic_input",
|
||||
"type": "FLOAT",
|
||||
"link": null,
|
||||
"_meta": "Dynamically added input via frontend JS logic"
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
{
|
||||
"id": "51b9b184-770d-40ac-a478-8cc31667ff23",
|
||||
"revision": 0,
|
||||
"last_node_id": 2,
|
||||
"last_link_id": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": "CLIPTextEncode",
|
||||
"pos": [904, 466],
|
||||
"size": [400, 200],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "text",
|
||||
"type": "STRING",
|
||||
"widget": {
|
||||
"name": "text"
|
||||
},
|
||||
"link": 1
|
||||
},
|
||||
{
|
||||
"name": "clip",
|
||||
"type": "CLIP",
|
||||
"link": null
|
||||
}
|
||||
],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "CONDITIONING",
|
||||
"type": "CONDITIONING",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "CLIPTextEncode"
|
||||
},
|
||||
"widgets_values": [""]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "PrimitiveString",
|
||||
"pos": [556.8589477539062, 472.94342041015625],
|
||||
"size": [315, 58],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "STRING",
|
||||
"type": "STRING",
|
||||
"links": [1]
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "PrimitiveString"
|
||||
},
|
||||
"widgets_values": ["foo"]
|
||||
}
|
||||
],
|
||||
"links": [[1, 2, 0, 1, 0, "STRING"]],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1.7715610000000013,
|
||||
"offset": [-388.521484375, -162.31336975097656]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -51,10 +51,7 @@
|
||||
0.85,
|
||||
false,
|
||||
false,
|
||||
"",
|
||||
{
|
||||
"foo": "bar"
|
||||
}
|
||||
""
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
{
|
||||
"last_node_id": 2,
|
||||
"last_link_id": 1,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 2,
|
||||
"type": "KSampler",
|
||||
"pos": {
|
||||
"0": 304.3653259277344,
|
||||
"1": 42.15586471557617
|
||||
},
|
||||
"size": [
|
||||
315,
|
||||
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,
|
||||
"shape": 3
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "KSampler"
|
||||
},
|
||||
"widgets_values": [
|
||||
0,
|
||||
"randomize",
|
||||
20,
|
||||
8,
|
||||
"euler",
|
||||
"normal",
|
||||
1
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"type": "PrimitiveInt",
|
||||
"pos": {
|
||||
"0": 14,
|
||||
"1": 43
|
||||
},
|
||||
"size": [
|
||||
203.1999969482422,
|
||||
40.368401303242536
|
||||
],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "value",
|
||||
"type": "INT",
|
||||
"links": [],
|
||||
"slot_index": 0
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "Int"
|
||||
},
|
||||
"widgets_values": [10]
|
||||
}
|
||||
],
|
||||
"links": [],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"ds": {
|
||||
"scale": 1,
|
||||
"offset": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
}
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"8": {
|
||||
"inputs": {
|
||||
"image": "animated_web.webp"
|
||||
},
|
||||
"class_type": "DevToolsLoadAnimatedImageTest",
|
||||
"_meta": {
|
||||
"title": "Load Animated Image"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
{
|
||||
"id": "3f1fcbf9-f9de-4935-8fad-401813f61b13",
|
||||
"revision": 0,
|
||||
"last_node_id": 10,
|
||||
"last_link_id": 4,
|
||||
"nodes": [
|
||||
{
|
||||
"id": 9,
|
||||
"type": "SaveAnimatedWEBP",
|
||||
"pos": [336, 104],
|
||||
"size": [210, 368],
|
||||
"flags": {},
|
||||
"order": 1,
|
||||
"mode": 0,
|
||||
"inputs": [
|
||||
{
|
||||
"name": "images",
|
||||
"type": "IMAGE",
|
||||
"link": 4
|
||||
}
|
||||
],
|
||||
"outputs": [],
|
||||
"properties": {},
|
||||
"widgets_values": ["ComfyUI", 6, true, 80, "default"]
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "DevToolsLoadAnimatedImageTest",
|
||||
"pos": [64, 104],
|
||||
"size": [210, 316],
|
||||
"flags": {},
|
||||
"order": 0,
|
||||
"mode": 0,
|
||||
"inputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "IMAGE",
|
||||
"type": "IMAGE",
|
||||
"links": [4]
|
||||
},
|
||||
{
|
||||
"name": "MASK",
|
||||
"type": "MASK",
|
||||
"links": null
|
||||
}
|
||||
],
|
||||
"properties": {
|
||||
"Node name for S&R": "DevToolsLoadAnimatedImageTest"
|
||||
},
|
||||
"widgets_values": ["animated_web.webp", "image"]
|
||||
}
|
||||
],
|
||||
"links": [[4, 10, 0, 9, 0, "IMAGE"]],
|
||||
"groups": [],
|
||||
"config": {},
|
||||
"extra": {
|
||||
"frontendVersion": "1.17.0"
|
||||
},
|
||||
"version": 0.4
|
||||
}
|
||||
|
Before Width: | Height: | Size: 75 KiB |
@@ -1,6 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Browser tab title', () => {
|
||||
test.describe('Beta Menu', () => {
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
ComfyPage,
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../fixtures/ComfyPage'
|
||||
} from './fixtures/ComfyPage'
|
||||
|
||||
async function beforeChange(comfyPage: ComfyPage) {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { Palette } from '../../src/schemas/colorPaletteSchema'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import type { Palette } from '../src/schemas/colorPaletteSchema'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
const customColorPalettes: Record<string, Palette> = {
|
||||
obsidian: {
|
||||
@@ -48,7 +48,6 @@ const customColorPalettes: Record<string, Palette> = {
|
||||
WIDGET_OUTLINE_COLOR: '#333',
|
||||
WIDGET_TEXT_COLOR: '#a3a3a8',
|
||||
WIDGET_SECONDARY_TEXT_COLOR: '#97979c',
|
||||
WIDGET_DISABLED_TEXT_COLOR: '#646464',
|
||||
LINK_COLOR: '#9A9',
|
||||
EVENT_LINK_COLOR: '#A86',
|
||||
CONNECTING_LINK_COLOR: '#AFA'
|
||||
@@ -112,7 +111,6 @@ const customColorPalettes: Record<string, Palette> = {
|
||||
WIDGET_OUTLINE_COLOR: '#333',
|
||||
WIDGET_TEXT_COLOR: '#a3a3a8',
|
||||
WIDGET_SECONDARY_TEXT_COLOR: '#97979c',
|
||||
WIDGET_DISABLED_TEXT_COLOR: '#646464',
|
||||
LINK_COLOR: '#9A9',
|
||||
EVENT_LINK_COLOR: '#A86',
|
||||
CONNECTING_LINK_COLOR: '#AFA'
|
||||
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 135 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 160 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 152 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 135 KiB |
@@ -1,6 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Keybindings', () => {
|
||||
test('Should execute command', async ({ comfyPage }) => {
|
||||
@@ -32,7 +32,7 @@ test.describe('Keybindings', () => {
|
||||
})
|
||||
|
||||
await comfyPage.executeCommand('TestCommand')
|
||||
expect(await comfyPage.getToastErrorCount()).toBe(1)
|
||||
await expect(comfyPage.page.locator('.p-toast')).toBeVisible()
|
||||
})
|
||||
|
||||
test('Should handle async command errors', async ({ comfyPage }) => {
|
||||
@@ -45,6 +45,6 @@ test.describe('Keybindings', () => {
|
||||
})
|
||||
|
||||
await comfyPage.executeCommand('TestCommand')
|
||||
expect(await comfyPage.getToastErrorCount()).toBe(1)
|
||||
await expect(comfyPage.page.locator('.p-toast')).toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Copy Paste', () => {
|
||||
test('Can copy and paste node', async ({ comfyPage }) => {
|
||||
|
After Width: | Height: | Size: 104 KiB |
|
After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
@@ -1,7 +1,7 @@
|
||||
import { Locator, expect } from '@playwright/test'
|
||||
|
||||
import type { Keybinding } from '../../src/schemas/keyBindingSchema'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import type { Keybinding } from '../src/schemas/keyBindingSchema'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Load workflow warning', () => {
|
||||
test('Should display a warning when loading a workflow with missing nodes', async ({
|
||||
@@ -309,62 +309,3 @@ test.describe('Feedback dialog', () => {
|
||||
await expect(feedbackHeader).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Error dialog', () => {
|
||||
test('Should display an error dialog when graph configure fails', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const graph = window['graph']
|
||||
graph.configure = () => {
|
||||
throw new Error('Error on configure!')
|
||||
}
|
||||
})
|
||||
|
||||
await comfyPage.loadWorkflow('default')
|
||||
|
||||
const errorDialog = comfyPage.page.locator('.comfy-error-report')
|
||||
await expect(errorDialog).toBeVisible()
|
||||
})
|
||||
|
||||
test('Should display an error dialog when prompt execution fails', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.evaluate(async () => {
|
||||
const app = window['app']
|
||||
app.api.queuePrompt = () => {
|
||||
throw new Error('Error on queuePrompt!')
|
||||
}
|
||||
await app.queuePrompt(0)
|
||||
})
|
||||
const errorDialog = comfyPage.page.locator('.comfy-error-report')
|
||||
await expect(errorDialog).toBeVisible()
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Signin dialog', () => {
|
||||
test('Paste content to signin dialog should not paste node on canvas', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const nodeNum = (await comfyPage.getNodes()).length
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
await comfyPage.ctrlC()
|
||||
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.fill('test_password')
|
||||
await textBox.press('Control+a')
|
||||
await textBox.press('Control+c')
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].extensionManager.dialog.showSignInDialog()
|
||||
})
|
||||
|
||||
const input = comfyPage.page.locator('#comfy-org-sign-in-password')
|
||||
await input.waitFor({ state: 'visible' })
|
||||
await input.press('Control+v')
|
||||
await expect(input).toHaveValue('test_password')
|
||||
|
||||
expect(await comfyPage.getNodes()).toHaveLength(nodeNum)
|
||||
})
|
||||
})
|
||||
27
browser_tests/domWidget.spec.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('DOM Widget', () => {
|
||||
test('Collapsed multiline textarea is not visible', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('collapsed_multiline')
|
||||
|
||||
expect(comfyPage.page.locator('.comfy-multiline-input')).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('Multiline textarea correctly collapses', async ({ comfyPage }) => {
|
||||
const multilineTextAreas = comfyPage.page.locator('.comfy-multiline-input')
|
||||
const firstMultiline = multilineTextAreas.first()
|
||||
const lastMultiline = multilineTextAreas.last()
|
||||
|
||||
await expect(firstMultiline).toBeVisible()
|
||||
await expect(lastMultiline).toBeVisible()
|
||||
|
||||
const nodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
|
||||
for (const node of nodes) {
|
||||
await node.click('collapse')
|
||||
}
|
||||
await expect(firstMultiline).not.toBeVisible()
|
||||
await expect(lastMultiline).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { SettingParams } from '../../src/types/settingTypes'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { SettingParams } from '../src/types/settingTypes'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Topbar commands', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
@@ -214,10 +214,6 @@ export class ComfyPage {
|
||||
`Failed to setup workflows directory: ${await resp.text()}`
|
||||
)
|
||||
}
|
||||
|
||||
await this.page.evaluate(async () => {
|
||||
await window['app'].extensionManager.workflow.syncWorkflows()
|
||||
})
|
||||
}
|
||||
|
||||
async setupUser(username: string) {
|
||||
@@ -270,7 +266,6 @@ export class ComfyPage {
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
localStorage.setItem('Comfy.userId', id)
|
||||
localStorage.setItem('api-nodes-news-seen', 'true')
|
||||
}, this.id)
|
||||
}
|
||||
await this.goto()
|
||||
@@ -413,7 +408,7 @@ export class ComfyPage {
|
||||
}
|
||||
|
||||
async getVisibleToastCount() {
|
||||
return await this.page.locator('.p-toast-message:visible').count()
|
||||
return await this.page.locator('.p-toast:visible').count()
|
||||
}
|
||||
|
||||
async clickTextEncodeNode1() {
|
||||
@@ -464,129 +459,55 @@ export class ComfyPage {
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async dragAndDropExternalResource(
|
||||
options: {
|
||||
fileName?: string
|
||||
url?: string
|
||||
dropPosition?: Position
|
||||
} = {}
|
||||
) {
|
||||
const { dropPosition = { x: 100, y: 100 }, fileName, url } = options
|
||||
async dragAndDropFile(fileName: string) {
|
||||
const filePath = this.assetPath(fileName)
|
||||
|
||||
if (!fileName && !url)
|
||||
throw new Error('Must provide either fileName or url')
|
||||
// Read the file content
|
||||
const buffer = fs.readFileSync(filePath)
|
||||
|
||||
const evaluateParams: {
|
||||
dropPosition: Position
|
||||
fileName?: string
|
||||
fileType?: string
|
||||
buffer?: Uint8Array | number[]
|
||||
url?: string
|
||||
} = { dropPosition }
|
||||
|
||||
// Dropping a file from the filesystem
|
||||
if (fileName) {
|
||||
const filePath = this.assetPath(fileName)
|
||||
const buffer = fs.readFileSync(filePath)
|
||||
|
||||
const getFileType = (fileName: string) => {
|
||||
if (fileName.endsWith('.png')) return 'image/png'
|
||||
if (fileName.endsWith('.svg')) return 'image/svg+xml'
|
||||
if (fileName.endsWith('.webp')) return 'image/webp'
|
||||
if (fileName.endsWith('.webm')) return 'video/webm'
|
||||
if (fileName.endsWith('.json')) return 'application/json'
|
||||
if (fileName.endsWith('.glb')) return 'model/gltf-binary'
|
||||
return 'application/octet-stream'
|
||||
}
|
||||
|
||||
evaluateParams.fileName = fileName
|
||||
evaluateParams.fileType = getFileType(fileName)
|
||||
evaluateParams.buffer = [...new Uint8Array(buffer)]
|
||||
// Get file type
|
||||
const getFileType = (fileName: string) => {
|
||||
if (fileName.endsWith('.png')) return 'image/png'
|
||||
if (fileName.endsWith('.webp')) return 'image/webp'
|
||||
if (fileName.endsWith('.webm')) return 'video/webm'
|
||||
if (fileName.endsWith('.json')) return 'application/json'
|
||||
return 'application/octet-stream'
|
||||
}
|
||||
|
||||
// Dropping a URL (e.g., dropping image across browser tabs in Firefox)
|
||||
if (url) evaluateParams.url = url
|
||||
const fileType = getFileType(fileName)
|
||||
|
||||
// Execute the drag and drop in the browser
|
||||
await this.page.evaluate(async (params) => {
|
||||
const dataTransfer = new DataTransfer()
|
||||
|
||||
// Add file if provided
|
||||
if (params.buffer && params.fileName && params.fileType) {
|
||||
const file = new File(
|
||||
[new Uint8Array(params.buffer)],
|
||||
params.fileName,
|
||||
{
|
||||
type: params.fileType
|
||||
}
|
||||
)
|
||||
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)
|
||||
}
|
||||
|
||||
// Add URL data if provided
|
||||
if (params.url) {
|
||||
dataTransfer.setData('text/uri-list', params.url)
|
||||
dataTransfer.setData('text/x-moz-url', params.url)
|
||||
}
|
||||
const dropEvent = new DragEvent('drop', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer
|
||||
})
|
||||
|
||||
const targetElement = document.elementFromPoint(
|
||||
params.dropPosition.x,
|
||||
params.dropPosition.y
|
||||
)
|
||||
Object.defineProperty(dropEvent, 'preventDefault', {
|
||||
value: () => {},
|
||||
writable: false
|
||||
})
|
||||
|
||||
if (!targetElement) {
|
||||
console.error('No element found at drop position:', params.dropPosition)
|
||||
return { success: false, error: 'No element at position' }
|
||||
}
|
||||
Object.defineProperty(dropEvent, 'stopPropagation', {
|
||||
value: () => {},
|
||||
writable: false
|
||||
})
|
||||
|
||||
const eventOptions = {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
dataTransfer,
|
||||
clientX: params.dropPosition.x,
|
||||
clientY: params.dropPosition.y
|
||||
}
|
||||
|
||||
const dragOverEvent = new DragEvent('dragover', eventOptions)
|
||||
const dropEvent = new DragEvent('drop', eventOptions)
|
||||
|
||||
Object.defineProperty(dropEvent, 'preventDefault', {
|
||||
value: () => {},
|
||||
writable: false
|
||||
})
|
||||
|
||||
Object.defineProperty(dropEvent, 'stopPropagation', {
|
||||
value: () => {},
|
||||
writable: false
|
||||
})
|
||||
|
||||
targetElement.dispatchEvent(dragOverEvent)
|
||||
targetElement.dispatchEvent(dropEvent)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
targetInfo: {
|
||||
tagName: targetElement.tagName,
|
||||
id: targetElement.id,
|
||||
classList: Array.from(targetElement.classList)
|
||||
}
|
||||
}
|
||||
}, evaluateParams)
|
||||
document.dispatchEvent(dropEvent)
|
||||
},
|
||||
{ buffer: [...new Uint8Array(buffer)], fileName, fileType }
|
||||
)
|
||||
|
||||
await this.nextFrame()
|
||||
}
|
||||
|
||||
async dragAndDropFile(
|
||||
fileName: string,
|
||||
options: { dropPosition?: Position } = {}
|
||||
) {
|
||||
return this.dragAndDropExternalResource({ fileName, ...options })
|
||||
}
|
||||
|
||||
async dragAndDropURL(url: string, options: { dropPosition?: Position } = {}) {
|
||||
return this.dragAndDropExternalResource({ url, ...options })
|
||||
}
|
||||
|
||||
async dragNode2() {
|
||||
await this.dragAndDrop({ x: 622, y: 400 }, { x: 622, y: 300 })
|
||||
await this.nextFrame()
|
||||
@@ -632,20 +553,11 @@ export class ComfyPage {
|
||||
await this.dragAndDrop(this.clipTextEncodeNode1InputSlot, this.emptySpace)
|
||||
}
|
||||
|
||||
async connectEdge(
|
||||
options: {
|
||||
reverse?: boolean
|
||||
} = {}
|
||||
) {
|
||||
const { reverse = false } = options
|
||||
const start = reverse
|
||||
? this.clipTextEncodeNode1InputSlot
|
||||
: this.loadCheckpointNodeClipOutputSlot
|
||||
const end = reverse
|
||||
? this.loadCheckpointNodeClipOutputSlot
|
||||
: this.clipTextEncodeNode1InputSlot
|
||||
|
||||
await this.dragAndDrop(start, end)
|
||||
async connectEdge() {
|
||||
await this.dragAndDrop(
|
||||
this.loadCheckpointNodeClipOutputSlot,
|
||||
this.clipTextEncodeNode1InputSlot
|
||||
)
|
||||
}
|
||||
|
||||
async adjustWidgetValue() {
|
||||
@@ -925,16 +837,10 @@ export class ComfyPage {
|
||||
return window['app'].canvas.ds.convertOffsetToCanvas(pos)
|
||||
}, pos)
|
||||
}
|
||||
|
||||
/** Get number of DOM widgets on the canvas. */
|
||||
async getDOMWidgetCount() {
|
||||
return await this.page.locator('.dom-widget').count()
|
||||
}
|
||||
|
||||
async getNodeRefById(id: NodeId) {
|
||||
return new NodeReference(id, this)
|
||||
}
|
||||
async getNodes(): Promise<LGraphNode[]> {
|
||||
async getNodes() {
|
||||
return await this.page.evaluate(() => {
|
||||
return window['app'].graph.nodes
|
||||
})
|
||||
|
||||
@@ -81,7 +81,7 @@ export class NodeWidgetReference {
|
||||
if (!widget) throw new Error(`Widget ${index} not found.`)
|
||||
|
||||
const [x, y, w, h] = node.getBounding()
|
||||
return window['app'].canvasPosToClientPos([
|
||||
return window['app'].canvas.ds.convertOffsetToCanvas([
|
||||
x + w / 2,
|
||||
y + window['LiteGraph']['NODE_TITLE_HEIGHT'] + widget.last_y + 1
|
||||
])
|
||||
@@ -94,36 +94,6 @@ export class NodeWidgetReference {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The position of the widget's associated socket
|
||||
*/
|
||||
async getSocketPosition(): Promise<Position> {
|
||||
const pos: [number, number] = await this.node.comfyPage.page.evaluate(
|
||||
([id, index]) => {
|
||||
const node = window['app'].graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
const widget = node.widgets[index]
|
||||
if (!widget) throw new Error(`Widget ${index} not found.`)
|
||||
|
||||
const slot = node.inputs.find(
|
||||
(slot) => slot.widget?.name === widget.name
|
||||
)
|
||||
if (!slot) throw new Error(`Socket ${widget.name} not found.`)
|
||||
|
||||
const [x, y] = node.getBounding()
|
||||
return window['app'].canvasPosToClientPos([
|
||||
x + slot.pos[0],
|
||||
y + slot.pos[1] + window['LiteGraph']['NODE_TITLE_HEIGHT']
|
||||
])
|
||||
},
|
||||
[this.node.id, this.index] as const
|
||||
)
|
||||
return {
|
||||
x: pos[0],
|
||||
y: pos[1]
|
||||
}
|
||||
}
|
||||
|
||||
async click() {
|
||||
await this.node.comfyPage.canvas.click({
|
||||
position: await this.getPosition()
|
||||
@@ -145,20 +115,8 @@ export class NodeWidgetReference {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async getValue() {
|
||||
return await this.node.comfyPage.page.evaluate(
|
||||
([id, index]) => {
|
||||
const node = window['app'].graph.getNodeById(id)
|
||||
if (!node) throw new Error(`Node ${id} not found.`)
|
||||
const widget = node.widgets[index]
|
||||
if (!widget) throw new Error(`Widget ${index} not found.`)
|
||||
return widget.value
|
||||
},
|
||||
[this.node.id, this.index] as const
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class NodeReference {
|
||||
constructor(
|
||||
readonly id: NodeId,
|
||||
@@ -280,7 +238,7 @@ export class NodeReference {
|
||||
const targetWidget = await targetNode.getWidget(targetWidgetIndex)
|
||||
await this.comfyPage.dragAndDrop(
|
||||
await originSlot.getPosition(),
|
||||
await targetWidget.getSocketPosition()
|
||||
await targetWidget.getPosition()
|
||||
)
|
||||
return originSlot
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Graph Canvas Menu', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
|
After Width: | Height: | Size: 85 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 100 KiB |
@@ -1,7 +1,7 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { ComfyPage, comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
||||
import { ComfyPage, comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
import type { NodeReference } from './fixtures/utils/litegraphUtils'
|
||||
|
||||
test.describe('Group Node', () => {
|
||||
test.describe('Node library sidebar', () => {
|
||||
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB |
@@ -1,6 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from './fixtures/ComfyPage'
|
||||
|
||||
test.describe('Item Interaction', () => {
|
||||
test('Can select/delete all items', async ({ comfyPage }) => {
|
||||
@@ -91,20 +91,15 @@ test.describe('Node Interaction', () => {
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'no action')
|
||||
})
|
||||
|
||||
// Test both directions of edge connection.
|
||||
;[{ reverse: false }, { reverse: true }].forEach(({ reverse }) => {
|
||||
test(`Can disconnect/connect edge ${reverse ? 'reverse' : 'normal'}`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('disconnected-edge.png')
|
||||
await comfyPage.connectEdge({ reverse })
|
||||
// Move mouse to empty area to avoid slot highlight.
|
||||
await comfyPage.moveMouseToEmptyArea()
|
||||
// Litegraph renders edge with a slight offset.
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default.png', {
|
||||
maxDiffPixels: 50
|
||||
})
|
||||
test('Can disconnect/connect edge', async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('disconnected-edge.png')
|
||||
await comfyPage.connectEdge()
|
||||
// Move mouse to empty area to avoid slot highlight.
|
||||
await comfyPage.moveMouseToEmptyArea()
|
||||
// Litegraph renders edge with a slight offset.
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default.png', {
|
||||
maxDiffPixels: 50
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 97 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 105 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 109 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 73 KiB |
|
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
Before Width: | Height: | Size: 7.2 KiB After Width: | Height: | Size: 7.2 KiB |
|
After Width: | Height: | Size: 89 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 45 KiB After Width: | Height: | Size: 45 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 102 KiB |