Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web

Developing Protractor tests in C# (or Java)

4.95/5 (14 votes)
29 May 2016CPOL6 min read 94.4K   1.9K  
In this article, an Angular JS Protractor C# and Java Client Library examples are explained.

Introduction

As the slogan proudly says, AngularJS is what HTML would have been, had it been designed for building web-apps. AngularJS was designed from ground up to be testable.

Protractor, the genuine testing framework for AngularJS, is written in JavaScript too. A lot of Selenium developers wish to continue using their existing Java or C# code base and skills when switching to testing AngularJS MVC web applications.

Background

It is quote easy to port Protractor to another client language - it uses a small subset of JsonWire Protocol supported by browserc, namely just one interface. Protractor on its own does not have an specific variants of Wait Conditions available heavily in a core Selenium, but as we see below, Protracor's NgBy class methods are returning valid OpenQA.Selenium.By instances allowing use of IWait<T> interface already available in C# and Java implementations, to handle the service load latency. On the other hand even these is often not required - all NgWebElement interactions involve running the following Javascript code fragment in the browser

var rootSelector = arguments[0];
var callback = arguments[1];
if (window.getAngularTestability) {
    window.getAngularTestability(el).whenStable(callback);
    return;
}

or

var el = document.querySelector(arguments[0]);
var callback = arguments[1];
angular.element(el).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);

both of which block the test client execution until the view finishes updating. It is called from all "core" methods e.g.:

C#
public bool Displayed
{
  get {
    this.ngDriver.WaitForAngular();
    return this.element.Displayed;
  }
}

this allows synchronization between site under test and test scenario - one may say Protractor is not about testing how the page looks like, it tests how it should.

Protractor (and its C# wrapper) offers a rich set of Angular-backed locators some of which is not easy or not at all possible to mimic in a pure Selenium:

  • Evaluate
  • FindBindings
  • FindCssContainingText
  • FindModel
  • FindSelectedOption
  • FindSelectedRepeaterOption
  • FindByButtonText
  • FindByPartialButtonText
  • FindByOptions
  • FindAllRepeaterRows
  • FindRepeaterColumn
  • FindRepeaterElement
  • SetLocation

all of which in fact implemented through the following single Selenium call:

C#
      public override ReadOnlyCollection<IWebElement> FindElements(ISearchContext context)
      {
          // Create script arguments
          object[] scriptArgs = new object[this.args.Length + 1];
          scriptArgs[0] = this.RootElement;
          Array.Copy(this.args, 0, scriptArgs, 1, this.args.Length);

          IJavaScriptExecutor jsExecutor = context as IJavaScriptExecutor;
          if (jsExecutor == null)
          {
              IWrapsDriver wrapsDriver = context as IWrapsDriver;
              if (wrapsDriver != null)
              {
                  jsExecutor = wrapsDriver.WrappedDriver as IJavaScriptExecutor;
              }
          }
          if (jsExecutor == null)
          {
              throw new NotSupportedException("Could not get an IJavaScriptExecutor instance from the context.");
          }

          ReadOnlyCollection<IWebElement> elements = jsExecutor.ExecuteScript(this.script, scriptArgs) as ReadOnlyCollection<IWebElement>;
          if (elements == null)
          {
              elements = new ReadOnlyCollection<IWebElement>(new List<IWebElement>(0));
          }
          return elements;
      }
  }
}

and the Javascript code is taken nearly verbatim from https://github.com/angular/protractor/blob/master/lib/clientsidescripts.js so one can always keep the code current. For example,

JavaScript
/**
 * Find all rows of an ng-repeat.
 *
 * arguments[0] {Element} The scope of the search.
 * arguments[1] {string} The text of the repeater, e.g. 'cat in cats'.
 *
 * @return {Array.WebElement} All rows of the repeater.
 */

