mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-24 06:35:10 +00:00
## Summary Adds a Cloud Playwright regression test for the nested subgraph case where an installed Lotus diffusion model is incorrectly surfaced as missing after returning to the root graph. The fixture keeps the reproduction small: root graph -> subgraph node -> nested subgraph node -> `UNETLoader` using `lotus-depth-d-v1-1.safetensors`. The test stubs `/api/assets` through the shared asset API fixture so that model is explicitly present as a `diffusion_models` asset. This test is intentionally written as an XFAIL regression guard. Its setup and precondition checks are outside the XFAIL section: initial workflow load must not show the error overlay, the Errors tab must initially stay hidden, subgraph entry must succeed, root return must succeed, and the replay scan must run. Only the final `Errors` tab visibility assertion is expected to fail on current Cloud behavior. ## What a green run means A green CI run for this PR means the Cloud-only bug was reproduced at the intended point. The test reaches the root-return replay scan, verifies that the replay scan ran, and then current Cloud behavior makes the Errors tab visible even though the Lotus model exists in `/api/assets`. If any earlier setup or navigation step fails, or if the root-return replay scan does not run, the test fails normally because those checks happen before `test.fail()` is applied. Locally, removing `test.fail()` produces the expected red result after the replay-scan precondition passes, with `panel-tab-errors` visible. The intended post-fix contract is that the replay scan still runs, but the Errors tab remains hidden. ## Why this is XFAIL This PR intentionally ships only the regression test, not the production fix. The final behavioral assertion is annotated with `test.fail()` because the current Cloud replay path still treats the nested subgraph promoted model widget as missing. When the follow-up fix lands, Playwright will report this test as an unexpected pass until the `test.fail()` annotation is removed. That is the handoff point for converting this regression guard into a normal passing E2E test. ## Follow-up The stacked fix PR is #11908. It updates the replay scan so nested subgraph container nodes are skipped, then removes the `test.fail()` annotation from this test. ## Verification - `pnpm exec oxfmt --check browser_tests/fixtures/assetApiFixture.ts browser_tests/tests/cloud-asset-default.spec.ts browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts` - `pnpm exec oxlint browser_tests/fixtures/assetApiFixture.ts browser_tests/tests/cloud-asset-default.spec.ts browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts --type-aware` - `pnpm exec eslint browser_tests/fixtures/assetApiFixture.ts browser_tests/tests/cloud-asset-default.spec.ts browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts` - `pnpm typecheck:browser` - `pnpm typecheck` - `pnpm lint` - `PLAYWRIGHT_LOCAL=1 PLAYWRIGHT_TEST_URL=http://localhost:8188 pnpm exec playwright test browser_tests/tests/propertiesPanel/errorsTabCloudMissingModels.spec.ts browser_tests/tests/cloud-asset-default.spec.ts --project=cloud` - Temporarily removed `test.fail()` locally and verified the test fails only after the replay-scan precondition passes, with `panel-tab-errors` visible ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-11907-test-add-Playwright-regression-test-for-nested-subgraph-Cloud-missing-model-3566d73d3650810b86d4de916c2852f9) by [Unito](https://www.unito.io)
96 lines
3.3 KiB
TypeScript
96 lines
3.3 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
|
|
import type { Asset } from '@comfyorg/ingest-types'
|
|
import {
|
|
assetRequestIncludesTag,
|
|
createCloudAssetsFixture
|
|
} from '@e2e/fixtures/assetApiFixture'
|
|
import {
|
|
STABLE_CHECKPOINT,
|
|
STABLE_CHECKPOINT_2
|
|
} from '@e2e/fixtures/data/assetFixtures'
|
|
|
|
const CLOUD_ASSETS: Asset[] = [STABLE_CHECKPOINT, STABLE_CHECKPOINT_2]
|
|
const WAITING_FOR_WIDGET_TYPE = 'waiting:type'
|
|
const WAITING_FOR_WIDGET_VALUE = 'waiting:value'
|
|
|
|
const test = createCloudAssetsFixture(CLOUD_ASSETS)
|
|
|
|
test.describe('Asset-supported node default value', { tag: '@cloud' }, () => {
|
|
test.afterEach(async ({ comfyPage }) => {
|
|
await comfyPage.nodeOps.clearGraph()
|
|
})
|
|
|
|
test('should use first cloud asset when server default is not in assets', async ({
|
|
cloudAssetRequests,
|
|
comfyPage
|
|
}) => {
|
|
// Wait for the checkpoint asset query to complete and the existing widget
|
|
// to upgrade into asset mode before creating a fresh node. The current
|
|
// default node may keep a previously resolved value; what matters is that
|
|
// new nodes resolve against the cloud asset list after the fetch.
|
|
await expect
|
|
.poll(() =>
|
|
cloudAssetRequests.some((url) =>
|
|
assetRequestIncludesTag(url, 'checkpoints')
|
|
)
|
|
)
|
|
.toBe(true)
|
|
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
comfyPage.page.evaluate((waitingForWidgetType) => {
|
|
const node = window.app!.graph.nodes.find(
|
|
(n: { type: string }) => n.type === 'CheckpointLoaderSimple'
|
|
)
|
|
return (
|
|
node?.widgets?.find(
|
|
(w: { name: string }) => w.name === 'ckpt_name'
|
|
)?.type ?? waitingForWidgetType
|
|
)
|
|
}, WAITING_FOR_WIDGET_TYPE),
|
|
{ timeout: 10_000 }
|
|
)
|
|
.toBe('asset')
|
|
|
|
// Add a new CheckpointLoaderSimple — should use first cloud asset,
|
|
// not the server's object_info default.
|
|
// Production resolves via getAssetFilename (user_metadata.filename →
|
|
// metadata.filename → asset.name). Test fixtures have no metadata
|
|
// filename, so asset.name is the resolved value.
|
|
const nodeId = await comfyPage.page.evaluate(() => {
|
|
const node = window.LiteGraph!.createNode('CheckpointLoaderSimple')
|
|
window.app!.graph.add(node!)
|
|
return node!.id
|
|
})
|
|
|
|
// Wait for the asset widget to mount AND its value to resolve.
|
|
// The widget type becomes 'asset' before the value is populated,
|
|
// so poll for both conditions together to avoid a race where the
|
|
// type check passes but the value is still the placeholder.
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
comfyPage.page.evaluate(
|
|
({ id, waitingForWidgetType, waitingForWidgetValue }) => {
|
|
const node = window.app!.graph.getNodeById(id)
|
|
const widget = node?.widgets?.find(
|
|
(w: { name: string }) => w.name === 'ckpt_name'
|
|
)
|
|
if (widget?.type !== 'asset') return waitingForWidgetType
|
|
const val = String(widget?.value ?? '')
|
|
return val === 'Select model' ? waitingForWidgetValue : val
|
|
},
|
|
{
|
|
id: nodeId,
|
|
waitingForWidgetType: WAITING_FOR_WIDGET_TYPE,
|
|
waitingForWidgetValue: WAITING_FOR_WIDGET_VALUE
|
|
}
|
|
),
|
|
{ timeout: 15_000 }
|
|
)
|
|
.toBe(CLOUD_ASSETS[0].name)
|
|
})
|
|
})
|