feat(ci): integrate Argos visual regression via vitest screenshots (#21210)
## Summary
- Adds `@argos-ci/storybook` vitest plugin to `twenty-ui` for automatic
screenshot capture during vitest storybook tests
- Uploads captured screenshots (PNG, ~5MB) as a CI artifact instead of
passing the full storybook build
- Updates the visual regression dispatch workflow to pass
`mode=argos-screenshots` to ci-privileged, which then uploads
screenshots to Argos via CLI
This replaces the 10-minute Storybook screenshot capture with a ~30s
vitest browser-mode approach. The heavy screenshot work happens on free
public runners, while ci-privileged only handles the Argos API upload
(keeping secrets private).
## Architecture
```
twenty (public, free runners) ci-privileged (private)
───────────────────────────── ────────────────────────
1. Build storybook-static 4. Download screenshots artifact
2. Vitest captures screenshots 5. `argos upload` → Argos API
3. Upload screenshots artifact 6. Poll for results
7. Post PR comment
```
## Test plan
- [x] Verified locally: vitest captures 225 screenshots in ~28s
- [x] Verified `@argos-ci/cli upload` successfully creates Argos build
from captured screenshots
- [x] Argos diffs computed and results visible via API
- [ ] CI runs end-to-end on a PR
This commit is contained in:
@@ -87,17 +87,18 @@ jobs:
|
||||
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: Upload screenshots for visual regression
|
||||
if: always() && !cancelled()
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: argos-screenshots-twenty-ui
|
||||
path: packages/twenty-ui/screenshots
|
||||
retention-days: 1
|
||||
ci-ui-status-check:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs:
|
||||
[
|
||||
changed-files-check,
|
||||
ui-task,
|
||||
ui-sb-build,
|
||||
ui-sb-test,
|
||||
]
|
||||
needs: [changed-files-check, ui-task, ui-sb-build, ui-sb-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
|
||||
@@ -32,16 +32,18 @@ jobs:
|
||||
core.setOutput('artifact_name', 'storybook-static');
|
||||
core.setOutput('tarball_name', 'storybook-twenty-front-tarball');
|
||||
core.setOutput('tarball_file', 'storybook-twenty-front.tar.gz');
|
||||
core.setOutput('mode', 'storybook-tarball');
|
||||
} else if (workflowName === 'CI UI') {
|
||||
core.setOutput('project', 'twenty-ui');
|
||||
core.setOutput('artifact_name', 'storybook-twenty-ui');
|
||||
core.setOutput('tarball_name', 'storybook-twenty-ui-tarball');
|
||||
core.setOutput('tarball_file', 'storybook-twenty-ui.tar.gz');
|
||||
core.setOutput('artifact_name', 'argos-screenshots-twenty-ui');
|
||||
core.setOutput('tarball_name', 'argos-screenshots-twenty-ui-tarball');
|
||||
core.setOutput('tarball_file', 'argos-screenshots-twenty-ui.tar.gz');
|
||||
core.setOutput('mode', 'argos-screenshots');
|
||||
} else {
|
||||
core.setFailed(`Unexpected workflow: ${workflowName}`);
|
||||
}
|
||||
|
||||
- name: Check if storybook artifact exists
|
||||
- name: Check if artifact exists
|
||||
id: check-artifact
|
||||
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
|
||||
with:
|
||||
@@ -59,7 +61,7 @@ jobs:
|
||||
core.setOutput('exists', found ? 'true' : 'false');
|
||||
|
||||
if (!found) {
|
||||
core.info(`Artifact "${artifactName}" not found in run ${runId} — storybook build was likely skipped`);
|
||||
core.info(`Artifact "${artifactName}" not found in run ${runId} — build was likely skipped`);
|
||||
}
|
||||
|
||||
- name: Get PR number
|
||||
@@ -105,20 +107,20 @@ jobs:
|
||||
core.setOutput('has_pr', 'true');
|
||||
core.info(`PR #${prNumber}`);
|
||||
|
||||
- name: Download storybook artifact from triggering run
|
||||
- name: Download artifact from triggering run
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: ${{ steps.project.outputs.artifact_name }}
|
||||
path: storybook-static
|
||||
path: artifact-content
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
github-token: ${{ github.token }}
|
||||
|
||||
- name: Package storybook
|
||||
- name: Package artifact as tarball
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
run: tar -czf /tmp/${{ steps.project.outputs.tarball_file }} -C storybook-static .
|
||||
run: tar -czf /tmp/${{ steps.project.outputs.tarball_file }} -C artifact-content .
|
||||
|
||||
- name: Upload storybook tarball
|
||||
- name: Upload tarball
|
||||
if: steps.check-artifact.outputs.exists == 'true' && steps.pr-info.outputs.has_pr == 'true'
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
@@ -136,6 +138,7 @@ jobs:
|
||||
PROJECT: ${{ steps.project.outputs.project }}
|
||||
BRANCH: ${{ github.event.workflow_run.head_branch }}
|
||||
COMMIT: ${{ github.event.workflow_run.head_sha }}
|
||||
MODE: ${{ steps.project.outputs.mode }}
|
||||
run: |
|
||||
gh api repos/twentyhq/ci-privileged/dispatches \
|
||||
-f event_type=visual-regression \
|
||||
@@ -144,4 +147,5 @@ jobs:
|
||||
-f "client_payload[repo]=$REPOSITORY" \
|
||||
-f "client_payload[project]=$PROJECT" \
|
||||
-f "client_payload[branch]=$BRANCH" \
|
||||
-f "client_payload[commit]=$COMMIT"
|
||||
-f "client_payload[commit]=$COMMIT" \
|
||||
-f "client_payload[mode]=$MODE"
|
||||
|
||||
@@ -56,3 +56,4 @@ TRANSLATION_QA_REPORT.md
|
||||
.playwright-mcp/
|
||||
.playwright-cli/
|
||||
output/playwright/
|
||||
screenshots/
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"**/*.css"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@argos-ci/storybook": "^6.0.6",
|
||||
"@babel/core": "^7.14.5",
|
||||
"@babel/preset-env": "^7.26.9",
|
||||
"@babel/preset-react": "^7.26.3",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { argosVitestPlugin } from '@argos-ci/storybook/vitest-plugin';
|
||||
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
|
||||
import { playwright } from '@vitest/browser-playwright';
|
||||
import path from 'node:path';
|
||||
@@ -21,6 +22,13 @@ export default defineConfig({
|
||||
configDir: path.join(dirname, '.storybook'),
|
||||
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,
|
||||
branch: process.env.ARGOS_BRANCH || undefined,
|
||||
commit: process.env.ARGOS_COMMIT || undefined,
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
name: 'storybook',
|
||||
|
||||
@@ -526,6 +526,71 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@argos-ci/api-client@npm:0.22.0":
|
||||
version: 0.22.0
|
||||
resolution: "@argos-ci/api-client@npm:0.22.0"
|
||||
dependencies:
|
||||
debug: "npm:^4.4.3"
|
||||
openapi-fetch: "npm:^0.17.0"
|
||||
p-retry: "npm:^8.0.0"
|
||||
checksum: 10c0/10d61796a7f5c7ef7be3f781522d80f9f7eeef006ea691d64b0139d2337be7ada7ac452e46cdfb148fdbb0f29dc6714905fa9988de3e1ae5334f18594c13f222
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@argos-ci/browser@npm:6.1.0":
|
||||
version: 6.1.0
|
||||
resolution: "@argos-ci/browser@npm:6.1.0"
|
||||
checksum: 10c0/3b5e59fc1652a86bc22aeed3ef36f19d3c769b6cb8a9f1ac0a97205e3ab90c13ddce6167d483bf43a7786ad23aa4635d4a53654935d614b6de4d43c0d6d31fb2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@argos-ci/core@npm:6.1.0":
|
||||
version: 6.1.0
|
||||
resolution: "@argos-ci/core@npm:6.1.0"
|
||||
dependencies:
|
||||
"@argos-ci/api-client": "npm:0.22.0"
|
||||
"@argos-ci/util": "npm:4.0.0"
|
||||
convict: "npm:^6.2.5"
|
||||
debug: "npm:^4.4.3"
|
||||
fast-glob: "npm:^3.3.3"
|
||||
mime-types: "npm:^3.0.2"
|
||||
sharp: "npm:^0.34.5"
|
||||
tmp: "npm:^0.2.5"
|
||||
checksum: 10c0/47d688d1547d3672101c8f02e70736d1858db353852ab94389620e3c8f03a026d903357e2da97e0604a504ae7af229733879b006228d167ac904e8df0866f53a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@argos-ci/playwright@npm:7.0.5":
|
||||
version: 7.0.5
|
||||
resolution: "@argos-ci/playwright@npm:7.0.5"
|
||||
dependencies:
|
||||
"@argos-ci/browser": "npm:6.1.0"
|
||||
"@argos-ci/core": "npm:6.1.0"
|
||||
"@argos-ci/util": "npm:4.0.0"
|
||||
chalk: "npm:^5.6.2"
|
||||
debug: "npm:^4.4.3"
|
||||
checksum: 10c0/7687306587aacd8654518d1f942816e34d405be9d07aab48fa86a4209dfd3c245f67bdef99436ccc58e18ac379bc5059f2fcab9624d3de87fc9e4c81073c13e0
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@argos-ci/storybook@npm:^6.0.6":
|
||||
version: 6.0.6
|
||||
resolution: "@argos-ci/storybook@npm:6.0.6"
|
||||
dependencies:
|
||||
"@argos-ci/core": "npm:6.1.0"
|
||||
"@argos-ci/playwright": "npm:7.0.5"
|
||||
"@argos-ci/util": "npm:4.0.0"
|
||||
checksum: 10c0/37909b5323dc81a5c167e761f31912ad48cbb513732d71189b19c941e02360ecdf08144300a9b89900b93ef379966da032f79a2747ae66ac801d92804eec016c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@argos-ci/util@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "@argos-ci/util@npm:4.0.0"
|
||||
checksum: 10c0/be54a0888dd1d5554bd8830da3f4647ac5596cab691605d3508d1723c2b9522375c9f1c462b145ac857d3cacf8f8a8fdcbf0d263d6cc7f6ef798311c46acc968
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@ark-ui/react@npm:^5.31.0":
|
||||
version: 5.31.0
|
||||
resolution: "@ark-ui/react@npm:5.31.0"
|
||||
@@ -32155,6 +32220,16 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"convict@npm:^6.2.5":
|
||||
version: 6.2.5
|
||||
resolution: "convict@npm:6.2.5"
|
||||
dependencies:
|
||||
lodash.clonedeep: "npm:^4.5.0"
|
||||
yargs-parser: "npm:^20.2.7"
|
||||
checksum: 10c0/4773180a3d02eb9d5cf6a3b6ec2b6bc94e6b8dd5073cbf6035cb55f615286835af1fe7f549aee085909a1130f6b461476f9b6a0d1fdbdee733402289e5196c1e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cookie-signature@npm:1.0.7, cookie-signature@npm:~1.0.6":
|
||||
version: 1.0.7
|
||||
resolution: "cookie-signature@npm:1.0.7"
|
||||
@@ -36275,7 +36350,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"fast-glob@npm:^3.2.7":
|
||||
"fast-glob@npm:^3.2.7, fast-glob@npm:^3.3.3":
|
||||
version: 3.3.3
|
||||
resolution: "fast-glob@npm:3.3.3"
|
||||
dependencies:
|
||||
@@ -40709,6 +40784,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-network-error@npm:^1.3.0":
|
||||
version: 1.3.2
|
||||
resolution: "is-network-error@npm:1.3.2"
|
||||
checksum: 10c0/37edc576497b21d022754b49203358ee80fdac4a11a71a09687f38ad789ec437dd930c223301df39f1c920566692b31345e0108ae720996e592cab3879eca74f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-node-process@npm:^1.0.1, is-node-process@npm:^1.2.0":
|
||||
version: 1.2.0
|
||||
resolution: "is-node-process@npm:1.2.0"
|
||||
@@ -45429,6 +45511,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime-types@npm:^3.0.2":
|
||||
version: 3.0.2
|
||||
resolution: "mime-types@npm:3.0.2"
|
||||
dependencies:
|
||||
mime-db: "npm:^1.54.0"
|
||||
checksum: 10c0/35a0dd1035d14d185664f346efcdb72e93ef7a9b6e9ae808bd1f6358227010267fab52657b37562c80fc888ff76becb2b2938deb5e730818b7983bf8bd359767
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mime@npm:1.6.0, mime@npm:^1.6.0":
|
||||
version: 1.6.0
|
||||
resolution: "mime@npm:1.6.0"
|
||||
@@ -47572,6 +47663,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openapi-fetch@npm:^0.17.0":
|
||||
version: 0.17.0
|
||||
resolution: "openapi-fetch@npm:0.17.0"
|
||||
dependencies:
|
||||
openapi-typescript-helpers: "npm:^0.1.0"
|
||||
checksum: 10c0/7e981d28c7839469662a73d44f3a85e05c1a5842a8afd5d6e36c75cbf085aa7f705862871cc7ce07c04605ddc936af3d5850c2cc66d193ee1fea07e7135da192
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openapi-fetch@npm:^0.9.7":
|
||||
version: 0.9.8
|
||||
resolution: "openapi-fetch@npm:0.9.8"
|
||||
@@ -47595,6 +47695,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openapi-typescript-helpers@npm:^0.1.0":
|
||||
version: 0.1.0
|
||||
resolution: "openapi-typescript-helpers@npm:0.1.0"
|
||||
checksum: 10c0/3a148cf7d6a7f51124966a0fef64c2fbcf1a5a0a1e6320dd627f0733787ac4259877be43f7a7a2910684e3d5d238c6a9655c6f9e1f3adaa9a2a61477e3b8481f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"openapi3-ts@npm:^3.0.0":
|
||||
version: 3.2.0
|
||||
resolution: "openapi3-ts@npm:3.2.0"
|
||||
@@ -48102,6 +48209,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-retry@npm:^8.0.0":
|
||||
version: 8.0.0
|
||||
resolution: "p-retry@npm:8.0.0"
|
||||
dependencies:
|
||||
is-network-error: "npm:^1.3.0"
|
||||
checksum: 10c0/81a788f35888c3cdb36f97286577c8ba57ccd8e9347db3c6ded79b3e12e2838bcda733753e0c0bdaac77d4620ddfc7e230d3c9a4bcf2fda3a12ea57bcf9443d1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"p-some@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "p-some@npm:6.0.0"
|
||||
@@ -56159,6 +56275,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tmp@npm:^0.2.5":
|
||||
version: 0.2.7
|
||||
resolution: "tmp@npm:0.2.7"
|
||||
checksum: 10c0/59eb55584f2f07210d3231b6a1f6b5c2b9794d8a7b509c8ee867ed2acad6d2245ee2448b7937b676ffbff3155a70077edde8a69f9d7cf0f90c86a62e8910c357
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"tmpl@npm:1.0.5":
|
||||
version: 1.0.5
|
||||
resolution: "tmpl@npm:1.0.5"
|
||||
@@ -57361,6 +57484,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "twenty-ui@workspace:packages/twenty-ui"
|
||||
dependencies:
|
||||
"@argos-ci/storybook": "npm:^6.0.6"
|
||||
"@babel/core": "npm:^7.14.5"
|
||||
"@babel/preset-env": "npm:^7.26.9"
|
||||
"@babel/preset-react": "npm:^7.26.3"
|
||||
@@ -60728,7 +60852,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yargs-parser@npm:^20.2.2":
|
||||
"yargs-parser@npm:^20.2.2, yargs-parser@npm:^20.2.7":
|
||||
version: 20.2.9
|
||||
resolution: "yargs-parser@npm:20.2.9"
|
||||
checksum: 10c0/0685a8e58bbfb57fab6aefe03c6da904a59769bd803a722bb098bd5b0f29d274a1357762c7258fb487512811b8063fb5d2824a3415a0a4540598335b3b086c72
|
||||
|
||||
Reference in New Issue
Block a user