var findAllRepeaterRows = function(using, repeater) {
  var rows = [];
  var prefixes = ['ng-', 'ng_', 'data-ng-', 'x-ng-', 'ng\\:'];
  for (var p = 0; p < prefixes.length; ++p) {
      var attr = prefixes[p] + 'repeat';
      var repeatElems = using.querySelectorAll('[' + attr + ']');
      attr = attr.replace(/\\/g, '');
      for (var i = 0; i < repeatElems.length; ++i) {
          if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
              rows.push(repeatElems[i]);
          }
      }
  }
  for (var p = 0; p < prefixes.length; ++p) {
      var attr = prefixes[p] + 'repeat-start';
      var repeatElems = using.querySelectorAll('[' + attr + ']');
      attr = attr.replace(/\\/g, '');
      for (var i = 0; i < repeatElems.length; ++i) {
          if (repeatElems[i].getAttribute(attr).indexOf(repeater) != -1) {
              var elem = repeatElems[i];
              while (elem.nodeType != 8 ||
                  !(elem.nodeValue.indexOf(repeater) != -1)) {
                  if (elem.nodeType == 1) {
                      rows.push(elem);
                  }
                  elem = elem.nextSibling;
              }
          }
      }
  }
  return rows;
};
var using = arguments[0] || document;
var repeater = arguments[1];
return findAllRepeaterRows(using, repeater);

which is embedded in C# source as a static string.

Protractor also provides important Angular-specific Wait methods :

  • WaitForAngular
  • WaitForAllAngular2
  • TestForAngular

which are implemented through ExecuteAsyncScript() too. Finally it goes the extra mile and provides custom NgWebDriver,NgNavigation and NgWebElement classes to minimize the need to switch between Protractor and wrapped Selenium.

The Code

We examine a small test application that performs few typical customer actions in a demo Angular application offered (along with many other useful resources) by http://www.way2automation.com. We will be testing customer accessing a XYZ Bank and adding funds to his/her account.

Note - the login popup displayed on the home page http://way2automation.com/way2auto_jquery/index.php is optional and can be ignored by starting the test execution on the XYZ Bank page http://www.way2automation.com/angularjs-protractor/banking (the method to provide the login data, is available in the attached zip)

Login page

The code in question logs as a specific XYZ Bank customer, selects an account, notes the balance, makes a deposit, and after the transaction is complete, confirms the balance change. The example does not demonstrate any Page Object techniques,the focus is on raw Protractor API:

C#
[TestFixture]
public class Way2AutomationTests
{
    private StringBuilder verificationErrors = new StringBuilder();
    private IWebDriver driver;
    private NgWebDriver ngDriver;
    private WebDriverWait wait;
    private IAlert alert;
    private string alert_text;
    private Regex theReg;
    private MatchCollection theMatches;
    private Match theMatch;
    private Capture theCapture;
    private int wait_seconds = 3;
    private int highlight_timeout = 100;
    private Actions actions;
    private String base_url = "http://www.way2automation.com/angularjs-protractor/banking";

    [TestFixtureSetUp]
    public void SetUp()
    {
        driver = new FirefoxDriver();
        driver.Manage().Timeouts().SetScriptTimeout(TimeSpan.FromSeconds(60));
        ngDriver = new NgWebDriver(driver);
        wait = new WebDriverWait(driver, TimeSpan.FromSeconds(wait_seconds));
        actions = new Actions(driver);
    }

    [SetUp]
    public void NavigateToBankingExamplePage()
    {
        driver.Navigate().GoToUrl(base_url);
        ngDriver.Url = driver.Url;
    }

    [TestFixtureTearDown]
    public void TearDown()
    {
        try
        {
            driver.Close();
            driver.Quit();
        }
        catch (Exception) { }
        Assert.IsEmpty(verificationErrors.ToString());
    }

