From 21c0f91581a40c1b56a3b043d98ca41c6e7efb05 Mon Sep 17 00:00:00 2001 From: Rowan Merewood Date: Fri, 25 Jun 2021 10:01:44 +0100 Subject: [PATCH 1/6] feat(page): add User Agent Client Hints support Adds userAgentData to setUserAgent that supports specifying user agent data for the new navigator.userAgentData and Client Hints headers. --- docs/api.md | 19 +++++++++++++++---- src/common/NetworkManager.ts | 14 +++++++++++--- src/common/Page.ts | 11 +++++++++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/docs/api.md b/docs/api.md index 34ef02d644e9a..2d1d941a75c3f 100644 --- a/docs/api.md +++ b/docs/api.md @@ -180,7 +180,7 @@ * [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled) * [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled) * [page.setRequestInterception(value)](#pagesetrequestinterceptionvalue) - * [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) + * [page.setUserAgent(userAgent[, userAgentData])](#pagesetuseragentuseragent-useragentdata) * [page.setViewport(viewport)](#pagesetviewportviewport) * [page.tap(selector)](#pagetapselector) * [page.target()](#pagetarget) @@ -942,7 +942,7 @@ the method will return an array with all the targets in all browser contexts. - returns: <[Promise]<[string]>> Promise which resolves to the browser's original user agent. -> **NOTE** Pages can override browser user agent with [page.setUserAgent](#pagesetuseragentuseragent) +> **NOTE** Pages can override browser user agent with [page.setUserAgent](#pagesetuseragentuseragent-useragentdata) #### browser.version() @@ -1558,7 +1558,7 @@ const puppeteer = require('puppeteer'); Emulates given device metrics and user agent. This method is a shortcut for calling two methods: -- [page.setUserAgent(userAgent)](#pagesetuseragentuseragent) +- [page.setUserAgent(userAgent)](#pagesetuseragentuseragent-useragentdata) - [page.setViewport(viewport)](#pagesetviewportviewport) To aid emulation, Puppeteer provides a list of device descriptors that can be obtained via the [`puppeteer.devices`](#puppeteerdevices). @@ -2344,9 +2344,20 @@ const puppeteer = require('puppeteer'); })(); ``` -#### page.setUserAgent(userAgent) +#### page.setUserAgent(userAgent[, userAgentData]) - `userAgent` <[string]> Specific user agent to use in this page +- `userAgentData` <[Object]> Optional user agent data to use in this page. Any + values not provided will use the client's default. + - `brands` <...[Object]> + - `brand` <[string|> Browser or client brand name. + - `version` <[string|> Browser or client major version. + - `fullVersion` <[string|> Browser or client full version. + - `platform` <[string|> Operating system name. + - `platformVersion` <[string|> Operating system version. + - `architecture` <[string|> CPU architecture. + - `model` <[string|> Device model. + - `mobile` <[boolean|> Indicate if this is a mobile device. - returns: <[Promise]> Promise which resolves when the user agent is set. #### page.setViewport(viewport) diff --git a/src/common/NetworkManager.ts b/src/common/NetworkManager.ts index 0fdb778d4d54e..ac9b3261abb0b 100644 --- a/src/common/NetworkManager.ts +++ b/src/common/NetworkManager.ts @@ -47,7 +47,6 @@ export interface NetworkConditions { export interface InternalNetworkConditions extends NetworkConditions { offline: boolean; } - /** * We use symbols to prevent any external parties listening to these events. * They are internal to Puppeteer. @@ -218,8 +217,17 @@ export class NetworkManager extends EventEmitter { }); } - async setUserAgent(userAgent: string): Promise { - await this._client.send('Network.setUserAgentOverride', { userAgent }); + async setUserAgent( + userAgent: string, + userAgentData?: Protocol.Emulation.UserAgentMetadata + ): Promise { + const params = { userAgent: userAgent }; + + if (userAgentData !== undefined) { + params['userAgentData'] = userAgentData; + } + + await this._client.send('Network.setUserAgentOverride', params); } async setCacheEnabled(enabled: boolean): Promise { diff --git a/src/common/Page.ts b/src/common/Page.ts index 238d7bde360d5..95e42623d13e8 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -1362,10 +1362,17 @@ export class Page extends EventEmitter { /** * @param userAgent - Specific user agent to use in this page + * @param userAgentData - Specific user agent client hint data to use in this + * page * @returns Promise which resolves when the user agent is set. */ - async setUserAgent(userAgent: string): Promise { - return this._frameManager.networkManager().setUserAgent(userAgent); + async setUserAgent( + userAgent: string, + userAgentData?: Protocol.Emulation.UserAgentMetadata + ): Promise { + return this._frameManager + .networkManager() + .setUserAgent(userAgent, userAgentData); } /** From e2a88570949cfb92bfd7bbab6d043802bc406812 Mon Sep 17 00:00:00 2001 From: Rowan Merewood Date: Fri, 25 Jun 2021 10:23:10 +0100 Subject: [PATCH 2/6] feat(page): add User Agent Client Hints support Fix naming --- docs/api.md | 6 +++--- src/common/NetworkManager.ts | 6 +++--- src/common/Page.ts | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/api.md b/docs/api.md index 2d1d941a75c3f..454f4c1981fb9 100644 --- a/docs/api.md +++ b/docs/api.md @@ -180,7 +180,7 @@ * [page.setJavaScriptEnabled(enabled)](#pagesetjavascriptenabledenabled) * [page.setOfflineMode(enabled)](#pagesetofflinemodeenabled) * [page.setRequestInterception(value)](#pagesetrequestinterceptionvalue) - * [page.setUserAgent(userAgent[, userAgentData])](#pagesetuseragentuseragent-useragentdata) + * [page.setUserAgent(userAgent[, userAgentMetadata])](#pagesetuseragentuseragent-useragentmetadata) * [page.setViewport(viewport)](#pagesetviewportviewport) * [page.tap(selector)](#pagetapselector) * [page.target()](#pagetarget) @@ -2344,10 +2344,10 @@ const puppeteer = require('puppeteer'); })(); ``` -#### page.setUserAgent(userAgent[, userAgentData]) +#### page.setUserAgent(userAgent[, userAgentMetadata]) - `userAgent` <[string]> Specific user agent to use in this page -- `userAgentData` <[Object]> Optional user agent data to use in this page. Any +- `userAgentMetadata` <[Object]> Optional user agent data to use in this page. Any values not provided will use the client's default. - `brands` <...[Object]> - `brand` <[string|> Browser or client brand name. diff --git a/src/common/NetworkManager.ts b/src/common/NetworkManager.ts index ac9b3261abb0b..dd563231b5241 100644 --- a/src/common/NetworkManager.ts +++ b/src/common/NetworkManager.ts @@ -219,12 +219,12 @@ export class NetworkManager extends EventEmitter { async setUserAgent( userAgent: string, - userAgentData?: Protocol.Emulation.UserAgentMetadata + userAgentMetadata?: Protocol.Emulation.UserAgentMetadata ): Promise { const params = { userAgent: userAgent }; - if (userAgentData !== undefined) { - params['userAgentData'] = userAgentData; + if (userAgentMetadata !== undefined) { + params['userAgentMetadata'] = userAgentMetadata; } await this._client.send('Network.setUserAgentOverride', params); diff --git a/src/common/Page.ts b/src/common/Page.ts index 95e42623d13e8..25f5e46332b0f 100644 --- a/src/common/Page.ts +++ b/src/common/Page.ts @@ -1368,11 +1368,11 @@ export class Page extends EventEmitter { */ async setUserAgent( userAgent: string, - userAgentData?: Protocol.Emulation.UserAgentMetadata + userAgentMetadata?: Protocol.Emulation.UserAgentMetadata ): Promise { return this._frameManager .networkManager() - .setUserAgent(userAgent, userAgentData); + .setUserAgent(userAgent, userAgentMetadata); } /** From b881d01c333f8e17be45c629749511bf50addbc6 Mon Sep 17 00:00:00 2001 From: Rowan Merewood Date: Fri, 25 Jun 2021 15:28:18 +0100 Subject: [PATCH 3/6] feat(page): add User Agent Client Hints support Corrected documentation and tests for navigator.userAgentData Fixes #7269 --- docs/api.md | 18 +++++++------- src/common/NetworkManager.ts | 12 ++++------ test/page.spec.ts | 31 +++++++++++++++++++++++++ utils/doclint/check_public_api/index.js | 7 ++++++ 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/docs/api.md b/docs/api.md index 454f4c1981fb9..6ff23fc619695 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2349,15 +2349,15 @@ const puppeteer = require('puppeteer'); - `userAgent` <[string]> Specific user agent to use in this page - `userAgentMetadata` <[Object]> Optional user agent data to use in this page. Any values not provided will use the client's default. - - `brands` <...[Object]> - - `brand` <[string|> Browser or client brand name. - - `version` <[string|> Browser or client major version. - - `fullVersion` <[string|> Browser or client full version. - - `platform` <[string|> Operating system name. - - `platformVersion` <[string|> Operating system version. - - `architecture` <[string|> CPU architecture. - - `model` <[string|> Device model. - - `mobile` <[boolean|> Indicate if this is a mobile device. + - `brands` <[Array]<[Object]>> Optional brand information + - `brand` <[string]> Browser or client brand name. + - `version` <[string]> Browser or client major version. + - `fullVersion` <[string]> Optional browser or client full version. + - `platform` <[string]> Operating system name. + - `platformVersion` <[string]> Operating system version. + - `architecture` <[string]> CPU architecture. + - `model` <[string]> Device model. + - `mobile` <[boolean]> Indicate if this is a mobile device. - returns: <[Promise]> Promise which resolves when the user agent is set. #### page.setViewport(viewport) diff --git a/src/common/NetworkManager.ts b/src/common/NetworkManager.ts index dd563231b5241..ab4e6fe13aee3 100644 --- a/src/common/NetworkManager.ts +++ b/src/common/NetworkManager.ts @@ -47,6 +47,7 @@ export interface NetworkConditions { export interface InternalNetworkConditions extends NetworkConditions { offline: boolean; } + /** * We use symbols to prevent any external parties listening to these events. * They are internal to Puppeteer. @@ -221,13 +222,10 @@ export class NetworkManager extends EventEmitter { userAgent: string, userAgentMetadata?: Protocol.Emulation.UserAgentMetadata ): Promise { - const params = { userAgent: userAgent }; - - if (userAgentMetadata !== undefined) { - params['userAgentMetadata'] = userAgentMetadata; - } - - await this._client.send('Network.setUserAgentOverride', params); + await this._client.send('Network.setUserAgentOverride', { + userAgent: userAgent, + userAgentMetadata: userAgentMetadata, + }); } async setCacheEnabled(enabled: boolean): Promise { diff --git a/test/page.spec.ts b/test/page.spec.ts index de8b56f370d6f..b1103480966fa 100644 --- a/test/page.spec.ts +++ b/test/page.spec.ts @@ -998,6 +998,37 @@ describe('Page', function () { 'iPhone' ); }); + it('should work with additional userAgentMetdata', async () => { + const { page, server } = getTestState(); + + + await page.setUserAgent('MockBrowser', { + architecture: 'Mock1', + mobile: false, + model: 'Mockbook', + platform: 'MockOS', + platformVersion: '3.1', + }); + const [request] = await Promise.all([ + server.waitForRequest('/empty.html'), + page.goto(server.EMPTY_PAGE), + ]); + const uaData = await page.evaluate(() => + // @ts-ignore: userAgentData not yet in TypeScript DOM API + navigator.userAgentData.getHighEntropyValues([ + 'architecture', + 'model', + 'platform', + 'platformVersion', + ]) + ); + expect(uaData['architecture']).toBe('Mock1'); + expect(uaData.mobile).toBeFalsy(); + expect(uaData['model']).toBe('Mockbook'); + expect(uaData['platform']).toBe('MockOS'); + expect(uaData['platformVersion']).toBe('3.1'); + expect(request.headers['user-agent']).toBe('MockBrowser'); + }); }); describe('Page.setContent', function () { diff --git a/utils/doclint/check_public_api/index.js b/utils/doclint/check_public_api/index.js index cafd0cfd87772..9d96fa276077e 100644 --- a/utils/doclint/check_public_api/index.js +++ b/utils/doclint/check_public_api/index.js @@ -697,6 +697,13 @@ function compareDocumentations(actual, expected) { expectedName: 'NetworkConditions', }, ], + [ + 'Method Page.setUserAgent() userAgentMetadata', + { + actualName: 'Object', + expectedName: 'UserAgentMetadata', + }, + ], [ 'Method Page.setViewport() options.viewport', { From 5f0a0f00943cd24624558c429a585da989b6f176 Mon Sep 17 00:00:00 2001 From: Rowan Merewood Date: Mon, 28 Jun 2021 11:39:33 +0100 Subject: [PATCH 4/6] feat(page): add User-Agent Client Hints support Correct checking of navigator.userAgentData values --- test/page.spec.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/test/page.spec.ts b/test/page.spec.ts index b1103480966fa..0fcb665983236 100644 --- a/test/page.spec.ts +++ b/test/page.spec.ts @@ -1001,7 +1001,6 @@ describe('Page', function () { it('should work with additional userAgentMetdata', async () => { const { page, server } = getTestState(); - await page.setUserAgent('MockBrowser', { architecture: 'Mock1', mobile: false, @@ -1013,17 +1012,25 @@ describe('Page', function () { server.waitForRequest('/empty.html'), page.goto(server.EMPTY_PAGE), ]); - const uaData = await page.evaluate(() => + expect( + await page.evaluate(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore: userAgentData not yet in TypeScript DOM API + return navigator.userAgentData.mobile; + }) + ).toBe(false); + + const uaData = await page.evaluate(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore: userAgentData not yet in TypeScript DOM API - navigator.userAgentData.getHighEntropyValues([ + return navigator.userAgentData.getHighEntropyValues([ 'architecture', 'model', 'platform', 'platformVersion', - ]) - ); + ]); + }); expect(uaData['architecture']).toBe('Mock1'); - expect(uaData.mobile).toBeFalsy(); expect(uaData['model']).toBe('Mockbook'); expect(uaData['platform']).toBe('MockOS'); expect(uaData['platformVersion']).toBe('3.1'); From 36f202eefa6e68b7d2df7f8dbdcd4ed64d09a95a Mon Sep 17 00:00:00 2001 From: Jan Scheffler Date: Mon, 28 Jun 2021 18:04:21 +0200 Subject: [PATCH 5/6] chore: disable test for firefox --- test/page.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/page.spec.ts b/test/page.spec.ts index 0fcb665983236..f81851c5fad4b 100644 --- a/test/page.spec.ts +++ b/test/page.spec.ts @@ -998,7 +998,7 @@ describe('Page', function () { 'iPhone' ); }); - it('should work with additional userAgentMetdata', async () => { + itFailsFirefox('should work with additional userAgentMetdata', async () => { const { page, server } = getTestState(); await page.setUserAgent('MockBrowser', { From 1934e7166eb2a2a8b12ef68743d5c3b7aec8d0c9 Mon Sep 17 00:00:00 2001 From: Rowan Merewood Date: Tue, 29 Jun 2021 17:00:55 +0100 Subject: [PATCH 6/6] feat(page): add User Agent Client Hints support Corrected documentation and tests for navigator.userAgentData Fixes puppeteer#7269 --- docs/api.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/api.md b/docs/api.md index 6ff23fc619695..d80daf316ffc5 100644 --- a/docs/api.md +++ b/docs/api.md @@ -2360,6 +2360,23 @@ const puppeteer = require('puppeteer'); - `mobile` <[boolean]> Indicate if this is a mobile device. - returns: <[Promise]> Promise which resolves when the user agent is set. +> **NOTE** support for `userAgentMetadata` is experimental in the DevTools +> protocol and more properties will be added. + +Providing the optional `userAgentMetadata` header will update the related +entries in `navigator.userAgentData` and associated `Sec-CH-UA`* headers. + +```js +const page = await browser.newPage(); +await page.setUserAgent('MyBrowser', { + architecture: 'My1', + mobile: false, + model: 'Mybook', + platform: 'MyOS', + platformVersion: '3.1', +}); +``` + #### page.setViewport(viewport) - `viewport` <[Object]>