jProtractor

Info

The Angular Protractor is a very popular Selenium Web Driver based project.

There have been recenly few some deeply pessimistic posts about the Google Angular team plans to end the development of Protractor at the end of 2022 (links at the end of the README) and a number of older debates of (dis) advantages of various asyncronuos syntaxes intrinsic to Javascipt, this project is not about specifics of Javascript testsute development but constructs a set of plain Java By-alike classes on top of the 4-year old Protractor’s own vanilla Javascript Client Script Library in much the same way one may choose to invoke straight vanilla Javascript querySelector(selector) and querySelectorAll(selector) instead of findElement and findElements with an By.cssSelector(...)) argument.

However being a Javascript testing tool, depreated or not, genuine Protractor does force one to fully buy in by (re)writing the entire test suite in Javascript which few find an acceptable price.

This project and its twins C# Protractor Client Framework and pytractor manage to make such sacrifice unnecessary.

Angular 1.x a.k.a. AngularJS did introduce few unique (from tester perspective) features: ng-repeat and ng_model, which Protractor takes advantage of by providing unique MVC-style locator strategies

The jProtractor project makes these, plus few extra, locator extensions available to Java:

findBindings = function(binding, exactMatch, using, rootSelector)

finds a list of elements in the page by their angular binding

findByButtonText = function(searchText, using)

finds buttons by textual content

findByCssContainingText = function(cssSelector, searchText, using)

finds elements by css selector and textual content

findByModel = function(model, using, rootSelector)

finds elements by model name

findByOptions = function(options, using)

finds elements by options

findByPartialButtonText = function(searchText, using)

finds button(s) by textual content fragment

findAllRepeaterRows = function(using, repeater)

finds an array of elements matching a row within an ng-repeat

findRepeaterColumn = function(repeater, exact, binding, using, rootSelector)

finds the elements in a column of an ng-repeat

findRepeaterElement = function(repeater, exact, index, binding, using, rootSelector)

finds an element within an ng-repeat by its row and column.

findSelectedOption = function(model, using)

finds the selected option elements by model name (Angular 1.x).

findSelectedRepeaterOption = function(repeater, using)

finds selected option elements in the select implemented via repeater without a model.

TestForAngular = function(attempts)

tests whether the angular global variable is present on a page

waitForAngular = function(rootSelector, callback)

waits until Angular has finished rendering

return angular.element(element).scope().$eval(expression)

evaluates an Angular expression in the context of a given element.

These are implemented in a form of Javascript snippets, one file per method, borrowed from Protractor project’s clientsidescripts.js and can be found in the src/main/java/resources directory:

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

Many AngularJS-specific locators aren’t any longer supported by Angular 2.

The newest Protractor By.deepCss (Shadow DOM) and flexible By.addLocator are not yet supported.

The E2E_Tests section of the document covers the migration.

The standard pure Java Selenium locators are also supported.

Example Tests

There is a big number of examples in src/test/java/com/jprotractor/integration directory.

For desktop browser testing, run a Selenium node and Selenium hub on port 4444 and

  @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);
      ngDriver = new NgWebDriver(seleniumDriver);
  }

  @Before
  public void beforeEach() {
	  String baseUrl = "http://www.way2automation.com/angularjs-protractor/banking";
	  ngDriver.navigate().to(baseUrl);
  }

  @Test
  public void testCustomerLogin() throws Exception {
	  NgWebElement element = ngDriver.findElement(NgBy.buttonText("Customer Login"));
	  highlight(element, 100);
	  element.click();
	  element = ngDriver.findElement(NgBy.input("custId"));
	  assertThat(element.getAttribute("id"), equalTo("userSelect"));
	  Enumeration<WebElement> elements = Collections.enumeration(ngDriver.findElements(NgBy.repeater("cust in Customers")));

	  while (elements.hasMoreElements()){
      WebElement next_element = elements.nextElement();
      if (next_element.getText().indexOf("Harry Potter") >= 0 ){
      	System.err.println(next_element.getText());
      	next_element.click();
      }
	  }
	  NgWebElement login_element = ngDriver.findElement(NgBy.buttonText("Login"));
	  assertTrue(login_element.isEnabled());	
	  login_element.click();
	  assertThat(ngDriver.findElement(NgBy.binding("user")).getText(),containsString("Harry"));
	
	  NgWebElement account_number_element = ngDriver.findElement(NgBy.binding("accountNo"));
	  assertThat(account_number_element, notNullValue());
	  assertTrue(account_number_element.getText().matches("^\\d+$"));
  }

for CI build replace the Setup () with

  @BeforeClass
  public static void setup() throws IOException {
	  seleniumDriver = new PhantomJSDriver();
	  ngDriver = new NgWebDriver(seleniumDriver);
  }

Tests

The project contains over 50 tests execrising various scenarios with popular web sites like and also with static AngularJS sample pages checked in to src/test/resources. To prevent running all these tests during package generation, these tests are currenly converted to integration tests. To run these tests use the comand:

mvn integration-test

Note

