<?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: Anna Karnaukh</title>
    <description>The latest articles on DEV Community by Anna Karnaukh (@annakarnaukh).</description>
    <link>https://dev.to/annakarnaukh</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%2F3155684%2Fb747aa5e-d143-4dfa-8110-a15ec0f3dbc2.jpeg</url>
      <title>DEV Community: Anna Karnaukh</title>
      <link>https://dev.to/annakarnaukh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/annakarnaukh"/>
    <language>en</language>
    <item>
      <title>DOM Serialization for AI Testing: Why We Bet on Structure Over Screenshots</title>
      <dc:creator>Anna Karnaukh</dc:creator>
      <pubDate>Mon, 14 Jul 2025 20:05:01 +0000</pubDate>
      <link>https://dev.to/annakarnaukh/dom-serialization-for-ai-testing-why-we-bet-on-structure-over-screenshots-1egj</link>
      <guid>https://dev.to/annakarnaukh/dom-serialization-for-ai-testing-why-we-bet-on-structure-over-screenshots-1egj</guid>
      <description>&lt;p&gt;Modern LLMs offer powerful capabilities for test automation. But there’s a catch: they can only reason as well as the data you give them. And when it comes to testing the web, messy or noisy data leads directly to hallucinations, broken flows, and unusable test cases.&lt;/p&gt;

&lt;p&gt;As we described in the &lt;a href="https://dev.to/annakarnaukh/ai-powered-end-to-end-testing-part-1-37ed"&gt;1st&lt;/a&gt; and &lt;a href="https://dev.to/annakarnaukh/how-we-built-an-ai-powered-e2e-testing-tool-part-2-1l35"&gt;2nd&lt;/a&gt; articles about AI-powered end-to-end testing, we are working on the automated test case generation system that software engineers can rely on. That’s not about record&amp;amp;playback, but about the full-fledged generation of meaningful test cases. &lt;/p&gt;

&lt;p&gt;In the picture below, you can see a simplified scheme of how end-to-end testing works in Treegress. In this article, we describe the first block outlined on the scheme (DOM serialization)  - the basis that allows us to say that our system is reliable and has predicted stable results. This article explains why we chose the DOM serialization approach, what it took to get it right, and how it compares to alternatives like screenshots and visual models.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceh6gvaq5daqjwel8g5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fceh6gvaq5daqjwel8g5w.png" alt=" " width="800" height="846"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Business Value: Accuracy, Predictability, and Cost Reduction
&lt;/h2&gt;

&lt;p&gt;AI testing is only as good as the data that powers it. Visual-based systems that send screenshots or videos to multimodal models like GPT-4o quickly run into two serious business problems:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It’s expensive&lt;/strong&gt; — thousands of tokens per interaction.&lt;br&gt;
&lt;strong&gt;2. It’s unreliable&lt;/strong&gt; — visuals miss semantic context and lead to errors in test generation.&lt;br&gt;
&lt;strong&gt;3. It’s uncontrollable&lt;/strong&gt;  - raw screenshots don’t let us guide the model. LLMs rely on vague visual input and general pretraining, without structure or context. &lt;a href="https://arxiv.org/html/2411.07457v1" rel="noopener noreferrer"&gt;Studies&lt;/a&gt; show that well-structured prompts drastically reduce hallucinations and improve reliability, something raw images can’t offer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In other words:&lt;/strong&gt; the less structured and domain-aware your prompt is, the worse your outcome. Screenshots are the least structured prompt possible.&lt;br&gt;
A smarter alternative is DOM serialization. By feeding the model only the meaningful elements of the interface, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DOM serialization pipeline filters noise and embeds two decades of empirical knowledge about real-world frontend development&lt;/li&gt;
&lt;li&gt;Reduce costs by filtering irrelevant UI components&lt;/li&gt;
&lt;li&gt;Dramatically improve test accuracy&lt;/li&gt;
&lt;li&gt;Eliminate LLM hallucinations&lt;/li&gt;
&lt;li&gt;Build stable, repeatable test flows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is the comparison of DOM serialization and visual models approaches:&lt;br&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4io5b0k6y7ahcb5amhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy4io5b0k6y7ahcb5amhi.png" alt=" " width="800" height="360"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In short:&lt;/strong&gt; cleaner input, smarter output, better ROI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Journey: From Simple Snapshots to Precision DOM Maps
&lt;/h2&gt;

