Compare commits
100 Commits
vue-widget
...
feat/impor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
97547434b0 | ||
|
|
c7877dbd18 | ||
|
|
4cbcded820 | ||
|
|
469594e5cc | ||
|
|
191b4574b9 | ||
|
|
0c4339f652 | ||
|
|
35556eb674 | ||
|
|
92b65ca00e | ||
|
|
8f4e807468 | ||
|
|
c1db367422 | ||
|
|
3b435e337e | ||
|
|
ee5088551e | ||
|
|
44bbfa9f39 | ||
|
|
1b4ad61e7f | ||
|
|
7befec5b17 | ||
|
|
a51c09893f | ||
|
|
f290c00a61 | ||
|
|
a45753486d | ||
|
|
5cc1a8dea2 | ||
|
|
959ab3b3ec | ||
|
|
35ff882ff2 | ||
|
|
f57f97cfcd | ||
|
|
8f825c066b | ||
|
|
e6f90e3101 | ||
|
|
d68391a80a | ||
|
|
df710945c9 | ||
|
|
8d6360074d | ||
|
|
26c106c3e4 | ||
|
|
d92c282439 | ||
|
|
bf3dcc83a0 | ||
|
|
6470a0bbd9 | ||
|
|
c75015c5b8 | ||
|
|
64a2a5b3ae | ||
|
|
fada8bf9cf | ||
|
|
5bbed91295 | ||
|
|
eb8b67dd9d | ||
|
|
d6a8f98327 | ||
|
|
8457768a41 | ||
|
|
3ae7faa8c5 | ||
|
|
a7fb685290 | ||
|
|
7620bb9063 | ||
|
|
2d2cec2e79 | ||
|
|
c2ae40bab5 | ||
|
|
10fbf7e847 | ||
|
|
0bbfc44bc7 | ||
|
|
a9b7ed2a53 | ||
|
|
35ee8f2d92 | ||
|
|
9a3530dc3a | ||
|
|
4c177121a6 | ||
|
|
63181a1ddd | ||
|
|
e17ca7ce71 | ||
|
|
77d2cae301 | ||
|
|
164a4c4c25 | ||
|
|
47145ce4b8 | ||
|
|
6cf77a9814 | ||
|
|
886e4908d4 | ||
|
|
24cbc41832 | ||
|
|
a80a939324 | ||
|
|
8e2d7cabba | ||
|
|
e8dd26ff59 | ||
|
|
3a1bd1829a | ||
|
|
2f9dcd1669 | ||
|
|
e23547dd5a | ||
|
|
f0f40bc39b | ||
|
|
4b32786ef5 | ||
|
|
9942b17388 | ||
|
|
b99214bf5e | ||
|
|
2ef760c599 | ||
|
|
429ab6c365 | ||
|
|
b7693ae9f5 | ||
|
|
ebedf1074d | ||
|
|
0832347f47 | ||
|
|
c745af0f25 | ||
|
|
8c05266b83 | ||
|
|
fa14ec52f4 | ||
|
|
ec9da0b6c5 | ||
|
|
98bb1df436 | ||
|
|
75077fe9ed | ||
|
|
d5ecfb2c99 | ||
|
|
3211875084 | ||
|
|
a6bd04f951 | ||
|
|
5b32d2aad0 | ||
|
|
23ba7e6501 | ||
|
|
1e2b16f14d | ||
|
|
ec27d50333 | ||
|
|
693e156ab2 | ||
|
|
8274df5075 | ||
|
|
55bf36564d | ||
|
|
48ac4a2b36 | ||
|
|
c9c1275e4c | ||
|
|
78ebc54ebe | ||
|
|
88f2cc7847 | ||
|
|
7907e206da | ||
|
|
c4fa3dfe5a | ||
|
|
587d7a19a1 | ||
|
|
9ca705381c | ||
|
|
a937ac59ad | ||
|
|
995979a4e1 | ||
|
|
c02ac95815 | ||
|
|
d01926b043 |
@@ -1,111 +0,0 @@
|
||||
Apply performance monitoring concepts from performance-test-guide.md to the specified test file: $ARGUMENTS
|
||||
|
||||
## Task Overview
|
||||
Transform browser tests to include performance monitoring for canvas, node, and widget operations following the established performance testing patterns.
|
||||
|
||||
## Instructions
|
||||
|
||||
<analysis_phase>
|
||||
1. **Read the target test file** specified in $ARGUMENTS
|
||||
2. **Analyze test operations** to identify which ones should have performance monitoring based on the guide criteria:
|
||||
- ✅ **Monitor**: Node operations, widget interactions, canvas operations, graph operations, background operations
|
||||
- ❌ **Skip**: UI chrome elements, dialogs/modals, floating menus, gallery/template views
|
||||
3. **Review existing test structure** to understand the test flow and key operations
|
||||
</analysis_phase>
|
||||
|
||||
<implementation_phase>
|
||||
4. **Add performance monitoring** following these steps:
|
||||
|
||||
**a. Import and setup:**
|
||||
- Add `import { PerformanceMonitor } from '../helpers/performanceMonitor'`
|
||||
- Add `@perf` tag to test name
|
||||
- Initialize PerformanceMonitor with `comfyPage.page`
|
||||
- Create descriptive kebab-case test name
|
||||
- Call `startMonitoring(testName)`
|
||||
|
||||
**b. Wrap appropriate operations:**
|
||||
- Use `measureOperation()` for node operations (creating, selecting, dragging, copying, deleting)
|
||||
- Use `measureOperation()` for widget interactions (input changes, clicks, value modifications)
|
||||
- Use `measureOperation()` for canvas operations (panning, zooming, selections, connections)
|
||||
- Use `measureOperation()` for graph operations (loading workflows, undo/redo, batch operations)
|
||||
- Use `markEvent()` for logical boundaries and state transitions
|
||||
- Group related operations when they represent a single user action
|
||||
- Keep assertions and expectations outside performance measurements
|
||||
|
||||
**c. Apply appropriate patterns:**
|
||||
- **User Interaction Sequence**: Separate click, type, submit operations
|
||||
- **Copy/Paste Operations**: Separate select, copy, paste with before/after marks
|
||||
- **Drag Operations**: Separate start-drag, drag-to-position, drop
|
||||
|
||||
**d. Finalize:**
|
||||
- Call `finishMonitoring(testName)` at the end
|
||||
- Ensure all async operations are properly wrapped
|
||||
</implementation_phase>
|
||||
|
||||
<naming_conventions>
|
||||
- **Test names**: kebab-case, descriptive (e.g., 'copy-paste-multiple-nodes')
|
||||
- **Operation names**: kebab-case, action-focused (e.g., 'click-node', 'drag-to-position')
|
||||
- **Event marks**: kebab-case, state-focused (e.g., 'before-paste', 'after-render')
|
||||
</naming_conventions>
|
||||
|
||||
<quality_guidelines>
|
||||
- **Balance granularity**: Don't wrap every line, focus on meaningful operations
|
||||
- **Maintain readability**: Wrapped code should remain clear and understandable
|
||||
- **Preserve test logic**: Don't change test functionality, only add monitoring
|
||||
- **Keep consistency**: Use similar operation names across similar tests
|
||||
- **Group intelligently**: Combine related operations that represent single user actions
|
||||
</quality_guidelines>
|
||||
|
||||
## Expected Output
|
||||
|
||||
Transform the test file to include:
|
||||
1. Performance monitor import and initialization
|
||||
2. `@perf` tag in test name
|
||||
3. Appropriate `measureOperation()` wrapping for qualifying operations
|
||||
4. `markEvent()` calls for logical boundaries
|
||||
5. `finishMonitoring()` call at the end
|
||||
6. Preserved test assertions and expectations outside performance measurements
|
||||
|
||||
Show the complete transformed test file with clear before/after comparison if the changes are substantial.
|
||||
|
||||
## Example Transformation Reference
|
||||
|
||||
Follow this pattern for transformation:
|
||||
|
||||
**Before:**
|
||||
```typescript
|
||||
test('Can copy and paste node', async ({ comfyPage }) => {
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
await comfyPage.ctrlC()
|
||||
await comfyPage.ctrlV()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-node.png')
|
||||
})
|
||||
```
|
||||
|
||||
**After:**
|
||||
```typescript
|
||||
test('@perf Can copy and paste node', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-node'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('click-node', async () => {
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('copy-node', async () => {
|
||||
await comfyPage.ctrlC()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-node', async () => {
|
||||
await comfyPage.ctrlV()
|
||||
})
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-node.png')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
```
|
||||
|
||||
Now apply these concepts to the test file: $ARGUMENTS
|
||||
@@ -1,53 +0,0 @@
|
||||
# Create a Vue Widget for ComfyUI
|
||||
|
||||
Your task is to create a new Vue widget for ComfyUI based on the widget specification: $ARGUMENTS
|
||||
|
||||
## Instructions
|
||||
|
||||
Follow the comprehensive guide in `vue-widget-conversion/vue-widget-guide.md` to create the widget. This guide contains step-by-step instructions, examples from actual PRs, and best practices.
|
||||
|
||||
### Key Steps to Follow:
|
||||
|
||||
1. **Understand the Widget Type**
|
||||
- Analyze what type of widget is needed: $ARGUMENTS
|
||||
- Identify the data type (string, number, array, object, etc.)
|
||||
- Determine if it needs special behaviors (execution state awareness, dynamic management, etc.)
|
||||
|
||||
2. **Component Creation**
|
||||
- Create Vue component in `src/components/graph/widgets/`
|
||||
- REQUIRED: Use PrimeVue components (reference `vue-widget-conversion/primevue-components.md`)
|
||||
- Use Composition API with `<script setup>`
|
||||
- Implement proper v-model binding with `defineModel`
|
||||
|
||||
3. **Composable Pattern**
|
||||
- Always create widget constructor composable in `src/composables/widgets/`
|
||||
- Only create node-level composable in `src/composables/node/` if the widget needs dynamic management
|
||||
- Follow the dual composable pattern explained in the guide
|
||||
|
||||
4. **Registration**
|
||||
- Register in `src/scripts/widgets.ts`
|
||||
- Use appropriate widget type name
|
||||
|
||||
5. **Testing**
|
||||
- Create unit tests for composables
|
||||
- Test with actual nodes that use the widget
|
||||
|
||||
### Important Requirements:
|
||||
|
||||
- **Always use PrimeVue components** - Check `vue-widget-conversion/primevue-components.md` for available components
|
||||
- Use TypeScript with proper types
|
||||
- Follow Vue 3 Composition API patterns
|
||||
- Use Tailwind CSS for styling (no custom CSS unless absolutely necessary)
|
||||
- Implement proper error handling and validation
|
||||
- Consider performance (use v-show vs v-if appropriately)
|
||||
|
||||
### Before Starting:
|
||||
|
||||
1. First read through the entire guide at `vue-widget-conversion/vue-widget-guide.md`
|
||||
2. Check existing widget implementations for similar patterns
|
||||
3. Identify which PrimeVue component(s) best fit the widget requirements
|
||||
|
||||
### Widget Specification to Implement:
|
||||
$ARGUMENTS
|
||||
|
||||
Begin by analyzing the widget requirements and proposing an implementation plan based on the guide.
|
||||
6
.github/workflows/i18n.yaml
vendored
@@ -3,6 +3,12 @@ name: Update Locales
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ main, master, dev* ]
|
||||
paths-ignore:
|
||||
- '.github/**'
|
||||
- '.husky/**'
|
||||
- '.vscode/**'
|
||||
- 'browser_tests/**'
|
||||
- 'tests-ui/**'
|
||||
|
||||
jobs:
|
||||
update-locales:
|
||||
|
||||
16
.github/workflows/release.yaml
vendored
@@ -15,6 +15,7 @@ jobs:
|
||||
contains(github.event.pull_request.labels.*.name, 'Release')
|
||||
outputs:
|
||||
version: ${{ steps.current_version.outputs.version }}
|
||||
is_prerelease: ${{ steps.check_prerelease.outputs.is_prerelease }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
@@ -24,6 +25,15 @@ jobs:
|
||||
- name: Get current version
|
||||
id: current_version
|
||||
run: echo "version=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT
|
||||
- name: Check if prerelease
|
||||
id: check_prerelease
|
||||
run: |
|
||||
VERSION=${{ steps.current_version.outputs.version }}
|
||||
if [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+- ]]; then
|
||||
echo "is_prerelease=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "is_prerelease=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
- name: Build project
|
||||
env:
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
@@ -62,9 +72,9 @@ jobs:
|
||||
dist.zip
|
||||
tag_name: v${{ needs.build.outputs.version }}
|
||||
target_commitish: ${{ github.event.pull_request.base.ref }}
|
||||
make_latest: ${{ github.event.pull_request.base.ref == 'main' }}
|
||||
draft: ${{ github.event.pull_request.base.ref != 'main' }}
|
||||
prerelease: false
|
||||
make_latest: ${{ github.event.pull_request.base.ref == 'main' && needs.build.outputs.is_prerelease == 'false' }}
|
||||
draft: ${{ github.event.pull_request.base.ref != 'main' || needs.build.outputs.is_prerelease == 'true' }}
|
||||
prerelease: ${{ needs.build.outputs.is_prerelease == 'true' }}
|
||||
generate_release_notes: true
|
||||
|
||||
publish_pypi:
|
||||
|
||||
10
.github/workflows/test-ui.yaml
vendored
@@ -46,8 +46,8 @@ jobs:
|
||||
id: cache-key
|
||||
run: echo "key=$(date +%s)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cache setup
|
||||
uses: actions/cache@v3
|
||||
- name: Save cache
|
||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
with:
|
||||
path: |
|
||||
ComfyUI
|
||||
@@ -62,9 +62,13 @@ jobs:
|
||||
matrix:
|
||||
browser: [chromium, chromium-2x, mobile-chrome]
|
||||
steps:
|
||||
- name: Wait for cache propagation
|
||||
run: sleep 10
|
||||
|
||||
- name: Restore cached setup
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684
|
||||
with:
|
||||
fail-on-cache-miss: true
|
||||
path: |
|
||||
ComfyUI
|
||||
ComfyUI_frontend
|
||||
|
||||
26
.github/workflows/version-bump.yaml
vendored
@@ -8,10 +8,12 @@ on:
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: 'choice'
|
||||
options:
|
||||
- patch
|
||||
- minor
|
||||
- major
|
||||
options: [patch, minor, major, prepatch, preminor, premajor, prerelease]
|
||||
pre_release:
|
||||
description: Pre-release ID (suffix)
|
||||
required: false
|
||||
default: ''
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
bump-version:
|
||||
@@ -33,19 +35,25 @@ jobs:
|
||||
- name: Bump version
|
||||
id: bump-version
|
||||
run: |
|
||||
npm version ${{ github.event.inputs.version_type }} --no-git-tag-version
|
||||
npm version ${{ github.event.inputs.version_type }} --preid ${{ github.event.inputs.pre_release }} --no-git-tag-version
|
||||
NEW_VERSION=$(node -p "require('./package.json').version")
|
||||
echo "NEW_VERSION=$NEW_VERSION" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Format PR string
|
||||
id: capitalised
|
||||
run: |
|
||||
CAPITALISED_TYPE=${{ github.event.inputs.version_type }}
|
||||
echo "capitalised=${CAPITALISED_TYPE@u}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
|
||||
with:
|
||||
token: ${{ secrets.PR_GH_TOKEN }}
|
||||
commit-message: '[release] Bump version to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
||||
title: '${{ steps.bump-version.outputs.NEW_VERSION }}'
|
||||
commit-message: '[release] Increment version to ${{ steps.bump-version.outputs.NEW_VERSION }}'
|
||||
title: ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
body: |
|
||||
Automated version bump to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
${{ steps.capitalised.outputs.capitalised }} version increment to ${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
branch: version-bump-${{ steps.bump-version.outputs.NEW_VERSION }}
|
||||
base: main
|
||||
labels: |
|
||||
Release
|
||||
Release
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
if [[ "$OS" == "Windows_NT" ]]; then
|
||||
npx.cmd lint-staged
|
||||
# Check for unused i18n keys in staged files
|
||||
npx.cmd tsx scripts/check-unused-i18n-keys.ts
|
||||
else
|
||||
npx lint-staged
|
||||
# Check for unused i18n keys in staged files
|
||||
npx tsx scripts/check-unused-i18n-keys.ts
|
||||
fi
|
||||
|
||||
14
CLAUDE.md
@@ -1,8 +1,9 @@
|
||||
- use npm run to see what commands are available
|
||||
- use `npm run` to see what commands are available
|
||||
- For component communication, prefer Vue's event-based pattern (emit/@event-name) for state changes and notifications; use defineExpose with refs only for imperative operations that need direct control (like form.validate(), modal.open(), or editor.focus()); events promote loose coupling and are better for reusable components, while exposed methods are acceptable for tightly-coupled component pairs or when wrapping third-party libraries that require imperative APIs
|
||||
- After making code changes, follow this general process: (1) Create unit tests, component tests, browser tests (if appropriate for each), (2) run unit tests, component tests, and browser tests until passing, (3) run typecheck, lint, format (with prettier) -- you can use `npm run` command to see the scripts available, (4) check if any READMEs (including nested) or documentation needs to be updated, (5) Decide whether the changes are worth adding new content to the external documentation for (or would requires changes to the external documentation) at https://docs.comfy.org, then present your suggestion
|
||||
- When referencing PrimeVue, you can get all the docs here: https://primevue.org. Do this instead of making up or inferring names of Components
|
||||
- When trying to set tailwind classes for dark theme, use "dark-theme:" prefix rather than "dark:"
|
||||
- Never add lines to PR descriptions that say "Generated with Claude Code"
|
||||
- Never add lines to PR descriptions or commit messages that say "Generated with Claude Code"
|
||||
- When making PR names and commit messages, if you are going to add a prefix like "docs:", "feat:", "bugfix:", use square brackets around the prefix term and do not use a colon (e.g., should be "[docs]" rather than "docs:").
|
||||
- When I reference GitHub Repos related to Comfy-Org, you should proactively fetch or read the associated information in the repo. To do so, you should exhaust all options: (1) Check if we have a local copy of the repo, (2) Use the GitHub API to fetch the information; you may want to do this IN ADDITION to the other options, especially for reading specific branches/PRs/comments/reviews/metadata, and (3) curl the GitHub website and parse the html or json responses
|
||||
- For information about ComfyUI, ComfyUI_frontend, or ComfyUI-Manager, you can web search or download these wikis: https://deepwiki.com/Comfy-Org/ComfyUI-Manager, https://deepwiki.com/Comfy-Org/ComfyUI_frontend/1-overview, https://deepwiki.com/comfyanonymous/ComfyUI/2-core-architecture
|
||||
@@ -17,7 +18,6 @@
|
||||
- Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
|
||||
- when we are solving an issue we know the link/number for, we should add "Fixes #n" (where n is the issue number) to the PR description.
|
||||
- Never write css if you can accomplish the same thing with tailwind utility classes
|
||||
- Use setup() function for component logic
|
||||
- Utilize ref and reactive for reactive state
|
||||
- Implement computed properties with computed()
|
||||
- Use watch and watchEffect for side effects
|
||||
@@ -27,14 +27,12 @@
|
||||
- Use Tailwind CSS for styling
|
||||
- Leverage VueUse functions for performance-enhancing styles
|
||||
- Use lodash for utility functions
|
||||
- Use TypeScript for type safety
|
||||
- Implement proper props and emits definitions
|
||||
- Utilize Vue 3's Teleport component when needed
|
||||
- Use Suspense for async components
|
||||
- Implement proper error handling
|
||||
- Follow Vue 3 style guide and naming conventions
|
||||
- Use Vite for fast development and building
|
||||
- Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json.
|
||||
- IMPORTANT: Use vue-i18n for ALL user-facing strings - no hard-coded text in services/utilities. Place new translation entries in src/locales/en/main.json
|
||||
- Avoid using `@ts-expect-error` to work around type issues. We needed to employ it to migrate to TypeScript, but it should not be viewed as an accepted practice or standard.
|
||||
- DO NOT use deprecated PrimeVue components. Use these replacements instead:
|
||||
* `Dropdown` → Use `Select` (import from 'primevue/select')
|
||||
@@ -54,3 +52,7 @@
|
||||
- Templates: `api.fileURL('/templates/default.json')`
|
||||
- Extensions: `api.fileURL(extensionPath)` for loading JS modules
|
||||
- Any static assets that exist in the public directory
|
||||
- When implementing code that outputs raw HTML (e.g., using v-html directive), always ensure dynamic content has been properly sanitized with DOMPurify or validated through trusted sources. Prefer Vue templates over v-html when possible.
|
||||
- For any async operations (API calls, timers, etc), implement cleanup/cancellation in component unmount to prevent memory leaks
|
||||
- Extract complex template conditionals into separate components or computed properties
|
||||
- Error messages should be actionable and user-friendly (e.g., "Failed to load data. Please refresh the page." instead of "Unknown error")
|
||||
|
||||
@@ -29,6 +29,16 @@ A template with helpful information can be found in `.env_example`.
|
||||
### Multiple Tests
|
||||
If you are running Playwright tests in parallel or running the same test multiple times, the flag `--multi-user` must be added to the main ComfyUI process.
|
||||
|
||||
### Release API Mocking
|
||||
By default, all tests mock the release API (`api.comfy.org/releases`) to prevent release notification popups from interfering with test execution. This is necessary because the release notifications can appear over UI elements and block test interactions.
|
||||
|
||||
To test with real release data, you can disable mocking:
|
||||
```typescript
|
||||
await comfyPage.setup({ mockReleases: false });
|
||||
```
|
||||
|
||||
For tests that specifically need to test release functionality, see the example in `tests/releaseNotifications.spec.ts`.
|
||||
|
||||
## Running Tests
|
||||
|
||||
There are multiple ways to run the tests:
|
||||
|
||||
@@ -10,7 +10,6 @@ import type { KeyCombo } from '../../src/schemas/keyBindingSchema'
|
||||
import type { useWorkspaceStore } from '../../src/stores/workspaceStore'
|
||||
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
||||
import { ComfyActionbar } from '../helpers/actionbar'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
import { ComfyTemplates } from '../helpers/templates'
|
||||
import { ComfyMouse } from './ComfyMouse'
|
||||
import { ComfyNodeSearchBox } from './components/ComfyNodeSearchBox'
|
||||
@@ -144,7 +143,6 @@ export class ComfyPage {
|
||||
public readonly templates: ComfyTemplates
|
||||
public readonly settingDialog: SettingDialog
|
||||
public readonly confirmDialog: ConfirmDialog
|
||||
public readonly performanceMonitor: PerformanceMonitor
|
||||
|
||||
/** Worker index to test user ID */
|
||||
public readonly userIds: string[] = []
|
||||
@@ -172,7 +170,6 @@ export class ComfyPage {
|
||||
this.templates = new ComfyTemplates(page)
|
||||
this.settingDialog = new SettingDialog(page)
|
||||
this.confirmDialog = new ConfirmDialog(page)
|
||||
this.performanceMonitor = new PerformanceMonitor(page)
|
||||
}
|
||||
|
||||
convertLeafToContent(structure: FolderStructure): FolderStructure {
|
||||
@@ -271,8 +268,34 @@ export class ComfyPage {
|
||||
return this._history
|
||||
}
|
||||
|
||||
async setup({ clearStorage = true }: { clearStorage?: boolean } = {}) {
|
||||
async setup({
|
||||
clearStorage = true,
|
||||
mockReleases = true
|
||||
}: {
|
||||
clearStorage?: boolean
|
||||
mockReleases?: boolean
|
||||
} = {}) {
|
||||
await this.goto()
|
||||
|
||||
// Mock release endpoint to prevent changelog popups
|
||||
if (mockReleases) {
|
||||
await this.page.route('**/releases**', async (route) => {
|
||||
const url = route.request().url()
|
||||
if (
|
||||
url.includes('api.comfy.org') ||
|
||||
url.includes('stagingapi.comfy.org')
|
||||
) {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: 'application/json',
|
||||
body: JSON.stringify([])
|
||||
})
|
||||
} else {
|
||||
await route.continue()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (clearStorage) {
|
||||
await this.page.evaluate((id) => {
|
||||
localStorage.clear()
|
||||
@@ -1049,6 +1072,8 @@ export class ComfyPage {
|
||||
}
|
||||
}
|
||||
|
||||
export const testComfySnapToGridGridSize = 50
|
||||
|
||||
export const comfyPageFixture = base.extend<{
|
||||
comfyPage: ComfyPage
|
||||
comfyMouse: ComfyMouse
|
||||
@@ -1061,14 +1086,6 @@ export const comfyPageFixture = base.extend<{
|
||||
const userId = await comfyPage.setupUser(username)
|
||||
comfyPage.userIds[parallelIndex] = userId
|
||||
|
||||
// Enable performance monitoring for @perf tagged tests
|
||||
const isPerformanceTest = testInfo.title.includes('@perf')
|
||||
// console.log('test info', testInfo)
|
||||
if (isPerformanceTest) {
|
||||
console.log('Enabling performance monitoring')
|
||||
// PerformanceMonitor.enable()
|
||||
}
|
||||
|
||||
try {
|
||||
await comfyPage.setupSettings({
|
||||
'Comfy.UseNewMenu': 'Disabled',
|
||||
@@ -1083,30 +1100,19 @@ export const comfyPageFixture = base.extend<{
|
||||
'Comfy.EnableTooltips': false,
|
||||
'Comfy.userId': userId,
|
||||
// Set tutorial completed to true to avoid loading the tutorial workflow.
|
||||
'Comfy.TutorialCompleted': true
|
||||
'Comfy.TutorialCompleted': true,
|
||||
'Comfy.SnapToGrid.GridSize': testComfySnapToGridGridSize
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
if (isPerformanceTest) {
|
||||
// Start performance monitoring just before test execution
|
||||
console.log('Starting performance monitoring')
|
||||
await comfyPage.performanceMonitor.startMonitoring(testInfo.title)
|
||||
}
|
||||
|
||||
await comfyPage.setup()
|
||||
await use(comfyPage)
|
||||
|
||||
// Cleanup performance monitoring and collect final metrics
|
||||
if (isPerformanceTest) {
|
||||
console.log('Finishing performance monitoring')
|
||||
await comfyPage.performanceMonitor.finishMonitoring(testInfo.title)
|
||||
}
|
||||
},
|
||||
comfyMouse: async ({ comfyPage }, use) => {
|
||||
const comfyMouse = new ComfyMouse(comfyPage)
|
||||
void use(comfyMouse)
|
||||
await use(comfyMouse)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,30 +1,13 @@
|
||||
import { FullConfig } from '@playwright/test'
|
||||
import dotenv from 'dotenv'
|
||||
|
||||
import { PerformanceMonitor } from './helpers/performanceMonitor'
|
||||
import { restorePath } from './utils/backupUtils'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
export default async function globalTeardown(config: FullConfig) {
|
||||
console.log('🧹 Global teardown starting...')
|
||||
|
||||
// Always try to save performance metrics (handles temp files from workers)
|
||||
try {
|
||||
const filePath = await PerformanceMonitor.saveMetricsToFile()
|
||||
console.log(`✅ Performance metrics saved successfully to: ${filePath}`)
|
||||
} catch (error) {
|
||||
console.error(
|
||||
'❌ Failed to save performance metrics in global teardown:',
|
||||
error
|
||||
)
|
||||
}
|
||||
|
||||
// Existing teardown logic
|
||||
export default function globalTeardown(config: FullConfig) {
|
||||
if (!process.env.CI && process.env.TEST_COMFYUI_DIR) {
|
||||
restorePath([process.env.TEST_COMFYUI_DIR, 'user'])
|
||||
restorePath([process.env.TEST_COMFYUI_DIR, 'models'])
|
||||
}
|
||||
|
||||
console.log('🧹 Global teardown completed')
|
||||
}
|
||||
|
||||
@@ -1,346 +0,0 @@
|
||||
import type { Page, TestInfo } from '@playwright/test'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
export interface PerformanceMetrics {
|
||||
testName: string
|
||||
timestamp: number
|
||||
branch?: string
|
||||
memoryUsage: {
|
||||
usedJSHeapSize: number
|
||||
totalJSHeapSize: number
|
||||
jsHeapSizeLimit: number
|
||||
} | null
|
||||
timing: {
|
||||
loadStart?: number
|
||||
domContentLoaded?: number
|
||||
loadComplete?: number
|
||||
firstPaint?: number
|
||||
firstContentfulPaint?: number
|
||||
largestContentfulPaint?: number
|
||||
}
|
||||
customMetrics: Record<string, number>
|
||||
}
|
||||
|
||||
export interface PerformanceRunSummary {
|
||||
runId: string
|
||||
timestamp: number
|
||||
branch: string
|
||||
gitCommit?: string
|
||||
environment: {
|
||||
nodeVersion: string
|
||||
playwrightVersion: string
|
||||
os: string
|
||||
}
|
||||
testMetrics: PerformanceMetrics[]
|
||||
}
|
||||
|
||||
export class PerformanceMonitor {
|
||||
private metrics: PerformanceMetrics[] = []
|
||||
private static allMetrics: PerformanceMetrics[] = []
|
||||
|
||||
constructor(
|
||||
private page: Page,
|
||||
private testInfo?: TestInfo
|
||||
) {}
|
||||
|
||||
async startMonitoring(testName: string) {
|
||||
await this.page.evaluate((testName) => {
|
||||
// Initialize performance monitoring
|
||||
window.perfMonitor = {
|
||||
testName,
|
||||
startTime: performance.now(),
|
||||
marks: new Map(),
|
||||
measures: new Map()
|
||||
}
|
||||
|
||||
// Mark test start
|
||||
performance.mark(`${testName}-start`)
|
||||
|
||||
// Set up performance observer to capture paint metrics
|
||||
if ('PerformanceObserver' in window) {
|
||||
const observer = new PerformanceObserver((list) => {
|
||||
for (const entry of list.getEntries()) {
|
||||
if (
|
||||
entry.entryType === 'paint' ||
|
||||
entry.entryType === 'largest-contentful-paint'
|
||||
) {
|
||||
window.perfMonitor?.measures.set(entry.name, entry.startTime)
|
||||
}
|
||||
}
|
||||
})
|
||||
observer.observe({ entryTypes: ['paint', 'largest-contentful-paint'] })
|
||||
}
|
||||
}, testName)
|
||||
}
|
||||
|
||||
async markEvent(eventName: string) {
|
||||
await this.page.evaluate((eventName) => {
|
||||
if (window.perfMonitor) {
|
||||
const markName = `${window.perfMonitor.testName}-${eventName}`
|
||||
performance.mark(markName)
|
||||
window.perfMonitor.marks.set(
|
||||
eventName,
|
||||
performance.now() - window.perfMonitor.startTime
|
||||
)
|
||||
}
|
||||
}, eventName)
|
||||
}
|
||||
|
||||
async measureOperation<T>(
|
||||
operationName: string,
|
||||
operation: () => Promise<T>
|
||||
): Promise<T> {
|
||||
await this.markEvent(`${operationName}-start`)
|
||||
const result = await operation()
|
||||
await this.markEvent(`${operationName}-end`)
|
||||
|
||||
// Create performance measure
|
||||
await this.page.evaluate((operationName) => {
|
||||
if (window.perfMonitor) {
|
||||
const testName = window.perfMonitor.testName
|
||||
const startMark = `${testName}-${operationName}-start`
|
||||
const endMark = `${testName}-${operationName}-end`
|
||||
|
||||
try {
|
||||
performance.measure(`${operationName}`, startMark, endMark)
|
||||
const measure = performance.getEntriesByName(`${operationName}`)[0]
|
||||
window.perfMonitor.measures.set(operationName, measure.duration)
|
||||
} catch (e) {
|
||||
console.warn('Failed to create performance measure:', e)
|
||||
}
|
||||
}
|
||||
}, operationName)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async collectMetrics(
|
||||
testName: string,
|
||||
branch: string = 'unknown'
|
||||
): Promise<PerformanceMetrics | null> {
|
||||
const metrics = await this.page.evaluate(
|
||||
({ testName, branch }) => {
|
||||
if (!window.perfMonitor) return null
|
||||
|
||||
// Collect all performance data
|
||||
const navigationEntry = performance.getEntriesByType(
|
||||
'navigation'
|
||||
)[0] as PerformanceNavigationTiming
|
||||
const paintEntries = performance.getEntriesByType('paint')
|
||||
const lcpEntries = performance.getEntriesByType(
|
||||
'largest-contentful-paint'
|
||||
)
|
||||
|
||||
const timing: any = {}
|
||||
if (navigationEntry) {
|
||||
timing.loadStart = navigationEntry.loadEventStart
|
||||
timing.domContentLoaded = navigationEntry.domContentLoadedEventEnd
|
||||
timing.loadComplete = navigationEntry.loadEventEnd
|
||||
}
|
||||
|
||||
paintEntries.forEach((entry) => {
|
||||
if (entry.name === 'first-paint') {
|
||||
timing.firstPaint = entry.startTime
|
||||
} else if (entry.name === 'first-contentful-paint') {
|
||||
timing.firstContentfulPaint = entry.startTime
|
||||
}
|
||||
})
|
||||
|
||||
if (lcpEntries.length > 0) {
|
||||
timing.largestContentfulPaint =
|
||||
lcpEntries[lcpEntries.length - 1].startTime
|
||||
}
|
||||
|
||||
const customMetrics: Record<string, number> = {}
|
||||
window.perfMonitor.measures.forEach((value, key) => {
|
||||
customMetrics[key] = value
|
||||
})
|
||||
|
||||
return {
|
||||
testName,
|
||||
timestamp: Date.now(),
|
||||
branch,
|
||||
memoryUsage: performance.memory
|
||||
? {
|
||||
usedJSHeapSize: performance.memory.usedJSHeapSize,
|
||||
totalJSHeapSize: performance.memory.totalJSHeapSize,
|
||||
jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
|
||||
}
|
||||
: null,
|
||||
timing,
|
||||
customMetrics
|
||||
}
|
||||
},
|
||||
{ testName, branch }
|
||||
)
|
||||
|
||||
if (metrics) {
|
||||
this.metrics.push(metrics)
|
||||
PerformanceMonitor.allMetrics.push(metrics)
|
||||
|
||||
// Write individual metric file immediately for worker persistence
|
||||
try {
|
||||
const tempDir = path.join(process.cwd(), 'test-results', '.perf-temp')
|
||||
if (!fs.existsSync(tempDir)) {
|
||||
fs.mkdirSync(tempDir, { recursive: true })
|
||||
}
|
||||
const tempFile = path.join(
|
||||
tempDir,
|
||||
`metric-${Date.now()}-${Math.random().toString(36).substr(2, 9)}.json`
|
||||
)
|
||||
fs.writeFileSync(tempFile, JSON.stringify(metrics, null, 2))
|
||||
} catch (error) {
|
||||
console.warn('Failed to write temp metric file:', error)
|
||||
}
|
||||
|
||||
console.log('PERFORMANCE_METRICS:', JSON.stringify(metrics))
|
||||
console.log(
|
||||
`📈 Total metrics collected so far: ${PerformanceMonitor.allMetrics.length}`
|
||||
)
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
|
||||
async finishMonitoring(testName: string) {
|
||||
await this.markEvent('test-end')
|
||||
await this.collectMetrics(testName, 'vue-widget/perf-test')
|
||||
console.log('Finishing performance monitoring')
|
||||
// Print all metrics
|
||||
console.log('PERFORMANCE_METRICS:', JSON.stringify(this.metrics))
|
||||
|
||||
// Cleanup
|
||||
await this.page.evaluate(() => {
|
||||
if (window.perfMonitor) {
|
||||
delete window.perfMonitor
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
getAllMetrics(): PerformanceMetrics[] {
|
||||
return this.metrics
|
||||
}
|
||||
|
||||
static getAllCollectedMetrics(): PerformanceMetrics[] {
|
||||
return PerformanceMonitor.allMetrics
|
||||
}
|
||||
|
||||
static clearAllMetrics() {
|
||||
PerformanceMonitor.allMetrics = []
|
||||
}
|
||||
|
||||
static async saveMetricsToFile(outputPath?: string): Promise<string> {
|
||||
// This runs in Node.js context (global teardown), not browser
|
||||
if (typeof window !== 'undefined') {
|
||||
throw new Error(
|
||||
'saveMetricsToFile should only be called from Node.js context'
|
||||
)
|
||||
}
|
||||
|
||||
// Collect metrics from temp files (handles worker persistence)
|
||||
const allMetrics: PerformanceMetrics[] = []
|
||||
const tempDir = path.join(process.cwd(), 'test-results', '.perf-temp')
|
||||
|
||||
if (fs.existsSync(tempDir)) {
|
||||
const tempFiles = fs
|
||||
.readdirSync(tempDir)
|
||||
.filter((f) => f.startsWith('metric-') && f.endsWith('.json'))
|
||||
for (const file of tempFiles) {
|
||||
try {
|
||||
const content = fs.readFileSync(path.join(tempDir, file), 'utf8')
|
||||
const metric = JSON.parse(content)
|
||||
allMetrics.push(metric)
|
||||
} catch (error) {
|
||||
console.warn(`Failed to read temp metric file ${file}:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
// Clean up temp files
|
||||
try {
|
||||
fs.rmSync(tempDir, { recursive: true, force: true })
|
||||
} catch (error) {
|
||||
console.warn('Failed to clean up temp directory:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Also include any metrics from static array (fallback)
|
||||
allMetrics.push(...PerformanceMonitor.allMetrics)
|
||||
|
||||
const defaultPath = path.join(process.cwd(), 'test-results', 'performance')
|
||||
const resultsDir = outputPath || defaultPath
|
||||
|
||||
// Ensure directory exists
|
||||
if (!fs.existsSync(resultsDir)) {
|
||||
fs.mkdirSync(resultsDir, { recursive: true })
|
||||
}
|
||||
|
||||
const runId = `run-${new Date().toISOString().replace(/[:.]/g, '-')}`
|
||||
const branch =
|
||||
process.env.GIT_BRANCH ||
|
||||
process.env.GITHUB_HEAD_REF ||
|
||||
process.env.GITHUB_REF_NAME ||
|
||||
'unknown'
|
||||
|
||||
// Get Playwright version more safely
|
||||
let playwrightVersion = 'unknown'
|
||||
try {
|
||||
playwrightVersion = require('@playwright/test/package.json').version
|
||||
} catch {
|
||||
// Fallback if package.json not accessible
|
||||
playwrightVersion = 'unknown'
|
||||
}
|
||||
|
||||
const summary: PerformanceRunSummary = {
|
||||
runId,
|
||||
timestamp: Date.now(),
|
||||
branch,
|
||||
gitCommit: process.env.GITHUB_SHA || process.env.GIT_COMMIT,
|
||||
environment: {
|
||||
nodeVersion: process.version,
|
||||
playwrightVersion,
|
||||
os: process.platform
|
||||
},
|
||||
testMetrics: allMetrics
|
||||
}
|
||||
|
||||
const fileName = `${runId}.json`
|
||||
const filePath = path.join(resultsDir, fileName)
|
||||
|
||||
try {
|
||||
fs.writeFileSync(filePath, JSON.stringify(summary, null, 2))
|
||||
console.log(`\n📊 Performance metrics saved to: ${filePath}`)
|
||||
console.log(`📈 Total tests measured: ${allMetrics.length}`)
|
||||
|
||||
// Also create/update a latest.json for easy access
|
||||
const latestPath = path.join(resultsDir, 'latest.json')
|
||||
fs.writeFileSync(latestPath, JSON.stringify(summary, null, 2))
|
||||
|
||||
return filePath
|
||||
} catch (error) {
|
||||
console.error('Failed to save performance metrics:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extend window type for TypeScript
|
||||
declare global {
|
||||
interface Window {
|
||||
perfMonitor?: {
|
||||
testName: string
|
||||
startTime: number
|
||||
marks: Map<string, number>
|
||||
measures: Map<string, number>
|
||||
}
|
||||
}
|
||||
|
||||
// Chrome-specific performance.memory extension
|
||||
interface Performance {
|
||||
memory?: {
|
||||
usedJSHeapSize: number
|
||||
totalJSHeapSize: number
|
||||
jsHeapSizeLimit: number
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
test.describe('Background Image Upload', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
@@ -45,14 +44,9 @@ test.describe('Background Image Upload', () => {
|
||||
await expect(clearButton).toBeDisabled() // Should be disabled when no image
|
||||
})
|
||||
|
||||
test('@perf should upload image file and set as background', async ({
|
||||
test('should upload image file and set as background', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'upload-background-image-file'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
// Open settings dialog
|
||||
await comfyPage.page.keyboard.press('Control+,')
|
||||
|
||||
@@ -69,18 +63,16 @@ test.describe('Background Image Upload', () => {
|
||||
'button:has(.pi-upload)'
|
||||
)
|
||||
|
||||
// Set up file upload handler and upload
|
||||
await perfMonitor.measureOperation('trigger-file-upload', async () => {
|
||||
const fileChooserPromise = comfyPage.page.waitForEvent('filechooser')
|
||||
await uploadButton.click()
|
||||
const fileChooser = await fileChooserPromise
|
||||
await fileChooser.setFiles(comfyPage.assetPath('image32x32.webp'))
|
||||
})
|
||||
// Set up file upload handler
|
||||
const fileChooserPromise = comfyPage.page.waitForEvent('filechooser')
|
||||
await uploadButton.click()
|
||||
const fileChooser = await fileChooserPromise
|
||||
|
||||
// Upload the test image
|
||||
await fileChooser.setFiles(comfyPage.assetPath('image32x32.webp'))
|
||||
|
||||
// Wait for upload to complete and verify the setting was updated
|
||||
await perfMonitor.measureOperation('process-uploaded-file', async () => {
|
||||
await comfyPage.page.waitForTimeout(500) // Give time for file reading
|
||||
})
|
||||
await comfyPage.page.waitForTimeout(500) // Give time for file reading
|
||||
|
||||
// Verify the URL input now has an API URL
|
||||
const urlInput = backgroundImageSetting.locator('input[type="text"]')
|
||||
@@ -96,18 +88,11 @@ test.describe('Background Image Upload', () => {
|
||||
'Comfy.Canvas.BackgroundImage'
|
||||
)
|
||||
expect(settingValue).toMatch(/^\/api\/view\?.*subfolder=backgrounds/)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf should accept URL input for background image', async ({
|
||||
test('should accept URL input for background image', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'input-background-image-url'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const testImageUrl = 'https://example.com/test-image.png'
|
||||
|
||||
// Open settings dialog
|
||||
@@ -121,13 +106,12 @@ test.describe('Background Image Upload', () => {
|
||||
const backgroundImageSetting = comfyPage.page.locator(
|
||||
'#Comfy\\.Canvas\\.BackgroundImage'
|
||||
)
|
||||
|
||||
// Enter URL in the input field
|
||||
const urlInput = backgroundImageSetting.locator('input[type="text"]')
|
||||
await perfMonitor.measureOperation('input-url-text', async () => {
|
||||
await urlInput.fill(testImageUrl)
|
||||
await urlInput.blur()
|
||||
})
|
||||
await urlInput.fill(testImageUrl)
|
||||
|
||||
// Trigger blur event to ensure the value is set
|
||||
await urlInput.blur()
|
||||
|
||||
// Verify clear button is now enabled
|
||||
const clearButton = backgroundImageSetting.locator('button:has(.pi-trash)')
|
||||
@@ -138,24 +122,15 @@ test.describe('Background Image Upload', () => {
|
||||
'Comfy.Canvas.BackgroundImage'
|
||||
)
|
||||
expect(settingValue).toBe(testImageUrl)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf should clear background image when clear button is clicked', async ({
|
||||
test('should clear background image when clear button is clicked', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'clear-background-image'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const testImageUrl = 'https://example.com/test-image.png'
|
||||
|
||||
// First set a background image
|
||||
await perfMonitor.measureOperation('set-initial-setting', async () => {
|
||||
await comfyPage.setSetting('Comfy.Canvas.BackgroundImage', testImageUrl)
|
||||
})
|
||||
await comfyPage.setSetting('Comfy.Canvas.BackgroundImage', testImageUrl)
|
||||
|
||||
// Open settings dialog
|
||||
await comfyPage.page.keyboard.press('Control+,')
|
||||
@@ -177,9 +152,7 @@ test.describe('Background Image Upload', () => {
|
||||
await expect(clearButton).toBeEnabled()
|
||||
|
||||
// Click the clear button
|
||||
await perfMonitor.measureOperation('click-clear-button', async () => {
|
||||
await clearButton.click()
|
||||
})
|
||||
await clearButton.click()
|
||||
|
||||
// Verify the input is now empty
|
||||
await expect(urlInput).toHaveValue('')
|
||||
@@ -192,8 +165,6 @@ test.describe('Background Image Upload', () => {
|
||||
'Comfy.Canvas.BackgroundImage'
|
||||
)
|
||||
expect(settingValue).toBe('')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('should show tooltip on upload and clear buttons', async ({
|
||||
@@ -240,14 +211,9 @@ test.describe('Background Image Upload', () => {
|
||||
await expect(clearTooltip).toBeVisible()
|
||||
})
|
||||
|
||||
test('@perf should maintain reactive updates between URL input and clear button state', async ({
|
||||
test('should maintain reactive updates between URL input and clear button state', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'reactive-widget-updates'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
// Open settings dialog
|
||||
await comfyPage.page.keyboard.press('Control+,')
|
||||
|
||||
@@ -266,30 +232,20 @@ test.describe('Background Image Upload', () => {
|
||||
await expect(clearButton).toBeDisabled()
|
||||
|
||||
// Type some text - clear button should become enabled
|
||||
await perfMonitor.measureOperation('input-partial-text', async () => {
|
||||
await urlInput.fill('test')
|
||||
})
|
||||
await urlInput.fill('test')
|
||||
await expect(clearButton).toBeEnabled()
|
||||
|
||||
// Clear the text manually - clear button should become disabled again
|
||||
await perfMonitor.measureOperation('clear-input-manually', async () => {
|
||||
await urlInput.fill('')
|
||||
})
|
||||
await urlInput.fill('')
|
||||
await expect(clearButton).toBeDisabled()
|
||||
|
||||
// Add text again - clear button should become enabled
|
||||
await perfMonitor.measureOperation('input-full-url', async () => {
|
||||
await urlInput.fill('https://example.com/image.png')
|
||||
})
|
||||
await urlInput.fill('https://example.com/image.png')
|
||||
await expect(clearButton).toBeEnabled()
|
||||
|
||||
// Use clear button - should clear input and disable itself
|
||||
await perfMonitor.measureOperation('clear-via-button', async () => {
|
||||
await clearButton.click()
|
||||
})
|
||||
await clearButton.click()
|
||||
await expect(urlInput).toHaveValue('')
|
||||
await expect(clearButton).toBeDisabled()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
comfyExpect as expect,
|
||||
comfyPageFixture as test
|
||||
} from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
async function beforeChange(comfyPage: ComfyPage) {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
@@ -23,161 +22,93 @@ test.describe('Change Tracker', () => {
|
||||
await comfyPage.setupWorkflowsDirectory({})
|
||||
})
|
||||
|
||||
test.skip('@perf Can undo multiple operations', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'undo-multiple-operations'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Can undo multiple operations', async ({ comfyPage }) => {
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
expect(await comfyPage.getRedoQueueSize()).toBe(0)
|
||||
|
||||
// Save, confirm no errors & workflow modified flag removed
|
||||
await perfMonitor.measureOperation('save-workflow', async () => {
|
||||
await comfyPage.menu.topbar.saveWorkflow('undo-redo-test')
|
||||
})
|
||||
await comfyPage.menu.topbar.saveWorkflow('undo-redo-test')
|
||||
expect(await comfyPage.getToastErrorCount()).toBe(0)
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
expect(await comfyPage.getRedoQueueSize()).toBe(0)
|
||||
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
|
||||
await perfMonitor.measureOperation('click-node-title', async () => {
|
||||
await node.click('title')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('collapse-node', async () => {
|
||||
await node.click('collapse')
|
||||
})
|
||||
await node.click('title')
|
||||
await node.click('collapse')
|
||||
await expect(node).toBeCollapsed()
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(1)
|
||||
expect(await comfyPage.getRedoQueueSize()).toBe(0)
|
||||
|
||||
await perfMonitor.measureOperation('bypass-node', async () => {
|
||||
await comfyPage.ctrlB()
|
||||
})
|
||||
await comfyPage.ctrlB()
|
||||
await expect(node).toBeBypassed()
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(2)
|
||||
expect(await comfyPage.getRedoQueueSize()).toBe(0)
|
||||
|
||||
await perfMonitor.markEvent('before-undo-operations')
|
||||
|
||||
await perfMonitor.measureOperation('undo-bypass', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(node).not.toBeBypassed()
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(true)
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(1)
|
||||
expect(await comfyPage.getRedoQueueSize()).toBe(1)
|
||||
|
||||
await perfMonitor.measureOperation('undo-collapse', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(node).not.toBeCollapsed()
|
||||
expect(await comfyPage.isCurrentWorkflowModified()).toBe(false)
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
expect(await comfyPage.getRedoQueueSize()).toBe(2)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
test('@perf Can group multiple change actions into a single transaction', async ({
|
||||
test('Can group multiple change actions into a single transaction', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'group-change-transactions'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
expect(node).toBeTruthy()
|
||||
await expect(node).not.toBeCollapsed()
|
||||
await expect(node).not.toBeBypassed()
|
||||
|
||||
await perfMonitor.markEvent('individual-changes-start')
|
||||
|
||||
// Make changes outside set
|
||||
// Bypass + collapse node
|
||||
await perfMonitor.measureOperation('click-node-title', async () => {
|
||||
await node.click('title')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('collapse-node', async () => {
|
||||
await node.click('collapse')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('bypass-node', async () => {
|
||||
await comfyPage.ctrlB()
|
||||
})
|
||||
await node.click('title')
|
||||
await node.click('collapse')
|
||||
await comfyPage.ctrlB()
|
||||
await expect(node).toBeCollapsed()
|
||||
await expect(node).toBeBypassed()
|
||||
|
||||
// Undo, undo, ensure both changes undone
|
||||
await perfMonitor.measureOperation('undo-bypass', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(node).not.toBeBypassed()
|
||||
await expect(node).toBeCollapsed()
|
||||
|
||||
await perfMonitor.measureOperation('undo-collapse', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(node).not.toBeBypassed()
|
||||
await expect(node).not.toBeCollapsed()
|
||||
|
||||
// Prevent clicks registering a double-click
|
||||
await perfMonitor.measureOperation('click-empty-space', async () => {
|
||||
await comfyPage.clickEmptySpace()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('click-node-title-again', async () => {
|
||||
await node.click('title')
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('transaction-changes-start')
|
||||
await comfyPage.clickEmptySpace()
|
||||
await node.click('title')
|
||||
|
||||
// Run again, but within a change transaction
|
||||
await perfMonitor.measureOperation('begin-change-transaction', async () => {
|
||||
await beforeChange(comfyPage)
|
||||
})
|
||||
await beforeChange(comfyPage)
|
||||
|
||||
await perfMonitor.measureOperation('collapse-in-transaction', async () => {
|
||||
await node.click('collapse')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('bypass-in-transaction', async () => {
|
||||
await comfyPage.ctrlB()
|
||||
})
|
||||
await node.click('collapse')
|
||||
await comfyPage.ctrlB()
|
||||
await expect(node).toBeCollapsed()
|
||||
await expect(node).toBeBypassed()
|
||||
|
||||
// End transaction
|
||||
await perfMonitor.measureOperation('end-change-transaction', async () => {
|
||||
await afterChange(comfyPage)
|
||||
})
|
||||
await afterChange(comfyPage)
|
||||
|
||||
// Ensure undo reverts both changes
|
||||
await perfMonitor.measureOperation('undo-transaction', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(node).not.toBeBypassed()
|
||||
await expect(node).not.toBeCollapsed()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Can nest multiple change transactions without adding undo steps', async ({
|
||||
test('Can nest multiple change transactions without adding undo steps', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'nested-change-transactions'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const node = (await comfyPage.getFirstNodeRef())!
|
||||
const bypassAndPin = async () => {
|
||||
await beforeChange(comfyPage)
|
||||
@@ -205,67 +136,32 @@ test.describe('Change Tracker', () => {
|
||||
await afterChange(comfyPage)
|
||||
}
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'execute-nested-transactions',
|
||||
async () => {
|
||||
await multipleChanges()
|
||||
}
|
||||
)
|
||||
await multipleChanges()
|
||||
|
||||
await perfMonitor.measureOperation('undo-all-changes', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(node).not.toBeBypassed()
|
||||
await expect(node).not.toBePinned()
|
||||
await expect(node).not.toBeCollapsed()
|
||||
|
||||
await perfMonitor.measureOperation('redo-all-changes', async () => {
|
||||
await comfyPage.ctrlY()
|
||||
})
|
||||
await comfyPage.ctrlY()
|
||||
await expect(node).toBeBypassed()
|
||||
await expect(node).toBePinned()
|
||||
await expect(node).toBeCollapsed()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
// Skip because fails with vue widget nodes (reason not investigated)
|
||||
test.skip('@perf Can detect changes in workflow.extra', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'detect-workflow-extra-changes'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Can detect changes in workflow.extra', async ({ comfyPage }) => {
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
|
||||
await perfMonitor.measureOperation('modify-workflow-extra', async () => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].graph.extra.foo = 'bar'
|
||||
})
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].graph.extra.foo = 'bar'
|
||||
})
|
||||
|
||||
// Click empty space to trigger a change detection.
|
||||
await perfMonitor.measureOperation('trigger-change-detection', async () => {
|
||||
await comfyPage.clickEmptySpace()
|
||||
})
|
||||
await comfyPage.clickEmptySpace()
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Ignores changes in workflow.ds', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'ignore-workflow-ds-changes'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Ignores changes in workflow.ds', async ({ comfyPage }) => {
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
|
||||
await perfMonitor.measureOperation('pan-canvas', async () => {
|
||||
await comfyPage.pan({ x: 10, y: 10 })
|
||||
})
|
||||
await comfyPage.pan({ x: 10, y: 10 })
|
||||
expect(await comfyPage.getUndoQueueSize()).toBe(0)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Page, expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
interface ChatHistoryEntry {
|
||||
prompt: string
|
||||
@@ -43,66 +42,49 @@ test.describe('Chat History Widget', () => {
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
})
|
||||
|
||||
// Skip because fails with vue widget nodes (reason not investigated)
|
||||
test.skip('@perf displays chat history when receiving display_component message', async ({
|
||||
test('displays chat history when receiving display_component message', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'display-chat-history-component'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
// Verify the chat history is displayed correctly
|
||||
await expect(comfyPage.page.getByText('Hello')).toBeVisible()
|
||||
await expect(comfyPage.page.getByText('World')).toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test.skip('@perf handles message editing interaction', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'message-editing-interaction'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('handles message editing interaction', async ({ comfyPage }) => {
|
||||
// Get first node's ID
|
||||
await perfMonitor.measureOperation('setup-node-widgets', async () => {
|
||||
nodeId = await comfyPage.page.evaluate(() => {
|
||||
const node = window['app'].graph.nodes[0]
|
||||
nodeId = await comfyPage.page.evaluate(() => {
|
||||
const node = window['app'].graph.nodes[0]
|
||||
|
||||
// Make sure the node has a prompt widget (for editing functionality)
|
||||
if (!node.widgets) {
|
||||
node.widgets = []
|
||||
}
|
||||
// Make sure the node has a prompt widget (for editing functionality)
|
||||
if (!node.widgets) {
|
||||
node.widgets = []
|
||||
}
|
||||
|
||||
// Add a prompt widget if it doesn't exist
|
||||
if (!node.widgets.find((w) => w.name === 'prompt')) {
|
||||
node.widgets.push({
|
||||
name: 'prompt',
|
||||
type: 'text',
|
||||
value: 'Original prompt'
|
||||
})
|
||||
}
|
||||
// Add a prompt widget if it doesn't exist
|
||||
if (!node.widgets.find((w) => w.name === 'prompt')) {
|
||||
node.widgets.push({
|
||||
name: 'prompt',
|
||||
type: 'text',
|
||||
value: 'Original prompt'
|
||||
})
|
||||
}
|
||||
|
||||
return node.id
|
||||
})
|
||||
return node.id
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('render-chat-history', async () => {
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Message 1',
|
||||
response: 'Response 1',
|
||||
response_id: '123'
|
||||
},
|
||||
{
|
||||
prompt: 'Message 2',
|
||||
response: 'Response 2',
|
||||
response_id: '456'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
})
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Message 1',
|
||||
response: 'Response 1',
|
||||
response_id: '123'
|
||||
},
|
||||
{
|
||||
prompt: 'Message 2',
|
||||
response: 'Response 2',
|
||||
response_id: '456'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
|
||||
const originalTextAreaInput = await comfyPage.page
|
||||
.getByPlaceholder('text')
|
||||
@@ -110,73 +92,48 @@ test.describe('Chat History Widget', () => {
|
||||
.inputValue()
|
||||
|
||||
// Click edit button on first message
|
||||
await perfMonitor.measureOperation('click-edit-button', async () => {
|
||||
await comfyPage.page.getByLabel('Edit').first().click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
await comfyPage.page.getByLabel('Edit').first().click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Verify cancel button appears
|
||||
await expect(comfyPage.page.getByLabel('Cancel')).toBeVisible()
|
||||
|
||||
// Click cancel edit
|
||||
await perfMonitor.measureOperation('click-cancel-button', async () => {
|
||||
await comfyPage.page.getByLabel('Cancel').click()
|
||||
})
|
||||
await comfyPage.page.getByLabel('Cancel').click()
|
||||
|
||||
// Verify prompt input is restored
|
||||
await expect(comfyPage.page.getByPlaceholder('text').nth(1)).toHaveValue(
|
||||
originalTextAreaInput
|
||||
)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test.skip('@perf handles real-time updates to chat history', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'real-time-chat-history-updates'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('handles real-time updates to chat history', async ({ comfyPage }) => {
|
||||
// Send initial history
|
||||
await perfMonitor.measureOperation('render-initial-history', async () => {
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Initial message',
|
||||
response: 'Initial response',
|
||||
response_id: '123'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('before-history-update')
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Initial message',
|
||||
response: 'Initial response',
|
||||
response_id: '123'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
|
||||
// Update history with additional messages
|
||||
await perfMonitor.measureOperation('update-chat-history', async () => {
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Follow-up',
|
||||
response: 'New response',
|
||||
response_id: '456'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
})
|
||||
await renderChatHistory(comfyPage.page, [
|
||||
{
|
||||
prompt: 'Follow-up',
|
||||
response: 'New response',
|
||||
response_id: '456'
|
||||
}
|
||||
])
|
||||
await comfyPage.page.waitForSelector('.pi-pencil')
|
||||
|
||||
// Move mouse over the canvas to force update
|
||||
await perfMonitor.measureOperation('trigger-canvas-update', async () => {
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('after-canvas-update')
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Verify new messages appear
|
||||
await expect(comfyPage.page.getByText('Follow-up')).toBeVisible()
|
||||
await expect(comfyPage.page.getByText('New response')).toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,7 +2,6 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import type { Palette } from '../../src/schemas/colorPaletteSchema'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
const customColorPalettes: Record<string, Palette> = {
|
||||
obsidian: {
|
||||
@@ -149,99 +148,45 @@ const customColorPalettes: Record<string, Palette> = {
|
||||
}
|
||||
|
||||
test.describe('Color Palette', () => {
|
||||
test('@perf Can show custom color palette', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'show-custom-color-palette'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('set-custom-palettes', async () => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.CustomColorPalettes',
|
||||
customColorPalettes
|
||||
)
|
||||
})
|
||||
|
||||
test('Can show custom color palette', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.CustomColorPalettes', customColorPalettes)
|
||||
// Reload to apply the new setting. Setting Comfy.CustomColorPalettes directly
|
||||
// doesn't update the store immediately.
|
||||
await perfMonitor.measureOperation('reload-page', async () => {
|
||||
await comfyPage.setup()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('load-workflow', async () => {
|
||||
await comfyPage.loadWorkflow('every_node_color')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'apply-obsidian-dark-palette',
|
||||
async () => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
|
||||
}
|
||||
)
|
||||
await comfyPage.setup()
|
||||
|
||||
await comfyPage.loadWorkflow('every_node_color')
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'custom-color-palette-obsidian-dark-all-colors.png'
|
||||
)
|
||||
|
||||
await perfMonitor.measureOperation('apply-light-red-palette', async () => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light_red')
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light_red')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'custom-color-palette-light-red.png'
|
||||
)
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'apply-default-dark-palette',
|
||||
async () => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'dark')
|
||||
await comfyPage.nextFrame()
|
||||
}
|
||||
)
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'dark')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('default-color-palette.png')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test.skip('@perf Can add custom color palette', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'add-custom-color-palette'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('add-palette-via-api', async () => {
|
||||
await comfyPage.page.evaluate((p) => {
|
||||
window['app'].extensionManager.colorPalette.addCustomColorPalette(p)
|
||||
}, customColorPalettes.obsidian_dark)
|
||||
})
|
||||
|
||||
test('Can add custom color palette', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate((p) => {
|
||||
window['app'].extensionManager.colorPalette.addCustomColorPalette(p)
|
||||
}, customColorPalettes.obsidian_dark)
|
||||
expect(await comfyPage.getToastErrorCount()).toBe(0)
|
||||
|
||||
await perfMonitor.measureOperation('apply-custom-palette', async () => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'obsidian_dark')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'custom-color-palette-obsidian-dark.png'
|
||||
)
|
||||
|
||||
// Legacy `custom_` prefix is still supported
|
||||
await perfMonitor.measureOperation(
|
||||
'apply-custom-palette-legacy-prefix',
|
||||
async () => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'custom_obsidian_dark')
|
||||
await comfyPage.nextFrame()
|
||||
}
|
||||
)
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'custom_obsidian_dark')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'custom-color-palette-obsidian-dark.png'
|
||||
)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -250,121 +195,58 @@ test.describe('Node Color Adjustments', () => {
|
||||
await comfyPage.loadWorkflow('every_node_color')
|
||||
})
|
||||
|
||||
test('@perf should adjust opacity via node opacity setting', async ({
|
||||
test('should adjust opacity via node opacity setting', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'adjust-node-opacity'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('set-opacity-0-5', async () => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
await comfyPage.page.waitForTimeout(128)
|
||||
})
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
await comfyPage.page.waitForTimeout(128)
|
||||
|
||||
// Drag mouse to force canvas to redraw
|
||||
await perfMonitor.measureOperation('redraw-canvas', async () => {
|
||||
await comfyPage.page.mouse.move(0, 0)
|
||||
})
|
||||
await comfyPage.page.mouse.move(0, 0)
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-opacity-0.5.png')
|
||||
|
||||
await perfMonitor.measureOperation('set-opacity-1-0', async () => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 1.0)
|
||||
await comfyPage.page.waitForTimeout(128)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'redraw-canvas-full-opacity',
|
||||
async () => {
|
||||
await comfyPage.page.mouse.move(8, 8)
|
||||
}
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 1.0)
|
||||
await comfyPage.page.waitForTimeout(128)
|
||||
|
||||
await comfyPage.page.mouse.move(8, 8)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-opacity-1.png')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf should persist color adjustments when changing themes', async ({
|
||||
test('should persist color adjustments when changing themes', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'persist-opacity-across-themes'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('set-opacity-and-theme', async () => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.2)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'arc')
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('redraw-canvas-with-theme', async () => {
|
||||
await comfyPage.page.mouse.move(0, 0)
|
||||
})
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf should not serialize color adjustments in workflow', async ({
|
||||
test('should not serialize color adjustments in workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'workflow-serialization-color-adjustments'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('apply-color-settings', async () => {
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
})
|
||||
|
||||
await comfyPage.setSetting('Comfy.Node.Opacity', 0.5)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
const saveWorkflowInterval = 1000
|
||||
await perfMonitor.measureOperation('wait-for-workflow-save', async () => {
|
||||
await comfyPage.page.waitForTimeout(saveWorkflowInterval)
|
||||
await comfyPage.page.waitForTimeout(saveWorkflowInterval)
|
||||
const workflow = await comfyPage.page.evaluate(() => {
|
||||
return localStorage.getItem('workflow')
|
||||
})
|
||||
|
||||
let workflow: string | null
|
||||
await perfMonitor.measureOperation(
|
||||
'get-workflow-from-storage',
|
||||
async () => {
|
||||
workflow = await comfyPage.page.evaluate(() => {
|
||||
return localStorage.getItem('workflow')
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
for (const node of JSON.parse(workflow! ?? '{}').nodes) {
|
||||
for (const node of JSON.parse(workflow ?? '{}').nodes) {
|
||||
if (node.bgcolor) expect(node.bgcolor).not.toMatch(/hsla/)
|
||||
if (node.color) expect(node.color).not.toMatch(/hsla/)
|
||||
}
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
// Skip because fails with vue widget nodes (reason not investigated)
|
||||
test.skip('@perf should lighten node colors when switching to light theme', async ({
|
||||
test('should lighten node colors when switching to light theme', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'lighten-colors-light-theme'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('apply-light-theme', async () => {
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-lightened-colors.png')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test.describe('Context menu color adjustments', () => {
|
||||
@@ -375,48 +257,26 @@ test.describe('Node Color Adjustments', () => {
|
||||
await node?.clickContextMenuOption('Colors')
|
||||
})
|
||||
|
||||
test('@perf should persist color adjustments when changing custom node colors', async ({
|
||||
test('should persist color adjustments when changing custom node colors', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'persist-opacity-color-change'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
// Context menu interaction - monitor the node color change operation
|
||||
await perfMonitor.measureOperation('select-red-color', async () => {
|
||||
await comfyPage.page
|
||||
.locator('.litemenu-entry.submenu span:has-text("red")')
|
||||
.click()
|
||||
})
|
||||
|
||||
await comfyPage.page
|
||||
.locator('.litemenu-entry.submenu span:has-text("red")')
|
||||
.click()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'node-opacity-0.3-color-changed.png'
|
||||
)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf should persist color adjustments when removing custom node color', async ({
|
||||
test('should persist color adjustments when removing custom node color', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'persist-opacity-color-removal'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
// Context menu interaction - monitor the node color removal operation
|
||||
await perfMonitor.measureOperation('remove-node-color', async () => {
|
||||
await comfyPage.page
|
||||
.locator('.litemenu-entry.submenu span:has-text("No color")')
|
||||
.click()
|
||||
})
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 132 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 159 KiB After Width: | Height: | Size: 158 KiB |
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 152 KiB After Width: | Height: | Size: 149 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 146 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 133 KiB |
@@ -1,7 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
test.describe('Copy Paste', () => {
|
||||
test('Can copy and paste node', async ({ comfyPage }) => {
|
||||
@@ -12,290 +11,107 @@ test.describe('Copy Paste', () => {
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-node.png')
|
||||
})
|
||||
|
||||
test('@perf Can copy and paste node with link', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-node-with-link'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
// Click node with performance tracking
|
||||
await perfMonitor.measureOperation('click-text-encode-node', async () => {
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
})
|
||||
|
||||
// Mouse move with performance tracking
|
||||
await perfMonitor.measureOperation('mouse-move', async () => {
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
})
|
||||
|
||||
// Copy operation with performance tracking
|
||||
await perfMonitor.measureOperation('copy-operation', async () => {
|
||||
await comfyPage.ctrlC()
|
||||
})
|
||||
|
||||
// Mark before paste
|
||||
await perfMonitor.markEvent('before-paste')
|
||||
|
||||
// Paste operation with performance tracking
|
||||
await perfMonitor.measureOperation('paste-operation', async () => {
|
||||
await comfyPage.page.keyboard.press('Control+Shift+V')
|
||||
})
|
||||
|
||||
// Mark after paste
|
||||
await perfMonitor.markEvent('after-paste')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
test('Can copy and paste node with link', async ({ comfyPage }) => {
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.ctrlC()
|
||||
await comfyPage.page.keyboard.press('Control+Shift+V')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-node-with-link.png')
|
||||
})
|
||||
|
||||
test('@perf Can copy and paste text', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-text'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Can copy and paste text', async ({ comfyPage }) => {
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
|
||||
await perfMonitor.measureOperation('click-textbox', async () => {
|
||||
await textBox.click()
|
||||
})
|
||||
|
||||
let originalString: string
|
||||
await perfMonitor.measureOperation('get-input-value', async () => {
|
||||
originalString = await textBox.inputValue()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('select-text', async () => {
|
||||
await textBox.selectText()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('copy-text', async () => {
|
||||
await comfyPage.ctrlC(null)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-text-first', async () => {
|
||||
await comfyPage.ctrlV(null)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-text-second', async () => {
|
||||
await comfyPage.ctrlV(null)
|
||||
})
|
||||
|
||||
await textBox.click()
|
||||
const originalString = await textBox.inputValue()
|
||||
await textBox.selectText()
|
||||
await comfyPage.ctrlC(null)
|
||||
await comfyPage.ctrlV(null)
|
||||
await comfyPage.ctrlV(null)
|
||||
const resultString = await textBox.inputValue()
|
||||
expect(resultString).toBe(originalString! + originalString!)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
expect(resultString).toBe(originalString + originalString)
|
||||
})
|
||||
|
||||
// skip reason: fails, did not investigate why
|
||||
test.skip('@perf Can copy and paste widget value', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-widget-value'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Can copy and paste widget value', async ({ comfyPage }) => {
|
||||
// Copy width value (512) from empty latent node to KSampler's seed.
|
||||
// KSampler's seed
|
||||
await perfMonitor.measureOperation('click-ksampler-seed', async () => {
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 1005,
|
||||
y: 281
|
||||
}
|
||||
})
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 1005,
|
||||
y: 281
|
||||
}
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('copy-widget-value', async () => {
|
||||
await comfyPage.ctrlC(null)
|
||||
})
|
||||
|
||||
await comfyPage.ctrlC(null)
|
||||
// Empty latent node's width
|
||||
await perfMonitor.measureOperation('click-empty-latent-width', async () => {
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 718,
|
||||
y: 643
|
||||
}
|
||||
})
|
||||
await comfyPage.canvas.click({
|
||||
position: {
|
||||
x: 718,
|
||||
y: 643
|
||||
}
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-widget-value', async () => {
|
||||
await comfyPage.ctrlV(null)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('confirm-with-enter', async () => {
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
})
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
await comfyPage.ctrlV(null)
|
||||
await comfyPage.page.keyboard.press('Enter')
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('copied-widget-value.png')
|
||||
})
|
||||
|
||||
/**
|
||||
* https://github.com/Comfy-Org/ComfyUI_frontend/issues/98
|
||||
*/
|
||||
test('@perf Paste in text area with node previously copied', async ({
|
||||
test('Paste in text area with node previously copied', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'paste-text-with-node-copied'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('click-empty-latent-node', async () => {
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('copy-node', async () => {
|
||||
await comfyPage.ctrlC(null)
|
||||
})
|
||||
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
await comfyPage.ctrlC(null)
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
|
||||
await perfMonitor.measureOperation('click-textbox', async () => {
|
||||
await textBox.click()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('get-input-value', async () => {
|
||||
await textBox.inputValue()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('select-text', async () => {
|
||||
await textBox.selectText()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('copy-text', async () => {
|
||||
await comfyPage.ctrlC(null)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-text-first', async () => {
|
||||
await comfyPage.ctrlV(null)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-text-second', async () => {
|
||||
await comfyPage.ctrlV(null)
|
||||
})
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
await textBox.click()
|
||||
await textBox.inputValue()
|
||||
await textBox.selectText()
|
||||
await comfyPage.ctrlC(null)
|
||||
await comfyPage.ctrlV(null)
|
||||
await comfyPage.ctrlV(null)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'paste-in-text-area-with-node-previously-copied.png'
|
||||
)
|
||||
})
|
||||
|
||||
test('@perf Copy text area does not copy node', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-text-no-node'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Copy text area does not copy node', async ({ comfyPage }) => {
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
|
||||
await perfMonitor.measureOperation('click-textbox', async () => {
|
||||
await textBox.click()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('get-input-value', async () => {
|
||||
await textBox.inputValue()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('select-text', async () => {
|
||||
await textBox.selectText()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('copy-text', async () => {
|
||||
await comfyPage.ctrlC(null)
|
||||
})
|
||||
|
||||
await textBox.click()
|
||||
await textBox.inputValue()
|
||||
await textBox.selectText()
|
||||
await comfyPage.ctrlC(null)
|
||||
// Unfocus textbox.
|
||||
await perfMonitor.measureOperation('unfocus-textbox', async () => {
|
||||
await comfyPage.page.mouse.click(10, 10)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-attempt', async () => {
|
||||
await comfyPage.ctrlV(null)
|
||||
})
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
await comfyPage.page.mouse.click(10, 10)
|
||||
await comfyPage.ctrlV(null)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('no-node-copied.png')
|
||||
})
|
||||
|
||||
test('@perf Copy node by dragging + alt', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-node-drag-alt'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Copy node by dragging + alt', async ({ comfyPage }) => {
|
||||
// TextEncodeNode1
|
||||
await perfMonitor.measureOperation('mouse-move-to-node', async () => {
|
||||
await comfyPage.page.mouse.move(618, 191)
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('alt-key-down')
|
||||
await comfyPage.page.mouse.move(618, 191)
|
||||
await comfyPage.page.keyboard.down('Alt')
|
||||
|
||||
await perfMonitor.measureOperation('mouse-down', async () => {
|
||||
await comfyPage.page.mouse.down()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('drag-node', async () => {
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('mouse-up', async () => {
|
||||
await comfyPage.page.mouse.up()
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('alt-key-up')
|
||||
await comfyPage.page.mouse.down()
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
await comfyPage.page.mouse.up()
|
||||
await comfyPage.page.keyboard.up('Alt')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('drag-copy-copied-node.png')
|
||||
})
|
||||
|
||||
// skip reason: fails, did not investigate why
|
||||
test.skip('@perf Can undo paste multiple nodes as single action', async ({
|
||||
test('Can undo paste multiple nodes as single action', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'undo-paste-multiple-nodes'
|
||||
const initialCount = await comfyPage.getGraphNodesCount()
|
||||
expect(initialCount).toBeGreaterThan(1)
|
||||
await comfyPage.canvas.click()
|
||||
await comfyPage.ctrlA()
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
await comfyPage.ctrlC()
|
||||
await comfyPage.ctrlV()
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
const pasteCount = await comfyPage.getGraphNodesCount()
|
||||
expect(pasteCount).toBe(initialCount * 2)
|
||||
|
||||
let initialCount: number
|
||||
await perfMonitor.measureOperation('get-initial-count', async () => {
|
||||
initialCount = await comfyPage.getGraphNodesCount()
|
||||
})
|
||||
expect(initialCount!).toBeGreaterThan(1)
|
||||
|
||||
await perfMonitor.measureOperation('click-canvas', async () => {
|
||||
await comfyPage.canvas.click()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('select-all', async () => {
|
||||
await comfyPage.ctrlA()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('mouse-move', async () => {
|
||||
await comfyPage.page.mouse.move(10, 10)
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('copy-all-nodes', async () => {
|
||||
await comfyPage.ctrlC()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-all-nodes', async () => {
|
||||
await comfyPage.ctrlV()
|
||||
})
|
||||
|
||||
let pasteCount: number
|
||||
await perfMonitor.measureOperation('get-paste-count', async () => {
|
||||
pasteCount = await comfyPage.getGraphNodesCount()
|
||||
})
|
||||
expect(pasteCount!).toBe(initialCount! * 2)
|
||||
|
||||
await perfMonitor.measureOperation('undo-paste', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
|
||||
let undoCount: number
|
||||
await perfMonitor.measureOperation('get-undo-count', async () => {
|
||||
undoCount = await comfyPage.getGraphNodesCount()
|
||||
})
|
||||
expect(undoCount!).toBe(initialCount!)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
await comfyPage.ctrlZ()
|
||||
const undoCount = await comfyPage.getGraphNodesCount()
|
||||
expect(undoCount).toBe(initialCount)
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 102 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 100 KiB |
@@ -2,7 +2,6 @@ import { Locator, expect } from '@playwright/test'
|
||||
|
||||
import type { Keybinding } from '../../src/schemas/keyBindingSchema'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
test.describe('Load workflow warning', () => {
|
||||
test('Should display a warning when loading a workflow with missing nodes', async ({
|
||||
@@ -16,89 +15,46 @@ test.describe('Load workflow warning', () => {
|
||||
})
|
||||
})
|
||||
|
||||
test.skip('@perf Does not report warning on undo/redo', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'undo-redo-no-warning'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Does not report warning on undo/redo', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.NodeSearchBoxImpl', 'default')
|
||||
|
||||
await perfMonitor.measureOperation('load-workflow', async () => {
|
||||
await comfyPage.loadWorkflow('missing_nodes')
|
||||
})
|
||||
|
||||
await comfyPage.loadWorkflow('missing_nodes')
|
||||
await comfyPage.closeDialog()
|
||||
|
||||
// Make a change to the graph
|
||||
await perfMonitor.measureOperation('add-node-sequence', async () => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||
})
|
||||
await comfyPage.doubleClickCanvas()
|
||||
await comfyPage.searchBox.fillAndSelectFirstNode('KSampler')
|
||||
|
||||
// Undo and redo the change
|
||||
await perfMonitor.measureOperation('undo-operation', async () => {
|
||||
await comfyPage.ctrlZ()
|
||||
})
|
||||
|
||||
await comfyPage.ctrlZ()
|
||||
await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
|
||||
|
||||
await perfMonitor.measureOperation('redo-operation', async () => {
|
||||
await comfyPage.ctrlY()
|
||||
})
|
||||
|
||||
await comfyPage.ctrlY()
|
||||
await expect(comfyPage.page.locator('.comfy-missing-nodes')).not.toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test.describe('Execution error', () => {
|
||||
test.skip('@perf Should display an error message when an execution error occurs', async ({
|
||||
test('Should display an error message when an execution error occurs', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'execution-error-display'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('load-workflow', async () => {
|
||||
await comfyPage.loadWorkflow('execution_error')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('queue-execution', async () => {
|
||||
await comfyPage.queueButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
await comfyPage.loadWorkflow('execution_error')
|
||||
await comfyPage.queueButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
// Wait for the element with the .comfy-execution-error selector to be visible
|
||||
const executionError = comfyPage.page.locator('.comfy-error-report')
|
||||
await expect(executionError).toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test.skip('@perf Can display Issue Report form', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'issue-report-form-display'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('load-workflow', async () => {
|
||||
await comfyPage.loadWorkflow('execution_error')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('queue-execution', async () => {
|
||||
await comfyPage.queueButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
test('Can display Issue Report form', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('execution_error')
|
||||
await comfyPage.queueButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await comfyPage.page.getByLabel('Help Fix This').click()
|
||||
const issueReportForm = comfyPage.page.getByText(
|
||||
'Submit Error Report (Optional)'
|
||||
)
|
||||
await expect(issueReportForm).toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -399,29 +355,18 @@ test.describe('Error dialog', () => {
|
||||
})
|
||||
|
||||
test.describe('Signin dialog', () => {
|
||||
// Skip because fails with vue widget nodes (reason not investigated)
|
||||
test.skip('@perf Paste content to signin dialog should not paste node on canvas', async ({
|
||||
test('Paste content to signin dialog should not paste node on canvas', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'signin-dialog-paste-isolation'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const nodeNum = (await comfyPage.getNodes()).length
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
await comfyPage.ctrlC()
|
||||
|
||||
await perfMonitor.measureOperation('copy-node-sequence', async () => {
|
||||
await comfyPage.clickEmptyLatentNode()
|
||||
await comfyPage.ctrlC()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('widget-text-operations', async () => {
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.fill('test_password')
|
||||
await textBox.press('Control+a')
|
||||
await textBox.press('Control+c')
|
||||
})
|
||||
const textBox = comfyPage.widgetTextBox
|
||||
await textBox.click()
|
||||
await textBox.fill('test_password')
|
||||
await textBox.press('Control+a')
|
||||
await textBox.press('Control+c')
|
||||
|
||||
await comfyPage.page.evaluate(() => {
|
||||
window['app'].extensionManager.dialog.showSignInDialog()
|
||||
@@ -433,7 +378,5 @@ test.describe('Signin dialog', () => {
|
||||
await expect(input).toHaveValue('test_password')
|
||||
|
||||
expect(await comfyPage.getNodes()).toHaveLength(nodeNum)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,35 +1,15 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
test.describe('DOM Widget', () => {
|
||||
test('@perf Collapsed multiline textarea is not visible', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'collapsed-multiline-textarea-visibility'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('load-workflow', async () => {
|
||||
await comfyPage.loadWorkflow('collapsed_multiline')
|
||||
})
|
||||
|
||||
test('Collapsed multiline textarea is not visible', async ({ comfyPage }) => {
|
||||
await comfyPage.loadWorkflow('collapsed_multiline')
|
||||
const textareaWidget = comfyPage.page.locator('.comfy-multiline-input')
|
||||
await expect(textareaWidget).not.toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test.skip('@perf Multiline textarea correctly collapses', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'multiline-textarea-collapse'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Multiline textarea correctly collapses', async ({ comfyPage }) => {
|
||||
const multilineTextAreas = comfyPage.page.locator('.comfy-multiline-input')
|
||||
const firstMultiline = multilineTextAreas.first()
|
||||
const lastMultiline = multilineTextAreas.last()
|
||||
@@ -37,91 +17,34 @@ test.describe('DOM Widget', () => {
|
||||
await expect(firstMultiline).toBeVisible()
|
||||
await expect(lastMultiline).toBeVisible()
|
||||
|
||||
let nodes: any[]
|
||||
await perfMonitor.measureOperation('get-nodes-by-type', async () => {
|
||||
nodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('before-collapse')
|
||||
|
||||
await perfMonitor.measureOperation('collapse-all-nodes', async () => {
|
||||
for (const node of nodes!) {
|
||||
await node.click('collapse')
|
||||
}
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('after-collapse')
|
||||
|
||||
const nodes = await comfyPage.getNodeRefsByType('CLIPTextEncode')
|
||||
for (const node of nodes) {
|
||||
await node.click('collapse')
|
||||
}
|
||||
await expect(firstMultiline).not.toBeVisible()
|
||||
await expect(lastMultiline).not.toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
// Skip because fails with vue widget nodes (reason not investigated)
|
||||
test.skip('@perf Position update when entering focus mode', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'focus-mode-position-update'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('set-menu-setting', async () => {
|
||||
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('toggle-focus-mode', async () => {
|
||||
await comfyPage.executeCommand('Workspace.ToggleFocusMode')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('wait-frame-update', async () => {
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
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')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
// No DOM widget should be created by creation of interim LGraphNode objects.
|
||||
test.skip('@perf Copy node with DOM widget by dragging + alt', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-node-alt-drag'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
let initialCount: number
|
||||
await perfMonitor.measureOperation('get-initial-widget-count', async () => {
|
||||
initialCount = await comfyPage.getDOMWidgetCount()
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('before-copy-operation')
|
||||
test('Copy node with DOM widget by dragging + alt', async ({ comfyPage }) => {
|
||||
const initialCount = await comfyPage.getDOMWidgetCount()
|
||||
|
||||
// TextEncodeNode1
|
||||
await perfMonitor.measureOperation('position-mouse-on-node', async () => {
|
||||
await comfyPage.page.mouse.move(618, 191)
|
||||
})
|
||||
await comfyPage.page.mouse.move(618, 191)
|
||||
await comfyPage.page.keyboard.down('Alt')
|
||||
await comfyPage.page.mouse.down()
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
await comfyPage.page.mouse.up()
|
||||
await comfyPage.page.keyboard.up('Alt')
|
||||
|
||||
await perfMonitor.measureOperation('alt-drag-copy', async () => {
|
||||
await comfyPage.page.keyboard.down('Alt')
|
||||
await comfyPage.page.mouse.down()
|
||||
await comfyPage.page.mouse.move(100, 100)
|
||||
await comfyPage.page.mouse.up()
|
||||
await comfyPage.page.keyboard.up('Alt')
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('after-copy-operation')
|
||||
|
||||
let finalCount: number
|
||||
await perfMonitor.measureOperation('get-final-widget-count', async () => {
|
||||
finalCount = await comfyPage.getDOMWidgetCount()
|
||||
})
|
||||
|
||||
expect(finalCount!).toBe(initialCount! + 1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
const finalCount = await comfyPage.getDOMWidgetCount()
|
||||
expect(finalCount).toBe(initialCount + 1)
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 82 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 95 KiB |
@@ -1,7 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
test.describe('Graph Canvas Menu', () => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
@@ -10,28 +9,14 @@ test.describe('Graph Canvas Menu', () => {
|
||||
await comfyPage.setSetting('Comfy.LinkRenderMode', 2)
|
||||
})
|
||||
|
||||
test('@perf Can toggle link visibility', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'toggle-link-visibility'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Can toggle link visibility', async ({ comfyPage }) => {
|
||||
// Note: `Comfy.Graph.CanvasMenu` is disabled in comfyPage setup.
|
||||
// so no cleanup is needed.
|
||||
await perfMonitor.measureOperation('enable-canvas-menu', async () => {
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
})
|
||||
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
|
||||
|
||||
const button = comfyPage.page.getByTestId('toggle-link-visibility-button')
|
||||
|
||||
await perfMonitor.markEvent('before-hide-links')
|
||||
await perfMonitor.measureOperation('hide-links', async () => {
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
await perfMonitor.markEvent('after-hide-links')
|
||||
|
||||
// Screenshot assertions and validations stay outside performance monitoring
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
await expect(comfyPage.canvas).toHaveScreenshot(
|
||||
'canvas-with-hidden-links.png'
|
||||
)
|
||||
@@ -42,21 +27,13 @@ test.describe('Graph Canvas Menu', () => {
|
||||
hiddenLinkRenderMode
|
||||
)
|
||||
|
||||
await perfMonitor.markEvent('before-show-links')
|
||||
await perfMonitor.measureOperation('show-links', async () => {
|
||||
await button.click()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
await perfMonitor.markEvent('after-show-links')
|
||||
|
||||
// Screenshot assertions and validations stay outside performance monitoring
|
||||
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 perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 98 KiB |
@@ -2,7 +2,6 @@ import { expect } from '@playwright/test'
|
||||
|
||||
import { ComfyPage, comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import type { NodeReference } from '../fixtures/utils/litegraphUtils'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
test.describe('Group Node', () => {
|
||||
test.describe('Node library sidebar', () => {
|
||||
@@ -18,51 +17,29 @@ test.describe('Group Node', () => {
|
||||
await libraryTab.open()
|
||||
})
|
||||
|
||||
test('Is added to node library sidebar', async ({ comfyPage }) => {
|
||||
test.skip('Is added to node library sidebar', async ({ comfyPage }) => {
|
||||
expect(await libraryTab.getFolder('group nodes').count()).toBe(1)
|
||||
})
|
||||
|
||||
test('@perf Can be added to canvas using node library sidebar', async ({
|
||||
test.skip('Can be added to canvas using node library sidebar', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'add-group-node-from-library'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const initialNodeCount = await comfyPage.getGraphNodesCount()
|
||||
|
||||
// Add group node from node library sidebar
|
||||
await perfMonitor.measureOperation('expand-category-folder', async () => {
|
||||
await libraryTab.getFolder(groupNodeCategory).click()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('add-node-from-library', async () => {
|
||||
await libraryTab.getNode(groupNodeName).click()
|
||||
})
|
||||
await libraryTab.getFolder(groupNodeCategory).click()
|
||||
await libraryTab.getNode(groupNodeName).click()
|
||||
|
||||
// Verify the node is added to the canvas
|
||||
expect(await comfyPage.getGraphNodesCount()).toBe(initialNodeCount + 1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Can be bookmarked and unbookmarked', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'bookmark-unbookmark-group-node'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('expand-category-folder', async () => {
|
||||
await libraryTab.getFolder(groupNodeCategory).click()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('bookmark-node', async () => {
|
||||
await libraryTab
|
||||
.getNode(groupNodeName)
|
||||
.locator('.bookmark-button')
|
||||
.click()
|
||||
})
|
||||
test.skip('Can be bookmarked and unbookmarked', async ({ comfyPage }) => {
|
||||
await libraryTab.getFolder(groupNodeCategory).click()
|
||||
await libraryTab
|
||||
.getNode(groupNodeName)
|
||||
.locator('.bookmark-button')
|
||||
.click()
|
||||
|
||||
// Verify the node is added to the bookmarks tab
|
||||
expect(
|
||||
@@ -72,23 +49,19 @@ test.describe('Group Node', () => {
|
||||
expect(await libraryTab.getNode(groupNodeName).count()).not.toBe(0)
|
||||
|
||||
// Unbookmark the node
|
||||
await perfMonitor.measureOperation('unbookmark-node', async () => {
|
||||
await libraryTab
|
||||
.getNode(groupNodeName)
|
||||
.locator('.bookmark-button')
|
||||
.first()
|
||||
.click()
|
||||
})
|
||||
await libraryTab
|
||||
.getNode(groupNodeName)
|
||||
.locator('.bookmark-button')
|
||||
.first()
|
||||
.click()
|
||||
|
||||
// Verify the node is removed from the bookmarks tab
|
||||
expect(
|
||||
await comfyPage.getSetting('Comfy.NodeLibrary.Bookmarks.V2')
|
||||
).toHaveLength(0)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('Displays preview on bookmark hover', async ({ comfyPage }) => {
|
||||
test.skip('Displays preview on bookmark hover', async ({ comfyPage }) => {
|
||||
await libraryTab.getFolder(groupNodeCategory).click()
|
||||
await libraryTab
|
||||
.getNode(groupNodeName)
|
||||
@@ -122,38 +95,18 @@ test.describe('Group Node', () => {
|
||||
)
|
||||
})
|
||||
|
||||
test.skip('@perf Displays tooltip on title hover', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'group-node-tooltip-display'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test.skip('Displays tooltip on title hover', async ({ comfyPage }) => {
|
||||
await comfyPage.setSetting('Comfy.EnableTooltips', true)
|
||||
|
||||
await perfMonitor.measureOperation('convert-to-group-node', async () => {
|
||||
await comfyPage.convertAllNodesToGroupNode('Group Node')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('hover-for-tooltip', async () => {
|
||||
await comfyPage.page.mouse.move(47, 173)
|
||||
const tooltipTimeout = 500
|
||||
await comfyPage.page.waitForTimeout(tooltipTimeout + 16)
|
||||
})
|
||||
|
||||
await comfyPage.convertAllNodesToGroupNode('Group Node')
|
||||
await comfyPage.page.mouse.move(47, 173)
|
||||
const tooltipTimeout = 500
|
||||
await comfyPage.page.waitForTimeout(tooltipTimeout + 16)
|
||||
await expect(comfyPage.page.locator('.node-tooltip')).toBeVisible()
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
// Skip because fails with vue widget nodes (reason not investigated)
|
||||
test.skip('@perf Manage group opens with the correct group selected', async ({
|
||||
test.skip('Manage group opens with the correct group selected', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'manage-group-node-selection'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const makeGroup = async (name, type1, type2) => {
|
||||
const node1 = (await comfyPage.getNodeRefsByType(type1))[0]
|
||||
const node2 = (await comfyPage.getNodeRefsByType(type2))[0]
|
||||
@@ -164,44 +117,21 @@ test.describe('Group Node', () => {
|
||||
return await node2.convertToGroupNode(name)
|
||||
}
|
||||
|
||||
let group1
|
||||
await perfMonitor.measureOperation('create-first-group', async () => {
|
||||
group1 = await makeGroup('g1', 'CLIPTextEncode', 'CheckpointLoaderSimple')
|
||||
})
|
||||
|
||||
let group2
|
||||
await perfMonitor.measureOperation('create-second-group', async () => {
|
||||
group2 = await makeGroup('g2', 'EmptyLatentImage', 'KSampler')
|
||||
})
|
||||
|
||||
let manage1
|
||||
await perfMonitor.measureOperation('open-first-manage-dialog', async () => {
|
||||
manage1 = await group1.manageGroupNode()
|
||||
await comfyPage.nextFrame()
|
||||
})
|
||||
|
||||
expect(await manage1.getSelectedNodeType()).toBe('g1')
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'close-first-manage-dialog',
|
||||
async () => {
|
||||
await manage1.close()
|
||||
}
|
||||
const group1 = await makeGroup(
|
||||
'g1',
|
||||
'CLIPTextEncode',
|
||||
'CheckpointLoaderSimple'
|
||||
)
|
||||
const group2 = await makeGroup('g2', 'EmptyLatentImage', 'KSampler')
|
||||
|
||||
const manage1 = await group1.manageGroupNode()
|
||||
await comfyPage.nextFrame()
|
||||
expect(await manage1.getSelectedNodeType()).toBe('g1')
|
||||
await manage1.close()
|
||||
await expect(manage1.root).not.toBeVisible()
|
||||
|
||||
let manage2
|
||||
await perfMonitor.measureOperation(
|
||||
'open-second-manage-dialog',
|
||||
async () => {
|
||||
manage2 = await group2.manageGroupNode()
|
||||
}
|
||||
)
|
||||
|
||||
const manage2 = await group2.manageGroupNode()
|
||||
expect(await manage2.getSelectedNodeType()).toBe('g2')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('Preserves hidden input configuration when containing duplicate node types', async ({
|
||||
@@ -235,14 +165,9 @@ test.describe('Group Node', () => {
|
||||
expect(visibleInputCount).toBe(2)
|
||||
})
|
||||
|
||||
test.skip('@perf Reconnects inputs after configuration changed via manage dialog save', async ({
|
||||
test.skip('Reconnects inputs after configuration changed via manage dialog save', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'reconnect-inputs-after-config-change'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
const expectSingleNode = async (type: string) => {
|
||||
const nodes = await comfyPage.getNodeRefsByType(type)
|
||||
expect(nodes).toHaveLength(1)
|
||||
@@ -250,65 +175,30 @@ test.describe('Group Node', () => {
|
||||
}
|
||||
const latent = await expectSingleNode('EmptyLatentImage')
|
||||
const sampler = await expectSingleNode('KSampler')
|
||||
|
||||
// Remove existing link
|
||||
const samplerInput = await sampler.getInput(0)
|
||||
await perfMonitor.measureOperation('remove-existing-links', async () => {
|
||||
await samplerInput.removeLinks()
|
||||
})
|
||||
|
||||
await samplerInput.removeLinks()
|
||||
// Group latent + sampler
|
||||
await perfMonitor.measureOperation('select-nodes-for-group', async () => {
|
||||
await latent.click('title', {
|
||||
modifiers: ['Shift']
|
||||
})
|
||||
await sampler.click('title', {
|
||||
modifiers: ['Shift']
|
||||
})
|
||||
await latent.click('title', {
|
||||
modifiers: ['Shift']
|
||||
})
|
||||
|
||||
let groupNode
|
||||
await perfMonitor.measureOperation('convert-to-group-node', async () => {
|
||||
groupNode = await sampler.convertToGroupNode()
|
||||
await sampler.click('title', {
|
||||
modifiers: ['Shift']
|
||||
})
|
||||
|
||||
const groupNode = await sampler.convertToGroupNode()
|
||||
// Connect node to group
|
||||
const ckpt = await expectSingleNode('CheckpointLoaderSimple')
|
||||
let input
|
||||
await perfMonitor.measureOperation('connect-nodes', async () => {
|
||||
input = await ckpt.connectOutput(0, groupNode, 0)
|
||||
})
|
||||
|
||||
const input = await ckpt.connectOutput(0, groupNode, 0)
|
||||
expect(await input.getLinkCount()).toBe(1)
|
||||
|
||||
// Modify the group node via manage dialog
|
||||
await perfMonitor.markEvent('before-manage-dialog')
|
||||
|
||||
let manage
|
||||
await perfMonitor.measureOperation('open-manage-dialog', async () => {
|
||||
manage = await groupNode.manageGroupNode()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'configure-in-manage-dialog',
|
||||
async () => {
|
||||
await manage.selectNode('KSampler')
|
||||
await manage.changeTab('Inputs')
|
||||
await manage.setLabel('model', 'test')
|
||||
await manage.save()
|
||||
}
|
||||
)
|
||||
|
||||
await perfMonitor.measureOperation('close-manage-dialog', async () => {
|
||||
await manage.close()
|
||||
})
|
||||
|
||||
await perfMonitor.markEvent('after-manage-dialog')
|
||||
|
||||
const manage = await groupNode.manageGroupNode()
|
||||
await manage.selectNode('KSampler')
|
||||
await manage.changeTab('Inputs')
|
||||
await manage.setLabel('model', 'test')
|
||||
await manage.save()
|
||||
await manage.close()
|
||||
// Ensure the link is still present
|
||||
expect(await input.getLinkCount()).toBe(1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('Loads from a workflow using the legacy path separator ("/")', async ({
|
||||
@@ -364,131 +254,57 @@ test.describe('Group Node', () => {
|
||||
await groupNode.copy()
|
||||
})
|
||||
|
||||
test('@perf Copies and pastes group node within the same workflow', async ({
|
||||
test('Copies and pastes group node within the same workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-group-node-same-workflow'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('paste-group-node', async () => {
|
||||
await comfyPage.ctrlV()
|
||||
})
|
||||
|
||||
await comfyPage.ctrlV()
|
||||
await verifyNodeLoaded(comfyPage, 2)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Copies and pastes group node after clearing workflow', async ({
|
||||
test('Copies and pastes group node after clearing workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-group-node-after-clear'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('clear-workflow', async () => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand([
|
||||
'Edit',
|
||||
'Clear Workflow'
|
||||
])
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-group-node', async () => {
|
||||
await comfyPage.ctrlV()
|
||||
})
|
||||
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand([
|
||||
'Edit',
|
||||
'Clear Workflow'
|
||||
])
|
||||
await comfyPage.ctrlV()
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Copies and pastes group node into a newly created blank workflow', async ({
|
||||
test('Copies and pastes group node into a newly created blank workflow', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-group-node-new-workflow'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('create-new-workflow', async () => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-group-node', async () => {
|
||||
await comfyPage.ctrlV()
|
||||
})
|
||||
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.ctrlV()
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Copies and pastes group node across different workflows', async ({
|
||||
test('Copies and pastes group node across different workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'copy-paste-group-node-different-workflow'
|
||||
await comfyPage.loadWorkflow('default')
|
||||
await comfyPage.ctrlV()
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
})
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'load-different-workflow',
|
||||
async () => {
|
||||
await comfyPage.loadWorkflow('default')
|
||||
}
|
||||
test('Serializes group node after copy and paste across workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
await comfyPage.ctrlV()
|
||||
const currentGraphState = await comfyPage.page.evaluate(() =>
|
||||
window['app'].graph.serialize()
|
||||
)
|
||||
|
||||
await perfMonitor.measureOperation('paste-group-node', async () => {
|
||||
await comfyPage.ctrlV()
|
||||
})
|
||||
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Serializes group node after copy and paste across workflows', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'serialize-group-node-cross-workflow'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation('create-new-workflow', async () => {
|
||||
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation('paste-group-node', async () => {
|
||||
await comfyPage.ctrlV()
|
||||
})
|
||||
|
||||
let currentGraphState
|
||||
await perfMonitor.measureOperation('serialize-graph', async () => {
|
||||
currentGraphState = await comfyPage.page.evaluate(() =>
|
||||
window['app'].graph.serialize()
|
||||
)
|
||||
})
|
||||
|
||||
await test.step('Load workflow containing a group node pasted from a different workflow', async () => {
|
||||
await perfMonitor.measureOperation(
|
||||
'load-serialized-workflow',
|
||||
async () => {
|
||||
await comfyPage.page.evaluate(
|
||||
(workflow) => window['app'].loadGraphData(workflow),
|
||||
currentGraphState
|
||||
)
|
||||
await comfyPage.nextFrame()
|
||||
}
|
||||
await comfyPage.page.evaluate(
|
||||
(workflow) => window['app'].loadGraphData(workflow),
|
||||
currentGraphState
|
||||
)
|
||||
|
||||
await comfyPage.nextFrame()
|
||||
await verifyNodeLoaded(comfyPage, 1)
|
||||
})
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -499,31 +315,12 @@ test.describe('Group Node', () => {
|
||||
await comfyPage.page.waitForTimeout(300)
|
||||
expect(await comfyPage.getVisibleToastCount()).toBe(1)
|
||||
})
|
||||
test('@perf Convert to group node, selected 1 node', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'convert-single-node-to-group-keybinding'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Convert to group node, selected 1 node', async ({ comfyPage }) => {
|
||||
expect(await comfyPage.getVisibleToastCount()).toBe(0)
|
||||
|
||||
await perfMonitor.measureOperation('select-node', async () => {
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'trigger-group-keybinding',
|
||||
async () => {
|
||||
await comfyPage.page.keyboard.press('Alt+g')
|
||||
await comfyPage.page.waitForTimeout(300)
|
||||
}
|
||||
)
|
||||
|
||||
await comfyPage.clickTextEncodeNode1()
|
||||
await comfyPage.page.keyboard.press('Alt+g')
|
||||
await comfyPage.page.waitForTimeout(300)
|
||||
expect(await comfyPage.getVisibleToastCount()).toBe(1)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 90 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 62 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 103 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 108 KiB |
|
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
|
Before Width: | Height: | Size: 89 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 77 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 99 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 56 KiB |
|
Before Width: | Height: | Size: 69 KiB After Width: | Height: | Size: 67 KiB |
|
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 98 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 102 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 74 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 68 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 97 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 96 KiB |
|
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 93 KiB |
@@ -1,7 +1,6 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
function listenForEvent(): Promise<Event> {
|
||||
return new Promise<Event>((resolve) => {
|
||||
@@ -12,50 +11,27 @@ function listenForEvent(): Promise<Event> {
|
||||
}
|
||||
|
||||
test.describe('Canvas Event', () => {
|
||||
test.skip('@perf Emit litegraph:canvas empty-release', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'canvas-empty-release'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Emit litegraph:canvas empty-release', async ({ comfyPage }) => {
|
||||
const eventPromise = comfyPage.page.evaluate(listenForEvent)
|
||||
|
||||
await perfMonitor.measureOperation('disconnect-edge', async () => {
|
||||
await comfyPage.disconnectEdge()
|
||||
})
|
||||
|
||||
const disconnectPromise = comfyPage.disconnectEdge()
|
||||
const event = await eventPromise
|
||||
await disconnectPromise
|
||||
|
||||
expect(event).not.toBeNull()
|
||||
// No further check on event content as the content is dropped by
|
||||
// playwright for some reason.
|
||||
// See https://github.com/microsoft/playwright/issues/31580
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
// Skip because fails with vue widget nodes (reason not investigated)
|
||||
test.skip('@perf Emit litegraph:canvas empty-double-click', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'canvas-double-click'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test('Emit litegraph:canvas empty-double-click', async ({ comfyPage }) => {
|
||||
const eventPromise = comfyPage.page.evaluate(listenForEvent)
|
||||
|
||||
await perfMonitor.measureOperation('double-click-canvas', async () => {
|
||||
await comfyPage.doubleClickCanvas()
|
||||
})
|
||||
|
||||
const doubleClickPromise = comfyPage.doubleClickCanvas()
|
||||
const event = await eventPromise
|
||||
await doubleClickPromise
|
||||
|
||||
expect(event).not.toBeNull()
|
||||
// No further check on event content as the content is dropped by
|
||||
// playwright for some reason.
|
||||
// See https://github.com/microsoft/playwright/issues/31580
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 105 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 98 KiB |
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 98 KiB |
@@ -3,200 +3,109 @@ import { expect } from '@playwright/test'
|
||||
import type { ComfyApp } from '../../src/scripts/app'
|
||||
import { NodeBadgeMode } from '../../src/types/nodeSource'
|
||||
import { comfyPageFixture as test } from '../fixtures/ComfyPage'
|
||||
import { PerformanceMonitor } from '../helpers/performanceMonitor'
|
||||
|
||||
test.describe('Node Badge', () => {
|
||||
test('@perf Can add badge', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'add-single-badge'
|
||||
test('Can add badge', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
for (const node of nodes) {
|
||||
node.badges = [new LGraphBadge({ text: 'Test Badge' })]
|
||||
}
|
||||
|
||||
await perfMonitor.measureOperation('add-badge-to-nodes', async () => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
for (const node of nodes) {
|
||||
node.badges = [new LGraphBadge({ text: 'Test Badge' })]
|
||||
}
|
||||
|
||||
graph.setDirtyCanvas(true, true)
|
||||
})
|
||||
graph.setDirtyCanvas(true, true)
|
||||
})
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-badge.png')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Can add multiple badges', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'add-multiple-badges'
|
||||
test('Can add multiple badges', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'add-multiple-badges-to-nodes',
|
||||
async () => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
for (const node of nodes) {
|
||||
node.badges = [
|
||||
new LGraphBadge({ text: 'Test Badge 1' }),
|
||||
new LGraphBadge({ text: 'Test Badge 2' })
|
||||
]
|
||||
}
|
||||
|
||||
graph.setDirtyCanvas(true, true)
|
||||
})
|
||||
for (const node of nodes) {
|
||||
node.badges = [
|
||||
new LGraphBadge({ text: 'Test Badge 1' }),
|
||||
new LGraphBadge({ text: 'Test Badge 2' })
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
graph.setDirtyCanvas(true, true)
|
||||
})
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-badge-multiple.png')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Can add badge left-side', async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'add-badge-left-position'
|
||||
test('Can add badge left-side', async ({ comfyPage }) => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
for (const node of nodes) {
|
||||
node.badges = [new LGraphBadge({ text: 'Test Badge' })]
|
||||
// @ts-expect-error - Enum value
|
||||
node.badgePosition = 'top-left'
|
||||
}
|
||||
|
||||
await perfMonitor.measureOperation('add-badge-with-position', async () => {
|
||||
await comfyPage.page.evaluate(() => {
|
||||
const LGraphBadge = window['LGraphBadge']
|
||||
const app = window['app'] as ComfyApp
|
||||
const graph = app.graph
|
||||
const nodes = graph.nodes
|
||||
|
||||
for (const node of nodes) {
|
||||
node.badges = [new LGraphBadge({ text: 'Test Badge' })]
|
||||
// @ts-expect-error - Enum value
|
||||
node.badgePosition = 'top-left'
|
||||
}
|
||||
|
||||
graph.setDirtyCanvas(true, true)
|
||||
})
|
||||
graph.setDirtyCanvas(true, true)
|
||||
})
|
||||
|
||||
await expect(comfyPage.canvas).toHaveScreenshot('node-badge-left.png')
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Node source badge', () => {
|
||||
Object.values(NodeBadgeMode).forEach(async (mode) => {
|
||||
test(`@perf Shows node badges (${mode})`, async ({ comfyPage }) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = `node-source-badge-${mode}`
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
test(`Shows node badges (${mode})`, async ({ comfyPage }) => {
|
||||
// Execution error workflow has both custom node and core node.
|
||||
await perfMonitor.measureOperation('load-workflow', async () => {
|
||||
await comfyPage.loadWorkflow('execution_error')
|
||||
})
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'configure-badge-settings',
|
||||
async () => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeSourceBadgeMode',
|
||||
mode
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.NodeBadge.NodeIdBadgeMode', mode)
|
||||
}
|
||||
)
|
||||
|
||||
await perfMonitor.measureOperation('render-badges', async () => {
|
||||
await comfyPage.nextFrame()
|
||||
await comfyPage.resetView()
|
||||
})
|
||||
|
||||
await comfyPage.loadWorkflow('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`)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
test.describe('Node badge color', () => {
|
||||
test('@perf Can show node badge with unknown color palette', async ({
|
||||
test('Can show node badge with unknown color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'node-badge-unknown-color-palette'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'configure-badge-and-palette',
|
||||
async () => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'unknown')
|
||||
}
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'render-with-unknown-palette',
|
||||
async () => {
|
||||
await comfyPage.nextFrame()
|
||||
// Click empty space to trigger canvas re-render.
|
||||
await comfyPage.clickEmptySpace()
|
||||
}
|
||||
)
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
|
||||
test('@perf Can show node badge with light color palette', async ({
|
||||
test('Can show node badge with light color palette', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const perfMonitor = new PerformanceMonitor(comfyPage.page)
|
||||
const testName = 'node-badge-light-color-palette'
|
||||
|
||||
await perfMonitor.startMonitoring(testName)
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'configure-badge-and-light-palette',
|
||||
async () => {
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
await comfyPage.setSetting('Comfy.ColorPalette', 'light')
|
||||
}
|
||||
await comfyPage.setSetting(
|
||||
'Comfy.NodeBadge.NodeIdBadgeMode',
|
||||
NodeBadgeMode.ShowAll
|
||||
)
|
||||
|
||||
await perfMonitor.measureOperation(
|
||||
'render-with-light-palette',
|
||||
async () => {
|
||||
await comfyPage.nextFrame()
|
||||
// Click empty space to trigger canvas re-render.
|
||||
await comfyPage.clickEmptySpace()
|
||||
}
|
||||
)
|
||||
|
||||
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'
|
||||
)
|
||||
|
||||
await perfMonitor.finishMonitoring(testName)
|
||||
})
|
||||
})
|
||||
|
||||