Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -767,8 +767,8 @@ export class ComfyPage {
await this.nextFrame()
}

async rightClickCanvas() {
await this.page.mouse.click(10, 10, { button: 'right' })
async rightClickCanvas(x: number = 10, y: number = 10) {
await this.page.mouse.click(x, y, { button: 'right' })
await this.nextFrame()
}

Expand Down
19 changes: 14 additions & 5 deletions browser_tests/tests/interaction.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { expect } from '@playwright/test'
import { Locator, expect } from '@playwright/test'
import { Position } from '@vueuse/core'

import {
Expand Down Expand Up @@ -767,6 +767,17 @@ test.describe('Viewport settings', () => {
comfyPage,
comfyMouse
}) => {
const changeTab = async (tab: Locator) => {
await tab.click()
await comfyPage.nextFrame()
await comfyMouse.move(comfyPage.emptySpace)

// If tooltip is visible, wait for it to hide
await expect(
comfyPage.page.locator('.workflow-popover-fade')
).toHaveCount(0)
}

// Screenshot the canvas element
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', true)
const toggleButton = comfyPage.page.getByTestId('toggle-minimap-button')
Expand Down Expand Up @@ -794,15 +805,13 @@ test.describe('Viewport settings', () => {
const tabB = comfyPage.menu.topbar.getWorkflowTab('Workflow B')

// Go back to Workflow A
await tabA.click()
await comfyPage.nextFrame()
await changeTab(tabA)
expect((await comfyPage.canvas.screenshot()).toString('base64')).toBe(
screenshotA
)

// And back to Workflow B
await tabB.click()
await comfyPage.nextFrame()
await changeTab(tabB)
expect((await comfyPage.canvas.screenshot()).toString('base64')).toBe(
screenshotB
)
Expand Down
155 changes: 155 additions & 0 deletions browser_tests/tests/workflowTabThumbnail.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { expect } from '@playwright/test'

import { type ComfyPage, comfyPageFixture as test } from '../fixtures/ComfyPage'

test.describe('Workflow Tab Thumbnails', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
await comfyPage.setSetting('Comfy.Workflow.WorkflowTabsPosition', 'Topbar')
await comfyPage.setup()
})

async function getTab(comfyPage: ComfyPage, index: number) {
const tab = comfyPage.page
.locator(`.workflow-tabs .p-togglebutton`)
.nth(index)
return tab
}

async function getTabPopover(
comfyPage: ComfyPage,
index: number,
name?: string
) {
const tab = await getTab(comfyPage, index)
await tab.hover()

const popover = comfyPage.page.locator('.workflow-popover-fade')
await expect(popover).toHaveCount(1)
await expect(popover).toBeVisible({ timeout: 500 })
if (name) {
await expect(popover).toContainText(name)
}
return popover
}

async function getTabThumbnailImage(
comfyPage: ComfyPage,
index: number,
name?: string
) {
const popover = await getTabPopover(comfyPage, index, name)
const thumbnailImg = popover.locator('.workflow-preview-thumbnail img')
return thumbnailImg
}

async function getNodeThumbnailBase64(comfyPage: ComfyPage, index: number) {
const thumbnailImg = await getTabThumbnailImage(comfyPage, index)
const src = (await thumbnailImg.getAttribute('src'))!

// Convert blob to base64, need to execute a script to get the base64
const base64 = await comfyPage.page.evaluate(async (src: string) => {
const blob = await fetch(src).then((res) => res.blob())
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onloadend = () => resolve(reader.result)
reader.onerror = reject
reader.readAsDataURL(blob)
})
}, src)
return base64
}

test('Should show thumbnail when hovering over a non-active tab', async ({
comfyPage
}) => {
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
const thumbnailImg = await getTabThumbnailImage(
comfyPage,
0,
'Unsaved Workflow'
)
await expect(thumbnailImg).toBeVisible()
})

test('Should not show thumbnail for active tab', async ({ comfyPage }) => {
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
const thumbnailImg = await getTabThumbnailImage(
comfyPage,
1,
'Unsaved Workflow (2)'
)
await expect(thumbnailImg).not.toBeVisible()
})

async function addNode(comfyPage: ComfyPage, category: string, node: string) {
const canvasArea = await comfyPage.canvas.boundingBox()

await comfyPage.page.mouse.move(
canvasArea!.x + canvasArea!.width - 100,
100
)
await comfyPage.delay(300) // Wait for the popover to hide

await comfyPage.rightClickCanvas(200, 200)
await comfyPage.page.getByText('Add Node').click()
await comfyPage.nextFrame()
await comfyPage.page.getByText(category).click()
await comfyPage.nextFrame()
await comfyPage.page.getByText(node).click()
await comfyPage.nextFrame()
}

test('Thumbnail should update when switching tabs', async ({ comfyPage }) => {
// Wait for initial workflow to load
await comfyPage.nextFrame()

// Create a new workflow (tab 1) which will be empty
await comfyPage.menu.topbar.triggerTopbarCommand(['Workflow', 'New'])
await comfyPage.nextFrame()

// Now we have two tabs: tab 0 (default workflow with nodes) and tab 1 (empty)
// Tab 1 is currently active, so we can only get thumbnail for tab 0

// Step 1: Different tabs should show different previews
const tab0ThumbnailWithNodes = await getNodeThumbnailBase64(comfyPage, 0)

// Add a node to tab 1 (current active tab)
await addNode(comfyPage, 'loaders', 'Load Checkpoint')
await comfyPage.nextFrame()

// Switch to tab 0 so we can get tab 1's thumbnail
await (await getTab(comfyPage, 0)).click()
await comfyPage.nextFrame()

const tab1ThumbnailWithNode = await getNodeThumbnailBase64(comfyPage, 1)

// The thumbnails should be different
expect(tab0ThumbnailWithNodes).not.toBe(tab1ThumbnailWithNode)

// Step 2: Switching without changes shouldn't update thumbnail
const tab1ThumbnailBefore = await getNodeThumbnailBase64(comfyPage, 1)

// Switch to tab 1 and back to tab 0 without making changes
await (await getTab(comfyPage, 1)).click()
await comfyPage.nextFrame()
await (await getTab(comfyPage, 0)).click()
await comfyPage.nextFrame()

const tab1ThumbnailAfter = await getNodeThumbnailBase64(comfyPage, 1)
expect(tab1ThumbnailBefore).toBe(tab1ThumbnailAfter)

// Step 3: Adding another node should cause thumbnail to change
// We're on tab 0, add a node
await addNode(comfyPage, 'loaders', 'Load VAE')
await comfyPage.nextFrame()

// Switch to tab 1 and back to update tab 0's thumbnail
await (await getTab(comfyPage, 1)).click()

const tab0ThumbnailAfterNewNode = await getNodeThumbnailBase64(comfyPage, 0)

// The thumbnail should have changed after adding a node
expect(tab0ThumbnailWithNodes).not.toBe(tab0ThumbnailAfterNewNode)
})
})
57 changes: 47 additions & 10 deletions src/components/topbar/WorkflowTab.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
<template>
<div ref="workflowTabRef" class="flex p-2 gap-2 workflow-tab" v-bind="$attrs">
<span
v-tooltip.bottom="{
value: workflowOption.workflow.key,
class: 'workflow-tab-tooltip',
showDelay: 512
}"
class="workflow-label text-sm max-w-[150px] min-w-[30px] truncate inline-block"
>
<div
ref="workflowTabRef"
class="flex p-2 gap-2 workflow-tab"
v-bind="$attrs"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
@click="handleClick"
>
<span class="workflow-label text-sm max-w-[150px] truncate inline-block">
{{ workflowOption.workflow.filename }}
</span>
<div class="relative">
Expand All @@ -22,23 +22,33 @@
/>
</div>
</div>

