test(rx-coverage): budget the viewport assertions in pixels, not a flat 0.001° (#2040)

assertViewport allowed 0.001 degrees between the centre it asked for and the centre Leaflet reports back. Leaflet keeps the centre as a pixel coordinate, so a read-back centre is only accurate to the pixel it landed on, and one pixel spans 360/(256*2^zoom) degrees: 0.02197 at zoom 6, 0.00137 at zoom 10. The flat budget demanded 1/20 of a pixel at zoom 6 while its own comment said 'allow sub-pixel coordinate rounding'.

It passes on master because the container rounds favourably. It fails as soon as the layout changes: on the ON8AR fork, whose coverage page carries one extra toolbar row, the first assertion reported {lat:11.996338401936226, lng:33.99169921875001, zoom:6} against an expected 12/34 — 0.167 px of latitude and 0.378 px of longitude, so the view was right and the assertion was wrong. Any container-height change can trip it, fork or not.

The budget is now one pixel at the asserted zoom via a degPerPixel(zoom) helper, applied to both centre assertions and to the waitForFunction on the hash (zoom 10, where 0.001 was 0.73 px and merely lucky). For latitude the Mercator scale is the longitude figure times cos(lat), so the longitude figure is the looser bound there, deliberately: this asserts the view is where we asked, not a projection identity. A one-tile miss at zoom 6 is 5.6 degrees and still fails.

Verified by arithmetic rather than locally (the suite needs a served instance and a browser): the observed drift falls inside the new budget and a one-tile miss does not. CI run 35273657579 is green, with 'RX coverage viewport browser regressions OK' in the Playwright job.

Merged by the interim maintainer without a second human reviewer: CI is the independent check.
This commit is contained in:
efiten
2026-09-18 10:51:34 +02:00
committed by GitHub
parent bbf54cfe1b
commit 6c4fa041de
+16 -5
View File
@@ -39,17 +39,28 @@ const BASE = process.env.BASE_URL || 'http://localhost:3000';
async function viewport() {
return page.evaluate(() => { const m = window.__rxTestMap, c = m.getCenter(); return { lat: c.lat, lng: c.lng, zoom: m.getZoom() }; });
}
// Leaflet keeps the centre as a pixel coordinate, so a centre read back is
// only ever accurate to the pixel it landed on. One pixel spans
// 360/(256*2^zoom) degrees of longitude: 0.022° at zoom 6, 0.0014° at
// zoom 10. The budget below used to be a flat 0.001°, which at zoom 6
// demanded 1/20 of a pixel, so any change to the map container's size
// moved the pixel centre enough to fail an assertion about the *view*.
// For latitude the Mercator scale is this figure times cos(lat), so using
// the longitude figure is the looser bound there, deliberately.
const degPerPixel = (zoom) => 360 / (256 * Math.pow(2, zoom));
function assertViewport(actual, expected) {
// Leaflet projects centers to pixels during invalidateSize/pan, so allow
// sub-pixel coordinate rounding while still checking the intended view.
assert(Math.abs(actual.lat - expected.lat) < 0.001, JSON.stringify(actual));
assert(Math.abs(actual.lng - expected.lng) < 0.001, JSON.stringify(actual));
const budget = degPerPixel(expected.zoom);
const off = (axis) => axis + ' ' + actual[axis] + ' is more than one pixel ('
+ budget.toFixed(5) + '°) from ' + expected[axis] + ': ' + JSON.stringify(actual);
assert(Math.abs(actual.lat - expected.lat) < budget, off('lat'));
assert(Math.abs(actual.lng - expected.lng) < budget, off('lng'));
assert.equal(actual.zoom, expected.zoom);
}
await page.goto(BASE + '/#/rx-coverage'); await waitMap();
assertViewport(await viewport(), { lat: 12, lng: 34, zoom: 6 });
await page.evaluate(() => window.__rxTestMap.setView([22, 44], 10, { animate: false }));
await page.waitForFunction(() => Math.abs(Number(new URLSearchParams(location.hash.split('?')[1]).get('lat')) - 22) < 0.001);
await page.waitForFunction((budget) => Math.abs(Number(new URLSearchParams(location.hash.split('?')[1]).get('lat')) - 22) < budget, degPerPixel(10));
assertViewport(await page.evaluate(() => JSON.parse(localStorage.getItem('rx-coverage-view'))), { lat: 22, lng: 44, zoom: 10 });
assert.equal(await page.evaluate(() => localStorage.getItem('map-view')), null, 'coverage must not write the main map\'s saved viewport');
await page.reload(); await waitMap(); assertViewport(await viewport(), { lat: 22, lng: 44, zoom: 10 });