    [Test]
    public void ShouldDeposit()
    {
        ngDriver.FindElement(NgBy.ButtonText("Customer Login")).Click();
        ReadOnlyCollection<NgWebElement> ng_customers = ngDriver.FindElement(NgBy.Model("custId")).FindElements(NgBy.Repeater("cust in Customers"));
        // select customer to log in
        ng_customers.First(cust => Regex.IsMatch(cust.Text, "Harry Potter")).Click();

        ngDriver.FindElement(NgBy.ButtonText("Login")).Click();
        ngDriver.FindElement(NgBy.Options("account for account in Accounts")).Click();


        NgWebElement ng_account_number_element = ngDriver.FindElement(NgBy.Binding("accountNo"));
        int account_id  = 0;
        int.TryParse(ng_account_number_element.Text.FindMatch(@"(?<result>\d+)$"), out account_id);
        Assert.AreNotEqual(0, account_id);

        int account_amount = -1;
        int.TryParse(ngDriver.FindElement(NgBy.Binding("amount")).Text.FindMatch(@"(?<result>\d+)$"), out account_amount);
        Assert.AreNotEqual(-1, account_amount);

        ngDriver.FindElement(NgBy.PartialButtonText("Deposit")).Click();

        // core Selenium
        wait.Until(ExpectedConditions.ElementExists(By.CssSelector("form[name='myForm']")));
        NgWebElement ng_form_element = new NgWebElement(ngDriver, driver.FindElement(By.CssSelector("form[name='myForm']")));


        NgWebElement ng_deposit_amount_element = ng_form_element.FindElement(NgBy.Model("amount"));
        ng_deposit_amount_element.SendKeys("100");

        NgWebElement ng_deposit_button_element = ng_form_element.FindElement(NgBy.ButtonText("Deposit"));
        ngDriver.Highlight(ng_deposit_button_element);
        ng_deposit_button_element.Click();

        // inspect status message
        var ng_message_element = ngDriver.FindElement(NgBy.Binding("message"));
        StringAssert.Contains("Deposit Successful", ng_message_element.Text);
        ngDriver.Highlight(ng_message_element);

        // re-read the amount
        int updated_account_amount = -1;
        int.TryParse(ngDriver.FindElement(NgBy.Binding("amount")).Text.FindMatch(@"(?<result>\d+)$"), out updated_account_amount);
        Assert.AreEqual(updated_account_amount, account_amount + 100);
    }

Protractor .Net test execution

converted to gif via http://www.online-convert.com/

Performing withdrawl is not shown, code can be foung in the attached zip.

Next we dissect the code above.

The locators are remarkably clean compared to pure Selenium e.g:

C#
// Protractor
NgWebElement ng_customer_login_button_element = ngDriver.FindElement(NgBy.ButtonText("Customer Login"));
// core Selenium
IWebElement customer_login_button_element = driver.FindElement(By.XPath("//button[contains(.,'Customer Login')]"));

We will not even look at pure Selenium equivalents of e.g. finding the customer in Customer Login view

C#
ReadOnlyCollection<ngwebelement> ng_customers = ngDriver.FindElement(NgBy.Model("custId")).FindElements(NgBy.Repeater("cust in Customers"));

Note, to select individual customer from the list above, C# extention methods come quite handy:

C#
// select customer to log in
ng_customers.First(cust => Regex.IsMatch(cust.Text, "Harry Potter")).Click();

For example to ensure table sorting works correctly on Customers table one may extract and compare the last and first elements of the customer list:

C#
[Test]
public void ShouldSortCustomersAccounts()
{
    ngDriver.FindElement(NgBy.ButtonText("Bank Manager Login")).Click();
    ngDriver.FindElement(NgBy.PartialButtonText("Customers")).Click();
    wait.Until(ExpectedConditions.ElementExists(NgBy.Repeater("cust in Customers")));
    // alterntive locator using core selenium
    wait.Until(ExpectedConditions.ElementExists(By.CssSelector("tr[ng-repeat*='cust in Customers']")));

    IWebElement sort_link = ngDriver.WrappedDriver.FindElement(By.CssSelector("a[ng-click*='sortType'][ng-click*= 'fName']"));
    StringAssert.Contains("First Name", sort_link.Text);
    ngDriver.Highlight(sort_link);
    sort_link.Click();
    // TODO: utilize NgBy.RepeaterColumn
    ReadOnlyCollection<ngwebelement> ng_accounts = ngDriver.FindElements(NgBy.Repeater("cust in Customers"));
    // inspect first and last elements
    List<string> ng_account_names = ng_accounts.Select(element => element.Text).ToList();
    String last_customer_name = ng_account_names.FindLast(element => true);
    ngDriver.Highlight(sort_link);
    sort_link.Click();
    // confirm the customers are sorted in reverse order now
    StringAssert.Contains(last_customer_name, ngDriver.FindElements(NgBy.Repeater("cust in Customers")).First().Text);
}

Regular Helper methods had been added to deal with account information extraction from page element text:

C#
public static string FindMatch(this string element_text, string match_string = "(?<result>.+)$", string match_name = "result"){
      result ="";
      theReg = new Regex(match_string,
                  RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace | RegexOptions.Compiled);

      theMatches = theReg.Matches(element_text);
      foreach (Match theMatch in theMatches)
      {
          if (theMatch.Length != 0)
          {
              foreach (Capture theCapture in theMatch.Groups[match_name].Captures)
              {
                  result = theCapture.ToString();
              }
          }
      }
      return result;
}

and highlight page elements

C#
    public static void  Highlight(this NgWebDriver driver, IWebElement element, int highlight_timeout = 100, int px = 3, string color = "yellow")
{
    IWebDriver context = driver.WrappedDriver;
    ((IJavaScriptExecutor)context).ExecuteScript("arguments[0].style.border='" + px + "px solid " + color + "'", element);
    Thread.Sleep(highlight_timeout);
    ((IJavaScriptExecutor)context).ExecuteScript("arguments[0].style.border=''", element);
}

Another notable difference - during test case development, instead of inspecting the page element CSS selectors and XPaths in the browser, the page view partial e.g. http://www.way2automation.com/angularjs-protractor/banking/depositTx.html

HTML
<span class="error" ng-show="message" >{{message}}</span><br>

and controller http://www.way2automation.com/angularjs-protractor/banking/depositController.js

JavaScript
if (txObj.success) {
    $scope.message = "Deposit Successful";
} else {
    $scope.message = "Something went wrong. Please try again.";
}

have been inspected e.g. to construct a Binding locator and the associated message contents:

C#
var ng_message_element = ngDriver.FindElement(NgBy.Binding("message"));
StringAssert.Contains("Deposit Successful", ng_message_element.Text);
highlight(ng_message_element);

At one step one may discover two buttons on the page with the text Deposit , and hence one has to switch to form context to click on a second of the two. To accomplish that a small piece of core Selenium code is used:

C#
int wait_seconds = 3;
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(wait_seconds));
String css_selector = "form[name='myForm']"l
wait.Until(ExpectedConditions.ElementExists(By.CssSelector(css_selector)));
NgWebElement ng_form_element = new NgWebElement(ngDriver, driver.FindElement(By.CssSelector(css_selector)));

