From d40cd6a9e9bf9182ab1552c52e0199cf8e1d00c5 Mon Sep 17 00:00:00 2001
From: DudaGod <dudkevich@yandex-team.ru>
Date: Mon, 25 Dec 2023 14:51:28 +0300
Subject: [PATCH] feat: generate "X-Request-ID" header for each browser request

---
 README.md                                     | 14 ++++
 src/browser-pool/basic-pool.js                |  3 +-
 src/browser/browser.js                        | 29 +++++--
 src/browser/existing-browser.js               | 12 +--
 src/browser/new-browser.js                    |  4 +-
 src/constants/browser.ts                      |  1 +
 src/runner/browser-agent.js                   |  6 +-
 src/runner/browser-runner.ts                  |  6 +-
 .../high-priority-browser-agent.js            |  4 +-
 src/runner/test-runner/regular-test-runner.js |  4 +-
 src/worker/hermione.ts                        |  1 +
 src/worker/runner/browser-agent.js            | 13 +--
 src/worker/runner/browser-pool.js             |  9 ++-
 src/worker/runner/index.js                    |  6 +-
 src/worker/runner/test-runner/index.js        |  4 +-
 test/src/browser-pool/basic-pool.js           |  4 +-
 test/src/browser/existing-browser.js          | 81 +++++++++++++++----
 test/src/browser/new-browser.js               | 68 +++++++++++++++-
 test/src/browser/utils.js                     | 11 ++-
 test/src/runner/browser-agent.js              |  2 +-
 test/src/runner/browser-runner.js             |  4 +-
 .../high-priority-browser-agent.js            |  6 +-
 .../runner/test-runner/regular-test-runner.js | 10 ++-
 test/src/worker/runner/browser-agent.js       | 10 ++-
 test/src/worker/runner/browser-pool.js        | 37 +++------
 test/src/worker/runner/index.js               |  8 +-
 test/src/worker/runner/test-runner/index.js   |  7 +-
 27 files changed, 261 insertions(+), 103 deletions(-)

