Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions .github/workflows/visual-comparison.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
name: Visual Comparison

on:
pull_request:
types: [opened, reopened, synchronize, labeled]
workflow_dispatch:

permissions:
contents: write
pull-requests: write

env:
HTML_REPORT_URL_PATH: visual-tests/${{ github.head_ref || github.ref_name }}/${{ github.run_id }}/${{ github.run_attempt }}

concurrency:
group: visual-comparison-${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
cancel-in-progress: true

jobs:
guard:
name: Check Trigger Conditions
runs-on: ubuntu-latest
if: |
github.event_name == 'workflow_dispatch' ||
(
github.event.pull_request.draft == false &&
github.actor != 'dependabot[bot]' &&
github.actor != 'dependabot-preview[bot]' &&
contains(github.event.pull_request.labels.*.name, 'visual-tests')
)
steps:
- run: |
echo "Visual comparison enabled for this run"
echo "base ref: ${{ github.base_ref }}"
echo "head ref: ${{ github.head_ref }}"
echo "report path: ${{ env.HTML_REPORT_URL_PATH }}"

Copy link

@semgrep-managed-scans semgrep-managed-scans bot Oct 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using variable interpolation ${{...}} with github context data in a run: step could allow an attacker to inject their own code into the runner. This would allow them to steal secrets and code. github context data can have arbitrary user input and should be treated as untrusted. Instead, use an intermediate environment variable with env: to store the data and use the environment variable in the run: script. Be sure to use double-quotes the environment variable, like this: "$ENVVAR".

🚀 Fixed in commit 5fb6ce4 🚀

build-site:
name: Build Docusaurus Site
needs: guard
if: ${{ needs.guard.result == 'success' }}
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Build static site
run: npm run build

- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: static-site
path: build
retention-days: 1

visual-tests:
name: Run Playwright Visual Tests
needs: [guard, build-site]
if: ${{ needs.guard.result == 'success' && needs['build-site'].result == 'success' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
shardIndex: [1, 2, 3, 4]
shardTotal: [4]
steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Playwright browsers
run: npx playwright install --with-deps chromium

- name: Download build artifact
uses: actions/download-artifact@v5
with:
name: static-site
path: build

- name: Run Playwright tests
run: npx playwright test --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}

- name: Upload blob report
uses: actions/upload-artifact@v4
with:
name: blob-report-${{ matrix.shardIndex }}
path: blob-report
retention-days: 1

merge-report:
name: Merge HTML Report
needs: visual-tests
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v5

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Download blob reports
uses: actions/download-artifact@v5
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 HTML report
uses: actions/upload-artifact@v4
with:
name: html-report
path: playwright-report
retention-days: 3

publish-report:
name: Publish HTML Report
needs: [guard, visual-tests, merge-report]
runs-on: ubuntu-latest
if: ${{ needs.guard.result == 'success' && needs['visual-tests'].result == 'success' && needs['merge-report'].result == 'success' }}
steps:
- name: Checkout gh-pages branch
uses: actions/checkout@v5
with:
ref: gh-pages
fetch-depth: 0

- name: Configure git user
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"

- name: Download HTML report
uses: actions/download-artifact@v5
with:
name: html-report
path: ${{ env.HTML_REPORT_URL_PATH }}

- name: Commit report
run: |
if git status --short | grep .; then
git add ${{ env.HTML_REPORT_URL_PATH }}
git commit -m "workflow: add visual report for run ${{ github.run_id }} (attempt ${{ github.run_attempt }})"
for attempt in {1..5}; do
git pull --rebase
if git push; then
exit 0
fi
echo "Push failed (attempt $attempt). Retrying in 5s..."
sleep 5
done
echo "Unable to push HTML report after 5 attempts"
exit 1
else
echo "No report changes to commit"
fi

- name: Display report URL
run: |
echo "::notice title=Playwright Visual Report::https://temporalio.github.io/documentation/${{ env.HTML_REPORT_URL_PATH }}"

- name: Comment on PR with report URL
if: ${{ github.event_name == 'pull_request' }}
uses: mshick/add-pr-comment@v2
with:
message: |
📋 Visual comparison report for `${{ github.head_ref || github.ref_name }}` (run ${{ github.run_id }}, attempt ${{ github.run_attempt }}) is ready:
https://temporalio.github.io/documentation/${{ env.HTML_REPORT_URL_PATH }}

💡 If the page 404s, wait for GitHub Pages to publish the new commit and try again.
message-failure: 👎 Unable to publish the visual comparison report link.
refresh-message-position: true
update-only: false
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ secure

# Production
build
playwright-report
blob-report
test-results

# Generated files
.docusaurus
Expand Down Expand Up @@ -34,4 +37,4 @@ package-lock.json

# Env
.env
/assembly/.env
/assembly/.env
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"start": "docusaurus start",
"swizzle": "docusaurus swizzle",
"vale": "./assembly/run-vale.sh",
"test:visual": "playwright test",
"write-heading-ids": "docusaurus write-heading-ids",
"write-translations": "docusaurus write-translations"
},
Expand Down Expand Up @@ -66,6 +67,7 @@
"webpack-font-preload-plugin": "^1.5.0"
},
"devDependencies": {
"@playwright/test": "^1.55.1",
"dprint": "^0.45.0",
"eslint": "^7.32.0",
"eslint-plugin-react": "^7.23.2",
Expand Down
37 changes: 37 additions & 0 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { devices, defineConfig } from "@playwright/test";

const DEFAULT_BASE_URL = "http://127.0.0.1:3000";

export default defineConfig({
snapshotDir: "screenshots",
testDir: "visuals",
fullyParallel: true,
retries: process.env.CI ? 1 : 0,
expect: {
toMatchSnapshot: {
maxDiffPixels: 100,
},
toHaveScreenshot: {
maxDiffPixels: 100,
},
timeout: 30_000,
},
reporter: process.env.CI ? "blob" : "html",
webServer: {
command: "npm run serve -- --dir build --no-open --host 127.0.0.1 --port 3000",
url: process.env.PLAYWRIGHT_BASE_URL ?? DEFAULT_BASE_URL,
reuseExistingServer: !process.env.CI,
timeout: 120 * 1000,
},
projects: [
{
name: "chromium",
use: {
...devices["Desktop Chrome"],
baseURL: process.env.PLAYWRIGHT_BASE_URL ?? DEFAULT_BASE_URL,
trace: process.env.CI ? "retain-on-failure" : "off",
video: process.env.CI ? "retain-on-failure" : "off",
},
},
],
});
18 changes: 18 additions & 0 deletions visuals/docs-home.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { expect, test } from "@playwright/test";

const basePath = "/";

// Disable animations so captured screenshots are stable.
const screenshotOptions = {
animations: "disabled" as const,
fullPage: true,
};

test.describe("Documentation visuals", () => {
test("homepage", async ({ page }) => {
await page.goto(basePath);
await page.waitForLoadState("networkidle");

await expect(page).toHaveScreenshot("homepage.png", screenshotOptions);
});
});
26 changes: 26 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2342,6 +2342,13 @@
dependencies:
"@octokit/openapi-types" "^12.11.0"

"@playwright/test@^1.55.1":
version "1.55.1"
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.55.1.tgz#80f775d5f948cd3ef550fcc45ef99986d3ffb36c"
integrity sha512-IVAh/nOJaw6W9g+RJVlIQJ6gSiER+ae6mKQ5CX1bERzQgbC1VSeBlwdvczT7pxb0GWiyrxH4TGKbMfDb4Sq/ig==
dependencies:
playwright "1.55.1"

"@pnpm/config.env-replace@^1.1.0":
version "1.1.0"
resolved "https://registry.npmjs.org/@pnpm/config.env-replace/-/config.env-replace-1.1.0.tgz"
Expand Down Expand Up @@ -6034,6 +6041,11 @@ fs.realpath@^1.0.0:
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==

[email protected]:
version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==

fsevents@~2.3.2:
version "2.3.3"
resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
Expand Down Expand Up @@ -9226,6 +9238,20 @@ pkg-dir@^7.0.0:
dependencies:
find-up "^6.3.0"

[email protected]:
version "1.55.1"
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.55.1.tgz#5d3bb1846bc4289d364ea1a9dcb33f14545802e9"
integrity sha512-Z6Mh9mkwX+zxSlHqdr5AOcJnfp+xUWLCt9uKV18fhzA8eyxUd8NUWzAjxUh55RZKSYwDGX0cfaySdhZJGMoJ+w==

[email protected]:
version "1.55.1"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.55.1.tgz#8a9954e9e61ed1ab479212af9be336888f8b3f0e"
integrity sha512-cJW4Xd/G3v5ovXtJJ52MAOclqeac9S/aGGgRzLabuF8TnIb6xHvMzKIa6JmrRzUkeXJgfL1MhukP0NK6l39h3A==
dependencies:
playwright-core "1.55.1"
optionalDependencies:
fsevents "2.3.2"

possible-typed-array-names@^1.0.0:
version "1.1.0"
resolved "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz"
Expand Down