Skip to content

Commit

Permalink
The method send commands to the browser to avoid blocking
Browse files Browse the repository at this point in the history
of downloading in headless mode.
(The issue is described here:
SeleniumHQ#5159)
  • Loading branch information
IlyaNaumovich committed Jan 12, 2018
1 parent aa2afab commit 6785a3f
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 54 deletions.
64 changes: 10 additions & 54 deletions java/client/src/org/openqa/selenium/chrome/ChromeDriver.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,6 @@

import com.google.common.collect.ImmutableMap;

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;
Expand All @@ -36,22 +29,15 @@
import org.openqa.selenium.html5.WebStorage;
import org.openqa.selenium.interactions.HasTouchScreen;
import org.openqa.selenium.interactions.TouchScreen;
import org.openqa.selenium.json.Json;
import org.openqa.selenium.mobile.NetworkConnection;
import org.openqa.selenium.remote.DriverCommand;
import org.openqa.selenium.remote.FileDetector;
import org.openqa.selenium.remote.HttpCommandExecutor;
import org.openqa.selenium.remote.RemoteTouchScreen;
import org.openqa.selenium.remote.RemoteWebDriver;
import org.openqa.selenium.remote.html5.RemoteLocationContext;
import org.openqa.selenium.remote.html5.RemoteWebStorage;
import org.openqa.selenium.remote.mobile.RemoteNetworkConnection;

import java.lang.reflect.Field;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

/**
* A {@link WebDriver} implementation that controls a Chrome browser running on the local machine.
* This class is provided as a convenience for easily testing the Chrome browser. The control server
Expand Down Expand Up @@ -194,6 +180,10 @@ public ChromeDriver(ChromeDriverService service, ChromeOptions options) {
@Deprecated
public ChromeDriver(ChromeDriverService service, Capabilities capabilities) {
super(new ChromeDriverCommandExecutor(service), capabilities);
if (capabilities.getCapability("enableDownloading")!=null){
sendCommandForDownloadChromeHeadLess(
(String) capabilities.getCapability("enableDownloading"));
}
locationContext = new RemoteLocationContext(getExecuteMethod());
webStorage = new RemoteWebStorage(getExecuteMethod());
touchScreen = new RemoteTouchScreen(getExecuteMethod());
Expand Down Expand Up @@ -257,45 +247,11 @@ public void launchApp(String id) {
* @param downloadPath
*/
public void sendCommandForDownloadChromeHeadLess(String downloadPath) {
Json json = new Json();
Map<String, Object> paramsMap = new HashMap<>();
paramsMap.put("cmd", "Page.setDownloadBehavior");
Map<String, String> cmdParamsMap = new HashMap<>();
cmdParamsMap.put("behavior", "allow");
cmdParamsMap.put("downloadPath", downloadPath);
paramsMap.put("params", cmdParamsMap);
String content = json.toJson(paramsMap);
URL remoteServerUri = null;
try {
Field field = HttpCommandExecutor.class.getDeclaredField("remoteServer");
field.setAccessible(true);
remoteServerUri = (URL) field.get(getCommandExecutor());
} catch (Exception e) {
return;
}
CloseableHttpClient httpclient = null;
try {
httpclient = HttpClients.createDefault();
URIBuilder builder = null;
try {
builder = new URIBuilder(remoteServerUri.toURI());
} catch (URISyntaxException e) {
}
builder.setPath("session/" + getSessionId().toString() + "/chromium/send_command");
HttpPost sendCommandPost = new HttpPost(builder.build());
sendCommandPost.setHeader("Content-Type", ContentType.APPLICATION_JSON.getMimeType());
StringEntity entity = new StringEntity(content, ContentType.APPLICATION_JSON);
sendCommandPost.setEntity(entity);
CloseableHttpResponse response = httpclient.execute(sendCommandPost);
} catch (Exception e) {
} finally {
if (httpclient != null) {
try {
httpclient.close();
} catch (Exception e) {
}
}
if (downloadPath.length()>0){
execute(DriverCommand.SEND_COMMAND_TO_BROWSER,
ImmutableMap.of("cmd", "Page.setDownloadBehavior",
"params",
ImmutableMap.of("behavior", "allow","downloadPath", downloadPath)));
}
}

}
10 changes: 10 additions & 0 deletions java/client/src/org/openqa/selenium/chrome/ChromeOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.openqa.selenium.remote.CapabilityType.ACCEPT_INSECURE_CERTS;
import static org.openqa.selenium.remote.CapabilityType.ENABLE_DOWNLOADING;
import static org.openqa.selenium.remote.CapabilityType.PAGE_LOAD_STRATEGY;
import static org.openqa.selenium.remote.CapabilityType.UNEXPECTED_ALERT_BEHAVIOUR;
import static org.openqa.selenium.remote.CapabilityType.UNHANDLED_PROMPT_BEHAVIOUR;
Expand Down Expand Up @@ -235,6 +236,15 @@ public ChromeOptions setAcceptInsecureCerts(boolean acceptInsecureCerts) {
return this;
}

public ChromeOptions setEnableDownloading(boolean enableDownloading, String downloadPath){
if (enableDownloading){
setCapability(ENABLE_DOWNLOADING, downloadPath);
}else {
setCapability(ENABLE_DOWNLOADING, "");
}
return this;
}

public ChromeOptions setHeadless(boolean headless) {
args.remove("--headless");
if (headless) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public interface CapabilityType {
String ELEMENT_SCROLL_BEHAVIOR = "elementScrollBehavior";
String HAS_TOUCHSCREEN = "hasTouchScreen";
String OVERLAPPING_CHECK_DISABLED = "overlappingCheckDisabled";
String ENABLE_DOWNLOADING = "enableDownloading";

String LOGGING_PREFS = "loggingPrefs";

Expand Down
2 changes: 2 additions & 0 deletions java/client/src/org/openqa/selenium/remote/DriverCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ public interface DriverCommand {
String SET_SCREEN_ROTATION = "setScreenRotation";
String GET_SCREEN_ROTATION = "getScreenRotation";

String SEND_COMMAND_TO_BROWSER = "sendCommandToBrowser";

// W3C Actions APIs
String ACTIONS = "actions";
String CLEAR_ACTIONS_STATE = "clearActionState";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import static org.openqa.selenium.remote.DriverCommand.MOVE_TO;
import static org.openqa.selenium.remote.DriverCommand.REMOVE_LOCAL_STORAGE_ITEM;
import static org.openqa.selenium.remote.DriverCommand.REMOVE_SESSION_STORAGE_ITEM;
import static org.openqa.selenium.remote.DriverCommand.SEND_COMMAND_TO_BROWSER;
import static org.openqa.selenium.remote.DriverCommand.SEND_KEYS_TO_ACTIVE_ELEMENT;
import static org.openqa.selenium.remote.DriverCommand.SET_ALERT_VALUE;
import static org.openqa.selenium.remote.DriverCommand.SET_CURRENT_WINDOW_POSITION;
Expand Down Expand Up @@ -133,6 +134,8 @@ public JsonHttpCommandCodec() {
defineCommand(TOUCH_MOVE, post("/session/:sessionId/touch/move"));
defineCommand(TOUCH_SCROLL, post("/session/:sessionId/touch/scroll"));
defineCommand(TOUCH_UP, post("/session/:sessionId/touch/up"));

defineCommand(SEND_COMMAND_TO_BROWSER, post("/session/:sessionId/chromium/send_command_and_get_result"));
}

@Override
Expand Down
200 changes: 200 additions & 0 deletions java/client/test/org/openqa/selenium/chrome/ChromeHeadlessTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.chrome;

import static org.junit.Assert.assertTrue;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.environment.webserver.Page;
import org.openqa.selenium.io.TemporaryFilesystem;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import org.openqa.selenium.testing.JUnit4TestBase;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Random;

public class ChromeHeadlessTest extends JUnit4TestBase {

private File forDownloading;
private File downloaded;
private File fileForDownloading;
private TemporaryFilesystem tmpFs;

@Before
public void setUp() throws Exception {
File baseForTest = new File(System.getProperty("java.io.tmpdir"), "tmpTest");
baseForTest.mkdir();
tmpFs = TemporaryFilesystem.getTmpFsBasedOn(baseForTest);

forDownloading = tmpFs.createTempDir("forDownloading", "headless");
downloaded = tmpFs.createTempDir("downloaded", "headless");

fileForDownloading = new File(forDownloading, "fileForDownloading.txt");
writeTestFile(fileForDownloading);
}

private void writeTestFile(File file) throws IOException {
File parent = file.getParentFile();
if (!parent.exists()) {
assertTrue(parent.mkdirs());
}
byte[] byteArray = new byte[16];
new Random().nextBytes(byteArray);
try (OutputStream out = new FileOutputStream(file)) {
out.write(byteArray);
}
file.deleteOnExit();
}

@After
public void tearDown() throws Exception {
tmpFs.deleteTemporaryFiles();
if (driver != null) {
driver.quit();
}
}

@Test
public void canStartChromeWithCustomOptions_Headless() {
String downloadFilePathath = downloaded.getAbsolutePath();

HashMap<String, Object> chromePrefs = new HashMap<String, Object>();
chromePrefs.put("profile.default_content_settings.popups", 0);
chromePrefs.put("download.default_directory", downloadFilePathath);
ChromeOptions options = new ChromeOptions();
HashMap<String, Object> chromeOptionsMap = new HashMap<String, Object>();
options.setExperimentalOption("prefs", chromePrefs);
options.addArguments("--test-type");
options.addArguments("--disable-extensions"); //to disable browser extension popup
options.addArguments("--headless");

DesiredCapabilities cap = DesiredCapabilities.chrome();
cap.setCapability(ChromeOptions.CAPABILITY, chromeOptionsMap);
cap.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true);
cap.setCapability(ChromeOptions.CAPABILITY, options);
ChromeDriver driver = new ChromeDriver(cap);

driver.sendCommandForDownloadChromeHeadLess(downloadFilePathath);

driver.get("https://chromedriver.storage.googleapis.com/index.html?path=2.34/");
(new WebDriverWait(driver, 20))
.until(ExpectedConditions
.presenceOfElementLocated(By.xpath("//a[text()='chromedriver_win32.zip']")))
.click();

try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

private String createDownloadingPage() {
String pathToDownloading = fileForDownloading.getAbsolutePath();
return appServer.create(new Page()
.withTitle("Download")
.withBody("<a href=\"" + pathToDownloading + "\" "
+ "download=\"file to download\">Download</a>"));
}

@Test
public void canDownloadInHeadlessMode() {
String downloadFilePath = downloaded.getAbsolutePath();

ChromeOptions options = new ChromeOptions();
options.addArguments("--headless");

DesiredCapabilities cap = DesiredCapabilities.chrome();
cap.setCapability(ChromeOptions.CAPABILITY, options);
ChromeDriver driver = new ChromeDriver(cap);
driver.sendCommandForDownloadChromeHeadLess(downloadFilePath);

driver.get(createDownloadingPage());

driver.findElement(By.tagName("a")).click();

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

assertTrue("Downloaded file should be present",
new File(downloadFilePath + "\\fileForDownloading.txt").exists());
}

@Test
public void canDownloadInHeadlessModeCommand() {
String downloadFilePath = downloaded.getAbsolutePath();

ChromeOptions options = new ChromeOptions();
options.setHeadless(true);

ChromeDriver driver = new ChromeDriver(options);
driver.sendCommandForDownloadChromeHeadLess(downloadFilePath);

driver.get(createDownloadingPage());

driver.findElement(By.tagName("a")).click();

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

assertTrue("Downloaded file should be present",
new File(downloadFilePath + "\\fileForDownloading.txt").exists());
}

@Test
public void canDownloadInHeadlessModeOption() {
String downloadFilePath = downloaded.getAbsolutePath();

ChromeOptions options = new ChromeOptions();
options.setHeadless(true);
options.setEnableDownloading(true, downloadFilePath);

ChromeDriver driver = new ChromeDriver(options);

driver.get(createDownloadingPage());

driver.findElement(By.tagName("a")).click();

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}

assertTrue("Downloaded file should be present",
new File(downloadFilePath + "\\fileForDownloading.txt").exists());
}


}

0 comments on commit 6785a3f

Please sign in to comment.