Selenium WebDriver – File Downloads & Uploads Using Docker Grids

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.

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.

doc-vol-mapping-02

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.

doc-vol-mapping-03

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?

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.

 

doc-vol-mapping-05

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!

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 🙂

 

Share This:

12 thoughts on “Selenium WebDriver – File Downloads & Uploads Using Docker Grids

  1. Thanks for this genius post. However, how will this be approached if you are running automated tests on the Jenkins CI server which spins up a docker. In this case there is no ‘host machine’; only possibly the Jenkins workspace? How will the volume mapping be approached for copying the data from the docker to the Jenkins workspace? Would one still be able to access the file, edit and save etc.?

    1. Host machine is basically the machine in which you create the docker container. I assume you Jenkins might do that in one of its nodes/slaves. Yes, you could do map any directory under your workspace to the docker container. So that after the build, jenkins could archive all the files if you need them.

  2. The docker is created on a mac pc and started up there. The Jenkins automated test then run on the docker. Will this pose any problems with the solution on your reply above about mapping the home/seluser/Downloads to the jenkins workspace?

  3. Update: Before trying to map the home/seluser/Downloads to the jenkins workspace. I thought I should try it out on the host mac pc first using the following:

    docker run -d -P -p 4444:4444 selenium/standalone-chrome:3.7.1-beryllium -v /Users/MyUsername/Downloads/MappedFolder:/home/seluser/Downloads. However when I run the download test,

    Expected result:
    /Users/MyUsername/Downloads/MappedFolder contains downloaded file

    Actual result:
    /Users/MyUsername/Downloads/MappedFolder is empty

    1. Do the followings:

      1. Check inside the container if the file is really present in the /home/seluser/Downlaods
      2. Always use absolute path to map to avoid confusions. Ensure that host folder has write permission for the users.
  4. I can confirm that the files are in the /home/seluser/Downlaods folder.

    However, I just realised that whenever i try:

    docker run -d -P -p 4444:4444 selenium/standalone-chrome:3.7.1-beryllium -v /Users/MyUsername/Downloads/MappedFolder:/home/seluser/Downloads.

    I get the following error and the docker does not start:
    docker: Error response from daemon: oci runtime error: container_linux.go:247: starting container process caused “exec: \”-v\”: executable file not found in $PATH”

    But when I try to re-arrange the command:
    docker run -v /Users/MyUsername/Downloads/MappedFolder:/home/seluser/Downloads -d -P -p 4444:4444 selenium/standalone-chrome:3.7.1-beryllium

    The docker starts successfully. The file is downloaded in the docker container but not on the host.

    I tried using docker cp, which worked for copying data to my host. But this is a manual process that will need to be done everytime there is a change in the docker container, and is not what I am looking for in the automated test. The permission seem ok and I am not getting any error messages about permissions.

  5. Hi,

    Could you please include the part of your code where the remote webdriver is instantiated and also explain how you were able to talk to the docker considering that it was started with ‘sudo’?

    Thanks

  6. I’ve noticed that in my case the reason I am not able to talk to the docker container after starting it up using sudo is likely due to the use of the following command:

    export DOCKER_HOST=docker

  7. Hello,
    I was wondering how will this work if I am running my test pointing to a docker swarm. My eclipse testcases are located on a Windows 10 and I am running my test pointing to a selenium hub (1 manager, 2 worker nodes) which was installed on a different machine. In my case, when I download a file from a chrome browser running on a worker node, how does it map back to my local file on the Windows 10? Is there a way to map the volumes remotely?

    1. This is an article as part of docker series. Here the assumption is entire test execution along with grid is brought up as and when required and execution is happening in a single machine.So it will work just fine in this case. In your case, you are using an existing selenium grid in a remote machine. This is not docker specific issue. Even if you do not use docker for the grid, your issue will still be there. Usually grabbing the URL for the resource and directly downloading via tests is very easy. Assuming the application does not do that, you can expose another service – like a nanoservice in the grid machine which gives you the file downloaded as and when you call the endpoint. You can also use some SSH client to connect to the machine and get the file.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.