开发 Protractor 测试(C# 或 Java)
本文将解释 Angular JS Protractor C# 和 Java 客户端库的示例。
引言
正如其口号自豪地宣称的那样, AngularJS 如果是为构建 Web 应用程序而设计的话,它将是 HTML 的样子。AngularJS 从一开始就被设计成可测试的。
AngularJS 的原生测试框架 Protractor 也是用 JavaScript 编写的。许多 Selenium 开发人员在切换到测试 AngularJS MVC Web 应用程序时,希望继续使用他们现有的 Java 或 C# 代码库和技能。
背景
将Protractor 移植到其他客户端语言非常容易——它使用浏览器支持的 JsonWire Protocol 的一小部分,即只有一个接口。Protractor 本身没有核心 Selenium 中大量可用的特定 Wait Conditions 变体,但正如我们在下面看到的,Protracor 的NgBy
类方法返回有效的OpenQA.Selenium.By
实例,允许使用 C# 和 Java 实现中已有的IWait<T>接口来处理服务负载延迟。另一方面,即使这些通常也不需要——所有NgWebElement
的交互都涉及在浏览器中运行以下 JavaScript 代码片段。
var rootSelector = arguments[0]; var callback = arguments[1]; if (window.getAngularTestability) { window.getAngularTestability(el).whenStable(callback); return; }
或
var el = document.querySelector(arguments[0]); var callback = arguments[1]; angular.element(el).injector().get('$browser').notifyWhenNoOutstandingRequests(callback);
这两者都会阻止测试客户端执行,直到视图完成更新。它从所有“核心”方法中调用,例如。
public bool Displayed
{
get {
this.ngDriver.WaitForAngular();
return this.element.Displayed;
}
}
这允许被测站点和测试场景之间的同步——可以说Protractor不是测试页面看起来怎么样,而是测试它应该怎么样。
Protractor (及其 C# 包装器) 提供了一套丰富的 Angular 支持的定位器,其中一些在纯 Selenium 中很难甚至不可能模拟。
评估
FindBindings
FindCssContainingText
FindModel
FindSelectedOption
FindSelectedRepeaterOption
FindByButtonText
FindByPartialButtonText
FindByOptions
FindAllRepeaterRows
FindRepeaterColumn
FindRepeaterElement
SetLocation
所有这些实际上都通过以下单个 Selenium 调用实现。
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;
}
}
}
JavaScript 代码几乎 verbatim 地取自 https://github.com/angular/protractor/blob/master/lib/clientsidescripts.js,因此人们可以随时保持代码的更新。例如。
/**
* 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);
它被嵌入在 C# 源代码中作为一个静态字符串。
Protractor 还提供了重要的 Angular 特有的Wait
方法。
WaitForAngular
WaitForAllAngular2
TestForAngular
这些也通过ExecuteAsyncScript()
实现。最后,它更进一步,提供了自定义的NgWebDriver
、NgNavigation
和NgWebElement
类,以最大限度地减少在Protractor和包装的 Selenium 之间切换的需要。
代码
我们检查了一个小型测试应用程序,该应用程序执行一些典型的客户操作,这些操作在 http://www.way2automation.com 提供的演示 Angular 应用程序中(以及许多其他有用的资源)进行。我们将测试客户访问XYZ Bank并向其账户存款。
注意 - 主页上显示的登录弹出窗口 http://way2automation.com/way2auto_jquery/index.php 是可选的,可以通过在XYZ Bank页面 http://www.way2automation.com/angularjs-protractor/banking
上开始测试执行来忽略(提供登录数据的方法包含在附件的 zip 文件中)。
相关代码登录为特定的XYZ Bank客户,选择一个账户,记录余额,进行存款,并在交易完成后确认余额变化。该示例不演示任何 Page Object 技术,重点是原始的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"));
// 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);
}
通过 http://www.online-convert.com/
转换为 gif
未显示取款操作,代码可在附件 zip 文件中找到。
接下来我们剖析上面的代码。
与纯 Selenium 相比,定位器非常简洁,例如。
// 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')]"));
我们甚至不看纯 Selenium 中例如在Customer Login视图中查找客户的等效代码。
ReadOnlyCollection<ngwebelement> ng_customers = ngDriver.FindElement(NgBy.Model("custId")).FindElements(NgBy.Repeater("cust in Customers"));
注意,要从上面的列表中选择单个客户,C# 扩展方法非常方便。
// select customer to log in
ng_customers.First(cust => Regex.IsMatch(cust.Text, "Harry Potter")).Click();
例如,要确保 Customers 表的表排序正常工作,可以提取并比较客户列表的最后一个和第一个元素。
[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);
}
已添加常规帮助方法来处理从页面元素文本中提取的帐户信息。
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;
}
并高亮显示页面元素。
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);
}
另一个显著的区别是——在测试用例开发过程中,不是在浏览器中检查页面元素的 CSS 选择器和 XPath,而是检查页面视图部分,例如 http://www.way2automation.com/angularjs-protractor/banking/depositTx.html
。
<span class="error" ng-show="message" >{{message}}</span><br>
以及控制器 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.";
}
例如,检查它们来构建一个Binding
定位器和相关的message
内容。
var ng_message_element = ngDriver.FindElement(NgBy.Binding("message"));
StringAssert.Contains("Deposit Successful", ng_message_element.Text);
highlight(ng_message_element);
在某个步骤中,您可能会发现页面上有两个文本为Deposit
的按钮,因此需要切换到form
上下文来点击第二个按钮。为了完成这个操作,使用了一小段核心 Selenium 代码。
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)));
代码定位form
并从IWebElement
实例化NgWebElement
对象。
另一个需要切换到核心 Selenium 的情况是处理 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);
}
以及OpenQA.Selenium.Interactions.Actions
。
actions.MoveToElement(ng_button.WrappedElement).ClickAndHold().Build().Perform();
请注意,除了定位元素外,Protractor还允许您评估它们。
[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 UniversalSortableDateTimePattern 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());
}
}
源代码
作者的 protractor-net 项目比 Bruno Baia 的 upstream project 领先几个提交(PR 待合并),其中一些核心 JavaScript Find elements 方法已从参考的 Angular protractor project 中更新。
NgBy.RepeaterColumn
方法 - 在ShouldListTransactions
测试中说明。NgBy.ButtonText
和NgBy.PartialButtonText
,在几乎所有测试中都有练习。
以及 Extensions 类。
以下测试可以在附件的 zip 文件中找到。
- C#
-
ShouldAddCustomer
ShouldDeleteCustomer
ShouldDeposit
ShouldEvaluateTransactionDetails
ShouldListTransactions
ShouldLoginCustomer
ShouldOpenAccount
ShouldSortCustomersAccounts
ShouldWithdraw
Protractor Java 客户端
Protractor Java 绑定项目最初是 Carlos Alexandro Becker 的 jProtractor 的一个分支。后者项目似乎不太成熟——它最初只支持。
binding.js
buttonText.js
model.js
options.js
repeater.js
通过By
类扩展,但缺少waitForAngular
调用器。后来, Aaron Van Prooyen 的 Protractor-jvm 项目中的代码被合并到 jProtractor 中。方法被实现为。
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
并进行了测试。
- Java (桌面)
-
testAddCustomer
testCustomerLogin
testDepositAndWithdraw
testEvaluateTransactionDetails
testListTransactions
testOpenAccount
testSortCustomerAccounts
- Java (travis)
-
testAddition
testChangeSelectedtOption
testEvaluate
testFindElementByOptions
testFindElementByRepeaterColumn
testFindElementByRepeaterWithBeginEnd
testFindSelectedtOption
有大量的Local Page
测试探索了核心 Angular、AngularUI 和通用 Angular 指令的各个方面。
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()
(Java 和 C# 均有)。核心 Angular 页面资源已提交到项目Samples
和src/test/resources
目录中。
提供了演示jProtractor.jar
用法的示例和 Maven 项目。Cucumber 转换正在进行中。
调用链的骨架如下所示。
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);
}
希望 .Net 和 Java 实现之间的类比是清晰的。已开发的 .Net 测试作为 Java 测试的蓝图。
在附件的 zip 文件中,您还将找到在 Java 和 C# 中实现的 http://www.java2s.com 演示页面的各种测试。
单个测试看起来与 .Net 非常相似。
@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 测试可以轻松地构建为 Page Objects 和 Java 中的 Cucumber 步骤定义。
@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
}
或 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")));
}
随着项目的演进,.Net 客户端的测试用例和Protractor方法正在被移植到 Java 客户端——这基本上就是您在附件 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");
// 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);
}
对于桌面浏览器测试,启动 Selenium 节点和中心(例如,在本地端口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);
}
您可以在节点控制台中检查脚本执行。
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: /**
用于 CI 构建。
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);
}
发布历史
- 2015-12-24 - 初始版本
- 2015-12-26 - 添加了源代码下载链接。
- 2015-12-27 - 更新了 protractor-net 和 protractor-java 下载,提供了上游项目信息。
- 2015-12-28 - 将 protractor-java 与 Protractor-jvm 合并。更新了 protractor-net 和 protractor-java 下载 - 修复了链接。
- 2015-12-31 - 提供了更多 Java 和 .Net 测试示例。更新了 protractor-java 和 protractor-net 下载。纠正了一些关于 Protractor 与核心 Selenium 集成的陈述。
- 2016-01-01 - 完成了一些示例。
- 2016-01-02 - 添加了示例,更新了下载。
- 2016-01-07 - 已合并到 jProtractor 的 Pull Request - 更新了示例、下载。
- 2016-02-02 - 添加了示例,更新了下载。
- 2016-02-10 - 在下载 zip 中包含了测试项目。
- 2016-05-29 - 更新了下载。