11import { expect , mergeTests } from '@playwright/test'
22import type { Page , Response } from '@playwright/test'
33
4+ import type { Asset , ListAssetsResponse } from '@comfyorg/ingest-types'
45import { comfyPageFixture } from '@e2e/fixtures/ComfyPage'
56import { expectNoErrorUiAfterVerification } from '@e2e/fixtures/helpers/ErrorsTabHelper'
67import {
@@ -10,11 +11,14 @@ import {
1011 routeMockJobTimestamp
1112} from '@e2e/fixtures/jobsRouteFixture'
1213import { TestIds } from '@e2e/fixtures/selectors'
14+ import { mockBilling } from '@e2e/fixtures/utils/cloudBillingMocks'
15+ import { mockCloudBoot } from '@e2e/fixtures/utils/cloudBootMocks'
1316import { PropertiesPanelHelper } from '@e2e/tests/propertiesPanel/PropertiesPanelHelper'
1417import type {
1518 JobDetail ,
1619 RawJobListItem
1720} from '@/platform/remote/comfyui/jobs/jobTypes'
21+ import type { ComfyNodeDef } from '@/schemas/nodeDefSchema'
1822
1923const test = mergeTests ( comfyPageFixture , jobsRouteFixture )
2024
@@ -103,6 +107,19 @@ const viewFiles = {
103107 'multi-output-b.png' : { }
104108}
105109
110+ const alphaOutputAsset : Asset = {
111+ id : 'alpha-asset-id' ,
112+ name : 'ComfyUI_alpha.png' ,
113+ hash : 'alpha.png' ,
114+ mime_type : 'image/png' ,
115+ tags : [ 'output' ] ,
116+ created_at : '2026-01-01T12:00:00Z' ,
117+ updated_at : '2026-01-01T12:00:00Z'
118+ }
119+
120+ const outputAssetsByPage = new WeakMap < Page , Asset [ ] > ( )
121+ const generatedJobsByPage = new WeakMap < Page , RawJobListItem [ ] > ( )
122+
106123async function mockInputFiles ( page : Page , files : readonly string [ ] ) {
107124 await page . route ( '**/internal/files/input**' , async ( route ) => {
108125 if ( route . request ( ) . method ( ) . toUpperCase ( ) !== 'GET' ) {
@@ -170,6 +187,89 @@ const bulkInsertionTest = comfyPageFixture.extend({
170187 }
171188} )
172189
190+ const loadImageNodeDef : ComfyNodeDef = {
191+ name : 'LoadImage' ,
192+ display_name : 'Load Image' ,
193+ description : '' ,
194+ category : 'image' ,
195+ input : {
196+ required : {
197+ image : [ [ 'alpha.png [output]' ] , { image_upload : true } ]
198+ }
199+ } ,
200+ output : [ 'IMAGE' , 'MASK' ] ,
201+ output_is_list : [ false , false ] ,
202+ output_name : [ 'IMAGE' , 'MASK' ] ,
203+ output_node : false ,
204+ python_module : 'nodes' ,
205+ deprecated : false ,
206+ experimental : false
207+ }
208+
209+ const cloudAssetDeletionTest = test . extend ( {
210+ page : async ( { page } , use ) => {
211+ outputAssetsByPage . set ( page , [ alphaOutputAsset ] )
212+ generatedJobsByPage . set ( page , [ alphaJob ] )
213+ await mockCloudBoot ( page , {
214+ features : { } ,
215+ settings : {
216+ 'Comfy.Queue.QPOV2' : false ,
217+ 'Comfy.RightSidePanel.ShowErrorsTab' : false ,
218+ 'Comfy.TutorialCompleted' : true ,
219+ 'Comfy.UseNewMenu' : 'Top' ,
220+ 'Comfy.VersionCompatibility.DisableWarnings' : true ,
221+ 'Comfy.VueNodes.Enabled' : true
222+ }
223+ } )
224+ await mockBilling ( page )
225+ await page . route ( '**/api/devtools/set_settings' , ( route ) =>
226+ route . fulfill ( { json : { } } )
227+ )
228+ await page . route ( / \/ a p i \/ a s s e t s (?: \? .* ) ? $ / , ( route ) => {
229+ const assets = new URL ( route . request ( ) . url ( ) ) . searchParams
230+ . get ( 'include_tags' )
231+ ?. split ( ',' )
232+ . includes ( 'output' )
233+ ? ( outputAssetsByPage . get ( page ) ?? [ ] )
234+ : [ ]
235+ return route . fulfill ( {
236+ json : {
237+ assets,
238+ total : assets . length ,
239+ has_more : false
240+ } satisfies ListAssetsResponse
241+ } )
242+ } )
243+ await page . route ( / \/ a p i \/ j o b s (?: \? .* ) ? $ / , ( route ) => {
244+ const url = new URL ( route . request ( ) . url ( ) )
245+ const isHistory = url . searchParams
246+ . get ( 'status' )
247+ ?. split ( ',' )
248+ . includes ( 'completed' )
249+ const jobs = isHistory ? ( generatedJobsByPage . get ( page ) ?? [ ] ) : [ ]
250+ const limit = Number ( url . searchParams . get ( 'limit' ) ?? 200 )
251+ const offset = Number ( url . searchParams . get ( 'offset' ) ?? 0 )
252+ return route . fulfill ( {
253+ json : {
254+ jobs,
255+ pagination : {
256+ offset,
257+ limit,
258+ total : jobs . length ,
259+ has_more : false
260+ }
261+ }
262+ } )
263+ } )
264+ await page . route ( '**/api/object_info' , ( route ) =>
265+ route . fulfill ( { json : { LoadImage : loadImageNodeDef } } )
266+ )
267+ await use ( page )
268+ outputAssetsByPage . delete ( page )
269+ generatedJobsByPage . delete ( page )
270+ }
271+ } )
272+
173273test . describe ( 'FE-130 assets sidebar route mocks' , ( ) => {
174274 test . beforeEach ( async ( { jobsRoutes, page } ) => {
175275 await jobsRoutes . mockJobsQueue ( [ ] )
@@ -309,6 +409,79 @@ test.describe('FE-130 assets sidebar route mocks', () => {
309409 } )
310410} )
311411
412+ cloudAssetDeletionTest . describe (
413+ 'IR-91 generated output deletion' ,
414+ { tag : '@cloud' } ,
415+ ( ) => {
416+ cloudAssetDeletionTest . beforeEach ( async ( { page } ) => {
417+ await mockInputFiles ( page , [ ] )
418+ await mockViewFiles ( page , viewFiles )
419+ } )
420+
421+ cloudAssetDeletionTest (
422+ 'removes a deleted generated output from Load Image options' ,
423+ async ( { comfyPage, page } ) => {
424+ const deletedAssetIds : string [ ] = [ ]
425+ await page . route ( '**/api/jobs/alpha/assets?*' , async ( route ) => {
426+ await route . fulfill ( {
427+ json : {
428+ assets : [ { id : 'alpha-asset-id' } ] ,
429+ pagination : {
430+ offset : 0 ,
431+ limit : 500 ,
432+ total : 1 ,
433+ has_more : false
434+ }
435+ }
436+ } )
437+ } )
438+ await page . route ( '**/api/assets/alpha-asset-id' , async ( route ) => {
439+ deletedAssetIds . push ( 'alpha-asset-id' )
440+ outputAssetsByPage . set ( page , [ ] )
441+ await route . fulfill ( { status : 204 , body : '' } )
442+ } )
443+
444+ await comfyPage . workflow . loadWorkflow ( 'widgets/load_image_widget' )
445+ await comfyPage . vueNodes . waitForNodes ( 1 )
446+ const imageWidgetButton = comfyPage . vueNodes
447+ . getNodeByTitle ( 'Load Image' )
448+ . getByRole ( 'button' , { name : 'Select image...' } )
449+ await imageWidgetButton . click ( )
450+ const imagePicker = page . getByRole ( 'dialog' )
451+ await expect (
452+ imagePicker . getByText ( 'ComfyUI_alpha.png [output]' , { exact : true } )
453+ ) . toBeVisible ( )
454+ await page . keyboard . press ( 'Escape' )
455+
456+ const tab = comfyPage . menu . assetsTab
457+ await tab . open ( )
458+ await expect ( tab . getAssetCardByName ( 'alpha' ) ) . toBeVisible ( )
459+ const historyDeleteRequests : { delete : string [ ] } [ ] = [ ]
460+ await page . route ( '**/api/history' , async ( route ) => {
461+ historyDeleteRequests . push ( route . request ( ) . postDataJSON ( ) )
462+ generatedJobsByPage . set ( page , [ ] )
463+ await route . fulfill ( { json : { } } )
464+ } )
465+ await tab . getAssetCardByName ( 'alpha' ) . click ( { button : 'right' } )
466+ await tab . contextMenuItem ( 'Delete' ) . click ( )
467+ await comfyPage . confirmDialog . delete . click ( )
468+
469+ await expect . poll ( ( ) => deletedAssetIds ) . toEqual ( [ 'alpha-asset-id' ] )
470+ await expect
471+ . poll ( ( ) => historyDeleteRequests )
472+ . toEqual ( [ { delete : [ 'alpha' ] } ] )
473+ await expect ( tab . getAssetCardByName ( 'alpha' ) ) . toHaveCount ( 0 )
474+ await tab . dismissToasts ( )
475+ await tab . close ( )
476+ await imageWidgetButton . click ( )
477+ await expect (
478+ imagePicker . getByText ( 'ComfyUI_alpha.png [output]' , { exact : true } )
479+ ) . toHaveCount ( 0 )
480+ }
481+ )
482+ }
483+ )
484+
312485bulkInsertionTest . describe (
313486 'Assets sidebar - bulk insert as nodes' ,
314487 { tag : [ '@vue-nodes' , '@ui' , '@node' , '@widget' ] } ,
0 commit comments