Multi-client remote debugging using ChromeDriver and Chrome DevTools protocol - google-chrome

So with Chrome 63 there is now support for Multi-client remote debugging (https://developers.google.com/web/updates/2017/10/devtools-release-notes)
What I want to achieve is use the Chrome DevTools Protocol HeapProfiler with some selenium tests. I'm running version 64 Chrome dev channel and ChromeDriver 2.33.
ChromeOptions options = newChromeOptions();
options.addArguments("--remote-debugging-port=9222");
WebDriver driver = new ChromeDriver(options);
... selenium stuff
A new chrome window will open and hang until it times out. I can confirm that the chrome window opened is chrome 64 by going to help > about google chrome to check the version.
I get this error which appears to be the the webdriver losing connection.
Exception in thread "main" org.openqa.selenium.WebDriverException: chrome not
reachable
The DevTools Protocol is working because I am able to open http://localhost:9222 in another chrome window and see debugging interface.
Has anyone been able to get these two things to work together?
Thanks :)

Here the catch was that if you pass the "remote-debugging-port" switch then chromedriver has a bug where it still internally assigns a randon port and keep trying to connect to it rather than connecting to 9222 port.
options.addArguments("--remote-debugging-port=9222");
We can solve this by skipping this command switch and let chrome decides this random port and extract this port number from chromedriver logs.
I made it work and here I have blogged it in detail.
https://medium.com/#sahajamit/selenium-chrome-dev-tools-makes-a-perfect-browser-automation-recipe-c35c7f6a2360

Selenium 4 release will have a user friendly API for Chrome DevTools protocol. I just finished implementing Network and Performance domains for the Selenium Java client. https://github.com/SeleniumHQ/selenium/pull/7212
In addition, there is a generic API for all domains in Java client that was merged a while ago. All those new features will be released probably in the next Alpha release.
This is a nice article on how to use Log:
https://codoid.com/selenium-4-chrome-devtools-log-entry-listeners/

