Skip to content
Latchkey

Visual regression: masking dynamic regions in CI

Some regions cannot be made deterministic (third-party embeds, ads, live charts, user-generated content). Rather than fight them, mask those regions so the comparison ignores their pixels while still catching real changes elsewhere.

What this error means

Visual tests keep flagging the same widget (an ad slot, a map tile, a chart, a relative timestamp) even though the surrounding UI is unchanged. Freezing does not help because the content is external or genuinely variable.

visual-regression
# recurring diff on the same region every run:
#   third-party ad iframe and a live "online users" counter

Common causes

Genuinely variable or third-party content

Ad slots, embedded maps, and live counters change every run and cannot be frozen from your test, so their pixels always differ.

User-generated or randomized content in the frame

Avatars, names, or charts driven by real data vary between runs, producing diffs unrelated to the code change.

How to fix it

Mask regions in Playwright

Pass mask selectors so Playwright paints over those elements before comparing.

tests/page.spec.ts
await expect(page).toHaveScreenshot('page.png', {
  mask: [page.locator('.ad-slot'), page.locator('.live-counter')],
});

Use ignore regions in the visual service

Percy and Applitools support ignoring regions so the volatile area is excluded from the diff.

tests/page.spec.ts
await percySnapshot(page, 'Page', {
  percyCSS: '.ad-slot, .live-counter { visibility: hidden; }',
});

How to prevent it

  • Mask third-party and live regions that cannot be frozen.
  • Prefer freezing over masking where the content is under your control.
  • Keep masks tight so they do not hide real regressions nearby.

Related guides

Tired of flaky CI? Latchkey auto-heals failed jobs and retries them for you. Start free →