Compare commits

...

10 Commits

Author SHA1 Message Date
bymyself
967bbd6ff1 chore: fix lint and typecheck issues exposed by main rebase
- Replace .locator('[data-testid="error-overlay"]') with
  getByTestId('error-overlay') to satisfy the
  playwright/prefer-native-locators rule.
- Replace relative ../fixtures/ComfyPage imports with the @e2e/
  path alias to satisfy no-restricted-imports.
- Migrate vueNode.titleInput usage to the new vueNode.titleEditor
  API after the VueNodeFixture refactor on main.
- Replace .not.toBeVisible() with .toBeHidden() to satisfy the
  playwright/no-useless-not rule.

Pre-existing in the PR but newly enforced after the merge with main.
2026-05-03 23:15:38 -07:00
bymyself
b8376e2a17 test: add data-testid to error message and use it in errorsTab spec
Add a data-testid='error-card-message' to the error message <p> element
in ErrorNodeCard.vue and update the spec to locate it via getByTestId
instead of the .whitespace-pre-wrap Tailwind utility class. The previous
selector coupled the test to a styling decision; the testid is a
behavioral selector that survives style changes.

Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/9557#discussion_r2982269131
2026-05-03 23:10:01 -07:00
bymyself
55324d0bf7 test: replace icon utility-class selectors with semantic locators
Use getByRole('button', { name: 'Locate node on canvas' }) instead of
matching the .icon-[lucide--locate] UnoCSS/Iconify utility class. The
locate button already has an aria-label, so the role-based locator is
behavioral rather than coupling the test to icon implementation
details.

Addresses review feedback:
https://github.com/Comfy-Org/ComfyUI_frontend/pull/9557#discussion_r2982269120
2026-05-03 23:09:40 -07:00
bymyself
3e195e6650 chore: resolve merge conflicts with main
Take origin/main's version of queueNotificationBanners.spec.ts, which has
been superseded by a more comprehensive test suite that uses the
TestIds.queue.notificationBanner selector and dispatches promptQueueing/
promptQueued events directly via the API.
2026-05-03 23:08:15 -07:00
Christian Byrne
74997e556d Merge branch 'main' into playwright-coverage/wave-3-medium 2026-04-19 18:52:03 -07:00
bymyself
684261d592 fix: enable ShowErrorsTab setting for queue notification tests
The error overlay only appears when Comfy.RightSidePanel.ShowErrorsTab
is enabled. Matches the pattern used in execution.spec.ts.
2026-03-16 07:27:51 -07:00
GitHub Action
1d231bd988 [automated] Apply ESLint and Oxfmt fixes 2026-03-16 13:57:30 +00:00
bymyself
d370b182e7 fix: correct toast notification test setup for wave 3 2026-03-16 06:54:40 -07:00
GitHub Action
1a8a2be4c3 [automated] Apply ESLint and Oxfmt fixes 2026-03-16 06:54:40 -07:00
bymyself
026bd4d757 test: add 24 Playwright tests for medium-priority UI areas
- Errors tab interactions (6 tests)
- Vue node header actions — collapse/expand/rename (6 tests)
- Queue notification banners (6 tests)
- Settings sidebar button (6 tests)
2026-03-16 06:54:40 -07:00
4 changed files with 277 additions and 0 deletions

View File