&lt;p&gt;Our first version of a DOM serializer was direct and naive, and by that, very stupid. It treated every visible element as potentially interactive, and we quickly ran into issues with noise: only ~3% of its output was actually useful. That meant 97% of the noise.&lt;br&gt;
We tried other tools like Browser Use, which performed much better (about half of the usable data), but still fell short. Complex CRMs, layered modals, and custom dropdowns broke every generic solution we tested.&lt;/p&gt;

&lt;p&gt;So we built our own.&lt;/p&gt;

&lt;p&gt;We applied our 20+ years of frontend experience and created a new kind of DOM serializer. One that doesn't just parse HTML, but deeply understands what’s actionable on the screen.&lt;/p&gt;

&lt;h2&gt;
  
  
  Our Approach: Building a DOM That Makes Sense
&lt;/h2&gt;

&lt;p&gt;We don’t just serialize the DOM. We reconstruct a representation of the UI that includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Interactive elements, detected by 18 empirical rules&lt;/strong&gt; (rules that we have collected over 20 years of front-end technology development),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Analysis of 20+ JS frameworks&lt;/strong&gt; (React, Angular, Vue, Hotwire, etc.),&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Semantic and technical specifics&lt;/strong&gt;, such as label + input, data-attributes, class naming + semantic markup, accessibility trees, and shadow DOM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSS and z-index layers&lt;/strong&gt;, to determine what’s visible and actionable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DOM mutation tracking&lt;/strong&gt;, to account for JavaScript hydration and dynamic components.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We filter out invisible or irrelevant layers, group related components (like dropdowns or calendars), and assign roles to elements (button, input, datepicker, etc.).&lt;/p&gt;

&lt;p&gt;This gives us a high-resolution map of the screen that an LLM can understand. No hallucinations, no fake buttons, no broken flows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use Visual Models?
&lt;/h2&gt;

&lt;p&gt;Visual models sound great in theory. They "see" the screen like a human. But in practice, they’re expensive and incomplete.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;High cost:&lt;/strong&gt; every screenshot costs thousands of tokens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lack of semantic insight:&lt;/strong&gt; visuals can’t detect div vs. button vs. custom components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poor generalization:&lt;/strong&gt; visual models often fail on non-standard layouts or layered modals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DOM serialization gives us control. We decide what goes into the model. We reduce the noise. And we build a flow that can be reproduced and debugged.&lt;/p&gt;

&lt;p&gt;The only category of UI we can't support is canvas-based rendering (like Flutter for Web), which lacks a DOM entirely. Even then, we’re planning to train a model for visual fallback. But that comes later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Result: 95% Precision, Scalable AI Testing
&lt;/h2&gt;

&lt;p&gt;Our current serializer identifies ~95% of real interactive elements across a diverse dataset of old, modern, and complex web apps.&lt;/p&gt;

&lt;p&gt;That includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modern React/Angular/Vue apps,&lt;/li&gt;
&lt;li&gt;legacy systems,&lt;/li&gt;
&lt;li&gt;CRM platforms with complex flows,&lt;/li&gt;
&lt;li&gt;Shadow DOM-heavy interfaces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With this foundation, we can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate reliable test cases using LLMs and a rule-based approach developed by professional QA engineers&lt;/li&gt;
&lt;li&gt;Run tests without LLMs (for predictability and cost)&lt;/li&gt;
&lt;li&gt;Use self-healing to recover from failures&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Future: Visual Models on Our Terms
&lt;/h2&gt;

&lt;p&gt;Once we lock in our primary flow, our next step is training a visual model tailored to the DOM maps we’ve built. Because our structure includes visual anchors, we can mark up thousands of real sites, create a dataset, and train a lightweight object detector.&lt;/p&gt;

&lt;p&gt;This model will be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ultra-fast,&lt;/li&gt;
&lt;li&gt;cost-efficient,&lt;/li&gt;
&lt;li&gt;and tuned to real-world UI patterns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s how we combine the best of both worlds: structure and vision. But it all starts with one thing done well: clean, smart DOM serialization.&lt;/p&gt;

