<?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: INTFRAME</title>
    <description>The latest articles on DEV Community by INTFRAME (@intframe).</description>
    <link>https://dev.to/intframe</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%2F4078029%2F1c9fc8fe-bfa8-4837-be9f-bb879d1f7064.png</url>
      <title>DEV Community: INTFRAME</title>
      <link>https://dev.to/intframe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/intframe"/>
    <language>en</language>
    <item>
      <title>Design QA at 1:1</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Fri, 14 Aug 2026 18:15:44 +0000</pubDate>
      <link>https://dev.to/intframe/design-qa-at-11-12ba</link>
      <guid>https://dev.to/intframe/design-qa-at-11-12ba</guid>
      <description>&lt;p&gt;Two screens can look identical at a glance and be twenty percent apart in every measurement that matters. Font size, line height, spacing rhythm, contrast. The glance is the least reliable instrument in the building, and for months it was the only one we used.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measure the original, not your memory of it
&lt;/h2&gt;

&lt;p&gt;When the brief is "make it feel like this reference", the first step is no longer a mood board. It is a probe run in both pages' consoles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;probe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getComputedStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;sel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;letterSpacing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;color&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;  &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;p&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;nav a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;probe&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it on the reference, run it on ours, diff the two tables. Design conversations change completely when both sides are looking at numbers. "It feels lighter" becomes "their h1 is 88/0.94 at weight 900, ours is 72/1.1 at 800", which is a ticket, not a debate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compare crops, not impressions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Crop the same region from ours and theirs, stack them at 1:1, and look at the pair. Differences that vanish in side-by-side thumbnails become obvious.&lt;/li&gt;
&lt;li&gt;Do it on mobile first. Desktop hides crimes that a 390-pixel viewport confesses immediately.&lt;/li&gt;
&lt;li&gt;Automate the mechanical half: a script walks the page and flags text overflow, unexpected horizontal scroll and elements taller than their containers, before a human spends taste on it.&lt;/li&gt;
&lt;li&gt;Record scroll behavior too. Easing curves and pin durations are design decisions, and they are measurable in the performance timeline, not just feelable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Taste still decides
&lt;/h2&gt;

&lt;p&gt;None of this replaces judgment. It feeds judgment with facts. The measurements tell you where you diverge from the reference. Whether the divergence is a bug or a choice is still a human call, and making that call consciously is the entire difference between a site that is almost right and one that is right.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://intframe.com/blog/design-qa-at-one-to-one" rel="noopener noreferrer"&gt;INTFRAME engineering blog&lt;/a&gt;. The companion code lives at &lt;a href="https://github.com/intframe/scroll-qa" rel="noopener noreferrer"&gt;github.com/intframe/scroll-qa&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>A planet in a browser tab</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Fri, 14 Aug 2026 18:14:15 +0000</pubDate>
      <link>https://dev.to/intframe/a-planet-in-a-browser-tab-4m5</link>
      <guid>https://dev.to/intframe/a-planet-in-a-browser-tab-4m5</guid>
      <description>&lt;p&gt;Our site now has a chapter where you scroll a camera through low orbit, visiting the four places we operate from. It looks like it should weigh fifty megabytes. It ships in roughly 1.5, and it is built almost entirely from public-domain science data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real data, baked hard
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The planet&lt;/strong&gt; is NASA Blue Marble imagery, resized and recompressed by a build-time generator. Never hand-edited: when we want a change, we change the generator and bake again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;City lights&lt;/strong&gt; are not a texture. A script samples a NASA night-lights raster into about 46,000 weighted points packed into a small binary buffer, rendered as glowing sprites. Crisp at any zoom, because they are geometry, not pixels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Close-ups&lt;/strong&gt; use a two-stage patch trick: whole-earth imagery is about 10 km per pixel, hopeless at 200 km altitude, so the generator bakes high-resolution patches (down to 76 m per pixel, from Sentinel-2 cloudless data) only around the four cities the camera actually visits.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The patches blend in the fragment shader, feathered at the edges so no seam ever shows, and the procedural detail noise fades out underneath them so photography is not fighting synthesis:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight glsl"&gt;&lt;code&gt;&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;fe&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;smoothstep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;edge&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;edge&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;feather&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dist&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// patch edge feather&lt;/span&gt;
&lt;span class="kt"&gt;vec3&lt;/span&gt;  &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;patchTexel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fe&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;uPatch&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;col&lt;/span&gt;      &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;patchAmt&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                    &lt;span class="c1"&gt;// damp noise under imagery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  One line of trigonometry fixed the camera
&lt;/h2&gt;

