Tag Archives: Selenium

Comparing Playwright to Selenium

Playwright is a library for controlling web browsers similar to Cypress, Nightwatch, Selenium etc etc. Its modern, advanced and fast! Especially compared to Selenium and Selenium Grid from providers such as BrowserStack and SauceLabs.

Playwright supports videos, console and network logs out of the box. Even for headless browsers.

You can work with downloads, browser’s network-stack and console.

It has convenient ways of locating elements and easily combines different locator types into 1 locator. Selenium has ByChained, but is more cumbersome.

It automatically waits for elements to be actionable, while Selenium requires the tester to use constructs like: Wait.Until(ElementIsClickable()).Click()

Playwright does way less DOM access than Selenium. Here’s an somewhat extreme example to show the difference. If you do this in Selenium, then for each row, it will query the DOM to return a WebElement for the checkbox in the row.

var rows = FindElements(By.ClassName("table-row"))
foreach(var row in rows)
{
   var checkbox = row.FindElement(By.ClassName("checkbox"))
}

Playwright won’t query the DOM for the checkbox. It returns a new locator (equivalent to Selenium’s By class) derived from the row web element to find that specific check-box.

Runs headless in CI/CD pipelines but still delivers video recordings and logfiles.

Although most tutorials use the default Playwright test runner, its works great with TypeScript and cucumber-js.

Zalenium a stable and scalable Selenium grid

I just want to give well deserved thumbs up to Zalando’s Zalenium Their own description says it best:

Allows anyone to have a disposable, flexible, container based Selenium Grid infrastructure featuring video recording, live preview, basic auth & online/offline dashboards

Getting up and-running really is only one docker pull and docker run command away.

How to get Selenium to run the browser as a different user

Selenium is great for testing web-apps. One of the challenges that you’ll eventually run into is needing to control which user is connecting to the system under test. Achieving this is far from intuitive. A search for this topic gives many links that don’t solve this problem:

Most of these are attempting to make use of Impersonation. This doesn’t work as the web-browser process ends-up running under your credentials not the credentials that you impersonated into. This is because Selenium’s code on Windows uses the CreateProcess() function to starts its sub-processes. The MSDN page on that says

“If the calling process is impersonating another user, the new process uses the token for the calling process, not the impersonation token”

How do we achieve our objective then? The answer is to start using RemoteWebDriver together with Selenium Grid. Lets assume we want to run testcases under your account and the account of a user I will call user1.

Start a Selenium hub on your local machine under your account.

Start a Selenium node on your local machine under your account.This node will run web-browsers using your credentials. The following command will start a node that can run Firefox and Internet Explorer on Windows:

java 
-jar Selenium-server-standalone-x.xx.x.jar 
-role node 
-port 55565 
-hub "http://<yourmachine>:4444/grid/register" 
-browser "browserName=firefox" 
-browser "browserName=iexplore"

Start a Selenium node on another machine that is logged on as user1. This node will run web browsers as user1. Make sure that node is starting with different capabilities than your node. For example, here although we start the node on Windows, we manually overrule that and the node tells the hub that its running on Mac.

java 
-jar Selenium-server-standalone-x.xx.x.jar 
-role node 
-port 5556 
-hub "http://<yourmachine>:4444/grid/register" 
-browser "platform=MAC,browserName=firefox,maxinstances=12"

Instead of instantiating the actual implementations such as InternetExplorerDriver or FirefoxDriver, you need to instantiate a RemoteWebDriver. When instantiating the RemoteWebDriver, you can use the DesiredCapabilities object to determine which machine (and thus which user) Selenium will choose to run the browser on. For example:

public IWebDriver CreateNewBrowserFor(string Who)
{
    //Decide which of Selenium nodes we want to connect to 
    string CapabilitiesOfTargetUser;
    if(Who.Equals("user1"))
    {
        //We need to control a browser for user1 on his node
        CapabilitiesOfTargetUser = "platform=Mac;browserName=firefox";
    }
    else if(Who.Equals("me"))
    {
        //We need to control a browser for me on my node
        CapabilitiesOfTargetUser = "platform=WINDOWS";
    }
    else
    {
        throw new ArgumentException();
    }
    
    //Create a Selenium DesiredCapabilities object that contains our choosen capabilities
    Dictionary<string, object> RequestedCapabilities = new Dictionary<string, object> ();
    string[] CapabilitiesArray = CapabilitiesOfTargetUser.Split(';');
    foreach(string KeyValuePair in CapabilitiesArray)
    {
        string key = KeyValuePair.Split('=').First().Trim();
        string value = KeyValuePair.Split('=').Last().Trim();
        RequestedCapabilities[key] = value;
    }
    DesiredCapabilities Capabilities = new DesiredCapabilities(RequestedCapabilities);
    
    //Create the RemoteWebDriver. Selenium's hub will ensure that this RemoteWebDriver is
    //actually controlling a new browser on the correct machine
    return  new RemoteWebDriver
    (
         new Uri("http://localhost:4444/wd/hub")
       , Capabilities
       , new TimeSpan(0, 0, 50)
    );
}

