From 8b75d9260c06343287afbdf9bc793bbbf8059eb4 Mon Sep 17 00:00:00 2001
From: Alex Popov <iampopovich@gmail.com>
Date: Sat, 21 Jun 2025 16:59:49 +0700
Subject: [PATCH 1/7] Add environment variable support for driver executables
 in Chrome, Edge, Firefox, IE, and Safari

---
 javascript/selenium-webdriver/chrome.js  | 11 +++++++++--
 javascript/selenium-webdriver/edge.js    | 11 +++++++++--
 javascript/selenium-webdriver/firefox.js | 11 +++++++++--
 javascript/selenium-webdriver/ie.js      |  6 ++++--
 javascript/selenium-webdriver/safari.js  | 11 +++++++++--
 5 files changed, 40 insertions(+), 10 deletions(-)

diff --git a/javascript/selenium-webdriver/chrome.js b/javascript/selenium-webdriver/chrome.js
index 9d644ba9666f6..344c3d104efeb 100644
--- a/javascript/selenium-webdriver/chrome.js
+++ b/javascript/selenium-webdriver/chrome.js
@@ -128,6 +128,12 @@ const { Browser } = require('./lib/capabilities')
 const chromium = require('./chromium')
 const CHROME_CAPABILITY_KEY = 'goog:chromeOptions'
 
+/**
+ * Environment variable that defines the location of the ChromeDriver executable.
+ * @const {string}
+ */
+const CHROME_DRIVER_EXE_ENV_VAR = 'SE_CHROMEDRIVER'
+
 /** @type {remote.DriverService} */
 
 /**
@@ -138,14 +144,15 @@ const CHROME_CAPABILITY_KEY = 'goog:chromeOptions'
 class ServiceBuilder extends chromium.ServiceBuilder {
   /**
    * @param {string=} opt_exe Path to the server executable to use. If omitted,
-   *     the builder will attempt to locate the chromedriver on the current
+   *     the builder will attempt to use the chromedriver path from the
+   *     SE_CHROMEDRIVER environment variable, then locate the chromedriver on the current
    *     PATH. If the chromedriver is not available in path, selenium-manager will
    *     download the chromedriver
    * @throws {Error} If provided executable does not exist, or the chromedriver
    *     cannot be found on the PATH.
    */
   constructor(opt_exe) {
-    super(opt_exe)
+    super(opt_exe || process.env[CHROME_DRIVER_EXE_ENV_VAR])
   }
 }
 
diff --git a/javascript/selenium-webdriver/edge.js b/javascript/selenium-webdriver/edge.js
index 7b633804f6831..d6f297e289d78 100644
--- a/javascript/selenium-webdriver/edge.js
+++ b/javascript/selenium-webdriver/edge.js
@@ -83,6 +83,12 @@ const { Browser } = require('./lib/capabilities')
 const chromium = require('./chromium')
 const EDGE_CAPABILITY_KEY = 'ms:edgeOptions'
 
+/**
+ * Environment variable that defines the location of the MSEdgeDriver executable.
+ * @const {string}
+ */
+const EDGE_DRIVER_EXE_ENV_VAR = 'SE_EDGEDRIVER'
+
 /** @type {remote.DriverService} */
 
 /**
@@ -93,13 +99,14 @@ const EDGE_CAPABILITY_KEY = 'ms:edgeOptions'
 class ServiceBuilder extends chromium.ServiceBuilder {
   /**
    * @param {string=} opt_exe Path to the server executable to use. If omitted,
-   *     the builder will attempt to locate the msedgedriver on the current
+   *     the builder will attempt to use the msedgedriver path from the
+   *     SE_EDGEDRIVER environment variable, then locate the msedgedriver on the current
    *     PATH.
    * @throws {Error} If provided executable does not exist, or the msedgedriver
    *     cannot be found on the PATH.
    */
   constructor(opt_exe) {
-    super(opt_exe)
+    super(opt_exe || process.env[EDGE_DRIVER_EXE_ENV_VAR])
     this.setLoopback(true)
   }
 }
diff --git a/javascript/selenium-webdriver/firefox.js b/javascript/selenium-webdriver/firefox.js
index d97500398fb99..8699044750ec6 100644
--- a/javascript/selenium-webdriver/firefox.js
+++ b/javascript/selenium-webdriver/firefox.js
@@ -124,6 +124,12 @@ const { getBinaryPaths } = require('./common/driverFinder')
 const { findFreePort } = require('./net/portprober')
 const FIREFOX_CAPABILITY_KEY = 'moz:firefoxOptions'
 
