0

I am writing a selenium test to verify a file being downloaded. It works fine in locally. And I can easily access the file both through the 'target' folder and inside the container /home/seluser/Downloads.

The test script is:

@BeforeMethod
public void setUp() throws  MalformedURLException {

    folder = new File("target");
    
    for(File file : folder.listFiles()) {
        file.delete();
    } 

    System.setProperty("webdriver.chrome.driver", "chromedriver.exe");
    ChromeOptions options = new ChromeOptions();

    Map<String, Object> prefs = new HashMap<String, Object>();
    prefs.put("profile.default_content_settings.popups", 0);
    prefs.put("download.default_directory", folder.getAbsolutePath());

    options.setExperimentalOption("prefs", prefs);
    DesiredCapabilities cap = new DesiredCapabilities();
    cap.setBrowserName("chrome");
    cap.setCapability(ChromeOptions.CAPABILITY, options);

    //driver = new ChromeDriver(cap);
    //driver = new RemoteWebDriver(new URL("http://localhost:4444/wd/hub"), cap);
    driver = new RemoteWebDriver(new URL("http://docker:4444/wd/hub"), cap);
}

@AfterMethod
public void tearDown() {
    driver.quit();
}

@Test
public void downloadFileTest() throws InterruptedException {
    driver.get("http://the-internet.herokuapp.com/download");
    driver.findElement(By.linkText("some-file.txt")).click();

    Thread.sleep(2000);
    File listOffFiles[] = folder.listFiles();
    Assert.assertTrue(listOffFiles.length > 0);

    for(File file : listOffFiles) {
        Assert.assertTrue(file.length() > 0);
    } 
}

Let me explain a little. First I create a folder named "target" in the project root repository. Then I set the download path in the chrome docker comtainer via container volumes in docker-compose file.

version: "3"
services:
  chrome:
    image: selenium/node-chrome:4.0.0-20211013
    container_name: chrome
    shm_size: 2gb
    depends_on:
      - selenium-hub
    volumes:
      - ./target:/home/seluser/Downloads
    environment:
      - SE_EVENT_BUS_HOST=selenium-hub
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
      - SE_NODE_GRID_URL=http://localhost:4444
    ports:
      - "6900:5900"
  selenium-hub:
    image: selenium/hub:4.0.0-20211013
    container_name: selenium-hub
    ports:
      - "4444:4444"

This setting works fine locally. when I run it in the gitlab CI, i cannot push a empty fold to gitlab, so i have to create a file and store it in the folder and push it to gitlab. But it the test script, I delete this file in the setup stage in case it disturb the assertion. But the pipeline fails. The result does not give me more details , just said the assertionException. Here is the gitlab-ci.yml:

image: adoptopenjdk/openjdk11

stages:
  - gradle-build
  - docker-test

.gradle_template: &gradle_definition
  variables:
    GRADLE_OPTS: "-Dorg.gradle.daemon=false"
  before_script:
    - export GRADLE_USER_HOME=${CI_PROJECT_DIR}/.gradle