&lt;p&gt;DOM serialization isn’t just cheaper than visual models — it’s better. It gives AI models the right data to make the right decisions, prevents hallucinations, and creates predictable, scalable automation. At &lt;a href="https://www.treegress.com/" rel="noopener noreferrer"&gt;Treegress&lt;/a&gt;, we’ve invested heavily in getting this right, and we believe it’s the key to making AI-powered testing truly production-ready.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>How We Built an AI-Powered E2E Testing Tool (Part 2)</title>
      <dc:creator>Anna Karnaukh</dc:creator>
      <pubDate>Fri, 04 Jul 2025 15:33:29 +0000</pubDate>
      <link>https://dev.to/annakarnaukh/how-we-built-an-ai-powered-e2e-testing-tool-part-2-1l35</link>
      <guid>https://dev.to/annakarnaukh/how-we-built-an-ai-powered-e2e-testing-tool-part-2-1l35</guid>
      <description>&lt;p&gt;We’re still on a mission to build a true QA autopilot - one that needs only a URL to get going. In &lt;a href="https://dev.to/annakarnaukh/ai-powered-end-to-end-testing-part-1-37ed"&gt;Part 1&lt;/a&gt;, we covered our shift from vision-based RL to self-hosted LLMs and all the tweaks that made it reliable. Now in Part 2, we’re scaling up: testing OpenAI and Gemini APIs, plugging in RAG to keep tests focused, and evolving our multi-agent flow. Let’s jump in!&lt;/p&gt;

&lt;h2&gt;
  
  
  1. API Era: OpenAI &amp;amp; Gemini
&lt;/h2&gt;

&lt;p&gt;By early 2025, self-hosting small LLMs couldn’t meet scale or latency needs. We prototyped:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OpenAI GPT-3.5/4 APIs&lt;/strong&gt;: Good quality, big context windows, ~0.5–1 s per call. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Google Gemini API&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Latency: ~0.8 s per 1k tokens.&lt;/li&gt;
&lt;li&gt;Quality: Superior in complex navigation and multi-step wizards.&lt;/li&gt;
&lt;li&gt;Cost: Comparable to GPT-3.5, with more generous quotas.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;We decided to continue experimenting with Gemini.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Gemini?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bypassed local VRAM constraints.&lt;/li&gt;
&lt;li&gt;Handled thousands of tokens (full-page JSON) without major trimming.&lt;/li&gt;
&lt;li&gt;Scaled horizontally: we spun up multiple API workers in Docker Swarm.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That enabled us to test:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Multi-step Forms.&lt;/strong&gt; “Part 1 on Step 1 → Click Next → Part 2 on Step 2 → Submit” flows worked reliably, since Gemini tracked state across contexts.&lt;br&gt;
&lt;strong&gt;2. CRM CRUD Workflows.&lt;/strong&gt; Complex user flows - like “Create record → Edit → Delete” - across nested tables and modals can now be executed in a single prompt. This is a crucial step forward: for example, to properly test the delete functionality for a "Lead" in a CRM, you first need to create a Lead, then delete it, ensuring you're not interfering with any existing data. Treegress now makes this kind of isolated, end-to-end scenario possible with one command.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Introducing RAG for Larger Pages
&lt;/h2&gt;

