Browser API Testing Guide
How to Test the Geolocation Prompt with Playwright: Complete 2026 Guide
A scenario-by-scenario walkthrough of testing geolocation permission prompts with Playwright. Permission grants, coordinate mocking, denial handling, watchPosition subscriptions, high accuracy mode, timeout behavior, and the pitfalls that silently break location-dependent test suites.
“The Geolocation API is called over five billion times per month across the web according to Chrome Platform Status usage metrics, making it one of the most widely used permission-gated browser APIs.”
Geolocation Permission Flow
1. Why Testing the Geolocation Prompt Is Harder Than It Looks
The Geolocation API looks simple on the surface. You call navigator.geolocation.getCurrentPosition(), the browser shows a permission prompt, the user accepts, and your callback receives latitude and longitude. In reality, testing this flow reliably in an automated browser requires solving several problems that the API's simplicity hides.
The first problem is the permission prompt itself. Playwright runs Chromium (or Firefox, or WebKit) in a headless or headed mode, but neither mode allows your test to click native browser permission dialogs. These dialogs sit outside the page DOM. You cannot query them with page.locator() or dismiss them with page.on('dialog') because they are not JavaScript dialogs. Playwright solves this with context.grantPermissions(['geolocation']), which pre-approves the permission before any page code runs, but you must call it at the right time and with the right origin scope.
The second problem is coordinate mocking. Real geolocation depends on hardware (GPS, Wi-Fi triangulation, cell tower positioning) that does not exist in CI environments. Playwright provides context.setGeolocation() to inject mock coordinates, but the interaction between setGeolocation and grantPermissions has subtle ordering requirements. Calling setGeolocation without first granting the permission causes the API to return a PERMISSION_DENIED error even though coordinates are technically available.
The third problem is the denial path. When a user denies geolocation permission, the browser does not throw an exception. Instead, the error callback fires with a GeolocationPositionError object whose codeproperty is 1 (PERMISSION_DENIED). Your application must handle this gracefully, showing a fallback UI or a re-prompt message. Testing denial requires explicitly not granting the permission, which is Playwright's default state, but many developers do not realize that Playwright's default behavior already simulates denial rather than the prompt state.
Beyond these three core issues, there are additional complexities. watchPosition creates a subscription that fires every time the device moves, requiring your test to update mock coordinates over time. enableHighAccuracy: true switches from Wi-Fi/cell triangulation to GPS, which has different timeout characteristics. The maximumAge option causes the browser to return cached positions, which can make your assertions pass even when your mock coordinates have changed.
Geolocation Permission Decision Flow
App Calls API
getCurrentPosition()
Permission Check
Is geolocation granted?
Prompt Shown
Native browser dialog
User Decision
Grant or Deny
Coordinates
lat/lng from OS or mock
Callback Fires
Success or error handler
Playwright Geolocation Mock Flow
Create Context
browser.newContext()
Grant Permission
grantPermissions(['geolocation'])
Set Coordinates
setGeolocation({ lat, lng })
Navigate
page.goto(url)
API Returns Mock
Your coordinates injected
A thorough geolocation test suite covers permission grants, denials, watchPosition subscriptions, high accuracy toggling, timeout scenarios, and coordinate updates. The sections below walk through each of these with runnable Playwright TypeScript code.
2. Setting Up Your Geolocation Test Environment
Before writing scenarios, configure Playwright to handle geolocation properly. The key insight is that geolocation permissions and mock coordinates are set at the browser context level, not at the page level. This means all pages opened within the same context share the same geolocation state, which is both useful (for testing navigation persistence) and a trap (for tests that need different coordinates).
Geolocation Test Environment Checklist
- Install Playwright with npx playwright install chromium
- Create a playwright.config.ts with geolocation defaults
- Set up a local dev server or use a public geolocation demo page
- Configure separate browser contexts per scenario for isolation
- Prepare coordinate fixtures for known locations (offices, landmarks)
- Add the permissions array to your context factory function
Coordinate Fixtures for Reproducible Tests
Hardcoding coordinates throughout your test files is a maintenance burden. Instead, create a shared fixture file with named locations that your scenarios can reference by name. This also makes it clear what location each test is simulating.
Granting Permission and Mocking Coordinates
Straightforward3. Scenario: Granting Permission and Mocking Coordinates
The most common geolocation test scenario is the happy path: the user grants permission, and the app receives coordinates. In Playwright, you accomplish this by calling context.grantPermissions(['geolocation']) and context.setGeolocation() before the page code calls the Geolocation API. The order matters. Grant the permission first, then set coordinates.
What to Assert Beyond the UI
Beyond checking that coordinates render on screen, verify that your app sends the correct coordinates to your backend. Use Playwright's route interception to capture the API call and assert the payload.
Denying Permission and Handling the Error
Moderate4. Scenario: Denying Permission and Handling the Error
When a user denies geolocation permission, the browser calls the error callback with a GeolocationPositionError where error.code === 1 (PERMISSION_DENIED). Your app should handle this gracefully by showing a fallback UI, prompting the user to enter their location manually, or defaulting to IP-based geolocation.
In Playwright, simulating denial is straightforward: do not grant the geolocation permission. When you create a context without the permission in the permissions array, any call to getCurrentPosition() or watchPosition() will immediately fire the error callback. You can also explicitly clear permissions with context.clearPermissions() to simulate a user revoking a previously granted permission.
Checking the Permission State Programmatically
Modern browsers expose the Permissions API through navigator.permissions.query(), which returns a PermissionStatus object with a state property. Your app might use this to check permission state before calling the Geolocation API. You can verify this behavior in Playwright using page.evaluate().
Permission Denial: Playwright vs Assrt
import { test, expect } from '@playwright/test';
test('geolocation denial shows fallback', async ({ browser }) => {
const context = await browser.newContext({
permissions: [],
});
const page = await context.newPage();
await page.goto('/location-finder');
await page.getByRole('button', { name: 'Find My Location' }).click();
await expect(
page.getByText('Location access was denied')
).toBeVisible();
await expect(
page.getByRole('textbox', { name: 'Enter your city' })
).toBeVisible();
await context.close();
});Testing watchPosition with Moving Coordinates
Complex5. Scenario: Testing watchPosition with Moving Coordinates
The navigator.geolocation.watchPosition() method creates a subscription that fires every time the device's position changes. This is used by apps that track movement in real time: delivery tracking, fitness apps, ride-hailing services, and turn-by-turn navigation. Testing it requires updating mock coordinates over time and verifying that each update triggers the callback.
Playwright's context.setGeolocation() can be called multiple times to simulate movement. Each call updates the mock coordinates, and any active watchPosition subscription receives the new position. The challenge is timing: you need to wait long enough between updates for the browser to process and dispatch each position change, but not so long that your test times out.
Simulating a GPS Route
For apps that trace a route (like fitness or delivery tracking), you can feed a sequence of coordinates with timed intervals to simulate a realistic path. Use page.waitForTimeout() between setGeolocation calls, or for more deterministic tests, use the app's own rendering cycle as the synchronization point.
High Accuracy Mode and enableHighAccuracy
Moderate6. Scenario: High Accuracy Mode and enableHighAccuracy
When your app passes { enableHighAccuracy: true } to getCurrentPosition or watchPosition, the browser requests the most precise position available. On real devices this means GPS rather than Wi-Fi or cell tower triangulation. The trade-off is longer acquisition time and higher battery consumption. In tests, you need to verify that your app handles both the high accuracy case and the fallback when high accuracy is unavailable.
Playwright's setGeolocation accepts an accuracy parameter measured in meters. You can simulate different accuracy levels to test how your app responds. An accuracy of 10 meters represents GPS-level precision, while 1000 meters represents a rough cell tower estimate. Your app might display a different map zoom level, show an accuracy radius on the map, or warn the user when precision is too low for the feature they are using.
High Accuracy Test: Playwright vs Assrt
import { test, expect } from '@playwright/test';
test('high accuracy geolocation', async ({ browser }) => {
const context = await browser.newContext({
geolocation: {
latitude: 37.7749,
longitude: -122.4194,
accuracy: 10,
},
permissions: ['geolocation'],
});
const page = await context.newPage();
await page.goto('/location-finder?highAccuracy=true');
await page.getByRole('button', { name: 'Find My Location' }).click();
await expect(page.getByTestId('accuracy-badge')).toHaveText('High Precision');
await expect(page.getByTestId('accuracy-meters')).toHaveText('±10m');
await context.close();
});Timeout and Position Unavailable Errors
Complex7. Scenario: Timeout and Position Unavailable Errors
The Geolocation API defines three error codes: PERMISSION_DENIED (1), POSITION_UNAVAILABLE (2), and TIMEOUT(3). We covered PERMISSION_DENIED in section 4. The other two errors are harder to simulate because Playwright's geolocation mocking always returns a position when the permission is granted. There is no built-in way to make setGeolocation return an error.
To test POSITION_UNAVAILABLE and TIMEOUT, you need to override the Geolocation API at the JavaScript level using page.addInitScript(). This injects a script before any page JavaScript runs, allowing you to replace navigator.geolocation.getCurrentPosition with a version that calls the error callback instead of the success callback.
Permission State Persistence Across Navigations
Moderate8. Scenario: Permission State Persistence Across Navigations
In real browsers, when a user grants geolocation permission for a site, that permission persists across page navigations and reloads within the same session. Your app might rely on this behavior: it checks the permission state on page load and either immediately requests the position (if already granted) or shows a prompt button (if not yet decided).
In Playwright, permissions set on a browser context persist for the lifetime of that context. This means navigating to different pages within the same context maintains the geolocation grant. However, creating a new context resets everything. This is useful for testing both the persistence case and the fresh-visit case.
Permission Persistence: Playwright vs Assrt
import { test, expect } from '@playwright/test';
test('geo permission persists across navigation', async ({ browser }) => {
const context = await browser.newContext({
geolocation: { latitude: 51.5074, longitude: -0.1278 },
permissions: ['geolocation'],
});
const page = await context.newPage();
await page.goto('/location-finder');
await page.getByRole('button', { name: 'Find My Location' }).click();
await expect(page.getByTestId('latitude')).toHaveText('51.5074');
await page.goto('/settings');
await page.goto('/location-finder');
await page.getByRole('button', { name: 'Find My Location' }).click();
await expect(page.getByTestId('latitude')).toHaveText('51.5074');
await context.close();
});9. Common Pitfalls That Break Geolocation Test Suites
These pitfalls are sourced from real Playwright GitHub issues and Stack Overflow threads. Each one has caused test suites to fail silently or produce false positives in production CI pipelines.
Geolocation Testing Anti-Patterns
- Calling setGeolocation() before grantPermissions(). The order matters: grant first, then set coordinates. Reversing this causes PERMISSION_DENIED even though coordinates are configured.
- Forgetting that Playwright's default state is 'denied', not 'prompt'. Unlike real browsers where a first-time visitor sees a prompt, Playwright contexts deny permissions by default. Your 'first visit' tests may pass incorrectly.
- Setting geolocation on the page object instead of the context. The setGeolocation() method belongs to BrowserContext, not Page. Calling it on the wrong object results in a TypeScript error in strict mode or a silent failure in JavaScript.
- Using maximumAge in getCurrentPosition options without accounting for cached results. If your test sets maximumAge to a non-zero value, the browser may return a previously cached position instead of your updated mock coordinates.
- Not closing browser contexts between tests. Geolocation state persists for the context lifetime. Tests that share a context can leak coordinates from one test to another, causing intermittent failures.
- Testing only on Chromium. Firefox and WebKit handle geolocation permissions differently. Firefox requires 'geolocation' in the permissions array exactly like Chromium, but WebKit's implementation has subtle timing differences with watchPosition callbacks.
- Assuming accuracy is always present. The accuracy field in Playwright's setGeolocation defaults to 0 if omitted. Some apps treat accuracy of 0 as invalid data, so always specify a realistic accuracy value.
- Hardcoding coordinate assertions with exact equality. Floating point representation can differ slightly across browser engines. Use toBeCloseTo() or a small epsilon comparison instead of exact string matching for coordinate values.
Cross-Browser Gotchas
WebKit (Safari) does not support the navigator.permissions.query() method for the geolocation permission name. If your app uses the Permissions API to pre-check geolocation state before calling getCurrentPosition, your WebKit tests will fail unless you handle the exception. Always wrap Permissions API calls in a try/catch in your application code, and test that the fallback path works in WebKit.
Firefox handles watchPosition slightly differently when coordinates are updated rapidly. If you call context.setGeolocation() multiple times without any delay, Firefox may batch the updates and only deliver one callback. Add a small await page.waitForTimeout(50) between rapid coordinate changes in Firefox tests to ensure each update fires a separate callback.
10. Writing Geolocation Tests in Plain English with Assrt
Every scenario in this guide required understanding Playwright's context-level permission APIs, the correct ordering of grantPermissions and setGeolocation, and the nuances of error simulation with addInitScript. Assrt lets you describe the same scenarios in plain English while handling the underlying Playwright orchestration automatically.
Here is the watchPosition scenario from section 5 written as an Assrt .assrt file. Assrt handles context creation, permission granting, coordinate mocking, and the movement simulation behind the scenes.
The Assrt version reads like a test plan you would hand to a QA engineer. No boilerplate for context creation or cleanup. No remembering whether setGeolocation goes on the context or the page. No manual tracking of async state. Assrt compiles each step into the correct Playwright API call, including the correct ordering of permission grants and coordinate mocks.
Notice how the error simulation (which required 15 lines of addInitScript boilerplate in raw Playwright) becomes a single declarative line: geolocation-error: TIMEOUT. Assrt maintains a library of browser API overrides internally, so you describe the scenario rather than implementing the plumbing.
Related Guides
How to Test PWA Install Prompt
A practical guide to testing PWA install prompts with Playwright. Covers...
How to Test Service Worker Offline
A practical guide to testing service worker offline behavior with Playwright. Covers...
How to Test Toast Notifications
A practical, scenario-by-scenario guide to testing toast notifications with Playwright....
Ready to automate your testing?
Assrt discovers test scenarios, writes Playwright tests from plain English, and self-heals when your UI changes.