The code locates the form and instantiates the NgWebElement object from an IWebElement

Another case to switch to core Selenium is when dealing with Javascript alerts:

JavaScript
try
{    
IAlert alert = ngDriver.WrappedDriver.SwitchTo().Alert(); 
String alert_text = alert.Text;
StringAssert.StartsWith("Account created successfully with account Number",   alert_text);
alert.Accept();
}
catch (NoAlertPresentException ex)
{
// Alert not present
verificationErrors.Append(ex.StackTrace);
}
catch (WebDriverException ex)
{
// Alert may not be handled properly by PhantomJS
verificationErrors.Append(ex.StackTrace);
}    

and OpenQA.Selenium.Interactions.Actions

C#
actions.MoveToElement(ng_button.WrappedElement).ClickAndHold().Build().Perform();

Note that in addition to locating elements, Protractor allows one to evaluate them:

C#
[Test]
public void ShouldEvaluateTransactionDetails()
{
    ngDriver.FindElement(NgBy.ButtonText("Customer Login")).Click();
    // select customer/account with transactions
    ngDriver.FindElement(NgBy.Model("custId")).FindElements(NgBy.Repeater("cust in Customers")).First(cust => Regex.IsMatch(cust.Text, "Hermoine Granger")).Click();
    ngDriver.FindElement(NgBy.ButtonText("Login")).Click();
    ngDriver.FindElements(NgBy.Options("account for account in Accounts")).First(account => Regex.IsMatch(account.Text, "1001")).Click();

    // switch to transactions
    NgWebElement ng_transaction_list_button = ngDriver.FindElement(NgBy.PartialButtonText("Transactions"));
    StringAssert.Contains("Transactions", ng_transaction_list_button.Text);
    ngDriver.Highlight(ng_transaction_list_button);
    ng_transaction_list_button.Click();

    // wait for transaction information to be loaded and rendered
    wait.Until(ExpectedConditions.ElementExists(NgBy.Repeater("tx in transactions")));

    // examine first few transactions using Evaluate
    ReadOnlyCollection<NgWebElement> ng_transactions = ngDriver.FindElements(NgBy.Repeater("tx in transactions"));
    int cnt = 0;
    foreach (NgWebElement ng_current_transaction in ng_transactions) {
      if (cnt++ > 5) { break; }
      StringAssert.IsMatch("(?i:credit|debit)", ng_current_transaction.Evaluate("tx.type").ToString());
      StringAssert.IsMatch(@"(?:\d+)", ng_current_transaction.Evaluate("tx.amount").ToString());
      // 'tx.date' is in Javascript UTC format similar to UniversalSorta­bleDateTimePat­tern in C#
      var transaction_date = ng_current_transaction.Evaluate("tx.date");
      StringAssert.IsMatch(@"(?:\d{4}\-\d{2}\-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z)", transaction_date.ToString());
    }
}