&lt;p&gt;We understand that generating a large number of test cases is pointless if they aren’t clear and meaningful. That’s why we rely on our team’s deep experience in software engineering and subject matter expertise in QA to ensure every test adds real value. So we built a basic RAG pipeline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;QA-Curated Test Templates.&lt;/strong&gt; QA engineers wrote generic test-case templates for common page types (Login, Contact Us, CRUD lists). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retrieval at Runtime.&lt;/strong&gt; When our system knows it’s on a Contact Us page, it pulls that template from our document store.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM calls → Tests.&lt;/strong&gt; Because we lock onto QA templates, hallucinations drop, and coverage improves.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Result:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Predictability.&lt;/li&gt;
&lt;li&gt;No fluff—just real, meaningful test cases.&lt;/li&gt;
&lt;li&gt;Easier to cover edge cases without relying solely on LLM's creative power.&lt;/li&gt;
&lt;li&gt;Templates + full-page JSON give LLM enough context to customize tests per page.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi4gpvoomzuoqbp7315x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqi4gpvoomzuoqbp7315x.png" alt="Image description" width="728" height="1059"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Multistep support, work with intentions&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. What Failed &amp;amp; Why
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vision+Text RL Models:&lt;/strong&gt; Massive and slow.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flat List JSON:&lt;/strong&gt; Lost parent/child relations—poor element detection.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fix: Switch to nested JSON reflecting the true DOM tree.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Local VLLM + Large Models:&lt;/strong&gt; OOMs, crashes, high latency.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lesson: If you need low latency and high concurrency, self-hosting big models can be a dead end—APIs often win.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;We set out to build an AI agent that “sees” a page, “understands” its purpose, and “generates” or “validates” test cases with minimal manual work. Our path led us from massive vision+RL efforts to lean JSON+LLM setups, and ultimately to multi-agent architecture, a hybrid of self-hosted LLMs, external APIs, and RAG. Each dead end taught us something crucial: raw vision is heavy (but still can be helpful), flat JSON loses context, and local LLM hosting runs into VRAM walls.&lt;/p&gt;

&lt;p&gt;Today, our production flow looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Website Crawler &amp;amp; Analyzer&lt;/strong&gt; – Parses and serializes each page into nested JSON. Thanks to our unique DOM handling, it includes a built-in self-healing engine (a topic we’ll cover in a separate article).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent for Test Case Generation&lt;/strong&gt; – Uses a RAG layer to fetch QA templates from a file store based on the page type. These are combined into a prompt and sent to the Gemini API, which returns structured test cases or direct verdicts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI Agent for Test Case Verification&lt;/strong&gt; – Reviews the generated test cases for accuracy and relevance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend Services with Playwright&lt;/strong&gt; – Executes the tests against the live environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s a long journey - not just to generate test cases, but to build a platform that truly understands your website, generates meaningful, context-aware test cases, and ensures reliability with predictable outcomes. But let us be clear: we didn’t set out to build a Copilot for QA automation — we’re building an Autopilot for testing.&lt;/p&gt;

&lt;p&gt;If you’d like to be part of that journey, we’d love for you to try &lt;a href="https://www.treegress.com/" rel="noopener noreferrer"&gt;Treegress&lt;/a&gt; and share your thoughts.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>testing</category>
      <category>automaton</category>
    </item>
    <item>
      <title>AI Testing Isn’t Magic — How We Built an AI-Powered E2E Testing Tool (Part 1)</title>
      <dc:creator>Anna Karnaukh</dc:creator>
      <pubDate>Tue, 17 Jun 2025 21:22:35 +0000</pubDate>
      <link>https://dev.to/annakarnaukh/ai-powered-end-to-end-testing-part-1-37ed</link>
      <guid>https://dev.to/annakarnaukh/ai-powered-end-to-end-testing-part-1-37ed</guid>
      <description>&lt;p&gt;We set out to build a system that could generate and run end-to-end tests automatically with predictable results and minimal input from the user. No recorded flows, no screenshots, no manual setup, just a URL. The system scans the page, builds a structured view using a serialized DOM, and generates test cases based on what it finds.&lt;br&gt;
In this article, we’re openly sharing the engineering side of that journey - what we tried, what didn’t work, and how the system evolved. We started with reinforcement learning, moved on to transformer-based models, and eventually landed on a mix of self-hosted LLMs, external APIs, and RAG patterns. And yes, we ended up building a multi-agent system - though not because it’s trendy, but because the architecture naturally led us there.&lt;br&gt;
If you're curious about integrating AI into test automation or just curious how this kind of system comes together, read on.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Early Experiments: Reinforcement Learning on Visual Inputs
&lt;/h2&gt;

