From 9042e8a5422d20e438f5d63fdc75302687a4a974 Mon Sep 17 00:00:00 2001 From: Charles Bochet Date: Thu, 4 Jun 2026 14:55:08 +0200 Subject: [PATCH] feat(ci): Argos main baselines + local visual diff support (#21217) ## Summary **CI: Main-branch Argos baselines** - Run storybook build + screenshot capture on `push` to `main` in CI UI workflow - Add `dispatch-main` job in visual regression dispatch to forward main-branch screenshots to ci-privileged - Simplify `dispatch-pr` by inlining the artifact name and removing unused `project` output **Local visual diff support** - Add `scripts/visual-diff.sh` for running Argos uploads locally via tunnel - Add `storybook:visual-diff` Nx target wrapping the script (depends on `storybook:build`) - Honor `STORYBOOK_URL` env in `vitest.config.ts` to reuse pre-served static builds (mirrors twenty-front pattern) - Support `ARGOS_BUILD_NAME`, `ARGOS_REFERENCE_BRANCH` env overrides in vitest plugin config ## Context Argos builds on PRs are all "Orphan" because there's no reference build on `main` to compare against. The CI changes add the missing piece: every merge to main now produces screenshots and uploads them to Argos as reference builds. The local visual diff script enables developers to run visual regression checks from their machine against the self-hosted Argos instance via `kubectl port-forward` (set up by the twenty-infra `argos-tunnel` command). ## Related - twentyhq/twenty-argos#1 (backend config for self-hosted HTTPS redirect) - twentyhq/twenty-infra#709 (argos-tunnel super CLI command + self-hosted mode) ## Test plan - [ ] Verify CI UI runs on next push to main and produces the `argos-screenshots-twenty-ui` artifact - [ ] Verify `dispatch-main` triggers and uploads screenshots to Argos - [ ] Verify subsequent PR builds show diffs against the main baseline instead of "Orphan" - [ ] Run `ARGOS_TOKEN= npx nx storybook:visual-diff twenty-ui` locally with tunnel active --- .github/workflows/ci-ui.yaml | 18 ++--- .../workflows/visual-regression-dispatch.yaml | 71 ++++++++++++------- packages/twenty-ui/project.json | 8 +++ packages/twenty-ui/scripts/visual-diff.sh | 38 ++++++++++ .../__stories__/ProgressBar.stories.tsx | 1 + packages/twenty-ui/vitest.config.ts | 5 +- 6 files changed, 106 insertions(+), 35 deletions(-) create mode 100755 packages/twenty-ui/scripts/visual-diff.sh diff --git a/.github/workflows/ci-ui.yaml b/.github/workflows/ci-ui.yaml index 61cc80291db..8d15e6faa5b 100644 --- a/.github/workflows/ci-ui.yaml +++ b/.github/workflows/ci-ui.yaml @@ -3,6 +3,8 @@ name: CI UI on: pull_request: merge_group: + push: + branches: [main] permissions: contents: read @@ -13,7 +15,7 @@ concurrency: jobs: changed-files-check: - if: github.event_name != 'merge_group' + if: github.event_name == 'pull_request' uses: ./.github/workflows/changed-files.yaml with: files: | @@ -40,7 +42,10 @@ jobs: run: npx nx ${{ matrix.task }} twenty-ui ui-sb-build: needs: changed-files-check - if: needs.changed-files-check.outputs.any_changed == 'true' + if: >- + always() && + (github.event_name == 'push' || + needs.changed-files-check.outputs.any_changed == 'true') timeout-minutes: 30 runs-on: ubuntu-latest steps: @@ -62,8 +67,6 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest needs: ui-sb-build - env: - STORYBOOK_URL: http://localhost:6007 steps: - name: Fetch custom Github Actions and base branch history uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 @@ -82,11 +85,8 @@ jobs: run: | cd packages/twenty-ui npx playwright install - - name: Serve storybook & run tests - run: | - npx http-server packages/twenty-ui/storybook-static --port 6007 --silent & - timeout 30 bash -c 'until curl -sf http://localhost:6007 > /dev/null 2>&1; do sleep 1; done' - npx nx storybook:test twenty-ui + - name: Run storybook tests + run: npx nx storybook:test twenty-ui - name: Upload screenshots for visual regression if: always() && !cancelled() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 diff --git a/.github/workflows/visual-regression-dispatch.yaml b/.github/workflows/visual-regression-dispatch.yaml index 45ed9500f32..4afb3f57882 100644 --- a/.github/workflows/visual-regression-dispatch.yaml +++ b/.github/workflows/visual-regression-dispatch.yaml @@ -14,40 +14,19 @@ permissions: pull-requests: read jobs: - dispatch: + dispatch-pr: if: >- github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest timeout-minutes: 5 steps: - - name: Determine project - id: project - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 - with: - script: | - const workflowName = context.payload.workflow_run.name; - const projects = { - 'CI UI': { project: 'twenty-ui', artifact: 'argos-screenshots-twenty-ui' }, - // Add more projects here when ready: - // 'CI Front': { project: 'twenty-front', artifact: 'argos-screenshots-twenty-front' }, - }; - - const config = projects[workflowName]; - if (!config) { - core.setFailed(`Unexpected workflow: ${workflowName}`); - return; - } - - core.setOutput('project', config.project); - core.setOutput('artifact_name', config.artifact); - - name: Check if screenshots artifact exists id: check-artifact uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | - const artifactName = '${{ steps.project.outputs.artifact_name }}'; + const artifactName = 'argos-screenshots-twenty-ui'; const runId = context.payload.workflow_run.id; const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ @@ -111,7 +90,6 @@ jobs: PR_NUMBER: ${{ steps.pr-info.outputs.pr_number }} WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} REPOSITORY: ${{ github.repository }} - PROJECT: ${{ steps.project.outputs.project }} BRANCH: ${{ github.event.workflow_run.head_branch }} COMMIT: ${{ github.event.workflow_run.head_sha }} run: | @@ -121,6 +99,49 @@ jobs: -f "client_payload[pr_number]=$PR_NUMBER" \ -f "client_payload[run_id]=$WORKFLOW_RUN_ID" \ -f "client_payload[repo]=$REPOSITORY" \ - -f "client_payload[project]=$PROJECT" \ + -f "client_payload[branch]=$BRANCH" \ + -f "client_payload[commit]=$COMMIT" + + dispatch-main: + if: >- + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' && + github.event.workflow_run.conclusion == 'success' + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Check if screenshots artifact exists + id: check-artifact + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + with: + script: | + const runId = context.payload.workflow_run.id; + const { data: artifacts } = await github.rest.actions.listWorkflowRunArtifacts({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: runId, + }); + + const found = artifacts.artifacts.some(a => a.name === 'argos-screenshots-twenty-ui'); + core.setOutput('exists', found ? 'true' : 'false'); + + if (!found) { + core.info(`Artifact not found in run ${runId} — skipping`); + } + + - name: Dispatch to ci-privileged + if: steps.check-artifact.outputs.exists == 'true' + env: + GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }} + WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} + REPOSITORY: ${{ github.repository }} + BRANCH: ${{ github.event.workflow_run.head_branch }} + COMMIT: ${{ github.event.workflow_run.head_sha }} + run: | + gh api repos/twentyhq/ci-privileged/dispatches \ + --method POST \ + -f event_type=visual-regression \ + -f "client_payload[run_id]=$WORKFLOW_RUN_ID" \ + -f "client_payload[repo]=$REPOSITORY" \ -f "client_payload[branch]=$BRANCH" \ -f "client_payload[commit]=$COMMIT" diff --git a/packages/twenty-ui/project.json b/packages/twenty-ui/project.json index 1775723c615..3ccdbd9a8df 100644 --- a/packages/twenty-ui/project.json +++ b/packages/twenty-ui/project.json @@ -84,6 +84,14 @@ }, "storybook:test": {}, "storybook:test:no-coverage": {}, + "storybook:visual-diff": { + "executor": "nx:run-commands", + "dependsOn": ["storybook:build"], + "options": { + "cwd": "{projectRoot}", + "command": "bash scripts/visual-diff.sh" + } + }, "storybook:coverage": {} } } diff --git a/packages/twenty-ui/scripts/visual-diff.sh b/packages/twenty-ui/scripts/visual-diff.sh new file mode 100755 index 00000000000..7fbac392b5b --- /dev/null +++ b/packages/twenty-ui/scripts/visual-diff.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +if [[ -f "$SCRIPT_DIR/.env" ]]; then + set -a + source "$SCRIPT_DIR/.env" + set +a +fi + +ARGOS_API_BASE_URL="${ARGOS_API_BASE_URL:-http://127.0.0.1:4002/v2/}" +ARGOS_TOKEN="${ARGOS_TOKEN:?ARGOS_TOKEN is required – set it in packages/twenty-ui/.env}" + +USERNAME=$(whoami) +BRANCH="${ARGOS_BRANCH:-$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")}" +COMMIT="${ARGOS_COMMIT:-$(git rev-parse HEAD 2>/dev/null || echo "unknown")}" + +export ARGOS_API_BASE_URL +export ARGOS_TOKEN +export ARGOS_BUILD_NAME="${USERNAME}/twenty-ui" +export ARGOS_BRANCH="$BRANCH" +export ARGOS_COMMIT="$COMMIT" + +echo "Argos visual diff" +echo " API: $ARGOS_API_BASE_URL" +echo " Build name: $ARGOS_BUILD_NAME" +echo " Branch: $ARGOS_BRANCH" +echo " Commit: ${ARGOS_COMMIT:0:12}" +echo "" + +npx http-server storybook-static --port 6007 --silent & +HTTP_PID=$! +trap "kill $HTTP_PID 2>/dev/null || true" EXIT + +for i in $(seq 1 30); do curl -sf http://localhost:6007 > /dev/null 2>&1 && break; sleep 1; done + +export STORYBOOK_URL="http://localhost:6007" +npx vitest run diff --git a/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx b/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx index 15ea9edf1ca..83b71be9653 100644 --- a/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx +++ b/packages/twenty-ui/src/feedback/progress-bar/components/__stories__/ProgressBar.stories.tsx @@ -24,6 +24,7 @@ export const Default: Story = { }; export const Animated: Story = { + tags: ['!test'], argTypes: { value: { control: false }, }, diff --git a/packages/twenty-ui/vitest.config.ts b/packages/twenty-ui/vitest.config.ts index 2c32f2f6daf..dc301bb7250 100644 --- a/packages/twenty-ui/vitest.config.ts +++ b/packages/twenty-ui/vitest.config.ts @@ -20,12 +20,15 @@ export default defineConfig({ plugins: [ storybookTest({ configDir: path.join(dirname, '.storybook'), - storybookScript: 'yarn storybook --no-open --port 6007', + ...(process.env.STORYBOOK_URL + ? { storybookUrl: process.env.STORYBOOK_URL } + : { storybookScript: 'yarn storybook --no-open --port 6007' }), }), argosVitestPlugin({ uploadToArgos: !!process.env.ARGOS_TOKEN, token: process.env.ARGOS_TOKEN, apiBaseUrl: process.env.ARGOS_API_BASE_URL, + buildName: process.env.ARGOS_BUILD_NAME || undefined, branch: process.env.ARGOS_BRANCH || undefined, commit: process.env.ARGOS_COMMIT || undefined, }),