+/**
+ * Environment variable that defines the location of the GeckoDriver executable.
+ * @const {string}
+ */
+const GECKO_DRIVER_EXE_ENV_VAR = 'SE_GECKODRIVER'
+
 /**
  * Thrown when there an add-on is malformed.
  * @final
@@ -489,10 +495,11 @@ function configureExecutor(executor) {
 class ServiceBuilder extends remote.DriverService.Builder {
   /**
    * @param {string=} opt_exe Path to the server executable to use. If omitted,
-   *     the builder will attempt to locate the geckodriver on the system PATH.
+   *     the builder will attempt to use the geckodriver path from the
+   *     SE_GECKODRIVER environment variable, then locate the geckodriver on the system PATH.
    */
   constructor(opt_exe) {
-    super(opt_exe)
+    super(opt_exe || process.env[GECKO_DRIVER_EXE_ENV_VAR])
     this.setLoopback(true) // Required.
   }
 
diff --git a/javascript/selenium-webdriver/ie.js b/javascript/selenium-webdriver/ie.js
index d4615d49a38bd..9bb28377dfa9b 100644
--- a/javascript/selenium-webdriver/ie.js
+++ b/javascript/selenium-webdriver/ie.js
@@ -38,6 +38,7 @@ const error = require('./lib/error')
 const { getBinaryPaths } = require('./common/driverFinder')
 
 const OPTIONS_CAPABILITY_KEY = 'se:ieOptions'
+const IE_DRIVER_EXE_ENV_VAR = 'SE_IEDRIVER'
 const SCROLL_BEHAVIOUR = {
   BOTTOM: 1,
   TOP: 0,
@@ -422,10 +423,11 @@ function createServiceFromCapabilities(capabilities) {
 class ServiceBuilder extends remote.DriverService.Builder {
   /**
    * @param {string=} opt_exe Path to the server executable to use. If omitted,
-   *     the builder will attempt to locate the IEDriverServer on the system PATH.
+   *     the builder will attempt to use the IEDriverServer path from the
+   *     SE_IEDRIVER environment variable, then locate the IEDriverServer on the system PATH.
    */
   constructor(opt_exe) {
-    super(opt_exe)
+    super(opt_exe || process.env[IE_DRIVER_EXE_ENV_VAR])
     this.setLoopback(true) // Required.
   }
 }
diff --git a/javascript/selenium-webdriver/safari.js b/javascript/selenium-webdriver/safari.js
index bc1b9508942e3..f23ac4fc39c90 100644
--- a/javascript/selenium-webdriver/safari.js
+++ b/javascript/selenium-webdriver/safari.js
@@ -29,6 +29,12 @@ const webdriver = require('./lib/webdriver')
 const { Browser, Capabilities } = require('./lib/capabilities')
 const { getBinaryPaths } = require('./common/driverFinder')
 
+/**
+ * Environment variable that defines the location of the SafariDriver executable.
+ * @const {string}
+ */
+const SAFARI_DRIVER_EXE_ENV_VAR = 'SE_SAFARIDRIVER'
+
 /**
  * Creates {@link remote.DriverService} instances that manage
  * a [safaridriver] server in a child process.
@@ -38,10 +44,11 @@ const { getBinaryPaths } = require('./common/driverFinder')
 class ServiceBuilder extends remote.DriverService.Builder {
   /**
    * @param {string=} opt_exe Path to the server executable to use. If omitted,
-   *     the builder will attempt to locate the safaridriver on the system PATH.
+   *     the builder will attempt to use the safaridriver path from the
+   *     SE_SAFARIDRIVER environment variable, then locate the safaridriver on the system PATH.
    */
   constructor(opt_exe) {
-    super(opt_exe)
+    super(opt_exe || process.env[SAFARI_DRIVER_EXE_ENV_VAR])
     this.setLoopback(true) // Required.
   }
 }

From 7e7a17d27f003f45753e3d648741492c29d95620 Mon Sep 17 00:00:00 2001
From: Alex Popov <iampopovich@gmail.com>
Date: Sat, 21 Jun 2025 17:54:42 +0700
Subject: [PATCH 2/7] Add environment variable support for driver executables
 in Chrome, Edge, Firefox, IE, and Safari

---
 .../test/chrome/service_test.js               | 45 +++++++++
 .../test/edge/service_test.js                 | 45 +++++++++
 .../test/firefox/service_test.js              | 90 ++++++++++++++++++
 .../test/ie/service_test.js                   | 93 +++++++++++++++++++
 .../selenium-webdriver/test/safari_test.js    | 45 +++++++++
 5 files changed, 318 insertions(+)
 create mode 100644 javascript/selenium-webdriver/test/firefox/service_test.js
 create mode 100644 javascript/selenium-webdriver/test/ie/service_test.js

diff --git a/javascript/selenium-webdriver/test/chrome/service_test.js b/javascript/selenium-webdriver/test/chrome/service_test.js
index 7dc1b953a739a..d249092b02312 100644
--- a/javascript/selenium-webdriver/test/chrome/service_test.js
+++ b/javascript/selenium-webdriver/test/chrome/service_test.js
@@ -41,6 +41,51 @@ test.suite(
           assert.ok(url.endsWith('/foo/bar/baz'), 'unexpected url: ' + url)
         })
       })