@@ -0,0 +1,150 @@
import type { Page } from '@playwright/test'
import {
comfyPageFixture as test,
comfyExpect as expect
} from '@e2e/fixtures/ComfyPage'
async function triggerExecutionError(comfyPage: {
canvasOps: { disconnectEdge: () => Promise<void> }
page: Page
command: { executeCommand: (cmd: string) => Promise<void> }
}) {
await comfyPage.canvasOps.disconnectEdge()
await comfyPage.page.keyboard.press('Escape')
await comfyPage.command.executeCommand('Comfy.QueuePrompt')
}
test.describe('Errors tab in right side panel', { tag: '@ui' }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting(
'Comfy.RightSidePanel.ShowErrorsTab',
true
)
await comfyPage.setup()
})
test('Errors tab appears after execution error', async ({ comfyPage }) => {
await triggerExecutionError(comfyPage)
// Dismiss the error overlay
const overlay = comfyPage.page.getByTestId('error-overlay')
await expect(overlay).toBeVisible()
await overlay.getByRole('button', { name: /Dismiss/i }).click()
await comfyPage.actionbar.propertiesButton.click()
const { propertiesPanel } = comfyPage.menu
await expect(propertiesPanel.root).toBeVisible()
await expect(
propertiesPanel.root.getByRole('tab', { name: 'Errors' })
).toBeVisible()
})
test('Error card shows locate button', async ({ comfyPage }) => {
await triggerExecutionError(comfyPage)
const overlay = comfyPage.page.getByTestId('error-overlay')
await expect(overlay).toBeVisible()
await overlay.getByRole('button', { name: /See Errors/i }).click()
const { propertiesPanel } = comfyPage.menu
await expect(propertiesPanel.root).toBeVisible()
const locateButton = propertiesPanel.root.getByRole('button', {
name: 'Locate node on canvas'
})
await expect(locateButton.first()).toBeVisible()
})
test('Clicking locate button focuses canvas', async ({ comfyPage }) => {
await triggerExecutionError(comfyPage)
const overlay = comfyPage.page.getByTestId('error-overlay')
await expect(overlay).toBeVisible()
await overlay.getByRole('button', { name: /See Errors/i }).click()
const { propertiesPanel } = comfyPage.menu
await expect(propertiesPanel.root).toBeVisible()
const locateButton = propertiesPanel.root
.getByRole('button', { name: 'Locate node on canvas' })
.first()
await locateButton.click()
await expect(comfyPage.canvas).toBeVisible()
})
test('Collapse all button collapses error groups', async ({ comfyPage }) => {
await triggerExecutionError(comfyPage)
const overlay = comfyPage.page.getByTestId('error-overlay')
await expect(overlay).toBeVisible()
await overlay.getByRole('button', { name: /See Errors/i }).click()
const { propertiesPanel } = comfyPage.menu
await expect(propertiesPanel.root).toBeVisible()
const collapseButton = propertiesPanel.root.getByRole('button', {
name: 'Collapse all'
})
// The collapse toggle only appears when there are multiple groups.
// If only one group exists, this test verifies the button is not shown.
const count = await collapseButton.count()
if (count > 0) {
await collapseButton.click()
const expandButton = propertiesPanel.root.getByRole('button', {
name: 'Expand all'
})
await expect(expandButton).toBeVisible()
}
})
test('Search filters errors', async ({ comfyPage }) => {
await triggerExecutionError(comfyPage)
const overlay = comfyPage.page.getByTestId('error-overlay')
await expect(overlay).toBeVisible()
await overlay.getByRole('button', { name: /See Errors/i }).click()
const { propertiesPanel } = comfyPage.menu
await expect(propertiesPanel.root).toBeVisible()
// Search for a term that won't match any error
await propertiesPanel.searchBox.fill('zzz_nonexistent_zzz')
await expect(
propertiesPanel.root.getByRole('button', {
name: 'Locate node on canvas'
})
).toHaveCount(0)
// Clear the search to restore results
await propertiesPanel.searchBox.fill('')
await expect(
propertiesPanel.root
.getByRole('button', { name: 'Locate node on canvas' })
.first()
).toBeVisible()
})
test('Errors tab shows error message text', async ({ comfyPage }) => {
await triggerExecutionError(comfyPage)
const overlay = comfyPage.page.getByTestId('error-overlay')
await expect(overlay).toBeVisible()
await overlay.getByRole('button', { name: /See Errors/i }).click()
const { propertiesPanel } = comfyPage.menu
await expect(propertiesPanel.root).toBeVisible()
const errorMessage = propertiesPanel.root
.getByTestId('error-card-message')
.first()
await expect(errorMessage).toBeVisible()
await expect(errorMessage).not.toHaveText('')
})
})

View File