&lt;p&gt;We initially approached UI testing as a reinforcement learning (RL) problem. The idea was to train an agent to “see” a rendered web page: locate input fields and buttons based on screenshots, and act as a human user would. We fed screenshots into an RL model so it could learn where fields and click targets were. After a few iterations into the RL research, we began building a visual dataset and training a multimodal model that combined raw HTML (as text) with corresponding screenshots. The hope was that the model would use HTML structure and pixel data together to detect interactive elements. In practice, this setup was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Huge (15 GB+ models). The RL we experimented with quickly ballooned in size.&lt;/li&gt;
&lt;li&gt;Slow to train/infer. Every RL step required rendering a screenshot, feeding it into a massive vision+text network, and running a full forward pass—way too expensive for anything like CI.&lt;/li&gt;
&lt;li&gt;Unstable. We never got to a “production-ready” state. After a couple of weeks of labeling a few hundred sites for object detection (e.g., headers, footers, forms), we hit dead ends when generalizing to arbitrary pages.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That effort taught us two things: purely vision-based RL for E2E testing was “prehistoric” in our context (we had no real stake in it anymore), and we needed lighter, text-centric approaches to move forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Pivot to Text-First Models &amp;amp; NLP Tasks
&lt;/h2&gt;

&lt;p&gt;After experimenting with RL-based visual parsing, we shifted to an NLP-based approach, starting with a few simple language tasks:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Language Detection.&lt;/strong&gt; We needed to know what language a page’s text was in. We pulled in a few proven, lightweight language detection models - self-hosted, &amp;lt; 100 MB each, MIT/Apache-licensed - since we try to keep things cost-effective and avoid paying for 3d-party services when possible.&lt;br&gt;
&lt;strong&gt;2. Page Summarization.&lt;/strong&gt; We wanted a quick way to classify a page’s purpose. For example, if we click “Contact Us” and the model summarizes “This is the Contact Us page,” we know the navigation worked. If it summarizes “Login,” that signals a bug.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgjv3zv75xqmfxp6ux1g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpgjv3zv75xqmfxp6ux1g.png" alt="Image description" width="800" height="462"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Example of page summarization&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;At first, we tried specialized summarization models available online. Every one of them was 1.5 GB in general—way too large, outdated, and underwhelming in quality. Then we discovered that the smallest LLMs (1–2 GB) often outperformed older summarizers.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Self-Hosted LLM Era: Finding the Right Model
&lt;/h2&gt;

&lt;p&gt;We hunted on Hugging Face for small, open-source transformer models with MIT or Apache licenses to enable cost-effective summarization, navigation testing, and eventually reuse the same model for test case generation. Here’s what we tried:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Microsoft PHI (1.5 GB).&lt;/strong&gt; The model didn’t meet our quality bar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QWEN Series (Alibaba).&lt;/strong&gt; They offer a tiered lineup: 0.5 B, 1.5 B, 3 B, 7 B parameters. The 1.5 B “QWEN-tiny” was our first real winner - weights could fit on a single 16 GB GPU, inference latency was reasonable, and summarization quality was solid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How we used it:&lt;/strong&gt; We ran the QWEN-1.5B model in Docker and used our DOM serializer for E2E flows. When navigating (e.g., “Click Contact Us”), the serializer generated a clean, structured JSON of the page. For summarization, we sent only the visible text—not the full JSON - to the LLM. If the summary matched our expectations, the step passed; if not, we flagged a bug.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why JSON over raw HTML?&lt;/strong&gt; Raw HTML includes a lot of unnecessary data like styles and classes, which wastes tokens. Our serializer kept only the useful parts—tags, types, key attributes - and reduced token usage by over 50%. We later added a nested structure so the model could better understand the page layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. From Summaries to Element-Finding &amp;amp; Test Case Generation and Validation
&lt;/h2&gt;

&lt;p&gt;Once summarization was reliable, we shifted to the next core tasks:&lt;br&gt;
&lt;strong&gt;1. Test Case Suggestions.&lt;/strong&gt; The system generates test cases using only a URL as input  - no need for users to record and play back actions, provide documentation, or upload baseline screenshots. It has to understand the page, identify fields and forms, figure out how to interact with them, and generate meaningful test cases with concrete steps from there.&lt;/p&gt;

&lt;p&gt;Imagine it like asking LLM: “Given these elements, generate basic E2E test cases for login.” It produced 4-5 JSON-formatted test scenarios (valid login, invalid credentials, missing fields, etc.).&lt;br&gt;
It’s important to highlight that element discovery played a crucial role in generating test case suggestions. We have to prompt LLM with the JSON DOM and ask it to list form fields/buttons related to login. It returned a JSON array of elements (e.g., “email,” “password,” “submit button”).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Test Execution.&lt;/strong&gt; The system runs the generated test cases, validates the results against the expected assertions, and provides a clear conclusion to the user.&lt;/p&gt;

