Monday, 3 September 2018

Handling Authentication required dialog using Winium with C# for Chrome Browser (Best)


// Sending user name on Authentication required dialog

Your WiniumObject.FindElementsByName("Username").SendKeys(Your user name);

System.Windows.Forms.SendKeys.SendWait("{TAB}"); // Not mandatory

// Sending password on Authentication required dialog
Your WiniumObject.FindElementsByName("Password").SendKeys(Your password);

System.Windows.Forms.SendKeys.SendWait("{TAB}"); // Not mandatory

// Click on Sign in button

System.Windows.Forms.SendKeys.SendWait("{ENTER}"); or

 Your WiniumObject.FindElementsByName("Sign in").Click




ReUse Browser session using Selenium with Java


Attempt 1

ChromeDriver driver = new ChromeDriver();
HttpCommandExecutor executor = (HttpCommandExecutor) driver.getCommandExecutor();
URL url = executor.getAddressOfRemoteServer();
SessionId session_id = driver.getSessionId();

RemoteWebDriver driver2 = new RemoteWebDriver(executor, new DesiredCapabilities());

When we run the above code, it errors out with below exception

objc[52278]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/bin/java (0x1040284c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x1040f04e0). One of the two will be used. Which one is undefined.
Starting ChromeDriver 2.27.440174 (e97a722caafc2d3a8b807ee115bfb307f7d2cfd9) on port 29572
Only local connections are allowed.
Jun 01, 2017 12:24:17 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: OSS
Exception in thread "main" org.openqa.selenium.SessionNotCreatedException: Session already exists
Build info: version: '3.4.0', revision: 'unknown', time: 'unknown'
Driver info: driver.version: RemoteWebDriver
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:138)
    at org.openqa.selenium.remote.service.DriverCommandExecutor.execute(DriverCommandExecutor.java:82)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:637)
    at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:250)
    at org.openqa.selenium.remote.RemoteWebDriver.startSession(RemoteWebDriver.java:236)
    at org.openqa.selenium.remote.RemoteWebDriver.<init>(RemoteWebDriver.java:137)
    at TestApp.main(TestApp.java:79)

Attempt 2

Next guess was to override “org.openqa.selenium.remote.HttpCommandExecutor.execute” and override the response for the newSession command

public static RemoteWebDriver createDriverFromSession(final SessionId sessionId, URL command_executor){
    CommandExecutor executor = new HttpCommandExecutor(command_executor) {

        @Override
        public Response execute(Command command) throws IOException {
            Response response = null;
            if (command.getName() == "newSession") {
                response = new Response();
                response.setSessionId(sessionId.toString());
                response.setStatus(0);
                response.setValue(Collections.<String, String>emptyMap());


            } else {
                response = super.execute(command);
            }
            return response;
        }
    };

    return new RemoteWebDriver(executor, new DesiredCapabilities());
}

public static void main(String [] args) {

    ChromeDriver driver = new ChromeDriver();
    HttpCommandExecutor executor = (HttpCommandExecutor) driver.getCommandExecutor();
    URL url = executor.getAddressOfRemoteServer();
    SessionId session_id = driver.getSessionId();


    RemoteWebDriver driver2 = createDriverFromSession(session_id, url);
}

The driver2 objects get created, which was a wow moment. Next thing was to test a simple command on the driver

driver2.get("http://google.com");

 exception

objc[53332]: Class JavaLaunchHelper is implemented in both /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/bin/java (0x10ec5c4c0) and /Library/Java/JavaVirtualMachines/jdk1.8.0_102.jdk/Contents/Home/jre/lib/libinstrument.dylib (0x10ed244e0). One of the two will be used. Which one is undefined.
Starting ChromeDriver 2.27.440174 (e97a722caafc2d3a8b807ee115bfb307f7d2cfd9) on port 5528
Only local connections are allowed.
Jun 01, 2017 12:41:02 AM org.openqa.selenium.remote.ProtocolHandshake createSession
INFO: Detected dialect: OSS
Exception in thread "main" org.openqa.selenium.WebDriverException: No command or response codec has been defined. Unable to proceed
Build info: version: '3.4.0', revision: 'unknown', time: 'unknown'
Driver info: driver.version: RemoteWebDriver
    at org.openqa.selenium.remote.HttpCommandExecutor.execute(HttpCommandExecutor.java:154)
    at TestApp$1.execute(TestApp.java:54)
    at org.openqa.selenium.remote.RemoteWebDriver.execute(RemoteWebDriver.java:637)
    at org.openqa.selenium.remote.RemoteWebDriver.get(RemoteWebDriver.java:364)
    at TestApp.main(TestApp.java:74)

