diff --git a/.github/workflows/ci-front.yaml b/.github/workflows/ci-front.yaml index a262f09aeb..5e4e27efca 100644 --- a/.github/workflows/ci-front.yaml +++ b/.github/workflows/ci-front.yaml @@ -3,12 +3,14 @@ name: CI Front on: pull_request: merge_group: + push: + branches: [main] permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}${{ github.ref == 'refs/heads/main' && format('-{0}', github.sha) || '' }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} env: @@ -18,7 +20,7 @@ env: jobs: changed-files-check: - if: github.event_name != 'merge_group' + if: github.event_name == 'pull_request' uses: ./.github/workflows/changed-files.yaml with: files: | @@ -33,7 +35,10 @@ jobs: !packages/twenty-sdk/package.json front-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-8-cores env: @@ -65,11 +70,12 @@ jobs: timeout-minutes: 30 runs-on: ubuntu-latest needs: front-sb-build + if: always() && needs.front-sb-build.result == 'success' strategy: fail-fast: false matrix: shard: [1, 2, 3, 4] - storybook_scope: [modules, pages, performance] + storybook_scope: ${{ github.event_name == 'push' && fromJSON('["modules", "pages"]') || fromJSON('["modules", "pages", "performance"]') }} env: SHARD_COUNTER: 4 REACT_APP_SERVER_BASE_URL: http://localhost:3000 @@ -119,6 +125,14 @@ jobs: npx http-server packages/twenty-front/storybook-static --port 6006 --silent & timeout 30 bash -c 'until curl -sf http://localhost:6006 > /dev/null 2>&1; do sleep 1; done' npx nx storybook:test twenty-front --configuration=${{ matrix.storybook_scope }} --shard=${{ matrix.shard }}/${{ env.SHARD_COUNTER }} + - name: Upload screenshots for visual regression + if: always() && !cancelled() && matrix.storybook_scope != 'performance' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: argos-screenshots-twenty-front-${{ matrix.storybook_scope }}-${{ matrix.shard }} + path: packages/twenty-front/screenshots + retention-days: 1 + overwrite: true # - name: Rename coverage file # run: | # if [ -f "packages/twenty-front/coverage/storybook/coverage-final.json" ]; then @@ -160,6 +174,25 @@ jobs: # npx nyc merge coverage-artifacts ${{ env.PATH_TO_COVERAGE }}/coverage-storybook.json # - name: Checking coverage # run: npx nx storybook:coverage twenty-front --checkCoverage=true --configuration=${{ matrix.storybook_scope }} + front-sb-screenshots: + timeout-minutes: 5 + runs-on: ubuntu-latest + needs: front-sb-test + if: always() && needs.front-sb-test.result == 'success' + steps: + - name: Download shard screenshot artifacts + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + with: + pattern: argos-screenshots-twenty-front-* + merge-multiple: true + path: screenshots + - name: Upload merged screenshots artifact + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: argos-screenshots-twenty-front + path: screenshots + retention-days: 1 + overwrite: true front-task: needs: changed-files-check if: needs.changed-files-check.outputs.any_changed == 'true' @@ -235,6 +268,7 @@ jobs: # merge-reports-and-check-coverage, front-sb-test, front-sb-build, + front-sb-screenshots, ] steps: - name: Fail job if any needs failed diff --git a/.github/workflows/ci-new-ui.yaml b/.github/workflows/ci-new-ui.yaml index c294bf6dc1..7717d1312f 100644 --- a/.github/workflows/ci-new-ui.yaml +++ b/.github/workflows/ci-new-ui.yaml @@ -10,7 +10,7 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}${{ github.ref == 'refs/heads/main' && format('-{0}', github.sha) || '' }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: diff --git a/.github/workflows/ci-ui.yaml b/.github/workflows/ci-ui.yaml index 08b0d8c0f3..566c1214ca 100644 --- a/.github/workflows/ci-ui.yaml +++ b/.github/workflows/ci-ui.yaml @@ -10,7 +10,7 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.ref }} + group: ${{ github.workflow }}-${{ github.ref }}${{ github.ref == 'refs/heads/main' && format('-{0}', github.sha) || '' }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} jobs: diff --git a/.github/workflows/visual-regression-dispatch.yaml b/.github/workflows/visual-regression-dispatch.yaml index 6447f03102..b83ba12d17 100644 --- a/.github/workflows/visual-regression-dispatch.yaml +++ b/.github/workflows/visual-regression-dispatch.yaml @@ -9,7 +9,7 @@ name: Visual Regression Dispatch on: workflow_run: - workflows: ['CI UI', 'CI New UI'] + workflows: ['CI UI', 'CI New UI', 'CI Front'] types: [completed] permissions: @@ -24,6 +24,7 @@ jobs: timeout-minutes: 5 outputs: workflow_name: ${{ steps.context.outputs.workflow_name }} + project: ${{ steps.context.outputs.project }} artifact_name: ${{ steps.context.outputs.artifact_name }} has_artifact: ${{ steps.check-artifact.outputs.exists }} is_pr: ${{ steps.pr-info.outputs.has_pr }} @@ -36,12 +37,17 @@ jobs: with: script: | const name = context.payload.workflow_run.name; - const artifactName = name === 'CI New UI' - ? 'argos-screenshots-twenty-new-ui' - : 'argos-screenshots-twenty-ui'; + const projectByWorkflow = { + 'CI UI': 'twenty-ui', + 'CI New UI': 'twenty-new-ui', + 'CI Front': 'twenty-front', + }; + const project = projectByWorkflow[name] ?? 'twenty-ui'; + const artifactName = `argos-screenshots-${project}`; core.setOutput('workflow_name', name); + core.setOutput('project', project); core.setOutput('artifact_name', artifactName); - core.info(`Workflow: ${name}, artifact: ${artifactName}`); + core.info(`Workflow: ${name}, project: ${project}, artifact: ${artifactName}`); - name: Check if screenshots artifact exists id: check-artifact @@ -134,18 +140,17 @@ jobs: core.setOutput('sha', ''); } - # ── Dispatch: twenty-ui pixel diff (CI UI, PRs + main) ── - dispatch-twenty-ui: + # ── Dispatch: pixel diff for the triggering workflow's project (PRs + main) ── + dispatch-pixel-diff: needs: resolve-context - if: >- - needs.resolve-context.outputs.workflow_name == 'CI UI' && - needs.resolve-context.outputs.has_artifact == 'true' + if: needs.resolve-context.outputs.has_artifact == 'true' runs-on: ubuntu-latest timeout-minutes: 5 steps: - name: Dispatch to ci-privileged env: GH_TOKEN: ${{ secrets.CI_PRIVILEGED_DISPATCH_TOKEN }} + PROJECT: ${{ needs.resolve-context.outputs.project }} PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }} WORKFLOW_RUN_ID: ${{ github.event.workflow_run.id }} REPOSITORY: ${{ github.repository }} @@ -157,47 +162,7 @@ jobs: ARGS=( --method POST -f event_type=visual-regression - -f "client_payload[project]=twenty-ui" - -f "client_payload[artifact_name]=$ARTIFACT_NAME" - -f "client_payload[run_id]=$WORKFLOW_RUN_ID" - -f "client_payload[repo]=$REPOSITORY" - -f "client_payload[branch]=$BRANCH" - -f "client_payload[commit]=$COMMIT" - ) - - if [ -n "$PR_NUMBER" ]; then - ARGS+=(-f "client_payload[pr_number]=$PR_NUMBER") - fi - if [ -n "$REFERENCE_COMMIT" ]; then - ARGS+=(-f "client_payload[reference_commit]=$REFERENCE_COMMIT") - fi - - gh api repos/twentyhq/ci-privileged/dispatches "${ARGS[@]}" - - # ── Dispatch: twenty-new-ui pixel diff (CI New UI, PRs + main) ── - dispatch-twenty-new-ui: - needs: resolve-context - if: >- - needs.resolve-context.outputs.workflow_name == 'CI New UI' && - needs.resolve-context.outputs.has_artifact == 'true' - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Dispatch to ci-privileged - 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 }} - PR_NUMBER: ${{ needs.resolve-context.outputs.pr_number }} - REFERENCE_COMMIT: ${{ needs.resolve-context.outputs.merge_base_sha }} - ARTIFACT_NAME: ${{ needs.resolve-context.outputs.artifact_name }} - run: | - ARGS=( - --method POST - -f event_type=visual-regression - -f "client_payload[project]=twenty-new-ui" + -f "client_payload[project]=$PROJECT" -f "client_payload[artifact_name]=$ARTIFACT_NAME" -f "client_payload[run_id]=$WORKFLOW_RUN_ID" -f "client_payload[repo]=$REPOSITORY" diff --git a/packages/twenty-front/.storybook/vitest.setup.ts b/packages/twenty-front/.storybook/vitest.setup.ts index 0dbcad938e..c3d0fa7c70 100644 --- a/packages/twenty-front/.storybook/vitest.setup.ts +++ b/packages/twenty-front/.storybook/vitest.setup.ts @@ -1,6 +1,9 @@ import { setProjectAnnotations } from '@storybook/react-vite'; +import { MotionGlobalConfig } from 'framer-motion'; import * as projectAnnotations from './preview'; +MotionGlobalConfig.skipAnimations = true; + // Pre-warm the dynamic import used by WorkflowStepDecorator so the // module is cached before any test runs (avoids flaky timeouts in CI). import('~/testing/utils/getTestEnrichedObjectMetadataItemsMock'); diff --git a/packages/twenty-front/package.json b/packages/twenty-front/package.json index 83cabbfb70..5579a066fd 100644 --- a/packages/twenty-front/package.json +++ b/packages/twenty-front/package.json @@ -148,6 +148,7 @@ "zod": "^4.1.11" }, "devDependencies": { + "@argos-ci/storybook": "^6.0.6", "@babel/core": "^7.14.5", "@babel/preset-typescript": "^7.24.6", "@graphql-codegen/cli": "^6.3.1", diff --git a/packages/twenty-front/src/modules/ai/components/__stories__/CodeExecutionDisplay.stories.tsx b/packages/twenty-front/src/modules/ai/components/__stories__/CodeExecutionDisplay.stories.tsx index 781ee5b19d..96367b775a 100644 --- a/packages/twenty-front/src/modules/ai/components/__stories__/CodeExecutionDisplay.stories.tsx +++ b/packages/twenty-front/src/modules/ai/components/__stories__/CodeExecutionDisplay.stories.tsx @@ -3,6 +3,7 @@ import { expect, userEvent, within } from 'storybook/test'; import { ComponentDecorator } from 'twenty-ui-deprecated/testing'; import { CodeExecutionDisplay } from '@/ai/components/CodeExecutionDisplay'; +import { ARGOS_CAPTURE_DISABLED } from '~/testing/constants/ArgosCaptureDisabled'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; const samplePythonCode = `import pandas as pd @@ -32,6 +33,7 @@ const meta: Meta = { component: CodeExecutionDisplay, decorators: [SnackBarDecorator, ComponentDecorator], parameters: { + argos: ARGOS_CAPTURE_DISABLED, container: { width: 600 }, }, args: { diff --git a/packages/twenty-front/src/modules/workflow/components/__stories__/WorkflowStepExecutionResult.stories.tsx b/packages/twenty-front/src/modules/workflow/components/__stories__/WorkflowStepExecutionResult.stories.tsx index e812c8ad84..fe704c7242 100644 --- a/packages/twenty-front/src/modules/workflow/components/__stories__/WorkflowStepExecutionResult.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/components/__stories__/WorkflowStepExecutionResult.stories.tsx @@ -1,6 +1,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { ComponentDecorator } from 'twenty-ui-deprecated/testing'; +import { ARGOS_CAPTURE_DISABLED } from '~/testing/constants/ArgosCaptureDisabled'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { WorkflowStepExecutionResult } from '@/workflow/components/WorkflowStepExecutionResult'; @@ -9,6 +10,9 @@ const meta: Meta = { title: 'Modules/Workflow/Components/StepExecutionResult', component: WorkflowStepExecutionResult, decorators: [ComponentDecorator, SnackBarDecorator], + parameters: { + argos: ARGOS_CAPTURE_DISABLED, + }, args: { result: JSON.stringify( { message: 'Hello World', status: 'success' }, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/__stories__/WorkflowEditActionCode.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/__stories__/WorkflowEditActionCode.stories.tsx index 86d3e4e13b..49a9ef2051 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/__stories__/WorkflowEditActionCode.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/code-action/components/__stories__/WorkflowEditActionCode.stories.tsx @@ -7,6 +7,7 @@ import { ComponentDecorator, RouterDecorator, } from 'twenty-ui-deprecated/testing'; +import { ARGOS_CAPTURE_DISABLED } from '~/testing/constants/ArgosCaptureDisabled'; import { ObjectMetadataItemsDecorator } from '~/testing/decorators/ObjectMetadataItemsDecorator'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { WorkflowStepActionDrawerDecorator } from '~/testing/decorators/WorkflowStepActionDrawerDecorator'; @@ -80,6 +81,7 @@ const meta: Meta = { title: 'Modules/Workflow/Actions/Code/EditAction', component: WorkflowEditActionCode, parameters: { + argos: ARGOS_CAPTURE_DISABLED, msw: { handlers: [ ...graphqlMocks.handlers, diff --git a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx index d29806d4e0..49d31f12ff 100644 --- a/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx +++ b/packages/twenty-front/src/modules/workflow/workflow-steps/workflow-actions/http-request-action/components/__stories__/HttpRequestExecutionResult.stories.tsx @@ -2,6 +2,7 @@ import { type Meta, type StoryObj } from '@storybook/react-vite'; import { expect, within } from 'storybook/test'; import { ComponentDecorator } from 'twenty-ui-deprecated/testing'; +import { ARGOS_CAPTURE_DISABLED } from '~/testing/constants/ArgosCaptureDisabled'; import { SnackBarDecorator } from '~/testing/decorators/SnackBarDecorator'; import { HttpRequestExecutionResult } from '@/workflow/workflow-steps/workflow-actions/http-request-action/components/HttpRequestExecutionResult'; @@ -12,6 +13,7 @@ const meta: Meta = { component: HttpRequestExecutionResult, decorators: [ComponentDecorator, SnackBarDecorator], parameters: { + argos: ARGOS_CAPTURE_DISABLED, layout: 'fullscreen', }, }; diff --git a/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx b/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx index d499c90f4f..42931f70ed 100644 --- a/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx +++ b/packages/twenty-front/src/pages/settings/logic-functions/__stories__/SettingsLogicFunctionDetail.stories.tsx @@ -4,6 +4,7 @@ import { within } from 'storybook/test'; import { getImageAbsoluteURI } from 'twenty-shared/utils'; import { REACT_APP_SERVER_BASE_URL } from '~/config'; import { SettingsLogicFunctionDetail } from '~/pages/settings/logic-functions/SettingsLogicFunctionDetail'; +import { ARGOS_CAPTURE_DISABLED } from '~/testing/constants/ArgosCaptureDisabled'; import { PageDecorator, type PageDecoratorArgs, @@ -29,6 +30,7 @@ const meta: Meta = { }, }, parameters: { + argos: ARGOS_CAPTURE_DISABLED, msw: { handlers: [ ...graphqlMocks.handlers, diff --git a/packages/twenty-front/src/testing/constants/ArgosCaptureDisabled.ts b/packages/twenty-front/src/testing/constants/ArgosCaptureDisabled.ts new file mode 100644 index 0000000000..a2ffaf4584 --- /dev/null +++ b/packages/twenty-front/src/testing/constants/ArgosCaptureDisabled.ts @@ -0,0 +1,5 @@ +export const ARGOS_CAPTURE_DISABLED = { + modes: { + default: { disabled: true }, + }, +}; diff --git a/packages/twenty-front/vitest.config.ts b/packages/twenty-front/vitest.config.ts index 779dfc1c90..81497d5bde 100644 --- a/packages/twenty-front/vitest.config.ts +++ b/packages/twenty-front/vitest.config.ts @@ -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'; @@ -6,6 +7,10 @@ import { defineConfig } from 'vitest/config'; const MINUTES_IN_MS = 60 * 1000; +const shouldCaptureArgosScreenshots = ['modules', 'pages'].includes( + process.env.STORYBOOK_SCOPE ?? '', +); + const dirname = typeof __dirname !== 'undefined' ? __dirname @@ -28,6 +33,20 @@ export default defineConfig({ ? { storybookUrl: process.env.STORYBOOK_URL } : { storybookScript: 'yarn storybook --no-open' }), }), + ...(shouldCaptureArgosScreenshots + ? [ + 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, + referenceCommit: + process.env.ARGOS_REFERENCE_COMMIT || undefined, + }), + ] + : []), ], test: { name: 'storybook', diff --git a/yarn.lock b/yarn.lock index 9fe9f2d02b..b02c1277d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -54822,6 +54822,7 @@ __metadata: resolution: "twenty-front@workspace:packages/twenty-front" dependencies: "@apollo/client": "npm:^4.0.0" + "@argos-ci/storybook": "npm:^6.0.6" "@babel/core": "npm:^7.14.5" "@babel/preset-typescript": "npm:^7.24.6" "@blocknote/mantine": "npm:^0.51.4"