&lt;p&gt;For these tasks, we needed a reliable interaction between the back-end and the LLM - a proper validation loop. It’s very much a “ping-pong” process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backend (Node.js + Playwright) sends the DOM to LLM.&lt;/li&gt;
&lt;li&gt;LLM returns test cases → Playwright executes them → grabs new DOM.&lt;/li&gt;
&lt;li&gt;LLM compares old vs. new DOM summary → returns pass/fail.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A simplified version of the process is shown in the diagram below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmbjklpituhk3j7k8b98.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmmbjklpituhk3j7k8b98.png" alt="Image description" width="728" height="1059"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We set temperature=0 to force determinism, so LLM produced the same output for identical inputs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Technical Hurdles &amp;amp; Micro-Optimizations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Context Window Overflows:&lt;/strong&gt; Pages with a large number of elements and a complex layout still overflowed LLM’s token limit. Also, the question is that every Front-End developer codes in their own way, and there is no strict typification as the back-end developers have. And here we're not even talking about complex pages with tables, charts, pagination, or filters - sometimes we hit the token limit even with simple login pages.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DOM Splitting:&lt;/strong&gt; We split the serialized JSON into blocks (header, content, footer), summarized each, and merged the results—partial win. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ID Compression:&lt;/strong&gt; Replaced long UUIDs with small integers before sending to LLM, then remapped after inference. Saved ~30 % of tokens when element counts were high.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Memory &amp;amp; Latency:&lt;/strong&gt; To keep the platform cost-effective, we opted to use a self-hosted model running in our internal cluster, without relying on external APIs.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;QWEN-1.5 B (~3 GB VRAM):&lt;/strong&gt; quality was limited. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QWEN-3 B (8 GB):&lt;/strong&gt; improved output, but came with a “Research” license, which we couldn’t use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;QWEN-7 B (14 GB):&lt;/strong&gt; required quantization to work with the vLLM engine to fit our memory space.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To improve speed and reduce memory usage, we switched from Hugging Face’s Python runner to vLLM, which supports attention caching. Even with vLLM, the full 7B model occasionally ran out of memory, so in the end, we ran a quantized version of QWEN-7B on vLLM. This reduced VRAM usage, improved inference speed by about 30%, and delivered acceptable accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent Architecture (Ping-Pong):&lt;/strong&gt; The challenge was stability: a single user could have dozens of projects, each with hundreds of pages. The system had to handle that volume while keeping the “ping-pong” flow intact. As described earlier, the LLM received a serialized DOM, identified elements, and generated actions. The back end executed them using Playwright, captured the updated page, and returned it for validation. But REST calls became unstable under load, and vLLM didn’t work well with uWSGI, so we switched to a Redis-based queue for more reliable communication. Despite these tweaks, throughput remained up to 50s per test generation or validation - too slow for large suites. Multi-user CI would require multiple GPU instances, which wasn’t cost-effective.&lt;/p&gt;

&lt;p&gt;So the next step was to:&lt;br&gt;
 – optimize the input sent to the LLM,&lt;br&gt;
 – review the entire flow,&lt;br&gt;
 – and consider switching to external APIs like OpenAI or Gemini.&lt;/p&gt;

&lt;p&gt;That’s exactly what we’ll cover in the next article - stay tuned!&lt;br&gt;
Give it a spin: paste any URL into &lt;a href="https://www.treegress.com/" rel="noopener noreferrer"&gt;Treegress&lt;/a&gt; and see how it works in action.  &lt;/p&gt;

</description>
      <category>ai</category>
      <category>softwaredevelopment</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>How We Built UI Bug Detection from Scratch: What Worked and What Didn't</title>
      <dc:creator>Anna Karnaukh</dc:creator>
      <pubDate>Thu, 22 May 2025 14:13:29 +0000</pubDate>
      <link>https://dev.to/annakarnaukh/how-we-built-ui-bug-detection-from-scratch-what-worked-and-what-didnt-ll6</link>
      <guid>https://dev.to/annakarnaukh/how-we-built-ui-bug-detection-from-scratch-what-worked-and-what-didnt-ll6</guid>
      <description>&lt;p&gt;When we first started planning our own test automation product, our core goal was full end-to-end testing — a system that could test any website automatically, with minimal manual setup. Ideally, it would be as simple as providing a link and letting the system handle the rest. To move faster, we decided to start with what looked like low-hanging fruit: UI bug detection.&lt;/p&gt;