Here is what i do to get the information needed fro remotedebugging nd additionally to prevent defining the port. I get it through the SeleniumLog-API
DesiredCapabilities capabilities = DesiredCapabilities.chrome();
ChromeOptions options = new ChromeOptions();
options.setBinary(chromeBin);
capabilities.setCapability(ChromeOptions.CAPABILITY, options);
LoggingPreferences logPref = new LoggingPreferences();
logPref.enable(LogType.DRIVER, Level.ALL);
driverInstance = new ChromeDriver(capabilities);
LogEntries x = driverInstance.manage().logs().get(LogType.DRIVER);
for(LogEntry e:x.getAll()){
if(e.getMessage().contains("DevTools request:")){
String url = e.getMessage().replaceFirst("DevTools request:", "").trim();
}
if(e.getMessage().contains("DevTools response:")){
String json = e.getMessage().replaceFirst("DevTools response:", "");
try {
if("page".equals(JSONUtil.get(json,"type" ))){
webSocketDebuggerUrl = JSONUtil.get(json,"webSocketDebuggerUrl" );
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
System.out.println(e.getMessage());
}
The JSONUtil i use is my own tool, so don't wonder, just replace with whatever code to extract from the jsontext.

Here's a fairly robust implementation in java using the same target tab with selenium 3.13 & cdp4j 3.0.2-SNAPSHOT. Easily translates to any language.
package com.company;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import io.webfolder.cdp.session.SessionFactory;
import org.openqa.selenium.HasCapabilities;
import org.openqa.selenium.Keys;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.chrome.ChromeDriverService;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
public class Main {
public static void main(String[] args) {
System.setProperty(ChromeDriverService.CHROME_DRIVER_EXE_PROPERTY, "C:\\path\\to\\chromedriver.exe");
var driver = new ChromeDriver();
try {
var cdp = findCdpEndpoint(driver);
System.out.println(cdp.toString());
try (var factory = new SessionFactory(cdp.getPort())) {
driver.navigate().to("https://google.com");
String seTargetId = getSeTargetId(cdp, driver.getTitle());
try (var session = factory.connect(seTargetId)) {
session.waitDocumentReady();
session.sendKeys("Astronauts");
driver.getKeyboard().sendKeys(Keys.RETURN);
session.wait(2000);
driver.navigate().to("http://www.google.com");
session.waitDocumentReady();
}
}
} catch (Exception ex) {
System.out.println(ex.toString());
}
driver.quit();
}
private static String getSeTargetId(URL cdp, String title) throws IOException {
for (JsonElement element : new JsonParser().parse(new InputStreamReader(cdp.openStream(), "UTF-8")).getAsJsonArray()) {
var object = element.getAsJsonObject();
if (title == null || title.isEmpty()
? object.get("type").getAsString().equalsIgnoreCase("page")
: object.get("title").getAsString().equalsIgnoreCase(title)) {
return object.get("id").getAsString();
}
}
throw new IllegalStateException("Selenium target not found.");
}
private static URL findCdpEndpoint(WebDriver driver) throws IOException {
var capChrome = (Map<?,?>) ((HasCapabilities)driver).getCapabilities().getCapability("chrome");
var userDataDir = (String) capChrome.get("userDataDir");
var port = Integer.parseInt(Files.readAllLines(Paths.get(userDataDir, "DevToolsActivePort")).get(0));
return new URL("http", "localhost", port, "/json");
}
}

Related

How to download a file with Selenium and ChromeDriver

I have a website to test with selenium and ChromeDriver (on windows), where I like to test the functionality to export data and import it again.
The export creates a xml file that is downloaded on ones computer. When running this with webdriver, Chrome asks me whether to keep the file or discard it, as it might be a potential threat.
How can I switch off this behavior inside my test ? Is there a chrome setting I can use, so that a file is no matter what downloaded ?
Thanks
Try this. Executed on windows
(How to control the download of files with Selenium Python bindings in Chrome)
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_experimental_option("prefs", {
"download.default_directory": r"C:\Users\xxx\downloads\Test",
"download.prompt_for_download": False,
"download.directory_upgrade": True,
"safebrowsing.enabled": True
})
The below Program will help you to download the files in Chrome with
Desired-Capabilities. It is a rich class having lot of utilities, you can go through it in your free time.
public class DownloadChromeFile {
public static void main(String[] args) {
System.setProperty("webdriver.chrome.driver","./chromedriver.exe");
String downloadFilepath = "c:\\download";
HashMap<String, Object> chromePrefs = new HashMap<String, Object>();
chromePrefs.put("profile.default_content_settings.popups", 0);
chromePrefs.put("download.default_directory", downloadFilepath);
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
DesiredCapabilities cap = DesiredCapabilities.chrome();
cap.setCapability(ChromeOptions.CAPABILITY, chromeOptionsMap);
cap.setCapability(CapabilityType.ACCEPT_SSL_CERTS, true); // Bydefault it will accepts all popups.
cap.setCapability(ChromeOptions.CAPABILITY, options);
driver = new ChromeDriver(cap);
driver.get("Your Application Url");
driver.findElement(By.xpath("Export Button xpath")).click();
}
}

Setting device name in serenity.properties file for chrome driver

How do I set the mobile emulation for Nexus 5 view in Serenity managed chrome driver?
I tried going through this link:
https://johnfergusonsmart.com/configuring-chromedriver-easily-with-serenity-bdd/
Which explain setting preferences for chrome.
Chrome preferences
You can also provide more advanced options using the setExperimentalOption() method:
Map<String, Object> chromePrefs = new HashMap<String, Object>();
chromePrefs.put("download.default_directory", downLoadDirectory);
chromePrefs.put("profile.default_content_settings.popups", 0);
chromePrefs.put("pdfjs.disabled", true);
ChromeOptions options = new ChromeOptions();
options.setExperimentalOption("prefs", chromePrefs);
In Serenity, you would pass these using properties prefixed with the chrome_preferences prefix, e.g.
chrome_preferences.download.default_directory = /my/download/directory
chrome_preferences.profile_default_content_settings.popups = 0
chrome_preferences.pdfjs.disabled=true
From this, I tried setting the mobileEmulation as
chrome.capabilities.mobile_emulation.device_name= Google Nexus 5
chrome.options.mobileEmulation.deviceName= Google Nexus 5
and a few other logical variants, but none of them succeeded.
The best way I found to help me in this issue it to create a custom WebDriver.
I had to create a class which extends DriverSource. And then link it to the Serenity Properties file. This will give me the driver I need.
http://www.thucydides.info/docs/serenity/#_custom_webdriver_implementations
You can add your own custom WebDriver provider by implementing the
DriverSource interface. First, you need to set up the following system
properties (e.g. in your serenity.properties file):
webdriver.driver = provided
webdriver.provided.type = mydriver
webdriver.provided.mydriver = com.acme.MyPhantomJSDriver
thucydides.driver.capabilities = mydriver
Your custom driver must implement the DriverSource interface, as shown
here:
public class MyPhantomJSDriver implements DriverSource {
#Override
public WebDriver newDriver() {
try {
DesiredCapabilities capabilities = DesiredCapabilities.phantomjs();
// Add
return new PhantomJSDriver(ResolvingPhantomJSDriverService.createDefaultService(),
capabilities);
}
catch (IOException e) {
throw new Error(e);
}
}
#Override
public boolean takesScreenshots() {
return true;
}
}
This driver will now take screenshots normally.

Cannot retrieve document from couchbase lite when adding documents to couchbase server using admin UI

I am trying to add documents to couchbase server(admin UI) and then trying to retrieve it using couchbase Lite via sync gateway but unable to do so. What I am trying to achieve is I already have a lot of data in couchbase server now I want my mobile app to use it and because that data was not added using sync gateway I want to achieve something like I added data using web now I want my couchbase lite to connect to that couchbase server and retrieve data. Is there any way to do it? or only data that has been added using sync gateway can be retrieved?
EDIT 1 Added Source Codes
Below is the android app code
package com.couchbase.examples.couchbaseevents;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import com.couchbase.lite.CouchbaseLiteException;
import com.couchbase.lite.Database;
import com.couchbase.lite.Document;
import com.couchbase.lite.Manager;
import com.couchbase.lite.android.AndroidContext;
import com.couchbase.lite.replicator.Replication;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
public static final String DB_NAME = "couchbaseevents";
final String TAG = "CouchbaseEvents";
Database database = null;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.d(TAG, "Begin Couchbase Events App");
Manager manager = null;
try {
manager = new Manager(new AndroidContext(this), Manager.DEFAULT_OPTIONS);
database = manager.getDatabase(DB_NAME);
} catch (Exception e) {
Log.d(TAG, "Error getting database", e);
return;
}
/*try {
database.delete();
} catch (Exception e) {
Log.e(TAG, "Cannot delete database", e);
return;
}*/
try {
startReplications();
} catch (CouchbaseLiteException e) {
e.printStackTrace();
}
Document retrievedDocument = database.getDocument("123");
// display the retrieved document
Log.d(TAG, "retrievedDocument=" + String.valueOf(retrievedDocument.getProperties()));
Log.d(TAG, "End Couchbase Events App");
}
private URL createSyncURL(boolean isEncrypted){
URL syncURL = null;
String host = "http://172.16.25.100";
String port = "4986";
String dbName = "sync_gateway";
try {
//syncURL = new URL("http://127.0.0.1 :4986/sync_gateway");
syncURL = new URL(host + ":" + port + "/" + dbName);
} catch (Exception me) {
me.printStackTrace();
}
Log.d(syncURL.toString(),"URL");
return syncURL;
}
private void startReplications() throws CouchbaseLiteException {
Replication pull = database.createPullReplication(this.createSyncURL(false));
Replication push = database.createPushReplication(this.createSyncURL(false));
pull.setContinuous(true);
push.setContinuous(true);
pull.start();
push.start();
if(!push.isRunning()){
Log.d(TAG, "MyBad");
}
/*if(!push.isRunning()) {
Log.d(TAG, "Replication is not running due to " +push.getLastError().getMessage());
Log.d(TAG, "Replication is not running due to " +push.getLastError().getCause());
Log.d(TAG, "Replication is not running due to " +push.getLastError().getStackTrace());
Log.d(TAG, "Replication is not running due to " +push.getLastError().toString());
}*/
}
}
"123" is the document id of document I created in CouchBase server using admin UI
As you can see I first deleted the database( commented part) to make sure there is no document in database and then ran the above replication code.
Below is the sync gateway config file
{
"log":["CRUD+", "REST+", "Changes+", "Attach+"],
"interface":":4986",
"adminInterface":":14985",
"databases": {
"sync_gateway": {
"server":"http://172.16.25.100:8091",
"bucket":"sync_gateway",
"sync":`
function (doc) {
channel (doc.channels);
}`,
"users": {
"GUEST": {
"disabled": false,
"admin_channels": ["*"]
}
}
}
}
}
I also want to ask is there any UI or command line to access CBL. I am currently using CBL in android studio so I dont know how to access its UI or command line
Just for the information, I am able to push data from CBL to CouchBase server
We can solve the above issue by shadowing. If I want to sync my data from the already made bucket to sync gateway bucket shadowing is required. More about Shadowing here

Cordova InAppBrowser accessing certificate on virtual smartcard

I have an app running on Windows Phone 8.1 which calls a URL via InAppBrowser plugin. This URL is supposed to ask for the user certificate stored on a virtual smartcard on the phone.
When I call the URL via Internet Explorer, I am asked for my PIN to unlock the virtual smartcard but in the InAppBrowser, this doesn't work. No PIN prompt, nothing.
Iterating through the Certificates yielded from
IReadOnlyList<Certificate> certStores = await CertificateStores.FindAllAsync();
I can see the certificate at app runtime but InAppBrowser doesn't seem to query for them. Do I have to copy its reference to another certificate store or is InAppBrowser not capable of establishing SSL with user certificates ?
The issue is with the webview component, x-ms-webview to be more precisely. InAppBrowser plugin uses this component internally.
Found a workaround mentioned here, it kinda sounds like a security issue tbh so this could get fixed in the future but here are more details on said workaround:
Make a request to the URL which is supposed to trigger virtual smartcard unlock to access the user certificate, but with the HttpClient at native level (C#)
I've created another Windows Runtime Component in my solution which does a simple POST to the url I want to access from InAppBrowser later on.
While setting up the Windows.Web.Http.HttpClient, I fetch the user certificate from the smartcard and set it as HttpBaseProtocolFilter.ClientCertificate.
public sealed class SSLHelper
{
private static String errorMessage = "";
private static String statusMessage = "";
public static IAsyncOperation<Boolean> establishSSLConnection(String url)
{
return connect(url).AsAsyncOperation<Boolean>();
}
public static String getErrorMessage()
{
return SSLHelper.errorMessage;
}
public static String getStatusMessage()
{
return SSLHelper.statusMessage;
}
private static async Task<Boolean> connect(String urlString)
{
Certificate clientCert = await getCertificateAsync();
HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
filter.ClientCertificate = clientCert;
HttpClient client = new HttpClient(filter);
try
{
System.Uri url = new System.Uri(urlString);
HttpResponseMessage response = await client.PostAsync(url, new HttpStringContent(""));
response.EnsureSuccessStatusCode();
SSLHelper.statusMessage = response.StatusCode.ToString();
return true;
}
catch (Exception e)
{
SSLHelper.errorMessage = e.ToString();
return false;
}
}
private static async Task<Certificate> getCertificateAsync()
{
CertificateQuery query = new CertificateQuery();
query.IssuerName = "Sample Issuer";
IReadOnlyList<Certificate> certStores = await CertificateStores.FindAllAsync(query);
return certStores.FirstOrDefault<Certificate>();
}
}
Make that code return as a promise on Javascript level and once it resolves, start the code which uses InAppBrowser to access the secure URL again. The native request causes the PIN prompt for virtual smartcard access, once you have entered the correct PIN, InAppBrowser / WebView can magically establish the connection.

Export HAR using chromedriver

Is it possible to export HAR using chromedriver similar to what I can do with netexpert+firebug with Firefox?
Yes, using BrowsermobProxy you can generate HAR file using chromedriver.
Here is a script in python to programatically generate HAR file using Selenium, BrowserMob Proxy and chromedriver. Python Packages for selenium and browsermob-proxy are needed to run this script.
from browsermobproxy import Server
from selenium import webdriver
import os
import json
import urlparse
server = Server("path/to/browsermob-proxy")
server.start()
proxy = server.create_proxy()
chromedriver = "path/to/chromedriver"
os.environ["webdriver.chrome.driver"] = chromedriver
url = urlparse.urlparse (proxy.proxy).path
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument("--proxy-server={0}".format(url))
driver = webdriver.Chrome(chromedriver,chrome_options =chrome_options)
proxy.new_har("http://stackoverflow.com", options={'captureHeaders': True})
driver.get("http://stackoverflow.com")
result = json.dumps(proxy.har, ensure_ascii=False)
print result
proxy.stop()
driver.quit()
You can enable performance log via chromedriver and analyze the network traffic to build HAR on your own.
Please checkout the code at
https://gist.github.com/Ankit3794/01b63199bd7ed4f2539a088463e54615#gistcomment-3126071
Steps:
Initiate ChromeDriver instance with enabling Logging Preference
DesiredCapabilities capabilities = DesiredCapabilities.chrome();
ChromeOptions chromeOptions = new ChromeOptions();
chromeOptions.addArguments("ignore-certificate-errors");
chromeOptions.addArguments("disable-infobars");
chromeOptions.addArguments("start-maximized");
// More Performance Traces like devtools.timeline, enableNetwork and enablePage
Map<String, Object> perfLogPrefs = new HashMap<>();
perfLogPrefs.put("traceCategories", "browser,devtools.timeline,devtools");
perfLogPrefs.put("enableNetwork", true);
perfLogPrefs.put("enablePage", true);
chromeOptions.setExperimentalOption("perfLoggingPrefs", perfLogPrefs);
// For Enabling performance Logs for WebPageTest
LoggingPreferences logPrefs = new LoggingPreferences();
logPrefs.enable(LogType.PERFORMANCE, Level.ALL);
capabilities.setCapability("goog:loggingPrefs", logPrefs);
capabilities.merge(chromeOptions);
Get "message" JSONObject from Performance Logs
private static JSONArray getPerfEntryLogs(WebDriver driver) {
LogEntries logEntries = driver.manage().logs().get(LogType.PERFORMANCE);
JSONArray perfJsonArray = new JSONArray();
logEntries.forEach(entry -> {
JSONObject messageJSON = new JSONObject(entry.getMessage()).getJSONObject("message");
perfJsonArray.put(messageJSON);
});
return perfJsonArray;
}
Get HAR by passing PerfLogs
public static void getHAR(WebDriver driver, String fileName) throws IOException {
String destinationFile = "/HARs/" + fileName + ".har";
((JavascriptExecutor) driver).executeScript(
"!function(e,o){e.src=\"https://cdn.jsdelivr.net/gh/Ankit3794/chrome_har_js#master/chromePerfLogsHAR.js\",e.onload=function(){jQuery.noConflict(),console.log(\"jQuery injected\")},document.head.appendChild(e)}(document.createElement(\"script\"));");
File file = new File(destinationFile);
file.getParentFile().mkdirs();
FileWriter harFile = new FileWriter(file);
harFile.write((String) ((JavascriptExecutor) driver).executeScript(
"return module.getHarFromMessages(arguments[0])", getPerfEntryLogs(driver).toString()));
harFile.close();
}