+
+      describe('environment variable support', function () {
+        let originalEnvValue
+
+        beforeEach(function () {
+          originalEnvValue = process.env.SE_CHROMEDRIVER
+        })
+
+        afterEach(function () {
+          if (originalEnvValue) {
+            process.env.SE_CHROMEDRIVER = originalEnvValue
+          } else {
+            delete process.env.SE_CHROMEDRIVER
+          }
+        })
+
+        it('uses SE_CHROMEDRIVER environment variable when set', function () {
+          const testPath = '/custom/path/to/chromedriver'
+          process.env.SE_CHROMEDRIVER = testPath
+          
+          const serviceBuilder = new chrome.ServiceBuilder()
+          const service = serviceBuilder.build()
+          assert.strictEqual(service.getExecutable(), testPath)
+        })
+
+        it('explicit path overrides environment variable', function () {
+          const envPath = '/env/path/to/chromedriver'
+          const explicitPath = '/explicit/path/to/chromedriver'
+          
+          process.env.SE_CHROMEDRIVER = envPath
+          const serviceBuilder = new chrome.ServiceBuilder(explicitPath)
+          const service = serviceBuilder.build()
+          
+          assert.strictEqual(service.getExecutable(), explicitPath)
+        })
+
+        it('falls back to default behavior when environment variable is not set', function () {
+          delete process.env.SE_CHROMEDRIVER
+          
+          const serviceBuilder = new chrome.ServiceBuilder()
+          const service = serviceBuilder.build()
+          // Should be null/undefined when no explicit path and no env var
+          assert.ok(!service.getExecutable())
+        })
+      })
     })
   },
   { browsers: ['chrome'] },
diff --git a/javascript/selenium-webdriver/test/edge/service_test.js b/javascript/selenium-webdriver/test/edge/service_test.js
index 1b6c302ac2058..f06fd9f06ca23 100644
--- a/javascript/selenium-webdriver/test/edge/service_test.js
+++ b/javascript/selenium-webdriver/test/edge/service_test.js
@@ -39,6 +39,51 @@ test.suite(
         let url = await service.start()
         assert(/127\.0\.0\.1/.test(url), `unexpected url: ${url}`)
       })
+
+      describe('environment variable support', function () {
+        let originalEnvValue
+
+        beforeEach(function () {
+          originalEnvValue = process.env.SE_EDGEDRIVER
+        })
+
+        afterEach(function () {
+          if (originalEnvValue) {
+            process.env.SE_EDGEDRIVER = originalEnvValue
+          } else {
+            delete process.env.SE_EDGEDRIVER
+          }
+        })
+
+        it('uses SE_EDGEDRIVER environment variable when set', function () {
+          const testPath = '/custom/path/to/edgedriver'
+          process.env.SE_EDGEDRIVER = testPath
+          
+          const serviceBuilder = new edge.ServiceBuilder()
+          const service = serviceBuilder.build()
+          assert.strictEqual(service.getExecutable(), testPath)
+        })
+
+        it('explicit path overrides environment variable', function () {
+          const envPath = '/env/path/to/edgedriver'
+          const explicitPath = '/explicit/path/to/edgedriver'
+          
+          process.env.SE_EDGEDRIVER = envPath
+          const serviceBuilder = new edge.ServiceBuilder(explicitPath)
+          const service = serviceBuilder.build()
+          
+          assert.strictEqual(service.getExecutable(), explicitPath)
+        })
+
+        it('falls back to default behavior when environment variable is not set', function () {
+          delete process.env.SE_EDGEDRIVER
+          
+          const serviceBuilder = new edge.ServiceBuilder()
+          const service = serviceBuilder.build()
+          // Should be null/undefined when no explicit path and no env var
+          assert.ok(!service.getExecutable())
+        })
+      })
     })
   },
   { browsers: ['MicrosoftEdge'] },
