diff --git a/.github/workflows/visualdiffs.yml b/.github/workflows/visualdiffs.yml new file mode 100644 index 0000000..2071f5a --- /dev/null +++ b/.github/workflows/visualdiffs.yml @@ -0,0 +1,47 @@ +name: Visual URL Comparison + +on: + pull_request: + +jobs: + visual-diff: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Install deps for PR build + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps + + - name: Build PR version + run: npm run build + + - name: Serve PR build + run: npm run preview -- --port 3001 & + + # Checkout base branch and build + - name: Checkout base branch + run: | + git fetch origin ${{ github.event.pull_request.base.ref }} --depth=1 + git checkout FETCH_HEAD + + - name: Install deps for base build + run: npm ci + + - name: Build base version + run: npm run build + + - name: preview vite build + run: npm run preview -- --port 3002 & + + # Run screenshot diff script + - name: Run visual diff + run: node scripts/visualDiff.cjs + + - uses: actions/upload-artifact@v4 + with: + name: visual-diffs + path: diffs diff --git a/package-lock.json b/package-lock.json index 00c00fb..63a725c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,9 @@ "eslint": "^9.33.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^5.2.0", + "pixelmatch": "^7.1.0", + "playwright": "^1.57.0", + "pngjs": "^7.0.0", "tailwindcss": "^4.1.16", "vite": "^7.1.2" } @@ -15887,6 +15890,19 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz", + "integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==", + "dev": true, + "license": "ISC", + "dependencies": { + "pngjs": "^7.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -16024,6 +16040,63 @@ "node": ">=4" } }, + "node_modules/playwright": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.57.0.tgz", + "integrity": "sha512-ilYQj1s8sr2ppEJ2YVadYBN0Mb3mdo9J0wQ+UuDhzYqURwSoW4n1Xs5vs7ORwgDGmyEh33tRMeS8KhdkMoLXQw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.57.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.57.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.57.0.tgz", + "integrity": "sha512-agTcKlMw/mjBWOnD6kFZttAAGHgi/Nw0CZ2o6JqWSbMlI219lAFLZZCyqByTsvVAJq5XA5H8cA6PrvBRpBWEuQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "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, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/pngjs": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz", + "integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.19.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -20769,9 +20842,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "license": "Apache-2.0", "peer": true, "bin": { @@ -20779,7 +20852,7 @@ "tsserver": "bin/tsserver" }, "engines": { - "node": ">=14.17" + "node": ">=4.2.0" } }, "node_modules/unbox-primitive": { diff --git a/package.json b/package.json index 27f6553..2e5f7fc 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,9 @@ "eslint": "^9.33.0", "eslint-plugin-react": "^7.32.2", "eslint-plugin-react-hooks": "^5.2.0", + "pixelmatch": "^7.1.0", + "playwright": "^1.57.0", + "pngjs": "^7.0.0", "tailwindcss": "^4.1.16", "vite": "^7.1.2" } diff --git a/scripts/visualDiff.cjs b/scripts/visualDiff.cjs new file mode 100644 index 0000000..ea1bdc2 --- /dev/null +++ b/scripts/visualDiff.cjs @@ -0,0 +1,41 @@ +const { chromium } = require("playwright"); +const pixelmatch = require("pixelmatch").default || require("pixelmatch"); +const { PNG } = require("pngjs"); +const fs = require("fs"); + +const urls = [{ name: "home", path: "/" }]; + +async function run() { + if (!fs.existsSync("diffs")) fs.mkdirSync("diffs"); + + const browser = await chromium.launch(); + + for (const { name, path } of urls) { + const page = await browser.newPage(); + + // PR screenshot + await page.goto(`http://localhost:5173${path}`); + await page.screenshot({ path: `diffs/${name}_pr.png`, fullPage: true }); + + // Base screenshot + await page.goto(`http://localhost:5173${path}`); + await page.screenshot({ path: `diffs/${name}_base.png`, fullPage: true }); + + // Compare + const img1 = PNG.sync.read(fs.readFileSync(`diffs/${name}_base.png`)); + const img2 = PNG.sync.read(fs.readFileSync(`diffs/${name}_pr.png`)); + const out = new PNG({ width: img1.width, height: img1.height }); + + pixelmatch(img1.data, img2.data, out.data, img1.width, img1.height, { + threshold: 0.3, + }); + + fs.writeFileSync(`diffs/${name}_diff.png`, PNG.sync.write(out)); + + await page.close(); + } + + await browser.close(); +} + +run(); diff --git a/src/components/OptimadeClient/OptimadeFAQs.jsx b/src/components/OptimadeClient/OptimadeFAQs.jsx index 28d3844..99f84ef 100644 --- a/src/components/OptimadeClient/OptimadeFAQs.jsx +++ b/src/components/OptimadeClient/OptimadeFAQs.jsx @@ -33,7 +33,7 @@ export default function OptimadeFAQs() { {/* FAQ Content */}

- Why is a given provider not shown in the client? + Why is a given provider not shown in the client? ----

The most likely reason is that they have not yet registered with{" "}