Overview:
TestAutomationGuru has released few articles on using docker for Selenium Grids & to run your automated inside the docker containers. This approach has a lot of advantages like saving your time from setting up your remote/cloud machines & dealing with dependency related issues. It is easily scalable as well!
If you have not read below articles on docker before, I would suggest you to take a lot them first – so that you could understand this article better.
- How To Create A Disposable Selenium Grid Infrastructure using Docker
- How To Run Automated Tests Inside A Docker Container – Part 1
- How To Run Multiple Test Suites Using Docker Compose – Part 2
In this article, Lets see how we could handle file upload, downloads, accessing the file system while using dockerized selenium tests.
Sample Application:
To explain things better, I am going to use this automation practice site – http://the-internet.herokuapp.com for playing with file upload, download.
File Upload:
File upload is relatively very simple when you use dockerized grid . You need to ensure that you set the file detector as shown here. Running the test as usual will upload the file in the remote/docker grid browser.
((RemoteWebDriver) driver).setFileDetector(new LocalFileDetector());
Sample Page Object:
public class HerokuAppUploadPage {
private final WebDriver driver;
private final WebDriverWait wait;
@FindBy(id="file-upload")
private WebElement fileUpload;
@FindBy(id="file-submit")
private WebElement uploadBtn;
@FindBy(id="uploaded-files")
private WebElement uploadedFileName;
public HerokuAppUploadPage(final WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
this.wait = new WebDriverWait(driver, 30);
}
public void goTo() {
this.driver.get("http://the-internet.herokuapp.com/upload");
}
public void uploadFile(String path){
if(driver instanceof RemoteWebDriver){
((RemoteWebDriver) driver).setFileDetector(new LocalFileDetector());
}
this.fileUpload.sendKeys(path);
this.uploadBtn.click();
wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("uploaded-files")));
}
//once the upload is success, get the name of the file uploaded
public String getUploadedFileName(){
return this.uploadedFileName.getText().trim();
}
}
Sample Test:
public class UploadFileTest extends BaseTest {
private HerokuAppUploadPage herokuAppPage;
@BeforeTest
public void setUp() throws MalformedURLException {
super.setUp();
herokuAppPage = new HerokuAppUploadPage(driver);
}
@Test
public void uploadTest() throws InterruptedException {
herokuAppPage.goTo();
herokuAppPage.uploadFile("/home/qa/Downloads/sample.xlsx");
Assert.assertEquals("sample.xlsx", herokuAppPage.getUploadedFileName());
}
}
File Download:
Lets look at an easy example first. In the below, case, we could simply get the href attribute value to build the URL of the static file which we need to download.
<a href="download/sample.xlsx">sample.xlsx</a>
Even if you use remote webdriver for a docker grid, you could still get the attribute value and using the link, you could download the file easily by running some commands as shown here.
wget http://the-internet.herokuapp.com/download/sample.xlsx
Not all the file downloads might be very easy as the above one. In my application, there is no href attribute for a file download link! The file is getting generated at run time, gets downloaded immediately. In this case, It might be a challenge to assert if the file is downloaded successfully if you are using docker grid.
When you download a file using dockerized selenium grid, It downloads and keeps the downloaded file inside the container. The outside world which is your machine/any remote machine running docker will not have any information on it.
In this picture, Java testng/junit tests running on your host, use the dockerized selenium grid. It can control the browser inside the container via RemoteWebDriver object. But, there is no way to access container directory. In this case, How can we assert that file has been downloaded?
Volume Mapping:
Docker has a concept of ‘volume mapping‘ which is basically used to map a directory of the host machine to a directory inside the container. By volume mapping, the file gets downloaded inside the container’s directory will be available in your host directory as well.
To map a directory, we use below options when you start your docker container.
-v host-absolute-directory-path:container-abolute-directory-path
When you have your docker selenium grid, the files are getting downloaded at /home/seluser/Downloads inside the container. So you could map them to a directory somewhere in your host machine.
You start your hub as shown here.
sudo docker run -d -p 4444:4444 --name selenium-hub selenium/hub
When you start your chrome/firefox nodes, do the volume mapping as shown here.
sudo docker run -d --link selenium-hub:hub -v /home/vins/Downloads:/home/seluser/Downloads selenium/node-chrome
sudo docker run -d --link selenium-hub:hub -v /home/vins/Downloads:/home/seluser/Downloads selenium/node-firefox
I mapped my host machine download directory (it can be any directory in your host with enough permission) to a docker container’s download directory.
Let’s see how it works.
Sample Page Object:
public class HerokuAppDownloadPage {
private final WebDriver driver;
@FindBy(linkText="some-file.txt")
private WebElement downloadFile;
public HerokuAppDownloadPage(final WebDriver driver) {
this.driver = driver;
PageFactory.initElements(driver, this);
}
public void goTo() {
this.driver.get("http://the-internet.herokuapp.com/download");
}
public void downloadFile(){
//this will download file into the /home/seluser/Downloads directory
this.downloadFile.click();
}
}
When I run above code, I could see the downloaded file in my host directory.
ls -al
-rw-r--r-- 1 vins vins 0 Mar 3 11:25 some-file.txt
Sample Test:
@Test
public void downloadTest() {
downloadPage.goTo();
downloadPage.downloadFile();
Path path = Paths.get("/home/vins/Downloads/some-file.txt");
//this will wait until the file download is complete.
Awaitility.await()
.atMost(1, TimeUnit.MINUTES)
.until(() -> {
return path.toFile().exists();
});
Assert.assertTrue(path.toFile().exists());
}
Now, when I use docker selenium grid with volume mapping, the downloaded file is present in my host machine in the specific directory which I mapped. [In case of any large file download, the dowload process might take a while. I would suggest you to take a look at the awaitility library as shown above to wait for actions to complete . Check this article on that.]
This is great, so far! But, What about your tests itself runs inside a container as shown in the articles below.!? How can 2 containers share the file system?
- How To Run Automated Tests Inside A Docker Container – Part 1
- How To Run Multiple Test Suites Using Docker Compose – Part 2
Volume Mapping Between Containers:
Docker volume mapping will help here as well. In this case, we do the mapping twice. One for your browser node – to get the file in the host directory as we saw above and another mapping for your test container. This is to map the host directory which has the file to your container directory. So that your tests inside the container could see the file.
Lets assume that I have created a docker image with my tests included. If you are not sure how, You need to check these articles first!
- How To Run Automated Tests Inside A Docker Container – Part 1
- How To Run Multiple Test Suites Using Docker Compose – Part 2
I assume that there is a directory – /usr/share/tag/Downloads in my container. I also assume that all the downloaded files would be present there somehow like magic!!. So, in that case, I should modify my tests as shown here.
@Test
public void downloadTest() {
downloadPage.goTo();
downloadPage.downloadFile();
Path path = Paths.get("/user/share/tag/Downloads/some-file.txt");
//this will wait until the file download is complete.
Awaitility.await()
.atMost(1, TimeUnit.MINUTES)
.until(() -> {
return path.toFile().exists();
});
Assert.assertTrue(path.toFile().exists());
}
Now when I run my tests inside the container, I do the volume mapping. /home/vins/Downloads is the host directory which is expected to have all the downloaded files. Now we map that to a container which has my above test which is expecting the files to be in /usr/share/tag/Downloads.
sudo docker run -v /home/vins/Downloads:/usr/share/tag/Downloads vinsdocker/containertest:downloadtest
Now the rest is simple. Running the above command will start a container and run the test inside the container – this will trigger the execution in another docker grid. Since we map same host folder to both of these containers, the containers can share the files via the mapped folder.
Docker Compose:
As you might already know, we do not need to do the mapping every time through command line. Instead, we could define that in the docker-compose file. Create a docker-compose.yml file as shown here with appropriate volume mappings for your grid and test containers.
version: "3"
services:
selenium-hub:
image: selenium/hub
container_name: selenium-hub
ports:
- "4444:4444"
chrome:
image: selenium/node-chrome
depends_on:
- selenium-hub
environment:
- HUB_PORT_4444_TCP_ADDR=selenium-hub
- HUB_PORT_4444_TCP_PORT=4444
volumes:
- /home/qa/Downloads:/home/seluser/Downloads
firefox:
image: selenium/node-firefox
depends_on:
- selenium-hub
environment:
- HUB_PORT_4444_TCP_ADDR=selenium-hub
- HUB_PORT_4444_TCP_PORT=4444
volumes:
- /home/qa/Downloads:/home/seluser/Downloads
containertest:
image: vinsdocker/containertest:demo
depends_on:
- chrome
- firefox
environment:
- MODULE=upload-module.xml
- BROWSER=firefox
- SELENIUM_HUB=selenium-hub
volumes:
- /home/qa/Downloads:/usr/share/tag/Downloads
Now issuing a single command does all these things for you within a fraction of second!
- Creating selenium hub
- Chrome node & maps the directories
- Firefox node & maps the directories
- Test container & maps the directories
- Triggers the test
Summary:
Docker saves a huge amount of effort related to infrastructure set ups to run the test automation. We define everything in a single file. By feeding this to docker-compose, you create the entire setup with all the dependencies, directories etc in a single command and gets your tests executed.
Happy Testing & Subscribe 🙂