diff --git a/README.md b/README.md
index 7267cb8e8..374884ad3 100644
--- a/README.md
+++ b/README.md
@@ -1098,6 +1098,20 @@ Allows to intercept [HTTP request options](https://github.com/sindresorhus/got#o
 (RequestOptions) => RequestOptions
 ```
 
+In runtime a unique `X-Request-ID` header is generated for each browser request which consists of `${FIRST_X_REQ_ID}__${LAST_X_REQ_ID}`, where:
+- `FIRST_X_REQ_ID` - unique uuid for each test (different for each retry), allows to find all requests related to a single test run;
+- `LAST_X_REQ_ID` - unique uuid for each browser request, allows to find one unique request in one test (together with `FIRST_X_REQ_ID`).
+
+Header `X-Request-ID` can be useful if you manage the cloud with browsers yourself and collect logs with requests. Real-world example: `2f31ffb7-369d-41f4-bbb8-77744615d2eb__e8d011d8-bb76-42b9-b80e-02f03b8d6fe1`.
+
+To override generated `X-Request-ID` to your own value you need specify it in `transformRequest` handler. Example:
+
+```javascript
+transformRequest: (req) => {
+    req.handler["X-Request-ID"] = "my_x_req_id";
+}
+```
+
 ####  transformResponse
 Allows to intercept [HTTP response object](https://github.com/sindresorhus/got#response) after a WebDriver response has arrived. Default value is `null`. If function is passed then it takes `Response` (original response object) as the first and `RequestOptions` as the second argument. Should return modified `Response`. Example:
 
diff --git a/src/browser-pool/basic-pool.js b/src/browser-pool/basic-pool.js
index 495ae8444..f1e539b61 100644
--- a/src/browser-pool/basic-pool.js
+++ b/src/browser-pool/basic-pool.js
@@ -23,8 +23,7 @@ module.exports = class BasicPool extends Pool {
     }
 
     async getBrowser(id, opts = {}) {
-        const { version } = opts;
-        const browser = Browser.create(this._config, id, version);
+        const browser = Browser.create(this._config, { ...opts, id });
 
         try {
             await browser.init();
diff --git a/src/browser/browser.js b/src/browser/browser.js
index 58d4619ba..0f79298fb 100644
--- a/src/browser/browser.js
+++ b/src/browser/browser.js
@@ -1,7 +1,9 @@
 "use strict";
 
+const crypto = require("crypto");
 const _ = require("lodash");
 const { SAVE_HISTORY_MODE } = require("../constants/config");
+const { X_REQUEST_ID_DELIMITER } = require("../constants/browser");
 const history = require("./history");
 const addRunStepCommand = require("./commands/runStep");
 
@@ -19,13 +21,14 @@ const CUSTOM_SESSION_OPTS = [
 ];
 
 module.exports = class Browser {
-    static create(config, id, version) {
-        return new this(config, id, version);
+    static create(config, opts) {
+        return new this(config, opts);
     }
 
-    constructor(config, id, version) {
-        this.id = id;
-        this.version = version;
+    constructor(config, opts) {
+        this.id = opts.id;
+        this.version = opts.version;
+        this.testXReqId = opts.testXReqId;
 
         this._config = config.forBrowser(this.id);
         this._debug = config.system.debug;
@@ -82,7 +85,21 @@ module.exports = class Browser {
 
     _getSessionOptsFromConfig(optNames = CUSTOM_SESSION_OPTS) {
         return optNames.reduce((options, optName) => {
-            if (!_.isNull(this._config[optName])) {
+            if (optName === "transformRequest") {
+                options[optName] = req => {
+                    if (!_.isNull(this._config[optName])) {
+                        req = this._config[optName](req);
+                    }
+
+                    if (!req.headers["X-Request-ID"]) {
+                        req.headers["X-Request-ID"] = `${
+                            this.testXReqId
+                        }${X_REQUEST_ID_DELIMITER}${crypto.randomUUID()}`;
+                    }
+
+                    return req;
+                };
+            } else if (!_.isNull(this._config[optName])) {
                 options[optName] = this._config[optName];
             }
 
diff --git a/src/browser/existing-browser.js b/src/browser/existing-browser.js
index d55ae8749..174cf119f 100644
--- a/src/browser/existing-browser.js
+++ b/src/browser/existing-browser.js
@@ -19,14 +19,14 @@ const { isSupportIsolation } = require("../utils/browser");
 const OPTIONAL_SESSION_OPTS = ["transformRequest", "transformResponse"];
 
 module.exports = class ExistingBrowser extends Browser {
-    static create(config, id, version, emitter) {
-        return new this(config, id, version, emitter);
+    static create(config, opts) {
+        return new this(config, opts);
     }
 
-    constructor(config, id, version, emitter) {
-        super(config, id, version);
+    constructor(config, opts) {
+        super(config, opts);
 
-        this._emitter = emitter;
+        this._emitter = opts.emitter;
         this._camera = Camera.create(this._config.screenshotMode, () => this._takeScreenshot());
 
         this._meta = this._initMeta();
@@ -169,7 +169,7 @@ module.exports = class ExistingBrowser extends Browser {
         return {
             pid: process.pid,
             browserVersion: this.version,
-            "X-Request-ID": this._config.headers["X-Request-ID"],
+            testXReqId: this.testXReqId,
             ...this._config.meta,
         };
     }
diff --git a/src/browser/new-browser.js b/src/browser/new-browser.js
index 6368085a0..011400e08 100644
--- a/src/browser/new-browser.js
+++ b/src/browser/new-browser.js
@@ -31,8 +31,8 @@ const headlessBrowserOptions = {
 };
 
 module.exports = class NewBrowser extends Browser {
-    constructor(config, id, version) {
-        super(config, id, version);
+    constructor(config, opts) {
+        super(config, opts);
 
         signalHandler.on("exit", () => this.quit());
     }
diff --git a/src/constants/browser.ts b/src/constants/browser.ts
index f7772cf45..cbbbe5eb4 100644
--- a/src/constants/browser.ts
+++ b/src/constants/browser.ts
@@ -1 +1,2 @@
 export const MIN_CHROME_VERSION_SUPPORT_ISOLATION = 93;
+export const X_REQUEST_ID_DELIMITER = "__";
diff --git a/src/runner/browser-agent.js b/src/runner/browser-agent.js
index 7d6252ef1..0bd43b343 100644
--- a/src/runner/browser-agent.js
+++ b/src/runner/browser-agent.js
@@ -1,11 +1,11 @@
 "use strict";
 
 module.exports = class BrowserAgent {
-    static create(id, version, pool) {
-        return new this(id, version, pool);
+    static create(opts = {}) {
+        return new this(opts);
     }
 
-    constructor(id, version, pool) {
+    constructor({ id, version, pool }) {
         this.browserId = id;
 
         this._version = version;
diff --git a/src/runner/browser-runner.ts b/src/runner/browser-runner.ts
index 583a3fc3e..75286dac2 100644
--- a/src/runner/browser-runner.ts
+++ b/src/runner/browser-runner.ts
@@ -60,7 +60,11 @@ export class BrowserRunner extends Runner {
     }
 
     private async _runTest(test: Test): Promise<void> {
-        const browserAgent = BrowserAgent.create(this._browserId, test.browserVersion, this.browserPool);
+        const browserAgent = BrowserAgent.create({
+            id: this._browserId,
+            version: test.browserVersion,
+            pool: this.browserPool,
+        });
         const runner = TestRunner.create(test, this.config, browserAgent);
 
         runner.on(MasterEvents.TEST_BEGIN, (test: Test) => this.suiteMonitor.testBegin(test));
diff --git a/src/runner/test-runner/high-priority-browser-agent.js b/src/runner/test-runner/high-priority-browser-agent.js
index 6099d358d..389d45cfe 100644
--- a/src/runner/test-runner/high-priority-browser-agent.js
+++ b/src/runner/test-runner/high-priority-browser-agent.js
@@ -9,8 +9,8 @@ module.exports = class HighPriorityBrowserAgent {
         this._browserAgent = browserAgent;
     }
 
-    getBrowser() {
-        return this._browserAgent.getBrowser({ highPriority: true });
+    getBrowser(opts = {}) {
+        return this._browserAgent.getBrowser({ ...opts, highPriority: true });
     }
 
     freeBrowser(...args) {
diff --git a/src/runner/test-runner/regular-test-runner.js b/src/runner/test-runner/regular-test-runner.js
index 449bd9c9a..f551498ec 100644
--- a/src/runner/test-runner/regular-test-runner.js
+++ b/src/runner/test-runner/regular-test-runner.js
@@ -1,5 +1,6 @@
 "use strict";
 
+const crypto = require("crypto");
 const _ = require("lodash");
 const { Runner } = require("../runner");
 const logger = require("../../utils/logger");
@@ -64,6 +65,7 @@ module.exports = class RegularTestRunner extends Runner {
             sessionCaps: this._browser.capabilities,
             sessionOpts: this._browser.publicAPI.options,
             file: this._test.file,
+            testXReqId: this._browser.testXReqId,
         });
     }
 
@@ -80,7 +82,7 @@ module.exports = class RegularTestRunner extends Runner {
 
     async _getBrowser() {
         try {
-            this._browser = await this._browserAgent.getBrowser();
+            this._browser = await this._browserAgent.getBrowser({ testXReqId: crypto.randomUUID() });
             this._test.sessionId = this._browser.sessionId;
 
             return this._browser;
diff --git a/src/worker/hermione.ts b/src/worker/hermione.ts
index 3f017654e..cd7ddb111 100644
--- a/src/worker/hermione.ts
+++ b/src/worker/hermione.ts
@@ -11,6 +11,7 @@ export interface WorkerRunTestOpts {
     sessionId: string;
     sessionCaps: WdioBrowser["capabilities"];
     sessionOpts: WdioBrowser["options"];
+    testXReqId: string;
 }
 
 export interface AssertViewResultsSuccess {
diff --git a/src/worker/runner/browser-agent.js b/src/worker/runner/browser-agent.js
index ce7f29783..43bf7d0e7 100644
--- a/src/worker/runner/browser-agent.js
+++ b/src/worker/runner/browser-agent.js
@@ -1,24 +1,25 @@
 "use strict";
 
 module.exports = class BrowserAgent {
-    static create(browserId, browserVersion, pool) {
-        return new BrowserAgent(browserId, browserVersion, pool);
+    static create(opts) {
+        return new this(opts);
     }
 
-    constructor(browserId, browserVersion, pool) {
-        this.browserId = browserId;
-        this.browserVersion = browserVersion;
+    constructor({ id, version, pool }) {
+        this.browserId = id;
+        this.browserVersion = version;
 
         this._pool = pool;
     }
 
-    getBrowser({ sessionId, sessionCaps, sessionOpts }) {
+    getBrowser({ sessionId, sessionCaps, sessionOpts, testXReqId }) {
         return this._pool.getBrowser({
             browserId: this.browserId,
             browserVersion: this.browserVersion,
             sessionId,
             sessionCaps,
             sessionOpts,
+            testXReqId,
         });
     }
 
diff --git a/src/worker/runner/browser-pool.js b/src/worker/runner/browser-pool.js
index 1796cae2e..c81ab468c 100644
--- a/src/worker/runner/browser-pool.js
+++ b/src/worker/runner/browser-pool.js
@@ -16,8 +16,13 @@ module.exports = class BrowserPool {
         this._calibrator = new Calibrator();
     }
 
-    async getBrowser({ browserId, browserVersion, sessionId, sessionCaps, sessionOpts }) {
-        const browser = Browser.create(this._config, browserId, browserVersion, this._emitter);
+    async getBrowser({ browserId, browserVersion, sessionId, sessionCaps, sessionOpts, testXReqId }) {
+        const browser = Browser.create(this._config, {
+            id: browserId,
+            version: browserVersion,
+            testXReqId,
+            emitter: this._emitter,
+        });
 
         try {
             await browser.init({ sessionId, sessionCaps, sessionOpts }, this._calibrator);
diff --git a/src/worker/runner/index.js b/src/worker/runner/index.js
index 4fcf3bbe8..e67f06041 100644
--- a/src/worker/runner/index.js
+++ b/src/worker/runner/index.js
@@ -27,12 +27,12 @@ module.exports = class Runner extends AsyncEmitter {
         ]);
     }
 
-    async runTest(fullTitle, { browserId, browserVersion, file, sessionId, sessionCaps, sessionOpts }) {
+    async runTest(fullTitle, { browserId, browserVersion, file, sessionId, sessionCaps, sessionOpts, testXReqId }) {
         const tests = await this._testParser.parse({ file, browserId });
         const test = tests.find(t => t.fullTitle() === fullTitle);
-        const browserAgent = BrowserAgent.create(browserId, browserVersion, this._browserPool);
+        const browserAgent = BrowserAgent.create({ id: browserId, version: browserVersion, pool: this._browserPool });
         const runner = TestRunner.create(test, this._config.forBrowser(browserId), browserAgent);
 
-        return runner.run({ sessionId, sessionCaps, sessionOpts });
+        return runner.run({ sessionId, sessionCaps, sessionOpts, testXReqId });
     }
 };
diff --git a/src/worker/runner/test-runner/index.js b/src/worker/runner/test-runner/index.js
index ace64318b..15e148580 100644
--- a/src/worker/runner/test-runner/index.js
+++ b/src/worker/runner/test-runner/index.js
@@ -22,14 +22,14 @@ module.exports = class TestRunner {
         this._browserAgent = browserAgent;
     }
 
-    async run({ sessionId, sessionCaps, sessionOpts }) {
+    async run({ sessionId, sessionCaps, sessionOpts, testXReqId }) {
         const test = this._test;
         const hermioneCtx = test.hermioneCtx || {};
 
         let browser;
 
         try {
-            browser = await this._browserAgent.getBrowser({ sessionId, sessionCaps, sessionOpts });
+            browser = await this._browserAgent.getBrowser({ sessionId, sessionCaps, sessionOpts, testXReqId });
         } catch (e) {
             throw Object.assign(e, { hermioneCtx });
         }
diff --git a/test/src/browser-pool/basic-pool.js b/test/src/browser-pool/basic-pool.js
index 5a46fbd48..f5095ed40 100644
--- a/test/src/browser-pool/basic-pool.js
+++ b/test/src/browser-pool/basic-pool.js
@@ -33,13 +33,13 @@ describe("browser-pool/basic-pool", () => {
 
         await mkPool_({ config }).getBrowser("broId");
 
-        assert.calledWith(Browser.create, config, "broId");
+        assert.calledWith(Browser.create, config, { id: "broId" });
     });
 
     it("should create new browser with specified version when requested", async () => {
         await mkPool_().getBrowser("broId", { version: "1.0" });
 
-        assert.calledWith(Browser.create, sinon.match.any, "broId", "1.0");
+        assert.calledWith(Browser.create, sinon.match.any, { id: "broId", version: "1.0" });
     });
 
     it("should init browser", async () => {
diff --git a/test/src/browser/existing-browser.js b/test/src/browser/existing-browser.js
index 4df1ca0a5..2d57a3c69 100644
--- a/test/src/browser/existing-browser.js
+++ b/test/src/browser/existing-browser.js
@@ -1,6 +1,7 @@
 "use strict";
 
 const { EventEmitter } = require("events");
+const crypto = require("crypto");
 const _ = require("lodash");
 const Promise = require("bluebird");
 const webdriverio = require("webdriverio");
@@ -12,7 +13,7 @@ const clientBridge = require("src/browser/client-bridge");
 const logger = require("src/utils/logger");
 const history = require("src/browser/history");
 const { SAVE_HISTORY_MODE, WEBDRIVER_PROTOCOL, DEVTOOLS_PROTOCOL } = require("src/constants/config");
-const { MIN_CHROME_VERSION_SUPPORT_ISOLATION } = require("src/constants/browser");
+const { MIN_CHROME_VERSION_SUPPORT_ISOLATION, X_REQUEST_ID_DELIMITER } = require("src/constants/browser");
 const {
     mkExistingBrowser_: mkBrowser_,
     mkSessionStub_,
@@ -56,7 +57,7 @@ describe("ExistingBrowser", () => {
         it("should set emitter", () => {
             const emitter = new EventEmitter();
 
-            const browser = mkBrowser_({}, "bro", null, emitter);
+            const browser = mkBrowser_({}, { emitter });
 
             assert.deepEqual(browser.emitter, emitter);
         });
@@ -69,7 +70,7 @@ describe("ExistingBrowser", () => {
             });
 
             it("should extend meta-info with browserVersion by default", () => {
-                const browser = mkBrowser_({}, "bro-id", "10.1");
+                const browser = mkBrowser_({}, { id: "bro-id", version: "10.1" });
 
                 assert.propertyVal(browser.meta, "browserVersion", "10.1");
             });
@@ -79,6 +80,12 @@ describe("ExistingBrowser", () => {
 
                 assert.propertyVal(browser.meta, "k1", "v1");
             });
+
+            it('should extend meta-info with "testXReqId" field', () => {
+                const browser = mkBrowser_({}, { testXReqId: "12345" });
+
+                assert.propertyVal(browser.meta, "testXReqId", "12345");
+            });
         });
 
         describe("Camera", () => {
@@ -136,21 +143,63 @@ describe("ExistingBrowser", () => {
             assert.calledWithMatch(webdriverio.attach, { foo: "bar" });
         });
 
-        it('should attach to browser with "transform*" options from browser config', async () => {
-            const transformRequestStub = sinon.stub();
-            const transformResponseStub = sinon.stub();
+        describe("transformRequest option", () => {
+            beforeEach(() => {
+                sandbox.stub(crypto, "randomUUID").returns("00000");
+            });
 
-            await initBrowser_(
-                mkBrowser_({
-                    transformRequest: transformRequestStub,
-                    transformResponse: transformResponseStub,
-                }),
-            );
+            it("should call user handler from config", async () => {
+                const request = { headers: {} };
+                const transformRequestStub = sinon.stub().returns(request);
 
-            assert.calledOnce(webdriverio.attach);
-            assert.calledWithMatch(webdriverio.attach, {
-                transformRequest: transformRequestStub,
-                transformResponse: transformResponseStub,
+                await initBrowser_(mkBrowser_({ transformRequest: transformRequestStub }));
+
+                const { transformRequest } = webdriverio.attach.lastCall.args[0];
+                transformRequest(request);
+
+                assert.calledOnceWith(transformRequestStub, request);
+            });
+
+            it('should not add "X-Request-ID" header if it is already add by user', async () => {
+                const request = { headers: {} };
+                const transformRequestStub = req => {
+                    req.headers["X-Request-ID"] = "100500";
+                    return req;
+                };
+
+                await initBrowser_(mkBrowser_({ transformRequest: transformRequestStub }));
+
+                const { transformRequest } = webdriverio.attach.lastCall.args[0];
+                transformRequest(request);
+
+                assert.equal(request.headers["X-Request-ID"], "100500");
+            });
+
+            it('should add "X-Request-ID" header', async () => {
+                crypto.randomUUID.returns("67890");
+                const testXReqId = "12345";
+                const request = { headers: {} };
+
+                await initBrowser_(mkBrowser_({}, { testXReqId }));
+
+                const { transformRequest } = webdriverio.attach.lastCall.args[0];
+                transformRequest(request);
+
+                assert.equal(request.headers["X-Request-ID"], `12345${X_REQUEST_ID_DELIMITER}67890`);
+            });
+        });
+
+        describe("transformResponse option", () => {
+            it("should call user handler from config", async () => {
+                const transformResponseStub = sinon.stub();
+                const response = {};
+
+                await initBrowser_(mkBrowser_({ transformResponse: transformResponseStub }));
+
+                const { transformResponse } = webdriverio.attach.lastCall.args[0];
+                transformResponse(response);
+
+                assert.calledOnceWith(transformResponseStub, response);
             });
         });
 
diff --git a/test/src/browser/new-browser.js b/test/src/browser/new-browser.js
index 6455c956e..47ae5e96a 100644
--- a/test/src/browser/new-browser.js
+++ b/test/src/browser/new-browser.js
@@ -1,10 +1,12 @@
 "use strict";
 
+const crypto = require("crypto");
 const webdriverio = require("webdriverio");
 const logger = require("src/utils/logger");
 const signalHandler = require("src/signal-handler");
 const history = require("src/browser/history");
 const { WEBDRIVER_PROTOCOL, SAVE_HISTORY_MODE } = require("src/constants/config");
+const { X_REQUEST_ID_DELIMITER } = require("src/constants/browser");
 const { mkNewBrowser_: mkBrowser_, mkSessionStub_ } = require("./utils");
 
 describe("NewBrowser", () => {
@@ -36,6 +38,7 @@ describe("NewBrowser", () => {
                 connectionRetryTimeout: 3000,
                 connectionRetryCount: 0,
                 baseUrl: "http://base_url",
+                transformRequest: sinon.match.func,
             });
         });
 
@@ -116,8 +119,7 @@ describe("NewBrowser", () => {
             it("it is already exists in capabilities", async () => {
                 await mkBrowser_(
                     { desiredCapabilities: { browserName: "browser", browserVersion: "1.0" } },
-                    "browser",
-                    "2.0",
+                    { id: "browser", version: "2.0" },
                 ).init();
 
                 assert.calledWithMatch(webdriverio.remote, {
@@ -126,7 +128,7 @@ describe("NewBrowser", () => {
             });
 
             it("w3c protocol is used", async () => {
-                await mkBrowser_({ sessionEnvFlags: { isW3C: true } }, "browser", "2.0").init();
+                await mkBrowser_({ sessionEnvFlags: { isW3C: true } }, { id: "browser", version: "2.0" }).init();
 
                 assert.calledWithMatch(webdriverio.remote, {
                     capabilities: { browserName: "browser", browserVersion: "2.0" },
@@ -188,6 +190,66 @@ describe("NewBrowser", () => {
             assert.notCalled(session.setTimeouts);
         });
 
+        describe("transformRequest option", () => {
+            beforeEach(() => {
+                sandbox.stub(crypto, "randomUUID").returns("00000");
+            });
+
+            it("should call user handler from config", async () => {
+                const request = { headers: {} };
+                const transformRequestStub = sinon.stub().returns(request);
+
+                await mkBrowser_({ transformRequest: transformRequestStub }).init();
+
+                const { transformRequest } = webdriverio.remote.lastCall.args[0];
+                transformRequest(request);
+
+                assert.calledOnceWith(transformRequestStub, request);
+            });
+
+            it('should not add "X-Request-ID" header if it is already add by user', async () => {
+                const request = { headers: {} };
+                const transformRequestStub = req => {
+                    req.headers["X-Request-ID"] = "100500";
+                    return req;
+                };
+
+                await mkBrowser_({ transformRequest: transformRequestStub }).init();
+
+                const { transformRequest } = webdriverio.remote.lastCall.args[0];
+                transformRequest(request);
+
+                assert.equal(request.headers["X-Request-ID"], "100500");
+            });
+
+            it('should add "X-Request-ID" header', async () => {
+                crypto.randomUUID.returns("67890");
+                const testXReqId = "12345";
+                const request = { headers: {} };
+
+                await mkBrowser_({}, { testXReqId }).init();
+
+                const { transformRequest } = webdriverio.remote.lastCall.args[0];
+                transformRequest(request);
+
+                assert.equal(request.headers["X-Request-ID"], `12345${X_REQUEST_ID_DELIMITER}67890`);
+            });
+        });
+
+        describe("transformResponse option", () => {
+            it("should call user handler from config", async () => {
+                const transformResponseStub = sinon.stub();
+                const response = {};
+
+                await mkBrowser_({ transformResponse: transformResponseStub }).init();
+
+                const { transformResponse } = webdriverio.remote.lastCall.args[0];
+                transformResponse(response);
+
+                assert.calledOnceWith(transformResponseStub, response);
+            });
+        });
+
         describe("commands-history", () => {
             beforeEach(() => {
                 sandbox.spy(history, "initCommandHistory");
diff --git a/test/src/browser/utils.js b/test/src/browser/utils.js
index 9f8d8459d..53919c789 100644
--- a/test/src/browser/utils.js
+++ b/test/src/browser/utils.js
@@ -49,12 +49,15 @@ function createBrowserConfig_(opts = {}) {
     };
 }
 
-exports.mkNewBrowser_ = (opts, browser = "browser", version) => {
-    return NewBrowser.create(createBrowserConfig_(opts), browser, version);
+exports.mkNewBrowser_ = (configOpts, opts = { id: "browser", version: "1.0", testXReqId: "" }) => {
+    return NewBrowser.create(createBrowserConfig_(configOpts), opts);
 };
 
-exports.mkExistingBrowser_ = (opts, browser = "browser", browserVersion, emitter = "emitter") => {
-    return ExistingBrowser.create(createBrowserConfig_(opts), browser, browserVersion, emitter);
+exports.mkExistingBrowser_ = (
+    configOpts,
+    opts = { id: "browser", version: "1.0", testXReqId: "", emitter: "emitter" },
+) => {
+    return ExistingBrowser.create(createBrowserConfig_(configOpts), opts);
 };
 
 exports.mkMockStub_ = () => {
diff --git a/test/src/runner/browser-agent.js b/test/src/runner/browser-agent.js
index b442ba4db..f546549be 100644
--- a/test/src/runner/browser-agent.js
+++ b/test/src/runner/browser-agent.js
@@ -11,7 +11,7 @@ describe("runner/browser-agent", () => {
         version = version || "some.default.version";
         pool = pool || Object.create(BrowserPool.prototype);
 
-        return BrowserAgent.create(id, version, pool);
+        return BrowserAgent.create({ id, version, pool });
     }
 
     function mkBrowser_(opts = {}) {
diff --git a/test/src/runner/browser-runner.js b/test/src/runner/browser-runner.js
index 56296361f..6e4cf5ef9 100644
--- a/test/src/runner/browser-runner.js
+++ b/test/src/runner/browser-runner.js
@@ -155,8 +155,8 @@ describe("runner/browser-runner", () => {
             await run_({ runner });
 
             assert.calledTwice(BrowserAgent.create);
-            assert.calledWith(BrowserAgent.create.firstCall, "bro", "1.0", pool);
-            assert.calledWith(BrowserAgent.create.secondCall, "bro", "1.0", pool);
+            assert.calledWith(BrowserAgent.create.firstCall, { id: "bro", version: "1.0", pool });
+            assert.calledWith(BrowserAgent.create.secondCall, { id: "bro", version: "1.0", pool });
         });
 
         it("should create test runner for each test in collection", async () => {
diff --git a/test/src/runner/test-runner/high-priority-browser-agent.js b/test/src/runner/test-runner/high-priority-browser-agent.js
index 44bc55264..f7c7893ab 100644
--- a/test/src/runner/test-runner/high-priority-browser-agent.js
+++ b/test/src/runner/test-runner/high-priority-browser-agent.js
@@ -5,13 +5,13 @@ const HighPriorityBrowserAgent = require("src/runner/test-runner/high-priority-b
 
 describe("runner/test-runner/high-priority-browser-agent", () => {
     describe("getBrowser", () => {
-        it("should call base browser agent getBrowser with highPriority option", () => {
+        it("should call base browser agent getBrowser with p highPriority option", () => {
             const browserAgent = sinon.createStubInstance(BrowserAgent);
             const highPriorityBrowserAgent = HighPriorityBrowserAgent.create(browserAgent);
 
-            highPriorityBrowserAgent.getBrowser();
+            highPriorityBrowserAgent.getBrowser({ foo: "bar" });
 
-            assert.calledOnceWith(browserAgent.getBrowser, { highPriority: true });
+            assert.calledOnceWith(browserAgent.getBrowser, { foo: "bar", highPriority: true });
         });
 
         it("should return base browser agent getBrowser result", () => {
diff --git a/test/src/runner/test-runner/regular-test-runner.js b/test/src/runner/test-runner/regular-test-runner.js
index 0e8a2e6d4..7b3a0e2da 100644
--- a/test/src/runner/test-runner/regular-test-runner.js
+++ b/test/src/runner/test-runner/regular-test-runner.js
@@ -1,5 +1,6 @@
 "use strict";
 
+const crypto = require("crypto");
 const _ = require("lodash");
 
 const BrowserAgent = require("src/runner/browser-agent");
@@ -32,7 +33,7 @@ describe("runner/test-runner/regular-test-runner", () => {
 
     const mkRunner_ = (opts = {}) => {
         const test = opts.test || new Test({ title: "defaultTest" });
-        const browserAgent = opts.browserAgent || BrowserAgent.create();
+        const browserAgent = opts.browserAgent || BrowserAgent.create({});
 
         return RegularTestRunner.create(test, browserAgent);
     };
@@ -70,6 +71,7 @@ describe("runner/test-runner/regular-test-runner", () => {
         sandbox.stub(AssertViewResults.prototype, "get").returns({});
 
         sandbox.stub(logger, "warn");
+        sandbox.stub(crypto, "randomUUID").returns("");
     });
 
     afterEach(() => sandbox.restore());
@@ -87,7 +89,9 @@ describe("runner/test-runner/regular-test-runner", () => {
 
     describe("run", () => {
         it("should get browser before running test", async () => {
-            BrowserAgent.prototype.getBrowser.resolves(
+            const testXReqId = "12345";
+            crypto.randomUUID.returns(testXReqId);
+            BrowserAgent.prototype.getBrowser.withArgs({ testXReqId }).resolves(
                 stubBrowser_({
                     id: "bro",
                     version: "1.0",
@@ -96,6 +100,7 @@ describe("runner/test-runner/regular-test-runner", () => {
                     publicAPI: {
                         options: { foo: "bar" },
                     },
+                    testXReqId,
                 }),
             );
             const workers = mkWorkers_();
@@ -111,6 +116,7 @@ describe("runner/test-runner/regular-test-runner", () => {
                     sessionId: "100500",
                     sessionCaps: { browserName: "bro" },
                     sessionOpts: { foo: "bar" },
+                    testXReqId,
                 }),
             );
         });
diff --git a/test/src/worker/runner/browser-agent.js b/test/src/worker/runner/browser-agent.js
index 6d5baf937..486d7d74b 100644
--- a/test/src/worker/runner/browser-agent.js
+++ b/test/src/worker/runner/browser-agent.js
@@ -17,14 +17,16 @@ describe("worker/browser-agent", () => {
                     sessionId: "100-500",
                     sessionCaps: "some-caps",
                     sessionOpts: "some-opts",
+                    testXReqId: "12345",
                 })
                 .returns({ some: "browser" });
-            const browserAgent = BrowserAgent.create("bro-id", null, browserPool);
+            const browserAgent = BrowserAgent.create({ id: "bro-id", version: null, pool: browserPool });
 
             const browser = browserAgent.getBrowser({
                 sessionId: "100-500",
                 sessionCaps: "some-caps",
                 sessionOpts: "some-opts",
+                testXReqId: "12345",
             });
 
             assert.deepEqual(browser, { some: "browser" });
@@ -38,14 +40,16 @@ describe("worker/browser-agent", () => {
                     sessionId: "100-500",
                     sessionCaps: "some-caps",
                     sessionOpts: "some-opts",
+                    testXReqId: "12345",
                 })
                 .returns({ some: "browser" });
-            const browserAgent = BrowserAgent.create("bro-id", "10.1", browserPool);
+            const browserAgent = BrowserAgent.create({ id: "bro-id", version: "10.1", pool: browserPool });
 
             const browser = browserAgent.getBrowser({
                 sessionId: "100-500",
                 sessionCaps: "some-caps",
                 sessionOpts: "some-opts",
+                testXReqId: "12345",
             });
 
             assert.deepEqual(browser, { some: "browser" });
@@ -54,7 +58,7 @@ describe("worker/browser-agent", () => {
 
     describe("freeBrowser", () => {
         it("should free the browser in the pool", () => {
-            BrowserAgent.create(null, null, browserPool).freeBrowser({ some: "browser" });
+            BrowserAgent.create({ pool: browserPool }).freeBrowser({ some: "browser" });
 
             assert.calledOnceWith(browserPool.freeBrowser, { some: "browser" });
         });
diff --git a/test/src/worker/runner/browser-pool.js b/test/src/worker/runner/browser-pool.js
index cacd8180a..d7b029ad3 100644
--- a/test/src/worker/runner/browser-pool.js
+++ b/test/src/worker/runner/browser-pool.js
@@ -52,24 +52,16 @@ describe("worker/browser-pool", () => {
             const config = stubConfig();
             const emitter = new EventEmitter();
             const browserPool = createPool({ config, emitter });
-            const browser = stubBrowser({ browserId: "bro-id" });
-            Browser.create.withArgs(config, "bro-id", undefined, emitter).returns(browser);
-
-            await browserPool.getBrowser({ browserId: "bro-id" });
-
-            assert.calledOnceWith(Browser.create, config, "bro-id", undefined, emitter);
-        });
-
-        it("should create specific version of browser with correct args", async () => {
-            const config = stubConfig();
-            const emitter = new EventEmitter();
-            const browserPool = createPool({ config, emitter });
-            const browser = stubBrowser({ browserId: "bro-id" });
-            Browser.create.withArgs(config, "bro-id", "10.1", emitter).returns(browser);
+            Browser.create.returns(stubBrowser({ browserId: "bro-id" }));
 
-            await browserPool.getBrowser({ browserId: "bro-id", browserVersion: "10.1" });
+            await browserPool.getBrowser({ browserId: "bro-id", browserVersion: "1.0", testXReqId: "12345" });
 
-            assert.calledOnceWith(Browser.create, config, "bro-id", "10.1", emitter);
+            assert.calledOnceWith(Browser.create, config, {
+                id: "bro-id",
+                version: "1.0",
+                testXReqId: "12345",
+                emitter,
+            });
         });
 
         it("should init a new created browser ", async () => {
@@ -108,22 +100,11 @@ describe("worker/browser-pool", () => {
             const config = stubConfig();
             const browserPool = createPool({ config });
             const browser = stubBrowser({ browserId: "bro-id" });
-
-            Browser.create.withArgs(config, "bro-id").returns(browser);
+            Browser.create.returns(browser);
 
             return assert.becomes(browserPool.getBrowser({ browserId: "bro-id" }), browser);
         });
 
-        it("should return a new createt browser with specific version", () => {
-            const config = stubConfig();
-            const browserPool = createPool({ config });
-            const browser = stubBrowser({ browserId: "bro-id" });
-
-            Browser.create.withArgs(config, "bro-id", "10.1").returns(browser);
-
-            return assert.becomes(browserPool.getBrowser({ browserId: "bro-id", browserVersion: "10.1" }), browser);
-        });
-
         describe("getting of browser fails", () => {
             beforeEach(() => {
                 sandbox.spy(BrowserPool.prototype, "freeBrowser");
diff --git a/test/src/worker/runner/index.js b/test/src/worker/runner/index.js
index 43d022258..c8025c3ba 100644
--- a/test/src/worker/runner/index.js
+++ b/test/src/worker/runner/index.js
@@ -93,15 +93,17 @@ describe("worker/runner", () => {
         });
 
         it("should create browser agent for test runner", async () => {
+            const pool = { browser: "pool" };
+            BrowserPool.create.returns(pool);
             const runner = mkRunner_();
 
             const test = makeTest({ fullTitle: () => "some test" });
             CachingTestParser.prototype.parse.resolves([test]);
 
             const browserAgent = Object.create(BrowserAgent.prototype);
-            BrowserAgent.create.withArgs("bro").returns(browserAgent);
+            BrowserAgent.create.withArgs({ id: "bro", version: "1.0", pool }).returns(browserAgent);
 
-            await runner.runTest("some test", { browserId: "bro" });
+            await runner.runTest("some test", { browserId: "bro", browserVersion: "1.0" });
 
             assert.calledOnceWith(TestRunner.create, test, sinon.match.any, browserAgent);
         });
@@ -128,12 +130,14 @@ describe("worker/runner", () => {
                 sessionId: "100500",
                 sessionCaps: "some-caps",
                 sessionOpts: "some-opts",
+                testXReqId: "12345",
             });
 
             assert.calledOnceWith(TestRunner.prototype.run, {
                 sessionId: "100500",
                 sessionCaps: "some-caps",
                 sessionOpts: "some-opts",
+                testXReqId: "12345",
             });
         });
     });
diff --git a/test/src/worker/runner/test-runner/index.js b/test/src/worker/runner/test-runner/index.js
index 29967a3ff..b322c0037 100644
--- a/test/src/worker/runner/test-runner/index.js
+++ b/test/src/worker/runner/test-runner/index.js
@@ -106,7 +106,12 @@ describe("worker/runner/test-runner", () => {
 
         it("should request browser for passed session", async () => {
             const runner = mkRunner_();
-            const opts = { sessionId: "100500", sessionCaps: "some-caps", sessionOpts: "some-opts" };
+            const opts = {
+                sessionId: "100500",
+                sessionCaps: "some-caps",
+                sessionOpts: "some-opts",
+                testXReqId: "12345",
+            };
 
             await runner.run(opts);