diff --git a/README.md b/README.md index 7267cb8e8..3f5e8354e 100644 --- a/README.md +++ b/README.md @@ -1089,7 +1089,7 @@ module.exports = (hermione) => { Allows to use a custom `http`/`https`/`http2` [agent](https://www.npmjs.com/package/got#agent) to make requests. Default value is `null` (it means that will be used default http-agent from got). #### headers -Allows to set custom [headers](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md#headers) to pass into every webdriver request. These headers aren't passed into browser request. Read more about this option in [wdio](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md#headers). Default value is `null`. +Allows to set custom [headers](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md#headers) to pass into every webdriver request. These headers aren't passed into browser request. Read more about this option in [wdio](https://github.com/sindresorhus/got/blob/main/documentation/2-options.md#headers). Default value is `{'X-Request-ID': ''}`. One `X-Request-ID` is generated for the entire test run. It can be useful if you manage the cloud with browsers yourself and collect logs with requests. #### transformRequest Allows to intercept [HTTP request options](https://github.com/sindresorhus/got#options) before a WebDriver request is made. Default value is `null`. If function is passed then it takes `RequestOptions` as the first argument and should return modified `RequestOptions`. Example: diff --git a/package-lock.json b/package-lock.json index 0b71cc9e0..bcef1a8a1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "uglifyify": "^3.0.4", "urijs": "^1.19.11", "url-join": "^4.0.1", + "uuid": "^9.0.1", "webdriverio": "8.13.4", "worker-farm": "^1.7.0", "yallist": "^3.1.1" @@ -4161,16 +4162,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/globals/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "optional": true, - "peer": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/@wdio/globals/node_modules/webdriver": { "version": "8.16.4", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.4.tgz", @@ -7425,14 +7416,6 @@ "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/devtools/node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/devtools/node_modules/which": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", @@ -10285,6 +10268,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", @@ -15484,10 +15476,13 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } @@ -19406,13 +19401,6 @@ "integrity": "sha512-pphNW/msgOUSkJbH58x8sqpq8uQj6b0ZKGxEsLKMUnGorRcDjrUaLS+39+/ub41JNTwrrMyJcUB8+YZs3mbwqw==", "optional": true }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "optional": true, - "peer": true - }, "webdriver": { "version": "8.16.4", "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.16.4.tgz", @@ -21958,11 +21946,6 @@ "ansi-regex": "^6.0.1" } }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" - }, "which": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/which/-/which-3.0.1.tgz", @@ -24050,6 +24033,12 @@ "requires": { "glob": "^7.1.3" } + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true } } }, @@ -28028,10 +28017,9 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" }, "v8-compile-cache-lib": { "version": "3.0.1", diff --git a/package.json b/package.json index c13a340fd..bf75eae19 100644 --- a/package.json +++ b/package.json @@ -73,6 +73,7 @@ "uglifyify": "^3.0.4", "urijs": "^1.19.11", "url-join": "^4.0.1", + "uuid": "^9.0.1", "webdriverio": "8.13.4", "worker-farm": "^1.7.0", "yallist": "^3.1.1" diff --git a/src/browser/existing-browser.js b/src/browser/existing-browser.js index cc71a37fe..d9124653a 100644 --- a/src/browser/existing-browser.js +++ b/src/browser/existing-browser.js @@ -169,6 +169,7 @@ module.exports = class ExistingBrowser extends Browser { return { pid: process.pid, browserVersion: this.version, + "X-Request-ID": this._config.headers["X-Request-ID"], ...this._config.meta, }; } diff --git a/src/config/browser-options.js b/src/config/browser-options.js index 244c0f670..79af74c3c 100644 --- a/src/config/browser-options.js +++ b/src/config/browser-options.js @@ -2,6 +2,7 @@ const _ = require("lodash"); const option = require("gemini-configparser").option; +const uuid = require("uuid"); const defaults = require("./defaults"); const optionsBuilder = require("./options-builder"); const utils = require("./utils"); @@ -305,7 +306,13 @@ function buildBrowserOptions(defaultFactory, extra) { outputDir: options.optionalString("outputDir"), agent: options.optionalObject("agent"), - headers: options.optionalObject("headers"), + headers: option({ + parseEnv: JSON.parse, + parseCli: JSON.parse, + defaultValue: defaultFactory("headers"), + validate: value => utils.assertObject(value, "headers"), + map: value => (value["X-Request-ID"] ? value : { "X-Request-ID": uuid.v4(), ...value }), + }), transformRequest: options.optionalFunction("transformRequest"), transformResponse: options.optionalFunction("transformResponse"), strictSSL: options.optionalBoolean("strictSSL"), diff --git a/src/config/defaults.js b/src/config/defaults.js index 400a1d216..c7ac82cf1 100644 --- a/src/config/defaults.js +++ b/src/config/defaults.js @@ -83,7 +83,7 @@ module.exports = { fileExtensions: [".js", ".mjs", ".ts", ".mts"], outputDir: null, agent: null, - headers: null, + headers: {}, transformRequest: null, transformResponse: null, strictSSL: null, diff --git a/src/config/utils.js b/src/config/utils.js index ac70f9a74..be0433785 100644 --- a/src/config/utils.js +++ b/src/config/utils.js @@ -20,6 +20,12 @@ exports.assertNonNegativeNumber = (value, name) => { } }; +exports.assertObject = (value, name) => { + if (!_.isPlainObject(value)) { + throw new Error(`"${name}" must be an object`); + } +}; + exports.assertOptionalObject = (value, name) => { if (!_.isNull(value) && !_.isPlainObject(value)) { throw new Error(`"${name}" must be an object`); diff --git a/test/src/browser/existing-browser.js b/test/src/browser/existing-browser.js index 40279383c..41106c526 100644 --- a/test/src/browser/existing-browser.js +++ b/test/src/browser/existing-browser.js @@ -74,6 +74,12 @@ describe("ExistingBrowser", () => { assert.propertyVal(browser.meta, "browserVersion", "10.1"); }); + it("should extend meta-info with 'X-Request-ID' from headers", () => { + const browser = mkBrowser_({ headers: { "X-Request-ID": "uniq-req-id" } }); + + assert.propertyVal(browser.meta, "X-Request-ID", "uniq-req-id"); + }); + it("should set meta-info with provided meta option", () => { const browser = mkBrowser_({ meta: { k1: "v1" } }); diff --git a/test/src/browser/new-browser.js b/test/src/browser/new-browser.js index 6455c956e..dad4dc88e 100644 --- a/test/src/browser/new-browser.js +++ b/test/src/browser/new-browser.js @@ -36,6 +36,7 @@ describe("NewBrowser", () => { connectionRetryTimeout: 3000, connectionRetryCount: 0, baseUrl: "http://base_url", + headers: {}, }); }); diff --git a/test/src/browser/utils.js b/test/src/browser/utils.js index f00fccae2..8531d0293 100644 --- a/test/src/browser/utils.js +++ b/test/src/browser/utils.js @@ -29,7 +29,7 @@ function createBrowserConfig_(opts = {}) { }, waitOrientationChange: true, agent: null, - headers: null, + headers: {}, transformRequest: null, transformResponse: null, strictSSL: null, diff --git a/test/src/config/browser-options.js b/test/src/config/browser-options.js index 5443da888..b50e933d6 100644 --- a/test/src/config/browser-options.js +++ b/test/src/config/browser-options.js @@ -1,7 +1,6 @@ "use strict"; const _ = require("lodash"); - const { Config } = require("src/config"); const defaults = require("src/config/defaults"); const { WEBDRIVER_PROTOCOL, DEVTOOLS_PROTOCOL, SAVE_HISTORY_MODE } = require("src/constants/config"); @@ -1626,34 +1625,75 @@ describe("config browser-options", () => { }); }); - ["agent", "headers"].forEach(option => { - describe(option, () => { - it(`should throw error if "${option}" is not an object`, () => { - const readConfig = _.set({}, "browsers.b1", mkBrowser_({ [option]: "string" })); + describe("agent", () => { + it(`should throw error if "agent" is not an object`, () => { + const readConfig = _.set({}, "browsers.b1", mkBrowser_({ agent: "string" })); - Config.read.returns(readConfig); + Config.read.returns(readConfig); - assert.throws(() => createConfig(), Error, `"${option}" must be an object`); - }); + assert.throws(() => createConfig(), Error, `"agent" must be an object`); + }); - it("should set a default value if it is not set in config", () => { + it("should set a default value if it is not set in config", () => { + const readConfig = _.set({}, "browsers.b1", mkBrowser_()); + Config.read.returns(readConfig); + + const config = createConfig(); + + assert.equal(config.agent, defaults.option); + }); + + it("should set provided value", () => { + const readConfig = _.set({}, "browsers.b1", mkBrowser_({ agent: { k1: "v1", k2: "v2" } })); + Config.read.returns(readConfig); + + const config = createConfig(); + + assert.deepEqual(config.browsers.b1.agent, { k1: "v1", k2: "v2" }); + }); + }); + + describe("headers", () => { + it("should throw error if option is not an object", () => { + const readConfig = _.set({}, "browsers.b1", mkBrowser_({ headers: null })); + + Config.read.returns(readConfig); + + assert.throws(() => createConfig(), Error, `"headers" must be an object`); + }); + + describe("should generate 'X-Request-ID'", () => { + it("if value is not set in config", () => { const readConfig = _.set({}, "browsers.b1", mkBrowser_()); Config.read.returns(readConfig); const config = createConfig(); - assert.equal(config[option], defaults[option]); + assert.typeOf(config.browsers.b1.headers["X-Request-ID"], "string"); + assert.isAbove(config.browsers.b1.headers["X-Request-ID"].length, 20); }); - it("should set provided value", () => { - const readConfig = _.set({}, "browsers.b1", mkBrowser_({ [option]: { k1: "v1", k2: "v2" } })); + it("if it is not specified in value", () => { + const readConfig = _.set({}, "browsers.b1", mkBrowser_({ headers: { k1: "v1" } })); Config.read.returns(readConfig); const config = createConfig(); - assert.deepEqual(config.browsers.b1[option], { k1: "v1", k2: "v2" }); + assert.typeOf(config.browsers.b1.headers["X-Request-ID"], "string"); + assert.isAbove(config.browsers.b1.headers["X-Request-ID"].length, 20); + assert.equal(config.browsers.b1.headers.k1, "v1"); }); }); + + it("should not generate 'X-Request-ID' if it is specified by user", () => { + const headers = { "X-Request-ID": "my-req-id" }; + const readConfig = _.set({}, "browsers.b1", mkBrowser_({ headers })); + Config.read.returns(readConfig); + + const config = createConfig(); + + assert.deepEqual(config.browsers.b1.headers, headers); + }); }); ["transformRequest", "transformResponse"].forEach(option => {