Visual Regression Secure #3997
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Visual Regression Secure | |
| # Secure execution of continuous integration jobs | |
| # which are performed upon completion of the | |
| # "Continuous Integration" workflow | |
| # https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ | |
| on: | |
| workflow_run: | |
| workflows: [Visual Regression] | |
| types: [completed] | |
| env: | |
| PR_NUMBER: ${{ github.event.workflow_run.pull_requests[0] != null && github.event.workflow_run.pull_requests[0].number || '' }} | |
| VISUAL_REQUIRED: 'pr: visual review required' | |
| VISUAL_APPROVED: 'pr: visual review approved' | |
| IMAGE_REPO_VISUAL_REGRESSION: ghcr.io/sbb-design-systems/lyne-components/visual-regression | |
| jobs: | |
| visual-regression: | |
| runs-on: ubuntu-latest | |
| if: > | |
| github.event.workflow_run.event == 'pull_request' && | |
| github.event.workflow_run.conclusion == 'success' && | |
| github.event.workflow_run.pull_requests[0] != null | |
| permissions: | |
| checks: write | |
| deployments: write | |
| packages: write | |
| pull-requests: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: actions/setup-node@v6 | |
| with: | |
| node-version-file: .nvmrc | |
| cache: yarn | |
| - run: yarn install --frozen-lockfile --non-interactive | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: visual-regression-screenshots | |
| path: dist/screenshots/ | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GH_ACTIONS_ARTIFACT_DOWNLOAD }} | |
| - name: Build visual-regression-app | |
| run: yarn build visual-regression-app | |
| - name: Create check if changed | |
| uses: actions/github-script@v8 | |
| id: screenshot-check | |
| env: | |
| urlTemplate: https://lyne-components-visual-regression-diff-pr{}.app.sbb.ch | |
| with: | |
| script: | | |
| const { readdirSync, readFileSync } = await import('fs'); | |
| const diffUrl = process.env.urlTemplate.replace('{}', process.env.PR_NUMBER); | |
| const diffInfo = JSON.parse(readFileSync('dist/visual-regression-app/diff.json', 'utf8')); | |
| const createCheck = async (success) => { | |
| await github.rest.checks.create({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| name: 'Visual Regression Result', | |
| head_sha: context.payload.workflow_run.head_sha, | |
| details_url: diffUrl, | |
| output: { | |
| title: `Visual Regression ${success ? 'Success' : `Failed (${diffInfo.changedAmount + diffInfo.newAmount})`}`, | |
| summary: diffUrl, | |
| text: `Changes: ${diffInfo.changedAmount}\nNew: ${diffInfo.newAmount}`, | |
| }, | |
| ...(success ? { status: 'completed', conclusion: 'success' } : { status: 'in_progress' }), | |
| }); | |
| const environment = `pr${process.env.PR_NUMBER}-diff`; | |
| const payload = { owner: context.repo.owner, repo: context.repo.repo, environment }; | |
| const { data: deployment } = await github.rest.repos.createDeployment({ | |
| ...payload, | |
| ref: context.payload.workflow_run.head_sha, | |
| auto_merge: false, | |
| required_contexts: ['collect-test-results'] | |
| }); | |
| await github.rest.repos.createDeploymentStatus({ | |
| ...payload, | |
| deployment_id: deployment.id, | |
| state: success ? 'inactive' : 'in_progress', | |
| environment_url: diffUrl, | |
| }); | |
| }; | |
| // If we have no screenshots, we do not need to create a check. | |
| if (readdirSync('dist/screenshots').length <= 1) { | |
| await createCheck(true); | |
| return 'empty'; | |
| } | |
| let previousDiffInfo = {}; | |
| try { | |
| const response = await fetch(diffUrl + 'diff.json'); | |
| if (response.ok) { | |
| previousDiffInfo = await response.json(); | |
| } | |
| } catch {} | |
| await createCheck(false); | |
| // If the diff hash is the same as previously, we do not need to update the state or containers. | |
| return diffInfo.hash === previousDiffInfo.hash ? 'no-change' : 'changed'; | |
| result-encoding: string | |
| - name: Remove labels when no failed screenshots exist | |
| if: steps.screenshot-check.outputs.result == 'empty' | |
| run: gh issue edit $PR_NUMBER --remove-label "$VISUAL_REQUIRED" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| - uses: ./.github/actions/setup-mint | |
| - name: Create visual regression container | |
| run: | | |
| echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin | |
| docker build \ | |
| --file tools/visual-regression-testing/Dockerfile \ | |
| --tag ${{ env.IMAGE_REPO_VISUAL_REGRESSION }}:pr$PR_NUMBER-fat \ | |
| . | |
| mint slim \ | |
| --target ${{ env.IMAGE_REPO_VISUAL_REGRESSION }}:pr$PR_NUMBER-fat \ | |
| --tag ${{ env.IMAGE_REPO_VISUAL_REGRESSION }}:pr$PR_NUMBER \ | |
| --preserve-path /usr/share/nginx/html \ | |
| --include-new false | |
| docker push ${{ env.IMAGE_REPO_VISUAL_REGRESSION }}:pr$PR_NUMBER | |
| docker image list | |
| env: | |
| DOCKER_BUILDKIT: 1 | |
| - name: Apply labels | |
| if: steps.screenshot-check.outputs.result == 'changed' || steps.screenshot-check.outputs.result == 'empty' | |
| run: | | |
| gh issue edit $PR_NUMBER --remove-label "$VISUAL_APPROVED" | |
| gh issue edit $PR_NUMBER --add-label "$VISUAL_REQUIRED" | |
| gh issue edit $PR_NUMBER --add-label "diff-available" | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |