Masking Map Attribution and Scale Bars

Attribution, scale bars, zoom indicators and provider logos are the first things a team masks, usually all at once and usually on the first day. Two of the four do not need it: a scale bar and a zoom indicator are pure functions of the camera, so pinning the camera makes them deterministic without removing anything from the comparison. The other two vary for reasons genuinely outside the application, and even there masking is the third-best option. This page separates them and gives the cheaper treatment for each.

This is a task within Interactive Overlay Masking Rules, under Dynamic Element Masking & UI Stability. It assumes the camera fixture from Viewport & Zoom Sync Strategies is already in place.

Prerequisites

Which of the four actually vary

Four pieces of map chrome and why each one moves Four elements of standard map chrome are listed with the reason each varies between captures. The attribution string changes when a data provider is added or its wording is updated, which is outside the application team control. The scale bar changes its length and its unit whenever the camera latitude changes, because a screen pixel covers a different ground distance. The zoom level indicator changes with any camera adjustment, including one that was meant to be identical. The provider logo changes when the provider updates its asset. A note groups them: two are content the application does not own, and two are derived from the camera and become stable the moment the camera is pinned. Two are outside your control; two follow the camera attribution string changes when a provider is added or reworded not yours scale bar length and unit follow latitude and zoom stable once the camera is pinned zoom indicator reflects any camera adjustment at all stable once the camera is pinned provider logo changes when the provider updates its asset

The grouping is worth internalising because it generalises: chrome that derives from state the test already controls needs no special handling, and chrome that carries content from elsewhere needs the content controlled rather than the pixels hidden. Almost every element on a map falls into one of those two categories, and reaching for a mask is a sign that neither question was asked.

Attribution: serve the text, do not hide it

Three ways to handle attribution, ranked by what stays under test Three approaches are compared by how much of the attribution element remains in the comparison. Serving the attribution text from a fixture keeps everything under test — the position, the font, the wrapping and the container geometry — and only fixes the words. Substituting a fixed-width placeholder keeps position and geometry while losing the exact glyphs, which is adequate when the wording genuinely cannot be controlled. Masking the element removes position, geometry and any layout regression along with the words, and is the only option when the attribution is injected by a third-party script the test cannot reach. A caption notes that attribution sits in a corner where a layout regression is easy to miss by eye, which raises the cost of masking it. Attribution sits where a layout break is easiest to miss by eye fixture text placeholder mask keeps position, font, wrapping and container geometry keeps position and geometry, loses the glyphs keeps nothing use the mask only when a third-party script injects the element out of reach

The style’s attribution is usually declared per source, which means it can be pinned in the same fixture that pins the style:

{
  "sources": {
    "basemap": {
      "type": "vector",
      "tiles": ["http://127.0.0.1:8080/tiles/{z}/{x}/{y}.pbf"],
      "attribution": "© Fixture Data 2026"
    }
  }
}

With a fixed string, the whole attribution element stays under test: its position in the corner, its font and size, whether it wraps at a narrow viewport, whether it collides with the scale bar, and whether a long string overflows its container. Those are real layout regressions, they occur in a corner of the frame where nobody looks, and a mask removes every one of them.

If the attribution is injected by a third-party control the test cannot reach, a fixed-width placeholder substituted through an init script is the next-best option, and a mask is the last.

Scale bar and zoom indicator: pin the inputs

Why the scale bar becomes deterministic without any masking at all A scale bar is traced back to its inputs. Its rendered length in pixels is a function of the map latitude and the zoom, because Web Mercator makes a screen pixel cover a different ground distance at different latitudes. Its label text is a function of that length plus the unit system in use. Both inputs are already pinned by the camera fixture and the locale setting, so the scale bar is fully determined once those are fixed — no mask is required. A caption warns about the one thing that does still break it: a rounding rule that picks a nice number, which can flip between two values when the underlying length sits on a boundary, so a camera nudged in the last decimal place can change the label. Pin the camera and the locale, and the scale bar is already deterministic latitude + zoom locale / unit system bar length in pixels rendered bar + label the one thing that still breaks it: a nice-number rounding rule on a boundary a camera nudged in the last decimal can flip the label from 200 m to 500 m