<WorkflowTabPopover
ref="popoverRef"
:workflow-filename="workflowOption.workflow.filename"
:thumbnail-url="thumbnailUrl"
:is-active-tab="isActiveTab"
/>
</template>

<script setup lang="ts">
import Button from 'primevue/button'
import { computed, ref } from 'vue'
import { computed, onUnmounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'

import {
usePragmaticDraggable,
usePragmaticDroppable
} from '@/composables/usePragmaticDragAndDrop'
import { useWorkflowThumbnail } from '@/composables/useWorkflowThumbnail'
import { useWorkflowService } from '@/services/workflowService'
import { useSettingStore } from '@/stores/settingStore'
import { ComfyWorkflow } from '@/stores/workflowStore'
import { useWorkflowStore } from '@/stores/workflowStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'

import WorkflowTabPopover from './WorkflowTabPopover.vue'

interface WorkflowOption {
value: string
workflow: ComfyWorkflow
Expand All @@ -55,6 +65,8 @@ const workspaceStore = useWorkspaceStore()
const workflowStore = useWorkflowStore()
const settingStore = useSettingStore()
const workflowTabRef = ref<HTMLElement | null>(null)
const popoverRef = ref<InstanceType<typeof WorkflowTabPopover> | null>(null)
const workflowThumbnail = useWorkflowThumbnail()

// Use computed refs to cache autosave settings
const autoSaveSetting = computed(() =>
Expand Down Expand Up @@ -90,6 +102,27 @@ const shouldShowStatusIndicator = computed(() => {
return false
})

const isActiveTab = computed(() => {
return workflowStore.activeWorkflow?.key === props.workflowOption.workflow.key
})

const thumbnailUrl = computed(() => {
return workflowThumbnail.getThumbnail(props.workflowOption.workflow.key)
})

// Event handlers that delegate to the popover component
const handleMouseEnter = (event: Event) => {
popoverRef.value?.showPopover(event)
}

const handleMouseLeave = () => {
popoverRef.value?.hidePopover()
}

const handleClick = (event: Event) => {
popoverRef.value?.togglePopover(event)
}

const closeWorkflows = async (options: WorkflowOption[]) => {
for (const opt of options) {
if (
Expand Down Expand Up @@ -135,6 +168,10 @@ usePragmaticDroppable(tabGetter, {
}
}
})

onUnmounted(() => {
popoverRef.value?.hidePopover()
})
</script>

<style scoped>
Expand Down
Loading