@@ -0,0 +1,57 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '@e2e/fixtures/ComfyPage'
test.describe('Settings Sidebar', { tag: '@ui' }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.setup()
})
test('Settings button is visible in sidebar', async ({ comfyPage }) => {
await expect(comfyPage.menu.sideToolbar).toBeVisible()
const settingsButton = comfyPage.menu.sideToolbar.getByLabel(/settings/i)
await expect(settingsButton).toBeVisible()
})
test('Clicking settings button opens settings dialog', async ({
comfyPage
}) => {
const settingsButton = comfyPage.menu.sideToolbar.getByLabel(/settings/i)
await settingsButton.click()
await expect(comfyPage.settingDialog.root).toBeVisible()
})
test('Settings dialog shows categories', async ({ comfyPage }) => {
const settingsButton = comfyPage.menu.sideToolbar.getByLabel(/settings/i)
await settingsButton.click()
await expect(comfyPage.settingDialog.root).toBeVisible()
await expect(comfyPage.settingDialog.categories.first()).toBeVisible()
expect(await comfyPage.settingDialog.categories.count()).toBeGreaterThan(0)
})
test('Settings dialog can be closed with Escape', async ({ comfyPage }) => {
const settingsButton = comfyPage.menu.sideToolbar.getByLabel(/settings/i)
await settingsButton.click()
await expect(comfyPage.settingDialog.root).toBeVisible()
await comfyPage.page.keyboard.press('Escape')
await expect(comfyPage.settingDialog.root).toBeHidden()
})
test('Settings search box is functional', async ({ comfyPage }) => {
const settingsButton = comfyPage.menu.sideToolbar.getByLabel(/settings/i)
await settingsButton.click()
await expect(comfyPage.settingDialog.root).toBeVisible()
await comfyPage.settingDialog.searchBox.fill('color')
await expect(comfyPage.settingDialog.searchBox).toHaveValue('color')
})
test('Settings dialog can navigate to About panel', async ({ comfyPage }) => {
const settingsButton = comfyPage.menu.sideToolbar.getByLabel(/settings/i)
await settingsButton.click()
await expect(comfyPage.settingDialog.root).toBeVisible()
await comfyPage.settingDialog.goToAboutPanel()
await expect(comfyPage.page.locator('.about-container')).toBeVisible()
})
})

View File

@@ -0,0 +1,69 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '@e2e/fixtures/ComfyPage'
test.describe('Vue Node Header Actions', { tag: '@node' }, () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.settings.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.settings.setSetting('Comfy.Graph.CanvasMenu', false)
await comfyPage.settings.setSetting('Comfy.VueNodes.Enabled', true)
await comfyPage.setup()
await comfyPage.vueNodes.waitForNodes()
})
test('Collapse button is visible on node header', async ({ comfyPage }) => {
const vueNode = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
await expect(vueNode.collapseButton).toBeVisible()
})
test('Clicking collapse button hides node body', async ({ comfyPage }) => {
const vueNode = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
await expect(vueNode.body).toBeVisible()
await vueNode.toggleCollapse()
await comfyPage.nextFrame()
await expect(vueNode.body).toBeHidden()
})
test('Clicking collapse button again expands node', async ({ comfyPage }) => {
const vueNode = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
await vueNode.toggleCollapse()
await comfyPage.nextFrame()
await expect(vueNode.body).toBeHidden()
await vueNode.toggleCollapse()
await comfyPage.nextFrame()
await expect(vueNode.body).toBeVisible()
})
test('Double-click header enters title edit mode', async ({ comfyPage }) => {
const vueNode = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
await vueNode.header.dblclick()
await vueNode.titleEditor.expectVisible()
})
test('Title edit saves on Enter', async ({ comfyPage }) => {
const vueNode = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
await vueNode.setTitle('My Custom Sampler')
expect(await vueNode.getTitle()).toBe('My Custom Sampler')
})
test('Title edit cancels on Escape', async ({ comfyPage }) => {
const vueNode = await comfyPage.vueNodes.getFixtureByTitle('KSampler')
await vueNode.setTitle('Renamed Sampler')
expect(await vueNode.getTitle()).toBe('Renamed Sampler')
await vueNode.header.dblclick()
await vueNode.titleEditor.input.fill('This Should Be Cancelled')
await vueNode.titleEditor.cancel()
await comfyPage.nextFrame()
expect(await vueNode.getTitle()).toBe('Renamed Sampler')
})
})

View File

@@ -58,6 +58,7 @@
<p
v-if="error.message"
class="m-0 max-h-[4lh] overflow-y-auto px-0.5 text-sm/relaxed wrap-break-word whitespace-pre-wrap"
data-testid="error-card-message"
>
{{ error.message }}
</p>