mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
Add unit tests covering the workspace context requirement for profile creation: missing sessionStorage, malformed JSON, missing workspace ID, creation without avatar, and cached state update after success. Add E2E test verifying the publish intro panel renders when switching to the publish tab with comfyHubUploadEnabled. Fix pre-existing import path aliases in shareWorkflowDialog.spec.ts.
379 lines
10 KiB
TypeScript
379 lines
10 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
import { expect } from '@playwright/test'
|
|
|
|
import type { AssetInfo } from '@/schemas/apiSchema'
|
|
import { comfyPageFixture } from '@e2e/fixtures/ComfyPage'
|
|
import { TestIds } from '@e2e/fixtures/selectors'
|
|
|
|
interface PublishRecord {
|
|
workflow_id: string
|
|
share_id: string | null
|
|
listed: boolean
|
|
publish_time: string | null
|
|
}
|
|
|
|
const PUBLISHED_RECORD: PublishRecord = {
|
|
workflow_id: 'wf-1',
|
|
share_id: 'share-abc',
|
|
listed: false,
|
|
publish_time: new Date(Date.now() + 60_000).toISOString()
|
|
}
|
|
|
|
const PRIVATE_ASSET: AssetInfo = {
|
|
id: 'asset-1',
|
|
name: 'photo.png',
|
|
preview_url: '',
|
|
storage_url: '',
|
|
model: false,
|
|
public: false,
|
|
in_library: false
|
|
}
|
|
|
|
const test = comfyPageFixture
|
|
|
|
/**
|
|
* Enable the workflow_sharing_enabled server feature flag at runtime.
|
|
* FeatureFlagHelper.mockServerFeatures() intercepts `/api/features` but the
|
|
* flags are already loaded by the time tests run, so direct mutation of the
|
|
* reactive ref is the only reliable approach for server-side flags.
|
|
*/
|
|
async function enableWorkflowSharing(page: Page): Promise<void> {
|
|
await page.evaluate(() => {
|
|
const api = window.app!.api
|
|
api.serverFeatureFlags.value = {
|
|
...api.serverFeatureFlags.value,
|
|
workflow_sharing_enabled: true
|
|
}
|
|
})
|
|
}
|
|
|
|
async function mockPublishStatus(
|
|
page: Page,
|
|
record: PublishRecord | null
|
|
): Promise<void> {
|
|
await page.route('**/api/userdata/*/publish', async (route) => {
|
|
if (route.request().method() === 'GET') {
|
|
if (!record || !record.share_id) {
|
|
await route.fulfill({ status: 404, body: 'Not found' })
|
|
} else {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(record)
|
|
})
|
|
}
|
|
} else {
|
|
await route.fallback()
|
|
}
|
|
})
|
|
}
|
|
|
|
async function mockPublishWorkflow(
|
|
page: Page,
|
|
result: PublishRecord
|
|
): Promise<void> {
|
|
await page.route('**/api/userdata/*/publish', async (route) => {
|
|
if (route.request().method() === 'POST') {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify(result)
|
|
})
|
|
} else {
|
|
await route.fallback()
|
|
}
|
|
})
|
|
}
|
|
|
|
async function mockShareableAssets(
|
|
page: Page,
|
|
assets: AssetInfo[] = []
|
|
): Promise<void> {
|
|
await page.route('**/api/assets/from-workflow', async (route) => {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({ assets })
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Dismiss stale PrimeVue dialog masks left by cloud-mode's onboarding flow
|
|
* or auth-triggered modals by pressing Escape until they clear.
|
|
*/
|
|
async function dismissOverlays(page: Page): Promise<void> {
|
|
const mask = page.locator('.p-dialog-mask')
|
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
if ((await mask.count()) === 0) break
|
|
await page.keyboard.press('Escape')
|
|
await mask
|
|
.first()
|
|
.waitFor({ state: 'hidden', timeout: 2000 })
|
|
.catch(() => {})
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save the active workflow via the topbar Save menu action.
|
|
* Mocks the userdata POST endpoint to avoid real server calls in tests.
|
|
*/
|
|
async function saveAndWait(
|
|
comfyPage: {
|
|
page: Page
|
|
menu: { topbar: { saveWorkflow: (name: string) => Promise<void> } }
|
|
},
|
|
workflowName: string
|
|
): Promise<void> {
|
|
const filename =
|
|
workflowName + (workflowName.endsWith('.json') ? '' : '.json')
|
|
await comfyPage.page.route(
|
|
/\/api\/userdata\/workflows(%2F|\/).*$/,
|
|
async (route) => {
|
|
if (route.request().method() === 'POST') {
|
|
await route.fulfill({
|
|
status: 200,
|
|
contentType: 'application/json',
|
|
body: JSON.stringify({
|
|
path: `workflows/${filename}`,
|
|
size: 1024,
|
|
modified: Date.now()
|
|
})
|
|
})
|
|
} else {
|
|
await route.fallback()
|
|
}
|
|
}
|
|
)
|
|
|
|
await comfyPage.menu.topbar.saveWorkflow(workflowName)
|
|
}
|
|
|
|
async function openShareDialog(page: Page): Promise<void> {
|
|
await enableWorkflowSharing(page)
|
|
await dismissOverlays(page)
|
|
const shareButton = page.getByRole('button', { name: 'Share workflow' })
|
|
await shareButton.click()
|
|
}
|
|
|
|
function getShareDialog(page: Page) {
|
|
return page.getByRole('dialog')
|
|
}
|
|
|
|
test.describe('Share Workflow Dialog', { tag: '@cloud' }, () => {
|
|
test('should show unsaved state for a new workflow', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await expect(dialog).toBeVisible()
|
|
await expect(
|
|
dialog.getByRole('button', { name: /save workflow/i })
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should show ready state with create link button', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
const workflowName = 'share-test-ready'
|
|
|
|
await saveAndWait(comfyPage, workflowName)
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await expect(dialog).toBeVisible()
|
|
await expect(
|
|
dialog.getByRole('button', { name: /create a link/i })
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should show shared state with copy URL after publishing', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
const workflowName = 'share-test-shared'
|
|
|
|
await saveAndWait(comfyPage, workflowName)
|
|
|
|
await mockPublishStatus(page, PUBLISHED_RECORD)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await expect(dialog).toBeVisible()
|
|
await expect(
|
|
dialog.getByRole('textbox', { name: /share.*url/i })
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should show stale state with update link button', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
const workflowName = 'share-test-stale'
|
|
|
|
await saveAndWait(comfyPage, workflowName)
|
|
|
|
const staleRecord: PublishRecord = {
|
|
...PUBLISHED_RECORD,
|
|
publish_time: '2020-01-01T00:00:00Z'
|
|
}
|
|
|
|
await mockPublishStatus(page, staleRecord)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await expect(dialog).toBeVisible()
|
|
await expect(
|
|
dialog.getByRole('button', { name: /update\s+link/i })
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should close dialog when close button is clicked', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await expect(dialog).toBeVisible()
|
|
|
|
await dialog.getByRole('button', { name: /close/i }).click()
|
|
await expect(dialog).toBeHidden()
|
|
})
|
|
|
|
test('should create link and transition to shared state', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
const workflowName = 'share-test-create'
|
|
|
|
await saveAndWait(comfyPage, workflowName)
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page)
|
|
await mockPublishWorkflow(page, PUBLISHED_RECORD)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
const createButton = dialog.getByRole('button', { name: /create a link/i })
|
|
await expect(createButton).toBeVisible()
|
|
await createButton.click()
|
|
|
|
await expect(
|
|
dialog.getByRole('textbox', { name: /share.*url/i })
|
|
).toBeVisible()
|
|
})
|
|
|
|
test('should show tab buttons when comfyHubUploadEnabled is true', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
|
|
await page.evaluate(() => {
|
|
const api = window.app!.api
|
|
api.serverFeatureFlags.value = {
|
|
...api.serverFeatureFlags.value,
|
|
comfyhub_upload_enabled: true
|
|
}
|
|
})
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await expect(dialog).toBeVisible()
|
|
await expect(dialog.getByRole('tab', { name: /share/i })).toBeVisible()
|
|
await expect(dialog.getByRole('tab', { name: /publish/i })).toBeVisible()
|
|
})
|
|
|
|
test('should switch between share link and publish tabs', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
|
|
await page.evaluate(() => {
|
|
const api = window.app!.api
|
|
api.serverFeatureFlags.value = {
|
|
...api.serverFeatureFlags.value,
|
|
comfyhub_upload_enabled: true
|
|
}
|
|
})
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await expect(dialog).toBeVisible()
|
|
|
|
await dialog.getByRole('tab', { name: /publish/i }).click()
|
|
|
|
const publishPanel = dialog.getByTestId(TestIds.dialogs.publishTabPanel)
|
|
await expect(publishPanel).toBeVisible()
|
|
|
|
await dialog.getByRole('tab', { name: /share/i }).click()
|
|
await expect(publishPanel).toBeHidden()
|
|
})
|
|
|
|
test('should show publish intro panel when switching to publish tab', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
const workflowName = 'share-test-publish-intro'
|
|
|
|
await saveAndWait(comfyPage, workflowName)
|
|
|
|
await page.evaluate(() => {
|
|
const api = window.app!.api
|
|
api.serverFeatureFlags.value = {
|
|
...api.serverFeatureFlags.value,
|
|
comfyhub_upload_enabled: true
|
|
}
|
|
})
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page)
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
await dialog.getByRole('tab', { name: /publish/i }).click()
|
|
|
|
await expect(dialog.getByTestId('publish-intro')).toBeVisible()
|
|
})
|
|
|
|
test('should require acknowledgment before publishing private assets', async ({
|
|
comfyPage
|
|
}) => {
|
|
const { page } = comfyPage
|
|
const workflowName = 'share-test-ack'
|
|
|
|
await saveAndWait(comfyPage, workflowName)
|
|
|
|
await mockPublishStatus(page, null)
|
|
await mockShareableAssets(page, [PRIVATE_ASSET])
|
|
await openShareDialog(page)
|
|
|
|
const dialog = getShareDialog(page)
|
|
const createButton = dialog.getByRole('button', { name: /create a link/i })
|
|
await expect(createButton).toBeDisabled()
|
|
|
|
await dialog.getByRole('checkbox').check()
|
|
await expect(createButton).toBeEnabled()
|
|
})
|
|
})
|