frontend-profiling workflow (gradio-app/gradio)
The frontend-profiling workflow from gradio-app/gradio, explained and optimized by Latchkey.
CI health: A - excellent
Point runs-on at Latchkey and get job timeouts, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the frontend-profiling workflow from the gradio-app/gradio repository, a real project running GitHub Actions. It is shown here with attribution under its Apache-2.0 license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: "frontend-profiling"
on:
pull_request:
types: [opened, synchronize, reopened]
concurrency:
group: "frontend-profiling-${{ github.event.pull_request.number }}"
cancel-in-progress: true
permissions: {}
jobs:
benchmark:
env:
UV_EXCLUDE_NEWER: 7 days
PLAYWRIGHT_VERSION: 1.60.0
permissions:
contents: read
pull-requests: write
name: "frontend-benchmark"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: "gradio-app/gradio/.github/actions/changes@main"
id: changes
with:
filter: "js"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Find latest release tag
if: steps.changes.outputs.should_run == 'true'
id: latest_tag
run: echo "tag=$(git tag --list 'gradio@*' --sort=-v:refname | head -n 1)" >> "$GITHUB_OUTPUT"
- name: Setup Python
if: steps.changes.outputs.should_run == 'true'
uses: actions/setup-python@v5
with:
python-version: "3.10"
- name: Install uv
if: steps.changes.outputs.should_run == 'true'
run: curl -LsSf https://astral.sh/uv/0.11.3/install.sh | sh
- name: Install pnpm
if: steps.changes.outputs.should_run == 'true'
uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # @v4
with:
version: 10.17.0
- name: Setup Node.js
if: steps.changes.outputs.should_run == 'true'
uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
cache-dependency-path: pnpm-lock.yaml
# Install the benchmark "base" (latest release) straight from PyPI instead
# of checking out the tag and rebuilding it - the published wheel already
# ships the built frontend (gradio/templates), so this drops the ~4min base
# `pnpm build` on every run. We stay on the PR checkout the whole job: the
# benchmark spec + demo are the PR's versions for both runs, and the demo is
# launched as `python <runner>.py` (the runner/demo dir is on sys.path, not
# the cwd), so `import gradio` resolves to the installed package - the wheel
# here, the editable PR build later. Base and PR use separate venvs so the
# PR's editable install can't shadow the base wheel.
- name: Install base gradio from the published release (base)
if: steps.changes.outputs.should_run == 'true'
run: |
export PATH="$HOME/.cargo/bin:$PATH"
uv venv --python=3.10 venv_base
. venv_base/bin/activate
TAG="${{ steps.latest_tag.outputs.tag }}"
uv pip install "gradio==${TAG#gradio@}"
- name: Install frontend test deps (base)
if: steps.changes.outputs.should_run == 'true'
# node_modules is only needed to run the Playwright benchmark (no build).
run: |
pnpm i --frozen-lockfile --ignore-scripts
pnpm add -Dw --ignore-scripts playwright@$PLAYWRIGHT_VERSION @playwright/test@$PLAYWRIGHT_VERSION
- name: Cache Playwright Browsers
if: steps.changes.outputs.should_run == 'true'
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }}
restore-keys: |
playwright-${{ runner.os }}-
- name: Install Playwright (base)
if: steps.changes.outputs.should_run == 'true'
timeout-minutes: 3
run: pnpm exec playwright install chromium --only-shell
- name: Run benchmark (base)
if: steps.changes.outputs.should_run == 'true'
run: |
. venv_base/bin/activate
PERF_RESULTS_FILE=/tmp/bench_base.json pnpm exec playwright test \
--config .config/playwright.config.js \
js/spa/test/big_complex_demo.spec.ts
- name: Install and build PR
if: steps.changes.outputs.should_run == 'true'
run: |
export PATH="$HOME/.cargo/bin:$PATH"
uv venv --allow-existing venv --python=3.10
. venv/bin/activate
uv pip install -e client/python
uv pip install -e ".[oauth,mcp]"
pnpm install --no-frozen-lockfile
pnpm css && pnpm build
- name: Install Playwright (PR)
if: steps.changes.outputs.should_run == 'true'
timeout-minutes: 3
run: pnpm exec playwright install chromium --only-shell
- name: Run benchmark (PR)
if: steps.changes.outputs.should_run == 'true'
run: |
. venv/bin/activate
PERF_RESULTS_FILE=/tmp/bench_pr.json pnpm exec playwright test \
--config .config/playwright.config.js \
js/spa/test/big_complex_demo.spec.ts
- name: Compare results
id: compare
if: steps.changes.outputs.should_run == 'true'
env:
BASE_TAG: ${{ steps.latest_tag.outputs.tag }}
run: node scripts/compare_frontend_benchmarks.js
- name: Post or update PR comment
if: steps.compare.outputs.failed == 'true'
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const body = fs.readFileSync('/tmp/bench_comment.md', 'utf8');
const marker = '<!-- frontend-perf-benchmark -->';
const commentBody = marker + '\n' + body;
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
});
const existing = comments.find(c => c.body.includes(marker));
if (existing) {
await github.rest.issues.updateComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
body: commentBody,
});
} else {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: commentBody,
});
}
The same workflow, on Latchkey
Removes redundant runs and caps runaway jobs. Added and changed lines are highlighted.
name: "frontend-profiling" on: pull_request: types: [opened, synchronize, reopened] concurrency: group: "frontend-profiling-${{ github.event.pull_request.number }}" cancel-in-progress: true permissions: {} jobs: benchmark: timeout-minutes: 30 env: UV_EXCLUDE_NEWER: 7 days PLAYWRIGHT_VERSION: 1.60.0 permissions: contents: read pull-requests: write name: "frontend-benchmark" runs-on: latchkey-small steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: "gradio-app/gradio/.github/actions/changes@main" id: changes with: filter: "js" token: ${{ secrets.GITHUB_TOKEN }} - name: Find latest release tag if: steps.changes.outputs.should_run == 'true' id: latest_tag run: echo "tag=$(git tag --list 'gradio@*' --sort=-v:refname | head -n 1)" >> "$GITHUB_OUTPUT" - name: Setup Python if: steps.changes.outputs.should_run == 'true' uses: actions/setup-python@v5 with: python-version: "3.10" - name: Install uv if: steps.changes.outputs.should_run == 'true' run: curl -LsSf https://astral.sh/uv/0.11.3/install.sh | sh - name: Install pnpm if: steps.changes.outputs.should_run == 'true' uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # @v4 with: version: 10.17.0 - name: Setup Node.js if: steps.changes.outputs.should_run == 'true' uses: actions/setup-node@v4 with: node-version: 24 cache: pnpm cache-dependency-path: pnpm-lock.yaml # Install the benchmark "base" (latest release) straight from PyPI instead # of checking out the tag and rebuilding it - the published wheel already # ships the built frontend (gradio/templates), so this drops the ~4min base # `pnpm build` on every run. We stay on the PR checkout the whole job: the # benchmark spec + demo are the PR's versions for both runs, and the demo is # launched as `python <runner>.py` (the runner/demo dir is on sys.path, not # the cwd), so `import gradio` resolves to the installed package - the wheel # here, the editable PR build later. Base and PR use separate venvs so the # PR's editable install can't shadow the base wheel. - name: Install base gradio from the published release (base) if: steps.changes.outputs.should_run == 'true' run: | export PATH="$HOME/.cargo/bin:$PATH" uv venv --python=3.10 venv_base . venv_base/bin/activate TAG="${{ steps.latest_tag.outputs.tag }}" uv pip install "gradio==${TAG#gradio@}" - name: Install frontend test deps (base) if: steps.changes.outputs.should_run == 'true' # node_modules is only needed to run the Playwright benchmark (no build). run: | pnpm i --frozen-lockfile --ignore-scripts pnpm add -Dw --ignore-scripts playwright@$PLAYWRIGHT_VERSION @playwright/test@$PLAYWRIGHT_VERSION - name: Cache Playwright Browsers if: steps.changes.outputs.should_run == 'true' uses: actions/cache@v4 with: path: ~/.cache/ms-playwright key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} restore-keys: | playwright-${{ runner.os }}- - name: Install Playwright (base) if: steps.changes.outputs.should_run == 'true' timeout-minutes: 3 run: pnpm exec playwright install chromium --only-shell - name: Run benchmark (base) if: steps.changes.outputs.should_run == 'true' run: | . venv_base/bin/activate PERF_RESULTS_FILE=/tmp/bench_base.json pnpm exec playwright test \ --config .config/playwright.config.js \ js/spa/test/big_complex_demo.spec.ts - name: Install and build PR if: steps.changes.outputs.should_run == 'true' run: | export PATH="$HOME/.cargo/bin:$PATH" uv venv --allow-existing venv --python=3.10 . venv/bin/activate uv pip install -e client/python uv pip install -e ".[oauth,mcp]" pnpm install --no-frozen-lockfile pnpm css && pnpm build - name: Install Playwright (PR) if: steps.changes.outputs.should_run == 'true' timeout-minutes: 3 run: pnpm exec playwright install chromium --only-shell - name: Run benchmark (PR) if: steps.changes.outputs.should_run == 'true' run: | . venv/bin/activate PERF_RESULTS_FILE=/tmp/bench_pr.json pnpm exec playwright test \ --config .config/playwright.config.js \ js/spa/test/big_complex_demo.spec.ts - name: Compare results id: compare if: steps.changes.outputs.should_run == 'true' env: BASE_TAG: ${{ steps.latest_tag.outputs.tag }} run: node scripts/compare_frontend_benchmarks.js - name: Post or update PR comment if: steps.compare.outputs.failed == 'true' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const body = fs.readFileSync('/tmp/bench_comment.md', 'utf8'); const marker = '<!-- frontend-perf-benchmark -->'; const commentBody = marker + '\n' + body; const { data: comments } = await github.rest.issues.listComments({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, }); const existing = comments.find(c => c.body.includes(marker)); if (existing) { await github.rest.issues.updateComment({ owner: context.repo.owner, repo: context.repo.repo, comment_id: existing.id, body: commentBody, }); } else { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number, body: commentBody, }); }
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Add a job timeout so a hung step cannot burn hours of runner time.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
- End-to-end and browser tests
- Network fetches
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.