Moving the browser off the machine that runs the tests, with Selenium Grid and the project’s Docker images, so a suite can run in parallel and against several browsers.
A suite that takes an hour because every test starts its own browser on the same machine.
A requirement to test on a browser that is not installed where the tests run. A build server
that must not have a browser on it at all. In each case the browser needs to be somewhere
other than the test process.
Recommended approach
Selenium Grid is a server that accepts WebDriver sessions over HTTP and routes them to
browsers it manages. Your test code changes in exactly one place: instead of constructing a
local driver, construct a remote one with the Grid URL and the browser options. Everything
else stays the same, so a suite can run locally during development and on a Grid in CI with a
single configuration switch.
Standalone mode runs the whole Grid in one process and is the right starting point. It
needs a Java runtime and the Selenium Server jar, listens on http://localhost:4444, and can
use Selenium Manager to fetch drivers for the browsers it finds. Hub and Node spreads
browsers across several machines, and distributed mode separates each Grid component for
large installations.
The quickest way to get a Grid, and the only way to get one with no browser on the host, is
the project’s own Docker images in the
docker-selenium repository. They package the
Grid, a browser and its driver, and are published for each Selenium release. A single
docker run gives you a standalone Grid with Chrome, Firefox or Edge; the repository also has
Docker Compose and Kubernetes examples, video recording, and VNC for watching a session.
When the browser is remote, files to upload must reach the node first, through a file
detector, and downloads must be enabled on the Grid and fetched back through the client. Both
are covered on the remote WebDriver page.
packagedev.selenium.drivers;importdev.selenium.BaseTest;importjava.io.File;importjava.io.IOException;importjava.net.URL;importjava.nio.file.Files;importjava.nio.file.Path;importjava.time.Duration;importjava.util.ArrayList;importjava.util.Comparator;importjava.util.List;importjava.util.Map;importorg.junit.jupiter.api.Assertions;importorg.junit.jupiter.api.BeforeEach;importorg.junit.jupiter.api.Test;importorg.openqa.selenium.By;importorg.openqa.selenium.HasDownloads;importorg.openqa.selenium.WebElement;importorg.openqa.selenium.chrome.ChromeOptions;importorg.openqa.selenium.chromium.HasCasting;importorg.openqa.selenium.remote.Augmenter;importorg.openqa.selenium.remote.LocalFileDetector;importorg.openqa.selenium.remote.RemoteWebDriver;importorg.openqa.selenium.remote.http.ClientConfig;importorg.openqa.selenium.support.ui.WebDriverWait;publicclassRemoteWebDriverTestextendsBaseTest{URLgridUrl;@BeforeEachpublicvoidstartGrid(){gridUrl=startStandaloneGrid();}@TestpublicvoidrunRemote(){ChromeOptionsoptions=getDefaultChromeOptions();driver=newRemoteWebDriver(gridUrl,options);}@Testpublicvoiduploads(){ChromeOptionsoptions=getDefaultChromeOptions();driver=newRemoteWebDriver(gridUrl,options);driver.get("https://the-internet.herokuapp.com/upload");FileuploadFile=newFile("src/test/resources/selenium-snapshot.png");((RemoteWebDriver)driver).setFileDetector(newLocalFileDetector());WebElementfileInput=driver.findElement(By.cssSelector("input[type=file]"));fileInput.sendKeys(uploadFile.getAbsolutePath());driver.findElement(By.id("file-submit")).click();WebElementfileName=driver.findElement(By.id("uploaded-files"));Assertions.assertEquals("selenium-snapshot.png",fileName.getText());}@Testpublicvoiddownloads()throwsIOException{ChromeOptionsoptions=getDefaultChromeOptions();options.setEnableDownloads(true);driver=newRemoteWebDriver(gridUrl,options);List<String>fileNames=newArrayList<>();fileNames.add("file_1.txt");fileNames.add("file_2.jpg");driver.get("https://www.selenium.dev/selenium/web/downloads/download.html");driver.findElement(By.id("file-1")).click();driver.findElement(By.id("file-2")).click();newWebDriverWait(driver,Duration.ofSeconds(5)).until(d->((HasDownloads)d).getDownloadableFiles().contains("file_2.jpg"));List<String>files=((HasDownloads)driver).getDownloadableFiles();// Sorting them to avoid differences when comparing the filesfileNames.sort(Comparator.naturalOrder());files.sort(Comparator.naturalOrder());Assertions.assertEquals(fileNames,files);StringdownloadableFile=files.get(0);PathtargetDirectory=Files.createTempDirectory("download");((HasDownloads)driver).downloadFile(downloadableFile,targetDirectory);StringfileContent=String.join("",Files.readAllLines(targetDirectory.resolve(downloadableFile)));Assertions.assertEquals("Hello, World!",fileContent);((HasDownloads)driver).deleteDownloadableFiles();Assertions.assertTrue(((HasDownloads)driver).getDownloadableFiles().isEmpty());}@Testpublicvoidaugment(){ChromeOptionsoptions=getDefaultChromeOptions();driver=newRemoteWebDriver(gridUrl,options);driver=newAugmenter().augment(driver);Assertions.assertTrue(driverinstanceofHasCasting);}@TestpublicvoidremoteWebDriverBuilder(){driver=RemoteWebDriver.builder().address(gridUrl).oneOf(getDefaultChromeOptions()).setCapability("ext:options",Map.of("key","value")).config(ClientConfig.defaultConfig()).build();Assertions.assertTrue(driverinstanceofHasCasting);}}
const{Browser,Builder,By}=require('selenium-webdriver')constChrome=require('selenium-webdriver/chrome')constremote=require('selenium-webdriver/remote')constassert=require('node:assert')constfs=require('node:fs')constos=require('node:os')constpath=require('node:path')const{startGrid,stopGrid}=require('./gridServer')asyncfunctionwaitForFile(filePath,timeout){constdeadline=Date.now()+timeoutwhile(!fs.existsSync(filePath)){if(Date.now()>deadline){thrownewError(`Timed out waiting for ${filePath} to be written`)}awaitnewPromise((resolve)=>setTimeout(resolve,100))}}describe('Remote WebDriver Test',function(){letdriverletgridUrlletgridProcessbeforeEach(asyncfunction(){constgrid=awaitstartGrid()gridUrl=grid.urlgridProcess=grid.process})afterEach(asyncfunction(){try{if(driver){awaitdriver.quit()driver=null}}finally{stopGrid(gridProcess)}})it('Basic Example',asyncfunction(){constoptions=newChrome.Options().addArguments('--no-sandbox')driver=newBuilder().forBrowser(Browser.CHROME).setChromeOptions(options).usingServer(gridUrl).build()})it('Uploads',asyncfunction(){constoptions=newChrome.Options().addArguments('--no-sandbox')driver=newBuilder().forBrowser(Browser.CHROME).setChromeOptions(options).usingServer(gridUrl).build()awaitdriver.get('https://the-internet.herokuapp.com/upload')constuploadFile=path.resolve('./test/resources/selenium-snapshot.png')awaitdriver.setFileDetector(newremote.FileDetector())awaitdriver.findElement(By.css('input[type=file]')).sendKeys(uploadFile)awaitdriver.findElement(By.id('file-submit')).click()constfileName=awaitdriver.findElement(By.id('uploaded-files')).getText()assert.strictEqual(fileName,'selenium-snapshot.png')})it('Downloads',asyncfunction(){constoptions=newChrome.Options().addArguments('--no-sandbox').enableDownloads()driver=newBuilder().forBrowser(Browser.CHROME).setChromeOptions(options).usingServer(gridUrl).build()constfileNames=['file_1.txt','file_2.jpg']awaitdriver.get('https://www.selenium.dev/selenium/web/downloads/download.html')awaitdriver.findElement(By.id('file-1')).click()awaitdriver.findElement(By.id('file-2')).click()awaitdriver.wait(async(d)=>{constdownloaded=awaitd.getDownloadableFiles()returnfileNames.every((name)=>downloaded.includes(name))},5000)constfiles=awaitdriver.getDownloadableFiles()assert.deepStrictEqual(files.sort(),fileNames.sort())consttargetDirectory=fs.mkdtempSync(path.join(os.tmpdir(),'downloads-'))constdownloadableFile=files[0]awaitdriver.downloadFile(downloadableFile,targetDirectory)constdownloadedFilePath=path.join(targetDirectory,downloadableFile)awaitwaitForFile(downloadedFilePath,5000)constfileContent=fs.readFileSync(downloadedFilePath,'utf8')assert.strictEqual(fileContent.trim(),'Hello, World!')awaitdriver.deleteDownloadableFiles()assert.deepStrictEqual(awaitdriver.getDownloadableFiles(),[])})it('Browser specific functionalities',asyncfunction(){constoptions=newChrome.Options().addArguments('--no-sandbox')driver=newBuilder().forBrowser(Browser.CHROME).setChromeOptions(options).usingServer(gridUrl).build()assert.ok(driverinstanceofChrome.Driver)})})
Common errors
SessionNotCreatedException:
no node on the Grid can satisfy the requested browser and version. Check the Grid’s status
page at the Grid URL for the capabilities it offers.
InvalidArgumentException:
a local file path was sent to a remote browser. Set a file detector so the file is uploaded.
WebDriverException
with a connection error: the Grid URL is wrong, the server is not running, or a firewall is
between the client and the Grid.