So I digged into Selenium source code for file HttpCommandExecutor.java and found the below lines of code

    if (NEW_SESSION.equals(command.getName())) {
      if (commandCodec != null) {
        throw new SessionNotCreatedException("Session already exists");
      }
      ProtocolHandshake handshake = new ProtocolHandshake();
      log(LogType.PROFILER, new HttpProfilerLogEntry(command.getName(), true));
      ProtocolHandshake.Result result = handshake.createSession(client, command);
      Dialect dialect = result.getDialect();
      commandCodec = dialect.getCommandCodec();
      for (Map.Entry<String, CommandInfo> entry : additionalCommands.entrySet()) {
        defineCommand(entry.getKey(), entry.getValue());
      }
      responseCodec = dialect.getResponseCodec();
      log(LogType.PROFILER, new HttpProfilerLogEntry(command.getName(), false));
      return result.createResponse();
    }

    if (commandCodec == null || responseCodec == null) {
      throw new WebDriverException(
        "No command or response codec has been defined. Unable to proceed");
    }

So we can see that when we override the newSession command, we just return a response. But we don’t set the commandCodec and responseCodec. Which then errors out at the next command we executed.

If we look at the definition of these two objects in the class

  private CommandCodec<HttpRequest> commandCodec;
  private ResponseCodec<HttpResponse> responseCodec;

They both are private field.
Attempt 3:

So in our overriden HttpCommandExecutor, we need to set the commandCodec and responseCodec variables.

I believe these changes were made for supporting both W3C and the old command and response formats. Since I am using the latest version, I will override these to the W3C objects required.

public static RemoteWebDriver createDriverFromSession(final SessionId sessionId, URL command_executor){
    CommandExecutor executor = new HttpCommandExecutor(command_executor) {

    @Override
    public Response execute(Command command) throws IOException {
        Response response = null;
        if (command.getName() == "newSession") {
            response = new Response();
            response.setSessionId(sessionId.toString());
            response.setStatus(0);
            response.setValue(Collections.<String, String>emptyMap());

            try {
                Field commandCodec = null;
                commandCodec = this.getClass().getSuperclass().getDeclaredField("commandCodec");
                commandCodec.setAccessible(true);
                commandCodec.set(this, new W3CHttpCommandCodec());

                Field responseCodec = null;
                responseCodec = this.getClass().getSuperclass().getDeclaredField("responseCodec");
                responseCodec.setAccessible(true);
                responseCodec.set(this, new W3CHttpResponseCodec());
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }

        } else {
            response = super.execute(command);
        }
        return response;
    }
    };

    return new RemoteWebDriver(executor, new DesiredCapabilities());
}

public static void main(String [] args) {

    ChromeDriver driver = new ChromeDriver();
    HttpCommandExecutor executor = (HttpCommandExecutor) driver.getCommandExecutor();
    URL url = executor.getAddressOfRemoteServer();
    SessionId session_id = driver.getSessionId();


    RemoteWebDriver driver2 = createDriverFromSession(session_id, url);
    driver2.get("http://www.google.com");
}

ReUsing existing Browser Session in Selenium with C#



var driver = new ChromeDriver();

Two things that we need is the Session Id and the Executor url to be able to re-create the driver.
Getting the Session Id

Getting the Session Id is quite simple:

              Console.WriteLine(driver.SessionId.ToString());

Getting the Executor URL:

                Getting the Executor URL is not straight forward. So we will see how to get it in multiple steps

If we look at the RemoteWebDriver class source code we will find the executor is stored in a private field

private ICommandExecutor executor;

To get a private field we need to use Reflection concepts in C#

var executorField = driver.GetType().GetField("executor", BindingFlags.NonPublic |BindingFlags.Instance);

The executorField value will come as null. The reason this happens is that we had initiated the driver as a ChromeDriver and the private field is of RemoteWebDriver which means it is not accesible to the ChromeDriver class also. So we need to go to it’s base class to fetch the executor field.

So we update our code as below

var executorField = driver.GetType().GetField("executor", BindingFlags.NonPublic |BindingFlags.Instance);
if (executorField == null )
{
    executorField = driver.GetType().BaseType.GetField("executor", BindingFlags.NonPublic |BindingFlags.Instance);
}

object executor = executorField.GetValue(driver);

