[{"data":1,"prerenderedAt":327},["ShallowReactive",2],{"navigation_docs":3,"-learn-why-screenshots-flake":93,"-learn-why-screenshots-flake-surround":322},[4,41,46,89],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":40},"Learn","i-lucide-book-open","\u002Flearn","1.learn",[10,15,20,25,30,35],{"title":11,"path":12,"stem":13,"icon":14},"What visual regression testing is","\u002Flearn\u002Fwhat-is-visual-regression-testing","1.learn\u002F1.what-is-visual-regression-testing","i-lucide-eye",{"title":16,"path":17,"stem":18,"icon":19},"How image diffing works","\u002Flearn\u002Fhow-image-diffing-works","1.learn\u002F2.how-image-diffing-works","i-lucide-diff",{"title":21,"path":22,"stem":23,"icon":24},"Why screenshots flake","\u002Flearn\u002Fwhy-screenshots-flake","1.learn\u002F3.why-screenshots-flake","i-lucide-shuffle",{"title":26,"path":27,"stem":28,"icon":29},"Baselines and approval","\u002Flearn\u002Fbaselines-and-approval","1.learn\u002F4.baselines-and-approval","i-lucide-check-check",{"title":31,"path":32,"stem":33,"icon":34},"Running in CI","\u002Flearn\u002Frunning-in-ci","1.learn\u002F5.running-in-ci","i-lucide-workflow",{"title":36,"path":37,"stem":38,"icon":39},"Choosing a tool","\u002Flearn\u002Fchoosing-a-tool","1.learn\u002F6.choosing-a-tool","i-lucide-list-checks",false,{"title":42,"path":43,"stem":44,"icon":45},"Compare the tools","\u002Fcompare","2.compare","i-lucide-scale",{"title":47,"path":48,"stem":49,"children":50,"icon":53},"Manifest","\u002Fmanifest","3.manifest\u002F1.index",[51,54,59,64,69,74,79,84],{"title":52,"path":48,"stem":49,"icon":53},"The run manifest","i-lucide-file-json",{"title":55,"path":56,"stem":57,"icon":58},"The format","\u002Fmanifest\u002Fformat","3.manifest\u002F2.format","i-lucide-file-code",{"title":60,"path":61,"stem":62,"icon":63},"Reading manifests","\u002Fmanifest\u002Freading","3.manifest\u002F3.reading","i-lucide-book-open-check",{"title":65,"path":66,"stem":67,"icon":68},"Merging the manifests of a run","\u002Fmanifest\u002Fmerging","3.manifest\u002F4.merging","i-lucide-merge",{"title":70,"path":71,"stem":72,"icon":73},"Writing manifests","\u002Fmanifest\u002Fwriting","3.manifest\u002F5.writing","i-lucide-pencil-line",{"title":75,"path":76,"stem":77,"icon":78},"Converting to and from other formats","\u002Fmanifest\u002Fconverting","3.manifest\u002F6.converting","i-lucide-arrow-left-right",{"title":80,"path":81,"stem":82,"icon":83},"Reference","\u002Fmanifest\u002Freference","3.manifest\u002F7.reference","i-lucide-list",{"title":85,"path":86,"stem":87,"icon":88},"Schema reference","\u002Fmanifest\u002Fschema","3.manifest\u002F8.schema","i-lucide-braces",{"title":90,"path":91,"stem":92,"icon":14},"Viewer","\u002Fviewer","4.viewer",{"id":94,"title":21,"body":95,"description":315,"extension":316,"links":317,"meta":318,"navigation":319,"path":22,"seo":320,"stem":23,"__hash__":321},"docs\u002F1.learn\u002F3.why-screenshots-flake.md",{"type":96,"value":97,"toc":302},"minimark",[98,102,107,110,132,152,156,167,171,186,190,197,201,212,216,219,223,226,230,249,252,256],[99,100,101],"p",{},"A visual test that fails without any change to the code teaches people to approve without looking, and from then on the suite is worthless. Flakiness is the thing to design against from day one. Almost all of it comes from a short list of causes.",[103,104,106],"h2",{"id":105},"the-environment-renders-differently","The environment renders differently",[99,108,109],{},"Two machines do not draw the same page the same way. Font hinting and sub-pixel positioning differ between macOS, Linux and Windows; the same browser renders text differently between its own versions; a headless browser with GPU rasterisation produces different anti-aliasing from one with software rasterisation; system fonts differ, so a page that falls back to \"the default sans-serif\" looks different everywhere.",[99,111,112,113,117,118,125,126,131],{},"The fix is to ",[114,115,116],"strong",{},"render in one pinned environment"," and nowhere else: a Docker image with a specific browser version and bundled fonts, used both for recording baselines and for comparing. Playwright publishes ",[119,120,124],"a",{"href":121,"rel":122},"https:\u002F\u002Fplaywright.dev\u002Fdocs\u002Fdocker",[123],"nofollow","versioned images",", Cypress has ",[119,127,130],{"href":128,"rel":129},"https:\u002F\u002Fgithub.com\u002Fcypress-io\u002Fcypress-docker-images",[123],"its own",", and several tools (pixsame's pinned renderer, Loki's docker target, the hosted services' render farms) make it the default. Developers on laptops then either run the comparison inside that container or update baselines only from CI. Baselines made on a Mac and compared on Linux are the single most common source of noise.",[99,133,134,135,139,140,143,144,147,148,151],{},"If a pinned renderer is not an option, record the OS and browser in the baseline's name or metadata (Playwright and Vitest append ",[136,137,138],"code",{},"-chromium-linux"," to the file name; the ",[119,141,142],{"href":48},"manifest"," keeps ",[136,145,146],{},"platform"," and ",[136,149,150],{},"renderer"," per entry) and keep one baseline per combination you actually test in.",[103,153,155],{"id":154},"fonts","Fonts",[99,157,158,159,162,163,166],{},"Web fonts load asynchronously. A screenshot taken before the font arrives shows the fallback. Wait for ",[136,160,161],{},"document.fonts.ready"," before capturing, self-host the fonts so there is no network variance, and prefer ",[136,164,165],{},"font-display: block"," in test builds so a late font never swaps in halfway. Icon fonts are worse than text fonts: use SVG icons.",[103,168,170],{"id":169},"animations-and-transitions","Animations and transitions",[99,172,173,174,177,178,181,182,185],{},"A CSS transition caught mid-way is a different image every time. Disable animations globally for screenshots: Playwright's ",[136,175,176],{},"animations: 'disabled'"," option finishes CSS animations and transitions before capturing, most other tools accept an injected stylesheet (",[136,179,180],{},"*, *::before, *::after { animation: none !important; transition: none !important; }","), and emulating ",[136,183,184],{},"prefers-reduced-motion: reduce"," turns off well-behaved animations at the source. Carousels, auto-playing video, typewriter effects and skeleton shimmer need to be stopped explicitly.",[103,187,189],{"id":188},"timing-and-asynchronous-content","Timing and asynchronous content",[99,191,192,193,196],{},"Capture after the page is ",[114,194,195],{},"idle",", not after it has loaded: images decoded, lazy-loaded sections in view, data fetched, spinners gone. Waiting a fixed number of milliseconds is the flaky way to do this; waiting for a specific element or network idle is the stable way. Mock the data so the same content comes back every run.",[103,198,200],{"id":199},"dynamic-content","Dynamic content",[99,202,203,204,207,208,211],{},"Dates, times, counters, random avatars, ads, maps, charts with live data, cursors blinking in inputs. Each needs one of: mock the source, freeze the clock, mask the region, or hide the element with a test-only stylesheet. Tools offer ",[136,205,206],{},"mask"," (Playwright), ignore regions (odiff, Applitools) or a ",[136,209,210],{},"hideSelectors"," list (BackstopJS) for exactly this; the stylesheet injection works everywhere.",[103,213,215],{"id":214},"viewport-scrollbars-and-device-pixel-ratio","Viewport, scrollbars and device pixel ratio",[99,217,218],{},"A screenshot of a 1280-wide viewport on a machine whose window is 1200 wide is a different screenshot. Set the viewport explicitly. Scrollbars take space on Linux and Windows and overlay on macOS, which shifts layout by the scrollbar width; hide them for screenshots. A device pixel ratio of 2 doubles every dimension and changes anti-aliasing; fix it to 1 (or to 2, but fix it).",[103,220,222],{"id":221},"partial-renders-and-hover-state","Partial renders and hover state",[99,224,225],{},"The mouse resting over a button in a headless browser produces a hover state nobody intended. Move the pointer away before capturing. A focused input shows a caret that blinks; blur it or hide the caret with CSS.",[103,227,229],{"id":228},"the-order-to-try-things","The order to try things",[231,232,233,237,240,243,246],"ol",{},[234,235,236],"li",{},"Pin the renderer (Docker image, exact browser version, bundled fonts). This removes the majority.",[234,238,239],{},"Disable animations and wait for idle.",[234,241,242],{},"Mock data and freeze time.",[234,244,245],{},"Mask what is left.",[234,247,248],{},"Only then raise the threshold, and only for the screenshot that needs it.",[99,250,251],{},"Raising the threshold first is tempting and wrong: it hides real regressions in the same proportion as it hides noise.",[103,253,255],{"id":254},"further-reading","Further reading",[257,258,259,280,290],"ul",{},[234,260,261,266,267,270,271,270,274,147,276,279],{},[119,262,265],{"href":263,"rel":264},"https:\u002F\u002Fplaywright.dev\u002Fdocs\u002Ftest-snapshots",[123],"Playwright: screenshots and stability",", especially ",[136,268,269],{},"animations",", ",[136,272,273],{},"caret",[136,275,206],{},[136,277,278],{},"scale",".",[234,281,282,289],{},[119,283,286],{"href":284,"rel":285},"https:\u002F\u002Fplaywright.dev\u002Fdocs\u002Fapi\u002Fclass-page#page-emulate-media",[123],[136,287,288],{},"page.emulateMedia"," for reduced motion and colour scheme.",[234,291,292,297,298,301],{},[119,293,296],{"href":294,"rel":295},"https:\u002F\u002Fdocs.cypress.io\u002Fapi\u002Fcommands\u002Fscreenshot",[123],"Cypress screenshots"," and its ",[136,299,300],{},"disableTimersAndAnimations"," option.",{"title":303,"searchDepth":304,"depth":304,"links":305},"",2,[306,307,308,309,310,311,312,313,314],{"id":105,"depth":304,"text":106},{"id":154,"depth":304,"text":155},{"id":169,"depth":304,"text":170},{"id":188,"depth":304,"text":189},{"id":199,"depth":304,"text":200},{"id":214,"depth":304,"text":215},{"id":221,"depth":304,"text":222},{"id":228,"depth":304,"text":229},{"id":254,"depth":304,"text":255},"Fonts, animations, GPU rasterisation, scrollbars, timing and the one fix that removes most of it, rendering in a pinned environment.","md",null,{},{"icon":24},{"title":21,"description":315},"fmAsLzdexbHSEHGnbn_mL-VSZYryktJ7y2KkcVvN5Qw",[323,325],{"title":16,"path":17,"stem":18,"description":324,"icon":19,"children":-1},"Pixel comparison, colour distance, anti-aliasing detection, perceptual metrics, and what the two thresholds every tool exposes actually control.",{"title":26,"path":27,"stem":28,"description":326,"icon":29,"children":-1},"Where baselines live, who approves a change, how to keep history, and why the review step is the part of the workflow most teams get wrong.",1791993875572]