Running in CI
The suite passes on your laptop and fails on the build server. The server has no display, a
different browser version, no driver on the PATH, and a locked-down network. Each of those
is a separate failure with a separate fix, and the error messages rarely say which one you
have hit.
Recommended approach
No display. Run the browser headless. Chrome and Edge take the --headless=new argument,
Firefox takes -headless. The page renders the same way, so only the browser options change.
Keep headless behind a configuration flag so you can still watch a test locally.
No driver. Do not download driver binaries in your pipeline. Selenium Manager, which
ships with Selenium 4.6 and later, detects the installed browser, downloads the matching
driver, and caches it in ~/.cache/selenium; cache that directory between runs. Some hosted
runners pre-install drivers on the PATH, and a stale one there takes precedence over
Selenium Manager, so remove it or keep it current.
Locked-down network. Selenium Manager needs to reach the browser vendors’ download
endpoints the first time. Behind a proxy, set it with the SE_PROXY environment variable or
the Selenium Manager configuration file.
Scale. When one runner starting one browser per test becomes too slow, point the same tests at a Selenium Grid and run them in parallel without changing the test code.
Collect a screenshot and the Selenium log on every failure; a CI failure without artifacts is a re-run waiting to happen.
Start a headless browser:
options.addArguments("-headless");/examples/java/src/test/java/dev/selenium/browsers/FirefoxTest.java
package dev.selenium.browsers;
import dev.selenium.BaseTest;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnOs;
import org.junit.jupiter.api.condition.OS;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.*;
import org.openqa.selenium.remote.service.DriverFinder;
public class FirefoxTest extends BaseTest {
private FirefoxDriver driver;
@AfterEach
public void clearProperties() {
System.clearProperty(GeckoDriverService.GECKO_DRIVER_LOG_PROPERTY);
System.clearProperty(GeckoDriverService.GECKO_DRIVER_LOG_LEVEL_PROPERTY);
if (driver != null) {
driver.quit();
}
}
@Test
public void basicOptions() {
FirefoxOptions options = new FirefoxOptions();
driver = new FirefoxDriver(options);
}
@Test
public void arguments() {
FirefoxOptions options = new FirefoxOptions();
options.addArguments("-headless");
driver = new FirefoxDriver(options);
}
@Test
@DisabledOnOs(OS.WINDOWS)
public void setBrowserLocation() {
FirefoxOptions options = new FirefoxOptions();
options.setBinary(getFirefoxLocation());
driver = new FirefoxDriver(options);
}
@Test
public void logsToFile() throws IOException {
File logLocation = getTempFile("logsToFile", ".log");
FirefoxDriverService service =
new GeckoDriverService.Builder().withLogFile(logLocation).build();
driver = new FirefoxDriver(service);
String fileContent = new String(Files.readAllBytes(logLocation.toPath()));
Assertions.assertTrue(fileContent.contains("geckodriver INFO Listening on"));
}
@Test
public void logsToConsole() throws IOException {
File logLocation = getTempFile("logsToConsole", ".log");
System.setOut(new PrintStream(logLocation));
FirefoxDriverService service =
new GeckoDriverService.Builder().withLogOutput(System.out).build();
driver = new FirefoxDriver(service);
String fileContent = new String(Files.readAllBytes(logLocation.toPath()));
Assertions.assertTrue(fileContent.contains("geckodriver INFO Listening on"));
}
@Test
public void logsWithLevel() throws IOException {
File logLocation = getTempFile("logsWithLevel", ".log");
System.setProperty(GeckoDriverService.GECKO_DRIVER_LOG_PROPERTY, logLocation.getAbsolutePath());
FirefoxDriverService service =
new GeckoDriverService.Builder().withLogLevel(FirefoxDriverLogLevel.DEBUG).build();
driver = new FirefoxDriver(service);
String fileContent = new String(Files.readAllBytes(logLocation.toPath()));
Assertions.assertTrue(fileContent.contains("Marionette\tDEBUG"));
}
@Test
public void stopsTruncatingLogs() throws IOException {
File logLocation = getTempFile("geckodriver-", "log");
System.setProperty(GeckoDriverService.GECKO_DRIVER_LOG_PROPERTY, logLocation.getAbsolutePath());
System.setProperty(
GeckoDriverService.GECKO_DRIVER_LOG_LEVEL_PROPERTY, FirefoxDriverLogLevel.DEBUG.toString());
FirefoxDriverService service =
new GeckoDriverService.Builder().withTruncatedLogs(false).build();
driver = new FirefoxDriver(service);
String fileContent = new String(Files.readAllBytes(logLocation.toPath()));
Assertions.assertFalse(fileContent.contains(" ... "));
}
@Test
public void setProfileLocation() {
File profileDirectory = getTempDirectory("profile-");
FirefoxDriverService service =
new GeckoDriverService.Builder().withProfileRoot(profileDirectory).build();
driver = new FirefoxDriver(service);
String location = (String) driver.getCapabilities().getCapability("moz:profile");
Assertions.assertTrue(location.contains(profileDirectory.getAbsolutePath()));
}
@Test
public void installAddon() {
driver = startFirefoxDriver();
Path xpiPath = Paths.get("src/test/resources/extensions/selenium-example.xpi");
driver.installExtension(xpiPath);
driver.get("https://www.selenium.dev/selenium/web/blank.html");
WebElement injected = driver.findElement(By.id("webextensions-selenium-example"));
Assertions.assertEquals(
"Content injected by webextensions-selenium-example", injected.getText());
}
@Test
public void uninstallAddon() {
driver = startFirefoxDriver();
Path xpiPath = Paths.get("src/test/resources/extensions/selenium-example.xpi");
String id = driver.installExtension(xpiPath);
driver.uninstallExtension(id);
driver.get("https://www.selenium.dev/selenium/web/blank.html");
Assertions.assertEquals(driver.findElements(By.id("webextensions-selenium-example")).size(), 0);
}
@Test
public void installUnsignedAddonPath() {
driver = startFirefoxDriver();
Path path = Paths.get("src/test/resources/extensions/selenium-example");
driver.installExtension(path, true);
driver.get("https://www.selenium.dev/selenium/web/blank.html");
WebElement injected = getLocatedElement(driver, By.id("webextensions-selenium-example"));
Assertions.assertEquals(
"Content injected by webextensions-selenium-example", injected.getText());
}
private Path getFirefoxLocation() {
FirefoxOptions options = new FirefoxOptions();
options.setBrowserVersion("stable");
DriverFinder finder = new DriverFinder(GeckoDriverService.createDefaultService(), options);
return Path.of(finder.getBrowserPath());
}
@Test
public void fullPageScreenshot() throws Exception {
driver = startFirefoxDriver();
driver.get("https://www.selenium.dev");
File screenshot = driver.getFullPageScreenshotAs(OutputType.FILE);
File targetFile = new File("full_page_screenshot.png");
Files.move(screenshot.toPath(), targetFile.toPath());
// Verify the screenshot file exists
Assertions.assertTrue(targetFile.exists(), "The full page screenshot file should exist");
Files.deleteIfExists(targetFile.toPath());
}
@Test
public void setContext() {
GeckoDriverService service =
new GeckoDriverService.Builder().withAllowSystemAccess(true).build();
driver = new FirefoxDriver(service, new FirefoxOptions());
driver.setContext(FirefoxCommandContext.CHROME);
driver.executeScript("console.log('Inside Chrome context');");
// Verify the context is back to "content"
Assertions.assertEquals(
FirefoxCommandContext.CHROME, driver.getContext(),
"The context should be 'chrome'"
);
}
@Test
public void firefoxProfile() {
FirefoxProfile profile = new FirefoxProfile();
FirefoxOptions options = new FirefoxOptions();
profile.setPreference("javascript.enabled", "False");
options.setProfile(profile);
driver = new FirefoxDriver(options);
}
}
options.add_argument("-headless")/examples/python/tests/browsers/test_firefox.py
import os
import subprocess
import sys
import pytest
from selenium import webdriver
def test_basic_options():
options = webdriver.FirefoxOptions()
driver = webdriver.Firefox(options=options)
driver.quit()
def test_arguments():
options = webdriver.FirefoxOptions()
options.add_argument("-headless")
driver = webdriver.Firefox(options=options)
driver.quit()
def test_set_browser_location(firefox_bin):
options = webdriver.FirefoxOptions()
options.binary_location = firefox_bin
driver = webdriver.Firefox(options=options)
driver.quit()
def test_log_to_file(log_path):
service = webdriver.FirefoxService(log_output=log_path, service_args=['--log', 'debug'])
driver = webdriver.Firefox(service=service)
driver.get("https://www.selenium.dev")
with open(log_path, 'r') as fp:
assert "geckodriver INFO Listening on" in fp.readline()
driver.quit()
def test_log_to_stdout(capfd):
service = webdriver.FirefoxService(log_output=subprocess.STDOUT)
driver = webdriver.Firefox(service=service)
out, err = capfd.readouterr()
assert "geckodriver INFO Listening on" in out
driver.quit()
def test_log_level(log_path):
service = webdriver.FirefoxService(log_output=log_path, service_args=['--log', 'debug'])
driver = webdriver.Firefox(service=service)
with open(log_path, 'r') as f:
assert '\tDEBUG' in f.read()
driver.quit()
def test_log_truncation(log_path):
service = webdriver.FirefoxService(service_args=['--log-no-truncate', '--log', 'debug'], log_output=log_path)
driver = webdriver.Firefox(service=service)
with open(log_path, 'r') as f:
assert ' ... ' not in f.read()
driver.quit()
def test_profile_location(temp_dir):
service = webdriver.FirefoxService(service_args=['--profile-root', temp_dir])
driver = webdriver.Firefox(service=service)
profile_name = driver.capabilities.get('moz:profile').replace('\\', '/').split('/')[-1]
assert profile_name in os.listdir(temp_dir)
driver.quit()
def test_install_addon(firefox_driver, addon_path_xpi):
driver = firefox_driver
driver.install_addon(addon_path_xpi)
driver.get("https://www.selenium.dev/selenium/web/blank.html")
injected = driver.find_element(webdriver.common.by.By.ID, "webextensions-selenium-example")
assert injected.text == "Content injected by webextensions-selenium-example"
def test_uninstall_addon(firefox_driver, addon_path_xpi):
driver = firefox_driver
id = driver.install_addon(addon_path_xpi)
driver.uninstall_addon(id)
driver.get("https://www.selenium.dev/selenium/web/blank.html")
assert len(driver.find_elements(webdriver.common.by.By.ID, "webextensions-selenium-example")) == 0
def test_install_unsigned_addon_directory(firefox_driver, addon_path_dir):
driver = firefox_driver
driver.install_addon(addon_path_dir, temporary=True)
driver.get("https://www.selenium.dev/selenium/web/blank.html")
injected = driver.find_element(webdriver.common.by.By.ID, "webextensions-selenium-example")
assert injected.text == "Content injected by webextensions-selenium-example"
def test_install_unsigned_addon_directory_slash(firefox_driver, addon_path_dir_slash):
driver = firefox_driver
driver.install_addon(addon_path_dir_slash, temporary=True)
driver.get("https://www.selenium.dev/selenium/web/blank.html")
injected = driver.find_element(webdriver.common.by.By.ID, "webextensions-selenium-example")
assert injected.text == "Content injected by webextensions-selenium-example"
def test_full_page_screenshot(firefox_driver):
driver = firefox_driver
driver.get("https://www.selenium.dev")
driver.save_full_page_screenshot("full_page_screenshot.png")
assert os.path.exists("full_page_screenshot.png")
driver.quit()
def test_set_context():
options = webdriver.FirefoxOptions()
service = webdriver.FirefoxService(service_args=["--allow-system-access"])
driver = webdriver.Firefox(options=options, service=service)
with driver.context(driver.CONTEXT_CHROME):
driver.execute_script("console.log('Inside Chrome context');")
# Check if the context is back to content
assert driver.execute("GET_CONTEXT")["value"] == "content"
driver.quit()
def test_firefox_profile():
from selenium.webdriver.firefox.options import Options
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
options = Options()
firefox_profile = FirefoxProfile()
firefox_profile.set_preference("javascript.enabled", False)
options.profile = firefox_profile
driver = webdriver.Firefox(options=options)
driver.quit()
options.AddArgument("-headless");/examples/dotnet/SeleniumDocs/Browsers/FirefoxTest.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using OpenQA.Selenium;
using OpenQA.Selenium.Firefox;
using OpenQA.Selenium.Internal.Logging;
namespace SeleniumDocs.Browsers
{
[TestClass]
public class FirefoxTest
{
private FirefoxDriver driver;
private string _logLocation;
private string _tempPath;
[TestCleanup]
public void Cleanup()
{
driver?.Quit();
if (!String.IsNullOrEmpty(_logLocation) && File.Exists(_logLocation))
{
LogFile.TryDelete(_logLocation);
}
if (_tempPath != null && Directory.Exists(_tempPath))
{
Directory.Delete(_tempPath, true);
}
}
[TestMethod]
public void BasicOptions()
{
var options = new FirefoxOptions();
driver = new FirefoxDriver(options);
}
[TestMethod]
public void Arguments()
{
var options = new FirefoxOptions();
options.AddArgument("-headless");
driver = new FirefoxDriver(options);
}
[TestMethod]
public async System.Threading.Tasks.Task SetBinary()
{
var options = new FirefoxOptions();
options.BinaryLocation = await GetFirefoxLocationAsync();
driver = new FirefoxDriver(options);
}
[TestMethod]
public void LogsToFile()
{
var service = FirefoxDriverService.CreateDefaultService();
service.LogPath = GetLogLocation();
driver = new FirefoxDriver(service);
driver.Quit(); // Close the Service log file before reading
var lines = LogFile.ReadLines(GetLogLocation());
Assert.IsNotNull(lines.FirstOrDefault(line => line.Contains("geckodriver INFO Listening on")));
}
[TestMethod]
public void LogsToConsole()
{
TestLogHandler testLogHandler = new TestLogHandler();
ResetGlobalLog();
try
{
Log.SetLevel(LogEventLevel.Trace).Handlers.Add(testLogHandler);
var service = FirefoxDriverService.CreateDefaultService();
driver = new FirefoxDriver(service);
Assert.IsTrue(testLogHandler.Events.Count >= 1);
Assert.IsTrue(testLogHandler.Events.Any(e => e.Message.Contains("geckodriver")));
}
finally
{
ResetGlobalLog();
}
}
[TestMethod]
public void LogsLevel()
{
var service = FirefoxDriverService.CreateDefaultService();
service.LogPath = GetLogLocation();
service.LogLevel = FirefoxDriverLogLevel.Debug;
driver = new FirefoxDriver(service);
driver.Quit(); // Close the Service log file before reading
var lines = LogFile.ReadLines(GetLogLocation());
Assert.IsNotNull(lines.FirstOrDefault(line => line.Contains("Marionette\tDEBUG")));
}
[TestMethod]
public void StopsTruncatingLogs()
{
var service = FirefoxDriverService.CreateDefaultService();
service.LogTruncate = false;
service.LogLevel = FirefoxDriverLogLevel.Debug;
driver = new FirefoxDriver(service);
driver.Quit(); // Close the Service log file before reading
var lines = LogFile.ReadLines(GetLogLocation());
Assert.IsNull(lines.FirstOrDefault(line => line.Contains(" ... ")));
}
[TestMethod]
public void SetProfileLocation()
{
var service = FirefoxDriverService.CreateDefaultService();
service.ProfileRoot = GetTempDirectory();
driver = new FirefoxDriver(service);
string profile = ((string)driver.Capabilities.GetCapability("moz:profile")).Replace('\\', '/');
var dirName = profile.Split('/').Last();
var profileDirectories = Directory.GetDirectories(GetTempDirectory()).Select(Path.GetFileName);
Assert.IsTrue(profileDirectories.Contains(dirName));
}
[TestMethod]
public void InstallAddon()
{
SetWaitingDriver();
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string extensionFilePath = Path.Combine(baseDir, "../../../Extensions/webextensions-selenium-example.xpi");
driver.InstallAddOnFromFile(Path.GetFullPath(extensionFilePath));
driver.Url = "https://www.selenium.dev/selenium/web/blank.html";
IWebElement injected = driver.FindElement(By.Id("webextensions-selenium-example"));
Assert.AreEqual("Content injected by webextensions-selenium-example", injected.Text);
}
[TestMethod]
public void UnInstallAddon()
{
driver = new FirefoxDriver();
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string extensionFilePath = Path.Combine(baseDir, "../../../Extensions/webextensions-selenium-example.xpi");
string extensionId = driver.InstallAddOnFromFile(Path.GetFullPath(extensionFilePath));
driver.UninstallAddOn(extensionId);
driver.Url = "https://www.selenium.dev/selenium/web/blank.html";
Assert.AreEqual(driver.FindElements(By.Id("webextensions-selenium-example")).Count, 0);
}
[TestMethod]
public void InstallUnsignedAddon()
{
SetWaitingDriver();
string baseDir = AppDomain.CurrentDomain.BaseDirectory;
string extensionDirPath = Path.Combine(baseDir, "../../../Extensions/webextensions-selenium-example/");
driver.InstallAddOnFromDirectory(Path.GetFullPath(extensionDirPath), true);
driver.Url = "https://www.selenium.dev/selenium/web/blank.html";
IWebElement injected = driver.FindElement(By.Id("webextensions-selenium-example"));
Assert.AreEqual("Content injected by webextensions-selenium-example", injected.Text);
}
private string GetLogLocation()
{
if (string.IsNullOrEmpty(_logLocation) && !File.Exists(_logLocation))
{
_logLocation = Path.GetTempFileName();
}
return _logLocation;
}
private string GetTempDirectory()
{
if (string.IsNullOrEmpty(_tempPath))
{
_tempPath = Directory.CreateTempSubdirectory("profile-").FullName;
}
return _tempPath;
}
private void SetWaitingDriver()
{
driver = new FirefoxDriver();
driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(2);
}
private static async System.Threading.Tasks.Task<string> GetFirefoxLocationAsync()
{
var options = new FirefoxOptions()
{
BrowserVersion = "stable"
};
return await new DriverFinder(options).GetBrowserPathAsync();
}
private void ResetGlobalLog()
{
Log.SetLevel(LogEventLevel.Info);
Log.Handlers.Clear().Handlers.Add(new TextWriterHandler(Console.Error));
}
}
}
class TestLogHandler : ILogHandler
{
public ILogHandler Clone()
{
return this;
}
public void Handle(LogEvent logEvent)
{
Events.Add(logEvent);
}
public IList<LogEvent> Events { get; internal set; } = new List<LogEvent>();
} options.args << '-headless'/examples/ruby/spec/browsers/firefox_spec.rb
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Firefox' do
describe 'Options' do
let(:firefox_location) { driver_finder && ENV.fetch('FIREFOX_BIN', nil) }
it 'basic options' do
options = Selenium::WebDriver::Options.firefox
@driver = Selenium::WebDriver.for :firefox, options: options
end
it 'add arguments' do
options = Selenium::WebDriver::Options.firefox
options.args << '-headless'
@driver = Selenium::WebDriver.for :firefox, options: options
end
it 'sets location of binary' do
options = Selenium::WebDriver::Options.firefox
options.binary = firefox_location
@driver = Selenium::WebDriver.for :firefox, options: options
end
end
describe 'Service' do
let(:file_name) { Tempfile.new('geckodriver').path }
let(:root_directory) { Dir.mktmpdir }
after do
FileUtils.rm_f(file_name)
FileUtils.rm_rf(root_directory)
end
it 'logs to file' do
service = Selenium::WebDriver::Service.firefox
service.log = file_name
@driver = Selenium::WebDriver.for :firefox, service: service
expect(File.readlines(file_name).first).to include("geckodriver\tINFO\tListening on")
end
it 'logs to console' do
service = Selenium::WebDriver::Service.firefox
service.log = $stdout
expect {
@driver = Selenium::WebDriver.for :firefox, service: service
}.to output(/geckodriver INFO Listening on/).to_stdout_from_any_process
end
it 'sets log level' do
service = Selenium::WebDriver::Service.firefox
service.log = file_name
service.args += %w[--log debug]
@driver = Selenium::WebDriver.for :firefox, service: service
expect(File.readlines(file_name).grep(/Marionette DEBUG/).any?).to eq true
end
it 'stops truncating log lines' do
service = Selenium::WebDriver::Service.firefox(log: file_name, args: %w[--log debug])
service.args << '--log-no-truncate'
@driver = Selenium::WebDriver.for :firefox, service: service
expect(File.readlines(file_name).grep(/ \.\.\. /).any?).to eq false
end
it 'sets default profile location' do
service = Selenium::WebDriver::Service.firefox
service.args += ['--profile-root', root_directory]
@driver = Selenium::WebDriver.for :firefox, service: service
profile_location = Dir.new(@driver.capabilities['moz:profile'])
expect(profile_location.path.gsub('\\', '/')).to include(root_directory)
end
end
describe 'Features' do
let(:driver) { start_firefox }
it 'installs addon' do
extension_file_path = File.expand_path('../spec_support/extensions/webextensions-selenium-example.xpi', __dir__)
driver.install_addon(extension_file_path)
driver.get 'https://www.selenium.dev/selenium/web/blank.html'
injected = driver.find_element(id: 'webextensions-selenium-example')
expect(injected.text).to eq 'Content injected by webextensions-selenium-example'
end
it 'uninstalls addon' do
extension_file_path = File.expand_path('../spec_support/extensions/webextensions-selenium-example.xpi', __dir__)
extension_id = driver.install_addon(extension_file_path)
driver.uninstall_addon(extension_id)
driver.get 'https://www.selenium.dev/selenium/web/blank.html'
expect(driver.find_elements(id: 'webextensions-selenium-example')).to be_empty
end
it 'installs unsigned addon' do
extension_dir_path = File.expand_path('../spec_support/extensions/webextensions-selenium-example/', __dir__)
driver.install_addon(extension_dir_path, true)
driver.navigate.to 'https://www.selenium.dev/selenium/web/blank.html'
injected = driver.find_element(id: 'webextensions-selenium-example')
expect(injected.text).to eq 'Content injected by webextensions-selenium-example'
end
it 'takes full page screenshot' do
driver.navigate.to 'https://www.selenium.dev/selenium/web/blank.html'
Dir.mktmpdir('screenshot_test') do |dir|
screenshot = driver.save_full_page_screenshot(File.join(dir, 'screenshot.png'))
expect(screenshot).to be_a File
end
end
it 'sets the context' do
driver.context = 'content'
expect(driver.context).to eq 'content'
end
end
describe 'Profile' do
it 'creates a new profile' do
profile = Selenium::WebDriver::Firefox::Profile.new
profile['browser.download.dir'] = '/tmp/webdriver-downloads'
options = Selenium::WebDriver::Firefox::Options.new(profile: profile)
expect(options.profile).to eq(profile)
end
end
def driver_finder
options = Selenium::WebDriver::Options.firefox(browser_version: 'stable')
service = Selenium::WebDriver::Service.firefox
finder = Selenium::WebDriver::DriverFinder.new(options, service)
ENV['GECKODRIVER_BIN'] = finder.driver_path
ENV['FIREFOX_BIN'] = finder.browser_path
end
end
let driver = new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(options.addArguments('--headless=new'))
.build();/examples/javascript/test/browser/chromeSpecificCaps.spec.js
const Chrome = require('selenium-webdriver/chrome');
const { Browser, Builder } = require("selenium-webdriver");
const options = new Chrome.Options();
describe('Should be able to Test Command line arguments', function () {
it('headless', async function () {
let driver = new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(options.addArguments('--headless=new'))
.build();
await driver.get('https://www.selenium.dev/selenium/web/blank.html');
await driver.quit();
});
it('exclude switches', async function () {
let driver = new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(options.excludeSwitches('enable-automation'))
.build();
await driver.get('https://www.selenium.dev/selenium/web/blank.html');
await driver.quit();
});
it('Keep browser open - set detach to true ', async function () {
let driver = new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(options.detachDriver(true))
.build();
await driver.get('https://www.selenium.dev/selenium/web/blank.html');
// As tests runs in ci, quitting the driver instance to avoid any failures
await driver.quit();
});
xit('Start browser from specified location ', async function () {
let driver = new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(options.setChromeBinaryPath(`Path to chrome binary`))
.build();
await driver.get('https://www.selenium.dev/selenium/web/blank.html');
await driver.quit();
});
it('Basic Chrome test', async function () {
const Options = new Chrome.Options();
let driver = new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(Options)
.build();
await driver.get('https://www.selenium.dev/selenium/web/blank.html');
await driver.quit();
});
it('Add Extension', async function () {
const options = new Chrome.Options();
let driver = new Builder()
.forBrowser(Browser.CHROME)
.setChromeOptions(options.addExtensions(['./test/resources/extensions/webextensions-selenium-example.crx']))
.build();
await driver.get('https://www.selenium.dev/selenium/web/blank.html');
await driver.quit();
});
});A minimal GitHub Actions workflow, shown for Python, is a starting point only. The hosted Ubuntu runner already has Chrome and Firefox installed; the only Selenium-specific step is installing the binding:
name: tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install selenium pytest
- run: pytest
The same shape applies to every binding: check out, install the language toolchain, install the Selenium package, run the standard test command.
Common errors
- Unable to locate driver: Selenium Manager could not find or download a driver. Check the browser is installed and the network allows the download.
- SessionNotCreatedException:
the driver on the
PATHdoes not match the installed browser, or the browser cannot start without a display. Remove stale drivers and run headless. - InvalidArgumentException: a browser option is wrong for this browser or version, for example a user data directory that is already in use by another session.
Reference
- Browser options and the per-browser pages for Chrome, Edge and Firefox
- Selenium Manager: caching, proxies and offline use
- Grid getting started
- Test practices: keeping tests independent so they can run in parallel