Now if we look the executor object value. It is of type OpenQA.Selenium.Remote.DriverServiceCommandExecutor. If we look at the source code of this class

internal class DriverServiceCommandExecutor : ICommandExecutor
{
    private DriverService service;

    private HttpCommandExecutor internalExecutor;

This is a internal class, so we can’t cast this object. Also since the internalExecutor is a private field, we anyways have to use reflection further.

var internalExecutorField = executor.GetType().GetField("internalExecutor", BindingFlags.Instance | BindingFlags.NonPublic);
object internalExecutor = internalExecutorField.GetValue(executor);

The internalExecutor object has a type of OpenQA.Selenium.Remote.HttpCommandExecutor. The source code of the class is as below

internal class HttpCommandExecutor : ICommandExecutor
{
    private const string JsonMimeType = "application/json";

    private const string ContentTypeHeader = "application/json;charset=utf-8";

    private const string RequestAcceptHeader = "application/json, image/png";

    private Uri remoteServerUri;

So there is the field we are interested in remoteServerUri. We can get it the same way we got remoteServerUri.

var remoteServerUriField = internalExecutor.GetType().GetField("remoteServerUri", BindingFlags.Instance | BindingFlags.NonPublic);
var remoteServerUri = remoteServerUriField.GetValue(internalExecutor) as Uri;

The value of remoteServerUri comes out be http://localhost:52600/ in my case. This would change everytime we launch a new driver.

Here is a refactored and more polished version of the code we wrote

public static Uri GetExecutorURLFromDriver(OpenQA.Selenium.Remote.RemoteWebDriver driver)
{
    var executorField = typeof(OpenQA.Selenium.Remote.RemoteWebDriver)
        .GetField("executor",
                  System.Reflection.BindingFlags.NonPublic
                  | System.Reflection.BindingFlags.Instance);
   
    object executor = executorField.GetValue(driver);
   
    var internalExecutorField = executor.GetType()
        .GetField("internalExecutor",
                  System.Reflection.BindingFlags.NonPublic
                  | System.Reflection.BindingFlags.Instance);
    object internalExecutor = internalExecutorField.GetValue(executor);
   
    //executor.CommandInfoRepository
    var remoteServerUriField = internalExecutor.GetType()
        .GetField("remoteServerUri",
                  System.Reflection.BindingFlags.NonPublic
                  | System.Reflection.BindingFlags.Instance);
    var remoteServerUri = remoteServerUriField.GetValue(internalExecutor) as Uri;
   
    return remoteServerUri;
}

So now we have solved the first issue, which is to have the Session Id and Exeuctor URL for us to save before reconstructing the WebDriver next time.
Reconstructing the driver

So we have what we need to re-create the driver, now it is for us to look at what we need from a C# language perspective. We need to create a new class with base class OpenQA.Selenium.Remote.RemoteWebDriver class and override the execute method to change the NewSession command response

public class ReuseRemoteWebDriver: OpenQA.Selenium.Remote.RemoteWebDriver{
    private String _sessionId;
   
    public ReuseRemoteWebDriver(Uri remoteAddress, String sessionId)
        :base( remoteAddress,  new OpenQA.Selenium.Remote.DesiredCapabilities()) {
        this._sessionId = sessionId;
    }
   
    protected override OpenQA.Selenium.Remote.Response
        Execute(string driverCommandToExecute, System.Collections.Generic.Dictionary<string, object> parameters)
    {
        if (driverCommandToExecute == OpenQA.Selenium.Remote.DriverCommand.NewSession)
        {
            var resp =  new OpenQA.Selenium.Remote.Response();
            resp.Status = OpenQA.Selenium.WebDriverResult.Success;
            resp.SessionId = this._sessionId;
            resp.Value = new System.Collections.Generic.Dictionary<String, Object>();
            return resp;
        }
        var respBase = base.Execute(driverCommandToExecute, parameters);
        return respBase;
    }
}

This code creates a the driver successfully. Now let’s test it.

var driverReUse = new ReuseRemoteWebDriver(remoteUri, driverChrome.SessionId.ToString());
driverReUse.Url = "http://tarunlalwani.com";

Console.WriteLine(driverReUse.Url);

The line of code to set Url of the driver doesn’t do anything, but doesn’t error too. On printing the Url a exception is raised

OpenQA.Selenium.WebDriverException: no such session
  (Driver info: chromedriver=2.30.477700 (0057494ad8732195794a7b32078424f92a5fce41),platform=Windows NT 6.1.7601 SP1 x86_64)

