<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nijil</title>
    <description>The latest articles on DEV Community by Nijil (@nijil71).</description>
    <link>https://dev.to/nijil71</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2740740%2F2adfaf76-6a43-4461-998f-1f9f0cfba32d.png</url>
      <title>DEV Community: Nijil</title>
      <link>https://dev.to/nijil71</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nijil71"/>
    <language>en</language>
    <item>
      <title>Building a Visual Regression Engine in Python with Playwright</title>
      <dc:creator>Nijil</dc:creator>
      <pubDate>Sun, 22 Feb 2026 08:24:01 +0000</pubDate>
      <link>https://dev.to/nijil71/building-a-visual-regression-engine-in-python-with-playwright-2117</link>
      <guid>https://dev.to/nijil71/building-a-visual-regression-engine-in-python-with-playwright-2117</guid>
      <description>&lt;p&gt;Modern frontend applications are complex, responsive, and constantly evolving. Small CSS or layout changes can introduce subtle UI regressions that are hard to detect during manual review.&lt;/p&gt;

&lt;p&gt;I wanted a deterministic way to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture responsive screenshots across breakpoints&lt;/li&gt;
&lt;li&gt;Compare changes against an approved baseline&lt;/li&gt;
&lt;li&gt;Fail CI if layout drift exceeds a threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I built &lt;strong&gt;PixelFrame&lt;/strong&gt; - a CLI-based visual regression engine powered by Python and Playwright.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Visual regressions are tricky because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;They may only appear on specific breakpoints&lt;/li&gt;
&lt;li&gt;They may not break functionality&lt;/li&gt;
&lt;li&gt;They are hard to catch in code review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most teams rely on manual checks or expensive SaaS tools.&lt;/p&gt;

&lt;p&gt;I wanted something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scriptable&lt;/li&gt;
&lt;li&gt;CI-friendly&lt;/li&gt;
&lt;li&gt;Deterministic&lt;/li&gt;
&lt;li&gt;Open-source&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;PixelFrame is built around a few core components:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Screenshot Engine
&lt;/h3&gt;

&lt;p&gt;Using Playwright's Chromium engine, PixelFrame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launches a headless browser&lt;/li&gt;
&lt;li&gt;Emulates multiple breakpoints or device presets&lt;/li&gt;
&lt;li&gt;Captures full-page screenshots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each run produces a structured directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pixelframe-output/
  ├── screenshots/
  ├── composite/
  ├── diff/
  └── report/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Visual Diff Engine
&lt;/h3&gt;

&lt;p&gt;To detect regressions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PixelFrame compares baseline vs current screenshots&lt;/li&gt;
&lt;li&gt;Calculates similarity percentage&lt;/li&gt;
&lt;li&gt;Generates red-highlighted diff overlays&lt;/li&gt;
&lt;li&gt;Returns exit code 1 if similarity drops below threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pixelframe diff run ./baseline ./current &lt;span class="nt"&gt;--fail-under&lt;/span&gt; 99.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This makes it CI-ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Structured Reporting
&lt;/h3&gt;

&lt;p&gt;Each run generates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-resolution PNGs&lt;/li&gt;
&lt;li&gt;Composite grid preview&lt;/li&gt;
&lt;li&gt;Self-contained HTML report&lt;/li&gt;
&lt;li&gt;Optional PDF export&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes regression review shareable and portable.&lt;/p&gt;

&lt;h2&gt;
  
  
  CI Integration
&lt;/h2&gt;

&lt;p&gt;One of the main goals was CI gating.&lt;/p&gt;

&lt;p&gt;In GitHub Actions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Visual Threshold Check&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;pixelframe diff run ./baseline ./current --fail-under 99.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If UI changes exceed the threshold, the workflow fails.&lt;/p&gt;

&lt;p&gt;This turns visual regression into a first-class quality gate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Device Emulation &amp;amp; Configuration
&lt;/h2&gt;

&lt;p&gt;PixelFrame supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual breakpoints&lt;/li&gt;
&lt;li&gt;Named device presets (iPhone, iPad, MacBook, 4K desktop)&lt;/li&gt;
&lt;li&gt;YAML configuration files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://example.com&lt;/span&gt;
&lt;span class="na"&gt;full_page&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;devices&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iPhone&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;15&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Pro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Max"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MacBook&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Pro&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;14"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This keeps regression suites version-controlled and reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building a CLI devtool taught me a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deterministic output structure matters&lt;/li&gt;
&lt;li&gt;CI-first design changes architecture decisions&lt;/li&gt;
&lt;li&gt;Exit codes are critical for automation&lt;/li&gt;
&lt;li&gt;Clear reporting dramatically improves usability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I initially underestimated baseline management. My first CI setup regenerated the baseline on every run, which completely defeated the purpose of regression testing. Fixing that forced me to rethink the workflow structure.&lt;/p&gt;

&lt;p&gt;PixelFrame currently works best with publicly accessible sites. Handling authenticated flows is something I plan to improve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Visual regression doesn't have to be complex or SaaS-dependent.&lt;/p&gt;

&lt;p&gt;With Python + Playwright, it's possible to build a reproducible, CI-integrated testing engine that keeps UI drift under control.&lt;/p&gt;

&lt;p&gt;PixelFrame is available on PyPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pixelframe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Source code:&lt;br&gt;
&lt;a href="https://github.com/nijil71/PixelFrame" rel="noopener noreferrer"&gt;https://github.com/nijil71/PixelFrame&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>playwright</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