diff --git a/javascript/selenium-webdriver/test/firefox/service_test.js b/javascript/selenium-webdriver/test/firefox/service_test.js
new file mode 100644
index 0000000000000..be8d64f9e0cc6
--- /dev/null
+++ b/javascript/selenium-webdriver/test/firefox/service_test.js
@@ -0,0 +1,90 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The SFC licenses this file
+// to you 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.
+
+'use strict'
+
+const assert = require('node:assert')
+const firefox = require('selenium-webdriver/firefox')
+const test = require('../../lib/test')
+const { getBinaryPaths } = require('selenium-webdriver/common/driverFinder')
+
+test.suite(
+  function (_env) {
+    describe('geckodriver', function () {
+      let service
+
+      afterEach(function () {
+        if (service) {
+          return service.kill()
+        }
+      })
+
+      it('can start geckodriver', async function () {
+        service = new firefox.ServiceBuilder().build()
+        service.setExecutable(getBinaryPaths(new firefox.Options()).driverPath)
+        let url = await service.start()
+        assert(/127\.0\.0\.1/.test(url), `unexpected url: ${url}`)
+      })
+
+      describe('environment variable support', function () {
+        let originalEnvValue
+
+        beforeEach(function () {
+          originalEnvValue = process.env.SE_GECKODRIVER
+        })
+
+        afterEach(function () {
+          if (originalEnvValue) {
+            process.env.SE_GECKODRIVER = originalEnvValue
+          } else {
+            delete process.env.SE_GECKODRIVER
+          }
+        })
+
+        it('uses SE_GECKODRIVER environment variable when set', function () {
+          const testPath = '/custom/path/to/geckodriver'
+          process.env.SE_GECKODRIVER = testPath
+          
+          const serviceBuilder = new firefox.ServiceBuilder()
+          const service = serviceBuilder.build()
+          assert.strictEqual(service.getExecutable(), testPath)
+        })
+
+        it('explicit path overrides environment variable', function () {
+          const envPath = '/env/path/to/geckodriver'
+          const explicitPath = '/explicit/path/to/geckodriver'
+          
+          process.env.SE_GECKODRIVER = envPath
+          const serviceBuilder = new firefox.ServiceBuilder(explicitPath)
+          const service = serviceBuilder.build()
+          
+          assert.strictEqual(service.getExecutable(), explicitPath)
+        })
+
+        it('falls back to default behavior when environment variable is not set', function () {
+          delete process.env.SE_GECKODRIVER
+          
+          const serviceBuilder = new firefox.ServiceBuilder()
+          const service = serviceBuilder.build()
+          // Should be null/undefined when no explicit path and no env var
+          assert.ok(!service.getExecutable())
+        })
+      })
+    })
+  },
+  { browsers: ['firefox'] },
+)
diff --git a/javascript/selenium-webdriver/test/ie/service_test.js b/javascript/selenium-webdriver/test/ie/service_test.js
new file mode 100644
index 0000000000000..236d446e44451
--- /dev/null
+++ b/javascript/selenium-webdriver/test/ie/service_test.js
@@ -0,0 +1,93 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The SFC licenses this file
+// to you 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.
+
+'use strict'
+
+const assert = require('node:assert')
+const ie = require('selenium-webdriver/ie')
+const test = require('../../lib/test')
+const { getBinaryPaths } = require('selenium-webdriver/common/driverFinder')
+
+test.suite(
+  function (_env) {
+    describe('iedriver', function () {
+      let service
+
+      afterEach(function () {
+        if (service) {
+          return service.kill()
+        }
+      })
+
+      it('can start iedriver', async function () {
+        // Skip on non-Windows platforms
+        if (process.platform !== 'win32') {
+          this.skip()
+          return
+        }
+        
+        service = new ie.ServiceBuilder().build()
+        service.setExecutable(getBinaryPaths(new ie.Options()).driverPath)
+        let url = await service.start()
+        assert(/127\.0\.0\.1/.test(url), `unexpected url: ${url}`)
+      })
+
+      describe('environment variable support', function () {
+        let originalEnvValue
+
+        beforeEach(function () {
+          originalEnvValue = process.env.SE_IEDRIVER
+        })
+
+        afterEach(function () {
+          if (originalEnvValue) {
+            process.env.SE_IEDRIVER = originalEnvValue
+          } else {
+            delete process.env.SE_IEDRIVER
+          }
+        })
+
+        it('uses SE_IEDRIVER environment variable when set', function () {
+          const testPath = '/custom/path/to/iedriver'
+          process.env.SE_IEDRIVER = testPath
+          
+          const serviceBuilder = new ie.ServiceBuilder()
+          assert.strictEqual(serviceBuilder.getExecutable(), testPath)
+        })
+
+        it('explicit path overrides environment variable', function () {
+          const envPath = '/env/path/to/iedriver'
+          const explicitPath = '/explicit/path/to/iedriver'
+          
+          process.env.SE_IEDRIVER = envPath
+          const serviceBuilder = new ie.ServiceBuilder(explicitPath)
+          
+          assert.strictEqual(serviceBuilder.getExecutable(), explicitPath)
+        })
+
+        it('falls back to default behavior when environment variable is not set', function () {
+          delete process.env.SE_IEDRIVER
+          
+          const serviceBuilder = new ie.ServiceBuilder()
+          // Should be null/undefined when no explicit path and no env var
+          assert.ok(!serviceBuilder.getExecutable())
+        })
+      })
+    })
+  },
+  { browsers: ['ie'] },
+)
diff --git a/javascript/selenium-webdriver/test/safari_test.js b/javascript/selenium-webdriver/test/safari_test.js
index 98f164ce959df..0a3ec9f0bc916 100644
--- a/javascript/selenium-webdriver/test/safari_test.js
+++ b/javascript/selenium-webdriver/test/safari_test.js
@@ -38,6 +38,51 @@ test.suite(
         let url = await service.start()
         assert(/127\.0\.0\.1/.test(url), `unexpected url: ${url}`)
       })
