diff --git a/javascript/selenium-webdriver/chrome.js b/javascript/selenium-webdriver/chrome.js index 9d644ba9666f6..62b2ff53baf11 100644 --- a/javascript/selenium-webdriver/chrome.js +++ b/javascript/selenium-webdriver/chrome.js @@ -127,6 +127,27 @@ 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. + * @const {string} + */ +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} */ @@ -138,14 +159,29 @@ 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) + 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/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..ee25c9a9ea036 100644 --- a/javascript/selenium-webdriver/firefox.js +++ b/javascript/selenium-webdriver/firefox.js @@ -123,6 +123,18 @@ 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. + * @const {string} + */ +const GECKO_DRIVER_EXE_ENV_VAR = 'SE_GECKODRIVER' /** * Thrown when there an add-on is malformed. @@ -489,10 +501,25 @@ 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) + 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. } 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. } } diff --git a/javascript/selenium-webdriver/test/chrome/service_test.js b/javascript/selenium-webdriver/test/chrome/service_test.js index 7dc1b953a739a..b4b5b8706b0f3 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..0e2bc7bbaab9b 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..4d5a1f9265854 --- /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..b7b9123c32a76 --- /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..93df671035331 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'] },