From 8f5100f6145cef8a7dabbaca7a45088e1d7f776b Mon Sep 17 00:00:00 2001 From: Karl Cardenas Date: Tue, 2 Apr 2024 13:06:18 -0700 Subject: [PATCH] ci: visual tests (#2534) * ci: testing visual comp * ci: more test * test: changes * chore: update CI * chore: fix npm * save * chore: working locally * chore: working logic * chore: save * chore: testing CI * ci: test ci * ci: more test * ci: more ci tests * ci: test * chore: updated test * chore: test * more tests * ci: test * ci: change folder structure * chore: fixed ci * chore: reduce test feedback * chore: more test * ci: path change * ci: more test * ci: update tar config * ci: retrest * chore: test * chore: test * ci: more test * chore: more test * save * ci: different workflow * chore: fixed logic * chore: more tests * core: ready for PR test * ci: added missing steps * ci: screenshot logic * ci: trigger * ci: more logic * ci: fix test path * test: new logic * ci: fix download path * chore: ready * save * ci: more tests * ci: fix screenshots * ci: more changes * chore: more tests * ci: more tests * chore: restart * chore: update * ci: removed Appzi from builds * chore: save * ci: new test * save * ci: updated artifacts logic * chore: verify self-hosted runner * chore: ready for merge (cherry picked from commit 0670689d0fdd9814de4424d68563bee34dc539da) --- .dockerignore | 6 +- .github/workflows/clean-up-report.yaml | 61 +++ .github/workflows/screenshot_capture.yaml | 132 +++++ .github/workflows/visual-comparison.yaml | 245 ++++++++++ .gitignore | 9 +- Makefile | 23 + .../devx/services/service-listings/vault.md | 2 +- package-lock.json | 453 ++++++++++++++---- package.json | 7 +- playwright.config.ts | 25 + scripts/screenshot_artifacts.sh | 108 +++++ static/assets/icons/lock.svg | 12 + visuals/exclude.json | 1 + visuals/screenshot.api.spec.ts | 45 ++ visuals/screenshot.css | 51 ++ visuals/screenshot.docs.spec.ts | 50 ++ visuals/utils.ts | 24 + 17 files changed, 1165 insertions(+), 89 deletions(-) create mode 100644 .github/workflows/clean-up-report.yaml create mode 100644 .github/workflows/screenshot_capture.yaml create mode 100644 .github/workflows/visual-comparison.yaml create mode 100644 playwright.config.ts create mode 100755 scripts/screenshot_artifacts.sh create mode 100644 static/assets/icons/lock.svg create mode 100644 visuals/exclude.json create mode 100644 visuals/screenshot.api.spec.ts create mode 100644 visuals/screenshot.css create mode 100644 visuals/screenshot.docs.spec.ts create mode 100644 visuals/utils.ts diff --git a/.dockerignore b/.dockerignore index c75f282c3f..a2e0abb3d8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,4 +4,8 @@ CHANGES .gitignore .cache prow/ -.env \ No newline at end of file +.env +screenshots/ +tests/screenshot.spec.ts-snapshots/ +tests-results/ +playwright-report/ diff --git a/.github/workflows/clean-up-report.yaml b/.github/workflows/clean-up-report.yaml new file mode 100644 index 0000000000..a81244318b --- /dev/null +++ b/.github/workflows/clean-up-report.yaml @@ -0,0 +1,61 @@ +name: Delete Visual Tests Reports + +on: + delete: + branches-ignore: [master, main, gh-pages] + + +concurrency: + group: ${{ github.event.ref }} + cancel-in-progress: true + +jobs: + delete_reports: + name: Delete Reports + runs-on: ubuntu-latest + env: + # Contains all reports for deleted branch + BRANCH_REPORTS_DIR: reports/${{ github.event.ref }} + steps: + - name: Checkout GitHub Pages Branch + uses: actions/checkout@v4 + with: + ref: gh-pages + + - name: Set Git User + # see: https://github.com/actions/checkout/issues/13#issuecomment-724415212 + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Check for workflow reports + run: | + if [ -z "$(ls -A $BRANCH_REPORTS_DIR)" ]; then + echo "BRANCH_REPORTS_EXIST="false"" >> $GITHUB_ENV + else + echo "BRANCH_REPORTS_EXIST="true"" >> $GITHUB_ENV + fi + + - name: Delete reports from repo for branch + if: ${{ env.BRANCH_REPORTS_EXIST == 'true' }} + timeout-minutes: 3 + run: | + cd $BRANCH_REPORTS_DIR/.. + + rm -rf ${{ github.event.ref }} + git add . + git commit -m "workflow: remove all reports for branch ${{ github.event.ref }}" + + while true; do + git pull --rebase + if [ $? -ne 0 ]; then + echo "Failed to rebase. Please review manually." + exit 1 + fi + + git push + if [ $? -eq 0 ]; then + echo "Successfully pushed HTML reports to repo." + exit 0 + fi + done \ No newline at end of file diff --git a/.github/workflows/screenshot_capture.yaml b/.github/workflows/screenshot_capture.yaml new file mode 100644 index 0000000000..e138a540c0 --- /dev/null +++ b/.github/workflows/screenshot_capture.yaml @@ -0,0 +1,132 @@ +name: Screenshot Capture +# This workflow is triggered when a workflow run of the "Release to Production" workflow is completed or when manually triggered. +# The primary purpose of this workflow is to take screenshots of the website using Playwright and upload them as artifacts. +# The screenshots can be used to compare the visual appearance of the website before and after a release. +# The workflow Visual Comparison uses these screenshots to generate a visual comparison report. + +on: + workflow_run: + workflows: ["Release to Production"] + types: [completed] + workflow_dispatch: + + + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MENDABLE_API_KEY: ${{ secrets.MENDABLE_API_KEY }} + FULLSTORY_ORGID: ${{ secrets.FULLSTORY_ORGID }} + ALGOLIA_ADMIN_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_SEARCH_KEY: ${{ secrets.ALGOLIA_SEARCH_KEY }} + + +jobs: + + create-assets: + name: asset-builds + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js Environment + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "npm" + + - name: Install Dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Build Website + run: make build + + - name: Upload Build + uses: actions/upload-artifact@v4 + with: + name: "build" + path: | + build/ + if-no-files-found: error + retention-days: 1 + + + + + visual-snapshots: + runs-on: ubuntu-latest + needs: [create-assets] + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + shardTotal: [4] + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js Environment + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "npm" + + - name: Install Dependencies + uses: Wandalen/wretry.action@v3 + with: + command: npm ci + attempt_limit: 3 + attempt_delay: 60000 # 1 minute + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: retry action + uses: Wandalen/wretry.action@v3 + with: + attempt_limit: 3 + action: actions/download-artifact@v4 + with: | + name: build + path: build + attempt_delay: 60000 # 1 minute + + - name: Take Screenshots with Playwright + run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} || exit 0 + + - name: Upload Screenshots + uses: actions/upload-artifact@v4 + id: screenshots + with: + name: screenshots-${{ matrix.shardIndex }} + path: | + screenshots/ + if-no-files-found: error + retention-days: 1 + + merge-snapshots: + name: Merge Screenshots + runs-on: ubuntu-latest + needs: [visual-snapshots] + steps: + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: screenshots + pattern: screenshots-* + merge-multiple: true + + - name: Upload Screenshots + uses: actions/upload-artifact@v4 + id: screenshots + with: + name: "screenshots" + path: | + screenshots/ + if-no-files-found: error + retention-days: 3 diff --git a/.github/workflows/visual-comparison.yaml b/.github/workflows/visual-comparison.yaml new file mode 100644 index 0000000000..b95efa2c3d --- /dev/null +++ b/.github/workflows/visual-comparison.yaml @@ -0,0 +1,245 @@ +name: Visual Comparison + + +on: + pull_request: + types: [opened, reopened, synchronize, labeled] + branches-ignore: ["version-*", "release-*"] + paths: + - 'src/**' + - 'package.json' + - 'package-lock.json' + + +env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + MENDABLE_API_KEY: ${{ secrets.MENDABLE_API_KEY }} + FULLSTORY_ORGID: ${{ secrets.FULLSTORY_ORGID }} + ALGOLIA_ADMIN_KEY: ${{ secrets.ALGOLIA_ADMIN_KEY }} + ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} + ALGOLIA_SEARCH_KEY: ${{ secrets.ALGOLIA_SEARCH_KEY }} + HTML_REPORT_URL_PATH: reports/${{ github.ref_name }}/${{ github.run_id }}/${{ github.run_attempt }} + + + +concurrency: + group: "visual-comparison-${{ github.workflow }}" + cancel-in-progress: true + + +jobs: + run-ci: + runs-on: ubuntu-latest + defaults: + run: + shell: bash + if: ${{ !github.event.pull_request.draft && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' || contains(github.event.pull_request.labels.*.name, 'visual-tests') }} + steps: + # If the condition above is not met, aka, the PR is not in draft status, then this step is skipped. + # Because this step is part of the critical path, omission of this step will result in remaining CI steps not gettinge executed. + # As of 8/8/2022 there is now way to enforce this beahvior in GitHub Actions CI. + - run: exit 0 + + + create-assets: + name: create-assets + needs: [run-ci] + runs-on: ubuntu-latest + steps: + - name: Checkout Repository + uses: actions/checkout@v4 + + - name: Setup Node.js Environment + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "npm" + + - name: Post PR Comment + uses: mshick/add-pr-comment@v2 + with: + message: | + 🤖 Starting the visual tests. This will take approximately an hour. + refresh-message-position: true + + - name: Install Dependencies + uses: Wandalen/wretry.action@v3 + with: + command: npm ci + attempt_limit: 3 + attempt_delay: 60000 # 1 minute + + - name: Build Website + run: make build + + - name: Upload Build + uses: actions/upload-artifact@v4 + with: + name: "build" + path: | + build/ + if-no-files-found: error + retention-days: 1 + + + + take-screenshots: + name: Visual Comparison + needs: [run-ci, create-assets] + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + shardIndex: [1, 2, 3, 4] + shardTotal: [4] + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "npm" + + - name: Install Dependencies + uses: Wandalen/wretry.action@v3 + with: + command: npm ci + attempt_limit: 3 + attempt_delay: 60000 # 1 minute + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Download Build + uses: Wandalen/wretry.action@v3 + with: + attempt_limit: 3 + action: actions/download-artifact@v4 + with: | + name: build + path: build + attempt_delay: 60000 # 1 minute + + - name: Download Reference Screenshots + run: ./scripts/screenshot_artifacts.sh ./screenshots + + - name: Take screenshots with Playwright + run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} || exit 0 + + - name: Upload blob report to GitHub Actions Artifacts + uses: actions/upload-artifact@v4 + with: + name: blob-report-${{ matrix.shardIndex }} + path: blob-report + retention-days: 1 + + + merge-reports: + name: Merge Reports + needs: [take-screenshots] + runs-on: ubuntu-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Setup Node.js environment + uses: actions/setup-node@v4 + with: + node-version: "18" + cache: "npm" + + - name: Install Dependencies + uses: Wandalen/wretry.action@v3 + with: + command: npm ci + attempt_limit: 3 + attempt_delay: 60000 # 1 minute + + - name: Download blob reports from GitHub Actions Artifacts + uses: actions/download-artifact@v4 + with: + path: all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge into HTML Report + run: npx playwright merge-reports --reporter html ./all-blob-reports + + - name: Upload Report + uses: actions/upload-artifact@v4 + id: report + with: + name: "report" + path: | + playwright-report/ + if-no-files-found: error + retention-days: 1 + + + publish_report: + name: Publish HTML Report + needs: [run-ci, take-screenshots, merge-reports] + runs-on: ubuntu-latest + continue-on-error: true + steps: + + - name: Checkout GitHub Pages Branch + uses: actions/checkout@v4 + with: + ref: gh-pages + + - name: Set Git User + # see: https://github.com/actions/checkout/issues/13#issuecomment-724415212 + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" + + - name: Download zipped HTML report + uses: actions/download-artifact@v4 + with: + name: report + path: ${{ env.HTML_REPORT_URL_PATH }} + + - name: Push HTML Report + timeout-minutes: 5 + run: | + git add . + git commit -m "workflow: add HTML report for run-id ${{ github.run_id }} (attempt: ${{ github.run_attempt }})" + while true; do + git pull --rebase + if [ $? -ne 0 ]; then + echo "Failed to rebase. Please review manually." + exit 1 + fi + + git push + if [ $? -eq 0 ]; then + echo "Successfully pushed HTML report to repo." + exit 0 + fi + done + + + - name: DNS Wait + run: sleep 120 + + + - name: GH Pages URL + run: | + echo "::notice title= Published Playwright Test Report::https://spectrocloud.github.io/librarium/$HTML_REPORT_URL_PATH" + + + - name: Post Report URL Comment + uses: mshick/add-pr-comment@v2 + with: + message: | + 📋 Visual Report for PR ${{ github.ref_name }} with CI run ${{ github.run_id }} and attempt ${{ github.run_attempt }} is ready at + https://spectrocloud.github.io/librarium/${{env.HTML_REPORT_URL_PATH}} + + 💡 You may have to wait for DNS to resolve or the GitHub Pages job to complete. You can view the progress of the GitHub Pages job [here](https://github.com/spectrocloud/librarium/actions/workflows/pages/pages-build-deployment). + message-failure: | + 👎 Uh oh! Unable to publish Visual Report URL. + refresh-message-position: true + update-only: true \ No newline at end of file diff --git a/.gitignore b/.gitignore index bc3b331fe6..8ade0087c9 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,11 @@ staging_sidebars/ ## Cosign -cosign.key \ No newline at end of file +cosign.key + +# Visual Compare +screenshots/ +tests/screenshot.spec.ts-snapshots/ +test-results/ +playwright-report/ +artifact.zip \ No newline at end of file diff --git a/Makefile b/Makefile index bb532f940c..4f879211ca 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,10 @@ CHANGED_FILE=$(shell git diff-tree -r --no-commit-id --name-only master HEAD | g TEMP_DIR=$(shell $TMPDIR) +CPUS := $(shell sysctl -n hw.ncpu | awk '{print int($$1 / 2)}') + + + help: ## Display this help @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[0m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) @@ -31,6 +35,12 @@ clean-versions: ## Clean Docusarus content versions rm -rf api_versions.json versions.json versioned_docs versioned_sidebars api_versioned_sidebars api_versioned_docs git checkout -- docusaurus.config.js static/robots.txt +clean-visuals: + @echo "Cleaning visual regression tests" + + rm -rf test-results/ playwright-report/ screenshots/ + + ##@ npm Targets init: ## Initialize npm dependencies @@ -65,6 +75,19 @@ api: ## Generate API docs test: ## Run Jest tests npm test +test-visuals: ## Run visual regression tests + npx playwright test visuals/ + +test-visuals-ci: ## Run visual regression tests + npx playwright test --shard=1/4 + npx playwright test --shard=2/4 + npx playwright test --shard=3/4 + npx playwright test --shard=4/4 + + +view-visual-report: ## View visual regression test report + npx playwright show-report + ##@ Git Targets commit: ## Add a Git commit. Usage: make commit MESSAGE="" diff --git a/docs/docs-content/devx/services/service-listings/vault.md b/docs/docs-content/devx/services/service-listings/vault.md index 50180b7625..e7427c4c32 100644 --- a/docs/docs-content/devx/services/service-listings/vault.md +++ b/docs/docs-content/devx/services/service-listings/vault.md @@ -7,7 +7,7 @@ type: "appTier" category: ["security"] hiddenFromNav: false sidebar_position: 40 -logoUrl: "https://icon-library.com/images/padlock-icon.webp/padlock-icon.webp-29.jpg" +logoUrl: "/assets/icons/lock.svg" --- ## Vault diff --git a/package-lock.json b/package-lock.json index 6e0eb07351..cf5aafde96 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,9 @@ "sass": "^1.72.0" }, "devDependencies": { + "@argos-ci/cli": "^1.0.12", + "@argos-ci/playwright": "^2.0.0", + "@playwright/test": "^1.42.1", "@semantic-release/changelog": "^6.0.1", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "*", @@ -322,6 +325,108 @@ "url": "https://github.com/sponsors/philsturgeon" } }, + "node_modules/@argos-ci/browser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@argos-ci/browser/-/browser-1.5.0.tgz", + "integrity": "sha512-a9uzWL3L2qalqXa5kzkjlfnznpkGbzNhS5QSf0DtCylOWSrpnUMK+cIj3xzGMgmZK7SZ/r9OYdSZfsaSizWLXA==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@argos-ci/cli": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@argos-ci/cli/-/cli-1.0.12.tgz", + "integrity": "sha512-Hd/n6kuQwAbIMqfQg/+zzYzC4xoqekBCkfc695KdU2eFDnIokUcxyJmAnBDE9zD99VfaV2kw/0VGVAoAZnzEYA==", + "dev": true, + "dependencies": { + "@argos-ci/core": "1.5.5", + "commander": "^11.0.0", + "ora": "^7.0.1", + "update-notifier": "^6.0.2" + }, + "bin": { + "argos": "bin/argos-cli.js" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@argos-ci/cli/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "engines": { + "node": ">=16" + } + }, + "node_modules/@argos-ci/core": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@argos-ci/core/-/core-1.5.5.tgz", + "integrity": "sha512-m271rCtp5fnpUU89eSlS/If5WnDIUGduEHfW9YrwVHnMYWPCNBDxp2q87Wgjl3O5RTm/x+sDED3vFD9i1q+yTQ==", + "dev": true, + "dependencies": { + "@argos-ci/util": "1.2.1", + "axios": "^1.5.0", + "convict": "^6.2.4", + "debug": "^4.3.4", + "fast-glob": "^3.3.1", + "sharp": "^0.32.5", + "tmp": "^0.2.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@argos-ci/core/node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/@argos-ci/playwright": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@argos-ci/playwright/-/playwright-2.0.0.tgz", + "integrity": "sha512-rER3LPRYCS7CD3VK7ddP63tXj/3crQKZqrPGtY3QobyRO0IFech6xejEAHz2zM8ZT8aXH/dQydoU7vR9PCP8SQ==", + "dev": true, + "dependencies": { + "@argos-ci/browser": "1.5.0", + "@argos-ci/core": "1.5.5", + "@argos-ci/util": "1.2.1", + "chalk": "^5.3.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@argos-ci/playwright/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@argos-ci/util": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@argos-ci/util/-/util-1.2.1.tgz", + "integrity": "sha512-/o7t0TcSED3BsBnnPrvXdmT+reThGMoGC9Lk+TTghrEE9GFlSKhjBmfYt4fUgXj5hQIe5tcdO01BVB2TsjjYSw==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "license": "MIT", @@ -5035,6 +5140,21 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@playwright/test": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.42.1.tgz", + "integrity": "sha512-Gq9rmS54mjBL/7/MvBaNOBwbfnh7beHvS6oS4srqXFcQHpQCV1+c8JXWE8VLPyRDhgS3H8x8A7hztqI9VnwrAQ==", + "dev": true, + "dependencies": { + "playwright": "1.42.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@pnpm/config.env-replace": { "version": "1.1.0", "license": "MIT", @@ -10275,6 +10395,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cli-table3": { "version": "0.6.3", "license": "MIT", @@ -10938,6 +11070,19 @@ "version": "2.0.0", "license": "MIT" }, + "node_modules/convict": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/convict/-/convict-6.2.4.tgz", + "integrity": "sha512-qN60BAwdMVdofckX7AlohVJ2x9UvjTNoKVXCL2LxFk1l7757EJqf1nySdMkPQer0bt8kQ5lQiyZ9/2NvrFBuwQ==", + "dev": true, + "dependencies": { + "lodash.clonedeep": "^4.5.0", + "yargs-parser": "^20.2.7" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/cookie": { "version": "0.5.0", "license": "MIT", @@ -21293,6 +21438,18 @@ "node": ">=10" } }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-jpg": { "version": "2.0.0", "dev": true, @@ -26220,6 +26377,34 @@ "version": "4.3.1", "license": "MIT" }, + "node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dev": true, + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/log-update": { "version": "5.0.1", "dev": true, @@ -36860,17 +37045,6 @@ "node": ">=4" } }, - "node_modules/netlify-cli/node_modules/cli-spinners": { - "version": "2.6.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/netlify-cli/node_modules/cli-truncate": { "version": "3.1.0", "dev": true, @@ -40902,17 +41076,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/netlify-cli/node_modules/is-interactive": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/netlify-cli/node_modules/is-npm": { "version": "6.0.0", "dev": true, @@ -42016,21 +42179,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/netlify-cli/node_modules/log-symbols": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/netlify-cli/node_modules/log-update": { "version": "5.0.1", "dev": true, @@ -45454,53 +45602,6 @@ "dev": true, "license": "MIT" }, - "node_modules/netlify-cli/node_modules/stdin-discarder": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "bl": "^5.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/netlify-cli/node_modules/stdin-discarder/node_modules/bl": { - "version": "5.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer": "^6.0.3", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/netlify-cli/node_modules/stdin-discarder/node_modules/buffer": { - "version": "6.0.3", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, "node_modules/netlify-cli/node_modules/streamx": { "version": "2.15.0", "dev": true, @@ -50752,6 +50853,91 @@ "node": ">=10" } }, + "node_modules/ora": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", + "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "dev": true, + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.0", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.3.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "string-width": "^6.1.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true + }, + "node_modules/ora/node_modules/string-width": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", + "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^10.2.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/ordered-read-streams": { "version": "1.0.1", "dev": true, @@ -51621,6 +51807,50 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.42.1.tgz", + "integrity": "sha512-PgwB03s2DZBcNRoW+1w9E+VkLBxweib6KTXM0M3tkiT4jVxKSi6PmVJ591J+0u10LUrgxB7dLRbiJqO5s2QPMg==", + "dev": true, + "dependencies": { + "playwright-core": "1.42.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=16" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.42.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.42.1.tgz", + "integrity": "sha512-mxz6zclokgrke9p1vtdy/COWBH+eOZgYUVVU34C73M+4j4HLlQJHtfcqiqqxpP0o8HhMkflvfbquLX5dg6wlfA==", + "dev": true, + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/playwright/node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/plugin-error": { "version": "1.0.1", "dev": true, @@ -52550,6 +52780,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true + }, "node_modules/prr": { "version": "1.0.1", "dev": true, @@ -60736,6 +60972,46 @@ "version": "3.6.0", "license": "MIT" }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "dev": true, + "dependencies": { + "bl": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/stdin-discarder/node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dev": true, + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/stdin-discarder/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/stop-iteration-iterator": { "version": "1.0.0", "dev": true, @@ -62309,6 +62585,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/tmp": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", + "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, + "engines": { + "node": ">=14.14" + } + }, "node_modules/tmpl": { "version": "1.0.5", "dev": true, diff --git a/package.json b/package.json index cbf9851142..cedff664f9 100644 --- a/package.json +++ b/package.json @@ -56,10 +56,13 @@ "prism-react-renderer": "^2.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "sass": "^1.72.0", - "react-select": "^5.8.0" + "react-select": "^5.8.0", + "sass": "^1.72.0" }, "devDependencies": { + "@argos-ci/cli": "^1.0.12", + "@argos-ci/playwright": "^2.0.0", + "@playwright/test": "^1.42.1", "@semantic-release/changelog": "^6.0.1", "@semantic-release/exec": "^6.0.3", "@semantic-release/git": "latest", diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000000..754d40d085 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,25 @@ +import { PlaywrightTestConfig } from "@playwright/test"; + +const config: PlaywrightTestConfig = { + snapshotDir: "screenshots/", + testDir: "visuals", + fullyParallel: true, + retries: 1, + expect: { + toMatchSnapshot: { + maxDiffPixels: 100, + }, + toHaveScreenshot: { + maxDiffPixels: 100, + }, + timeout: 30000, + }, + reporter: process.env.CI ? "blob" : "html", + webServer: { + command: "npm run serve", + url: "http://127.0.0.1:3000", + reuseExistingServer: !process.env.CI, + }, +}; + +export default config; diff --git a/scripts/screenshot_artifacts.sh b/scripts/screenshot_artifacts.sh new file mode 100755 index 0000000000..0824805494 --- /dev/null +++ b/scripts/screenshot_artifacts.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +#################################################################################################### +# This script is used to download the latest screenshots captured by the screenshot_capture workflow. +# The assumption is that this script is executed in a GitHub Actions environment. +# The script fetches the latest workflow run for the specified workflow file and repository. +# It then downloads the first artifact from the latest run and unzips it to a specified folder. +# The unzipped folder is named "screenshots" by default and dropped one level above the script location. +# The script requires the GITHUB_TOKEN environment variable to be set with a valid GitHub token. +# Otherwise the artifacts cannot be downloaded. +#################################################################################################### + +set -e + +DESTINATION_FOLDER=$1 + +# Function to unzip the artifact +unzip_artifact() { + local zip_file=$1 + local destination_folder=$2 # Use passed variable for folder name + + echo "Unzipping $zip_file to $destination_folder..." + mkdir -p "$destination_folder" + unzip -o "$zip_file" -d "$destination_folder" + echo "Artifact unzipped successfully." + rm -f "$zip_file" +} + +# Ensure GITHUB_TOKEN is set +if [ -z "$GITHUB_TOKEN" ]; then + echo "GitHub token (GITHUB_TOKEN) not provided. Please set the GITHUB_TOKEN environment variable." + exit 1 +fi + +OWNER="${GITHUB_REPOSITORY_OWNER:-spectrocloud}" +REPO_NAME="${GITHUB_REPOSITORY#*/}" +REPO="${REPO_NAME:-librarium}" +WORKFLOW_FILE="screenshot_capture.yaml" +UNZIP_FOLDER="${DESTINATION_FOLDER:-screenshots}" + +perform_curl() { + local url=$1 + response=$(curl -s -f -w "%{http_code}" -o temp_response.txt -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" "$url") + http_code=$(tail -n1 temp_response.txt) + if [ "$response" != "200" ]; then + echo "Error: HTTP response $http_code for $url" + cat temp_response.txt + rm temp_response.txt + exit 1 + fi + jq '.' < temp_response.txt + rm temp_response.txt +} + +# Fetch the latest workflow run for the specified workflow +LATEST_RUN=$(perform_curl "https://api.github.com/repos/$OWNER/$REPO/actions/workflows/$WORKFLOW_FILE/runs" | jq '.workflow_runs[0]') + +# Extract status, conclusion, and artifacts_url +STATUS=$(echo $LATEST_RUN | jq -r '.status') +CONCLUSION=$(echo $LATEST_RUN | jq -r '.conclusion') +ARTIFACTS_URL=$(echo $LATEST_RUN | jq -r '.artifacts_url') + +# Check if the status is "completed" and the conclusion is "success" +if [[ "$STATUS" == "completed" && "$CONCLUSION" == "success" ]]; then + echo "The latest workflow run completed successfully ✅" + + # Fetch artifacts details + ARTIFACTS=$(perform_curl "$ARTIFACTS_URL") + + # Display total count of artifacts + ARTIFACTS_COUNT=$(echo "$ARTIFACTS" | jq '.total_count') + echo "Found $ARTIFACTS_COUNT artifact(s)." + + if [[ "$ARTIFACTS_COUNT" -gt 0 ]]; then + # Loop through each artifact and print details + echo $ARTIFACTS | jq -c '.artifacts[]' | while read -r ARTIFACT; do + NAME=$(echo $ARTIFACT | jq -r '.name') + SIZE=$(echo $ARTIFACT | jq -r '.size_in_bytes') + CREATED_AT=$(echo $ARTIFACT | jq -r '.created_at') + EXPIRES_AT=$(echo $ARTIFACT | jq -r '.expires_at') + echo "Artifact: $NAME" + echo "Size: $SIZE bytes" + echo "Created at: $CREATED_AT" + echo "Expires at: $EXPIRES_AT" + echo "--------------------------------" + done + + # Specifically download the "screenshots" artifact + DOWNLOAD_URL=$(echo "$ARTIFACTS" | jq -r '.artifacts[] | select(.name == "screenshots") | .archive_download_url') + if [[ -n "$DOWNLOAD_URL" && "$DOWNLOAD_URL" != "null" ]]; then + DOWNLOAD_PATH="artifact.zip" + echo "Downloading 'screenshots' artifact from $DOWNLOAD_URL..." + if ! curl -L -H "Authorization: token $GITHUB_TOKEN" -H "Accept: application/vnd.github.v3+json" -o "$DOWNLOAD_PATH" "$DOWNLOAD_URL"; then + echo "Error downloading 'screenshots' artifact." + exit 1 + fi + echo "'screenshots' artifact downloaded to $DOWNLOAD_PATH" + unzip_artifact "$DOWNLOAD_PATH" "$UNZIP_FOLDER" + else + echo "No 'screenshots' artifact download URL found ⛔" + fi + else + echo "No artifacts found ⛔" + fi +else + echo "The latest workflow run has not completed successfully ❌" + exit 1 +fi diff --git a/static/assets/icons/lock.svg b/static/assets/icons/lock.svg new file mode 100644 index 0000000000..a76443f4ee --- /dev/null +++ b/static/assets/icons/lock.svg @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/visuals/exclude.json b/visuals/exclude.json new file mode 100644 index 0000000000..a177746202 --- /dev/null +++ b/visuals/exclude.json @@ -0,0 +1 @@ +["/integrations/"] diff --git a/visuals/screenshot.api.spec.ts b/visuals/screenshot.api.spec.ts new file mode 100644 index 0000000000..de697b9c94 --- /dev/null +++ b/visuals/screenshot.api.spec.ts @@ -0,0 +1,45 @@ +import * as fs from "fs"; +import { test, expect } from "@playwright/test"; +import { extractSitemapPathnames, WaitForDocusaurusHydration } from "./utils"; + +const siteUrl = "http://localhost:3000"; +const sitemapPath = "build/sitemap.xml"; +const stylesheetPath = "visuals/screenshot.css"; +const stylesheet = fs.readFileSync(stylesheetPath).toString(); +const excludeList = require("./exclude.json"); + +test.describe.configure({ mode: "parallel" }); + +function isApiDocsPathname(pathname: string, excludeList: string[]): boolean { + if (excludeList.includes(pathname)) { + return false; + } + // return false if the pathname does not start with /api/ + if (pathname.startsWith("/api/") && !pathname.match(/^\/api\/(\d+\.\d+\.x)\//)) { + return true; + } + return false; +} + +test.beforeAll(async () => { + console.log("Excluded pages: ", excludeList); + console.log("Total pages: ", extractSitemapPathnames(sitemapPath).length); +}); + +function screenshotPathname(pathname: string) { + test(`pathname ${pathname}`, async ({ page }) => { + console.log(`Taking screenshot of ${pathname}`); + const url = siteUrl + pathname; + await page.goto(url); + await page.waitForFunction(WaitForDocusaurusHydration); + await page.waitForLoadState("domcontentloaded"); + await page.addStyleTag({ content: stylesheet }); + await page.waitForTimeout(1000); // Waits for 1000 milliseconds + await expect(page).toHaveScreenshot({ fullPage: true, timeout: 10000 }); + }); +} + +test.describe("API docs screenshots", () => { + const pathnames = extractSitemapPathnames(sitemapPath).filter((pathname) => isApiDocsPathname(pathname, excludeList)); + pathnames.forEach(screenshotPathname); +}); diff --git a/visuals/screenshot.css b/visuals/screenshot.css new file mode 100644 index 0000000000..c6b8e78eda --- /dev/null +++ b/visuals/screenshot.css @@ -0,0 +1,51 @@ +/* +We need to hide some elements in Playwright screenshots +Those elements are source of flakiness due to nondeterministic rendering +They don't consistently render exactly the same across CI runs + */ + +/******* DOCUSAURUS GLOBAL / THEME *******/ + +/* Iframes can load lazily */ +iframe, +/* Avatar images can be flaky due to using external sources: GitHub/Unavatar */ +.avatar__photo, +/* Gifs load lazily and are animated */ +img[src$='.gif'], +/* Algolia Keyboard shortcuts appear with a little delay */ +.DocSearch-Button-Keys > kbd, +/* The live playground preview can often display dates/counters */ +[class*='playgroundPreview'] { + visibility: hidden; +} + +/* +Different docs last-update dates can alter layout +"visibility: hidden" is not enough + */ +.theme-last-updated { + display: none; +} + +/* +Mermaid diagrams are rendered client-side and produce layout shifts + */ +.docusaurus-mermaid-container { + display: none; +} + +/* +Hide Cookie Consent banner + +*/ +#usercentrics-root { + display: none !important; +} + +/* +API Request Form + +*/ +.openapi-explorer__request-form { + display: none; +} diff --git a/visuals/screenshot.docs.spec.ts b/visuals/screenshot.docs.spec.ts new file mode 100644 index 0000000000..9588901edc --- /dev/null +++ b/visuals/screenshot.docs.spec.ts @@ -0,0 +1,50 @@ +import * as fs from "fs"; +import { test, expect } from "@playwright/test"; +import { extractSitemapPathnames, WaitForDocusaurusHydration } from "./utils"; +// Constants: +const siteUrl = "http://localhost:3000"; +const sitemapPath = "build/sitemap.xml"; +const stylesheetPath = "visuals/screenshot.css"; +const stylesheet = fs.readFileSync(stylesheetPath).toString(); +const excludeList = require("./exclude.json"); + +test.describe.configure({ mode: "parallel" }); + +function isVersionedDocsPathname(pathname: string, excludeList: string[]): boolean { + if (excludeList.includes(pathname)) { + console.log(`Excluding ${pathname}`); + return false; + } + + if (pathname.startsWith("/api/") || pathname.match(/\/\d+\.\d+\.x\//)) { + return false; + } + + return true; +} + +test.beforeAll(async () => { + console.log("Excluded pages: ", excludeList); + console.log("Total pages: ", extractSitemapPathnames(sitemapPath).length); +}); + +function screenshotPathname(pathname: string) { + test(`pathname ${pathname}`, async ({ page }) => { + console.log(`Taking screenshot of ${pathname}`); + const url = siteUrl + pathname; + await page.goto(url); + await page.waitForFunction(WaitForDocusaurusHydration); + await page.waitForLoadState("domcontentloaded"); + await page.addStyleTag({ content: stylesheet }); + await page.waitForTimeout(1000); // Waits for 1000 milliseconds + await expect(page).toHaveScreenshot({ fullPage: true, timeout: 10000 }); + }); +} + +test.describe("Docs screenshots", () => { + const pathnames = extractSitemapPathnames(sitemapPath).filter((pathname) => + isVersionedDocsPathname(pathname, excludeList) + ); + + pathnames.forEach(screenshotPathname); +}); diff --git a/visuals/utils.ts b/visuals/utils.ts new file mode 100644 index 0000000000..a524fc74e2 --- /dev/null +++ b/visuals/utils.ts @@ -0,0 +1,24 @@ +import * as cheerio from "cheerio"; +import * as fs from "fs"; + +export function extractSitemapPathnames(sitemapPath: string): string[] { + const sitemap = fs.readFileSync(sitemapPath).toString(); + const $ = cheerio.load(sitemap, { xmlMode: true }); + const urls: string[] = []; + $("loc").each(function handleLoc() { + urls.push($(this).text()); + }); + return urls.map((url) => new URL(url).pathname); +} + +// Converts a pathname to a decent screenshot name +export function pathnameToArgosName(pathname: string): string { + return pathname.replace(/^\/|\/$/g, "") || "index"; +} + +// Wait for hydration, requires Docusaurus v2.4.3+ +// See https://github.com/facebook/docusaurus/pull/9256 +// Docusaurus adds a once hydrated +export function WaitForDocusaurusHydration() { + return document.documentElement.dataset.hasHydrated === "true"; +}