PhantomJs allows loading Angular samples from file:// content, you need to allow some additional options if the test page loads external content:

  DesiredCapabilities capabilities = new DesiredCapabilities("phantomjs", "", Platform.ANY);
  capabilities.setCapability(PhantomJSDriverService.PHANTOMJS_CLI_ARGS, new String[] {
    "--web-security=false",
    "--ssl-protocol=any",
    "--ignore-ssl-errors=true",
    "--local-to-remote-url-access=true", // prevent local file test XMLHttpRequest Exception 101
    "--webdriver-loglevel=INFO" // set to DEBUG for a really verbose console output
  });
  seleniumDriver = new PhantomJSDriver(capabilities);

  seleniumDriver.manage().window().setSize(new Dimension(width , height ));
  seleniumDriver.manage().timeouts()
    .pageLoadTimeout(50, TimeUnit.SECONDS)
    .implicitlyWait(implicitWait, TimeUnit.SECONDS)
    .setScriptTimeout(10, TimeUnit.SECONDS);
  wait = new WebDriverWait(seleniumDriver, flexibleWait );
  wait.pollingEvery(pollingInterval,TimeUnit.MILLISECONDS);
  actions = new Actions(seleniumDriver);
  ngDriver = new NgWebDriver(seleniumDriver);
  localFile = "local_file.htm";
  URI uri = NgByIntegrationTest.class.getClassLoader().getResource(localFile).toURI();
  ngDriver.navigate().to(uri);
  WebElement element = ngDriver.findElement(NgBy.repeater("item in items"));
  assertThat(element, notNullValue());

Certain tests ( e.g. involving NgBy.selectedOption() ) currently fail under travis CI build.

Selenum Version compatibility

   
SELENIUM_VERSION 2.53.1
FIREFOX_VERSION 45.0.1
CHROME_VERSION 56.0.X
CHROMEDRIVER_VERSION 2.29
   
SELENIUM_VERSION 3.8.0
FIREFOX_VERSION 52.0
GECKODRIVER_VERSION 0.15
CHROME_VERSION 57.0.X
CHROMEDRIVER_VERSION 2.29

Building the jar

The snapshot release of jprotractor.jar is published to https://oss.sonatype.org/content/repositories/snapshots/com/github/sergueik/jprotractor/jprotractor/ and to make it the dependency add the following into the pom.xml:

    <dependency>
      <groupId>com.github.sergueik.jprotractor</groupId>
      <artifactId>jprotractor</artifactId>
      <version>1.12-SNAPSHOT</version>
      <exclusions>
        <exclusion>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-java</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-server</artifactId>
        </exclusion>
      </exclusions>
    </dependency>

and create or modify the repositories section of the pom.xml:

  <repositories>
    <repository>
      <id>ossrh</id>
      <url>https://oss.sonatype.org/content/repositories/snapshots</url>
    </repository>
  </repositories>

You can build the jprotractor.jar locally from the source by cloning the repository

git clone https://github.com/sergueik/jProtractor

and running the following in console:

Windows (jdk1.7.0_65, 32 bit)

set M2=c:\java\apache-maven-3.2.1\bin
set M2_HOME=c:\java\apache-maven-3.2.1
set MAVEN_OPTS=-Xms256m -Xmx512m
set JAVA_VERSION=1.8.0_112
set JAVA_HOME=c:\java\jdk%JAVA_VERSION%
PATH=%JAVA_HOME%\bin;%PATH%;%M2%
REM
REM move %USERPROFILE%\.M2 %USERPROFILE%\.M2.MOVED
REM rd /s/q %USERPROFILE%\.M2
set TRAVIS=true
mvn -Dmaven.test.skip=true clean package

Linux

export TRAVIS=true
mvn -Dmaven.test.skip=true clean package

The project contains substantial number of ‘integration tests’ and by default maven will run all, which will take quite some time, also some of the tests could fail in your environment. After a test failure, maven will not package the jar.

Alternatively you can temporarily remove the src/test/java directory from the project:

rm -f -r src/test/java
mvn clean package

The jar will be in the target folder:

[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ jprotractor ---
[INFO] Building jar: C:\developer\sergueik\jProtractor\target\jprotractor-1.2-SNAPSHOT.jar

You can install it into your local .m2 repository as explained below

Keyword-Driven Frameworks

For example of Keyword-Driven Framework, incorporating the Selenium Protractor methods see sergueik\keyword_driven_framework. It contains both jProtractor and a similar Java-based Protractor Client Library wrapper ptoject, paul-hammant/ngWebDriver.

Using with existing Java projects

Maven

+---src
    +---main
            +---java
            |   +---com
            |       +---mycompany
            |           +---app
            +---resources

1.2-SNAPSHOT
```xml
<dependencies>
<dependency>
     <groupId>com.jprotractor</groupId>
     <artifactId>jprotractor</artifactId>
     <version>${jprotractor.version}</version>
     <scope>system</scope>
     <!--
     <systemPath>${project.basedir}/src/main/resources/jprotractor-${jprotractor.version}.jar</systemPath>
     -->
      <exclusions>
        <exclusion>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-java</artifactId>
        </exclusion>
        <exclusion>
          <groupId>org.seleniumhq.selenium</groupId>
          <artifactId>selenium-server</artifactId>
        </exclusion>
      </exclusions>
</dependency>
</dependencies>

Ant

* Add reference to the code:
```java
import com.jprotractor.NgBy;
import com.jprotractor.NgWebDriver;
import com.jprotractor.NgWebElement;

Note

The ngWebDriver and the jProtractor projects are very similar in that they construct Java classes wrapping avascript Protractor methods. The internal class hierachy is different, of course.

It appears easier to construct the Page object factory By annotations (interfaces) describing the jProtractor library methods than to do the same with the ngWebDriver.

The other difference is that jProtractor splits the original javascript helper libary into small chunks and during execution of a find method sends only the specific fragment where the locator in question is implements, to the browser. The ngWebDriver always sends the whole library.

This also allows one to patch the individual api (it was important a few years ago) and adding new api (currently, only one such API was added, the selectedRepeaterOption. This of course comes at a higher cost of integrating the “upstream” changes, but the genuine Protracror project is no longer evolving as quickly as it used to.

ngWebDriver uses it unmodified.

See Also

Authors