&lt;p&gt;It sounded simple enough. But once we got into it, we realized just how tricky it was. We explored multiple approaches, ran into licensing and model limitations, spent weeks generating datasets, and rebuilt parts of the system more than once.&lt;/p&gt;

&lt;p&gt;This is a step-by-step look behind the scenes at how we designed and developed our UI bug detection system — and what we learned along the way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges on Our Path to UI Bug Detection
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Designing the System Architecture
&lt;/h3&gt;

&lt;p&gt;From the beginning, we aimed to keep the architecture lightweight — a modular system made of small, simple functional pieces. Cloud API showed potential, but it was too expensive for production use. So, the next logical step was to train an object detection model on our custom dataset to reduce costs while keeping performance.&lt;/p&gt;

&lt;h3&gt;
  
  
  Licensing: The Roadblock We Didn’t Expect
&lt;/h3&gt;

&lt;p&gt;We chose YOLO as our base model for detecting visual bugs. It was fast, well-documented, and great for object detection tasks — exactly what we needed.&lt;/p&gt;

&lt;p&gt;At first, we focused on YOLO NAS, since it was one of the few variants with a business-friendly license - Apache. Everything looked good, so we integrated it into our pipeline and started working with the pre-trained model provided.&lt;/p&gt;

&lt;p&gt;Later, when we took a closer look at the full licensing terms, things got tricky.&lt;/p&gt;

&lt;p&gt;While the core YOLO NAS framework had a permissive license, the pre-trained model weights were licensed differently. According to the terms, using them in a product required us to open-source our own code — something we clearly couldn’t do.&lt;/p&gt;

&lt;p&gt;This wasn’t obvious at the start, and it wasn’t mentioned front and center. But once we read the fine print in the documentation, the problem became clear: we couldn’t legally use those pretrained models in a commercial product.&lt;/p&gt;

&lt;p&gt;So we had to change direction. We retrained the model from scratch using only our own data and infrastructure — no third-party weights involved. We thought that switching from a pretrained model to re-trained from scratch by us would decrease the quality of the output, BUT the results were even a bit better&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; When working with any open-source model, check every layer — not just the framework, but the weights, datasets, and any dependencies. Licensing issues can sneak in where you least expect them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Building the Dataset from Scratch
&lt;/h3&gt;

&lt;p&gt;Once we decided to train the model on our own dataset, we faced the hardest part — creating a dataset from scratch, because there was no ready-to-use data due to the type of UI bugs we were planning to detect.&lt;/p&gt;

&lt;p&gt;At first, we built our own crawler. It could automatically browse websites, inject scripts, modify elements, and generate labeled screenshots. We even made sure it could pick up where it left off if something crashed, which happened a lot. Still, the entire process was slow and fragile.&lt;/p&gt;

&lt;p&gt;Second, we thought it would be simple: just break some styles, take screenshots, and start training. But of course, it turned out to be much more complex.&lt;/p&gt;

&lt;p&gt;We wrote scripts that used JavaScript and Selenium to manipulate live websites. We disabled images, shifted elements so they overlapped, and tweaked layouts in weird ways. After that, we captured screenshots and recorded the exact coordinates of each visual change. That gave us the raw materials for training, but it was painfully slow. And also, there were many errors - we tried to randomly modify the web page with JS, but markdown mutated differently. &lt;/p&gt;

&lt;p&gt;Each sample took about three seconds to generate. And we needed thousands. Tens of thousands. The more we tried to scale it, the more we realized: this was going to eat up time, memory, and patience.&lt;br&gt;
Still, the entire process was slow and fragile.&lt;/p&gt;

&lt;h3&gt;
  
  
  Synthetic Data Generation
&lt;/h3&gt;

&lt;p&gt;Eventually, we hit a wall. Scraping real websites and breaking them on the fly was too slow and inconsistent, because while using real screenshots, we had to filter appropriate sets manually. That’s when we added synthetic data.&lt;/p&gt;

