feat(geo): enable geolocation overrides on WK (#361)

This commit is contained in:
Pavel Feldman 2020-01-03 10:14:50 -08:00 committed by Dmitry Gozman
parent 508a7eb016
commit 2d14d1ec1f
11 changed files with 77 additions and 87 deletions

View File

@ -21,6 +21,7 @@
* [browserContext.newPage()](#browsercontextnewpage)
* [browserContext.pages()](#browsercontextpages)
* [browserContext.setCookies(cookies)](#browsercontextsetcookiescookies)
* [browserContext.setGeolocation(options)](#browsercontextsetgeolocationoptions)
* [browserContext.setPermissions(origin, permissions[])](#browsercontextsetpermissionsorigin-permissions)
- [class: BrowserFetcher](#class-browserfetcher)
* [browserFetcher.canDownload(revision)](#browserfetchercandownloadrevision)
@ -235,8 +236,6 @@
* [chromiumCoverage.startJSCoverage([options])](#chromiumcoveragestartjscoverageoptions)
* [chromiumCoverage.stopCSSCoverage()](#chromiumcoveragestopcsscoverage)
* [chromiumCoverage.stopJSCoverage()](#chromiumcoveragestopjscoverage)
- [class: ChromiumOverrides](#class-chromiumoverrides)
* [chromiumOverrides.setGeolocation(options)](#chromiumoverridessetgeolocationoptions)
- [class: ChromiumPlaywright](#class-chromiumplaywright)
* [chromiumPlaywright.connect(options)](#chromiumplaywrightconnectoptions)
* [chromiumPlaywright.createBrowserFetcher([options])](#chromiumplaywrightcreatebrowserfetcheroptions)
@ -465,6 +464,21 @@ An array of all pages inside the browser context.
await browserContext.setCookies([cookieObject1, cookieObject2]);
```
#### browserContext.setGeolocation(options)
- `options` <[Object]>
- `latitude` <[number]> Latitude between -90 and 90.
- `longitude` <[number]> Longitude between -180 and 180.
- `accuracy` <[number]> Optional non-negative accuracy value.
- returns: <[Promise]>
Sets the page's geolocation. Passing null or undefined emulates position unavailable.
```js
await browserContext.setGeolocation({latitude: 59.95, longitude: 30.31667});
```
> **NOTE** Consider using [browserContext.setPermissions](#browsercontextsetpermissions-permissions) to grant permissions for the page to read its geolocation.
#### browserContext.setPermissions(origin, permissions[])
- `origin` <[string]> The [origin] to grant permissions to, e.g. "https://example.com".
- `permissions` <[Array]<[string]>> An array of permissions to grant. All permissions that are not listed here will be automatically denied. Permissions can be one of the following values:
@ -3147,23 +3161,6 @@ _To output coverage in a form consumable by [Istanbul](https://github.com/istanb
> **NOTE** JavaScript Coverage doesn't include anonymous scripts by default. However, scripts with sourceURLs are
reported.
### class: ChromiumOverrides
#### chromiumOverrides.setGeolocation(options)
- `options` <[Object]>
- `latitude` <[number]> Latitude between -90 and 90.
- `longitude` <[number]> Longitude between -180 and 180.
- `accuracy` <[number]> Optional non-negative accuracy value.
- returns: <[Promise]>
Sets the page's geolocation.
```js
await browserContext.overrides.setGeolocation({latitude: 59.95, longitude: 30.31667});
```
> **NOTE** Consider using [browserContext.setPermissions](#browsercontextsetpermissions-permissions) to grant permissions for the page to read its geolocation.
### class: ChromiumPlaywright
Playwright module provides a method to launch a Chromium instance.

View File

@ -31,6 +31,8 @@ export interface BrowserContextDelegate {
setPermissions(origin: string, permissions: string[]): Promise<void>;
clearPermissions(): Promise<void>;
setGeolocation(geolocation: types.Geolocation | null): Promise<void>;
}
export type BrowserContextOptions = {
@ -41,7 +43,8 @@ export type BrowserContextOptions = {
mediaType?: input.MediaType,
colorScheme?: input.ColorScheme,
userAgent?: string,
timezoneId?: string
timezoneId?: string,
geolocation?: types.Geolocation
};
export class BrowserContext {
@ -84,6 +87,21 @@ export class BrowserContext {
await this._delegate.clearPermissions();
}
async setGeolocation(geolocation: types.Geolocation | null): Promise<void> {
if (geolocation) {
geolocation.accuracy = geolocation.accuracy || 0;
const { longitude, latitude, accuracy } = geolocation;
if (longitude < -180 || longitude > 180)
throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
if (latitude < -90 || latitude > 90)
throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0)
throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
}
this._options.geolocation = geolocation;
await this._delegate.setGeolocation(geolocation);
}
async close() {
if (this._closed)
return;

View File

@ -8,5 +8,4 @@ export { CRPlaywright as ChromiumPlaywright } from './crPlaywright';
export { CRTarget as ChromiumTarget } from './crTarget';
export { CRAccessibility as ChromiumAccessibility } from './features/crAccessibility';
export { CRCoverage as ChromiumCoverage } from './features/crCoverage';
export { CROverrides as ChromiumOverrides } from './features/crOverrides';
export { CRWorker as ChromiumWorker } from './features/crWorkers';

View File

@ -26,7 +26,7 @@ import { Protocol } from './protocol';
import { CRPage } from './crPage';
import * as browser from '../browser';
import * as network from '../network';
import { CROverrides } from './features/crOverrides';
import * as types from '../types';
import { CRWorker } from './features/crWorkers';
import { ConnectionTransport } from '../transport';
import { readProtocolStream } from './crProtocolHelper';
@ -137,10 +137,13 @@ export class CRBrowser extends browser.Browser {
clearPermissions: async () => {
await this._client.send('Browser.resetPermissions', { browserContextId: contextId || undefined });
}
},
setGeolocation: async (geolocation: types.Geolocation | null): Promise<void> => {
for (const page of await context.pages())
await (page._delegate as CRPage)._client.send('Emulation.setGeolocationOverride', geolocation || {});
}
}, options);
(context as any).overrides = new CROverrides(context);
return context;
}

View File

@ -38,7 +38,6 @@ import { BrowserContext } from '../browserContext';
import * as types from '../types';
import * as input from '../input';
import { ConsoleMessage } from '../console';
import { CROverrides } from './features/crOverrides';
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
@ -93,7 +92,6 @@ export class CRPage implements PageDelegate {
this._client.send('Page.setLifecycleEventsEnabled', { enabled: true }),
this._client.send('Runtime.enable', {}).then(() => this._ensureIsolatedWorld(UTILITY_WORLD_NAME)),
this._networkManager.initialize(),
((this._page.browserContext() as any).overrides as CROverrides)._applyOverrides(this),
];
const options = this._page.browserContext()._options;
if (options.bypassCSP)
@ -112,6 +110,8 @@ export class CRPage implements PageDelegate {
}
if (options.timezoneId)
promises.push(emulateTimezone(this._client, options.timezoneId));
if (options.geolocation)
promises.push(this._client.send('Emulation.setGeolocationOverride', options.geolocation));
await Promise.all(promises);
}

View File

@ -1,53 +0,0 @@
/**
* Copyright 2017 Google Inc. All rights reserved.
* Modifications copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { BrowserContext } from '../../browserContext';
import { CRPage } from '../crPage';
export class CROverrides {
private _context: BrowserContext;
private _geolocation: { longitude?: number; latitude?: number; accuracy?: number; } | null = null;
constructor(context: BrowserContext) {
this._context = context;
}
async setGeolocation(options: { longitude?: number; latitude?: number; accuracy?: (number | undefined); } | null) {
if (!options) {
for (const page of await this._context.pages())
await (page._delegate as CRPage)._client.send('Emulation.clearGeolocationOverride', {});
this._geolocation = null;
return;
}
const { longitude, latitude, accuracy = 0} = options;
if (longitude < -180 || longitude > 180)
throw new Error(`Invalid longitude "${longitude}": precondition -180 <= LONGITUDE <= 180 failed.`);
if (latitude < -90 || latitude > 90)
throw new Error(`Invalid latitude "${latitude}": precondition -90 <= LATITUDE <= 90 failed.`);
if (accuracy < 0)
throw new Error(`Invalid accuracy "${accuracy}": precondition 0 <= ACCURACY failed.`);
this._geolocation = { longitude, latitude, accuracy };
for (const page of await this._context.pages())
await (page._delegate as CRPage)._client.send('Emulation.setGeolocationOverride', this._geolocation);
}
async _applyOverrides(page: CRPage): Promise<void> {
if (this._geolocation)
await page._client.send('Emulation.setGeolocationOverride', this._geolocation);
}
}

View File

@ -20,6 +20,7 @@ import { BrowserContext, BrowserContextOptions } from '../browserContext';
import { Events } from '../events';
import { assert, helper, RegisteredListener } from '../helper';
import * as network from '../network';
import * as types from '../types';
import { Page } from '../page';
import { ConnectionTransport } from '../transport';
import { ConnectionEvents, FFConnection, FFSessionEvents } from './ffConnection';
@ -199,8 +200,11 @@ export class FFBrowser extends browser.Browser {
clearPermissions: async () => {
await this._connection.send('Browser.resetPermissions', { browserContextId: browserContextId || undefined });
}
},
setGeolocation: async (geolocation: types.Geolocation | null): Promise<void> => {
throw new Error('Geolocation emulation is not supported in Firefox');
}
}, options);
return context;
}

View File

@ -56,3 +56,9 @@ export type Credentials = {
username: string;
password: string;
}
export type Geolocation = {
longitude?: number;
latitude?: number;
accuracy?: number | undefined;
}

View File

@ -19,6 +19,7 @@ import { EventEmitter } from 'events';
import { helper, RegisteredListener, debugError, assert } from '../helper';
import * as browser from '../browser';
import * as network from '../network';
import * as types from '../types';
import { WKConnection, WKConnectionEvents, WKTargetSession } from './wkConnection';
import { Page } from '../page';
import { WKTarget } from './wkTarget';
@ -203,12 +204,27 @@ export class WKBrowser extends browser.Browser {
await this._connection.send('Browser.setCookies', { cookies: cc, browserContextId });
},
setPermissions: async (origin: string, permissions: string[]): Promise<void> => {
throw new Error('Permissions are not supported in WebKit');
const webPermissionToProtocol = new Map<string, string>([
['geolocation', 'geolocation'],
]);
const filtered = permissions.map(permission => {
const protocolPermission = webPermissionToProtocol.get(permission);
if (!protocolPermission)
throw new Error('Unknown permission: ' + permission);
return protocolPermission;
});
await this._connection.send('Browser.grantPermissions', { origin, browserContextId, permissions: filtered });
},
clearPermissions: async () => {
throw new Error('Permissions are not supported in WebKit');
await this._connection.send('Browser.resetPermissions', { browserContextId });
},
setGeolocation: async (geolocation: types.Geolocation | null): Promise<void> => {
const payload: any = geolocation ? { ...geolocation, timestamp: Date.now() } : undefined;
await this._connection.send('Browser.setGeolocationOverride', { browserContextId, geolocation: payload });
}
}, options);
return context;

View File

@ -15,18 +15,18 @@
* limitations under the License.
*/
module.exports.describe = function ({ testRunner, expect }) {
module.exports.describe = function ({ testRunner, expect, FFOX }) {
const {describe, xdescribe, fdescribe} = testRunner;
const {it, fit, xit, dit} = testRunner;
const {beforeAll, beforeEach, afterAll, afterEach} = testRunner;
// FIXME: not supported in WebKit (as well as Emulation domain in general).
// It was removed from WebKit in https://webkit.org/b/126630
describe('Overrides.setGeolocation', function() {
describe.skip(FFOX)('Overrides.setGeolocation', function() {
it('should work', async({page, server, context}) => {
await context.setPermissions(server.PREFIX, ['geolocation']);
await page.goto(server.EMPTY_PAGE);
await context.overrides.setGeolocation({longitude: 10, latitude: 10});
await context.setGeolocation({longitude: 10, latitude: 10});
const geolocation = await page.evaluate(() => new Promise(resolve => navigator.geolocation.getCurrentPosition(position => {
resolve({latitude: position.coords.latitude, longitude: position.coords.longitude});
})));
@ -38,7 +38,7 @@ module.exports.describe = function ({ testRunner, expect }) {
it('should throw when invalid longitude', async({context}) => {
let error = null;
try {
await context.overrides.setGeolocation({longitude: 200, latitude: 10});
await context.setGeolocation({longitude: 200, latitude: 10});
} catch (e) {
error = e;
}

View File

@ -166,11 +166,11 @@ module.exports.describe = ({testRunner, product, playwrightPath}) => {
testRunner.loadTests(require('./screenshot.spec.js'), testOptions);
testRunner.loadTests(require('./waittask.spec.js'), testOptions);
testRunner.loadTests(require('./interception.spec.js'), testOptions);
testRunner.loadTests(require('./geolocation.spec.js'), testOptions);
if (CHROME) {
testRunner.loadTests(require('./chromium/chromium.spec.js'), testOptions);
testRunner.loadTests(require('./chromium/coverage.spec.js'), testOptions);
testRunner.loadTests(require('./chromium/geolocation.spec.js'), testOptions);
testRunner.loadTests(require('./chromium/pdf.spec.js'), testOptions);
testRunner.loadTests(require('./chromium/session.spec.js'), testOptions);
testRunner.loadTests(require('./chromium/workers.spec.js'), testOptions);