mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-01-26 19:09:52 +00:00
Remove server elements from unit tests (#2777)
This commit is contained in:
@@ -21,8 +21,5 @@ DEPLOY_COMFYUI_DIR=/home/ComfyUI/web
|
||||
# If you aren't using a separate install for testing, point this to your regular install.
|
||||
TEST_COMFYUI_DIR=/home/ComfyUI
|
||||
|
||||
# The directory containing the ComfyUI_examples repo used to extract test workflows.
|
||||
EXAMPLE_REPO_PATH=tests-ui/ComfyUI_examples
|
||||
|
||||
# Whether to enable minification of the frontend code.
|
||||
ENABLE_MINIFY=true
|
||||
|
||||
19
.github/workflows/test-ui.yaml
vendored
19
.github/workflows/test-ui.yaml
vendored
@@ -68,27 +68,8 @@ jobs:
|
||||
ComfyUI_frontend
|
||||
key: comfyui-setup-${{ needs.setup.outputs.cache-key }}
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
|
||||
- name: Install requirements
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu
|
||||
pip install -r requirements.txt
|
||||
pip install wait-for-it
|
||||
working-directory: ComfyUI
|
||||
|
||||
- name: Start ComfyUI server
|
||||
run: |
|
||||
python main.py --cpu --multi-user &
|
||||
wait-for-it --service 127.0.0.1:8188 -t 600
|
||||
working-directory: ComfyUI
|
||||
|
||||
- name: Run Jest tests
|
||||
run: |
|
||||
npm run test:generate
|
||||
npm run test:jest -- --verbose
|
||||
working-directory: ComfyUI_frontend
|
||||
|
||||
|
||||
@@ -546,9 +546,7 @@ navigate to `http://<server_ip>:5173` (e.g. `http://192.168.2.20:5173` here), to
|
||||
|
||||
### Unit Test
|
||||
|
||||
- `git clone https://github.com/comfyanonymous/ComfyUI_examples.git` to `tests-ui/ComfyUI_examples` or the EXAMPLE_REPO_PATH location specified in .env
|
||||
- `npm i` to install all dependencies
|
||||
- `npm run test:generate` to fetch `tests-ui/data/object_info.json`
|
||||
- `npm run test:jest` to execute all unit tests.
|
||||
|
||||
### Component Test
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
"format": "prettier --write './**/*.{js,ts,tsx,vue,mts}'",
|
||||
"format:check": "prettier --check './**/*.{js,ts,tsx,vue,mts}'",
|
||||
"test:jest": "jest --config jest.config.ts",
|
||||
"test:generate": "npx tsx tests-ui/setup",
|
||||
"test:browser": "npx playwright test",
|
||||
"test:component": "vitest run src/components/",
|
||||
"prepare": "husky || true",
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
// @ts-strict-ignore
|
||||
/*
|
||||
Script to generate test API json from the ComfyUI_examples repo.
|
||||
Requires the repo to be cloned to the tests-ui directory or specified via the EXAMPLE_REPO_PATH env var.
|
||||
*/
|
||||
import chalk from 'chalk'
|
||||
import dotenv from 'dotenv'
|
||||
import fs from 'fs'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import path from 'path'
|
||||
|
||||
import { getFromFlacBuffer } from '@/scripts/metadata/flac'
|
||||
import { getFromPngBuffer } from '@/scripts/metadata/png'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const repoPath =
|
||||
process.env.EXAMPLE_REPO_PATH || path.resolve(dirname, 'ComfyUI_examples')
|
||||
const workflowsPath = path.resolve(dirname, 'workflows', 'examples')
|
||||
|
||||
if (!fs.existsSync(repoPath)) {
|
||||
console.error(
|
||||
`ComfyUI_examples repo not found. Please clone this to ${repoPath} or set the EXAMPLE_REPO_PATH env var (see .env_example) and re-run.`
|
||||
)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(workflowsPath)) {
|
||||
await fs.promises.mkdir(workflowsPath)
|
||||
}
|
||||
|
||||
async function* getFiles(
|
||||
dir: string,
|
||||
...exts: string[]
|
||||
): AsyncGenerator<string, void, void> {
|
||||
const dirents = await fs.promises.readdir(dir, { withFileTypes: true })
|
||||
for (const dirent of dirents) {
|
||||
const res = path.resolve(dir, dirent.name)
|
||||
if (dirent.isDirectory()) {
|
||||
yield* getFiles(res, ...exts)
|
||||
} else if (exts.includes(path.extname(res))) {
|
||||
yield res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function validateMetadata(metadata: Record<string, string>) {
|
||||
const check = (prop: 'prompt' | 'workflow') => {
|
||||
const v = metadata?.[prop]
|
||||
if (!v) throw `${prop} not found in metadata`
|
||||
try {
|
||||
JSON.parse(v)
|
||||
} catch (error) {
|
||||
throw `${prop} invalid json: ${error.message}`
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
return { prompt: check('prompt'), workflow: check('workflow') }
|
||||
}
|
||||
|
||||
async function hasExampleChanged(
|
||||
existingFilePath: string,
|
||||
exampleJson: string
|
||||
) {
|
||||
return exampleJson !== (await fs.promises.readFile(existingFilePath, 'utf8'))
|
||||
}
|
||||
|
||||
// Example images to ignore as they don't contain workflows
|
||||
const ignore = [
|
||||
'unclip_sunset.png',
|
||||
'unclip_mountains.png',
|
||||
'inpaint_yosemite_inpaint_example.png',
|
||||
'controlnet_shark_depthmap.png',
|
||||
'controlnet_pose_worship.png',
|
||||
'controlnet_pose_present.png',
|
||||
'controlnet_input_scribble_example.png',
|
||||
'controlnet_house_scribble.png'
|
||||
]
|
||||
|
||||
// Find all existing examples so we can check if any are removed/changed
|
||||
const existing = new Set(
|
||||
(await fs.promises.readdir(workflowsPath, { withFileTypes: true }))
|
||||
.filter((d) => d.isFile())
|
||||
.map((d) => path.resolve(workflowsPath, d.name))
|
||||
)
|
||||
|
||||
const results = {
|
||||
new: [],
|
||||
changed: [],
|
||||
unchanged: [],
|
||||
missing: [],
|
||||
failed: []
|
||||
}
|
||||
|
||||
let total = 0
|
||||
for await (const file of getFiles(repoPath, '.png', '.flac')) {
|
||||
const cleanedName = path
|
||||
.relative(repoPath, file)
|
||||
.replaceAll('/', '_')
|
||||
.replaceAll('\\', '_')
|
||||
|
||||
if (ignore.includes(cleanedName)) continue
|
||||
total++
|
||||
|
||||
let metadata: { prompt: string; workflow: string }
|
||||
try {
|
||||
const { buffer } = await fs.promises.readFile(file)
|
||||
switch (path.extname(file)) {
|
||||
case '.png':
|
||||
metadata = await validateMetadata(getFromPngBuffer(buffer))
|
||||
break
|
||||
case '.flac':
|
||||
metadata = await validateMetadata(getFromFlacBuffer(buffer))
|
||||
break
|
||||
}
|
||||
|
||||
const outPath = path.resolve(workflowsPath, cleanedName + '.json')
|
||||
const exampleJson = JSON.stringify(metadata)
|
||||
if (existing.has(outPath)) {
|
||||
existing.delete(outPath)
|
||||
if (await hasExampleChanged(outPath, exampleJson)) {
|
||||
results.changed.push(outPath)
|
||||
} else {
|
||||
// Unchanged, no point in re-saving
|
||||
results.unchanged.push(outPath)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
results.new.push(outPath)
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(outPath, exampleJson, 'utf8')
|
||||
} catch (error) {
|
||||
results.failed.push({ file, error })
|
||||
}
|
||||
}
|
||||
|
||||
// Any workflows left in the existing set are now missing, these will want checking and manually removing
|
||||
results.missing.push(...existing)
|
||||
|
||||
const c = (v: number, gt0: 'red' | 'yellow' | 'green') =>
|
||||
chalk[v > 0 ? gt0 : 'gray'](v)
|
||||
|
||||
console.log(`Processed ${chalk.green(total)} examples`)
|
||||
console.log(` ${chalk.gray(results.unchanged.length)} unchanged`)
|
||||
console.log(` ${c(results.changed.length, 'yellow')} changed`)
|
||||
console.log(` ${c(results.new.length, 'green')} new`)
|
||||
console.log(` ${c(results.missing.length, 'red')} missing`)
|
||||
console.log(` ${c(results.failed.length, 'red')} failed`)
|
||||
|
||||
if (results.missing.length) {
|
||||
console.log()
|
||||
console.log(
|
||||
chalk.red(
|
||||
'The following examples are missing and require manual reviewing & removal:'
|
||||
)
|
||||
)
|
||||
for (const m of results.missing) {
|
||||
console.log(m)
|
||||
}
|
||||
}
|
||||
|
||||
if (results.failed.length) {
|
||||
console.log()
|
||||
console.log(chalk.red('The following examples failed to extract:'))
|
||||
for (const m of results.failed) {
|
||||
console.log(m.file)
|
||||
console.error(m.error)
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
||||
import http from 'http'
|
||||
import { resolve } from 'path'
|
||||
|
||||
async function setup() {
|
||||
await new Promise<void>((res, rej) => {
|
||||
http
|
||||
.get('http://127.0.0.1:8188/object_info', (resp) => {
|
||||
let data = ''
|
||||
resp.on('data', (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
resp.on('end', () => {
|
||||
// Modify the response data to add some checkpoints
|
||||
const objectInfo = JSON.parse(data)
|
||||
objectInfo.CheckpointLoaderSimple.input.required.ckpt_name[0] = [
|
||||
'model1.safetensors',
|
||||
'model2.ckpt'
|
||||
]
|
||||
objectInfo.VAELoader.input.required.vae_name[0] = [
|
||||
'vae1.safetensors',
|
||||
'vae2.ckpt'
|
||||
]
|
||||
|
||||
data = JSON.stringify(objectInfo, undefined, '\t')
|
||||
|
||||
const outDir = resolve('./tests-ui/data')
|
||||
if (!existsSync(outDir)) {
|
||||
mkdirSync(outDir)
|
||||
}
|
||||
|
||||
const outPath = resolve(outDir, 'object_info.json')
|
||||
console.log(
|
||||
`Writing ${Object.keys(objectInfo).length} nodes to ${outPath}`
|
||||
)
|
||||
writeFileSync(outPath, data, {
|
||||
encoding: 'utf8'
|
||||
})
|
||||
res()
|
||||
})
|
||||
})
|
||||
.on('error', rej)
|
||||
})
|
||||
}
|
||||
|
||||
setup()
|
||||
@@ -1,7 +1,4 @@
|
||||
// @ts-strict-ignore
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import {
|
||||
type ComfyNodeDef,
|
||||
validateComfyNodeDef
|
||||
@@ -76,19 +73,4 @@ describe('validateNodeDef', () => {
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
it('Should accept all built-in node definitions', async () => {
|
||||
const nodeDefs = Object.values(
|
||||
JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.resolve('./tests-ui/data/object_info.json'),
|
||||
'utf8'
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
for (const nodeDef of nodeDefs) {
|
||||
expect(validateComfyNodeDef(nodeDef)).not.toBeNull()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user