&lt;p&gt;In addition to using existing websites, we began creating simple UI layouts on canvas from scratch. We manually placed overlapping texts, “broke” images by overlaying error graphics, and created fake popups with randomized elements. We started simulating UI bugs ourselves, in a fully controlled environment.&lt;/p&gt;

&lt;p&gt;With synthetic data, we didn’t have to worry about waiting for page loads, dealing with broken links, or handling unpredictable website structures. We could generate examples quickly, with the exact bugs we wanted, and in the right format for training.&lt;/p&gt;

&lt;p&gt;It wasn’t just faster — it was also cleaner. The model got better training inputs, and we spent way less time cleaning up bad screenshots or fixing crawler bugs.&lt;br&gt;
The issue was that synthetic data only covered a limited range of UI distortions - about 60%. Additionally, the dataset elements were too similar to each other — we needed more variety.&lt;/p&gt;

&lt;p&gt;From then on, we used a mixed approach: part real websites, part modified sites, and part synthetic data. And that’s when we finally started making real progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making the Model Smarter About UI Bugs
&lt;/h2&gt;

&lt;p&gt;As the project developed, our definition of a "UI bug" naturally expanded. We started with the most visible problems — unreadable text, overlapping elements, and broken layouts. But soon we realized that there were many other subtle yet impactful issues worth catching.&lt;/p&gt;

&lt;p&gt;Things like inconsistent letter spacing, unnecessary scrollbars caused by layout shifts, or mismatched font sizes across components began to surface as meaningful categories. Popups — such as cookie banners and modal dialogs — also became part of our scope, since they often interfere with user interaction.&lt;br&gt;
To detect these, we generated custom synthetic data. We built simplified UI layouts, layered elements in different ways, and added visual details like shadows to mimic real-world styles. This gave the model a wide variety of examples to learn from.&lt;br&gt;
We also recognized that not all bugs need bounding boxes. Some problems, like missing content or font inconsistencies, affect the entire page rather than a specific area. These worked better as image classification tasks, assigning a single label to the whole screenshot.&lt;/p&gt;

&lt;p&gt;In the end, we built a two-track system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object detection&lt;/strong&gt; for localized, visual issues like overlapping elements or broken images;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page-level classification&lt;/strong&gt; for broader layout or content problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This combined approach gave us more flexibility and accuracy. It allowed us to match the right detection method to each bug type, which turned out to be crucial for building something reliable. So, the final list of UI bugs looks as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Broken Image&lt;/li&gt;
&lt;li&gt;Missing Content&lt;/li&gt;
&lt;li&gt;Unnecessary Scroll&lt;/li&gt;
&lt;li&gt;Letter spacing issue&lt;/li&gt;
&lt;li&gt;Inconsisent font size&lt;/li&gt;
&lt;li&gt;Outdated style&lt;/li&gt;
&lt;li&gt;Inconsistent color scheme&lt;/li&gt;
&lt;li&gt;Empty layout&lt;/li&gt;
&lt;li&gt;Broken layout&lt;/li&gt;
&lt;li&gt;Overlapping content&lt;/li&gt;
&lt;li&gt;Unnecessary horizontal scroll&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Data is the real challenge&lt;/strong&gt; — Model training is easy compared to generating a diverse, high-quality dataset. Most of our time went into building and refining the data pipeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Licensing matters more than you think&lt;/strong&gt; — Even with open-source tools, you can run into restrictions. Always check licenses for models, weights, and datasets before integrating them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Synthetic + real = best results&lt;/strong&gt; — A mix of real websites, synthetic layouts, and manual edge cases gave us the most reliable coverage and flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;UI bug detection isn’t just one feature&lt;/strong&gt; — it’s a system. Without the right data, even the best model won’t help.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Curious how this all turned out?&lt;/strong&gt;&lt;br&gt;
We’re turning these ideas into a real tool at &lt;a href="https://www.treegress.com/?utm_source=dev.to&amp;amp;utm_medium=article&amp;amp;utm_campaign=lead%20generation&amp;amp;utm_content=UI%20Bug%20Detection"&gt;Treegress&lt;/a&gt; — a no-code platform for end-to-end testing with built-in visual bug detection.&lt;/p&gt;

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