Selenium 4 Grid Download File

Note: starting with selenium version 4.8.0 there is now an alternative way to download files from grid nodes.

Selenium 4 grid download file example code is shown below. Validate downloaded files to the grid node by using "chrome://downloads" to either just check that they did download or do further validation locally by downloading the file to your local machine.

The main advantage of using "chrome://downloads" is that it only displays the files downloaded in the session and ignores previously downloaded files to the grid node. Make sure you check for downloaded files in the same selenium session, maybe in another tab or window, as the next test is routed to next available grid node and will not necessary run on the grid node you downloaded the files to.

NOTE: if you do download a file to local, typically keep the file size below 50 mb as base64 encoding do enlarge the file on the node when read and are subject to resource limitations. Do let us know if you have a test scenario requiring downloading files larger than 50mb.

Java Example

This java example code is also available as a maven project on Github.

package download;

import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.openqa.selenium.Platform;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeOptions;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.http.ClientConfig;

import junit.framework.TestCase;

public class selenium_grid_download_files extends TestCase {

	 private WebDriver driver;
	 
	 // NOTE: find these credentials in your Gridlastic dashboard after launching your selenium grid (get a free account).
	 // This is a Selenium 4 example
	 String video_url = System.getenv("VIDEO_URL");
	 String hub = System.getenv("HUB"); // like "https://USERNAME:ACCESS_KEY@HUB_SUBDOMAIN.gridlastic.com/wd/hub";

	 
	 public void setUp() throws Exception {
	     
	 	ChromeOptions options = new ChromeOptions();
	 	options.setCapability("browserVersion", "latest");
	 	options.setCapability("platformName", Platform.WIN10);
	 	//options.setCapability("platformName", Platform.LINUX);
		Map gridlasticOptions = new HashMap<>();
		gridlasticOptions.put("video", true);
		options.setCapability("gridlastic:options", gridlasticOptions);
		
		HashMap chromePrefs = new HashMap();
		chromePrefs.put("download.prompt_for_download", Boolean.valueOf(false));
		chromePrefs.put("plugins.always_open_pdf_externally", Boolean.valueOf(true));
		chromePrefs.put("safebrowsing_for_trusted_sources_enabled", Boolean.valueOf(false));
		options.setExperimentalOption("prefs", chromePrefs);
		
		ClientConfig config = ClientConfig.defaultConfig().readTimeout(Duration.ofMinutes(10));
		driver = RemoteWebDriver.builder().address(new URL(hub)).oneOf(options).config(config).build();
	 }
	 
	 
		
		

	 public void test_download_file() throws Exception {
			
			File download_url = new File((String) "https://static.mozilla.com/foundation/documents/mf-articles-of-incorporation.pdf");		
			driver.get(download_url.toString());
			
			  int count = 1;
		        do {
		        	if (get_downloaded_files((RemoteWebDriver) driver).toString().contains((download_url.getName().substring(0,download_url.getName().indexOf(".")-1)))){ //Note: multiple file downloads on the same grid node of the same file name will increment the file name like 50MB(2).zip 
		        		System.out.println("FILE DOWNLOADED TO GRID NODE");
		    			break;
		    		} else {
		    			System.out.println("DOWNLOAD PROGRESS: " + get_download_progress_all((RemoteWebDriver) driver)+"%");	
		    		}
		             count++;
		             Thread.sleep(5000);
		        } while (count < 11);
		        
		        
		        ArrayList downloaded_files_arraylist = get_downloaded_files((RemoteWebDriver) driver);
		    	String content = get_file_content((RemoteWebDriver) driver,(String) downloaded_files_arraylist.get(0));// large files might need and increase in implicit wait.
		    	 try {	
		    		 	String home = System.getProperty("user.home");
		    	    	FileOutputStream fos = new FileOutputStream(home+"/downloads/gridnodes/" + download_url .getName());		    	    	
		    	        byte[] decoder = Base64.decodeBase64(content.substring(content.indexOf("base64,")+7));
		    	        fos.write(decoder);
		    	        System.out.println("File saved to local.");
		    	      } catch (Exception e) {
		    	         e.printStackTrace();
		    	      }
	}

