Skip to content

Add PR UI screenshot workflow#4

Merged
kushal238 merged 4 commits into
mainfrom
feat/pr-screenshots
Feb 6, 2026
Merged

Add PR UI screenshot workflow#4
kushal238 merged 4 commits into
mainfrom
feat/pr-screenshots

Conversation

@kushal238

Copy link
Copy Markdown
Owner

Summary

  • Adds a GitHub Actions workflow to capture before/after UI screenshots on PRs.
  • Uses Playwright + pixelmatch to generate diffs.
  • Uploads screenshot artifacts for review.

Details

  • Workflow runs on PRs to main.
  • Builds base and PR commits, captures screenshots for /, /pricing, /about, /contact, then diffs.
  • Artifacts stored as artifacts/before, artifacts/after, artifacts/diff.

Notes

  • Adjust route list in scripts/ui-snapshots.js if needed.
  • No breaking changes.

@vercel

vercel Bot commented Feb 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ariamed Ready Ready Preview, Comment Feb 6, 2026 9:57pm

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @kushal238, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request establishes a robust UI screenshot and visual regression testing pipeline within the GitHub Actions workflow. Its primary purpose is to automatically detect unintended visual changes in the user interface by comparing screenshots of key application pages before and after a pull request's changes are applied. This proactive approach helps maintain UI consistency and quality, providing developers and reviewers with immediate visual feedback on any layout or styling discrepancies.

Highlights

  • Automated UI Screenshot Workflow: A new GitHub Actions workflow has been introduced to automate UI screenshot capture and visual regression testing on pull requests.
  • Playwright and Pixelmatch Integration: The workflow leverages Playwright to render web pages and pixelmatch to compare 'before' and 'after' screenshots, generating visual diffs.
  • Artifact Uploads for Review: Captured screenshots (before, after) and their pixel-level diffs are uploaded as build artifacts, facilitating easy visual review of UI changes.
  • Configurable Route Coverage: The system is configured to run on pull requests targeting the main branch and covers specified routes like /, /pricing, /about, and /contact, with the route list being adjustable.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • package-lock.json
    • Added new development dependencies: @playwright/test, pixelmatch, pngjs, and wait-on.
    • Introduced several new transitive dependencies required by the new tooling, including various @Hapi, @sideway, es-errors, form-data, get-intrinsic, joi, mime-types, minimist, proxy-from-env, and rxjs packages.
    • Updated versions for existing dependencies such as @next/env, @next/swc-* variants, @remix-run/router, glob, js-yaml, lodash, next, react-router, react-router-dom, and vite.
    • Removed the @swc/helpers package.
  • package.json
    • Added @playwright/test, pixelmatch, pngjs, and wait-on to the devDependencies section.
  • scripts/ui-snapshots.js
    • This new script orchestrates the UI screenshot process. It uses Playwright to launch a Chromium browser, navigates to a predefined list of routes (configurable via ROUTES environment variable), and captures full-page screenshots.
    • It also contains logic to perform pixel-by-pixel comparisons between 'before' and 'after' screenshots using pixelmatch and pngjs, generating a diff image highlighting visual changes.
    • The script supports different execution modes: capturing screenshots or generating diffs, controlled by a --diff argument.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/pr-screenshots.yml
Activity
  • The author, kushal238, initiated this pull request to integrate a new UI screenshot workflow into the project's continuous integration process.
  • The changes involve setting up new development dependencies for Playwright, pixelmatch, and related utilities, as well as introducing a dedicated script to manage the screenshot and diff generation logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a UI screenshot testing workflow using Playwright and pixelmatch. The changes are well-structured, adding necessary dependencies and a script for capturing and comparing screenshots. I've identified a critical issue where an asynchronous function isn't awaited, which would cause the script to exit prematurely. Additionally, I've provided suggestions to improve performance by parallelizing screenshot capture and to enhance robustness by handling mismatched image dimensions during the diffing process.

Comment thread scripts/ui-snapshots.js Outdated
Comment on lines +57 to +61
if (process.argv.includes('--diff')) {
diffAll();
} else {
screenshotAll();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The screenshotAll function is async, but it is not being awaited. This will cause the script to exit before the asynchronous screenshot operations are complete, likely resulting in no screenshots being saved. You should wrap the main execution logic in an async IIFE (Immediately Invoked Function Expression) to properly await the screenshotAll function.

(async () => {
  if (process.argv.includes('--diff')) {
    diffAll();
  } else {
    await screenshotAll();
  }
})();

Comment thread scripts/ui-snapshots.js
Comment on lines +48 to +53
const img1 = PNG.sync.read(fs.readFileSync(beforePath));
const img2 = PNG.sync.read(fs.readFileSync(afterPath));
const { width, height } = img1;
const diff = new PNG({ width, height });
pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
fs.writeFileSync(path.join(diffDir, name), PNG.sync.write(diff));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

If the dimensions of the 'before' and 'after' images do not match, pixelmatch will throw an error, causing the script to crash. This can happen if a UI change affects the page height. It's better to handle this case gracefully by checking the dimensions. If they differ, you could log a warning and, for instance, copy the new image to the diff directory to make the size change apparent.

    const img1 = PNG.sync.read(fs.readFileSync(beforePath));
    const img2 = PNG.sync.read(fs.readFileSync(afterPath));
    const { width, height } = img1;

    if (width !== img2.width || height !== img2.height) {
      console.warn(`Image dimensions mismatch for ${name}. Copying new image to diff.`);
      fs.writeFileSync(path.join(diffDir, name), fs.readFileSync(afterPath));
    } else {
      const diff = new PNG({ width, height });
      pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
      fs.writeFileSync(path.join(diffDir, name), PNG.sync.write(diff));
    }

Comment thread scripts/ui-snapshots.js
Comment on lines +25 to +38
async function screenshotAll() {
ensureDir(outDir);
const browser = await chromium.launch();
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });

