65.9K
CodeProject 正在变化。 阅读更多。
Home

开发 Protractor 测试(C# 或 Java)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (14投票s)

2015 年 12 月 25 日

CPOL

6分钟阅读

viewsIcon

95465

downloadIcon

2018

本文将解释 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()实现。最后,它更进一步,提供了自定义的NgWebDriverNgNavigationNgWebElement类,以最大限度地减少在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 文件中)。

Login page

相关代码登录为特定的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);
      }

Protractor .Net test execution

通过 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 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());
      }
  }

源代码

作者的 protractor-net 项目比 Bruno Baia 的 upstream project 领先几个提交(PR 待合并),其中一些核心 JavaScript Find elements 方法已从参考的 Angular protractor project 中更新。

  • NgBy.RepeaterColumn 方法 - 在 ShouldListTransactions 测试中说明。
  • NgBy.ButtonTextNgBy.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 页面资源已提交到项目Samplessrc/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 演示页面的各种测试。

Protractor Java test execution

 

单个测试看起来与 .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);
}

Login page

发布历史

  • 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 - 更新了下载。
© . All rights reserved.