name: Tests permissions: contents: read on: pull_request: branches: [main] push: branches: [main] workflow_dispatch: # Superseded PR pushes cancel their older run. Every main push gets its own # group (run_id), so no main commit's run is ever cancelled or dropped as pending. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} env: PYTHONFAULTHANDLER: "1" # Every job has a time limit: ceil(1.5 x the slowest of three recent green runs on main) + 3 # minutes, never under 5. The 3 minutes leave room for the Python runner's watchdog (120 s, see # tests/run_suite.py) to print its stack dump before GitHub cancels the job. A limit changes only # with new measurements written here (TESTING.md "Time limits and hangs"). # Measured 2026-10-26 on runs 40257217681 (3be0ada), 40240706568 (fb9134f), 40228167328 (f9835aa): # py matrix, slowest py3.10 on windows-latest: 12m43s / 15m54s / 14m53s -> 27 min # (ubuntu 5m27s-6m27s, macos 6m14s-8m30s, windows 11m42s-15m54s) # 3.10 and 3.11 left the matrix and 3.14 joined it in #356; re-measure after three green main runs. # real ComfyUI compatibility: 13m32s / 8m55s / 11m46s -> 24 min # coverage gate (85%): 7m26s / 7m22s / 7m54s -> 15 min # perf budget 30s / 29s / 27s, black and flake8 39s / 37s / 39s, # frontend modules 23s / 19s / 29s -> 5 min (the floor) jobs: test: name: py${{ matrix.python-version }} on ${{ matrix.os }} runs-on: ${{ matrix.os }} timeout-minutes: 27 strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] python-version: ["3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v7 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v7 with: python-version: ${{ matrix.python-version }} cache: pip cache-dependency-path: requirements-dev.txt - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Run tests run: python -m tests.run_suite -v comfyui: name: real ComfyUI compatibility runs-on: ubuntu-latest timeout-minutes: 24 steps: - uses: actions/checkout@v7 with: persist-credentials: false - uses: actions/setup-node@v7 with: node-version: "24" cache: npm cache-dependency-path: tools/vendor/package-lock.json - name: Read the reviewed runtime pins id: runtime run: | node --input-type=module <<'JS' >> "$GITHUB_OUTPUT" import runtime from './tools/vendor/comfyui-runtime.json' with { type: 'json' }; for (const [key, value] of Object.entries(runtime)) console.log(`${key}=${value}`); import crossPack from './tools/vendor/cross-pack-runtime.json' with { type: 'json' }; console.log(`kikoRepository=${crossPack.repository}`); console.log(`kikoRevision=${crossPack.revision}`); JS - name: Check out pinned ComfyUI uses: actions/checkout@v7 with: repository: ${{ steps.runtime.outputs.repository }} ref: ${{ steps.runtime.outputs.revision }} path: .ci-comfyui persist-credentials: false - name: Check out pinned KikoTools for compatibility uses: actions/checkout@v7 with: repository: ${{ steps.runtime.outputs.kikoRepository }} ref: ${{ steps.runtime.outputs.kikoRevision }} path: .ci-kikotools persist-credentials: false - uses: actions/setup-python@v7 with: python-version: ${{ steps.runtime.outputs.pythonVersion }} cache: pip cache-dependency-path: | requirements-dev.txt .ci-comfyui/requirements.txt tools/vendor/comfyui-runtime.json - name: Install CPU runtime and test dependencies run: | python -m pip install --upgrade pip pip install "torch==${{ steps.runtime.outputs.torchVersion }}" "torchvision==${{ steps.runtime.outputs.torchvisionVersion }}" --index-url https://download.pytorch.org/whl/cpu pip install -r .ci-comfyui/requirements.txt -r requirements-dev.txt "torch==${{ steps.runtime.outputs.torchVersion }}" "torchvision==${{ steps.runtime.outputs.torchvisionVersion }}" - run: npm ci --prefix tools/vendor --ignore-scripts - run: npx --prefix tools/vendor playwright install --with-deps chromium - name: Classic and Nodes 2.0 startup and compatibility env: PM_COMFY_ROOT: ${{ github.workspace }}/.ci-comfyui PM_TEST_PYTHON: python run: npm run --prefix tools/vendor test:comfyui - name: Real cached and headless execution history env: PM_COMFY_ROOT: ${{ github.workspace }}/.ci-comfyui PM_TEST_PYTHON: python run: npm run --prefix tools/vendor test:executions - name: Independent and combined pack compatibility env: PM_COMFY_ROOT: ${{ github.workspace }}/.ci-comfyui PM_KIKO_ROOT: ${{ github.workspace }}/.ci-kikotools PM_TEST_PYTHON: python run: npm run --prefix tools/vendor test:compat - name: Upload real-host reports and startup logs if: always() uses: actions/upload-artifact@v7 with: name: real-comfyui-reports path: tools/vendor/test-results/ if-no-files-found: ignore retention-days: 14 coverage: name: coverage gate (85%) runs-on: ubuntu-latest timeout-minutes: 15 steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: "3.12" cache: pip cache-dependency-path: requirements-dev.txt - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Measure coverage run: | python -m coverage run -m tests.run_suite python -m coverage combine python -m coverage report --fail-under=85 perf-budget: # The reference wall-clock measurement (TESTING.md "Performance budgets"). # Shared-runner timing varies, so it never gates a pull request: it runs on # each push to main and on demand. A red run on main means fix the code. name: perf budget if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: "3.13" cache: pip cache-dependency-path: requirements-dev.txt - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r requirements-dev.txt - name: Workspace filter budget env: PROMPTMANAGER_PERF_BUDGET: "1" run: python -m unittest -v tests.test_api_workspace_images.TestWorkspaceImages.test_filter_budget lint: name: black and flake8 runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v7 - uses: actions/setup-python@v7 with: python-version: "3.12" - run: pip install black flake8 - run: black --check . - run: flake8 . js-test: name: frontend modules runs-on: ubuntu-latest timeout-minutes: 5 steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version: "24" - name: Check shipped extension and page modules parse run: | shopt -s globstar nullglob for f in web/comfy/**/*.js web/comfy/**/*.mjs web/js/**/*.js web/js/**/*.mjs; do node --input-type=module --check < "$f" || exit 1 done - name: Install pinned shared test contracts run: npm ci --prefix tools/vendor --ignore-scripts - name: Run JS tests run: node --test tests/js/*.test.js