Two practical notes follow from the diagram.

Pin the locale, not just the camera. A scale bar rendered in an environment defaulting to imperial units shows 500 ft where the baseline shows 200 m, and the difference has nothing to do with the map. Set locale on the browser context, and pin it in the runner image as well, so a container-level default cannot override it.

Watch the rounding boundary. Scale bars pick a “nice” number — 100, 200, 500 — from the available pixel width, and a camera that differs in the sixth decimal place can land on the other side of a boundary and flip the label. This is the one case where the scale bar is genuinely unstable, and the fix is upstream: the camera precision discipline in Serializing and Restoring Map Camera State for Tests removes it entirely. Masking the scale bar to avoid it would hide the symptom of a camera that is not actually being restored exactly, which is a much larger problem than a label.

A checklist for any piece of chrome

The two questions that sorted the four elements above generalise into a short routine, and running it takes about a minute per element.

Does this derive from state the test already controls? Camera, viewport, locale, timezone, theme and the frozen clock are all pinned by the harness. Anything computed from them — a scale bar, a coordinate readout, a zoom badge, a north arrow reflecting bearing, a formatted timestamp — is already deterministic and needs nothing. If it is not stable in practice, that is a report about the pin rather than about the element, and the pin is where the fix belongs.

Does it carry content from outside the application? Attribution strings, provider logos, third-party widget contents and anything fetched from a service the team does not own. For these, control the content: a fixture string, a checked-in asset, a stubbed response. The pixels stay under test and only the words are fixed.

Is it genuinely unreachable? Only after both questions have been answered no does a mask become the right answer, and then it gets a manifest entry with a reason, an owner and a review date — because “unreachable” is often a statement about how the application is currently wired rather than a permanent fact, and the review date is what prompts someone to check.

The routine matters because masking is self-reinforcing: each mask makes the suite greener, which makes the next mask easier to justify, and no individual decision looks unreasonable. Tracking the masked share of the frame as a number, as described in Dynamic Element Masking & UI Stability, is what makes the accumulated cost visible before it is large.

Verification

Confirm the procedure worked before wiring it into a blocking gate:

Troubleshooting

Symptom Likely cause Fix
The scale bar label flips between two values on repeat runs The camera is being restored approximately, and the underlying pixel length sits on a nice-number boundary Fix the camera restoration precision rather than masking the bar; the flip is a symptom worth keeping
Attribution differs only in CI The container’s locale or font differs, changing the wrapping of the same string Pin locale and fonts in the runner image; the string itself was never the problem
The attribution mask covers part of the map The element grows when the string is long, and the mask was sized against a short one Serve a fixed string instead, or derive the mask box at capture time rather than storing a rectangle

Frequently asked questions

Is it acceptable to hide attribution entirely during tests?

Removing it changes the layout — other corner controls reposition — so the capture no longer represents the product. It is also worth being careful about: attribution is frequently a licence requirement of the data provider, and a habit of switching it off in test builds has a way of reaching a staging environment that someone external then sees. Serving a fixture string keeps both the layout and the habit correct.

What about a zoom indicator that shows fractional zoom?

It becomes deterministic exactly when the camera does, and it is a useful canary: a zoom indicator reading 14.02 where the fixture said 14 is a visible, human-readable report that the camera restoration is not exact. That makes it one of the more valuable pieces of chrome to keep in the comparison rather than mask.

Should chrome have its own tolerance class?

Yes — it is DOM-rendered rather than rasterised by the map, so its noise floor is much lower than anything on the canvas. Giving it a tight class catches layout regressions that a canvas-calibrated tolerance absorbs entirely, and it costs one entry in the region map. The classes are described in Per-Region Tolerance Classes for Map Interfaces.

Does this change on a retina capture?

The scale bar does: at a different device pixel ratio the same ground distance covers a different number of CSS pixels only if the viewport changes, but the bar’s rasterisation changes and the nice-number choice can differ if the control measures in device pixels. It is one more reason to treat DPR as a deliberate axis rather than something that varies with the runner.