Compare commits
5 Commits
main
...
test/fe-74
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7bc4e5ce7f | ||
|
|
7f707cfef5 | ||
|
|
4fff42edb7 | ||
|
|
2025cbe78a | ||
|
|
b1b410b5fb |
@@ -1,30 +0,0 @@
|
||||
import { expect } from '@playwright/test'
|
||||
|
||||
import { comfyPageFixture as test } from '@e2e/fixtures/ComfyPage'
|
||||
|
||||
test.describe(
|
||||
'Textarea widget font size',
|
||||
{ tag: ['@widget', '@vue-nodes'] },
|
||||
() => {
|
||||
test.beforeEach(async ({ comfyPage }) => {
|
||||
await comfyPage.workflow.loadWorkflow('default')
|
||||
})
|
||||
|
||||
test('applies Comfy.TextareaWidget.FontSize to Vue Nodes 2.0 textarea widget', async ({
|
||||
comfyPage
|
||||
}) => {
|
||||
const textarea = comfyPage.vueNodes.nodes.locator('textarea').first()
|
||||
await expect(textarea).toBeVisible()
|
||||
|
||||
await comfyPage.settings.setSetting('Comfy.TextareaWidget.FontSize', 14)
|
||||
await expect
|
||||
.poll(() => textarea.evaluate((el) => getComputedStyle(el).fontSize))
|
||||
.toBe('14px')
|
||||
|
||||
await comfyPage.settings.setSetting('Comfy.TextareaWidget.FontSize', 22)
|
||||
await expect
|
||||
.poll(() => textarea.evaluate((el) => getComputedStyle(el).fontSize))
|
||||
.toBe('22px')
|
||||
})
|
||||
}
|
||||
)
|
||||
@@ -180,4 +180,44 @@ test.describe('Vue Nodes Batch Image Preview', { tag: '@vue-nodes' }, () => {
|
||||
await expect.poll(() => countColumns(node.imageGrid)).toBeLessThan(10)
|
||||
}
|
||||
)
|
||||
|
||||
wstest(
|
||||
'requests lightweight thumbnail URLs for grid cells',
|
||||
async ({ comfyPage, getWebSocket }) => {
|
||||
const execution = new ExecutionHelper(comfyPage, await getWebSocket())
|
||||
|
||||
await test.step('Add node', async () => {
|
||||
await comfyPage.menu.topbar.newWorkflowButton.click()
|
||||
await comfyPage.nextFrame()
|
||||
|
||||
await comfyPage.searchBoxV2.addNode('Preview Image')
|
||||
const previewImage = comfyPage.vueNodes.getNodeByTitle('Preview Image')
|
||||
await expect(previewImage).toBeVisible()
|
||||
})
|
||||
|
||||
const node = await comfyPage.vueNodes.getFixtureByTitle('Preview Image')
|
||||
const gridImages = node.imageGrid.locator('img')
|
||||
|
||||
await test.step('Inject a multi-image grid', async () => {
|
||||
const images = Array.from({ length: 4 }, (_, index) => ({
|
||||
filename: `grid-${index}.png`,
|
||||
subfolder: '',
|
||||
type: 'output'
|
||||
}))
|
||||
execution.executed('', '1', { images })
|
||||
await expect(gridImages).toHaveCount(4)
|
||||
})
|
||||
|
||||
// FE-741: small on-node grid cells must request a server re-encoded
|
||||
// thumbnail (`preview=webp;75`, `;` may be percent-encoded) instead of
|
||||
// downloading the full-resolution image, while still pointing at the
|
||||
// real `/api/view` URL for that output. Verifies the full path: WS
|
||||
// output -> nodeOutputStore.buildImageUrls -> getGridThumbnailUrl ->
|
||||
// rendered grid `<img>`.
|
||||
for (const cell of await gridImages.all()) {
|
||||
await expect(cell).toHaveAttribute('src', /[?&]preview=webp(%3B|;)75/)
|
||||
await expect(cell).toHaveAttribute('src', /[?&]filename=grid-\d+\.png/)
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 107 KiB |
|
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 139 KiB After Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 106 KiB |
@@ -121,7 +121,6 @@
|
||||
--comfy-topbar-height: 2.5rem;
|
||||
--workflow-tabs-height: 2.375rem;
|
||||
--comfy-input-bg: #222;
|
||||
--comfy-textarea-font-size: 10px;
|
||||
--input-text: #ddd;
|
||||
--descrip-text: #999;
|
||||
--drag-text: #ccc;
|
||||
|
||||
@@ -173,7 +173,7 @@ function makePreview3DAdvancedNode(
|
||||
constructor: { comfyClass: overrides.comfyClass ?? 'Preview3DAdvanced' },
|
||||
size: [400, 550],
|
||||
setSize: vi.fn(),
|
||||
widgets: overrides.widgets ?? [{ name: 'viewport_state', value: '' }],
|
||||
widgets: overrides.widgets ?? [{ name: 'image', value: '' }],
|
||||
properties: overrides.properties ?? {}
|
||||
} as unknown as LGraphNode
|
||||
}
|
||||
@@ -783,9 +783,9 @@ describe('Comfy.Preview3DAdvanced.nodeCreated', () => {
|
||||
expect(load3dInstance.setCameraState).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('attaches a camera-only serializeValue to the viewport_state widget', async () => {
|
||||
it('attaches a camera-only serializeValue to the image widget', async () => {
|
||||
const { preview3DAdvancedExt } = await loadExtensionsFresh()
|
||||
const widgets: FakeWidget[] = [{ name: 'viewport_state', value: '' }]
|
||||
const widgets: FakeWidget[] = [{ name: 'image', value: '' }]
|
||||
const node = makePreview3DAdvancedNode({ widgets })
|
||||
|
||||
await preview3DAdvancedExt.nodeCreated(node)
|
||||
@@ -795,7 +795,7 @@ describe('Comfy.Preview3DAdvanced.nodeCreated', () => {
|
||||
|
||||
it('serializeValue returns live camera_info plus empty media fields, omitting model_3d_info when none', async () => {
|
||||
const { preview3DAdvancedExt } = await loadExtensionsFresh()
|
||||
const widgets: FakeWidget[] = [{ name: 'viewport_state', value: '' }]
|
||||
const widgets: FakeWidget[] = [{ name: 'image', value: '' }]
|
||||
const node = makePreview3DAdvancedNode({ widgets })
|
||||
|
||||
const load3d = makeLoad3dMock()
|
||||
@@ -819,7 +819,7 @@ describe('Comfy.Preview3DAdvanced.nodeCreated', () => {
|
||||
|
||||
it('serializeValue wraps a present getModelInfo result in a single-element list', async () => {
|
||||
const { preview3DAdvancedExt } = await loadExtensionsFresh()
|
||||
const widgets: FakeWidget[] = [{ name: 'viewport_state', value: '' }]
|
||||
const widgets: FakeWidget[] = [{ name: 'image', value: '' }]
|
||||
const node = makePreview3DAdvancedNode({ widgets })
|
||||
|
||||
const load3d = makeLoad3dMock()
|
||||
|
||||
@@ -270,16 +270,8 @@ useExtensionService().registerExtension({
|
||||
}
|
||||
],
|
||||
getCustomWidgets() {
|
||||
const VIEWPORT_STATE_NODES = new Set([
|
||||
'Preview3DAdvanced',
|
||||
'PreviewGaussianSplat',
|
||||
'PreviewPointCloud'
|
||||
])
|
||||
return {
|
||||
LOAD_3D(node) {
|
||||
const inputName = VIEWPORT_STATE_NODES.has(node.constructor.comfyClass)
|
||||
? 'viewport_state'
|
||||
: 'image'
|
||||
const hasModelFileWidget = node.widgets?.some(
|
||||
(w) => w.name === 'model_file'
|
||||
)
|
||||
@@ -324,9 +316,9 @@ useExtensionService().registerExtension({
|
||||
|
||||
const widget = new ComponentWidgetImpl({
|
||||
node: node,
|
||||
name: inputName,
|
||||
name: 'image',
|
||||
component: Load3D,
|
||||
inputSpec: { ...inputSpecLoad3D, name: inputName },
|
||||
inputSpec: inputSpecLoad3D,
|
||||
options: {}
|
||||
})
|
||||
|
||||
@@ -723,7 +715,7 @@ useExtensionService().registerExtension({
|
||||
})
|
||||
|
||||
useLoad3d(node).waitForLoad3d((load3d) => {
|
||||
const sceneWidget = node.widgets?.find((w) => w.name === 'viewport_state')
|
||||
const sceneWidget = node.widgets?.find((w) => w.name === 'image')
|
||||
if (!sceneWidget) return
|
||||
|
||||
const resolveLoad3d = () => nodeToLoad3dMap.get(node) ?? load3d
|
||||
|
||||
@@ -186,7 +186,7 @@ describe('Comfy.PreviewGaussianSplat.nodeCreated', () => {
|
||||
|
||||
expect(node.properties['Last Time Model File']).toBe('scene.ply')
|
||||
expect(configureForSaveMeshMock).toHaveBeenLastCalledWith(
|
||||
'temp',
|
||||
'output',
|
||||
'scene.ply',
|
||||
expect.objectContaining({ silentOnNotFound: true })
|
||||
)
|
||||
@@ -231,7 +231,7 @@ describe('Comfy.PreviewGaussianSplat.nodeCreated', () => {
|
||||
const node = makePreviewNode({
|
||||
widgets: [
|
||||
{ name: 'model_file', value: '' },
|
||||
{ name: 'viewport_state', value: '' },
|
||||
{ name: 'image', value: '' },
|
||||
widthWidget,
|
||||
heightWidget
|
||||
]
|
||||
@@ -262,7 +262,7 @@ describe('Comfy.PreviewGaussianSplat.nodeCreated', () => {
|
||||
)
|
||||
const sceneWidget: FakeWidget & {
|
||||
serializeValue?: () => Promise<unknown>
|
||||
} = { name: 'viewport_state', value: '' }
|
||||
} = { name: 'image', value: '' }
|
||||
const node = makePreviewNode({
|
||||
widgets: [{ name: 'model_file', value: '' }, sceneWidget]
|
||||
})
|
||||
@@ -318,7 +318,7 @@ describe('Comfy.PreviewPointCloud.nodeCreated', () => {
|
||||
|
||||
expect(node.properties['Last Time Model File']).toBe('pointcloud.ply')
|
||||
expect(configureForSaveMeshMock).toHaveBeenLastCalledWith(
|
||||
'temp',
|
||||
'output',
|
||||
'pointcloud.ply',
|
||||
expect.objectContaining({ silentOnNotFound: true })
|
||||
)
|
||||
|
||||
@@ -46,7 +46,7 @@ function applyResultToLoad3d(
|
||||
}
|
||||
|
||||
const config = new Load3DConfiguration(load3d, node.properties)
|
||||
config.configureForSaveMesh('temp', normalizedPath, {
|
||||
config.configureForSaveMesh('output', normalizedPath, {
|
||||
silentOnNotFound: true
|
||||
})
|
||||
|
||||
@@ -119,7 +119,7 @@ function createPreview3DExtension(
|
||||
if (!lastTimeModelFile) return
|
||||
|
||||
const config = new Load3DConfiguration(load3d, node.properties)
|
||||
config.configureForSaveMesh('temp', lastTimeModelFile as string, {
|
||||
config.configureForSaveMesh('output', lastTimeModelFile as string, {
|
||||
silentOnNotFound: true
|
||||
})
|
||||
|
||||
@@ -136,9 +136,7 @@ function createPreview3DExtension(
|
||||
})
|
||||
|
||||
waitForLoad3d((load3d) => {
|
||||
const sceneWidget = node.widgets?.find(
|
||||
(w) => w.name === 'viewport_state'
|
||||
)
|
||||
const sceneWidget = node.widgets?.find((w) => w.name === 'image')
|
||||
const widthWidget = node.widgets?.find((w) => w.name === 'width')
|
||||
const heightWidget = node.widgets?.find((w) => w.name === 'height')
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
:class="
|
||||
cn(
|
||||
WidgetInputBaseClass,
|
||||
'size-full resize-none text-(length:--comfy-textarea-font-size) leading-normal',
|
||||
'size-full resize-none text-xs',
|
||||
!hideLayoutField && 'pt-5',
|
||||
// Avoid overflow-auto when idle to prevent per-textarea compositing layers.
|
||||
'overflow-hidden hover:overflow-auto focus:overflow-auto'
|
||||
|
||||