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

June 09, 2026 · 15 min read · How-To Guides

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:

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.

CategoryTest IDDescriptionExpected ResultCommon Failure
Happy PathHP‑1User 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 PathHP‑2watchPosition receives updates as the user moves (simulated).Success callback fires repeatedly with changing coordinates.No updates after first call.
Error PathEP‑1User denies permission.Error callback with PERMISSION_DENIED (code 1).Success callback fires (fallback used).
Error PathEP‑2Timeout expires before a position is obtained.Error callback with TIMEOUT (code 2).Success callback fires with stale data.
Error PathEP‑3Unsupported or insecure origin.SecurityError thrown; no callback.Silent failure, UI shows stale data.
Edge CaseEC‑1High accuracy requested but device lacks GPS.Falls back to network‑based location; accuracy radius larger than requested.API rejects request with POSITION_UNAVAILABLE.
Edge CaseEC‑2Rapid successive calls (e.g., polling every 200 ms).Browser throttles calls; later calls may return cached position.Excessive battery drain, UI jitter.
Edge CaseEC‑3Location change while page is hidden (background tab).watchPosition pauses; no callbacks until page regains focus.Continued callbacks causing unnecessary work.
Edge CaseEC‑4Simulated loss of network (offline) after permission granted.Error callback with POSITION_UNAVAILABLE (code 3) or timeout.Stale cached position returned as fresh.
AccessibilityAC‑1Permission prompt is announced by screen readers.ARIA live region or alert conveys “site wants to use your location”.Prompt invisible to assistive tech.
AccessibilityAC‑2Error messages are perceivable and actionable.Error text has sufficient contrast, is focusable, and explains next steps.Low‑contrast error toast that disappears quickly.
Security/PrivacySP‑1Site 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/PrivacySP‑2Location 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/PrivacySP‑3CSP 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 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

2. Test Permission Flows

3. Simulate Error Conditions

4. Validate High‑Accuracy Behavior

5. Check Accessibility

6. Test Fallback Mechanisms

7. Document Observations

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:

  1. 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.
  2. 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).
  3. 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.
  4. Detecting Subtle Faults – Because the agent interacts with the real browser, it can observe:

Example: Finding a Hidden Dead Button

During a recent SUSA run on an e‑commerce site, the agent:

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:

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.

IssueWhy It’s Hard to Catch in CIDetection Strategy
VPN or proxy that strips GPS headersCI 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 watchPositionEmulators 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 originA 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 callsA 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 APIA 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 attributeAn 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 errorsSome 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‑watchA 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 flowGrant, deny, and revoke permissions; verify appropriate UI and fallback.
Happy pathgetCurrentPosition returns coordinates within expected accuracy; watchPosition emits updates on movement.
Error handlingTimeout, POSITION_UNAVAILABLE, PERMISSION_DENIED, and SecurityError all trigger visible, accessible messages.
High accuracyWhen requested, the reported accuracy matches device capability; fallback behaves sensibly.
AccessibilityPermission prompts and error messages are announced by screen readers; sufficient contrast and focusable.
FallbacksManual entry (zip, city, address) works when geolocation is blocked or unavailable.
Security/PrivacyNo persistent storage of raw coordinates; no leaking via referrer, headers, or third‑party scripts; respects CSP/Feature‑Policy.
PerformancewatchPosition throttles appropriately; background tabs pause updates; no excessive wake‑locks.
Cross‑browser consistencyTested in Chrome, Firefox, Safari, Edge (Chromium) with identical behavior.
Device & network varianceVerified under VPN, battery‑saver, offline, and low‑bandwidth conditions using real devices or cloud farms.
ObservabilityAll geolocation calls are logged (including arguments and outcomes) for post‑mortem analysis.
Regression guardAutomated 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:

  1. Clear specification – Use the test matrix above as a living document that evolves with new features (e.g., geofencing, background location).
  2. Manual verification – Leverage DevTools sensors, screen readers, and real devices to catch UX and accessibility flaws that scripts often miss.
  3. 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.
  4. 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.
  5. Production awareness – Monitor real‑world metrics (e.g., error rates from window.onerror for 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