   at OpenQA.Selenium.Remote.RemoteWebDriver.UnpackAndThrowOnError(Response errorResponse)
   at OpenQA.Selenium.Remote.RemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters)
   at SeleniumReuseSession.ReuseRemoteWebDriver.Execute(String driverCommandToExecute, Dictionary`2 parameters) in c:\Users\tarun\Documents\SharpDevelop Projects\SeleniumReuseSession\SeleniumReuseSession\Program.cs:line 40
   at OpenQA.Selenium.Remote.RemoteWebDriver.get_Url()
   at SeleniumReuseSession.Program.Main(String[] args) in c:\Users\tarun\Documents\SharpDevelop Projects\SeleniumReuseSession\SeleniumReuseSession\Program.cs:line 83

After debugging the code, I found the issue. The problem is that the base class constructor is called first and then the line this._sessionId = sessionId. The base constructor calls the Execute method. So when we set the sessionId in the dummy response, the ID is null as our constructor code has not been called yet.

Now this is the way constructor are suppose to work and there is no workaround to this behavior. So we need to dig into the constructor to find our workaround

The constructor calls the StartSession method which in turn executes the NewSession command and then save the session in sessionId private field.

//https://github.com/SeleniumHQ/selenium/blob/master/dotnet/src/webdriver/Remote/RemoteWebDriver.cs#L1100

protected void StartSession(ICapabilities desiredCapabilities)
{
    Dictionary<string, object> parameters = new Dictionary<string, object>();
    parameters.Add("desiredCapabilities", this.GetLegacyCapabilitiesDictionary(desiredCapabilities));

    Dictionary<string, object> firstMatchCapabilities = this.GetCapabilitiesDictionary(desiredCapabilities);

    List<object> firstMatchCapabilitiesList = new List<object>();
    firstMatchCapabilitiesList.Add(firstMatchCapabilities);

    Dictionary<string, object> specCompliantCapabilities = new Dictionary<string, object>();
    specCompliantCapabilities["firstMatch"] = firstMatchCapabilitiesList;
    parameters.Add("capabilities", specCompliantCapabilities);

    Response response = this.Execute(DriverCommand.NewSession, parameters);

    Dictionary<string, object> rawCapabilities = (Dictionary<string, object>)response.Value;
    DesiredCapabilities returnedCapabilities = new DesiredCapabilities(rawCapabilities);
    this.capabilities = returnedCapabilities;
    this.sessionId = new SessionId(response.SessionId);
}

So basically when our constructor gets called and the sessionId on our base class is set to null because of our overriden response. The fix is to set the sessionId in our constructor. So if update the constructor as below

public ReuseRemoteWebDriver(Uri remoteAddress, String sessionId)
    :base( remoteAddress,  new OpenQA.Selenium.Remote.DesiredCapabilities()) {
    this._sessionId = sessionId;
    var sessionIdBase = this.GetType()
        .BaseType
        .GetField("sessionId",
                  System.Reflection.BindingFlags.Instance |
                  System.Reflection.BindingFlags.NonPublic);
    sessionIdBase.SetValue(this, new OpenQA.Selenium.Remote.SessionId(sessionId));
}

The finally updated code for our class is as below

public class ReuseRemoteWebDriver: OpenQA.Selenium.Remote.RemoteWebDriver{
    private String _sessionId;
   
    public ReuseRemoteWebDriver(Uri remoteAddress, String sessionId)
        :base( remoteAddress,  new OpenQA.Selenium.Remote.DesiredCapabilities()) {
        this._sessionId = sessionId;
        var sessionIdBase = this.GetType()
            .BaseType
            .GetField("sessionId",
                      System.Reflection.BindingFlags.Instance |
                      System.Reflection.BindingFlags.NonPublic);
        sessionIdBase.SetValue(this, new OpenQA.Selenium.Remote.SessionId(sessionId));
    }
   
    protected override OpenQA.Selenium.Remote.Response
        Execute(string driverCommandToExecute, System.Collections.Generic.Dictionary<string, object> parameters)
    {
        if (driverCommandToExecute == OpenQA.Selenium.Remote.DriverCommand.NewSession)
        {
            var resp =  new OpenQA.Selenium.Remote.Response();
            resp.Status = OpenQA.Selenium.WebDriverResult.Success;
            resp.SessionId = this._sessionId;
            resp.Value = new System.Collections.Generic.Dictionary<String, Object>();
            return resp;
        }
        var respBase = base.Execute(driverCommandToExecute, parameters);
        return respBase;
    }
}