Source Code

Author's protractor-net project is a few commits ahead (PR pending) of upstream project by Bruno Baia with some core Javascript Find elements methods updated from the reference Angular protractor project:

  • NgBy.RepeaterColumn method - illustrated in ShouldListTransactions test
  • NgBy.ButtonText and NgBy.PartialButtonText, exercised in practically every test

and the Extensions class.

 

The following tests can be found in the attached zip:

C#
  • ShouldAddCustomer
  • ShouldDeleteCustomer
  • ShouldDeposit
  • ShouldEvaluateTransactionDetails
  • ShouldListTransactions
  • ShouldLoginCustomer
  • ShouldOpenAccount
  • ShouldSortCustomersAccounts
  • ShouldWithdraw

Protractor Java Client

The Protractor Java binding project started as a fork of jProtractor by Carlos Alexandro Becker. The latter project appears not very mature - it originally supported only

  • binding.js
  • buttonText.js
  • model.js
  • options.js
  • repeater.js

through By class extension, but lacked waitForAngular invoker. Later the code from Protractor-jvm project by Aaron Van Prooyen was merged to jProtractor. Methods were implemented

  • binding.js
  • buttonText.js
  • cssContainingText.js
  • evaluate.js
  • getLocationAbsUrl.js
  • model.js
  • options.js
  • partialButtonText.js
  • repeater.js
  • repeaterColumn.jsv
  • repeaterElement.js
  • repeaterRows.js
  • resumeAngularBootstrap.js
  • selectedOption.js
  • selectedRepeaterOption.js
  • testForAngular.js
  • waitForAngular.js

and tested:

Java (desktop)
  • testAddCustomer
  • testCustomerLogin
  • testDepositAndWithdraw
  • testEvaluateTransactionDetails
  • testListTransactions
  • testOpenAccount
  • testSortCustomerAccounts
Java (travis)
  • testAddition
  • testChangeSelectedtOption
  • testEvaluate
  • testFindElementByOptions
  • testFindElementByRepeaterColumn
  • testFindElementByRepeaterWithBeginEnd
  • testFindSelectedtOption

There is a big number of Local Page test exploring various aspects of core Angular, AngularUI and common Angular directives:

  • testButtonNgIf()
  • testButtonStateText()
  • testAddition()
  • testDatePickerDirectSelect()
  • testNonAngular()
  • testNonAngularIgnoreSync()
  • testDatePickerDirectSelect()
  • testDatePickerNavigation()
  • testEvaluate()
  • testUpload1()
  • testUpload3()
  • testEvaluateEvenOdd()
  • testFindRepeaterElement()
  • testFindElementByRepeaterColumn()
  • testFindSelectedtOptionWithAlert()
  • testFindSelectedtOption()
  • testChangeSelectedtOption()
  • testChangeSelectedRepeaterOption()
  • testMultiSelect2()
  • testMultiSelect()
  • testFindElementByRepeaterWithBeginEnd()
  • testFindElementByOptions()
  • testFindElementByModel()
  • testElementTextIsGenerated()
  • testDropDownWatch()
  • testFindRepeaterRows()
  • testFindorderByField()
  • testAngularUISelectHandleSelectedAndAvailable()
  • testAngularUISelectHandleSearch()
  • testAngularUISelectHandleDeselect()
  • testPrintOrderByFieldColumn()
  • testFindAllBindings()
  • testDropDown()
  • testSelectOneByOne()
  • testSelectAll()
  • testAddFriend()
  • testSearchAndDeleteFriend()
  • testRemoveAllFriends()
  • testSliderKeyPress()
  • testSliderMouseMove()
  • testCircles()