+
+      describe('environment variable support', function () {
+        let originalEnvValue
+
+        beforeEach(function () {
+          originalEnvValue = process.env.SE_SAFARIDRIVER
+        })
+
+        afterEach(function () {
+          if (originalEnvValue) {
+            process.env.SE_SAFARIDRIVER = originalEnvValue
+          } else {
+            delete process.env.SE_SAFARIDRIVER
+          }
+        })
+
+        it('uses SE_SAFARIDRIVER environment variable when set', function () {
+          const testPath = '/custom/path/to/safaridriver'
+          process.env.SE_SAFARIDRIVER = testPath
+          
+          const serviceBuilder = new safari.ServiceBuilder()
+          const service = serviceBuilder.build()
+          assert.strictEqual(service.getExecutable(), testPath)
+        })
+
+        it('explicit path overrides environment variable', function () {
+          const envPath = '/env/path/to/safaridriver'
+          const explicitPath = '/explicit/path/to/safaridriver'
+          
+          process.env.SE_SAFARIDRIVER = envPath
+          const serviceBuilder = new safari.ServiceBuilder(explicitPath)
+          const service = serviceBuilder.build()
+          
+          assert.strictEqual(service.getExecutable(), explicitPath)
+        })
+
+        it('falls back to default behavior when environment variable is not set', function () {
+          delete process.env.SE_SAFARIDRIVER
+          
+          const serviceBuilder = new safari.ServiceBuilder()
+          const service = serviceBuilder.build()
+          // Should be null/undefined when no explicit path and no env var
+          assert.ok(!service.getExecutable())
+        })
+      })
     })
   },
   { browsers: ['safari'] },

From 2679e2416f2d5504f94d86b3c9053a60200f8d2b Mon Sep 17 00:00:00 2001
From: Alex Popov <iampopovich@gmail.com>
Date: Sat, 21 Jun 2025 18:01:29 +0700
Subject: [PATCH 3/7] Refactor service tests to remove unnecessary whitespace
 and improve readability

---
 .../selenium-webdriver/test/chrome/service_test.js     |  8 ++++----
 .../selenium-webdriver/test/edge/service_test.js       |  8 ++++----
 .../selenium-webdriver/test/firefox/service_test.js    |  8 ++++----
 javascript/selenium-webdriver/test/ie/service_test.js  | 10 +++++-----
 javascript/selenium-webdriver/test/safari_test.js      |  8 ++++----
 5 files changed, 21 insertions(+), 21 deletions(-)

