Awesome
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
- Copy
target\jprotractor-1.2-SNAPSHOT.jar
to your projectsrc/main/resources
:
+---src
+---main
+---java
| +---com
| +---mycompany
| +---app
+---resources
- Add reference to the project
pom.xml
(a sample project is checked in)
<properties>
<jprotractor.version>1.2-SNAPSHOT</jprotractor.version>
</properties>
<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>
- Add reference to the code:
import com.jprotractor.NgBy;
import com.jprotractor.NgWebDriver;
import com.jprotractor.NgWebElement;
Ant
- Copy the
target\jprotractor-1.2-SNAPSHOT.jar
in the same location oher dependency jars, e.g.c:\java\selenium
, - Use the bolierplate
build.xml
(a sample project is checked in) or merge with your existing build file(s):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project name="example" basedir=".">
<property name="build.dir" value="${basedir}/build"/>
<property name="selenium.jars" value="c:/java/selenium"/>
<property name="src.dir" value="${basedir}/src"/>
<target name="loadTestNG" depends="setClassPath">
<taskdef resource="testngtasks" classpath="${test.classpath}"/>
</target>
<target name="setClassPath">
<path id="classpath_jars">
<pathelement path="${basedir}/"/>
<fileset dir="${selenium.jars}" includes="*.jar"/>
</path>
<pathconvert pathsep=";" property="test.classpath" refid="classpath_jars"/>
</target>
<target name="clean">
<delete dir="${build.dir}"/>
</target>
<target name="compile" depends="clean,setClassPath,loadTestNG">
<mkdir dir="${build.dir}"/>
<javac destdir="${build.dir}" srcdir="${src.dir}">
<classpath refid="classpath_jars"/>
</javac>
</target>
<target name="test" depends="compile">
<testng classpath="${test.classpath};${build.dir}">
<xmlfileset dir="${basedir}" includes="testng.xml"/>
</testng>
</target>
</project>
- Add reference to the code:
import com.jprotractor.NgBy;
import com.jprotractor.NgWebDriver;
import com.jprotractor.NgWebElement;
Related Projects
- Protractor-jvm
- ngWebDriver
- angular/protractor
- bbaia/protractor-net
- sergueik/protractor-net
- symonk/automation-framework-java-angular-cucumber
- sergueik/jprotractor_cucumber
- henrrich/jpagefactory
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
- curious Case of Protractor and Page Synchronization blog
- older post on deprecation ana soon future removal of the WebDriverJS promise manager from SelnoumHQ
- post on Protractor end of life or support what next Future of Angular E2E
- issue on SeleniumHQ on future of Angular E2E & Plans for Protractor