gradle-build:
  <<: *gradle_definition
  stage: gradle-build
  script:
    - chmod +x ./gradlew
    - ./gradlew --build-cache assemble
  cache:
    key: "$CI_COMMIT_REF_NAME"
    paths:
      - build
      - .gradle
  artifacts:
    paths:
      - build/libs/*.jar
    expire_in: 1 day
  only:
    - feature/multi-browsers

chrome-test:
  stage: docker-test
  image:
    name: docker/compose:1.29.2
    entrypoint: [ "/bin/sh", "-c" ]
  services:
    - docker:19.03.12-dind
  variables:
    DOCKER_TLS_CERTDIR: ""
    DOCKER_DRIVER: overlay2
    DOCKER_HOST: tcp://docker:2375/
  cache:
    key: "$CI_COMMIT_REF_NAME"
    policy: pull
    paths:
      - build
      - .gradle
  dependencies:
    - gradle-build
  before_script:
    - docker info
    - docker-compose --version
  script:
    - apk add --no-cache docker-compose
    - apk add openjdk11
    - docker-compose down
    - docker-compose up --build --force-recreate --no-deps -d
    - echo "Hello, chrome-test"
    - chmod +x ./gradlew
    - ./gradlew :test --tests "LogInTest_chrome"
  artifacts:
    when: always
    reports:
      junit: build/test-results/test/**/TEST-*.xml
    paths:
      - build/reports/*
    expire_in: 1 day
  only:
    - feature/multi-browsers

I wonder if someone has experience with this download test in gitlab CI. I think the download path I set maybe not work in gitlab CI. I even have no ideas how to check if a file is downloaded or not it gitlab CI.

Zoe
  • 27,060
  • 21
  • 118
  • 148
AutoBeginner
  • 43
  • 1
  • 8
  • @sytech I am sorry to bother you. But you have very good knowledge on docker and gitlabCI. So I wonder if you have any suggestions on this topic. – AutoBeginner Nov 15 '21 at 12:18
  • Sure, I'll provide a answer shortly. The solution here has a similar connotation to your previous question. The bind-mounted docker volume and associated files are going to exist on the container / docker daemon -- not on the runner host. – sytech Nov 15 '21 at 18:12
  • I'd also maybe double-check your method of dealing with no being able to push an empty directory. Maybe just add `mkdir target` to the beginning of your job script. I'm not 100% certain your Java code is _creating_ the directory. – sytech Nov 15 '21 at 18:44
  • Actually I take back my previous comment -- The bind mount should work properly when using `docker:dind` with docker-compose -- I don't think you need to create an empty file and push it to GitLab. That may be failing your test case. The directory `target` will be created when you do `docker compose up` if it doesn't exist. Try deleting that directory from your code and see if that helps. Including debugging messages for your test cases / assertions would also be helpful. – sytech Nov 15 '21 at 19:26
  • @sytech Hi, Thanks for your reply. I have tried deleting the folder in my project repository and did not changed any code in the test script. when i run it locally, it does generate a 'target' folder in my project repository and run successfully. But when I run it in gitlab CI, it failed, and the error is 'AssertionError'. – AutoBeginner Nov 16 '21 at 09:54
  • There's some other problem -- you should add some logging to your test and have your assertions output more useful information. It's hard to say what the problem is if all we have is "AssertionError" and don't even know what line that occurs on. – sytech Nov 16 '21 at 09:56
  • I have a confusing about volume mapping. when i set "./target:/home/seluser/Downloads" in the deocker-compose file. I should have mapped the docker host and the chrome container. But how i can find this target folder in docker host? I can see the folder in my project repository, so dose it means i have mapped my local computer, the docker host and the chrome container? I am not sure if it is the reason it didn't work in gitlab CI. – AutoBeginner Nov 16 '21 at 10:27
  • I added a assertion in the gitlab CI for the first case ./target:/home/seluser/Downloads, and the listOffFiles.length get 0. so that means no files downloaded actually. and this setup does not work in gitlab CI. – AutoBeginner Nov 16 '21 at 11:12
  • I have tried also mapped '/opt/target:/home/seluser/Downloads'. This does maps the docker host and chrome container. And this time no folder appears in my project repository. But the test fails. And no files really downloaded in the chrome container : /home/seluser/Downloads. I tried set folder = new File("/home/seluser/Downloads"); or folder = new File("/opt/target"); The error is nolpointerException in listOffFiles.length. – AutoBeginner Nov 16 '21 at 11:51
  • When this 'works' locally, are you still using docker-compose to run this? Or are you just running the java app alone? I noticed the `System.setProperty("webdriver.chrome.driver", "chromedriver.exe");` which had me a bit puzzled, since it looked like you were using Linux images. – sytech Nov 16 '21 at 17:45
  • Yes, when i run it locally, i still use docker-compose in intellij. System.setProperty("webdriver.chrome.driver", "chromedriver.exe"); is used in the first step when I run the test in the local browser. I have three steps to run my test. First, with chromedriver.exe which is downloaded in my computer. If it succeeds, I then run the test on docker locally with selenium hub and different browsers docker images. Last, push it to the gitLab CI. so i use the same docker-compose file in the second and third steps. – AutoBeginner Nov 17 '21 at 07:08
  • when i set 'volumes: - ./target:/home/seluser/Downloads', then i can see the volume bindings in the chrome container shows 'Host path: C:\Users\MyName\IdeaProjects\selenium-project\target; Container path: /home/seluser/Downloads. But if I set 'volumes: - /target:/home/seluser/Downloads' , then Host path became: /target. And the test fails, and no files downloaded in home/seluser/Downloads. I am quite doubt about this volume mapping. Because the second way looks correct, and it should mapping the host and the container without connecting to my computer. But it does not work in my case. – AutoBeginner Nov 17 '21 at 07:45

2 Answers2

0

I dont exactly know what you expect to run on http://docker:4444, but that seems not correct at: driver = new RemoteWebDriver(new URL("http://docker:4444/wd/hub"), cap);.

I geuss you want to connect with the selenium-hub instead. Personally I always prefer the use of GitLab services over running docker-compose in your pipeline. Maybe this answer about running E2E tests with Docker in GitLab helps.

Robert-Jan Kuyper
  • 2,418
  • 12
  • 22
  • Hi, thanks for your reply. I need to set volumes for every browser container in order to set the download path . If I run selenium/chrome as service, then i could not set volumes. driver = new RemoteWebDriver(new URL("http://docker:4444/wd/hub"), cap); is for correct selenium hub and the test scripts. Because i used "http://seleniumhub'sIPaddress:4444/wd/hub" But it didn't work. I got help from here. https://stackoverflow.com/questions/69915035/why-cant-i-connect-to-selenium-docker-compose-service-from-my-gitlab-job – AutoBeginner Nov 15 '21 at 13:16
  • Now my problem is when i want to download a file, what can be the download path in gitlab-ci? it seems that it does not work on the download path i set in the container volumes. – AutoBeginner Nov 15 '21 at 13:17
0
  1. Statement : 'Let me explain a little. First I create a folder named "target" in the project root repository.' I assume that you are creating this by executing folder = new File("target"); This actually will not create the directory, what you need to apply something like the following:
String directory = "/target";
directory.mkdir()

However, when this part of your code runs, this will not actually create the directory due to your docker-compose.yml where the volume mapping configuration from your chrome-node is mapped to the host /target directory.

volumes:
  ./target:/home/seluser/Downloads

This will already exist, as docker will create it, so the java code will return a boolean value of 'false' (as it does not need to create the directory) and will proceed to the next step.

  1. docker-compose.yml: there is a mapping for your 'selenium/node-chrome' image where you have mapped /target to /home/seluser/Downloads. As this directory does not initially exist when 'docker-compose up' is run, docker will create the directory on the host, so it can be mapped to your desired volume. Here lies 2 problems, first of all docker runs as root and will create the new '/target' directory on the host, but only gives it linux permissions 'drwxr-xr-x' (755). This means that only root user can write to that directory. So even though the configuration has mapped the volume, when you download to the directory as 'seluser', it will not be able to write and the browser will be returned with a 'Permission denied' response.

  2. The other issue is the java code declares to the RemoteWebDriver that the download will need to be saved to prefs.put("download.default_directory", folder.getAbsolutePath());, which has been declared as "target", this is an issue as the chrome-node where the browser resides does not have a directory called '/target', so will fail to download anyway. This is the cause of your 'assertion exception'

Propose that the following should be done for efficiency and stability:

  • Update the gitlab-ci.yml prior to the 'docker-compose down & docker-compose up --build --force-recreate --no-deps -d' commands
mkdir /target
chmod -R 777 /target

This will ensure that the directory can be written to by 'seluser' on the node-chrome container

  • Update the docker-compose.yml

For Windows Host => GitLab CI Docker(WSL2) => Chrome-Node Docker

volumes:
  ./target:/home/seluser/Downloads

For Linux Host => GitLab CI Docker(Linux) => Chrome-Node Docker

volumes:
  /target:/home/seluser/Downloads

This will ensure that the Chrome browser can find a directory named '/target' and that the java test can see a file written to the host '/target' directory

Also your selenium-hub configuration needs to be updated, ports for publish events and subscribe events have not been mapped, update to the following:

  selenium-hub:
    image: selenium/hub:4.0.0-20211013
    container_name: selenium-hub
    ports:
      - "4442:4442"
      - "4443:4443"
      - "4444:4444"
djmonki
  • 3,020
  • 7
  • 18
  • Hi, Thanks a lot your suggestions. folder = new File("target"); is used to define the folder, because i need to do the assertion later with folder.listFiles(). I manually create a folder before, but I understand the docker-compose file can create the 'target' folder, I did not manually create this folder any more. – AutoBeginner Nov 16 '21 at 15:18
  • I have tried you suggestion. with adding mkdir /target and chmod -R 777 /target in yml file. and also changed the volumes to ./target:/target in docker-compose file. But the result is the same. AssertionError appears. – AutoBeginner Nov 16 '21 at 15:19
  • OK - lets carry out some troubleshooting. Can you access the chrome-node container, so that you are running as seluser, the cd to the /target dir, then add a file ```touch test.txt```. See if you have the correct permissions to start of with, hopefully that will be successful, then you can check that you can see the file on the host /target dir – djmonki Nov 16 '21 at 15:27
  • There is something strange. when i run the new docker-compose file locally, the volume is set successfully. i can see the container path is /target inside the volume bindings. But the test fails. no files found in the 'target' folder in the project repository and in the /target in the container. Instead, I found the file in home/seluser/Downloads. I have down the old contains before I run docker-compose up. I have not used this path in the test scipt. – AutoBeginner Nov 16 '21 at 15:52
  • The gitlab runner running on another computer, how can i access the chrome container when the test running in gitlab-ci. Can I run touch /target test.txt in the gitlab-ci.yml? But how can i know if I add it or not? – AutoBeginner Nov 16 '21 at 15:56
  • It's true. it just works when i used './target:/home/seluser/Downloads'. It seems that the home/seluser/Download is the default download path , and the 'prefs.put("download.default_directory", folder.getAbsolutePath());' doesn't work on dockers. – AutoBeginner Nov 16 '21 at 16:04
  • I'm looking into your java code – djmonki Nov 16 '21 at 16:06
  • Let's replace `folder = new File("target");` with ```String directory = "/target"; File folder = new File(directory);``` – djmonki Nov 16 '21 at 16:47
  • I have used String directory = "/target"; File folder = new File(directory); with volumes: - ./target:/target, still did not work, the file is downloaded in home/seluser/Downloads. Then I used it with volumes: - ./target:/home/seluser/Downloads, the file is downloaded in the 'target' folder now, but assertion is fail because of NullPointerException. so '/target' is not really referred to folder 'target'. Instead of '/target', i used './taget', then it works. – AutoBeginner Nov 17 '21 at 07:32
  • when i set 'volumes: - ./target:/home/seluser/Downloads', then i can see the volume bindings in the chrome container shows 'Host path: C:\Users\MyName\IdeaProjects\selenium-project\target; Container path: /home/seluser/Downloads. But if I set 'volumes: - /target:/home/seluser/Downloads' , then Host path became: /target. And the test fails, and no files downloaded in home/seluser/Downloads. I am quite doubt about this volume mapping. Because the second way looks correct, and it should mapping the host and the container without connecting to my computer. But it does not work in my case. – AutoBeginner Nov 17 '21 at 07:43
  • I was unaware that the host was a windows machine, the assumption was that it was linux. So the ./target now makes sense as you are mounting the Win OS \target folder to the gitlab docker, so then the chrome-node docker volume should be mapped as ./target:/home/seluser/Downloads. So the question is, are you still getting the NullPointerException with this setup. – djmonki Nov 17 '21 at 11:53
  • I am just aware that it does mater with different OS system. I am using Windows OS, but the computer which running the gitlab runner is using Linux. So does it mean i need to run ./target:/home/seluser/Downloads when i run locally? But run /target:/home/seluser/Downloads when i push it to Gitlab? So i use 'volumes: - ./target:/home/seluser/Downloads' in the docker-compose and 'String directory = "./target";' in the test script. But 'volumes: - /target:/home/seluser/Downloads' in the docker-compose, and 'String directory = "/target";' when i push it to gitlab? I will try tomorrow. Thanks :) – AutoBeginner Nov 17 '21 at 16:52
  • Yep, that's the understanding. Let's see how it goes. – djmonki Nov 17 '21 at 16:53
  • Hi, I tried to push it to gitlab just now. still failed. The error is 'java.lang.AssertionError at LoginTest_chrome_ci.java:71' The error is different from what i got when running this setting locally. I got 'NullPointerException' when running locally because the folder is not exited. – AutoBeginner Nov 18 '21 at 08:50
  • Raise a separate question for that issue to this one, but also reference this question, so the setup here is understood. On that new question provide the full 'LoginTest_chrome_ci.java' and as much of the stack trace as possible – djmonki Nov 18 '21 at 10:36
  • I now have created another issue. https://stackoverflow.com/questions/70019568/how-to-do-volume-mapping-mounting-volumes-in-dockdind-in-gitlab-ci And by the way, I have found a interesting post maybe related to my problem. https://gitlab.com/gitlab-org/gitlab-foss/-/issues/41227 Do you think i need to have this shared path between containers? – AutoBeginner Nov 18 '21 at 12:11
  • You may well need the shared path – djmonki Nov 18 '21 at 15:04