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.:
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:
public override ReadOnlyCollection<IWebElement> FindElements(ISearchContext context)
{
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,
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)
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:
[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"));
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();
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();
var ng_message_element = ngDriver.FindElement(NgBy.Binding("message"));
StringAssert.Contains("Deposit Successful", ng_message_element.Text);
ngDriver.Highlight(ng_message_element);
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);
}
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:
NgWebElement ng_customer_login_button_element = ngDriver.FindElement(NgBy.ButtonText("Customer Login"));
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
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:
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:
[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")));
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();
ReadOnlyCollection<ngwebelement> ng_accounts = ngDriver.FindElements(NgBy.Repeater("cust in Customers"));
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();
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:
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
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
<span class="error" ng-show="message" >{{message}}</span><br>
and controller http://www.way2automation.com/angularjs-protractor/banking/depositController.js
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:
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:
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:
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)
{
verificationErrors.Append(ex.StackTrace);
}
catch (WebDriverException ex)
{
verificationErrors.Append(ex.StackTrace);
}
and OpenQA.Selenium.Interactions.Actions
actions.MoveToElement(ng_button.WrappedElement).ClickAndHold().Build().Perform();
Note that in addition to locating elements, Protractor allows one to evaluate them:
[Test]
public void ShouldEvaluateTransactionDetails()
{
ngDriver.FindElement(NgBy.ButtonText("Customer Login")).Click();
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();
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.Until(ExpectedConditions.ElementExists(NgBy.Repeater("tx in transactions")));
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());
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:
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");
public class NgBy
{
private NgBy() { }
public static By partialButtonText(String text){
return new JavaScriptBy(ClientSideScripts.PartialButtonText, text);
}
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#.
Individual tests look very similar to .Net:
@Test
public void testListTransactions() throws Exception {
ngDriver.findElement(NgBy.buttonText("Customer Login")).click();
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();
}
}
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:
@Test
public void testSelectOneByOne() throws Exception {
String baseUrl = "http://amitava82.github.io/angular-multiselect/";
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();
List <webelement> cars = ng_directive.findElements(NgBy.repeater("i in items"));
int count = 0;
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();
}
cars = ng_directive.findElements(NgBy.repeater("i in items"));
assertThat(cars.size(), equalTo(count));
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());
}
or C#
[Test]
public void ShouldSelectAll()
{
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();
_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();
ReadOnlyCollection<ngwebelement> cars = ng_directive_selector.FindElements(NgBy.Repeater("i in items"));
Assert.AreEqual(cars.Count(), cars.Count(car => (Boolean) car.Evaluate("i.checked")));
}
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.
@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");
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) );
}
alert.accept();
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);
NgWebElement deleteCustomerButton = new NgWebElement(ngDriver, currentCustomer).findElement(NgBy.buttonText("Delete"));
assertThat(deleteCustomerButton, notNullValue());
assertThat(deleteCustomerButton.getText(),containsString("Delete"));
highlight(deleteCustomerButton,300);
actions.moveToElement(deleteCustomerButton.getWrappedElement()).clickAndHold().build().perform();
Thread.sleep(100);
actions.release().build().perform();
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
)
@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:
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
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);
}
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.