Введение
Как лозунг на Angular.org гордостью объясняет:
Angular is what HTML would have been, had it been designed for applications , что в вольном переводе звучит так: Angular является тем, чем был бы HTML — если бы он с самого начала был предназначен для создания (веб -) приложений. AngularJS был разработан с нуля, чтобы быть тестируемым. Но многие разработчиков Selenium хотят продолжать использовать свои существующие Java или C # кодовую базу и навыки но обнаруживают при переключении на тестирование AngularJS SPA и MVVM веб-приложений, что Protractor, лидирующий инструмент тестирования приложегий AngularJS, написан на JavaScript тоже.
К счастью, Protractor довольно легко портируется на другие языки — он использует небольшое подмножество протокола JsonWire на котором основан Selenium WebDriver, а именно всего один интерфейс.
За короткое время был дополнен и развит проект protractor-net представляющий порт существующих методов Protractor https://github.com/angular/protractor/blob/master/lib/clientsidescripts.js из Javascript на C# и затем другой проект, выполняющий ту же задачу из Java.
Для тестирования был выбран сайт http://www.way2automation.com на котором среди прочего есть и проект для AngularJS,
http://www.way2automation.com/angularjs-protractor/banking.
тесты представляют собой «стандарные» действия «клиента» и «менеджера» банка «XYZ Bank» по проверке баланса, созданию учетных записей, проведения платежей и т.п. — это позволило проиллюстрировать все имеющиеся методы. Вызов тестов осуществлен из проекта на C# и из Java
Примеры кода
C#
«Клиент» заходит, выбирает счет, заносит сумму, и когда транзакция прошла, проверяет баланс (есть и тест на съем средств — тут он не показан, смотрите архив).
[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); }
Java
«Клиент» заходит, выбирает счет, смотрит транзакции, умеет найти записи «Credit».
@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); } } }
Для интерактивного тестирования, стоит запустить 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); }
Для билда используем
@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); }
Полный список тестов на 01.01.2016:
ShouldAddCustomer
ShouldDeleteCustomer
ShouldDeposit
ShouldListTransactions
ShouldLoginCustomer
ShouldOpenAccount
ShouldSortCustomersAccounts
ShouldWithdraw
Это на C# — на Jave тестов пока меньше, но остаток — вопрос времени
Статья (гораздо более подробная версия) также опубликована мною на Code Project, туда же периодически загружаются наиболее свежие архивы проектов. Оба проекта на гитхабе полностью рабочие, коммиты практически каждый день.
Автор: sergueik