diff --git a/javascript/selenium-webdriver/test/chrome/service_test.js b/javascript/selenium-webdriver/test/chrome/service_test.js
index d249092b02312..b4b5b8706b0f3 100644
--- a/javascript/selenium-webdriver/test/chrome/service_test.js
+++ b/javascript/selenium-webdriver/test/chrome/service_test.js
@@ -60,7 +60,7 @@ test.suite(
         it('uses SE_CHROMEDRIVER environment variable when set', function () {
           const testPath = '/custom/path/to/chromedriver'
           process.env.SE_CHROMEDRIVER = testPath
-          
+
           const serviceBuilder = new chrome.ServiceBuilder()
           const service = serviceBuilder.build()
           assert.strictEqual(service.getExecutable(), testPath)
@@ -69,17 +69,17 @@ test.suite(
         it('explicit path overrides environment variable', function () {
           const envPath = '/env/path/to/chromedriver'
           const explicitPath = '/explicit/path/to/chromedriver'
-          
+
           process.env.SE_CHROMEDRIVER = envPath
           const serviceBuilder = new chrome.ServiceBuilder(explicitPath)
           const service = serviceBuilder.build()
-          
+
           assert.strictEqual(service.getExecutable(), explicitPath)
         })
 
         it('falls back to default behavior when environment variable is not set', function () {
           delete process.env.SE_CHROMEDRIVER
-          
+
           const serviceBuilder = new chrome.ServiceBuilder()
           const service = serviceBuilder.build()
           // Should be null/undefined when no explicit path and no env var
diff --git a/javascript/selenium-webdriver/test/edge/service_test.js b/javascript/selenium-webdriver/test/edge/service_test.js
index f06fd9f06ca23..0e2bc7bbaab9b 100644
--- a/javascript/selenium-webdriver/test/edge/service_test.js
+++ b/javascript/selenium-webdriver/test/edge/service_test.js
@@ -58,7 +58,7 @@ test.suite(
         it('uses SE_EDGEDRIVER environment variable when set', function () {
           const testPath = '/custom/path/to/edgedriver'
           process.env.SE_EDGEDRIVER = testPath
-          
+
           const serviceBuilder = new edge.ServiceBuilder()
           const service = serviceBuilder.build()
           assert.strictEqual(service.getExecutable(), testPath)
@@ -67,17 +67,17 @@ test.suite(
         it('explicit path overrides environment variable', function () {
           const envPath = '/env/path/to/edgedriver'
           const explicitPath = '/explicit/path/to/edgedriver'
-          
+
           process.env.SE_EDGEDRIVER = envPath
           const serviceBuilder = new edge.ServiceBuilder(explicitPath)
           const service = serviceBuilder.build()
-          
+
           assert.strictEqual(service.getExecutable(), explicitPath)
         })
 
         it('falls back to default behavior when environment variable is not set', function () {
           delete process.env.SE_EDGEDRIVER
-          
+
           const serviceBuilder = new edge.ServiceBuilder()
           const service = serviceBuilder.build()
           // Should be null/undefined when no explicit path and no env var
diff --git a/javascript/selenium-webdriver/test/firefox/service_test.js b/javascript/selenium-webdriver/test/firefox/service_test.js
index be8d64f9e0cc6..4d5a1f9265854 100644
--- a/javascript/selenium-webdriver/test/firefox/service_test.js
+++ b/javascript/selenium-webdriver/test/firefox/service_test.js
@@ -58,7 +58,7 @@ test.suite(
         it('uses SE_GECKODRIVER environment variable when set', function () {
           const testPath = '/custom/path/to/geckodriver'
           process.env.SE_GECKODRIVER = testPath
-          
+
           const serviceBuilder = new firefox.ServiceBuilder()
           const service = serviceBuilder.build()
           assert.strictEqual(service.getExecutable(), testPath)
@@ -67,17 +67,17 @@ test.suite(
         it('explicit path overrides environment variable', function () {
           const envPath = '/env/path/to/geckodriver'
           const explicitPath = '/explicit/path/to/geckodriver'
-          
+
           process.env.SE_GECKODRIVER = envPath
           const serviceBuilder = new firefox.ServiceBuilder(explicitPath)
           const service = serviceBuilder.build()
-          
+
           assert.strictEqual(service.getExecutable(), explicitPath)
         })
 
         it('falls back to default behavior when environment variable is not set', function () {
           delete process.env.SE_GECKODRIVER
-          
+
           const serviceBuilder = new firefox.ServiceBuilder()
           const service = serviceBuilder.build()
           // Should be null/undefined when no explicit path and no env var
diff --git a/javascript/selenium-webdriver/test/ie/service_test.js b/javascript/selenium-webdriver/test/ie/service_test.js
index 236d446e44451..b7b9123c32a76 100644
--- a/javascript/selenium-webdriver/test/ie/service_test.js
+++ b/javascript/selenium-webdriver/test/ie/service_test.js
@@ -39,7 +39,7 @@ test.suite(
           this.skip()
           return
         }
-        
+
         service = new ie.ServiceBuilder().build()
         service.setExecutable(getBinaryPaths(new ie.Options()).driverPath)
         let url = await service.start()
@@ -64,7 +64,7 @@ test.suite(
         it('uses SE_IEDRIVER environment variable when set', function () {
           const testPath = '/custom/path/to/iedriver'
           process.env.SE_IEDRIVER = testPath
-          
+
           const serviceBuilder = new ie.ServiceBuilder()
           assert.strictEqual(serviceBuilder.getExecutable(), testPath)
         })
@@ -72,16 +72,16 @@ test.suite(
         it('explicit path overrides environment variable', function () {
           const envPath = '/env/path/to/iedriver'
           const explicitPath = '/explicit/path/to/iedriver'
-          
+
           process.env.SE_IEDRIVER = envPath
           const serviceBuilder = new ie.ServiceBuilder(explicitPath)
-          
+
           assert.strictEqual(serviceBuilder.getExecutable(), explicitPath)
         })
 
         it('falls back to default behavior when environment variable is not set', function () {
           delete process.env.SE_IEDRIVER
-          
+
           const serviceBuilder = new ie.ServiceBuilder()
           // Should be null/undefined when no explicit path and no env var
           assert.ok(!serviceBuilder.getExecutable())
diff --git a/javascript/selenium-webdriver/test/safari_test.js b/javascript/selenium-webdriver/test/safari_test.js
index 0a3ec9f0bc916..93df671035331 100644
--- a/javascript/selenium-webdriver/test/safari_test.js
+++ b/javascript/selenium-webdriver/test/safari_test.js
@@ -57,7 +57,7 @@ test.suite(
         it('uses SE_SAFARIDRIVER environment variable when set', function () {
           const testPath = '/custom/path/to/safaridriver'
           process.env.SE_SAFARIDRIVER = testPath
-          
+
           const serviceBuilder = new safari.ServiceBuilder()
           const service = serviceBuilder.build()
           assert.strictEqual(service.getExecutable(), testPath)
@@ -66,17 +66,17 @@ test.suite(
         it('explicit path overrides environment variable', function () {
           const envPath = '/env/path/to/safaridriver'
           const explicitPath = '/explicit/path/to/safaridriver'
-          
+
           process.env.SE_SAFARIDRIVER = envPath
           const serviceBuilder = new safari.ServiceBuilder(explicitPath)
           const service = serviceBuilder.build()
-          
+
           assert.strictEqual(service.getExecutable(), explicitPath)
         })
 
         it('falls back to default behavior when environment variable is not set', function () {
           delete process.env.SE_SAFARIDRIVER
-          
+
           const serviceBuilder = new safari.ServiceBuilder()
           const service = serviceBuilder.build()
           // Should be null/undefined when no explicit path and no env var

From 49c39eed02e1e5685072ccf536cd8d8cf458c372 Mon Sep 17 00:00:00 2001
From: Alex Popov <iampopovich@gmail.com>
Date: Mon, 23 Jun 2025 23:30:47 +0700
Subject: [PATCH 4/7] Add support for resolving relative driver executable
 paths using Bazel runfiles

---
 javascript/selenium-webdriver/chrome.js  | 23 ++++++++++++++++++++++-
 javascript/selenium-webdriver/firefox.js | 22 +++++++++++++++++++++-
 2 files changed, 43 insertions(+), 2 deletions(-)

diff --git a/javascript/selenium-webdriver/chrome.js b/javascript/selenium-webdriver/chrome.js
index 344c3d104efeb..384102fa096fc 100644
--- a/javascript/selenium-webdriver/chrome.js
+++ b/javascript/selenium-webdriver/chrome.js
@@ -127,6 +127,13 @@
 const { Browser } = require('./lib/capabilities')
 const chromium = require('./chromium')
 const CHROME_CAPABILITY_KEY = 'goog:chromeOptions'
+const path = require('node:path')
+let runfiles = null
+try {
+  runfiles = require('@bazel/runfiles').runfiles
+} catch (e) {
+  // Ignore if @bazel/runfiles is not available
+}
 
 /**
  * Environment variable that defines the location of the ChromeDriver executable.
@@ -152,7 +159,21 @@ class ServiceBuilder extends chromium.ServiceBuilder {
    *     cannot be found on the PATH.
    */
   constructor(opt_exe) {
-    super(opt_exe || process.env[CHROME_DRIVER_EXE_ENV_VAR])
+    let exePath = opt_exe || process.env[CHROME_DRIVER_EXE_ENV_VAR]
+    
+    // If path is from env variable and appears to be a relative path, try to resolve it using runfiles
+    if (!opt_exe && exePath && !path.isAbsolute(exePath) && runfiles) {
+      try {
+        const resolvedPath = runfiles.resolve(exePath)
+        if (resolvedPath) {
+          exePath = resolvedPath
+        }
+      } catch (e) {
+        // If resolution fails, use the original path
+      }
+    }
+    
+    super(exePath)
   }
 }
 
diff --git a/javascript/selenium-webdriver/firefox.js b/javascript/selenium-webdriver/firefox.js
index 8699044750ec6..e75896a29e71f 100644
--- a/javascript/selenium-webdriver/firefox.js
+++ b/javascript/selenium-webdriver/firefox.js
@@ -123,6 +123,12 @@ const { Zip } = require('./io/zip')
 const { getBinaryPaths } = require('./common/driverFinder')
 const { findFreePort } = require('./net/portprober')
 const FIREFOX_CAPABILITY_KEY = 'moz:firefoxOptions'
+let runfiles = null
+try {
+  runfiles = require('@bazel/runfiles').runfiles
+} catch (e) {
+  // Ignore if @bazel/runfiles is not available
+}
 
 /**
  * Environment variable that defines the location of the GeckoDriver executable.
@@ -499,7 +505,21 @@ class ServiceBuilder extends remote.DriverService.Builder {
    *     SE_GECKODRIVER environment variable, then locate the geckodriver on the system PATH.
    */
   constructor(opt_exe) {
-    super(opt_exe || process.env[GECKO_DRIVER_EXE_ENV_VAR])
+    let exePath = opt_exe || process.env[GECKO_DRIVER_EXE_ENV_VAR]
+    
+    // If path is from env variable and appears to be a relative path, try to resolve it using runfiles
+    if (!opt_exe && exePath && !path.isAbsolute(exePath) && runfiles) {
+      try {
+        const resolvedPath = runfiles.resolve(exePath)
+        if (resolvedPath) {
+          exePath = resolvedPath
+        }
+      } catch (e) {
+        // If resolution fails, use the original path
+      }
+    }
+    
+    super(exePath)
     this.setLoopback(true) // Required.
   }
 

From 52bbdd54888f8c04fab05abd77457f293b2d1b50 Mon Sep 17 00:00:00 2001
From: Alex Popov <iampopovich@gmail.com>
Date: Tue, 24 Jun 2025 09:50:56 +0700
Subject: [PATCH 5/7] applied format.sh

---
 javascript/selenium-webdriver/chrome.js  | 12 ++++++++++--
 javascript/selenium-webdriver/firefox.js |  4 ++--
 2 files changed, 12 insertions(+), 4 deletions(-)

diff --git a/javascript/selenium-webdriver/chrome.js b/javascript/selenium-webdriver/chrome.js
index 384102fa096fc..62b2ff53baf11 100644
--- a/javascript/selenium-webdriver/chrome.js
+++ b/javascript/selenium-webdriver/chrome.js
@@ -141,6 +141,14 @@ try {
  */
 const CHROME_DRIVER_EXE_ENV_VAR = 'SE_CHROMEDRIVER'
 
+// Try to load @bazel/runfiles for resolving paths in Bazel environments
+let runfiles = null
+try {
+  runfiles = require('@bazel/runfiles').runfiles
+} catch (e) {
+  // Ignore if @bazel/runfiles is not available
+}
+
 /** @type {remote.DriverService} */
 
 /**
@@ -160,7 +168,7 @@ class ServiceBuilder extends chromium.ServiceBuilder {
    */
   constructor(opt_exe) {
     let exePath = opt_exe || process.env[CHROME_DRIVER_EXE_ENV_VAR]
-    
+
     // If path is from env variable and appears to be a relative path, try to resolve it using runfiles
     if (!opt_exe && exePath && !path.isAbsolute(exePath) && runfiles) {
       try {
@@ -172,7 +180,7 @@ class ServiceBuilder extends chromium.ServiceBuilder {
         // If resolution fails, use the original path
       }
     }
-    
+
     super(exePath)
   }
 }
diff --git a/javascript/selenium-webdriver/firefox.js b/javascript/selenium-webdriver/firefox.js
index e75896a29e71f..ee25c9a9ea036 100644
--- a/javascript/selenium-webdriver/firefox.js
+++ b/javascript/selenium-webdriver/firefox.js
@@ -506,7 +506,7 @@ class ServiceBuilder extends remote.DriverService.Builder {
    */
   constructor(opt_exe) {
     let exePath = opt_exe || process.env[GECKO_DRIVER_EXE_ENV_VAR]
-    
+
     // If path is from env variable and appears to be a relative path, try to resolve it using runfiles
     if (!opt_exe && exePath && !path.isAbsolute(exePath) && runfiles) {
       try {
@@ -518,7 +518,7 @@ class ServiceBuilder extends remote.DriverService.Builder {
         // If resolution fails, use the original path
       }
     }
-    
+
     super(exePath)
     this.setLoopback(true) // Required.
   }

From da56618bb51b12f1994de99db6de01adcbf03ccb Mon Sep 17 00:00:00 2001
From: Alex Popov <iampopovich@gmail.com>
Date: Mon, 30 Jun 2025 20:12:41 +0700
Subject: [PATCH 6/7] Remove unused runfiles import from chrome.js

---
 javascript/selenium-webdriver/chrome.js | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/javascript/selenium-webdriver/chrome.js b/javascript/selenium-webdriver/chrome.js
index 62b2ff53baf11..a608caaead2f8 100644
--- a/javascript/selenium-webdriver/chrome.js
+++ b/javascript/selenium-webdriver/chrome.js
@@ -128,12 +128,6 @@ const { Browser } = require('./lib/capabilities')
 const chromium = require('./chromium')
 const CHROME_CAPABILITY_KEY = 'goog:chromeOptions'
 const path = require('node:path')
-let runfiles = null
-try {
-  runfiles = require('@bazel/runfiles').runfiles
-} catch (e) {
-  // Ignore if @bazel/runfiles is not available
-}
 
 /**
  * Environment variable that defines the location of the ChromeDriver executable.

From 65064026ae26791bcc2a300182446629f48bd083 Mon Sep 17 00:00:00 2001
From: Alex Popov <iampopovich@gmail.com>
Date: Mon, 30 Jun 2025 21:30:26 +0700
Subject: [PATCH 7/7] Refactor error handling in chrome.js and firefox.js to
 omit error variable

---
 javascript/selenium-webdriver/chrome.js  | 4 ++--
 javascript/selenium-webdriver/firefox.js | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/javascript/selenium-webdriver/chrome.js b/javascript/selenium-webdriver/chrome.js
index a608caaead2f8..a1841df889798 100644
--- a/javascript/selenium-webdriver/chrome.js
+++ b/javascript/selenium-webdriver/chrome.js
@@ -139,7 +139,7 @@ const CHROME_DRIVER_EXE_ENV_VAR = 'SE_CHROMEDRIVER'
 let runfiles = null
 try {
   runfiles = require('@bazel/runfiles').runfiles
-} catch (e) {
+} catch {
   // Ignore if @bazel/runfiles is not available
 }
 
@@ -170,7 +170,7 @@ class ServiceBuilder extends chromium.ServiceBuilder {
         if (resolvedPath) {
           exePath = resolvedPath
         }
-      } catch (e) {
+      } catch {
         // If resolution fails, use the original path
       }
     }
diff --git a/javascript/selenium-webdriver/firefox.js b/javascript/selenium-webdriver/firefox.js
index ee25c9a9ea036..07a5cf3bd0157 100644
--- a/javascript/selenium-webdriver/firefox.js
+++ b/javascript/selenium-webdriver/firefox.js
@@ -126,7 +126,7 @@ const FIREFOX_CAPABILITY_KEY = 'moz:firefoxOptions'
 let runfiles = null
 try {
   runfiles = require('@bazel/runfiles').runfiles
-} catch (e) {
+} catch {
   // Ignore if @bazel/runfiles is not available
 }
 
@@ -514,7 +514,7 @@ class ServiceBuilder extends remote.DriverService.Builder {
         if (resolvedPath) {
           exePath = resolvedPath
         }
-      } catch (e) {
+      } catch {
         // If resolution fails, use the original path
       }
     }