for (const route of routes) {
const url = baseUrl + route;
await page.goto(url, { waitUntil: 'networkidle' });
const file = path.join(outDir, `${slug(route)}.png`);
await page.screenshot({ path: file, fullPage: true });
}

await browser.close();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The screenshotAll function currently takes screenshots sequentially. For a larger number of routes, this could be inefficient. You can significantly improve performance by running these operations in parallel using Promise.all. Additionally, wrapping resource allocation in a try...finally block is a good practice to ensure resources like the browser instance are always cleaned up, even if errors occur.

async function screenshotAll() {
  ensureDir(outDir);
  const browser = await chromium.launch();
  try {
    const context = await browser.newContext({ viewport: { width: 1280, height: 720 } });

    await Promise.all(
      routes.map(async (route) => {
        const page = await context.newPage();
        try {
          const url = baseUrl + route;
          await page.goto(url, { waitUntil: 'networkidle' });
          const file = path.join(outDir, `${slug(route)}.png`);
          await page.screenshot({ path: file, fullPage: true });
        } finally {
          await page.close();
        }
      }),
    );
  } finally {
    await browser.close();
  }
}

- Supabase client: use placeholder URL during static generation so
  createClient doesn't throw "supabaseUrl is required" at build time
- ui-snapshots: await screenshotAll() via async IIFE so script doesn't
  exit before screenshots are saved
- ui-snapshots: guard against image dimension mismatch in pixelmatch
- ui-snapshots: wrap browser in try/finally for cleanup on errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kushal238

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a valuable UI screenshot testing workflow using Playwright. The implementation is clear and well-structured. My main feedback is to refactor the new snapshot script to use asynchronous file system operations consistently, which will improve its robustness and maintainability, especially in a CI environment. The changes to the Supabase client initialization are a good improvement for build-time compatibility.

Comment thread scripts/ui-snapshots.js
Comment on lines +42 to +72
function diffAll() {
ensureDir(diffDir);
for (const route of routes) {
const name = `${slug(route)}.png`;
const beforePath = path.join(beforeDir, name);
const afterPath = path.join(outDir, name);
if (!fs.existsSync(beforePath) || !fs.existsSync(afterPath)) continue;

const img1 = PNG.sync.read(fs.readFileSync(beforePath));
const img2 = PNG.sync.read(fs.readFileSync(afterPath));
const { width, height } = img1;

if (width !== img2.width || height !== img2.height) {
console.warn(`Image dimensions mismatch for ${name}. Copying new image to diff.`);
fs.writeFileSync(path.join(diffDir, name), fs.readFileSync(afterPath));
continue;
}

const diff = new PNG({ width, height });
pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
fs.writeFileSync(path.join(diffDir, name), PNG.sync.write(diff));
}
}

(async () => {
if (process.argv.includes('--diff')) {
diffAll();
} else {
await screenshotAll();
}
})();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The diffAll function uses synchronous file system operations, which is inconsistent with the async screenshotAll function. This can be refactored to use async/await with fs.promises for better consistency and to make the script non-blocking. Additionally, the main execution block should await the async diffAll call and include error handling to ensure the script exits correctly on failure, which is important for CI environments.

async function diffAll() {
  ensureDir(diffDir);
  for (const route of routes) {
    const name = `${slug(route)}.png`;
    const beforePath = path.join(beforeDir, name);
    const afterPath = path.join(outDir, name);

    let beforeFile, afterFile;
    try {
      beforeFile = await fs.promises.readFile(beforePath);
      afterFile = await fs.promises.readFile(afterPath);
    } catch {
      // Skip if either file doesn't exist.
      continue;
    }

    const img1 = PNG.sync.read(beforeFile);
    const img2 = PNG.sync.read(afterFile);
    const { width, height } = img1;

    if (width !== img2.width || height !== img2.height) {
      console.warn(`Image dimensions mismatch for ${name}. Copying new image to diff.`);
      await fs.promises.writeFile(path.join(diffDir, name), afterFile);
      continue;
    }

    const diff = new PNG({ width, height });
    pixelmatch(img1.data, img2.data, diff.data, width, height, { threshold: 0.1 });
    await fs.promises.writeFile(path.join(diffDir, name), PNG.sync.write(diff));
  }
}

(async () => {
  try {
    if (process.argv.includes('--diff')) {
      await diffAll();
    } else {
      await screenshotAll();
    }
  } catch (error) {
    console.error('An error occurred:', error);
    process.exit(1);
  }
})();

The base branch (main) doesn't have the Supabase client fallback,
so both build steps need dummy env vars to avoid the createClient
crash during static generation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Main branch doesn't have ui-snapshots.js or its dependencies
(pixelmatch, pngjs, playwright). Restructure workflow to checkout
PR first, install deps, save the script, then swap source to base
for "before" screenshots.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@kushal238
kushal238 merged commit 986a61a into main Feb 6, 2026
3 checks passed
@kushal238
kushal238 deleted the feat/pr-screenshots branch February 6, 2026 22:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant