<?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: Shidesheng </title>
    <description>The latest articles on DEV Community by Shidesheng  (@shidesheng).</description>
    <link>https://dev.to/shidesheng</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4075532%2F9423e0be-ae64-4c39-9e6b-50cf5cc0c667.png</url>
      <title>DEV Community: Shidesheng </title>
      <link>https://dev.to/shidesheng</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shidesheng"/>
    <language>en</language>
    <item>
      <title>Building a Visual Regression Tool with VLMs and DOM Diffing</title>
      <dc:creator>Shidesheng </dc:creator>
      <pubDate>Thu, 13 Aug 2026 04:33:49 +0000</pubDate>
      <link>https://dev.to/shidesheng/building-a-visual-regression-tool-with-vlms-and-dom-diffing-1j4m</link>
      <guid>https://dev.to/shidesheng/building-a-visual-regression-tool-with-vlms-and-dom-diffing-1j4m</guid>
      <description>

&lt;p&gt;Hey Dev.to! I built a tool that combines DOM diffing with vision-language models (VLMs) for UI regression testing. Here's why and how.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Current Visual Regression Tools
&lt;/h2&gt;

&lt;p&gt;Ever used Percy, Chromatic, or Applitools? They catch real UI changes, but also flag &lt;strong&gt;tons of false positives&lt;/strong&gt; from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔴 Anti-aliasing noise&lt;/li&gt;
&lt;li&gt;🔴 Font rendering jitter&lt;/li&gt;
&lt;li&gt;🔴 Input focus rings&lt;/li&gt;
&lt;li&gt;🔴 Dynamic timestamps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates "alert fatigue" — developers start ignoring reports because 80% are false positives.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Key Insight
&lt;/h2&gt;

&lt;p&gt;UI screenshots have a &lt;strong&gt;unique advantage&lt;/strong&gt; over generic images: &lt;strong&gt;DOM structure as ground truth&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If the DOM hasn't changed, pixel differences are just rendering noise, not semantic changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Solution: VLM-Diff
&lt;/h2&gt;

&lt;p&gt;A two-stage hybrid pipeline:&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Deterministic Detection (No VLM needed)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
typescript
// Pseudo-code
const domChanges = compareDom(beforeDom, afterDom);
const pixelRegions = perceptualDiff(beforeImg, afterImg);

if (domChanges.length === 0) {
  return { changed: false };  // Suppress pixel noise!
}

// Otherwise, fuse DOM + pixel regions
const candidates = fuseDomAndPixelRegions(domChanges, pixelRegions);
Stage 2: VLM Classification (Only for changed regions)
for (const region of candidates) {
  const beforeCrop = cropImage(beforeImg, region);
  const afterCrop = cropImage(afterImg, region);

  const result = await vlm.classify({
    images: [beforeCrop, afterCrop],
    prompt: "What changed? color/text/position/size/other?"
  });

  console.log(result.changeType, result.description);
}
Why cropping? VLM-SubtleBench (March 2026) showed that side-by-side full images hurt accuracy in 9/10 categories. Cropping focuses the model's attention and reduces token cost by 68%.

Results (39 UI Screenshot Pairs)
Metric  Naive VLM   Pixel-Only  VLM-Diff
Recall  70-75%  92% 92% ✅
Precision   30-40%  85-90%  88-92%
False Positive Rate 15-25%  0%  0% ✅
Classification Accuracy 55-65%  N/A 75-82%
Avg Input Tokens    ~2500   0   ~800
✅ = Confirmed on real data (deterministic layer)
Others = Predicted based on VLM-SubtleBench baselines

Quick Start
git clone https://github.com/shidesheng0218/vlm-diff
cd vlm-diff
npm install
npm run demo:quick  # 2-minute demo, no API key needed
This runs a simulated demo showing how DOM diff detects changes and suppresses false positives.

Tech Stack
TypeScript + Playwright: Screenshot capture + DOM serialization
pixelmatch: Perceptual pixel diffing
Claude Opus 4 / GPT-4o: VLM classification
Architecture Diagram
Input: before.png + after.png + DOM snapshots
         ↓
┌─────────────────────────────────┐
│ Stage 1: Deterministic Detection│
│  • DOM diff (structure changes) │
│  • Pixel diff (visual changes)  │
│  • If DOM unchanged → return NO │
│  • Otherwise → fuse regions     │
└─────────────────────────────────┘
         ↓
┌─────────────────────────────────┐
│ Stage 2: VLM Classification     │
│  • Crop changed regions         │
│  • Send to Claude/GPT           │
│  • Get change type + description│
└─────────────────────────────────┘
         ↓
Output: {
  changed: true,
  regions: [
    {
      rect: {x, y, width, height},
      changeType: "color-change",
      description: "Button changed from blue to red",
      confidence: 0.92
    }
  ]
}
What's Next?
I'm deciding between two paths:

Productize: Build CLI + CI integration (GitHub Action)
Academic: Expand dataset, publish paper, submit to workshop
Questions for the community:

Would you use this in production?
What features are missing?
Anyone want to compare with Percy/Chromatic on real projects?
Open Source
GitHub: https://github.com/shidesheng0218/vlm-diff
License: MIT
Paper draft included (4 pages, VLM-SubtleBench format)
Happy to answer questions! What would make this useful for your projects? 🚀

Related Work
This builds on recent research:

VLM-SubtleBench (March 2026): GPT-5-thinking only achieves 77.8% on subtle visual differences
OmniDiff: Fine-tuned specialists outperform zero-shot VLMs by 6×
WUICC-bench (July 2026): First UI regression benchmark
The innovation here is using DOM structure as ground truth to suppress false positives — a domain-specific advantage that generic image-diff tools can't leverage.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>testing</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
