A ComfyUI update can break a node without a line of the pack changing, and until now nothing looked: CI installs no ComfyUI. A nightly workflow now installs ComfyUI's newest release and its development head for the CPU and runs three checks that need no model: - tests/test_comfy_core_contract.py reads the pack's source for everything it takes from ComfyUI (modules, functions, ComfyUI's own nodes it calls and the inputs it passes them) and looks each one up in the real thing. - scripts/compat_boot.py asks a running ComfyUI which nodes it offers: every public node of the pack, and every node and required input the example workflows use. - scripts/dev/face_smoke.mjs puts every node on a graph in headless Chrome, in both node views, and fails when a face does not come up or something throws. The full Python suite runs there too. A failure on the release opens an issue; the development head only after two nights in a row.
5.6 KiB
Live frontend testing
Frontend work in this pack is proven on a real canvas: a node is created in a real ComfyUI tab, real mouse drags are dispatched, and the result is a screenshot a person looks at. Reading the code, or a test that only checks which input received a link, has let broken designs through before (three sockets stacked on one pixel passed every automated check). This is the recipe.
The pieces
-
ComfyUI on
http://127.0.0.1:8188(setCOMFY_URLto point the scripts elsewhere), with this repository installed or linked intocustom_nodes. A changedINPUT_TYPESis served only after a full restart. Stop the test instance, then start it from the ComfyUI checkout using that installation's Python environment:python main.py --listen 127.0.0.1From this repository, run
python scripts/run_python_tests.pywith the same environment to isolate ComfyUI stubs and keep CPU thread counts bounded. SetAUSBOSS_COMFY_ROOTto the ComfyUI checkout to enable the optional core video integration checks. -
Headless Chrome or Chromium on an unused CDP port; these examples use 9334. Use a separate test profile and the installed browser executable:
chromium \ --headless=new --remote-debugging-port=9334 --user-data-dir=/tmp/chrome-ausboss \ --window-size=1800,1100 --no-first-run --no-default-browser-check about:blank & -
scripts/dev/— small Node scripts that talk CDP directly, with no package dependencies. Use a Node version with built-infetchandWebSocket:script does cdp.mjs <port> <workflow.json|-> <out.png|-> [eval.js] [waitMs]opens/reuses the Comfy tab, optionally loads a workflow, evaluates a JS file, screenshots (a returned __cliprect clips the shot), prints the eval result as JSONcdp_drag.mjs <port> <setup.js> <mid.png|-> <after.png|-> [check.js]setup.jsreturns{from, to, __clip}in client pixels; presses atfrom, moves toto, screenshots mid-drag, releases, evaluatescheck.js, screenshots againaudit_workflows.mjs <port> <output-dir> [workflow.json ...]loads every example (or selected files), exports API and loaded graphs, captures screenshots, and fails on overlaps, missing nodes, browser exceptions, or exposed AusBoss number widgets wheel_sweep.mjs <port> [--vue] [--only=Type,Type] [--workflow=file.json] [--shots=dir]turns the mouse wheel at every point of every node of the pack (or of the nodes in one workflow) and fails on any spot where the graph does not zoom, naming the element that took the wheel; --realsends real wheel turns, slower and on a coarser gridface_smoke.mjs <port> [--vue] [--only=Type,Type]puts one of each node of the pack on an empty graph and fails when a face does not come up: no node, a panel that is not on the page or has no size, an error thrown, a file of the pack that did not load. Needs no model, so it also runs in the nightly ComfyUI check cdp_nav_debug.mjs <port>navigates the tab in place and logs every CDP event, for when a tab stops answering cdp_hang.mjs <port>pre-arms the debugger on a fresh tab, loads ComfyUI, and pauses to print the stack if the page stops answering CDP_RELOAD=1closes the Comfy tab and opens a fresh one with the cache cleared — required after editing any.mjs, which the frontend caches hard.
Screenshot convention
An eval creates the node at pos [0, 0], sets app.canvas.ds.scale = 1 and
ds.offset = [160, 230], and returns
__clip: { x: r.left + 152, y: r.top + 192, width: node.size[0] + 16, height: node.size[1] + 46 }
(r is the canvas element's bounding rect). A drag gives two shots: _mid,
taken during the drag, and _after, the result.
Gotchas learned the hard way
- Clearing the graph reuses node ids, and the frontend's widget store
hands a new node the previous node's widget values (a toggled
previewcame backfalse). Bumpapp.graph.last_node_id += 50afterapp.graph.clear(), or set the value you want explicitly. - A real mouse drag marks the workflow modified. The next in-place
navigation raises ComfyUI's
beforeunloaddialog and the whole tab stops answering every CDP call, even1+1; it looks exactly like a renderer hang. The scripts auto-accept dialogs, andCDP_RELOAD=1makescdp.mjsreload into a fresh tab rather than navigate the old one. - Reset a half-finished link drag (
app.canvas.linkConnector.reset()) before clearing the graph in an eval. - The mid-drag screenshot shows the socket lit and the tooltip, not the
greyed field: the field greys once the link exists, in the
_aftershot. - Widget input sockets are drawn only while a link is dragged, hovered, or connected, so a "no socket visible" screenshot at rest is normal; the mid-drag shot is the one that shows where a link can land.
cdp.mjsandcdp_drag.mjsrequests time out after 25 s; the workflow audit allows 45 s. Inspect the browser's dialogs and logs when a request stops responding.- Keep test renders and logs out of the repository; only deliberately
shipped sample media belongs in
example_workflows/inputs/.
What to verify for a node face
- The face at rest (values, alignment, no clipped panel).
- Every group disclosure open.
- A real link dropped on every row and socket,
_midand_after. app.graphToPrompt()sends the link for a linked input and the typed value otherwise.- Save, reload (fresh tab), and confirm links, values and hidden state.
- The preview switch on and off where the node has one.
- The wheel zooms the graph over every part of the face:
wheel_sweep.mjs, once plain and once with--vue.