How to Test Location Services on Web (Complete Guide)
Location‑aware features are no longer niche extras; they power store finders, ride‑hail maps, weather widgets, and geofenced notifications. When the Geolocation API misbehaves, users see blank maps, w
Why Location Testing Matters for Web Applications
Location‑aware features are no longer niche extras; they power store finders, ride‑hail maps, weather widgets, and geofenced notifications. When the Geolocation API misbehaves, users see blank maps, wrong‑turn directions, or prompts that never disappear. In production these glitches translate into abandoned flows, support tickets, and, for regulated sectors like finance or health, compliance risks. Because the API lives in the browser, its behavior varies with device hardware, OS settings, network conditions, and user‑granted permissions. A test that passes on a developer laptop can fail on a low‑end Android phone with location disabled, or on a corporate workstation behind a strict proxy. Therefore, a systematic approach to location testing is essential to catch regressions before they reach real users.
Understanding the Web Location Stack
Before designing tests, it helps to know what the browser actually exposes.
Geolocation API
navigator.geolocation provides three core methods:
getCurrentPosition(success, error, options)– one‑shot request.watchPosition(success, error, options)– continuous updates.clearWatch(id)– stop watching.
The options object can request enableHighAccuracy, a timeout, and a maximumAge.
Permission Model
Modern browsers tie geolocation to the Permission API. Calling navigator.permissions.query({name:'geolocation'}) returns a promise that resolves to {state: 'granted'|'denied'|'prompt'}. The state can change if the user revokes access via the site settings pane.
Fallbacks and Polyfills
Some sites implement their own location resolution using IP‑based services, Wi‑Fi triangulation, or manual entry fields. These fallbacks often bypass the browser’s permission UI and must be tested separately.
Security Context
Geolocation is only available in secure contexts (HTTPS or localhost). Attempting to call it from an insecure origin throws a SecurityError.
Understanding these pieces lets you decide where to inject mocks, where to observe real prompts, and where to verify graceful degradation.
Test Matrix for Location Services
Below is a comprehensive matrix that covers the dimensions you should verify. Each cell notes the expected outcome and the typical failure mode.
| Category | Test ID | Description | Expected Result | Common Failure |
|---|---|---|---|---|
| Happy Path | HP‑1 | User grants permission, receives accurate coordinates within timeout. | Success callback fires with coords.latitude/longitude within 50 m of true location. | Timeout fired despite good signal. |
| Happy Path | HP‑2 | watchPosition receives updates as the user moves (simulated). | Success callback fires repeatedly with changing coordinates. | No updates after first call. |
| Error Path | EP‑1 | User denies permission. | Error callback with PERMISSION_DENIED (code 1). | Success callback fires (fallback used). |
| Error Path | EP‑2 | Timeout expires before a position is obtained. | Error callback with TIMEOUT (code 2). | Success callback fires with stale data. |
| Error Path | EP‑3 | Unsupported or insecure origin. | SecurityError thrown; no callback. | Silent failure, UI shows stale data. |
| Edge Case | EC‑1 | High accuracy requested but device lacks GPS. | Falls back to network‑based location; accuracy radius larger than requested. | API rejects request with POSITION_UNAVAILABLE. |
| Edge Case | EC‑2 | Rapid successive calls (e.g., polling every 200 ms). | Browser throttles calls; later calls may return cached position. | Excessive battery drain, UI jitter. |
| Edge Case | EC‑3 | Location change while page is hidden (background tab). | watchPosition pauses; no callbacks until page regains focus. | Continued callbacks causing unnecessary work. |
| Edge Case | EC‑4 | Simulated loss of network (offline) after permission granted. | Error callback with POSITION_UNAVAILABLE (code 3) or timeout. | Stale cached position returned as fresh. |
| Accessibility | AC‑1 | Permission prompt is announced by screen readers. | ARIA live region or alert conveys “site wants to use your location”. | Prompt invisible to assistive tech. |
| Accessibility | AC‑2 | Error messages are perceivable and actionable. | Error text has sufficient contrast, is focusable, and explains next steps. | Low‑contrast error toast that disappears quickly. |
| Security/Privacy | SP‑1 | Site does not retain location data longer than needed. | No storage of coordinates in localStorage, IndexedDB, or cookies after use. | Latitude/longitude persisted across sessions. |
| Security/Privacy | SP‑2 | Location data is not leaked via referrer or third‑party scripts. | Network requests contain no lat/long in query strings or headers. | Tracking pixel sends precise coords to analytics endpoint. |
| Security/Privacy | SP‑3 | CSP or feature policy blocks geolocation when disallowed. | Feature-Policy: geolocation 'none' results in immediate denial. | Feature policy ignored, allowing access. |
How to Use the Matrix
- Manual testers can walk through each row, checking the expected result and noting any deviation.
- Automated suites should encode each test ID as a distinct test case, using the same description for traceability.
- Risk‑based prioritization focuses first on HP‑1/HP‑2 (core functionality), then EP‑1/EP‑2 (error handling), followed by EC‑1/EC‑3 (environmental variance), and finally the accessibility and security rows.
Manual Testing Approach
A disciplined manual process catches nuances that automated scripts may overlook, especially around user perception and device‑specific quirks.
1. Prepare a Baseline
- Open the site on a desktop browser (Chrome, Firefox, Safari) with location enabled.
- Open DevTools → Sensors → Geolocation → set a custom latitude/longitude (e.g., 37.7749, -122.4194 for San Francisco).
- Verify that the UI shows a map pin or distance calculation matching the mocked coordinates.
2. Test Permission Flows
- Reload the page with location disabled in the OS (Windows Settings → Privacy → Location).
- Observe whether the site shows a clear prompt to enable location or falls back to a manual entry field.
- Re‑enable location, then manually deny the browser prompt. Confirm the error UI appears and no stale data is used.
3. Simulate Error Conditions
- In DevTools → Sensors, set Location unavailable (the “Location unavailable” option).
- Trigger a
getCurrentPositioncall; ensure the error callback fires withPOSITION_UNAVAILABLE. - Adjust the Timeout slider to a low value (e.g., 2 seconds) while keeping the sensor set to “Position not available”. Verify a
TIMEOUTerror.
4. Validate High‑Accuracy Behavior
- Enable high accuracy (
{enableHighAccuracy:true}) and watch the reportedaccuracyproperty. - On a laptop without GPS, notice the accuracy radius jumps to several hundred meters (network‑based).
- On a mobile device with GPS, confirm the radius drops to < 20 m when outdoors.
5. Check Accessibility
- Turn on a screen reader (NVDA, VoiceOver, or TalkBack).
- Focus on the location permission prompt; listen for an announcement that the site requests location.
- After an error, verify that the error message is announced and remains visible until dismissed.
6. Test Fallback Mechanisms
- Disallow geolocation via the browser’s site settings (click the lock icon → Location → Block).
- Enter a ZIP code or city manually; confirm the app derives coordinates from the entered value and proceeds normally.
7. Document Observations
- Record the device model, OS version, browser version, and any relevant flags (e.g.,
--disable-features=Geolocation). - Screenshot the UI state for each test ID and attach logs from the console (
console.errororconsole.warn).
Manual testing is labor‑intensive but invaluable for catching issues that depend on the interplay of OS settings, browser UI, and user perception.
Automated Testing Approaches
Automation brings repeatability and speed. For location services, the key is to control the Geolocation API’s output and permission state without relying on real hardware.
1. Playwright (Chromium, Firefox, WebKit)
Playwright exposes a context.grantPermissions method and a page.setGeolocation function that directly injects coordinates into the API.
// playwright-test.spec.js
const { test, expect } = require('@playwright/test');
test('geolocation happy path', async ({ page }) => {
// Grant permission and set coordinates
await page.context().grantPermissions(['geolocation']);
await page.setGeolocation({ latitude: 40.7128, longitude: -74.0060 });
await page.goto('https://example.com/store-finder');
await page.click('#use-my-location');
// Expect the store finder to show a result near NYC
const firstResult = await page.locator('.store-result').first();
await expect(firstResult).toContainText('New York, NY');
});
test('geolocation denied', async ({ page }) => {
await page.context().clearPermissions(); // ensures prompt appears
await page.goto('https://example.com/store-finder');
await page.click('#use-my-location');
// Handle the permission dialog
await page.waitForEvent('dialog');
const dialog = await page.waitForEvent('dialog');
await dialog.dismiss(); // deny
const errorMsg = await page.locator('#location-error');
await expect(errorMsg).toHaveText('We need your location to show nearby stores.');
});
*Advantages*: Works across browsers, automatic waiting, built‑in tracing.
*Limitations*: Requires a recent Playwright version (≥ 1.20) for setGeolocation.
2. Selenium WebDriver
Selenium can override the Geolocation API via Chrome DevTools Protocol (CDP) or Firefox’s Marionette.
// SeleniumJavaTest.java
@Test
public void testGeolocationTimeout() {
ChromeOptions options = new Options();
options.setExperimentalOption("useAutomationExtension", false);
WebDriver driver = new ChromeDriver(options);
try {
driver.get("https://example.com/weather");
// Use CDP to set geolocation and timeout
Map<String, Object> params = ImmutableMap.of(
"latitude", 51.5074,
"longitude", -0.1278,
"accuracy", 100
);
((HasWebSocket) driver).getWebSocket().send(
Json.toJson(ImmutableMap.of("method", "Emulation.setGeolocationOverride", "params", params))
);
// Trigger location request
driver.findElement(By.id("get-weather")).click();
// Expect timeout error after 5 seconds (set via options)
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(6));
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("error-timeout")));
Assert.assertEquals(
driver.findElement(By.id("error-timeout")).getText(),
"Location request timed out."
);
} finally {
driver.quit();
}
}
*Advantages*: Language‑agnostic, integrates with existing test grids.
*Limitations*: Manual handling of prompts; requires enabling CDP and dealing with browser‑specific quirks.
3. Cypress
Cypress does not natively support geolocation overrides, but you can stub navigator.geolocation directly.
// cypress/integration/geolocation_spec.js
describe('Location service', () => {
beforeEach(() => {
// Stub the API
cy.window().then((win) => {
win.navigator.geolocation = {
getCurrentPosition: (success, error, opts) => {
if (opts?.timeout && opts.timeout < 100) {
setTimeout(() => error({ code: 2, message: 'Timeout' }), opts.timeout);
} else {
success({
coords: {
latitude: 34.0522,
longitude: -118.2437,
accuracy: 20,
altitude: null,
altitudeAccuracy: null,
heading: null,
speed: null
},
timestamp: Date.now()
});
}
},
watchPosition: () => {},
clearWatch: () => {}
};
});
});
it('shows correct weather for mocked coords', () => {
cy.visit('https://example.com/weather');
cy.get('#use-location').click();
cy.get('#weather-report')
.should('contain', 'Los Angeles, CA')
.and('contain', '22°C');
});
it('handles timeout gracefully', () => {
cy.visit('https://example.com/weather');
cy.get('#use-location').click({ timeout: 50 }); // force low timeout
cy.get('#error-message')
.should('contain', 'Timeout');
});
});
*Advantages*: Fast, excellent debugging UI, easy to stub.
*Limitations*: Runs only in Chromium/Firefox via Electron; does not trigger real permission prompts.
4. Using Browser Extensions for Manual Overrides
Extensions like “Location Guard” (Chrome/Firefox) let you set a fixed location or add noise. In automated pipelines you can launch the browser with the extension pre‑installed and configure its options via command‑line flags.
# Install extension and launch Chrome with fixed location
google-chrome \
--load-extension=/path/to/location-guard \
--disable-features=GeolocationMoreThanPrompt \
--geolocation=48.8566,2.3522 \
https://example.com/tourist-guide
This approach is useful when you need to test the site’s behavior under *real* permission prompts while still controlling the coordinates.
5. Cloud‑Based Device Farms
Services like BrowserStack or Sauce Labs provide real Android/iOS devices where you can toggle GPS, airplane mode, or network throttling. Combine them with Playwright/Selenium scripts that call device.setGeolocation via the platform’s API.
// BrowserStack + Playwright example
const { chromium } = require('playwright');
(async () => {
const browser = await chromium.connect({
wsEndpoint: `wss://cdp.browserstack.com/playwright?caps=${encodeURIComponent(JSON.stringify({
'browserName': 'Chrome',
'browserVersion': 'latest',
'os': 'Windows',
'osVersion': '10',
'name': 'Geo test',
'build': 'location-suite'
}))}`
});
const context = await browser.newContext();
await context.grantPermissions(['geolocation']);
await context.setGeolocation({ latitude: -33.8688, longitude: 151.2093 });
const page = await context.newPage();
await page.goto('https://example.com/hotel-booking');
await page.click('#use-my-location');
await page.waitForSelector('.hotel-result');
await browser.close();
})();
*Advantages*: Real hardware sensors, network conditions, and geographic IP diversity.
*Limitations*: Higher cost, longer test execution times.
Concrete Code Examples
Below are ready‑to‑copy snippets that illustrate the most common automation patterns.
Playwright – Permission Prompt Handling
test('handles permission denial gracefully', async ({ page }) => {
await page.context().clearPermissions(); // ensures a fresh prompt
await page.goto('https://example.com/map');
await page.click('#find-me');
// Wait for the native permission dialog
const [dialog] = await Promise.all([
page.waitForEvent('dialog'),
page.waitForTimeout(500) // small buffer
]);
await dialog.dismiss(); // user clicks “Block”
const fallback = await page.locator('#manual-entry');
await expect(fallback).toBeVisible();
await fallback.fill('94043');
await page.click('#submit-zip');
await expect(page.locator('#map')).toContainText('Mountain View, CA');
});
Selenium – Setting High Accuracy and Verifying Accuracy Radius
@Test
public void testHighAccuracyReportsLowRadius() {
ChromeOptions opts = new Options();
opts.addArguments("--disable-features=GeolocationMoreThanPrompt");
WebDriver driver = new ChromeDriver(opts);
try {
driver.get("https://example.com/fitness-tracker");
// Enable high accuracy via CDP
Map<String, Object> geo = Map.of(
"latitude", 37.7749,
"longitude", -122.4194,
"accuracy", 5,
"enableHighAccuracy", true
);
executeCdp(driver, "Emulation.setGeolocationOverride", geo);
driver.findElement(By.id("start-run")).click();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
wait.until(ExpectedConditions.textToBePresentInElement(
By.id("accuracy-readout"), "Accuracy: 5 m"));
String accuracyText = driver.findElement(By.id("accuracy-readout")).getText();
assertTrue(accuracyText.contains("Accuracy: 5 m"));
} finally {
driver.quit();
}
}
private void executeCdp(WebDriver driver, String cmd, Map<String, Object> params) {
HasWebSocket hw = (HasWebSocket) driver;
hw.getWebSocket().send(
Json.toJson(Map.of("method", cmd, "params", params))
);
}
Cypress – Mocking watchPosition for Motion Simulation
it('updates UI as simulated position changes', () => {
let callCount = 0;
cy.window().then((win) => {
const fakeWatch = (success, error, opts) => {
const id = Math.random();
const interval = setInterval(() => {
// Simulate moving north 0.0001° each tick (~10 m)
const lat = 40.7128 + callCount * 0.0001;
success({
coords: {
latitude: lat,
longitude: -74.0060,
accuracy: 5,
altitude: null,
altitudeAccuracy: null,
heading: 0,
speed: 0
},
timestamp: Date.now()
});
callCount++;
if (callCount >= 5) clearInterval(interval);
}, 200);
return id;
};
win.navigator.geolocation = {
getCurrentPosition: (s, e, o) => fakeWatch(s, e, o),
watchPosition: fakeWatch,
clearWatch: (id) => clearTimeout(id)
};
});
cy.visit('https://example.com/live-track');
cy.get('#start-tracking').click();
// Expect five updates
cy.get('#position-log')
.should('have.descendants', '.coord-entry')
.its('length')
.should('eq', 5);
});
These snippets can be dropped into a test suite and adjusted for the specific URLs, selectors, and timing thresholds of your application.
Autonomous Persona‑Driven Exploration
Even the most thorough scripted matrix can miss bugs that appear only when real users behave in unexpected ways. Autonomous QA platforms like SUSA (SUSATest) address this gap by:
- Emulating Diverse Personas – Each persona (curious, impatient, novice, adversarial, elderly, accessibility‑focused, power user, etc.) follows a distinct behavior profile. For location services, a *curious* persona might repeatedly tap the “Use my location” button to see if the map updates, while an *impatient* persona may deny the permission prompt immediately and then try to proceed with a manual zip code entry.
- Exploring State Space Without Scripts – The agent starts from the landing page, discovers UI elements that trigger geolocation requests (buttons, links, autocomplete fields), and then exercises them in combinations that a human tester might not think to script (e.g., opening a location‑dependent modal, navigating away before the promise resolves, then returning).
- Learning from Prior Runs – Cross‑session memory lets SUSA remember which screens led to dead ends (e.g., a permission denial that shows no fallback) and which paths produced new information (e.g., a successful watch that updates after a page reload). Subsequent runs focus on unexplored branches, increasing bug‑finding efficiency over time.
- Detecting Subtle Faults – Because the agent interacts with the real browser, it can observe:
- Permission prompts that appear behind overlays and become inaccessible to keyboard navigation.
- Error messages that vanish before a screen reader can announce them (timing‑related accessibility bugs).
- Cases where a
watchPositioncallback continues after the page is hidden, causing unnecessary battery drain (visible via the DevTools performance panel). - Situations where a fallback IP‑based location returns coordinates that place the user in a different country, breaking geo‑restricted content logic.
Example: Finding a Hidden Dead Button
During a recent SUSA run on an e‑commerce site, the agent:
- Noticed a “Find Nearby Stores” button inside a collapsed accordion that only expanded after a user scrolled to the bottom of a long product description.
- Clicked the button while the accordion was still collapsed (simulating a power user who relies on spatial memory rather than visual cues).
- The click triggered a geolocation request, but the UI never showed a loading spinner or error state because the button’s event listener was guarded by a check for
accordion.isOpen()that returned false. - The request succeeded silently, but the store list never appeared, leaving the user confused.
A scripted test that always expanded the accordion first would never have seen this failure mode. The persona‑driven approach exposed a race condition between UI state and API invocation that only manifests under specific interaction patterns.
Integrating SUSA into Your Pipeline
You can run SUSA as a lightweight Docker container or via the CLI:
# Install the agent
pip install susatest-agent
# Run a session against a staging URL, saving artifacts to ./susa-out
susatest run \
--url https://staging.example.com \
--personas curious impatient elderly \
--output ./susa-out \
--max-depth 6 \
--timeout 300
The output includes:
- A video trace of each persona’s session.
- A JSON log of every geolocation call, its arguments, and the resulting callback or error.
- Screenshots taken at moments when the agent detected a new UI state (e.g., a permission prompt, an error toast, a fallback form).
By reviewing these artifacts, you can spot location‑related bugs that escape traditional test suites and then add targeted automated checks to prevent regression.
Production‑Only Edge Cases
Some problems only surface when the application meets real‑world network conditions, device quirks, or user configurations that are hard to emulate in a lab.
| Issue | Why It’s Hard to Catch in CI | Detection Strategy |
|---|---|---|
| VPN or proxy that strips GPS headers | CI runners usually have direct internet; corporate VPNs may alter the IP‑based fallback location. | Run a subset of tests from a VPN‑enabled node or use a service like browserstack with a custom geo‑IP. |
Battery‑saver mode throttling watchPosition | Emulators often report full power; real devices may drop update frequency to once per minute. | Use Android’s adb shell dumpsys battery to set low power mode and verify that update intervals respect the user’s setting. |
| Multiple tabs sharing the same origin | A background tab may continue receiving updates, causing duplicated map markers. | Open two tabs in the same browser session, trigger watchPosition in each, and verify that only the foreground tab receives callbacks (or that duplicates are deduped). |
| Service worker intercepting geolocation calls | A poorly written SW might respondWith a cached position, serving stale data. | Register a service worker that logs every fetch to navigator.geolocation and assert that no intercept occurs for geolocation‑related requests. |
| Content Security Policy (CSP) blocking the Geolocation API | A strict CSP with script-src 'self' can inadvertently block the API if the page loads a polyfill from a CDN. | Deploy a test CSP header that includes geolocation 'none' and confirm the site fails gracefully; then remove it to ensure recovery. |
iframe sandbox without allow attribute | An embedded map inside a sandboxed iframe lacking allow="geolocation" will silently fail. | Render the page with an iframe containing the map and check that the error callback fires with PERMISSION_DENIED. |
| Locale‑specific formatting errors | Some locales use a comma as decimal separator; if the app concatenates coordinates directly into a URL, the resulting request may be malformed. | Test with browsers set to fr-FR or ja-JP and verify that any generated links or API calls still parse correctly. |
| Network loss mid‑watch | A user may enter a tunnel; the API should transition from watchPosition updates to an error callback after the timeout. | Use DevTools → Network → Offline toggle while a watch is active and ensure the error handler fires within the configured timeout. |
Incorporating these scenarios into a nightly run (perhaps on a device farm with configurable network profiles) dramatically reduces the chance of a location‑related outage reaching real users.
Quick Reference Checklist
Use this list before each release or when adding a new location‑dependent feature.
| ✅ | Item |
|---|---|
| Permission flow | Grant, deny, and revoke permissions; verify appropriate UI and fallback. |
| Happy path | getCurrentPosition returns coordinates within expected accuracy; watchPosition emits updates on movement. |
| Error handling | Timeout, POSITION_UNAVAILABLE, PERMISSION_DENIED, and SecurityError all trigger visible, accessible messages. |
| High accuracy | When requested, the reported accuracy matches device capability; fallback behaves sensibly. |
| Accessibility | Permission prompts and error messages are announced by screen readers; sufficient contrast and focusable. |
| Fallbacks | Manual entry (zip, city, address) works when geolocation is blocked or unavailable. |
| Security/Privacy | No persistent storage of raw coordinates; no leaking via referrer, headers, or third‑party scripts; respects CSP/Feature‑Policy. |
| Performance | watchPosition throttles appropriately; background tabs pause updates; no excessive wake‑locks. |
| Cross‑browser consistency | Tested in Chrome, Firefox, Safari, Edge (Chromium) with identical behavior. |
| Device & network variance | Verified under VPN, battery‑saver, offline, and low‑bandwidth conditions using real devices or cloud farms. |
| Observability | All geolocation calls are logged (including arguments and outcomes) for post‑mortem analysis. |
| Regression guard | Automated test for each matrix row (HP‑1, EP‑1, etc.) runs on every commit. |
Closing Takeaways
Location testing is not a single checkbox; it is a matrix of permission states, error conditions, environmental variables, and user interaction patterns. A solid strategy combines:
- Clear specification – Use the test matrix above as a living document that evolves with new features (e.g., geofencing, background location).
- Manual verification – Leverage DevTools sensors, screen readers, and real devices to catch UX and accessibility flaws that scripts often miss.
- Targeted automation – Employ Playwright, Selenium, or Cypress to stub the API, control permissions, and assert expected outcomes; extend to device farms for hardware‑specific variables.
- Autonomous exploration – Let persona‑driven agents like SUSA probe the unknown corners of your application, surfacing bugs that appear only under unusual interaction sequences or edge‑case device states.
- Production awareness – Monitor real‑world metrics (e.g., error rates from
window.onerrorfor Geolocation, analytics on fallback usage) and feed those insights back into your test suite.
By treating location as a first‑class concern—complete with its own test matrix, dedicated tooling, and continuous learning loops—you turn a potential source of silent frustration into a reliably working feature that enhances, rather than hinders, the user experience. Happy testing.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free