&lt;p&gt;Scroll drives a great-circle flight between stops. The obvious way to aim the camera, normalize(next - pos), collapses to a zero vector exactly at the endpoints, which is why naive fly-over cameras snap on arrival. The fix is the analytic tangent of the spherical interpolation itself, stable everywhere including t = 0 and t = 1:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// bearing along slerp(a, b) at parameter t, W = angle between a and b
tangent(t) = normalize( -cos((1 - t) * W) * a  +  cos(t * W) * b )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arrivals settle north-up, hold while the camera descends, then rotate toward the next destination and depart with a slight bank, all of it driven by signed angles between that tangent and local north. Lighting is deliberately cinematic: the sun rises as you arrive so the terrain reads, and sinks back to a dawn band for the night flight.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest performance
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;ema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ema&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;frameMs&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// frame-time moving average&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ema&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;24.0&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;FLOOR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;px&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                                 &lt;span class="c1"&gt;// shed resolution before jank&lt;/span&gt;
  &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPixelRatio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;px&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A governor watches the running frame time and steps internal resolution down before a weak GPU starts dropping frames, with a floor so text and coastlines stay sharp. Assets load only when the chapter approaches, and the whole thing idles at zero cost when off-screen. Awards-bait visuals are allowed. Awards-bait jank is not.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://intframe.com/blog/planet-in-a-browser-tab" rel="noopener noreferrer"&gt;INTFRAME engineering blog&lt;/a&gt;. The companion code lives at &lt;a href="https://github.com/intframe/scroll-rig" rel="noopener noreferrer"&gt;github.com/intframe/scroll-rig&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>threejs</category>
      <category>webgl</category>
      <category>javascript</category>
      <category>frontend</category>
    </item>
    <item>
      <title>Screenshots or it didn't happen</title>
      <dc:creator>INTFRAME</dc:creator>
      <pubDate>Fri, 14 Aug 2026 18:13:41 +0000</pubDate>
      <link>https://dev.to/intframe/screenshots-or-it-didnt-happen-157d</link>
      <guid>https://dev.to/intframe/screenshots-or-it-didnt-happen-157d</guid>
      <description>&lt;p&gt;The most expensive bugs we have shipped were invisible to every automated check. The build was green. The tests passed. And the page was wrong: a headline wrapped into nonsense on mobile, a section rendered behind another, a form that looked fine and submitted nothing.&lt;br&gt;
So we adopted a rule that sounds primitive and works: &lt;strong&gt;no change is done until someone has looked at a screenshot of the deployed page.&lt;/strong&gt; Not the dev server. The real thing, both viewports, at full resolution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making evidence cheap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vp&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1440&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;900&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;390&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;844&lt;/span&gt; &lt;span class="p"&gt;}])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;viewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vp&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;console&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;    &lt;span class="c1"&gt;// console is part of the picture&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;networkidle&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;shot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;vp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Capture is one command per project, so there is no excuse to skip it. Timestamped captures accumulate into the cheapest regression archive ever built.&lt;/p&gt;

&lt;h2&gt;
  
  
  The machine reads first
&lt;/h2&gt;

&lt;p&gt;This year we put a vision model in front of the human. Every capture pair goes to a model with a deliberately narrow brief: report text overflow, truncation, overlapping elements, broken alignment and contrast failures, with pixel regions, and answer NONE if the page is clean. No taste, no compliments, defects only. It catches roughly 70% of mechanical failures before a person looks, and it never gets bored on capture number forty.&lt;br&gt;
The human pass still decides, and it has rules of its own: open captures at 1:1 because overflow hides at fit-to-window zoom, read the rendered copy aloud because screenshots find the typos your editor did not, and treat a clean-looking page with console errors as dirty.&lt;/p&gt;

&lt;h2&gt;
  
  
  The generalization
&lt;/h2&gt;

&lt;p&gt;Database features are done when the row is queried back. Emails are done when the message is opened in a real client. Model pipelines are done when the output is read at the destination, not at the API response. The pattern is one sentence: verify at the layer the user lives in, not the layer you happened to write.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://intframe.com/blog/screenshots-or-it-didnt-happen" rel="noopener noreferrer"&gt;INTFRAME engineering blog&lt;/a&gt;. The companion code lives at &lt;a href="https://github.com/intframe/scroll-qa" rel="noopener noreferrer"&gt;github.com/intframe/scroll-qa&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

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