(both for Java and C#). The bare bones Angular page resources are checked in in the roject Samples and src/test/resources directories.

Sample And and Maven projects demonstrating usage of jProtractor.jar are provided. Cucumber conversion is a work in progress.

The skeleton of the call chain is shown below:

Java
public class ClientSideScripts {

protected static String getScriptContent(String fileName) {
  try {
      InputStream is = ClientSideScripts.class.getClassLoader().getResourceAsStream(fileName);
      byte[] bytes = new byte[is.available()];
      is.read(bytes);
      return new String(bytes, "UTF-8");
  } catch ( IOException e) {
    throw new RuntimeException(e);
  }
}

public static final String PartialButtonText = getScriptContent("partialButtonText.js");
Java
public class NgBy 
{
private NgBy() { }
public static By partialButtonText(String text){
  return new JavaScriptBy(ClientSideScripts.PartialButtonText, text);
}
Java
public class NgWebElement implements WebElement, WrapsElement

{
  private NgWebDriver ngDriver;
  private WebElement element;

  public NgWebElement findElement(By arg0) {
    if (arg0 instanceof JavaScriptBy)  {
      ((JavaScriptBy)arg0).RootElement = this.element;
    }
    this.ngDriver.WaitForAngular();
    return new NgWebElement(this.ngDriver, this.element.findElement(arg0));
  }

  public Object evaluate(String expression){
    this.ngDriver.WaitForAngular();
    JavascriptExecutor jsExecutor = (JavascriptExecutor)this.ngDriver.getWrappedDriver();
    return jsExecutor.executeScript(ClientSideScripts.Evaluate, this.element, expression);
  }

Hopefully, the analogy between .Net and Java implementations is clear. The already developed .Net tests served as a blueprint for the Java ones.

In the atached zip one will also find miscellaneous tests of demo pages http://www.java2s.com implemented in both Java and C#.

Protractor Java test execution

 

Individual tests look very similar to .Net:

Java
@Test
    public void testListTransactions() throws Exception {
      // customer login
      ngDriver.findElement(NgBy.buttonText("Customer Login")).click();
      // select customer/account with transactions
      assertThat(ngDriver.findElement(NgBy.input("custId")).getAttribute("id"), equalTo("userSelect"));

      Enumeration<WebElement> customers = Collections.enumeration(ngDriver.findElement(NgBy.model("custId")).findElements(NgBy.repeater("cust in Customers")));
      
      while (customers.hasMoreElements()){
        WebElement next_customer = customers.nextElement();
        if (next_customer.getText().indexOf("Hermoine Granger") >= 0 ){
          System.err.println(next_customer.getText());
          next_customer.click();
        }
      }
      NgWebElement login_element = ngDriver.findElement(NgBy.buttonText("Login"));
      assertTrue(login_element.isEnabled());
      login_element.click();

      Enumeration<WebElement> accounts = Collections.enumeration(ngDriver.findElements(NgBy.options("account for account in Accounts")));
      
      while (accounts.hasMoreElements()){
        WebElement next_account = accounts.nextElement();
        if (Integer.parseInt(next_account.getText()) == 1001){
          System.err.println(next_account.getText());
          next_account.click();
        }
      }
      // inspect transactions
      NgWebElement ng_transactions_element = ngDriver.findElement(NgBy.partialButtonText("Transactions"));
      assertThat(ng_transactions_element.getText(), equalTo("Transactions"));
      highlight(ng_transactions_element);
      ng_transactions_element.click();
      wait.until(ExpectedConditions.visibilityOf(ngDriver.findElement(NgBy.repeater("tx in transactions")).getWrappedElement()));
      Iterator<WebElement> ng_transaction_type_columns = ngDriver.findElements(NgBy.repeaterColumn("tx in transactions", "tx.type")).iterator();
      while (ng_transaction_type_columns.hasNext() ) {
        WebElement column = (WebElement) ng_transaction_type_columns.next();
        if (column.getText().isEmpty()){
          break;
        }
        if (column.getText().equalsIgnoreCase("Credit") ){
          highlight(column);
        }
      }
    }

Protractor tests are easily structured for conversion into Page Objects and Cucumber step definitions in Java:

Java
@Test
  public void testSelectOneByOne() throws Exception {
  String baseUrl = "http://amitava82.github.io/angular-multiselect/";
  // Given selecting cars in multuselect directive
  NgWebElement ng_directive = ngDriver.findElement(NgBy.model("selectedCar"));
  assertThat(ng_directive, notNullValue());
  WebElement toggleSelect = ngDriver.findElement(NgBy.buttonText("Select Some Cars"));
  assertTrue(toggleSelect.isDisplayed());
  toggleSelect.click();
  // When selecting all cars
  // find how many cars there are
  List <webelement> cars = ng_directive.findElements(NgBy.repeater("i in items"));
  int count = 0;
  // select one car at a time
  for (count = 0; count != cars.size() ; count ++) {
    NgWebElement ng_car = ngDriver.findElement(NgBy.repeaterElement("i in items", count, "i.label"));
    System.err.println( "* " + ng_car.evaluate("i.label").toString());
    highlight(ng_car);
    ng_car.click();
  }
  // Then all cars were selected
  cars = ng_directive.findElements(NgBy.repeater("i in items"));
  assertThat(cars.size(), equalTo(count));
  // Then the number of selected cars is shown
  WebElement button = ngDriver.findElement(By.cssSelector("am-multiselect > div > button"));
  assertTrue(button.getText().matches("There are (\\d+) car\\(s\\) selected"));
  System.err.println( button.getText());
  // TODO: Then button text shows that all cars were selected
}

or C#

C#
[Test]
public void ShouldSelectAll()
{
    // Given selecting cars in multuselect directive
    NgWebElement ng_directive_selector = _ngDriver.FindElement(NgBy.Model("selectedCar"));
    Assert.IsNotNull(ng_directive_selector.WrappedElement);
    IWebElement toggleSelect = ng_directive_selector.FindElement(By.CssSelector("button[ng-click='toggleSelect()']"));
    Assert.IsNotNull(toggleSelect);
    Assert.IsTrue(toggleSelect.Displayed);
    toggleSelect.Click();
    // When selected all cars using 'check all' link
    _wait.Until(d => (d.FindElements(By.CssSelector("button[ng-click='checkAll()']")).Count != 0));
    IWebElement check_all = ng_directive_selector.FindElement(By.CssSelector("button[ng-click='checkAll()']"));
    Assert.IsTrue(check_all.Displayed);
    check_all.Click();
    // Then all cars were selected
    ReadOnlyCollection<ngwebelement> cars = ng_directive_selector.FindElements(NgBy.Repeater("i in items"));
    Assert.AreEqual(cars.Count(), cars.Count(car => (Boolean) car.Evaluate("i.checked")));
}

Image 4

As project is evolving, test cases and Protractor methods from the .Net client are being ported to the Java one - this is basically what one finds in the attached zip.

Java
  @Test
  public void testAddCustomer() throws Exception {
    ngDriver.findElement(NgBy.buttonText("Bank Manager Login")).click();
    ngDriver.findElement(NgBy.partialButtonText("Add Customer")).click();

    NgWebElement firstName  = ngDriver.findElement(NgBy.model("fName"));
    assertThat(firstName.getAttribute("placeholder"), equalTo("First Name"));
    firstName.sendKeys("John");

    NgWebElement lastName  = ngDriver.findElement(NgBy.model("lName"));
    assertThat(lastName.getAttribute("placeholder"), equalTo("Last Name"));
    lastName.sendKeys("Doe");

    NgWebElement postCode  = ngDriver.findElement(NgBy.model("postCd"));
    assertThat(postCode.getAttribute("placeholder"), equalTo("Post Code"));
    postCode.sendKeys("11011");

    // NOTE: there are two 'Add Customer' buttons on this form
    Object[] addCustomerButtonElements = ngDriver.findElements(NgBy.partialButtonText("Add Customer")).toArray();
    WebElement addCustomerButtonElement = (WebElement) addCustomerButtonElements[1];
    addCustomerButtonElement.submit();
    Alert alert =  seleniumDriver.switchTo().alert();
    String customer_added = "Customer added successfully with customer id :(\\d+)";

    Pattern pattern = Pattern.compile(customer_added);
          Matcher matcher = pattern.matcher(alert.getText());
    if (matcher.find()) {
      System.out.println("customer id " + matcher.group(1) );
    }
    // confirm alert
    alert.accept();

    // switch to "Customers" screen
    ngDriver.findElement(NgBy.partialButtonText("Customers")).click();
    Thread.sleep(1000);

    wait.until(ExpectedConditions.visibilityOf(ngDriver.findElement(NgBy.repeater("cust in Customers"))));

    Enumeration<WebElement> customers = Collections.enumeration(ngDriver.findElements(NgBy.repeater("cust in Customers")));

    WebElement currentCustomer = null;
    while (customers.hasMoreElements()){
      currentCustomer = customers.nextElement();
      if (currentCustomer.getText().indexOf("John Doe") >= 0 ){
        System.err.println(currentCustomer.getText());
        break;
      }
    }
    assertThat(currentCustomer, notNullValue());
    actions.moveToElement(currentCustomer).build().perform();

    highlight(currentCustomer);

    // delete the new customer
    NgWebElement deleteCustomerButton = new NgWebElement(ngDriver, currentCustomer).findElement(NgBy.buttonText("Delete"));
    assertThat(deleteCustomerButton, notNullValue());
    assertThat(deleteCustomerButton.getText(),containsString("Delete"));
    highlight(deleteCustomerButton,300);
    // .. in slow motion
    actions.moveToElement(deleteCustomerButton.getWrappedElement()).clickAndHold().build().perform();
    Thread.sleep(100);
    actions.release().build().perform();
    // let the customers reload
    wait.until(ExpectedConditions.visibilityOf(ngDriver.findElement(NgBy.repeater("cust in Customers"))));
    Thread.sleep(1000);
}

For desktop browser testing, it may help to launch Selenium node and hub (e.g. locally on port 4444)

Java
@BeforeClass
public static void setup() throws IOException {
  DesiredCapabilities capabilities =   new DesiredCapabilities("firefox", "", Platform.ANY);
  FirefoxProfile profile = new ProfilesIni().getProfile("default");
  capabilities.setCapability("firefox_profile", profile);
  seleniumDriver = new RemoteWebDriver(new URL("http://127.0.0.1:4444/wd/hub"), capabilities);
  try{
    seleniumDriver.manage().window().setSize(new Dimension(600, 800));
    seleniumDriver.manage().timeouts()
      .pageLoadTimeout(50, TimeUnit.SECONDS)
      .implicitlyWait(20, TimeUnit.SECONDS)
      .setScriptTimeout(10, TimeUnit.SECONDS);
  }  catch(Exception ex) {
    System.out.println(ex.toString());
  }
  ngDriver = new NgWebDriver(seleniumDriver);
}

one can inspect the script execution in node console:

Shell
20:11:25.184 INFO - Executing: [execute script: /**
 * Find elements by model name.
 *
 * arguments[0] {Element} The scope of the search.
 * arguments[1] {string} The model name.
 *
 * @return {Array.WebElement} The matching input elements.
 */
var findByModel = function(model, using, rootSelector) {
    var root = document.querySelector(rootSelector || 'body');
...
};
var using = arguments[0] || document;
var model = arguments[1];
var rootSelector = arguments[2];
return findByModel(model, using, rootSelector);, [null, Login]])
20:11:25.243 INFO - Done: [execute script: /**

For CI build

Java
private static void checkEnvironment() {
  if (env.containsKey("TRAVIS") && env.get("TRAVIS").equals("true")) {
    isCIBuild =  true;
    isDestopTesting = false;
  }
}

@BeforeClass
public static void setup() throws IOException {
  seleniumDriver = new PhantomJSDriver();
  wait = new WebDriverWait(seleniumDriver, flexible_wait_interval );
  wait.pollingEvery(wait_polling_interval,TimeUnit.MILLISECONDS);
  actions = new Actions(seleniumDriver);
  ngDriver = new NgWebDriver(seleniumDriver);
}

Login page

Release History

  • 2015-12-24 - Initial version
  • 2015-12-26 - Added source download links
  • 2015-12-27 - Updated protractor-net and protractor-java download, provided upstream project information
  • 2015-12-28 - Merged protractor-java with Protractor-jvm. Updated protractor-net and protractor-java downloads - fixed the link
  • 2015-12-31 - Provided more Java and .Net test examples. Updated protractor-java and protractor-net downloads. Corrected some statements regarging integration Protractor with core Selenium
  • 2016-01-01 - Finished some examples
  • 2016-01-02 - Added examples, Updated downloads.
  • 2016-01-07 - Pull request to jProtractor have been merged - updated examples, downloads.
  • 2016-02-02 - Added examples, Updated downloads.
  • 2016-02-10 - Included test projects in the download zip.
  • 2016-05-29 - Updated downloads.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)