	 public void tearDown() throws Exception {
		 System.out.println("GRIDLASTIC VIDEO URL: " + video_url + ((RemoteWebDriver) driver).getSessionId());
	     driver.quit();
	 }
	 
	 
	 private static String get_file_content(RemoteWebDriver remoteDriver,String path) {
			String file_content = null;
				try {
					if(!remoteDriver.getCurrentUrl().startsWith("chrome://downloads")) {
					remoteDriver.get("chrome://downloads/");
					}


				    WebElement elem = (WebElement) remoteDriver.executeScript(
						    "var input = window.document.createElement('INPUT'); "+
						    "input.setAttribute('type', 'file'); "+
						    "input.hidden = true; "+
						    "input.onchange = function (e) { e.stopPropagation() }; "+
						    "return window.document.documentElement.appendChild(input); "
						    ,"" );
					
					 elem.sendKeys(path);
				
				 file_content = (String) remoteDriver.executeAsyncScript(
							    "var input = arguments[0], callback = arguments[1]; "+
							    "var reader = new FileReader(); "+
							    "reader.onload = function (ev) { callback(reader.result) }; "+
							    "reader.onerror = function (ex) { callback(ex.message) }; "+
							    "reader.readAsDataURL(input.files[0]); "+
							    "input.remove(); "
							    , elem);
					
					if (!file_content.startsWith("data:")){
						System.out.println("Failed to get file content");
					}
				
				} catch (Exception e) {
					System.err.println(e);
				}
return file_content;

			}
	
		 
		 
		 private static ArrayList get_downloaded_files(RemoteWebDriver remoteDriver) {
			 ArrayList filesFound = null;
				try {
					if(!remoteDriver.getCurrentUrl().startsWith("chrome://downloads")) {
					remoteDriver.get("chrome://downloads/");
					}
					filesFound =  (ArrayList)  remoteDriver.executeScript(
					  "return  document.querySelector('downloads-manager')  "+
				      " .shadowRoot.querySelector('#downloadsList')         "+
				      " .items.filter(e => e.state === 'COMPLETE')          "+
				      " .map(e => e.filePath || e.file_path || e.fileUrl || e.file_url); ","");				
				} catch (Exception e) {
					System.err.println(e);
				}
				return filesFound;
			}
		 
		 private static String get_download_progress(RemoteWebDriver remoteDriver) {
			 String progress = null;
				try {
					if(!remoteDriver.getCurrentUrl().startsWith("chrome://downloads")) {
					remoteDriver.get("chrome://downloads/");
					}
					progress=  (String) remoteDriver.executeScript(						
							"var tag = document.querySelector('downloads-manager').shadowRoot;"+
						    "var intag = tag.querySelector('downloads-item').shadowRoot;"+
						    "var progress_tag = intag.getElementById('progress');"+
						    "var progress = null;"+
						   " if(progress_tag) { "+
						    "    progress = progress_tag.value; "+
						  "  }" +
						    "return progress;"
							,"");
					
		
				} catch (Exception e) {
					System.err.println(e);
				}
				return progress;
			}

		 
		 
		 
		 private static ArrayList get_download_progress_all(RemoteWebDriver remoteDriver) {
			 ArrayList progress = null;
				try {
					if(!remoteDriver.getCurrentUrl().startsWith("chrome://downloads")) {
					remoteDriver.get("chrome://downloads/");
					}
					progress=  (ArrayList) remoteDriver.executeScript(						
							" var tag = document.querySelector('downloads-manager').shadowRoot;" + 
							"			    var item_tags = tag.querySelectorAll('downloads-item');" + 
							"			    var item_tags_length = item_tags.length;" + 
							"			    var progress_lst = [];" + 
							"			    for(var i=0; i<item_tags_length; i++) {" + 
							"			        var intag = item_tags[i].shadowRoot;" + 
							"			        var progress_tag = intag.getElementById('progress');" + 
							"			        var progress = null;" + 
							"			        if(progress_tag) {" + 
							"			            var progress = progress_tag.value;" + 
							"			        }" + 
							"			        progress_lst.push(progress);" + 
							"			    }" + 
							"			    return progress_lst",
							"");
					
		
				} catch (Exception e) {
					System.err.println(e);
				}
				return progress;
			}	
		
	 
	}
NOTE: Gridlastic auto scaling requires all 3 test environment parameters platform, browser and browser version to be specified in each request in order to launch test nodes to fulfill test demand. Video recording is optional. See test environments for capabilities options.
It is important to ensure that "driver.quit()" is always called for proper test execution and creation of video recordings of failed tests.