mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 14:30:41 +00:00
Compare commits
67 Commits
v1.39.2
...
drjkl/plus
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a05a18e3c0 | ||
|
|
9faabd80e2 | ||
|
|
09d63c5ba3 | ||
|
|
b2c8ea3e50 | ||
|
|
f9af2cc4bd | ||
|
|
a3fba58c79 | ||
|
|
ae7728cd91 | ||
|
|
5847071bc1 | ||
|
|
cc10684c19 | ||
|
|
380db3ee10 | ||
|
|
cbdc7d030f | ||
|
|
21492ecca5 | ||
|
|
22755d2cb2 | ||
|
|
15f4f11ee2 | ||
|
|
bc19bb60fb | ||
|
|
139ee32d78 | ||
|
|
7928e8797d | ||
|
|
cd6cc7a51d | ||
|
|
aa979aa98c | ||
|
|
cc0307032a | ||
|
|
bc3b9585f9 | ||
|
|
1af9e00dd2 | ||
|
|
2f8bd7b04f | ||
|
|
cfdd002b7c | ||
|
|
7dd3098af5 | ||
|
|
2740c7cdd5 | ||
|
|
22daf48748 | ||
|
|
2ec954459c | ||
|
|
4e9f581625 | ||
|
|
d34d6a8a92 | ||
|
|
2a167a675d | ||
|
|
e1385b0d18 | ||
|
|
eaa3ff1579 | ||
|
|
4e20b7522b | ||
|
|
544ef5bb70 | ||
|
|
69129414d0 | ||
|
|
1bbbcfedf0 | ||
|
|
4f2872460c | ||
|
|
ea6928268f | ||
|
|
4a85bffb1f | ||
|
|
be7c34e28b | ||
|
|
a807986835 | ||
|
|
679288500a | ||
|
|
34d42311ea | ||
|
|
c4e5fc8dbf | ||
|
|
a64c561a5f | ||
|
|
59c58379fe | ||
|
|
6c14ae6f90 | ||
|
|
ee600a8951 | ||
|
|
e7d3bc7285 | ||
|
|
b4649bc96d | ||
|
|
d7ec24abc3 | ||
|
|
1ac214a1cd | ||
|
|
985d024a6e | ||
|
|
ee4a205d32 | ||
|
|
d784d4982b | ||
|
|
47113b117e | ||
|
|
13311a46ea | ||
|
|
067d80c4ed | ||
|
|
82bacb82a7 | ||
|
|
80ccc13659 | ||
|
|
dce6fd1040 | ||
|
|
e2625a4055 | ||
|
|
a4cf9a1ca8 | ||
|
|
d437b96238 | ||
|
|
f1cf8073d6 | ||
|
|
d7654baebf |
@@ -391,7 +391,7 @@ echo "Last stable release: $LAST_STABLE"
|
||||
|
||||
```bash
|
||||
# Trigger the workflow
|
||||
gh workflow run version-bump.yaml -f version_type=${VERSION_TYPE}
|
||||
gh workflow run release-version-bump.yaml -f version_type=${VERSION_TYPE}
|
||||
|
||||
# Workflow runs quickly - usually creates PR within 30 seconds
|
||||
echo "Workflow triggered. Waiting for PR creation..."
|
||||
@@ -443,28 +443,21 @@ echo "Workflow triggered. Waiting for PR creation..."
|
||||
gh pr view ${PR_NUMBER} --json labels | jq -r '.labels[].name' | grep -q "Release" || \
|
||||
echo "ERROR: Release label missing! Add it immediately!"
|
||||
```
|
||||
2. Check for update-locales commits:
|
||||
```bash
|
||||
# WARNING: update-locales may add [skip ci] which blocks release workflow!
|
||||
gh pr view ${PR_NUMBER} --json commits | grep -q "skip ci" && \
|
||||
echo "WARNING: [skip ci] detected - release workflow may not trigger!"
|
||||
```
|
||||
3. Verify version number in package.json
|
||||
4. Review all changed files
|
||||
5. Ensure no unintended changes included
|
||||
6. Wait for required PR checks:
|
||||
2. Verify version number in package.json
|
||||
3. Review all changed files
|
||||
4. Ensure no unintended changes included
|
||||
5. Wait for required PR checks:
|
||||
```bash
|
||||
gh pr checks ${PR_NUMBER} --watch
|
||||
```
|
||||
7. **FINAL CODE REVIEW**: Release label present and no [skip ci]?
|
||||
6. **FINAL CODE REVIEW**: Release label present and no [skip ci]?
|
||||
|
||||
### Step 12: Pre-Merge Validation
|
||||
|
||||
1. **Review Requirements**: Release PRs require approval
|
||||
2. Monitor CI checks - watch for update-locales
|
||||
3. **CRITICAL WARNING**: If update-locales adds [skip ci], the release workflow won't trigger!
|
||||
4. Check no new commits to main since PR creation
|
||||
5. **DEPLOYMENT READINESS**: Ready to merge?
|
||||
2. Monitor CI checks
|
||||
3. Check no new commits to main since PR creation
|
||||
4. **DEPLOYMENT READINESS**: Ready to merge?
|
||||
|
||||
### Step 13: Execute Release
|
||||
|
||||
|
||||
3
.coderabbit.yaml
Normal file
3
.coderabbit.yaml
Normal file
@@ -0,0 +1,3 @@
|
||||
issue_enrichment:
|
||||
auto_enrich:
|
||||
enabled: true
|
||||
5
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
5
.github/ISSUE_TEMPLATE/bug-report.yaml
vendored
@@ -10,10 +10,7 @@ body:
|
||||
options:
|
||||
- label: I am running the latest version of ComfyUI
|
||||
required: true
|
||||
- label: I have searched existing issues to make sure this isn't a duplicate
|
||||
required: true
|
||||
- label: I have tested with all custom nodes disabled ([see how](https://docs.comfy.org/troubleshooting/custom-node-issues#step-1%3A-test-with-all-custom-nodes-disabled))
|
||||
required: true
|
||||
- label: I have custom nodes enabled
|
||||
|
||||
- type: textarea
|
||||
id: description
|
||||
|
||||
7
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
7
.github/ISSUE_TEMPLATE/feature-request.yaml
vendored
@@ -4,13 +4,6 @@ labels: []
|
||||
type: Feature
|
||||
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Is there an existing issue for this?
|
||||
description: Please search to see if an issue already exists for the problem you're experiencing, and that it's not addressed 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: |
|
||||
|
||||
1
.github/workflows/ci-lint-format.yaml
vendored
1
.github/workflows/ci-lint-format.yaml
vendored
@@ -21,6 +21,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ !github.event.pull_request.head.repo.fork && github.head_ref || github.ref }}
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
- name: Setup frontend
|
||||
uses: ./.github/actions/setup-frontend
|
||||
|
||||
2
.github/workflows/i18n-update-core.yaml
vendored
2
.github/workflows/i18n-update-core.yaml
vendored
@@ -17,6 +17,8 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
# Setup playwright environment
|
||||
- name: Setup ComfyUI Frontend
|
||||
|
||||
@@ -173,6 +173,7 @@ jobs:
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.branch }}
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
|
||||
# Download all changed snapshot files from shards
|
||||
- name: Download snapshot artifacts
|
||||
|
||||
1
.github/workflows/release-draft-create.yaml
vendored
1
.github/workflows/release-draft-create.yaml
vendored
@@ -50,6 +50,7 @@ jobs:
|
||||
ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }}
|
||||
ENABLE_MINIFY: 'true'
|
||||
USE_PROD_CONFIG: 'true'
|
||||
IS_NIGHTLY: ${{ case(github.ref == 'refs/heads/main', 'true', 'false') }}
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
pnpm build
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -96,3 +96,5 @@ vitest.config.*.timestamp*
|
||||
|
||||
# Weekly docs check output
|
||||
/output.txt
|
||||
|
||||
# .vite-plus*.tmp.json
|
||||
@@ -1,14 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 80,
|
||||
"ignorePatterns": [
|
||||
"packages/registry-types/src/comfyRegistryTypes.ts",
|
||||
"public/materialdesignicons.min.css",
|
||||
"src/types/generatedManagerTypes.ts",
|
||||
"**/__fixtures__/**/*.json"
|
||||
]
|
||||
}
|
||||
115
.oxlintrc.json
115
.oxlintrc.json
@@ -1,115 +0,0 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"ignorePatterns": [
|
||||
".i18nrc.cjs",
|
||||
".nx/*",
|
||||
"**/vite.config.*.timestamp*",
|
||||
"**/vitest.config.*.timestamp*",
|
||||
"components.d.ts",
|
||||
"coverage/*",
|
||||
"dist/*",
|
||||
"packages/registry-types/src/comfyRegistryTypes.ts",
|
||||
"playwright-report/*",
|
||||
"src/extensions/core/*",
|
||||
"src/scripts/*",
|
||||
"src/types/generatedManagerTypes.ts",
|
||||
"src/types/vue-shim.d.ts",
|
||||
"test-results/*",
|
||||
"vitest.setup.ts"
|
||||
],
|
||||
"plugins": [
|
||||
"eslint",
|
||||
"import",
|
||||
"oxc",
|
||||
"typescript",
|
||||
"unicorn",
|
||||
"vitest",
|
||||
"vue"
|
||||
],
|
||||
"rules": {
|
||||
"no-async-promise-executor": "off",
|
||||
"no-console": [
|
||||
"error",
|
||||
{
|
||||
"allow": ["warn", "error"]
|
||||
}
|
||||
],
|
||||
"no-control-regex": "off",
|
||||
"no-eval": "off",
|
||||
"no-redeclare": "error",
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
{
|
||||
"name": "primevue/calendar",
|
||||
"message": "Calendar is deprecated in PrimeVue 4+. Use DatePicker instead: import DatePicker from 'primevue/datepicker'"
|
||||
},
|
||||
{
|
||||
"name": "primevue/dropdown",
|
||||
"message": "Dropdown is deprecated in PrimeVue 4+. Use Select instead: import Select from 'primevue/select'"
|
||||
},
|
||||
{
|
||||
"name": "primevue/inputswitch",
|
||||
"message": "InputSwitch is deprecated in PrimeVue 4+. Use ToggleSwitch instead: import ToggleSwitch from 'primevue/toggleswitch'"
|
||||
},
|
||||
{
|
||||
"name": "primevue/overlaypanel",
|
||||
"message": "OverlayPanel is deprecated in PrimeVue 4+. Use Popover instead: import Popover from 'primevue/popover'"
|
||||
},
|
||||
{
|
||||
"name": "primevue/sidebar",
|
||||
"message": "Sidebar is deprecated in PrimeVue 4+. Use Drawer instead: import Drawer from 'primevue/drawer'"
|
||||
},
|
||||
{
|
||||
"name": "@/i18n--to-enable",
|
||||
"importNames": ["st", "t", "te", "d"],
|
||||
"message": "Don't import `@/i18n` directly, prefer `useI18n()`"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"no-self-assign": "allow",
|
||||
"no-unused-expressions": "off",
|
||||
"no-unused-private-class-members": "off",
|
||||
"no-useless-rename": "off",
|
||||
"import/default": "error",
|
||||
"import/export": "error",
|
||||
"import/namespace": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"import/consistent-type-specifier-style": ["error", "prefer-top-level"],
|
||||
"jest/expect-expect": "off",
|
||||
"jest/no-conditional-expect": "off",
|
||||
"jest/no-disabled-tests": "off",
|
||||
"jest/no-standalone-expect": "off",
|
||||
"jest/valid-title": "off",
|
||||
"typescript/no-this-alias": "off",
|
||||
"typescript/no-unnecessary-parameter-property-assignment": "off",
|
||||
"typescript/no-unsafe-declaration-merging": "off",
|
||||
"typescript/no-unused-vars": "off",
|
||||
"unicorn/no-empty-file": "off",
|
||||
"unicorn/no-new-array": "off",
|
||||
"unicorn/no-single-promise-in-promise-methods": "off",
|
||||
"unicorn/no-useless-fallback-in-spread": "off",
|
||||
"unicorn/no-useless-spread": "off",
|
||||
"typescript/await-thenable": "off",
|
||||
"typescript/no-base-to-string": "off",
|
||||
"typescript/no-duplicate-type-constituents": "off",
|
||||
"typescript/no-for-in-array": "off",
|
||||
"typescript/no-meaningless-void-operator": "off",
|
||||
"typescript/no-redundant-type-constituents": "off",
|
||||
"typescript/restrict-template-expressions": "off",
|
||||
"typescript/unbound-method": "off",
|
||||
"typescript/no-floating-promises": "error",
|
||||
"vue/no-import-compiler-macros": "error",
|
||||
"vue/no-dupe-keys": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.{stories,test,spec}.ts", "**/*.stories.vue"],
|
||||
"rules": {
|
||||
"no-console": "allow"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -98,12 +98,10 @@ const config: StorybookConfig = {
|
||||
},
|
||||
build: {
|
||||
rolldownOptions: {
|
||||
experimental: {
|
||||
strictExecutionOrder: true
|
||||
},
|
||||
treeshake: false,
|
||||
output: {
|
||||
keepNames: true
|
||||
keepNames: true,
|
||||
strictExecutionOrder: true
|
||||
},
|
||||
onwarn: (warning, warn) => {
|
||||
// Suppress specific warnings
|
||||
|
||||
59
AGENTS.md
59
AGENTS.md
@@ -21,7 +21,7 @@ See @docs/guidance/\*.md for file-type-specific conventions (auto-loaded by glob
|
||||
- i18n: `src/i18n.ts`,
|
||||
- Entry Point: `src/main.ts`.
|
||||
- Tests:
|
||||
- unit/component in `tests-ui/` and `src/**/*.test.ts`
|
||||
- unit/component in `src/**/*.test.ts`
|
||||
- E2E (Playwright) in `browser_tests/**/*.spec.ts`
|
||||
- Public assets: `public/`
|
||||
- Build output: `dist/`
|
||||
@@ -264,7 +264,7 @@ A particular type of complexity is over-engineering, where developers have made
|
||||
|
||||
## Repository Navigation
|
||||
|
||||
- Check README files in key folders (tests-ui, browser_tests, composables, etc.)
|
||||
- Check README files in key folders (browser_tests, composables, etc.)
|
||||
- Prefer running single tests for performance
|
||||
- Use --help for unfamiliar CLI tools
|
||||
|
||||
@@ -312,3 +312,58 @@ When using `take_snapshot` to inspect dropdowns, listboxes, or other components
|
||||
- Put scripts used under `/temp/scripts/`
|
||||
- Put summaries of work performed under `/temp/summaries/`
|
||||
- Put TODOs and status updates under `/temp/in_progress/`
|
||||
|
||||
<!--VITE PLUS START-->
|
||||
|
||||
# Using Vite+, the Unified Toolchain for the Web
|
||||
|
||||
This project is using Vite+, a modern toolchain built on top of Vite, Rolldown, Vitest, tsdown, Oxlint, and Oxfmt. Vite+ wraps these tools and package manager commands in a single, global CLI called `vite`. Vite+ is distinct from Vite, but it invokes Vite through `vite dev` and `vite build`.
|
||||
|
||||
## Vite+ Workflow
|
||||
|
||||
`vite` is a global binary that handles the full development lifecycle. Run `vite help` to print a list of commands and `vite <command> --help` for information about a specific command.
|
||||
|
||||
### Vite+ Commands
|
||||
|
||||
- dev - Run the development server
|
||||
- build - Build for production
|
||||
- lint - Lint code
|
||||
- test - Run tests
|
||||
- fmt - Format code
|
||||
- lib - Build library
|
||||
- migrate - Migrate an existing project to Vite+
|
||||
- new - Create a new monorepo package (in-project) or a new project (global)
|
||||
- run - Run tasks from `package.json` scripts
|
||||
|
||||
These commands map to their corresponding tools. For example, `vite dev --port 3000` runs Vite's dev server and works the same as Vite. `vite test` runs JavaScript tests through the bundled Vitest. The version of all tools can be checked using `vite --version`. This is useful when researching documentation, features, and bugs.
|
||||
|
||||
### Package Manager Commands
|
||||
|
||||
Vite+ automatically detects and wraps the underlying package manager such as pnpm, npm, or Yarn through the `packageManager` field in `package.json` or package manager-specific lockfiles.
|
||||
|
||||
- install - Install all dependencies, or add packages if package names are provided
|
||||
- add - Add packages to dependencies
|
||||
- remove - Remove packages from dependencies
|
||||
- dlx - Execute a package binary without installing it as a dependency
|
||||
- info - View package information from the registry, including latest versions
|
||||
- link - Link packages for local development
|
||||
- outdated - Check for outdated packages
|
||||
- pm - Forward a command to the package manager
|
||||
- unlink - Unlink packages
|
||||
- update - Update packages to their latest versions
|
||||
- why - Show why a package is installed
|
||||
|
||||
## Common Pitfalls
|
||||
|
||||
- **Using the package manager directly:** Do not use pnpm, npm, or Yarn directly. Vite+ can handle all package manager operations.
|
||||
- **Always use Vite commands to run tools:** Don't attempt to run `vite vitest` or `vite oxlint`. They do not exist. Use `vite test` and `vite lint` instead.
|
||||
- **Running scripts:** Vite+ commands take precedence over `package.json` scripts. If there is a `test` script defined in `scripts` that conflicts with the built-in `vite test` command, run it using `vite run test`.
|
||||
- **Do not install Vitest, Oxlint, Oxfmt, or tsdown directly:** Vite+ wraps these tools. They must not be installed directly. You cannot upgrade these tools by installing their latest versions. Always use Vite+ commands.
|
||||
- **Import JavaScript modules from `vite-plus`:** Instead of importing from `vite` or `vitest`, all modules should be imported from the project's `vite-plus` dependency. For example, `import { defineConfig } from 'vite-plus';` or `import { expect, test, vi } from 'vite-plus/test';`. You must not install `vitest` to import test utilities.
|
||||
- **Type-Aware Linting:** There is no need to install `oxlint-tsgolint`, `vite lint --type-aware` works out of the box.
|
||||
|
||||
## Review Checklist for Agents
|
||||
|
||||
- [ ] Run `vite install` after pulling remote changes and before getting started.
|
||||
- [ ] Run `vite lint`, `vite fmt`, and `vite test` to validate changes.
|
||||
<!--VITE PLUS END-->
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"vite": "catalog:",
|
||||
"vite-plugin-html": "catalog:",
|
||||
"vite-plugin-vue-devtools": "catalog:",
|
||||
"vite-plus": "catalog:",
|
||||
"vue-tsc": "catalog:"
|
||||
},
|
||||
"nx": {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { FileSystemIconLoader } from 'unplugin-icons/loaders'
|
||||
import IconsResolver from 'unplugin-icons/resolver'
|
||||
import Icons from 'unplugin-icons/vite'
|
||||
import Components from 'unplugin-vue-components/vite'
|
||||
import { defineConfig } from 'vite'
|
||||
import { defineConfig } from 'vite-plus'
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
import vueDevTools from 'vite-plugin-vue-devtools'
|
||||
|
||||
|
||||
BIN
browser_tests/assets/workflowInMedia/workflow_itxt.png
Normal file
BIN
browser_tests/assets/workflowInMedia/workflow_itxt.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 517 B |
@@ -5,7 +5,7 @@ import * as fs from 'fs'
|
||||
|
||||
import type { LGraphNode, LGraph } from '../../src/lib/litegraph/src/litegraph'
|
||||
import type { NodeId } from '../../src/platform/workflow/validation/schemas/workflowSchema'
|
||||
import type { KeyCombo } from '../../src/schemas/keyBindingSchema'
|
||||
import type { KeyCombo } from '../../src/platform/keybindings'
|
||||
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
|
||||
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
||||
import { ComfyActionbar } from '../helpers/actionbar'
|
||||
|
||||
@@ -7,7 +7,7 @@ import { webSocketFixture } from '../fixtures/ws.ts'
|
||||
|
||||
const test = mergeTests(comfyPageFixture, webSocketFixture)
|
||||
|
||||
test.describe('Actionbar', () => {
|
||||
test.describe('Actionbar', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Bottom Panel Shortcuts', () => {
|
||||
test.describe('Bottom Panel Shortcuts', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Browser tab title', () => {
|
||||
test.describe('Browser tab title', { tag: '@smoke' }, () => {
|
||||
test.describe('Beta Menu', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
@@ -19,7 +19,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Change Tracker', () => {
|
||||
test.describe('Change Tracker', { tag: '@workflow' }, () => {
|
||||
test.describe('Undo/Redo', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
@@ -151,7 +151,7 @@ const customColorPalettes: Record<string, Palette> = {
|
||||
}
|
||||
}
|
||||
|
||||
test.describe('Color Palette', () => {
|
||||
test.describe('Color Palette', { tag: ['@screenshot', '@settings'] }, () => {
|
||||
test('Can show custom color palette', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.CustomColorPalettes', customColorPalettes)
|
||||
// Reload to apply the new setting. Setting Comfy.CustomColorPalettes directly
|
||||
@@ -194,104 +194,110 @@ test.describe('Color Palette', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Node Color Adjustments', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/every_node_color')
|
||||
})
|
||||
|
||||
test('should adjust opacity via node opacity setting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
|
||||
// Drag mouse to force canvas to redraw
|
||||
await comfyPage.page.mouse.move(0, 0)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-opacity-0.5.png')
|
||||
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 1.0)
|
||||
|
||||
await comfyPage.page.mouse.move(8, 8)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-opacity-1.png')
|
||||
})
|
||||
|
||||
test('should persist color adjustments when changing themes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.2)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'arc')
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.page.mouse.move(0, 0)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-opacity-0.2-arc-theme.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should not serialize color adjustments in workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.nextFrame()
|
||||
const parsed = await (
|
||||
await comfyPage.page.waitForFunction(
|
||||
() => {
|
||||
const workflow = localStorage.getItem('workflow')
|
||||
if (!workflow) return null
|
||||
try {
|
||||
const data = JSON.parse(workflow)
|
||||
return Array.isArray(data?.nodes) ? data : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
{ timeout: 3000 }
|
||||
)
|
||||
).jsonValue()
|
||||
expect(parsed.nodes).toBeDefined()
|
||||
expect(Array.isArray(parsed.nodes)).toBe(true)
|
||||
for (const node of parsed.nodes) {
|
||||
if (node.bgcolor) expect(node.bgcolor).not.toMatch(/hsla/)
|
||||
if (node.color) expect(node.color).not.toMatch(/hsla/)
|
||||
}
|
||||
})
|
||||
|
||||
test('should lighten node colors when switching to light theme', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-lightened-colors.png')
|
||||
})
|
||||
|
||||
test.describe('Context menu color adjustments', () => {
|
||||
test.describe(
|
||||
'Node Color Adjustments',
|
||||
{ tag: ['@screenshot', '@settings'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/every_node_color')
|
||||
})
|
||||
|
||||
test('should adjust opacity via node opacity setting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
|
||||
// Drag mouse to force canvas to redraw
|
||||
await comfyPage.page.mouse.move(0, 0)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-opacity-0.5.png')
|
||||
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 1.0)
|
||||
|
||||
await comfyPage.page.mouse.move(8, 8)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-opacity-1.png')
|
||||
})
|
||||
|
||||
test('should persist color adjustments when changing themes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.2)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'arc')
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.page.mouse.move(0, 0)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-opacity-0.2-arc-theme.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should not serialize color adjustments in workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.3)
|
||||
const node = await comfyPage.getFirstNodeRef()
|
||||
await node?.clickContextMenuOption('Colors')
|
||||
await comfyPage.nextFrame()
|
||||
const parsed = await (
|
||||
await comfyPage.page.waitForFunction(
|
||||
() => {
|
||||
const workflow = localStorage.getItem('workflow')
|
||||
if (!workflow) return null
|
||||
try {
|
||||
const data = JSON.parse(workflow)
|
||||
return Array.isArray(data?.nodes) ? data : null
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
},
|
||||
{ timeout: 3000 }
|
||||
)
|
||||
).jsonValue()
|
||||
expect(parsed.nodes).toBeDefined()
|
||||
expect(Array.isArray(parsed.nodes)).toBe(true)
|
||||
for (const node of parsed.nodes) {
|
||||
if (node.bgcolor) expect(node.bgcolor).not.toMatch(/hsla/)
|
||||
if (node.color) expect(node.color).not.toMatch(/hsla/)
|
||||
}
|
||||
})
|
||||
|
||||
test('should persist color adjustments when changing custom node colors', async ({
|
||||
test('should lighten node colors when switching to light theme', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page
|
||||
.locator('.litemenu-entry.submenu span:has-text("red")')
|
||||
.click()
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-opacity-0.3-color-changed.png'
|
||||
'node-lightened-colors.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should persist color adjustments when removing custom node color', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page
|
||||
.locator('.litemenu-entry.submenu span:has-text("No color")')
|
||||
.click()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-opacity-0.3-color-removed.png'
|
||||
)
|
||||
test.describe('Context menu color adjustments', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.3)
|
||||
const node = await comfyPage.getFirstNodeRef()
|
||||
await node?.clickContextMenuOption('Colors')
|
||||
})
|
||||
|
||||
test('should persist color adjustments when changing custom node colors', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page
|
||||
.locator('.litemenu-entry.submenu span:has-text("red")')
|
||||
.click()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-opacity-0.3-color-changed.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('should persist color adjustments when removing custom node color', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page
|
||||
.locator('.litemenu-entry.submenu span:has-text("No color")')
|
||||
.click()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-opacity-0.3-color-removed.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Keybindings', () => {
|
||||
test.describe('Keybindings', { tag: '@keyboard' }, () => {
|
||||
test('Should execute command', async ({ comfyPage }) => {
|
||||
await comfyPage.registerCommand('TestCommand', () => {
|
||||
window['foo'] = true
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Copy Paste', () => {
|
||||
test.describe('Copy Paste', { tag: ['@screenshot', '@workflow'] }, () => {
|
||||
test('Can copy and paste node', async ({ comfyPage }) => {
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
|
||||
@@ -22,7 +22,7 @@ async function verifyCustomIconSvg(iconElement: Locator) {
|
||||
expect(decodedSvg).toContain("<svg xmlns='http://www.w3.org/2000/svg'")
|
||||
}
|
||||
|
||||
test.describe('Custom Icons', () => {
|
||||
test.describe('Custom Icons', { tag: '@settings' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import type { Locator } from '@playwright/test'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import type { Keybinding } from '../../src/schemas/keyBindingSchema'
|
||||
import type { Keybinding } from '../../src/platform/keybindings'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Load workflow warning', () => {
|
||||
test.describe('Load workflow warning', { tag: '@ui' }, () => {
|
||||
test('Should display a warning when loading a workflow with missing nodes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('DOM Widget', () => {
|
||||
test.describe('DOM Widget', { tag: '@widget' }, () => {
|
||||
test('Collapsed multiline textarea is not visible', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/collapsed_multiline')
|
||||
const textareaWidget = comfyPage.page.locator('.comfy-multiline-input')
|
||||
@@ -29,12 +29,16 @@ test.describe('DOM Widget', () => {
|
||||
await expect(lastMultiline).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('Position update when entering focus mode', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.executeCommand('Workspace.ToggleFocusMode')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('focus-mode-on.png')
|
||||
})
|
||||
test(
|
||||
'Position update when entering focus mode',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.executeCommand('Workspace.ToggleFocusMode')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('focus-mode-on.png')
|
||||
}
|
||||
)
|
||||
|
||||
// No DOM widget should be created by creation of interim LGraphNode objects.
|
||||
test('Copy node with DOM widget by dragging + alt', async ({ comfyPage }) => {
|
||||
|
||||
@@ -6,40 +6,48 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Execution', () => {
|
||||
test('Report error on unconnected slot', async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await comfyPage.clickEmptySpace()
|
||||
test.describe('Execution', { tag: ['@smoke', '@workflow'] }, () => {
|
||||
test(
|
||||
'Report error on unconnected slot',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await comfyPage.clickEmptySpace()
|
||||
|
||||
await comfyPage.executeCommand('Comfy.QueuePrompt')
|
||||
await expect(comfyPage.page.locator('.comfy-error-report')).toBeVisible()
|
||||
await comfyPage.page.locator('.p-dialog-close-button').click()
|
||||
await comfyPage.page.locator('.comfy-error-report').waitFor({
|
||||
state: 'hidden'
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'execution-error-unconnected-slot.png'
|
||||
)
|
||||
})
|
||||
await comfyPage.executeCommand('Comfy.QueuePrompt')
|
||||
await expect(comfyPage.page.locator('.comfy-error-report')).toBeVisible()
|
||||
await comfyPage.page.locator('.p-dialog-close-button').click()
|
||||
await comfyPage.page.locator('.comfy-error-report').waitFor({
|
||||
state: 'hidden'
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'execution-error-unconnected-slot.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Execute to selected output nodes', () => {
|
||||
test('Execute to selected output nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('execution/partial_execution')
|
||||
const input = await comfyPage.getNodeRefById(3)
|
||||
const output1 = await comfyPage.getNodeRefById(1)
|
||||
const output2 = await comfyPage.getNodeRefById(4)
|
||||
expect(await (await input.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('')
|
||||
expect(await (await output2.getWidget(0)).getValue()).toBe('')
|
||||
|
||||
await output1.click('title')
|
||||
|
||||
await comfyPage.executeCommand('Comfy.QueueSelectedOutputNodes')
|
||||
await expect(async () => {
|
||||
test.describe(
|
||||
'Execute to selected output nodes',
|
||||
{ tag: ['@smoke', '@workflow'] },
|
||||
() => {
|
||||
test('Execute to selected output nodes', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('execution/partial_execution')
|
||||
const input = await comfyPage.getNodeRefById(3)
|
||||
const output1 = await comfyPage.getNodeRefById(1)
|
||||
const output2 = await comfyPage.getNodeRefById(4)
|
||||
expect(await (await input.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('')
|
||||
expect(await (await output2.getWidget(0)).getValue()).toBe('')
|
||||
}).toPass({ timeout: 2_000 })
|
||||
})
|
||||
})
|
||||
|
||||
await output1.click('title')
|
||||
|
||||
await comfyPage.executeCommand('Comfy.QueueSelectedOutputNodes')
|
||||
await expect(async () => {
|
||||
expect(await (await input.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output1.getWidget(0)).getValue()).toBe('foo')
|
||||
expect(await (await output2.getWidget(0)).getValue()).toBe('')
|
||||
}).toPass({ timeout: 2_000 })
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Feature Flags', () => {
|
||||
test.describe('Feature Flags', { tag: ['@slow', '@settings'] }, () => {
|
||||
test('Client and server exchange feature flags on connection', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Graph', () => {
|
||||
test.describe('Graph', { tag: ['@smoke', '@canvas'] }, () => {
|
||||
// Should be able to fix link input slot index after swap the input order
|
||||
// Ref: https://github.com/Comfy-Org/ComfyUI_frontend/issues/3348
|
||||
test('Fix link input slots', async ({ comfyPage }) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Graph Canvas Menu', () => {
|
||||
test.describe('Graph Canvas Menu', { tag: ['@screenshot', '@canvas'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// Set link render mode to spline to make sure it's not affected by other tests'
|
||||
// side effects.
|
||||
@@ -15,29 +15,33 @@ test.describe('Graph Canvas Menu', () => {
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
})
|
||||
|
||||
test('Can toggle link visibility', async ({ comfyPage }) => {
|
||||
const button = comfyPage.page.getByTestId('toggle-link-visibility-button')
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'canvas-with-hidden-links.png'
|
||||
)
|
||||
const hiddenLinkRenderMode = await comfyPage.page.evaluate(() => {
|
||||
return window['LiteGraph'].HIDDEN_LINK
|
||||
})
|
||||
expect(await comfyPage.getSetting('Comfy.LinkRenderMode')).toBe(
|
||||
hiddenLinkRenderMode
|
||||
)
|
||||
test(
|
||||
'Can toggle link visibility',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
const button = comfyPage.page.getByTestId('toggle-link-visibility-button')
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'canvas-with-hidden-links.png'
|
||||
)
|
||||
const hiddenLinkRenderMode = await comfyPage.page.evaluate(() => {
|
||||
return window['LiteGraph'].HIDDEN_LINK
|
||||
})
|
||||
expect(await comfyPage.getSetting('Comfy.LinkRenderMode')).toBe(
|
||||
hiddenLinkRenderMode
|
||||
)
|
||||
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'canvas-with-visible-links.png'
|
||||
)
|
||||
expect(await comfyPage.getSetting('Comfy.LinkRenderMode')).not.toBe(
|
||||
hiddenLinkRenderMode
|
||||
)
|
||||
})
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'canvas-with-visible-links.png'
|
||||
)
|
||||
expect(await comfyPage.getSetting('Comfy.LinkRenderMode')).not.toBe(
|
||||
hiddenLinkRenderMode
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test('Toggle minimap button is clickable and has correct test id', async ({
|
||||
comfyPage
|
||||
|
||||
@@ -8,7 +8,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Group Node', () => {
|
||||
test.describe('Group Node', { tag: '@node' }, () => {
|
||||
test.describe('Node library sidebar', () => {
|
||||
const groupNodeName = 'DefautWorkflowGroupNode'
|
||||
const groupNodeCategory = 'group nodes>workflow'
|
||||
@@ -89,16 +89,20 @@ test.describe('Group Node', () => {
|
||||
// does not have a v-model on the query, so we cannot observe the raw
|
||||
// query update, and thus cannot set the spinning state between the raw query
|
||||
// update and the debounced search update.
|
||||
test.skip('Can be added to canvas using search', async ({ comfyPage }) => {
|
||||
const groupNodeName = 'DefautWorkflowGroupNode'
|
||||
await comfyPage.convertAllNodesToGroupNode(groupNodeName)
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode(groupNodeName)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'group-node-copy-added-from-search.png'
|
||||
)
|
||||
})
|
||||
test.skip(
|
||||
'Can be added to canvas using search',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
const groupNodeName = 'DefautWorkflowGroupNode'
|
||||
await comfyPage.convertAllNodesToGroupNode(groupNodeName)
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode(groupNodeName)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'group-node-copy-added-from-search.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test('Displays tooltip on title hover', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.EnableTooltips', true)
|
||||
|
||||
@@ -13,7 +13,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Item Interaction', () => {
|
||||
test.describe('Item Interaction', { tag: ['@screenshot', '@node'] }, () => {
|
||||
test('Can select/delete all items', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/mixed_graph_items')
|
||||
await comfyPage.canvas.press('Control+a')
|
||||
@@ -60,13 +60,17 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('@2x Can highlight selected', async ({ comfyPage }) => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default.png')
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-node1.png')
|
||||
await comfyPage.clickTextEncodeNode2()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-node2.png')
|
||||
})
|
||||
test(
|
||||
'@2x Can highlight selected',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default.png')
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-node1.png')
|
||||
await comfyPage.clickTextEncodeNode2()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-node2.png')
|
||||
}
|
||||
)
|
||||
|
||||
const dragSelectNodes = async (
|
||||
comfyPage: ComfyPage,
|
||||
@@ -150,12 +154,12 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Can drag node', async ({ comfyPage }) => {
|
||||
test('Can drag node', { tag: '@screenshot' }, async ({ comfyPage }) => {
|
||||
await comfyPage.dragNode2()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('dragged-node1.png')
|
||||
})
|
||||
|
||||
test.describe('Edge Interaction', () => {
|
||||
test.describe('Edge Interaction', { tag: '@screenshot' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'no action')
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'no action')
|
||||
@@ -222,12 +226,18 @@ test.describe('Node Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test('Can adjust widget value', async ({ comfyPage }) => {
|
||||
await comfyPage.adjustWidgetValue()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('adjusted-widget-value.png')
|
||||
})
|
||||
test(
|
||||
'Can adjust widget value',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.adjustWidgetValue()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'adjusted-widget-value.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test('Link snap to slot', async ({ comfyPage }) => {
|
||||
test('Link snap to slot', { tag: '@screenshot' }, async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('links/snap_to_slot')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('snap_to_slot.png')
|
||||
|
||||
@@ -244,57 +254,67 @@ 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('links/batch_move_links')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('batch_move_links.png')
|
||||
test(
|
||||
'Can batch move links by drag with shift',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('links/batch_move_links')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('batch_move_links.png')
|
||||
|
||||
const outputSlot1Pos = {
|
||||
x: 304,
|
||||
y: 127
|
||||
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'
|
||||
)
|
||||
}
|
||||
const outputSlot2Pos = {
|
||||
x: 307,
|
||||
y: 310
|
||||
)
|
||||
|
||||
test(
|
||||
'Can batch disconnect links with ctrl+alt+click',
|
||||
{ tag: '@screenshot' },
|
||||
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'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
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
|
||||
test(
|
||||
'Can toggle dom widget node open/closed',
|
||||
{ tag: '@screenshot' },
|
||||
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'
|
||||
)
|
||||
}
|
||||
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
|
||||
@@ -341,19 +361,23 @@ test.describe('Node Interaction', () => {
|
||||
await expect(legacyPrompt).toBeHidden()
|
||||
})
|
||||
|
||||
test('Can double click node title to edit', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
await comfyPage.canvas.dblclick({
|
||||
position: {
|
||||
x: 50,
|
||||
y: 10
|
||||
},
|
||||
delay: 5
|
||||
})
|
||||
await comfyPage.page.keyboard.type('Hello World')
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-title-edited.png')
|
||||
})
|
||||
test(
|
||||
'Can double click node title to edit',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
await comfyPage.canvas.dblclick({
|
||||
position: {
|
||||
x: 50,
|
||||
y: 10
|
||||
},
|
||||
delay: 5
|
||||
})
|
||||
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
|
||||
@@ -369,29 +393,41 @@ test.describe('Node Interaction', () => {
|
||||
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')
|
||||
})
|
||||
test(
|
||||
'Can group selected nodes',
|
||||
{ tag: '@screenshot' },
|
||||
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'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test('Can fit group to contents', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/oversized_group')
|
||||
await comfyPage.ctrlA()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.executeCommand('Comfy.Graph.FitGroupToContents')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('group-fit-to-contents.png')
|
||||
})
|
||||
test(
|
||||
'Can fit group to contents',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/oversized_group')
|
||||
await comfyPage.ctrlA()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.executeCommand('Comfy.Graph.FitGroupToContents')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'group-fit-to-contents.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test('Can pin/unpin nodes', async ({ comfyPage }) => {
|
||||
test('Can pin/unpin nodes', { tag: '@screenshot' }, async ({ comfyPage }) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.executeCommand('Comfy.Canvas.ToggleSelectedNodes.Pin')
|
||||
await comfyPage.nextFrame()
|
||||
@@ -401,20 +437,22 @@ test.describe('Node Interaction', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-unpinned.png')
|
||||
})
|
||||
|
||||
test('Can bypass/unbypass nodes with keyboard shortcut', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.canvas.press('Control+b')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-bypassed.png')
|
||||
await comfyPage.canvas.press('Control+b')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-unbypassed.png')
|
||||
})
|
||||
test(
|
||||
'Can bypass/unbypass nodes with keyboard shortcut',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await comfyPage.canvas.press('Control+b')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-bypassed.png')
|
||||
await comfyPage.canvas.press('Control+b')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('nodes-unbypassed.png')
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test.describe('Group Interaction', () => {
|
||||
test.describe('Group Interaction', { tag: '@screenshot' }, () => {
|
||||
test('Can double click group title to edit', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('groups/single_group')
|
||||
await comfyPage.canvas.dblclick({
|
||||
@@ -430,7 +468,7 @@ test.describe('Group Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Canvas Interaction', () => {
|
||||
test.describe('Canvas Interaction', { tag: '@screenshot' }, () => {
|
||||
test('Can zoom in/out', async ({ comfyPage }) => {
|
||||
await comfyPage.zoom(-100)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-in.png')
|
||||
@@ -632,7 +670,7 @@ test.describe('Widget Interaction', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Load workflow', () => {
|
||||
test.describe('Load workflow', { tag: '@screenshot' }, () => {
|
||||
test('Can load workflow with string node id', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/string_node_id')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('string_node_id.png')
|
||||
@@ -824,7 +862,7 @@ test.describe('Viewport settings', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Canvas Navigation', () => {
|
||||
test.describe('Canvas Navigation', { tag: '@screenshot' }, () => {
|
||||
test.describe('Legacy Mode', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.NavigationMode', 'legacy')
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Keybindings', () => {
|
||||
test.describe('Keybindings', { tag: '@keyboard' }, () => {
|
||||
test('Should not trigger non-modifier keybinding when typing in input fields', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ function listenForEvent(): Promise<Event> {
|
||||
})
|
||||
}
|
||||
|
||||
test.describe('Canvas Event', () => {
|
||||
test.describe('Canvas Event', { tag: '@canvas' }, () => {
|
||||
test('Emit litegraph:canvas empty-release', async ({ comfyPage }) => {
|
||||
const eventPromise = comfyPage.page.evaluate(listenForEvent)
|
||||
const disconnectPromise = comfyPage.disconnectEdge()
|
||||
|
||||
@@ -6,46 +6,51 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Load Workflow in Media', () => {
|
||||
const fileNames = [
|
||||
'workflow.webp',
|
||||
'edited_workflow.webp',
|
||||
'no_workflow.webp',
|
||||
'large_workflow.webp',
|
||||
'workflow_prompt_parameters.png',
|
||||
'workflow.webm',
|
||||
// Skipped due to 3d widget unstable visual result.
|
||||
// 3d widget shows grid after fully loaded.
|
||||
// 'workflow.glb',
|
||||
'workflow.mp4',
|
||||
'workflow.mov',
|
||||
'workflow.m4v',
|
||||
'workflow.svg'
|
||||
// TODO: Re-enable after fixing test asset to use core nodes only
|
||||
// Currently opens missing nodes dialog which is outside scope of AVIF loading functionality
|
||||
// 'workflow.avif'
|
||||
]
|
||||
fileNames.forEach(async (fileName) => {
|
||||
test(`Load workflow in ${fileName} (drop from filesystem)`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.dragAndDropFile(`workflowInMedia/${fileName}`)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(`${fileName}.png`)
|
||||
test.describe(
|
||||
'Load Workflow in Media',
|
||||
{ tag: ['@screenshot', '@workflow'] },
|
||||
() => {
|
||||
const fileNames = [
|
||||
'workflow.webp',
|
||||
'edited_workflow.webp',
|
||||
'no_workflow.webp',
|
||||
'large_workflow.webp',
|
||||
'workflow_prompt_parameters.png',
|
||||
'workflow_itxt.png',
|
||||
'workflow.webm',
|
||||
// Skipped due to 3d widget unstable visual result.
|
||||
// 3d widget shows grid after fully loaded.
|
||||
// 'workflow.glb',
|
||||
'workflow.mp4',
|
||||
'workflow.mov',
|
||||
'workflow.m4v',
|
||||
'workflow.svg'
|
||||
// TODO: Re-enable after fixing test asset to use core nodes only
|
||||
// Currently opens missing nodes dialog which is outside scope of AVIF loading functionality
|
||||
// 'workflow.avif'
|
||||
]
|
||||
fileNames.forEach(async (fileName) => {
|
||||
test(`Load workflow in ${fileName} (drop from filesystem)`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.dragAndDropFile(`workflowInMedia/${fileName}`)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(`${fileName}.png`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const urls = [
|
||||
'https://comfyanonymous.github.io/ComfyUI_examples/hidream/hidream_dev_example.png'
|
||||
]
|
||||
urls.forEach(async (url) => {
|
||||
test(`Load workflow from URL ${url} (drop from different browser tabs)`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.dragAndDropURL(url)
|
||||
const readableName = url.split('/').pop()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
`dropped_workflow_url_${readableName}.png`
|
||||
)
|
||||
const urls = [
|
||||
'https://comfyanonymous.github.io/ComfyUI_examples/hidream/hidream_dev_example.png'
|
||||
]
|
||||
urls.forEach(async (url) => {
|
||||
test(`Load workflow from URL ${url} (drop from different browser tabs)`, async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.dragAndDropURL(url)
|
||||
const readableName = url.split('/').pop()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
`dropped_workflow_url_${readableName}.png`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('LOD Threshold', () => {
|
||||
test.describe('LOD Threshold', { tag: ['@screenshot', '@canvas'] }, () => {
|
||||
test('Should switch to low quality mode at correct zoom threshold', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
@@ -149,53 +149,55 @@ test.describe('LOD Threshold', () => {
|
||||
expect(state.scale).toBeLessThan(0.2) // Very zoomed out
|
||||
})
|
||||
|
||||
test('Should show visual difference between LOD on and off', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Load a workflow with text-heavy nodes for clear visual difference
|
||||
await comfyPage.loadWorkflow('default')
|
||||
test(
|
||||
'Should show visual difference between LOD on and off',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
// Load a workflow with text-heavy nodes for clear visual difference
|
||||
await comfyPage.loadWorkflow('default')
|
||||
|
||||
// Set zoom level clearly below the threshold to ensure LOD activates
|
||||
const targetZoom = 0.4 // Well below default threshold of ~0.571
|
||||
// Set zoom level clearly below the threshold to ensure LOD activates
|
||||
const targetZoom = 0.4 // Well below default threshold of ~0.571
|
||||
|
||||
// Zoom to target level
|
||||
await comfyPage.page.evaluate((zoom) => {
|
||||
window['app'].canvas.ds.scale = zoom
|
||||
window['app'].canvas.setDirty(true, true)
|
||||
}, targetZoom)
|
||||
await comfyPage.nextFrame()
|
||||
// Zoom to target level
|
||||
await comfyPage.page.evaluate((zoom) => {
|
||||
window['app'].canvas.ds.scale = zoom
|
||||
window['app'].canvas.setDirty(true, true)
|
||||
}, targetZoom)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Take snapshot with LOD active (default 8px setting)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'lod-comparison-low-quality.png'
|
||||
)
|
||||
// Take snapshot with LOD active (default 8px setting)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'lod-comparison-low-quality.png'
|
||||
)
|
||||
|
||||
const lowQualityState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
}
|
||||
})
|
||||
expect(lowQualityState.lowQuality).toBe(true)
|
||||
const lowQualityState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
}
|
||||
})
|
||||
expect(lowQualityState.lowQuality).toBe(true)
|
||||
|
||||
// Disable LOD to see high quality at same zoom
|
||||
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 0)
|
||||
await comfyPage.nextFrame()
|
||||
// Disable LOD to see high quality at same zoom
|
||||
await comfyPage.setSetting('LiteGraph.Canvas.MinFontSizeForLOD', 0)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Take snapshot with LOD disabled (full quality at same zoom)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'lod-comparison-high-quality.png'
|
||||
)
|
||||
// Take snapshot with LOD disabled (full quality at same zoom)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'lod-comparison-high-quality.png'
|
||||
)
|
||||
|
||||
const highQualityState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
}
|
||||
})
|
||||
expect(highQualityState.lowQuality).toBe(false)
|
||||
expect(highQualityState.scale).toBeCloseTo(targetZoom, 2)
|
||||
})
|
||||
const highQualityState = await comfyPage.page.evaluate(() => {
|
||||
const canvas = window['app'].canvas
|
||||
return {
|
||||
lowQuality: canvas.low_quality,
|
||||
scale: canvas.ds.scale
|
||||
}
|
||||
})
|
||||
expect(highQualityState.lowQuality).toBe(false)
|
||||
expect(highQualityState.scale).toBeCloseTo(targetZoom, 2)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Menu', () => {
|
||||
test.describe('Menu', { tag: '@ui' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Minimap', () => {
|
||||
test.describe('Minimap', { tag: '@canvas' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setSetting('Comfy.Minimap.Visible', true)
|
||||
|
||||
@@ -1,35 +1,39 @@
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
test.describe('Mobile Baseline Snapshots', () => {
|
||||
test('@mobile empty canvas', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.ConfirmClear', false)
|
||||
await comfyPage.executeCommand('Comfy.ClearWorkflow')
|
||||
await expect(async () => {
|
||||
expect(await comfyPage.getGraphNodesCount()).toBe(0)
|
||||
}).toPass({ timeout: 256 })
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('mobile-empty-canvas.png')
|
||||
})
|
||||
test.describe(
|
||||
'Mobile Baseline Snapshots',
|
||||
{ tag: ['@mobile', '@screenshot'] },
|
||||
() => {
|
||||
test('@mobile empty canvas', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.ConfirmClear', false)
|
||||
await comfyPage.executeCommand('Comfy.ClearWorkflow')
|
||||
await expect(async () => {
|
||||
expect(await comfyPage.getGraphNodesCount()).toBe(0)
|
||||
}).toPass({ timeout: 256 })
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('mobile-empty-canvas.png')
|
||||
})
|
||||
|
||||
test('@mobile default workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'mobile-default-workflow.png'
|
||||
)
|
||||
})
|
||||
test('@mobile default workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'mobile-default-workflow.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('@mobile settings dialog', async ({ comfyPage }) => {
|
||||
await comfyPage.settingDialog.open()
|
||||
await comfyPage.nextFrame()
|
||||
test('@mobile settings dialog', async ({ comfyPage }) => {
|
||||
await comfyPage.settingDialog.open()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.settingDialog.root).toHaveScreenshot(
|
||||
'mobile-settings-dialog.png',
|
||||
{
|
||||
mask: [
|
||||
comfyPage.settingDialog.root.getByTestId('current-user-indicator')
|
||||
]
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
await expect(comfyPage.settingDialog.root).toHaveScreenshot(
|
||||
'mobile-settings-dialog.png',
|
||||
{
|
||||
mask: [
|
||||
comfyPage.settingDialog.root.getByTestId('current-user-indicator')
|
||||
]
|
||||
}
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
@@ -8,7 +8,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Node Badge', () => {
|
||||
test.describe('Node Badge', { tag: ['@screenshot', '@smoke', '@node'] }, () => {
|
||||
test('Can add badge', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
@@ -66,50 +66,60 @@ test.describe('Node Badge', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Node source badge', () => {
|
||||
Object.values(NodeBadgeMode).forEach(async (mode) => {
|
||||
test(`Shows node badges (${mode})`, async ({ comfyPage }) => {
|
||||
// Execution error workflow has both custom node and core node.
|
||||
await comfyPage.loadWorkflow('nodes/execution_error')
|
||||
await comfyPage.setSetting('Comfy.NodeBadge.NodeSourceBadgeMode', mode)
|
||||
await comfyPage.setSetting('Comfy.NodeBadge.NodeIdBadgeMode', mode)
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.resetView()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(`node-badge-${mode}.png`)
|
||||
test.describe(
|
||||
'Node source badge',
|
||||
{ tag: ['@screenshot', '@smoke', '@node'] },
|
||||
() => {
|
||||
Object.values(NodeBadgeMode).forEach(async (mode) => {
|
||||
test(`Shows node badges (${mode})`, async ({ comfyPage }) => {
|
||||
// Execution error workflow has both custom node and core node.
|
||||
await comfyPage.loadWorkflow('nodes/execution_error')
|
||||
await comfyPage.setSetting('Comfy.NodeBadge.NodeSourceBadgeMode', mode)
|
||||
await comfyPage.setSetting('Comfy.NodeBadge.NodeIdBadgeMode', mode)
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.resetView()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
`node-badge-${mode}.png`
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test.describe('Node badge color', () => {
|
||||
test('Can show node badge with unknown color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'unknown')
|
||||
await comfyPage.nextFrame()
|
||||
// Click empty space to trigger canvas re-render.
|
||||
await comfyPage.clickEmptySpace()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-badge-unknown-color-palette.png'
|
||||
)
|
||||
})
|
||||
test.describe(
|
||||
'Node badge color',
|
||||
{ tag: ['@screenshot', '@smoke', '@node'] },
|
||||
() => {
|
||||
test('Can show node badge with unknown color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'unknown')
|
||||
await comfyPage.nextFrame()
|
||||
// Click empty space to trigger canvas re-render.
|
||||
await comfyPage.clickEmptySpace()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-badge-unknown-color-palette.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can show node badge with light color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.nextFrame()
|
||||
// Click empty space to trigger canvas re-render.
|
||||
await comfyPage.clickEmptySpace()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-badge-light-color-palette.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
test('Can show node badge with light color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.nextFrame()
|
||||
// Click empty space to trigger canvas re-render.
|
||||
await comfyPage.clickEmptySpace()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-badge-light-color-palette.png'
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
|
||||
// If an input is optional by node definition, it should be shown as
|
||||
// a hollow circle no matter what shape it was defined in the workflow JSON.
|
||||
test.describe('Optional input', () => {
|
||||
test.describe('Optional input', { tag: ['@screenshot', '@node'] }, () => {
|
||||
test('No shape specified', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/optional_input_no_shape')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('optional_input.png')
|
||||
|
||||
@@ -23,7 +23,7 @@ async function selectNodeWithPan(comfyPage: ComfyPage, nodeRef: NodeReference) {
|
||||
await nodeRef.click('title')
|
||||
}
|
||||
|
||||
test.describe('Node Help', () => {
|
||||
test.describe('Node Help', { tag: ['@slow', '@ui'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
|
||||
@@ -7,7 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Node search box', () => {
|
||||
test.describe('Node search box', { tag: '@node' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'search box')
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box')
|
||||
@@ -46,14 +46,14 @@ test.describe('Node search box', () => {
|
||||
await expect(comfyPage.searchBox.input).toBeVisible()
|
||||
})
|
||||
|
||||
test('Can add node', async ({ comfyPage }) => {
|
||||
test('Can add node', { tag: '@screenshot' }, async ({ comfyPage }) => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('added-node.png')
|
||||
})
|
||||
|
||||
test('Can auto link node', async ({ comfyPage }) => {
|
||||
test('Can auto link node', { tag: '@screenshot' }, async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
// Select the second item as the first item is always reroute
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('CLIPTextEncode', {
|
||||
@@ -62,41 +62,47 @@ test.describe('Node search box', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('auto-linked-node.png')
|
||||
})
|
||||
|
||||
test('Can auto link batch moved node', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('links/batch_move_links')
|
||||
test(
|
||||
'Can auto link batch moved node',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('links/batch_move_links')
|
||||
|
||||
const outputSlot1Pos = {
|
||||
x: 304,
|
||||
y: 127
|
||||
const outputSlot1Pos = {
|
||||
x: 304,
|
||||
y: 127
|
||||
}
|
||||
const emptySpacePos = {
|
||||
x: 5,
|
||||
y: 5
|
||||
}
|
||||
await comfyPage.page.keyboard.down('Shift')
|
||||
await comfyPage.dragAndDrop(outputSlot1Pos, emptySpacePos)
|
||||
await comfyPage.page.keyboard.up('Shift')
|
||||
|
||||
// Select the second item as the first item is always reroute
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('Load Checkpoint', {
|
||||
suggestionIndex: 0
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'auto-linked-node-batch.png'
|
||||
)
|
||||
}
|
||||
const emptySpacePos = {
|
||||
x: 5,
|
||||
y: 5
|
||||
)
|
||||
|
||||
test(
|
||||
'Link release connecting to node with no slots',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
await comfyPage.page.locator('.p-chip-remove-icon').click()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'added-node-no-connection.png'
|
||||
)
|
||||
}
|
||||
await comfyPage.page.keyboard.down('Shift')
|
||||
await comfyPage.dragAndDrop(outputSlot1Pos, emptySpacePos)
|
||||
await comfyPage.page.keyboard.up('Shift')
|
||||
|
||||
// Select the second item as the first item is always reroute
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('Load Checkpoint', {
|
||||
suggestionIndex: 0
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'auto-linked-node-batch.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Link release connecting to node with no slots', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await expect(comfyPage.searchBox.input).toHaveCount(1)
|
||||
await comfyPage.page.locator('.p-chip-remove-icon').click()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'added-node-no-connection.png'
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
test('Has correct aria-labels on search results', async ({ comfyPage }) => {
|
||||
const node = 'Load Checkpoint'
|
||||
@@ -251,40 +257,45 @@ test.describe('Node search box', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Release context menu', () => {
|
||||
test.describe('Release context menu', { tag: '@node' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.Action', 'context menu')
|
||||
await comfyPage.setSetting('Comfy.LinkRelease.ActionShift', 'search box')
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
})
|
||||
|
||||
test('Can trigger on link release', async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
const contextMenu = comfyPage.page.locator('.litecontextmenu')
|
||||
// Wait for context menu with correct title (slot name | slot type)
|
||||
// The title shows the output slot name and type from the disconnected link
|
||||
await expect(contextMenu.locator('.litemenu-title')).toContainText(
|
||||
'CLIP | CLIP'
|
||||
)
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'link-release-context-menu.png'
|
||||
)
|
||||
})
|
||||
test(
|
||||
'Can trigger on link release',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
const contextMenu = comfyPage.page.locator('.litecontextmenu')
|
||||
// Wait for context menu with correct title (slot name | slot type)
|
||||
// The title shows the output slot name and type from the disconnected link
|
||||
await expect(contextMenu.locator('.litemenu-title')).toContainText(
|
||||
'CLIP | CLIP'
|
||||
)
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'link-release-context-menu.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test('Can search and add node from context menu', async ({
|
||||
comfyPage,
|
||||
comfyMouse
|
||||
}) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await comfyMouse.move({ x: 10, y: 10 })
|
||||
await comfyPage.clickContextMenuItem('Search')
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('CLIP Prompt')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'link-context-menu-search.png'
|
||||
)
|
||||
})
|
||||
test(
|
||||
'Can search and add node from context menu',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage, comfyMouse }) => {
|
||||
await comfyPage.disconnectEdge()
|
||||
await comfyMouse.move({ x: 10, y: 10 })
|
||||
await comfyPage.clickContextMenuItem('Search')
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('CLIP Prompt')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'link-context-menu-search.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
test('Existing user (pre-1.24.1) gets context menu by default on link release', async ({
|
||||
comfyPage
|
||||
|
||||
@@ -6,8 +6,8 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Note Node', () => {
|
||||
test('Can load node nodes', async ({ comfyPage }) => {
|
||||
test.describe('Note Node', { tag: '@node' }, () => {
|
||||
test('Can load node nodes', { tag: '@screenshot' }, async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('nodes/note_nodes')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('note_nodes.png')
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Primitive Node', () => {
|
||||
test.describe('Primitive Node', { tag: ['@screenshot', '@node'] }, () => {
|
||||
test('Can load with correct size', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('primitive/primitive_node')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('primitive_node.png')
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Record Audio Node', () => {
|
||||
test.describe('Record Audio Node', { tag: '@screenshot' }, () => {
|
||||
test('should add a record audio node and take a screenshot', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect } from '@playwright/test'
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Remote COMBO Widget', () => {
|
||||
test.describe('Remote COMBO Widget', { tag: '@widget' }, () => {
|
||||
const mockOptions = ['d', 'c', 'b', 'a']
|
||||
|
||||
const addRemoteWidgetNode = async (
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect } from '@playwright/test'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { getMiddlePoint } from '../fixtures/utils/litegraphUtils'
|
||||
|
||||
test.describe('Reroute Node', () => {
|
||||
test.describe('Reroute Node', { tag: ['@screenshot', '@node'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
@@ -38,92 +38,96 @@ test.describe('Reroute Node', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('LiteGraph Native Reroute Node', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
await comfyPage.setSetting('LiteGraph.Reroute.SplineOffset', 80)
|
||||
})
|
||||
test.describe(
|
||||
'LiteGraph Native Reroute Node',
|
||||
{ tag: ['@screenshot', '@node'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
await comfyPage.setSetting('LiteGraph.Reroute.SplineOffset', 80)
|
||||
})
|
||||
|
||||
test('loads from workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('reroute/native_reroute')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('native_reroute.png')
|
||||
})
|
||||
test('loads from workflow', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('reroute/native_reroute')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('native_reroute.png')
|
||||
})
|
||||
|
||||
test('@2x @0.5x Can add reroute by alt clicking on link', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = (
|
||||
await comfyPage.getNodeRefsByTitle('Load Checkpoint')
|
||||
)[0]
|
||||
const clipEncodeNode = (
|
||||
await comfyPage.getNodeRefsByTitle('CLIP Text Encode (Prompt)')
|
||||
)[0]
|
||||
test('@2x @0.5x Can add reroute by alt clicking on link', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = (
|
||||
await comfyPage.getNodeRefsByTitle('Load Checkpoint')
|
||||
)[0]
|
||||
const clipEncodeNode = (
|
||||
await comfyPage.getNodeRefsByTitle('CLIP Text Encode (Prompt)')
|
||||
)[0]
|
||||
|
||||
const slot1 = await loadCheckpointNode.getOutput(1)
|
||||
const slot2 = await clipEncodeNode.getInput(0)
|
||||
const middlePoint = getMiddlePoint(
|
||||
await slot1.getPosition(),
|
||||
await slot2.getPosition()
|
||||
)
|
||||
const slot1 = await loadCheckpointNode.getOutput(1)
|
||||
const slot2 = await clipEncodeNode.getInput(0)
|
||||
const middlePoint = getMiddlePoint(
|
||||
await slot1.getPosition(),
|
||||
await slot2.getPosition()
|
||||
)
|
||||
|
||||
await comfyPage.page.keyboard.down('Alt')
|
||||
await comfyPage.page.mouse.click(middlePoint.x, middlePoint.y)
|
||||
await comfyPage.page.keyboard.up('Alt')
|
||||
await comfyPage.page.keyboard.down('Alt')
|
||||
await comfyPage.page.mouse.click(middlePoint.x, middlePoint.y)
|
||||
await comfyPage.page.keyboard.up('Alt')
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'native_reroute_alt_click.png'
|
||||
)
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'native_reroute_alt_click.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can add reroute by clicking middle of link context menu', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = (
|
||||
await comfyPage.getNodeRefsByTitle('Load Checkpoint')
|
||||
)[0]
|
||||
const clipEncodeNode = (
|
||||
await comfyPage.getNodeRefsByTitle('CLIP Text Encode (Prompt)')
|
||||
)[0]
|
||||
test('Can add reroute by clicking middle of link context menu', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const loadCheckpointNode = (
|
||||
await comfyPage.getNodeRefsByTitle('Load Checkpoint')
|
||||
)[0]
|
||||
const clipEncodeNode = (
|
||||
await comfyPage.getNodeRefsByTitle('CLIP Text Encode (Prompt)')
|
||||
)[0]
|
||||
|
||||
const slot1 = await loadCheckpointNode.getOutput(1)
|
||||
const slot2 = await clipEncodeNode.getInput(0)
|
||||
const middlePoint = getMiddlePoint(
|
||||
await slot1.getPosition(),
|
||||
await slot2.getPosition()
|
||||
)
|
||||
const slot1 = await loadCheckpointNode.getOutput(1)
|
||||
const slot2 = await clipEncodeNode.getInput(0)
|
||||
const middlePoint = getMiddlePoint(
|
||||
await slot1.getPosition(),
|
||||
await slot2.getPosition()
|
||||
)
|
||||
|
||||
await comfyPage.page.mouse.click(middlePoint.x, middlePoint.y)
|
||||
await comfyPage.page
|
||||
.locator('.litecontextmenu .litemenu-entry', { hasText: 'Add Reroute' })
|
||||
.click()
|
||||
await comfyPage.page.mouse.click(middlePoint.x, middlePoint.y)
|
||||
await comfyPage.page
|
||||
.locator('.litecontextmenu .litemenu-entry', { hasText: 'Add Reroute' })
|
||||
.click()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'native_reroute_context_menu.png'
|
||||
)
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'native_reroute_context_menu.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can delete link that is connected to two reroutes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/issues/4695
|
||||
await comfyPage.loadWorkflow(
|
||||
'reroute/single-native-reroute-default-workflow'
|
||||
)
|
||||
test('Can delete link that is connected to two reroutes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/issues/4695
|
||||
await comfyPage.loadWorkflow(
|
||||
'reroute/single-native-reroute-default-workflow'
|
||||
)
|
||||
|
||||
// To find the clickable midpoint button, we use the hardcoded value from the browser logs
|
||||
// since the link is a bezier curve and not a straight line.
|
||||
const middlePoint = { x: 359.4188232421875, y: 468.7716979980469 }
|
||||
// To find the clickable midpoint button, we use the hardcoded value from the browser logs
|
||||
// since the link is a bezier curve and not a straight line.
|
||||
const middlePoint = { x: 359.4188232421875, y: 468.7716979980469 }
|
||||
|
||||
// Click the middle point of the link to open the context menu.
|
||||
await comfyPage.page.mouse.click(middlePoint.x, middlePoint.y)
|
||||
// Click the middle point of the link to open the context menu.
|
||||
await comfyPage.page.mouse.click(middlePoint.x, middlePoint.y)
|
||||
|
||||
// Click the "Delete" context menu option.
|
||||
await comfyPage.page
|
||||
.locator('.litecontextmenu .litemenu-entry', { hasText: 'Delete' })
|
||||
.click()
|
||||
// Click the "Delete" context menu option.
|
||||
await comfyPage.page
|
||||
.locator('.litecontextmenu .litemenu-entry', { hasText: 'Delete' })
|
||||
.click()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'native_reroute_delete_from_midpoint_context_menu.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'native_reroute_delete_from_midpoint_context_menu.png'
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -7,43 +7,49 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Canvas Right Click Menu', () => {
|
||||
test('Can add node', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickCanvas()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-menu.png')
|
||||
await comfyPage.page.getByText('Add Node').click()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.page.getByText('loaders').click()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.page.getByText('Load VAE').click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('add-node-node-added.png')
|
||||
})
|
||||
test.describe(
|
||||
'Canvas Right Click Menu',
|
||||
{ tag: ['@screenshot', '@ui'] },
|
||||
() => {
|
||||
test('Can add node', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickCanvas()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-menu.png')
|
||||
await comfyPage.page.getByText('Add Node').click()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.page.getByText('loaders').click()
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.page.getByText('Load VAE').click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('add-node-node-added.png')
|
||||
})
|
||||
|
||||
test('Can add group', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickCanvas()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-menu.png')
|
||||
await comfyPage.page.getByText('Add Group', { exact: true }).click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('add-group-group-added.png')
|
||||
})
|
||||
test('Can add group', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickCanvas()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-menu.png')
|
||||
await comfyPage.page.getByText('Add Group', { exact: true }).click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'add-group-group-added.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('Can convert to group node', async ({ comfyPage }) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-2-nodes.png')
|
||||
await comfyPage.rightClickCanvas()
|
||||
await comfyPage.clickContextMenuItem('Convert to Group Node (Deprecated)')
|
||||
await comfyPage.promptDialogInput.fill('GroupNode2CLIP')
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await comfyPage.promptDialogInput.waitFor({ state: 'hidden' })
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'right-click-node-group-node.png'
|
||||
)
|
||||
})
|
||||
})
|
||||
test('Can convert to group node', async ({ comfyPage }) => {
|
||||
await comfyPage.select2Nodes()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('selected-2-nodes.png')
|
||||
await comfyPage.rightClickCanvas()
|
||||
await comfyPage.clickContextMenuItem('Convert to Group Node (Deprecated)')
|
||||
await comfyPage.promptDialogInput.fill('GroupNode2CLIP')
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await comfyPage.promptDialogInput.waitFor({ state: 'hidden' })
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'right-click-node-group-node.png'
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test.describe('Node Right Click Menu', () => {
|
||||
test.describe('Node Right Click Menu', { tag: ['@screenshot', '@ui'] }, () => {
|
||||
test('Can open properties panel', async ({ comfyPage }) => {
|
||||
await comfyPage.rightClickEmptyLatentNode()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('right-click-node.png')
|
||||
|
||||
@@ -10,7 +10,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
const BLUE_COLOR = 'rgb(51, 51, 85)'
|
||||
const RED_COLOR = 'rgb(85, 51, 51)'
|
||||
|
||||
test.describe('Selection Toolbox', () => {
|
||||
test.describe('Selection Toolbox', { tag: ['@screenshot', '@ui'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
})
|
||||
|
||||
@@ -7,178 +7,190 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Selection Toolbox - More Options Submenus', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.selectNodes(['KSampler'])
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
const openMoreOptions = async (comfyPage: ComfyPage) => {
|
||||
const ksamplerNodes = await comfyPage.getNodeRefsByTitle('KSampler')
|
||||
if (ksamplerNodes.length === 0) {
|
||||
throw new Error('No KSampler nodes found')
|
||||
}
|
||||
|
||||
// Drag the KSampler to the center of the screen
|
||||
const nodePos = await ksamplerNodes[0].getPosition()
|
||||
const viewportSize = comfyPage.page.viewportSize()
|
||||
const centerX = viewportSize.width / 3
|
||||
const centerY = viewportSize.height / 2
|
||||
await comfyPage.dragAndDrop(
|
||||
{ x: nodePos.x, y: nodePos.y },
|
||||
{ x: centerX, y: centerY }
|
||||
)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await ksamplerNodes[0].click('title')
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.page.locator('.selection-toolbox')).toBeVisible({
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
const moreOptionsBtn = comfyPage.page.locator(
|
||||
'[data-testid="more-options-button"]'
|
||||
)
|
||||
await expect(moreOptionsBtn).toBeVisible({ timeout: 3000 })
|
||||
|
||||
await comfyPage.page.click('[data-testid="more-options-button"]')
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const menuOptionsVisible = await comfyPage.page
|
||||
.getByText('Rename')
|
||||
.isVisible({ timeout: 2000 })
|
||||
.catch(() => false)
|
||||
if (menuOptionsVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
await moreOptionsBtn.click({ force: true })
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const menuOptionsVisibleAfterClick = await comfyPage.page
|
||||
.getByText('Rename')
|
||||
.isVisible({ timeout: 2000 })
|
||||
.catch(() => false)
|
||||
if (menuOptionsVisibleAfterClick) {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Could not open More Options menu - popover not showing')
|
||||
}
|
||||
|
||||
test('opens Node Info from More Options menu', async ({ comfyPage }) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
const nodeInfoButton = comfyPage.page.getByText('Node Info', {
|
||||
exact: true
|
||||
})
|
||||
await expect(nodeInfoButton).toBeVisible()
|
||||
await nodeInfoButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
test('changes node shape via Shape submenu', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
const initialShape = await nodeRef.getProperty<number>('shape')
|
||||
|
||||
await openMoreOptions(comfyPage)
|
||||
await comfyPage.page.getByText('Shape', { exact: true }).hover()
|
||||
await expect(comfyPage.page.getByText('Box', { exact: true })).toBeVisible({
|
||||
timeout: 5000
|
||||
})
|
||||
await comfyPage.page.getByText('Box', { exact: true }).click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newShape = await nodeRef.getProperty<number>('shape')
|
||||
expect(newShape).not.toBe(initialShape)
|
||||
expect(newShape).toBe(1)
|
||||
})
|
||||
|
||||
test('changes node color via Color submenu swatch', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
const initialColor = await nodeRef.getProperty<string | undefined>('color')
|
||||
|
||||
await openMoreOptions(comfyPage)
|
||||
await comfyPage.page.getByText('Color', { exact: true }).click()
|
||||
const blueSwatch = comfyPage.page.locator('[title="Blue"]')
|
||||
await expect(blueSwatch.first()).toBeVisible({ timeout: 5000 })
|
||||
await blueSwatch.first().click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newColor = await nodeRef.getProperty<string | undefined>('color')
|
||||
expect(newColor).toBe('#223')
|
||||
if (initialColor) {
|
||||
expect(newColor).not.toBe(initialColor)
|
||||
}
|
||||
})
|
||||
|
||||
test('renames a node using Rename action', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
await openMoreOptions(comfyPage)
|
||||
await comfyPage.page
|
||||
.getByText('Rename', { exact: true })
|
||||
.click({ force: true })
|
||||
const input = comfyPage.page.locator(
|
||||
'.group-title-editor.node-title-editor .editable-text input'
|
||||
)
|
||||
await expect(input).toBeVisible()
|
||||
await input.fill('RenamedNode')
|
||||
await input.press('Enter')
|
||||
await comfyPage.nextFrame()
|
||||
const newTitle = await nodeRef.getProperty<string>('title')
|
||||
expect(newTitle).toBe('RenamedNode')
|
||||
})
|
||||
|
||||
test('closes More Options menu when clicking outside', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
const renameItem = comfyPage.page.getByText('Rename', { exact: true })
|
||||
await expect(renameItem).toBeVisible({ timeout: 5000 })
|
||||
|
||||
// Wait for multiple frames to allow PrimeVue's outside click handler to initialize
|
||||
for (let i = 0; i < 30; i++) {
|
||||
test.describe(
|
||||
'Selection Toolbox - More Options Submenus',
|
||||
{ tag: '@ui' },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.selectNodes(['KSampler'])
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
const openMoreOptions = async (comfyPage: ComfyPage) => {
|
||||
const ksamplerNodes = await comfyPage.getNodeRefsByTitle('KSampler')
|
||||
if (ksamplerNodes.length === 0) {
|
||||
throw new Error('No KSampler nodes found')
|
||||
}
|
||||
|
||||
// Drag the KSampler to the center of the screen
|
||||
const nodePos = await ksamplerNodes[0].getPosition()
|
||||
const viewportSize = comfyPage.page.viewportSize()
|
||||
const centerX = viewportSize.width / 3
|
||||
const centerY = viewportSize.height / 2
|
||||
await comfyPage.dragAndDrop(
|
||||
{ x: nodePos.x, y: nodePos.y },
|
||||
{ x: centerX, y: centerY }
|
||||
)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await ksamplerNodes[0].click('title')
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(comfyPage.page.locator('.selection-toolbox')).toBeVisible({
|
||||
timeout: 5000
|
||||
})
|
||||
|
||||
const moreOptionsBtn = comfyPage.page.locator(
|
||||
'[data-testid="more-options-button"]'
|
||||
)
|
||||
await expect(moreOptionsBtn).toBeVisible({ timeout: 3000 })
|
||||
|
||||
await comfyPage.page.click('[data-testid="more-options-button"]')
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const menuOptionsVisible = await comfyPage.page
|
||||
.getByText('Rename')
|
||||
.isVisible({ timeout: 2000 })
|
||||
.catch(() => false)
|
||||
if (menuOptionsVisible) {
|
||||
return
|
||||
}
|
||||
|
||||
await moreOptionsBtn.click({ force: true })
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const menuOptionsVisibleAfterClick = await comfyPage.page
|
||||
.getByText('Rename')
|
||||
.isVisible({ timeout: 2000 })
|
||||
.catch(() => false)
|
||||
if (menuOptionsVisibleAfterClick) {
|
||||
return
|
||||
}
|
||||
|
||||
throw new Error('Could not open More Options menu - popover not showing')
|
||||
}
|
||||
|
||||
await comfyPage.page
|
||||
.locator('#graph-canvas')
|
||||
.click({ position: { x: 0, y: 50 }, force: true })
|
||||
test('opens Node Info from More Options menu', async ({ comfyPage }) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
const nodeInfoButton = comfyPage.page.getByText('Node Info', {
|
||||
exact: true
|
||||
})
|
||||
await expect(nodeInfoButton).toBeVisible()
|
||||
await nodeInfoButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
await expect(
|
||||
comfyPage.page.getByText('Rename', { exact: true })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
test('changes node shape via Shape submenu', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
const initialShape = await nodeRef.getProperty<number>('shape')
|
||||
|
||||
test('closes More Options menu when clicking the button again (toggle)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
await expect(
|
||||
comfyPage.page.getByText('Rename', { exact: true })
|
||||
).toBeVisible({ timeout: 5000 })
|
||||
await openMoreOptions(comfyPage)
|
||||
await comfyPage.page.getByText('Shape', { exact: true }).hover()
|
||||
await expect(
|
||||
comfyPage.page.getByText('Box', { exact: true })
|
||||
).toBeVisible({
|
||||
timeout: 5000
|
||||
})
|
||||
await comfyPage.page.getByText('Box', { exact: true }).click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const btn = document.querySelector('[data-testid="more-options-button"]')
|
||||
if (btn) {
|
||||
const event = new MouseEvent('click', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window,
|
||||
detail: 1
|
||||
})
|
||||
btn.dispatchEvent(event)
|
||||
const newShape = await nodeRef.getProperty<number>('shape')
|
||||
expect(newShape).not.toBe(initialShape)
|
||||
expect(newShape).toBe(1)
|
||||
})
|
||||
|
||||
test('changes node color via Color submenu swatch', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
const initialColor = await nodeRef.getProperty<string | undefined>(
|
||||
'color'
|
||||
)
|
||||
|
||||
await openMoreOptions(comfyPage)
|
||||
await comfyPage.page.getByText('Color', { exact: true }).click()
|
||||
const blueSwatch = comfyPage.page.locator('[title="Blue"]')
|
||||
await expect(blueSwatch.first()).toBeVisible({ timeout: 5000 })
|
||||
await blueSwatch.first().click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
const newColor = await nodeRef.getProperty<string | undefined>('color')
|
||||
expect(newColor).toBe('#223')
|
||||
if (initialColor) {
|
||||
expect(newColor).not.toBe(initialColor)
|
||||
}
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(
|
||||
comfyPage.page.getByText('Rename', { exact: true })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
})
|
||||
test('renames a node using Rename action', async ({ comfyPage }) => {
|
||||
const nodeRef = (await comfyPage.getNodeRefsByTitle('KSampler'))[0]
|
||||
await openMoreOptions(comfyPage)
|
||||
await comfyPage.page
|
||||
.getByText('Rename', { exact: true })
|
||||
.click({ force: true })
|
||||
const input = comfyPage.page.locator(
|
||||
'.group-title-editor.node-title-editor .editable-text input'
|
||||
)
|
||||
await expect(input).toBeVisible()
|
||||
await input.fill('RenamedNode')
|
||||
await input.press('Enter')
|
||||
await comfyPage.nextFrame()
|
||||
const newTitle = await nodeRef.getProperty<string>('title')
|
||||
expect(newTitle).toBe('RenamedNode')
|
||||
})
|
||||
|
||||
test('closes More Options menu when clicking outside', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
const renameItem = comfyPage.page.getByText('Rename', { exact: true })
|
||||
await expect(renameItem).toBeVisible({ timeout: 5000 })
|
||||
|
||||
// Wait for multiple frames to allow PrimeVue's outside click handler to initialize
|
||||
for (let i = 0; i < 30; i++) {
|
||||
await comfyPage.nextFrame()
|
||||
}
|
||||
|
||||
await comfyPage.page
|
||||
.locator('#graph-canvas')
|
||||
.click({ position: { x: 0, y: 50 }, force: true })
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
await expect(
|
||||
comfyPage.page.getByText('Rename', { exact: true })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
|
||||
test('closes More Options menu when clicking the button again (toggle)', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await openMoreOptions(comfyPage)
|
||||
await expect(
|
||||
comfyPage.page.getByText('Rename', { exact: true })
|
||||
).toBeVisible({ timeout: 5000 })
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const btn = document.querySelector(
|
||||
'[data-testid="more-options-button"]'
|
||||
)
|
||||
if (btn) {
|
||||
const event = new MouseEvent('click', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
view: window,
|
||||
detail: 1
|
||||
})
|
||||
btn.dispatchEvent(event)
|
||||
}
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await expect(
|
||||
comfyPage.page.getByText('Rename', { exact: true })
|
||||
).not.toBeVisible()
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ const SELECTORS = {
|
||||
promptDialog: '.graphdialog input'
|
||||
} as const
|
||||
|
||||
test.describe('Subgraph Slot Rename Dialog', () => {
|
||||
test.describe('Subgraph Slot Rename Dialog', { tag: '@subgraph' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ const SELECTORS = {
|
||||
domWidget: '.comfy-multiline-input'
|
||||
} as const
|
||||
|
||||
test.describe('Subgraph Operations', () => {
|
||||
test.describe('Subgraph Operations', { tag: ['@slow', '@subgraph'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ async function checkTemplateFileExists(
|
||||
return response.ok()
|
||||
}
|
||||
|
||||
test.describe('Templates', () => {
|
||||
test.describe('Templates', { tag: ['@slow', '@workflow'] }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setSetting('Comfy.Workflow.ShowMissingModelsWarning', false)
|
||||
@@ -207,109 +207,114 @@ test.describe('Templates', () => {
|
||||
await expect(nav).toBeVisible() // Nav should be visible at tablet size
|
||||
})
|
||||
|
||||
test('template cards descriptions adjust height dynamically', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Setup test by intercepting templates response to inject cards with varying description lengths
|
||||
await comfyPage.page.route('**/templates/index.json', async (route, _) => {
|
||||
const response = [
|
||||
{
|
||||
moduleName: 'default',
|
||||
title: 'Test Templates',
|
||||
type: 'image',
|
||||
templates: [
|
||||
test(
|
||||
'template cards descriptions adjust height dynamically',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
// Setup test by intercepting templates response to inject cards with varying description lengths
|
||||
await comfyPage.page.route(
|
||||
'**/templates/index.json',
|
||||
async (route, _) => {
|
||||
const response = [
|
||||
{
|
||||
name: 'short-description',
|
||||
title: 'Short Description',
|
||||
mediaType: 'image',
|
||||
mediaSubtype: 'webp',
|
||||
description: 'This is a short description.'
|
||||
},
|
||||
{
|
||||
name: 'medium-description',
|
||||
title: 'Medium Description',
|
||||
mediaType: 'image',
|
||||
mediaSubtype: 'webp',
|
||||
description:
|
||||
'This is a medium length description that should take up two lines on most displays.'
|
||||
},
|
||||
{
|
||||
name: 'long-description',
|
||||
title: 'Long Description',
|
||||
mediaType: 'image',
|
||||
mediaSubtype: 'webp',
|
||||
description:
|
||||
'This is a much longer description that should definitely wrap to multiple lines. It contains enough text to demonstrate how the cards handle varying amounts of content while maintaining a consistent layout grid.'
|
||||
moduleName: 'default',
|
||||
title: 'Test Templates',
|
||||
type: 'image',
|
||||
templates: [
|
||||
{
|
||||
name: 'short-description',
|
||||
title: 'Short Description',
|
||||
mediaType: 'image',
|
||||
mediaSubtype: 'webp',
|
||||
description: 'This is a short description.'
|
||||
},
|
||||
{
|
||||
name: 'medium-description',
|
||||
title: 'Medium Description',
|
||||
mediaType: 'image',
|
||||
mediaSubtype: 'webp',
|
||||
description:
|
||||
'This is a medium length description that should take up two lines on most displays.'
|
||||
},
|
||||
{
|
||||
name: 'long-description',
|
||||
title: 'Long Description',
|
||||
mediaType: 'image',
|
||||
mediaSubtype: 'webp',
|
||||
description:
|
||||
'This is a much longer description that should definitely wrap to multiple lines. It contains enough text to demonstrate how the cards handle varying amounts of content while maintaining a consistent layout grid.'
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(response),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store'
|
||||
}
|
||||
})
|
||||
}
|
||||
]
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
body: JSON.stringify(response),
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
)
|
||||
|
||||
// Mock the thumbnail images to avoid 404s
|
||||
await comfyPage.page.route('**/templates/**.webp', async (route) => {
|
||||
const headers = {
|
||||
'Content-Type': 'image/webp',
|
||||
'Cache-Control': 'no-store'
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
path: 'browser_tests/assets/example.webp',
|
||||
headers
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Mock the thumbnail images to avoid 404s
|
||||
await comfyPage.page.route('**/templates/**.webp', async (route) => {
|
||||
const headers = {
|
||||
'Content-Type': 'image/webp',
|
||||
'Cache-Control': 'no-store'
|
||||
}
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
path: 'browser_tests/assets/example.webp',
|
||||
headers
|
||||
})
|
||||
})
|
||||
// Open templates dialog
|
||||
await comfyPage.executeCommand('Comfy.BrowseTemplates')
|
||||
await expect(comfyPage.templates.content).toBeVisible()
|
||||
|
||||
// Open templates dialog
|
||||
await comfyPage.executeCommand('Comfy.BrowseTemplates')
|
||||
await expect(comfyPage.templates.content).toBeVisible()
|
||||
// Wait for cards to load
|
||||
await expect(
|
||||
comfyPage.page.locator(
|
||||
'[data-testid="template-workflow-short-description"]'
|
||||
)
|
||||
).toBeVisible({ timeout: 5000 })
|
||||
|
||||
// Wait for cards to load
|
||||
await expect(
|
||||
comfyPage.page.locator(
|
||||
// Verify all three cards with different descriptions are visible
|
||||
const shortDescCard = comfyPage.page.locator(
|
||||
'[data-testid="template-workflow-short-description"]'
|
||||
)
|
||||
).toBeVisible({ timeout: 5000 })
|
||||
const mediumDescCard = comfyPage.page.locator(
|
||||
'[data-testid="template-workflow-medium-description"]'
|
||||
)
|
||||
const longDescCard = comfyPage.page.locator(
|
||||
'[data-testid="template-workflow-long-description"]'
|
||||
)
|
||||
|
||||
// Verify all three cards with different descriptions are visible
|
||||
const shortDescCard = comfyPage.page.locator(
|
||||
'[data-testid="template-workflow-short-description"]'
|
||||
)
|
||||
const mediumDescCard = comfyPage.page.locator(
|
||||
'[data-testid="template-workflow-medium-description"]'
|
||||
)
|
||||
const longDescCard = comfyPage.page.locator(
|
||||
'[data-testid="template-workflow-long-description"]'
|
||||
)
|
||||
await expect(shortDescCard).toBeVisible()
|
||||
await expect(mediumDescCard).toBeVisible()
|
||||
await expect(longDescCard).toBeVisible()
|
||||
|
||||
await expect(shortDescCard).toBeVisible()
|
||||
await expect(mediumDescCard).toBeVisible()
|
||||
await expect(longDescCard).toBeVisible()
|
||||
// Verify descriptions are visible and have line-clamp class
|
||||
// The description is in a p tag with text-muted class
|
||||
const shortDesc = shortDescCard.locator('p.text-muted.line-clamp-2')
|
||||
const mediumDesc = mediumDescCard.locator('p.text-muted.line-clamp-2')
|
||||
const longDesc = longDescCard.locator('p.text-muted.line-clamp-2')
|
||||
|
||||
// Verify descriptions are visible and have line-clamp class
|
||||
// The description is in a p tag with text-muted class
|
||||
const shortDesc = shortDescCard.locator('p.text-muted.line-clamp-2')
|
||||
const mediumDesc = mediumDescCard.locator('p.text-muted.line-clamp-2')
|
||||
const longDesc = longDescCard.locator('p.text-muted.line-clamp-2')
|
||||
await expect(shortDesc).toContainText('short description')
|
||||
await expect(mediumDesc).toContainText('medium length description')
|
||||
await expect(longDesc).toContainText('much longer description')
|
||||
|
||||
await expect(shortDesc).toContainText('short description')
|
||||
await expect(mediumDesc).toContainText('medium length description')
|
||||
await expect(longDesc).toContainText('much longer description')
|
||||
|
||||
// Verify grid layout maintains consistency
|
||||
const templateGrid = comfyPage.page.locator(
|
||||
'[data-testid="template-workflows-content"]'
|
||||
)
|
||||
await expect(templateGrid).toBeVisible()
|
||||
await expect(templateGrid).toHaveScreenshot(
|
||||
'template-grid-varying-content.png'
|
||||
)
|
||||
})
|
||||
// Verify grid layout maintains consistency
|
||||
const templateGrid = comfyPage.page.locator(
|
||||
'[data-testid="template-workflows-content"]'
|
||||
)
|
||||
await expect(templateGrid).toBeVisible()
|
||||
await expect(templateGrid).toHaveScreenshot(
|
||||
'template-grid-varying-content.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Settings Search functionality', () => {
|
||||
test.describe('Settings Search functionality', { tag: '@settings' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
// Register test settings to verify hidden/deprecated filtering
|
||||
await comfyPage.page.evaluate(() => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { userSelectPageFixture as test } from '../fixtures/UserSelectPage'
|
||||
/**
|
||||
* Expects ComfyUI backend to be launched with `--multi-user` flag.
|
||||
*/
|
||||
test.describe('User Select View', () => {
|
||||
test.describe('User Select View', { tag: '@settings' }, () => {
|
||||
test.beforeEach(async ({ userSelectPage, page }) => {
|
||||
await page.goto(userSelectPage.url)
|
||||
await page.evaluate(() => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { expect } from '@playwright/test'
|
||||
import type { SystemStats } from '../../src/schemas/apiSchema'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Version Mismatch Warnings', () => {
|
||||
test.describe('Version Mismatch Warnings', { tag: '@slow' }, () => {
|
||||
const ALWAYS_AHEAD_OF_INSTALLED_VERSION = '100.100.100'
|
||||
const ALWAYS_BEHIND_INSTALLED_VERSION = '0.0.0'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Viewport', () => {
|
||||
test.describe('Viewport', { tag: ['@screenshot', '@smoke', '@canvas'] }, () => {
|
||||
test('Fits view to nodes when saved viewport position is offscreen', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
|
||||
const CREATE_GROUP_HOTKEY = 'Control+g'
|
||||
|
||||
test.describe('Vue Node Groups', () => {
|
||||
test.describe('Vue Node Groups', { tag: '@screenshot' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
await comfyPage.setSetting('Comfy.Minimap.ShowGroups', true)
|
||||
|
||||
@@ -9,10 +9,14 @@ test.describe('Vue Nodes Canvas Pan', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('@mobile Can pan with touch', async ({ comfyPage }) => {
|
||||
await comfyPage.panWithTouch({ x: 64, y: 64 }, { x: 256, y: 256 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-paned-with-touch.png'
|
||||
)
|
||||
})
|
||||
test(
|
||||
'@mobile Can pan with touch',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.panWithTouch({ x: 64, y: 64 }, { x: 256, y: 256 })
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-paned-with-touch.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -10,25 +10,30 @@ test.describe('Vue Nodes Zoom', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should not capture drag while zooming with ctrl+shift+drag', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
const nodeBoundingBox = await checkpointNode.boundingBox()
|
||||
if (!nodeBoundingBox) throw new Error('Node bounding box not available')
|
||||
test(
|
||||
'should not capture drag while zooming with ctrl+shift+drag',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
const checkpointNode =
|
||||
comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
const nodeBoundingBox = await checkpointNode.boundingBox()
|
||||
if (!nodeBoundingBox) throw new Error('Node bounding box not available')
|
||||
|
||||
const nodeMidpointX = nodeBoundingBox.x + nodeBoundingBox.width / 2
|
||||
const nodeMidpointY = nodeBoundingBox.y + nodeBoundingBox.height / 2
|
||||
const nodeMidpointX = nodeBoundingBox.x + nodeBoundingBox.width / 2
|
||||
const nodeMidpointY = nodeBoundingBox.y + nodeBoundingBox.height / 2
|
||||
|
||||
// Start the Ctrl+Shift drag-to-zoom on the canvas and continue dragging over
|
||||
// the node. The node should not capture the drag while drag-zooming.
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
await comfyPage.page.keyboard.down('Shift')
|
||||
await comfyPage.dragAndDrop(
|
||||
{ x: 200, y: 300 },
|
||||
{ x: nodeMidpointX, y: nodeMidpointY }
|
||||
)
|
||||
// Start the Ctrl+Shift drag-to-zoom on the canvas and continue dragging over
|
||||
// the node. The node should not capture the drag while drag-zooming.
|
||||
await comfyPage.page.keyboard.down('Control')
|
||||
await comfyPage.page.keyboard.down('Shift')
|
||||
await comfyPage.dragAndDrop(
|
||||
{ x: 200, y: 300 },
|
||||
{ x: nodeMidpointX, y: nodeMidpointY }
|
||||
)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('zoomed-in-ctrl-shift.png')
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'zoomed-in-ctrl-shift.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -98,7 +98,7 @@ async function connectSlots(
|
||||
await nextFrame()
|
||||
}
|
||||
|
||||
test.describe('Vue Node Link Interaction', () => {
|
||||
test.describe('Vue Node Link Interaction', { tag: '@screenshot' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
import type { ComfyPage } from '../../../../fixtures/ComfyPage'
|
||||
import { fitToViewInstant } from '../../../../helpers/fitToView'
|
||||
|
||||
test.describe('Vue Node Bring to Front', () => {
|
||||
test.describe('Vue Node Bring to Front', { tag: '@screenshot' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
await comfyPage.setSetting('Comfy.VueNodes.Enabled', true)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
type ComfyPage,
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../../../../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '../../../../fixtures/ComfyPage'
|
||||
import type { Position } from '../../../../fixtures/types'
|
||||
|
||||
test.describe('Vue Node Moving', () => {
|
||||
@@ -29,39 +29,47 @@ test.describe('Vue Node Moving', () => {
|
||||
expect(diffY).toBeGreaterThan(0)
|
||||
}
|
||||
|
||||
test('should allow moving nodes by dragging', async ({ comfyPage }) => {
|
||||
const loadCheckpointHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await comfyPage.dragAndDrop(loadCheckpointHeaderPos, {
|
||||
x: 256,
|
||||
y: 256
|
||||
})
|
||||
test(
|
||||
'should allow moving nodes by dragging',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
const loadCheckpointHeaderPos =
|
||||
await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await comfyPage.dragAndDrop(loadCheckpointHeaderPos, {
|
||||
x: 256,
|
||||
y: 256
|
||||
})
|
||||
|
||||
const newHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await expectPosChanged(loadCheckpointHeaderPos, newHeaderPos)
|
||||
const newHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await expectPosChanged(loadCheckpointHeaderPos, newHeaderPos)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-node-moved-node.png')
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-node-moved-node.png')
|
||||
}
|
||||
)
|
||||
|
||||
test('@mobile should allow moving nodes by dragging on touch devices', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
// Disable minimap (gets in way of the node on small screens)
|
||||
await comfyPage.setSetting('Comfy.Minimap.Visible', false)
|
||||
test(
|
||||
'@mobile should allow moving nodes by dragging on touch devices',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
// Disable minimap (gets in way of the node on small screens)
|
||||
await comfyPage.setSetting('Comfy.Minimap.Visible', false)
|
||||
|
||||
const loadCheckpointHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await comfyPage.panWithTouch(
|
||||
{
|
||||
x: 64,
|
||||
y: 64
|
||||
},
|
||||
loadCheckpointHeaderPos
|
||||
)
|
||||
const loadCheckpointHeaderPos =
|
||||
await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await comfyPage.panWithTouch(
|
||||
{
|
||||
x: 64,
|
||||
y: 64
|
||||
},
|
||||
loadCheckpointHeaderPos
|
||||
)
|
||||
|
||||
const newHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await expectPosChanged(loadCheckpointHeaderPos, newHeaderPos)
|
||||
const newHeaderPos = await getLoadCheckpointHeaderPos(comfyPage)
|
||||
await expectPosChanged(loadCheckpointHeaderPos, newHeaderPos)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-moved-node-touch.png'
|
||||
)
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-moved-node-touch.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -15,22 +15,25 @@ test.describe('Vue Node Bypass', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should allow toggling bypass on a selected node with hotkey', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
test(
|
||||
'should allow toggling bypass on a selected node with hotkey',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
|
||||
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-bypassed-state.png'
|
||||
)
|
||||
const checkpointNode =
|
||||
comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
await expect(checkpointNode).toHaveClass(BYPASS_CLASS)
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-bypassed-state.png'
|
||||
)
|
||||
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
||||
})
|
||||
await comfyPage.page.keyboard.press(BYPASS_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveClass(BYPASS_CLASS)
|
||||
}
|
||||
)
|
||||
|
||||
test('should allow toggling bypass on multiple selected nodes with hotkey', async ({
|
||||
comfyPage
|
||||
|
||||
@@ -3,7 +3,7 @@ import {
|
||||
comfyPageFixture as test
|
||||
} from '../../../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Node Custom Colors', () => {
|
||||
test.describe('Vue Node Custom Colors', { tag: '@screenshot' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true)
|
||||
|
||||
@@ -12,19 +12,24 @@ test.describe('Vue Node Mute', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should allow toggling mute on a selected node with hotkey', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
|
||||
test(
|
||||
'should allow toggling mute on a selected node with hotkey',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.page.getByText('Load Checkpoint').click()
|
||||
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
|
||||
|
||||
const checkpointNode = comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
await expect(checkpointNode).toHaveCSS('opacity', MUTE_OPACITY)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('vue-node-muted-state.png')
|
||||
const checkpointNode =
|
||||
comfyPage.vueNodes.getNodeByTitle('Load Checkpoint')
|
||||
await expect(checkpointNode).toHaveCSS('opacity', MUTE_OPACITY)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-node-muted-state.png'
|
||||
)
|
||||
|
||||
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveCSS('opacity', MUTE_OPACITY)
|
||||
})
|
||||
await comfyPage.page.keyboard.press(MUTE_HOTKEY)
|
||||
await expect(checkpointNode).not.toHaveCSS('opacity', MUTE_OPACITY)
|
||||
}
|
||||
)
|
||||
|
||||
test('should allow toggling mute on multiple selected nodes with hotkey', async ({
|
||||
comfyPage
|
||||
|
||||
@@ -9,13 +9,17 @@ test.describe('Vue Upload Widgets', () => {
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
})
|
||||
|
||||
test('should hide canvas-only upload buttons', async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('widgets/all_load_widgets')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
test(
|
||||
'should hide canvas-only upload buttons',
|
||||
{ tag: '@screenshot' },
|
||||
async ({ comfyPage }) => {
|
||||
await comfyPage.setup()
|
||||
await comfyPage.loadWorkflow('widgets/all_load_widgets')
|
||||
await comfyPage.vueNodes.waitForNodes()
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-upload-widgets.png'
|
||||
)
|
||||
})
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'vue-nodes-upload-widgets.png'
|
||||
)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
type ComfyPage,
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../../../../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '../../../../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Vue Multiline String Widget', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
|
||||
@@ -6,7 +6,7 @@ test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Disabled')
|
||||
})
|
||||
|
||||
test.describe('Combo text widget', () => {
|
||||
test.describe('Combo text widget', { tag: ['@screenshot', '@widget'] }, () => {
|
||||
test('Truncates text when resized', async ({ comfyPage }) => {
|
||||
await comfyPage.resizeLoadCheckpointNode(0.2, 1)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
@@ -79,7 +79,7 @@ test.describe('Combo text widget', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Boolean widget', () => {
|
||||
test.describe('Boolean widget', { tag: ['@screenshot', '@widget'] }, () => {
|
||||
test('Can toggle', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/boolean_widget')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('boolean_widget.png')
|
||||
@@ -92,7 +92,7 @@ test.describe('Boolean widget', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Slider widget', () => {
|
||||
test.describe('Slider widget', { tag: ['@screenshot', '@widget'] }, () => {
|
||||
test('Can drag adjust value', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('inputs/simple_slider')
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
@@ -113,7 +113,7 @@ test.describe('Slider widget', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Number widget', () => {
|
||||
test.describe('Number widget', { tag: ['@screenshot', '@widget'] }, () => {
|
||||
test('Can drag adjust value', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/seed_widget')
|
||||
|
||||
@@ -134,22 +134,28 @@ test.describe('Number widget', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Dynamic widget manipulation', () => {
|
||||
test('Auto expand node when widget is added dynamically', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
test.describe(
|
||||
'Dynamic widget manipulation',
|
||||
{ tag: ['@screenshot', '@widget'] },
|
||||
() => {
|
||||
test('Auto expand node when widget is added dynamically', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('nodes/single_ksampler')
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['graph'].nodes[0].addWidget('number', 'new_widget', 10)
|
||||
window['graph'].setDirtyCanvas(true, true)
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['graph'].nodes[0].addWidget('number', 'new_widget', 10)
|
||||
window['graph'].setDirtyCanvas(true, true)
|
||||
})
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'ksampler_widget_added.png'
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('ksampler_widget_added.png')
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Image widget', () => {
|
||||
test.describe('Image widget', { tag: ['@screenshot', '@widget'] }, () => {
|
||||
test('Can load image', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_image_widget')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('load_image_widget.png')
|
||||
@@ -236,99 +242,103 @@ test.describe('Image widget', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Animated image widget', () => {
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/issues/3718
|
||||
test.skip('Shows preview of uploaded animated image', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_animated_webp')
|
||||
test.describe(
|
||||
'Animated image widget',
|
||||
{ tag: ['@screenshot', '@widget'] },
|
||||
() => {
|
||||
// https://github.com/Comfy-Org/ComfyUI_frontend/issues/3718
|
||||
test.skip('Shows preview of uploaded animated image', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_animated_webp')
|
||||
|
||||
// Get position of the load animated webp node
|
||||
const nodes = await comfyPage.getNodeRefsByType(
|
||||
'DevToolsLoadAnimatedImageTest'
|
||||
)
|
||||
const loadAnimatedWebpNode = nodes[0]
|
||||
const { x, y } = await loadAnimatedWebpNode.getPosition()
|
||||
// Get position of the load animated webp node
|
||||
const nodes = await comfyPage.getNodeRefsByType(
|
||||
'DevToolsLoadAnimatedImageTest'
|
||||
)
|
||||
const loadAnimatedWebpNode = nodes[0]
|
||||
const { x, y } = await loadAnimatedWebpNode.getPosition()
|
||||
|
||||
// Drag and drop image file onto the load animated webp node
|
||||
await comfyPage.dragAndDropFile('animated_webp.webp', {
|
||||
dropPosition: { x, y }
|
||||
// Drag and drop image file onto the load animated webp node
|
||||
await comfyPage.dragAndDropFile('animated_webp.webp', {
|
||||
dropPosition: { x, y }
|
||||
})
|
||||
|
||||
// Expect the image preview to change automatically
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'animated_image_preview_drag_and_dropped.png'
|
||||
)
|
||||
|
||||
// Move mouse and click on canvas to trigger render
|
||||
await comfyPage.page.mouse.click(64, 64)
|
||||
|
||||
// Expect the image preview to change to the next frame of the animation
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'animated_image_preview_drag_and_dropped_next_frame.png'
|
||||
)
|
||||
})
|
||||
|
||||
// Expect the image preview to change automatically
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'animated_image_preview_drag_and_dropped.png'
|
||||
)
|
||||
test('Can drag-and-drop animated webp image', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_animated_webp')
|
||||
|
||||
// Move mouse and click on canvas to trigger render
|
||||
await comfyPage.page.mouse.click(64, 64)
|
||||
// Get position of the load animated webp node
|
||||
const nodes = await comfyPage.getNodeRefsByType(
|
||||
'DevToolsLoadAnimatedImageTest'
|
||||
)
|
||||
const loadAnimatedWebpNode = nodes[0]
|
||||
const { x, y } = await loadAnimatedWebpNode.getPosition()
|
||||
|
||||
// Expect the image preview to change to the next frame of the animation
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'animated_image_preview_drag_and_dropped_next_frame.png'
|
||||
)
|
||||
})
|
||||
// Drag and drop image file onto the load animated webp node
|
||||
await comfyPage.dragAndDropFile('animated_webp.webp', {
|
||||
dropPosition: { x, y },
|
||||
waitForUpload: true
|
||||
})
|
||||
|
||||
test('Can drag-and-drop animated webp image', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_animated_webp')
|
||||
|
||||
// Get position of the load animated webp node
|
||||
const nodes = await comfyPage.getNodeRefsByType(
|
||||
'DevToolsLoadAnimatedImageTest'
|
||||
)
|
||||
const loadAnimatedWebpNode = nodes[0]
|
||||
const { x, y } = await loadAnimatedWebpNode.getPosition()
|
||||
|
||||
// Drag and drop image file onto the load animated webp node
|
||||
await comfyPage.dragAndDropFile('animated_webp.webp', {
|
||||
dropPosition: { x, y },
|
||||
waitForUpload: true
|
||||
// Expect the filename combo value to be updated
|
||||
const fileComboWidget = await loadAnimatedWebpNode.getWidget(0)
|
||||
const filename = await fileComboWidget.getValue()
|
||||
expect(filename).toContain('animated_webp.webp')
|
||||
})
|
||||
|
||||
// Expect the filename combo value to be updated
|
||||
const fileComboWidget = await loadAnimatedWebpNode.getWidget(0)
|
||||
const filename = await fileComboWidget.getValue()
|
||||
expect(filename).toContain('animated_webp.webp')
|
||||
})
|
||||
test('Can preview saved animated webp image', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/save_animated_webp')
|
||||
|
||||
test('Can preview saved animated webp image', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/save_animated_webp')
|
||||
// Get position of the load animated webp node
|
||||
const loadNodes = await comfyPage.getNodeRefsByType(
|
||||
'DevToolsLoadAnimatedImageTest'
|
||||
)
|
||||
const loadAnimatedWebpNode = loadNodes[0]
|
||||
const { x, y } = await loadAnimatedWebpNode.getPosition()
|
||||
|
||||
// Get position of the load animated webp node
|
||||
const loadNodes = await comfyPage.getNodeRefsByType(
|
||||
'DevToolsLoadAnimatedImageTest'
|
||||
)
|
||||
const loadAnimatedWebpNode = loadNodes[0]
|
||||
const { x, y } = await loadAnimatedWebpNode.getPosition()
|
||||
// Drag and drop image file onto the load animated webp node
|
||||
await comfyPage.dragAndDropFile('animated_webp.webp', {
|
||||
dropPosition: { x, y }
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Drag and drop image file onto the load animated webp node
|
||||
await comfyPage.dragAndDropFile('animated_webp.webp', {
|
||||
dropPosition: { x, y }
|
||||
// Get the SaveAnimatedWEBP node
|
||||
const saveNodes = await comfyPage.getNodeRefsByType('SaveAnimatedWEBP')
|
||||
const saveAnimatedWebpNode = saveNodes[0]
|
||||
if (!saveAnimatedWebpNode)
|
||||
throw new Error('SaveAnimatedWEBP node not found')
|
||||
|
||||
// Simulate the graph executing
|
||||
await comfyPage.page.evaluate(
|
||||
([loadId, saveId]) => {
|
||||
// Set the output of the SaveAnimatedWEBP node to equal the loader node's image
|
||||
window['app'].nodeOutputs[saveId] = window['app'].nodeOutputs[loadId]
|
||||
app.canvas.setDirty(true)
|
||||
},
|
||||
[loadAnimatedWebpNode.id, saveAnimatedWebpNode.id]
|
||||
)
|
||||
await expect(
|
||||
comfyPage.page.locator('.dom-widget').locator('img')
|
||||
).toHaveCount(2)
|
||||
})
|
||||
await comfyPage.nextFrame()
|
||||
}
|
||||
)
|
||||
|
||||
// Get the SaveAnimatedWEBP node
|
||||
const saveNodes = await comfyPage.getNodeRefsByType('SaveAnimatedWEBP')
|
||||
const saveAnimatedWebpNode = saveNodes[0]
|
||||
if (!saveAnimatedWebpNode)
|
||||
throw new Error('SaveAnimatedWEBP node not found')
|
||||
|
||||
// Simulate the graph executing
|
||||
await comfyPage.page.evaluate(
|
||||
([loadId, saveId]) => {
|
||||
// Set the output of the SaveAnimatedWEBP node to equal the loader node's image
|
||||
window['app'].nodeOutputs[saveId] = window['app'].nodeOutputs[loadId]
|
||||
app.canvas.setDirty(true)
|
||||
},
|
||||
[loadAnimatedWebpNode.id, saveAnimatedWebpNode.id]
|
||||
)
|
||||
await expect(
|
||||
comfyPage.page.locator('.dom-widget').locator('img')
|
||||
).toHaveCount(2)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Load audio widget', () => {
|
||||
test.describe('Load audio widget', { tag: ['@screenshot', '@widget'] }, () => {
|
||||
test('Can load audio', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('widgets/load_audio_widget')
|
||||
// Wait for the audio widget to be rendered in the DOM
|
||||
@@ -338,7 +348,7 @@ test.describe('Load audio widget', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Unserialized widgets', () => {
|
||||
test.describe('Unserialized widgets', { tag: '@widget' }, () => {
|
||||
test('Unserialized widgets values do not mark graph as modified', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { type ComfyPage, comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import type { ComfyPage } from '../fixtures/ComfyPage'
|
||||
|
||||
test.describe('Workflow Tab Thumbnails', () => {
|
||||
test.describe('Workflow Tab Thumbnails', { tag: '@workflow' }, () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
await comfyPage.setSetting('Comfy.Workflow.WorkflowTabsPosition', 'Topbar')
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import path from 'path'
|
||||
import type { Plugin } from 'vite'
|
||||
import type { Plugin } from 'vite-plus'
|
||||
|
||||
interface ShimResult {
|
||||
code: string
|
||||
@@ -9,7 +9,11 @@ interface ShimResult {
|
||||
const SKIP_WARNING_FILES = new Set(['scripts/app', 'scripts/api'])
|
||||
|
||||
/** Files that will be removed in v1.34 */
|
||||
const DEPRECATED_FILES = ['scripts/ui', 'extensions/core/groupNode'] as const
|
||||
const DEPRECATED_FILES = [
|
||||
'scripts/ui',
|
||||
'extensions/core/groupNode',
|
||||
'extensions/core/nodeTemplates'
|
||||
] as const
|
||||
|
||||
function getWarningMessage(
|
||||
fileKey: string,
|
||||
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
|
||||
'./**/*.{ts,tsx,vue,mts}': (stagedFiles: string[]) => [
|
||||
...formatAndEslint(stagedFiles),
|
||||
'pnpm typecheck'
|
||||
'vite run typecheck'
|
||||
]
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ function formatAndEslint(fileNames: string[]) {
|
||||
const relativePaths = fileNames.map((f) => path.relative(process.cwd(), f))
|
||||
const joinedPaths = relativePaths.map((p) => `"${p}"`).join(' ')
|
||||
return [
|
||||
`pnpm exec oxfmt --write ${joinedPaths}`,
|
||||
`pnpm exec oxlint --fix ${joinedPaths}`,
|
||||
`pnpm exec eslint --cache --fix --no-warn-ignored ${joinedPaths}`
|
||||
`vite fmt ${joinedPaths}`,
|
||||
`vite lint --fix ${joinedPaths}`,
|
||||
`vite dlx eslint --cache --fix --no-warn-ignored ${joinedPaths}`
|
||||
]
|
||||
}
|
||||
|
||||
25
package.json
25
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@comfyorg/comfyui-frontend",
|
||||
"version": "1.39.2",
|
||||
"version": "1.39.5",
|
||||
"private": true,
|
||||
"description": "Official front-end implementation of ComfyUI",
|
||||
"homepage": "https://comfy.org",
|
||||
@@ -22,20 +22,20 @@
|
||||
"dev:no-vue": "cross-env DISABLE_VUE_PLUGINS=true nx serve",
|
||||
"dev": "nx serve",
|
||||
"devtools:pycheck": "python3 -m compileall -q tools/devtools",
|
||||
"format:check": "oxfmt --check",
|
||||
"format": "oxfmt --write",
|
||||
"format:check": "vite fmt --check",
|
||||
"format": "vite fmt --write",
|
||||
"json-schema": "tsx scripts/generate-json-schema.ts",
|
||||
"knip:no-cache": "knip",
|
||||
"knip": "knip --cache",
|
||||
"lint:fix:no-cache": "oxlint src --type-aware --fix && eslint src --fix",
|
||||
"lint:fix": "oxlint src --type-aware --fix && eslint src --cache --fix",
|
||||
"lint:no-cache": "oxlint src --type-aware && eslint src",
|
||||
"lint:fix:no-cache": "vite lint src --type-aware --fix && eslint src --fix",
|
||||
"lint:fix": "vite lint src --type-aware --fix && eslint src --cache --fix",
|
||||
"lint:no-cache": "vite lint src --type-aware && eslint src",
|
||||
"lint:unstaged:fix": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache --fix",
|
||||
"lint:unstaged": "git diff --name-only HEAD | grep -E '\\.(js|ts|vue|mts)$' | xargs -r eslint --cache",
|
||||
"lint": "oxlint src --type-aware && eslint src --cache",
|
||||
"lint": "vite lint src --type-aware && eslint src --cache",
|
||||
"lint:desktop": "nx run @comfyorg/desktop-ui:lint",
|
||||
"locale": "lobe-i18n locale",
|
||||
"oxlint": "oxlint src --type-aware",
|
||||
"oxlint": "vite lint src --type-aware",
|
||||
"preinstall": "pnpm dlx only-allow pnpm",
|
||||
"prepare": "husky || true && git config blame.ignoreRevsFile .git-blame-ignore-revs || true",
|
||||
"preview": "nx preview",
|
||||
@@ -160,9 +160,6 @@
|
||||
"markdown-table": "catalog:",
|
||||
"mixpanel-browser": "catalog:",
|
||||
"nx": "catalog:",
|
||||
"oxfmt": "catalog:",
|
||||
"oxlint": "catalog:",
|
||||
"oxlint-tsgolint": "catalog:",
|
||||
"picocolors": "catalog:",
|
||||
"postcss-html": "catalog:",
|
||||
"pretty-bytes": "catalog:",
|
||||
@@ -183,6 +180,7 @@
|
||||
"vite-plugin-dts": "catalog:",
|
||||
"vite-plugin-html": "catalog:",
|
||||
"vite-plugin-vue-devtools": "catalog:",
|
||||
"vite-plus": "catalog:",
|
||||
"vitest": "catalog:",
|
||||
"vue-component-type-helpers": "catalog:",
|
||||
"vue-eslint-parser": "catalog:",
|
||||
@@ -190,9 +188,8 @@
|
||||
"zip-dir": "^2.0.0",
|
||||
"zod-to-json-schema": "catalog:"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.2",
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vite": "^8.0.0-beta.8"
|
||||
}
|
||||
"overrides": {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { describe, expect, it } from 'vite-plus/test'
|
||||
|
||||
import { getMediaTypeFromFilename, truncateFilename } from './formatUtil'
|
||||
|
||||
|
||||
1573
pnpm-lock.yaml
generated
1573
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -41,8 +41,8 @@ catalog:
|
||||
'@vitest/coverage-v8': ^4.0.16
|
||||
'@vitest/ui': ^4.0.16
|
||||
'@vue/test-utils': ^2.4.6
|
||||
'@vueuse/core': ^11.0.0
|
||||
'@vueuse/integrations': ^13.9.0
|
||||
'@vueuse/core': ^14.2.0
|
||||
'@vueuse/integrations': ^14.2.0
|
||||
'@webgpu/types': ^0.1.66
|
||||
algoliasearch: ^5.21.0
|
||||
axios: ^1.8.2
|
||||
@@ -62,16 +62,13 @@ catalog:
|
||||
happy-dom: ^20.0.11
|
||||
husky: ^9.1.7
|
||||
jiti: 2.6.1
|
||||
jsonata: ^2.1.0
|
||||
jsdom: ^27.4.0
|
||||
jsonata: ^2.1.0
|
||||
knip: ^5.75.1
|
||||
lint-staged: ^16.2.7
|
||||
markdown-table: ^3.0.4
|
||||
mixpanel-browser: ^2.71.0
|
||||
nx: 22.2.6
|
||||
oxfmt: ^0.26.0
|
||||
oxlint: ^1.33.0
|
||||
oxlint-tsgolint: ^0.9.1
|
||||
picocolors: ^1.1.1
|
||||
pinia: ^3.0.4
|
||||
postcss-html: ^1.8.0
|
||||
@@ -92,11 +89,11 @@ catalog:
|
||||
unplugin-icons: ^22.5.0
|
||||
unplugin-typegpu: 0.8.0
|
||||
unplugin-vue-components: ^30.0.0
|
||||
vite: ^8.0.0-beta.8
|
||||
vite: npm:@voidzero-dev/vite-plus-core@latest
|
||||
vite-plugin-dts: ^4.5.4
|
||||
vite-plugin-html: ^3.2.2
|
||||
vite-plugin-vue-devtools: ^8.0.0
|
||||
vitest: ^4.0.16
|
||||
vitest: npm:@voidzero-dev/vite-plus-test@latest
|
||||
vue: ^3.5.13
|
||||
vue-component-type-helpers: ^3.2.1
|
||||
vue-eslint-parser: ^10.2.0
|
||||
@@ -109,6 +106,7 @@ catalog:
|
||||
zod: ^3.23.8
|
||||
zod-to-json-schema: ^3.24.1
|
||||
zod-validation-error: ^3.3.0
|
||||
vite-plus: latest
|
||||
|
||||
cleanupUnusedCatalogs: true
|
||||
|
||||
@@ -130,3 +128,12 @@ onlyBuiltDependencies:
|
||||
|
||||
overrides:
|
||||
'@types/eslint': '-'
|
||||
vite: 'catalog:'
|
||||
vitest: 'catalog:'
|
||||
peerDependencyRules:
|
||||
allowAny:
|
||||
- vite
|
||||
- vitest
|
||||
allowedVersions:
|
||||
vite: '*'
|
||||
vitest: '*'
|
||||
|
||||
@@ -46,6 +46,8 @@ onMounted(() => {
|
||||
document.addEventListener('contextmenu', showContextMenu)
|
||||
}
|
||||
|
||||
// Handle preload errors that occur during dynamic imports (e.g., stale chunks after deployment)
|
||||
// See: https://vite.dev/guide/build#load-error-handling
|
||||
window.addEventListener('vite:preloadError', (event) => {
|
||||
event.preventDefault()
|
||||
// eslint-disable-next-line no-undef
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
|
||||
import { downloadFile } from '@/base/common/downloadUtil'
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test } from 'vitest'
|
||||
import { describe, expect, test } from 'vite-plus/test'
|
||||
|
||||
import {
|
||||
CREDITS_PER_USD,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import type { MenuItem } from 'primevue/menuitem'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { computed, defineComponent, h, nextTick, onMounted } from 'vue'
|
||||
import type { Component } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import WorkspaceAuthGate from './WorkspaceAuthGate.vue'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { createPinia, setActivePinia } from 'pinia'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
|
||||
import EssentialsPanel from '@/components/bottomPanel/tabs/shortcuts/EssentialsPanel.vue'
|
||||
import ShortcutsList from '@/components/bottomPanel/tabs/shortcuts/ShortcutsList.vue'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { describe, expect, it, vi } from 'vite-plus/test'
|
||||
|
||||
import ShortcutsList from '@/components/bottomPanel/tabs/shortcuts/ShortcutsList.vue'
|
||||
import type { ComfyCommandImpl } from '@/stores/commandStore'
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createTestingPinia } from '@pinia/testing'
|
||||
import type { VueWrapper } from '@vue/test-utils'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import type { Mock } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { Mock } from 'vite-plus/test'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mount } from '@vue/test-utils'
|
||||
import ColorPicker from 'primevue/colorpicker'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import SelectButton from 'primevue/selectbutton'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it } from 'vite-plus/test'
|
||||
import { createApp, nextTick } from 'vue'
|
||||
|
||||
import ColorCustomizationSelector from './ColorCustomizationSelector.vue'
|
||||
|
||||
@@ -38,7 +38,10 @@ const {
|
||||
alt = 'Image content'
|
||||
} = defineProps<{
|
||||
src: string
|
||||
class?: any
|
||||
class?:
|
||||
| string
|
||||
| Record<string, boolean>
|
||||
| (string | Record<string, boolean>)[]
|
||||
contain?: boolean
|
||||
alt?: string
|
||||
}>()
|
||||
|
||||
@@ -28,13 +28,17 @@ const deviceColumns: { field: keyof DeviceStats; header: string }[] = [
|
||||
{ field: 'torch_vram_free', header: 'Torch VRAM Free' }
|
||||
]
|
||||
|
||||
const formatValue = (value: any, field: string) => {
|
||||
const formatValue = (value: string | number, field: string) => {
|
||||
if (
|
||||
['vram_total', 'vram_free', 'torch_vram_total', 'torch_vram_free'].includes(
|
||||
field
|
||||
)
|
||||
) {
|
||||
return formatSize(value)
|
||||
const num = Number(value)
|
||||
if (Number.isFinite(num)) {
|
||||
return formatSize(num)
|
||||
}
|
||||
return value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { beforeAll, describe, expect, it } from 'vitest'
|
||||
import { beforeAll, describe, expect, it } from 'vite-plus/test'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import EditableText from './EditableText.vue'
|
||||
|
||||
@@ -47,7 +47,7 @@ import InputSlider from '@/components/common/InputSlider.vue'
|
||||
import UrlInput from '@/components/common/UrlInput.vue'
|
||||
import type { FormItem } from '@/platform/settings/types'
|
||||
|
||||
const formValue = defineModel<any>('formValue')
|
||||
const formValue = defineModel<unknown>('formValue')
|
||||
const props = defineProps<{
|
||||
item: FormItem
|
||||
id?: string
|
||||
@@ -61,7 +61,7 @@ function getFormAttrs(item: FormItem) {
|
||||
attrs['renderFunction'] = () =>
|
||||
inputType(
|
||||
props.item.name,
|
||||
(v: any) => (formValue.value = v),
|
||||
(v: unknown) => (formValue.value = v),
|
||||
formValue.value,
|
||||
item.attrs
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import RadioButton from 'primevue/radiobutton'
|
||||
import { beforeAll, describe, expect, it } from 'vitest'
|
||||
import { beforeAll, describe, expect, it } from 'vite-plus/test'
|
||||
import { createApp } from 'vue'
|
||||
|
||||
import type { SettingOption } from '@/platform/settings/types'
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
<script setup lang="ts" generic="T extends string | number | boolean | null">
|
||||
import RadioButton from 'primevue/radiobutton'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import type { SettingOption } from '@/platform/settings/types'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: any
|
||||
modelValue: T
|
||||
options?: (string | SettingOption | Record<string, string>)[]
|
||||
optionLabel?: string
|
||||
optionValue?: string
|
||||
@@ -35,7 +35,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
|
||||
defineEmits<{
|
||||
'update:modelValue': [value: any]
|
||||
'update:modelValue': [value: T]
|
||||
}>()
|
||||
|
||||
const normalizedOptions = computed<SettingOption[]>(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { mount } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -221,7 +221,7 @@ const wrapCommandWithErrorHandler = (
|
||||
) => {
|
||||
return isAsync
|
||||
? errorHandling.wrapWithErrorHandlingAsync(
|
||||
command as (...args: any[]) => Promise<any>,
|
||||
command as (event: MenuItemCommandEvent) => Promise<void>,
|
||||
menuTargetNode.value?.handleError
|
||||
)
|
||||
: errorHandling.wrapWithErrorHandling(
|
||||
|
||||
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils'
|
||||
import Badge from 'primevue/badge'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest'
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vite-plus/test'
|
||||
import { createApp } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import PrimeVue from 'primevue/config'
|
||||
import IconField from 'primevue/iconfield'
|
||||
import InputIcon from 'primevue/inputicon'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it } from 'vite-plus/test'
|
||||
import { createApp, nextTick } from 'vue'
|
||||
|
||||
import UrlInput from './UrlInput.vue'
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { ComponentProps } from 'vue-component-type-helpers'
|
||||
import { mount } from '@vue/test-utils'
|
||||
import Avatar from 'primevue/avatar'
|
||||
import PrimeVue from 'primevue/config'
|
||||
import { beforeEach, describe, expect, it } from 'vitest'
|
||||
import { beforeEach, describe, expect, it } from 'vite-plus/test'
|
||||
import { createApp, nextTick } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user