Starting a process in PowerShell with dynamic command-line parameters

Starting a command-line process from PowerShell is very easy. A simple java -jar helloworld.jar works just fine. However when I’m starting nodes in my Selenium Grid I need to dynamically create a different number of parameters. The following code will fail because the various strings wont be correctly mapped to the usual argv[] input parameters for the java.exe process:

$arguments = ''
$arguments += ' ' + '-jar xxxx.jar'
$arguments += ' ' + '-browser'
$arguments += ' ' + 'browserName=firefox,version=3.6,platform=WINDOWS'
$arguments += ' ' + '-browser'
$arguments += ' ' + 'browserName=internet explorer,version=10,platform=WINDOWS'
java $arguments

Instead, just create an array of command-line arguments like this (assume that $Capabilities is an array of hash tables)

        $arguments = @()
        $arguments += '-jar'
        $arguments += $Jar
        $arguments += '-role'
        $arguments += 'node'
        $arguments += '-port'
        $arguments += 5555
        $arguments += '-hub'
        $arguments += '"' + 'http://127.0.0.1:4444/grid/register' + '"'

        foreach ($hashTable in $Capabilities)
        {
            $arguments += '-browser'
            $strCaps = ($hashTable.GetEnumerator() | ForEach-Object { '$($_.Key)=$($_.Value)' }) -join ','
            $strCaps = '"' + $strCaps + '"'
            $arguments += $strCaps
        }
        java $arguments

How to get video recordings of your testcase

Recording the screen during test execution is very helpful in diagnosing issues. Here are a few ways you can achieve this.

Visual Studio’s Data Diagnostic Adapters

If you’re running your tests with Visual Studio and/or Test Controller/Agents, then you can add a .testsettings file to the test project and configure it to include the “Video Data Diagnostic Adapter” That will record the screen while each testcase is executing and save the results in the output directory specified in the .testsettings file. I find this to be very useful when I’m authoring new testcases on my machine or when I’m running through a Controller/Agent for an app that interacts with the desktop.

Selenium Grid Servlets

If you’re running on a Selenium Grid setup, then there are various ways of getting the nodes to record the screen. Take a look at Tuenti VideoRecorderService or Selenium Video Node.

Screen recording software

You can use some kind of screen recording software like Camstudio or VNC. For me this isn’t preferred as it requires manual action on behalf of the tester.

Single Sign On with Selenium and Firefox in a windows environment

When you run a testcase using Selenium WebDriver against a site like SharePoint, you’ll frequently see that Firefox is blocked waiting for you to enter the username and password of a user. This may seem unexpected because it even happens when your own Firefox window doesn’t need you to enter any credentials.

The reason for this is because the FirefoxDriver creates a new temporary profile and it doesn’t include the settings needed for SSO. There’s 2 ways of dealing with this.

The first way

The first way only works on the local machine that is running the tests. You can configure the FirefoxDriver to use an existing profile instead of creating a new one. The following code snippet shows how to do that in C#.

new FirefoxDriver(new FirefoxProfileManager().GetProfile("default"));

You don’t have to use the “default” profile. You’re free to create a dedicated profile for Selenium tests if you want. Just start Firefox from the command line using firefox -p to start Firefox’s Profile Manager.

The second way

The first way wont work if your tests use RemoteWebDriver to connect to a remote instance of FirefoxDriver. This is due to the fact that identically named profiles, still have a different internal name on each machine.

