diff --git a/browser_tests/fixtures/selectors.ts b/browser_tests/fixtures/selectors.ts index 331e851482..8d1baf38e1 100644 --- a/browser_tests/fixtures/selectors.ts +++ b/browser_tests/fixtures/selectors.ts @@ -59,6 +59,9 @@ export const TestIds = { missingModelCopyName: 'missing-model-copy-name', missingModelCopyUrl: 'missing-model-copy-url', missingModelDownload: 'missing-model-download', + missingModelActions: 'missing-model-actions', + missingModelDownloadAll: 'missing-model-download-all', + missingModelRefresh: 'missing-model-refresh', missingModelImportUnsupported: 'missing-model-import-unsupported', missingMediaGroup: 'error-group-missing-media', missingMediaRow: 'missing-media-row', diff --git a/browser_tests/tests/propertiesPanel/errorsTabMissingModels.spec.ts b/browser_tests/tests/propertiesPanel/errorsTabMissingModels.spec.ts index eb2803efc6..1a043eed72 100644 --- a/browser_tests/tests/propertiesPanel/errorsTabMissingModels.spec.ts +++ b/browser_tests/tests/propertiesPanel/errorsTabMissingModels.spec.ts @@ -99,5 +99,58 @@ test.describe('Errors tab - Missing models', { tag: '@ui' }, () => { ) await expect(downloadButton.first()).toBeVisible() }) + + test('Should render Download all and Refresh actions for one downloadable model', async ({ + comfyPage + }) => { + await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models') + + await expect( + comfyPage.page.getByTestId(TestIds.dialogs.missingModelActions) + ).toBeVisible() + await expect( + comfyPage.page.getByTestId(TestIds.dialogs.missingModelDownloadAll) + ).toBeVisible() + await expect( + comfyPage.page.getByTestId(TestIds.dialogs.missingModelRefresh) + ).toBeVisible() + }) + + test('Should clear resolved missing model when Refresh is clicked', async ({ + comfyPage + }) => { + await loadWorkflowAndOpenErrorsTab(comfyPage, 'missing/missing_models') + await comfyPage.page.route(/\/object_info$/, async (route) => { + const response = await route.fetch() + const objectInfo = await response.json() + const ckptName = + objectInfo.CheckpointLoaderSimple.input.required.ckpt_name + ckptName[0] = [...ckptName[0], 'fake_model.safetensors'] + await route.fulfill({ response, json: objectInfo }) + }) + + const objectInfoResponse = comfyPage.page.waitForResponse((response) => { + const url = new URL(response.url()) + return url.pathname.endsWith('/object_info') && response.ok() + }) + const modelFoldersResponse = comfyPage.page.waitForResponse( + (response) => { + const url = new URL(response.url()) + return url.pathname.endsWith('/experiment/models') && response.ok() + } + ) + const refreshButton = comfyPage.page.getByTestId( + TestIds.dialogs.missingModelRefresh + ) + + await Promise.all([ + objectInfoResponse, + modelFoldersResponse, + refreshButton.click() + ]) + await expect( + comfyPage.page.getByTestId(TestIds.dialogs.missingModelsGroup) + ).toBeHidden() + }) }) }) diff --git a/src/components/rightSidePanel/errors/TabErrors.test.ts b/src/components/rightSidePanel/errors/TabErrors.test.ts index f7dc8502b3..11b8e06b43 100644 --- a/src/components/rightSidePanel/errors/TabErrors.test.ts +++ b/src/components/rightSidePanel/errors/TabErrors.test.ts @@ -5,6 +5,8 @@ import PrimeVue from 'primevue/config' import { beforeEach, describe, expect, it, vi } from 'vitest' import { createI18n } from 'vue-i18n' import TabErrors from './TabErrors.vue' +import { useMissingModelStore } from '@/platform/missingModel/missingModelStore' +import type { MissingModelCandidate } from '@/platform/missingModel/types' vi.mock('@/scripts/app', () => ({ app: { @@ -50,6 +52,12 @@ describe('TabErrors.vue', () => { rightSidePanel: { noErrors: 'No errors', noneSearchDesc: 'No results found', + missingModels: { + missingModelsTitle: 'Missing Models', + downloadAll: 'Download all', + refresh: 'Refresh', + refreshing: 'Refreshing missing models.' + }, promptErrors: { prompt_no_outputs: { desc: 'Prompt has no outputs' @@ -82,7 +90,7 @@ describe('TabErrors.vue', () => { template: '
' }, Button: { - template: '' + template: '' } } } @@ -241,4 +249,58 @@ describe('TabErrors.vue', () => { expect(screen.getByTestId('runtime-error-panel')).toBeInTheDocument() expect(screen.getAllByText('RuntimeError: Out of memory')).toHaveLength(1) }) + + it('shows missing model Refresh in the section header when no model is downloadable', async () => { + const missingModel = { + nodeId: '1', + nodeType: 'CheckpointLoaderSimple', + widgetName: 'ckpt_name', + name: 'local-only.safetensors', + directory: 'checkpoints', + isMissing: true, + isAssetSupported: true + } satisfies MissingModelCandidate + + const { user } = renderComponent({ + missingModel: { + missingModelCandidates: [missingModel] + } + }) + const missingModelStore = useMissingModelStore() + + expect(screen.getByText('Missing Models (1)')).toBeInTheDocument() + expect( + screen.queryByTestId('missing-model-actions') + ).not.toBeInTheDocument() + + await user.click(screen.getByTestId('missing-model-header-refresh')) + + expect(missingModelStore.refreshMissingModels).toHaveBeenCalled() + }) + + it('keeps missing model Refresh in the card actions when models are downloadable', () => { + const missingModel = { + nodeId: '1', + nodeType: 'CheckpointLoaderSimple', + widgetName: 'ckpt_name', + name: 'downloadable.safetensors', + url: 'https://huggingface.co/comfy/test/resolve/main/downloadable.safetensors', + directory: 'checkpoints', + isMissing: true, + isAssetSupported: true + } satisfies MissingModelCandidate + + renderComponent({ + missingModel: { + missingModelCandidates: [missingModel] + } + }) + + expect( + screen.queryByTestId('missing-model-header-refresh') + ).not.toBeInTheDocument() + expect(screen.getByTestId('missing-model-actions')).toBeInTheDocument() + expect(screen.getByRole('button', { name: /Download all/ })).toBeVisible() + expect(screen.getByRole('button', { name: 'Refresh' })).toBeVisible() + }) }) diff --git a/src/components/rightSidePanel/errors/TabErrors.vue b/src/components/rightSidePanel/errors/TabErrors.vue index bed0aa183a..6f0cc50716 100644 --- a/src/components/rightSidePanel/errors/TabErrors.vue +++ b/src/components/rightSidePanel/errors/TabErrors.vue @@ -101,18 +101,6 @@ : t('rightSidePanel.missingNodePacks.installAll') }} -