Instead we will configure Firefox to include the relevant SSO settings into every profile that’s created. You will need administrative credentials during this one-time configuration. Lets assume your site is called “https://myserver.contoso.local/sites/myapp”

  1. Open Windows Explorer and navigate to the Firefox installation directory. That’s usually “C:\Program Files (x86)\Mozilla Firefox”
  2. Create a file there (in this example, I used “mozilla.cfg”) with the following content:
    //
    lockPref("network.automatic-ntlm-auth.trusted-uris", "contoso.local");
    
  3. Use Windows Explorer to navigate to Firefox’s directory containing the preferences to be used for new profiles. That’s usually “C:\Program Files (x86)\Mozilla Firefox\defaults\pref”
  4. Create a file there called “local-settings.js” with the following content:
    //@line 2 "c:\builds\moz2_slave\rel-m-esr31-w32_bld-0000000000\build\browser\app\profile\channel-prefs.js"
    /* This Source Code Form is subject to the terms of the Mozilla Public
    * License, v. 2.0. If a copy of the MPL was not distributed with this
    * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
    pref("general.config.obscure_value", 0);
    pref("general.config.filename", "mozilla.cfg");
    

See Locking preferences in Firefox for details on this.

Fixing the slow combination of WebDriver and Internet Explorer.

Is your Selenium WebDriver running very slowly with Internet Explorer? Then you’re probably running a 64-bit IEDriverServer.exe with a 32 bit Internet Explorer. Even on 64-bit systems, Windows usually runs the 32-bit iexplore.exe.To solve the problem, just replace your IEDriverServer.exe with the 32 bit one and the speed increase will be enormous.

Highlighting webelements from Selenium

When I’m using selenium to navigate through a web-app, I like to record the session and visually highlight the DOM elements that my testscripts are using.

The IWebDriver and IWebElement interfaces don’t supply a method for this….so we’ll have to build our own. The approach I use, is to inject some JavaScript into the webpage to modify the style of the DOM element.

Here’s the code:

        public void highlightElement(IWebElement element)
        {
            IJavaScriptExecutor js = this.Driver as IJavaScriptExecutor;
            string OriginalStyle   = element.GetAttribute("style");
            string HighlightedStyle = OriginalStyle + "border: 3px solid red;";
            
            for (int i = 0; i < 3; i++)
            {
                js.ExecuteScript("arguments[0].setAttribute('style', arguments[1]);", element, HighlightedStyle);
                System.Threading.Thread.Sleep(500);
                js.ExecuteScript("arguments[0].setAttribute('style', arguments[1]);", element, OriginalStyle);
                System.Threading.Thread.Sleep(500);
            }
        }

Say for example, that I have the following testscript:

Feature: SeleniumHighlightingOfWebElements
	I want to show how I can show a user which elements in the DOM my testcases are using

Scenario: DemonstrateHighlighting
	Given I navigate to 'http://en.wikipedia.org/wiki/Hello_world_program'
	Then there must be a link with 'text' 'computer program'
	And there must be an element with 'xpath' '//./div[@class='thumbinner' and .//a[@href and normalize-space() =  'Firefox'] ]'
	And there must be a link with 'text' 'display device'
	And there must be an element with 'xpath' '//./div[@class='thumbinner' and .//a[@href and normalize-space() =  'CNC'] ]'
	And there must be a link with 'text' 'programming languages'
	And there must be an element with 'xpath' '//./div[@class='thumbinner' and .//a[@href and normalize-space() =  'PlayStation Portable'] ]'

That will result in the following screen capture:

Combining SpecFlow and Selenium

One of the cool things I like to do when testing webapps, is to define the testcases using SpecFlow (also known as Cucumber) and then use Selenium to actually execute the testcases against the web-application.

Here is an example testcase:

Feature: SpecFlowAndSelenium
	For demonstration purposes, I want to show how the human-readable
	SpecFlow testcases can be executed using Selenium to operate
	a real webbrowser

Scenario: SeleniumAndSpecFlow
	Given I navigate to 'http://www.google.com'
	Then there must be a control with 'id' 'gbqfq' 
	When I type 'Hello World!' into it
	Then there must be a control with 'id' 'gbqfb'
	When I click on it
	Then there must be a link with 'partialtext' 'Wikipedia' 
	When I click on it
	Then there must be a control with 'id' 'searchInput'
	When I type 'Selenium' into it
	Then there must be a control with 'id' 'searchButton' 
	When I click on it
	Then there must be a link with 'text' 'chemical element'
	And there must be a link with 'text' 'Selenium (software)'
	When I click on it
	Then there must be a link with 'text' 'web browsers'

And here you can see Selenium using Firefox to perform all the actions and checks: