Pull to refresh

Comments 29

Насколько я понимаю вы пишете под WD. Как вы используете разные браузеры: создаёте selenium grid или в коде отдельно для каждого браузера прописываете capabilities?
Вы учитываете особенности работы реализаций драйверов? Нам в своё время пришлось дописать обертку, для того, что бы код выполнялся одинаково во всех браузерах. Так же были случаи, когда js код добавлял атрибут к элементу дерева, после чего селениум проверял этот атрибут быстрее, чем браузер применял изменения и множество других проблем. В общем подавляющее большинство проблем у нас было с Actions и подобными вещами. Интересно послушать, как вы с этим боролись, заранее спасибо.
>>Насколько я понимаю вы пишете под WD.
Что такое WD?

>> Как вы используете разные браузеры: создаёте selenium grid или в коде отдельно для каждого браузера прописываете capabilities?
Для базового класса теста указан атрибут TestFixture, который передается в конструктор теста. На основе значения этого атрибута создаются различные драйвера. Например, если надо запустить тест под Chrome и IE — надо базовый класс тестов пометить вот так:

[TestFixture(«Chrome»)]
[TestFixture(«IE»)]
public class BaseTest {...}

Подробнее можно посмотреть в коде. Файл BaseTest.cs и BrowserFactory.cs.

>> Вы учитываете особенности работы реализаций драйверов?
Нет. Проверяем стандартно на IE и Chrome. Данных проверок в контексте текущего проекта достаточно.

>> Так же были случаи, когда js код добавлял атрибут к элементу дерева, после чего селениум проверял этот атрибут быстрее, чем браузер применял изменения и множество других проблем.
Интересно. Ситуация видимо не типовая, соответственно требует отдельного решения.

>> В общем подавляющее большинство проблем у нас было с Actions и подобными вещами. Интересно послушать, как вы с этим боролись, заранее спасибо.
Честно говоря, пока с такими проблемами не сталкивались.
Что такое WD?

WD это webdriver. Но по коду видно что вы всё запускаете локально, это значит у вас агент teamcity висит интерактивно? Почему не используете grid? Раз всю инфраструктуру у вас разворачивает teamcity, то при написании и отладке тестов всё это проделывается вручную?
И да, исправьте кодировку
image
Да, запускается все локально при помощи агента teamcity. В сторону grid честно говоря не смотрели.

>> при написании и отладке тестов всё это проделывается вручную?
разработчик и тестировщик отлаживают тесты локально, после чего заливают в репозиторий. Сразу после заливки в репозиторий — запускается агент teamcity.
Локально запускается агент teamcity? Хардкорно :)
Видимо не правильно выразился :)
Есть физическая машина, на который установлен агент. Т.е. запуск идет на удаленной машине, не на машине разработчика.
Мы друг друга не понимаем наверно. Давайте так. Чтобы написать тест нужно поднять всю обвязку для нормальной работы теста, т.е Поднять приложение, поднять базу и прочее и прочее. Это делается вручную каждый кто хочет написать/поправить тест? Так же, раз уж используете шаги, то почему не выбрали SpecFlow? Мы используем его для написание сценариев.
>> Это делается вручную каждый кто хочет написать/поправить тест?

Да. Вся обвязка поднята на машине разработчика локально. Для создания теста разработчик запускает систему у себя на машине и пишет тест.

Так же есть виртуальная машина с тестовой площадкой, где поднято все необходимое окружение.
Для написания и отладки приемочного теста возможно использовать данную площадку (за исключением сценариев, когда тест «ломает площадку»).

Насчет SpecFlow — пока руки не дошли попробовать этот фреймворк в работе.
Прошу прощения за столь поздний ответ.
>> Нет. Проверяем стандартно на IE и Chrome. Данных проверок в контексте текущего проекта достаточно.
Как показывает мой опыт, наиболее интересные баги воспроизводятся на FF, т.к. V8 у хрома практически не имеет недостатков, то тесты на хром можно мысленно приравнять к тестам проверки логики, а тесты на IE, FF — к тестам на проверку UI. (но это очень формально). Касательно реализаций: далеко не все браузеры умеют делать скриншоты, и capability у них очень сильно отличаются. Например известный баг, что FF на executeScript не может возвращать jQuery объект. И таких особенностей действительно много. Если вы хотите писать действительно кросбраузерные тесты, то допишите еще одну обертку над WD, которая будет учитывать ajax, переходы по фреймам, обрабатывать неподдерживаемые(или некорректно выполняемые) браузером действия. Потому что, когда ваш проект разрастется будет очень тяжело это всё рефакторить.

В любом случае успехов в вашем начинании, selenium не так прост, как кажется, но если его понять — он станет хорошим союзником в CI.
Спасибо за доброе слово!

Обертку для конкретного драйвера, думаю будем создавать по мере возникновения проблем. Обертка по сути будет полноценным Proxy классом над обычным драйвером. Мысль хорошая.
— кнопку переложили в другой div и 50 тестов отлетело?
— БД, как я понял, создается одна — т.е. следующие тесты могут быть затронуты изменениями в БД, сделанными предыдущими тестами?
— если уж Angular — то вроде можно контроллеры юнит-тестить, используете?
>> — кнопку переложили в другой div и 50 тестов отлетело?
Классический пример :) ничего не отлетит, т.к. кнопки выбираются при помощи css селектора по id. Тест надо будет править, если поменялся id у кнопки. Даже не сам тест, а описание страницы PageObject.

>>— БД, как я понял, создается одна — т.е. следующие тесты могут быть затронуты изменениями в БД, сделанными предыдущими тестами?
Все верно. Пока с необходимостью создавать новую БД при определенном тест-кейсе не сталкивались. Если потребуется, то добавим команду вызова скриптов на удаление/создание БД в базовый класс теста.

>>— если уж Angular — то вроде можно контроллеры юнит-тестить, используете?
Можно, но сейчас не используем. Тесты контроллеров это как вы правильно заметили unit-тесты :) Я же хочу указать на пользу именно приемочных тестов. В моей практике не раз было, что unit-тесты отрабатывают, а самые простые пользовательские сценарии сбоят.
>Классический пример :) ничего не отлетит, т.к. кнопки выбираются при помощи css селектора по id.
Не, ну про кнопку — это я немного утрировал. Часто ли вы UI меняете, как часто после этого приходится чинить тесты?

> Я же хочу указать на пользу именно приемочных тестов.
Как по мне — нужно использовать всё, что может принести пользу, но знать меру. Юнит-тесты проще писать и поддерживать. Автоматизированные тесты UI — сильно тяжелее.

Короче я бы не был настолько категоричен, что автотесты — это прям must have.
По факту Page Object файлы правятся не часто. Если все же правка необходима, то в основном все сводится к изменению селектора, что выполняется достаточно просто.

Насчет использования unit-тестов, согласен с вами — одно другое не исключает. Например, мы в работе используем как unit так и acceptance тесты.

Про категоричность и «must have» относительно приемочных тестов — тут каждый решает сам, нужно ли использовать в проекте те или иные тесты. Мою точку зрения вы поняли :) я за автоматизацию приемочных тестов, хотя бы их малой части, если нет ресурсов на выполнение такого рода задач в полном объеме.
Спасибо. Обязательно посмотрю.
У нас большинство разработчиков пишут на С#, поэтому Selenium с оберткой для .NET нам ближе.
Treshnikov, спасибо за то, что делитесь информацией, а особенно об использовании WebDriver с PageObject'ами.

Разрешите мне также поделится теми наблюдениями, чем мой подход при работе с WebDriver отличается от вашего:

Сразу скажу, что у вас хорошее решение со Step(“step step step”) ;)

1. Я бы сразу разделил код на 3 проекта: отдельно тесты (AcceptanceTesting.Tests), отдельно библиотека с PageObject (AcceptanceTesting.TestModel), отдельно другие библиотечные функции (AcceptanceTesting.Core). – к этому все равно приходят, когда тестов станет много и их захочится разделить на несколько тестовых наборов для запуска параллельно

2. Для себя я выработал правило: стараться не использовать вебэлементы и другие вызовы вебдрайвера непосредственно в тесте, а использовать только вызовы методов PageObject.
Т.е. сделать так, чтобы тесты не знали, что они работают с WebDriver.

Вот, например, этот код я бы оставил не измененным:

Step("Ввод запроса 'смс автоматизация'",
       () => googleSearchResultPage = googlePage.EnterSearchText("смс автоматизация"));


А вот этот бы заменил

Step("Первая ссылка должна содержать адрес sms-automation.ru",
() =>
{
      var href = googleSearchResultPage.FirstLink.GetAttribute("href");
      Assert.AreEqual(href, "http://www.sms-automation.ru/");
});


на

Step("Первая ссылка должна содержать адрес sms-automation.ru",
() =>
{
      var href = googleSearchResultPage.GetFirstLink();
      // или 
      // var href = googleSearchResultPage.GetAllLinks().First();
      Assert.AreEqual(href, "http://www.sms-automation.ru/");
});


Спасибо вам еще раз, за то, что делитесь знаниями и хорошими подходами!

Минутка самопиара:
А посмотрите ещё мой Page Recorder, который может ускорить процесс создания PageObject классов.
Ловите плюс в карму :)

Насчет разделения тестов и классов page-object на проекты — думаю это уже более косметический штрих, т.к. проекты будут знать друг о друге.
Вместо проектов мы группируем файлы по директориям внутри одного проекта. Не исключаю, что в итоге придем к вашему решению.

>> Для себя я выработал правило: стараться не использовать вебэлементы и другие вызовы вебдрайвера непосредственно в тесте, а использовать только вызовы методов PageObject.

Согласен с вами на все 100%. Примеры, которые заливал на github были написаны на «скорую руку». Боевые классы page-object инкапсулируют в себе знания о драйвере, а наружу выставляют только свойства и методы.

Page Recorder посмотрю, выглядит заманчиво.
А у Вас надобности в создании своего DSL (domain specific language) для тестов не было? Что бы абстрагироваться от кода и писать в бизнес терминах? Мы так делали. Очень удобно — программисты раз написали DSL (пишется очень легко — по сути у нас нужна была поддержка pageObject на уровне языка, что бы их не создавать в коде) и дальше QA пишут тестовый скрипт, который не содержит в себе никаких лишних строк — только бизнес действия (плюс в верху для всех тестовых скриптов одинаковая «шапка» импорта).
Звучит здорово! Хотелось бы взглянуть на пример.

Насчет DSL — немного смущает еще один уровень абстракции, о котором так же надо думать.
Работа с page object на текущем уровне тестирования для нас пока достаточна.

В комментарии выше мне уже указывали на SpecFlow. Складывается впечатление, что это уже немного другой уровень восприятия задачи тестирования как таковой и похоже мы до этого уровня пока не дошли :)
import pageobject.*
import pageobject.properties.*
import pageobject.search.*
import pageobject.selector.*
import pageobject.tabs.*

import static pageobject.PageObjectFactory.getPage

def authorLogin = "aaa"
def authorPassword = "bbb"
def firstApproverLogin = "ccc"
def firstApproverPassword = "ddd"
def secondApproverLogin = "eee"
def secondApproverPassword = "fff"
def firstApproverComment = "ggg"
def regNumberbudget = "kkk"

def sleepInterval = 20000

// Вход в систему под Автором
loginLogoutPage.login(authorLogin, authorPassword)
// Вызов меню Создать Договор бюджета
menuBarPage.pressFileCreateMenuItem(MenuBarPage.FileCreateMenuItem.CONTRACT)
// Заполнение обязательных полей карточки
//1. Добавление контента
contractPage.selectFile("C:\\Users\\lll\\Desktop\\Архивная справка_скан.PDF")
// Выбор бюджета
//1. Нажать кнопку выбор бюджета
contractPage.pressSelectBudget()
//2. Страница селектора бюджетов, Ввести значение в поле номер
selectorBudgetPage.inputRegNum(regNumberbudget)
//3. Нажать кнопку поиск
selectorBudgetPage.pressSearch()
//4. Добавить первый результат поиска к выбранным
selectorBudgetPage.clickFirstResult()
//5. Нажать кнопку выбрать
selectorBudgetPage.pressSelectButton()

Написано на Groovy.
Тут объекты loginLogoutPage, menuBarPage, contractPage и т.д. фактически являются PageObject и определены как часть языка (с помощью простого конфига для IDEA будут подсвечиваться в IDE с поддержкой autocomlite). Т.е. в скрипте их создавать не нужно. Из за того, что это свой DSL, со своими объектами языка, скрипт имеет своё расширение. (в конфиге для IDE как раз и прописываются правила обработки файлов этого расширения). Блок с импортами во всех скриптах одинаков и фактически является копипастой, но вынести в отдельный файл не получилось.
Работа с этим скриптов проста — сначала пишется test case, потом после каждой строчки test case пишется на DSL код. Фактически получается простое соотношение — строчка test case равна строчке на DSL. Ничего лишнего. Пример этого виден в скрипте — комментарии как раз и являются test case.
Я понял вашу идею — выглядит очень не плохо. При этом похоже на наш подход с единственным отличием — у вас страницы представлены статическими объектами и при написании тестов пользователь не думает о времени жизни страницы и пишет сценарий последовательно по шагам.

Соглашусь, что ваш код читается легче :)

protected override void Setup()
{
    dailyListPage = new LoginPage(Driver)
        .LoginAsAdmin()
        .OpenDailyList();
}

[Test]
public void DailyList_Test_Parameter_Calculation()
{
    var random = new Random();
    var v110 = random.Next(100);
    var v220 = random.Next(100);
    var gou = v110 + v220;

    Step("Блокировать документ на редактирование",
        () => dailyListPage.LockButon.Click());

    Step("Отредактировать параметр 110 и 220 за третий час",
        () =>
        {
            dailyListPage.PowerTable.EditCell(3, 2, v110.ToString());
            dailyListPage.PowerTable.EditCell(3, 3, v220.ToString());
        });

    Step("Сохранить изменения",
        () => dailyListPage.SaveButon.Click());

    Step("Измененные ячейки отмечены меткой \"есть изменения\",
        () => (dailyListPage.PowerTable.CellMarkedAsChanged(3, 2) && dailyListPage.PowerTable.CellMarkedAsChanged(3, 3)).ShouldBeTrue());

    Step("Разблокировать документ на редактирование",
        () => dailyListPage.LockButon.Click());

    Step("Проверка колонки ГОУ = 110 + 220",
        () => dailyListPage.PowerTable.Rows[3].Cells[1].Value.ShouldEqual(gou.ToString()));
}
При этом похоже на наш подход с единственным отличием — у вас страницы представлены статическими объектами и при написании тестов пользователь не думает о времени жизни страницы и пишет сценарий последовательно по шагам.

Да, у нас так получилось, что большинство PageObject не нужно было каждый раз заново создавать, но в паре мест, где PageObject должен знать в контексте какой странице он применён, приходилось определять эти PageObject через переменные, e.g.:
def menu = contractPage.getMenu()
Тоже сейчас делаю свой проект с Selenium + JavaMail. У меня не все так глобально, как у вас, но все же. Моя программа работает для автоматической регистрации на классы в моем университете :) Tracking почты на получение сообщения от университета, парсинг сообщения и запуск автоматической регистрации через Selenium, использую FireFox драйвер, т.к. с HtmlDriver'ом проблемы. Он не определает xss путь textField'а ("//*[@type='TEXT']") в режиме HtmlDriver, только в Firefox. Это осталось для меня загадкой. А так подкреплюсь вашим опытом, может что нибудь да оптимизирую у себя.

Так же подумываю перенести всю эту ахинею на сервер. Кажется видел, что у Selenium'а есть поддержка JavaScript
Заинтересовалась статьей из-за названия:
Selenium + .NET Web Api + AngularJs
А в итоге чуток Selenium упомянут, показана архитектура согласно модели PageObjects, а про то, как натравить webDriver на веб-приложение, написанное с использованием AngularJs — как искать WebElement-ы, например — не написано почти ничего.

Я верю, что у вас эта экспертиза есть. поделитесь, пожалуйста, светом знаний!

Например, у меня не получается узнать, какой чекбокс выбран. Чекбоксы выглядят вот так:
<span radio="Model.application.type" value="desired_key_word" class="ng-isolate-scope">
<span class="radio" style="cursor: pointer; background-position: 0px -57px;" data-styled="1"></span> 
<span class="radio-label" style="cursor: pointer; background-position: 0px -57px;" ng-transclude="">
<span class="ng-scope">Лейбл чекбокса, название опции</span></span></span>


desired_key_word — это value чекбокса, которое я хотела бы вытащить у того чекбокса, который выбран, и сравнить с ожидаемым.

Ломаю голову, как использовать CSSselector чтобы найти чекбокс в состоянии isChecked и узнать значение его value.
Пробую так:
var checkboxElement = _webDriver.FindElement(By.CssSelector("span[radio='Model.application.type']"));
var s = checkboxElement.GetAttribute("value");
Assert.AreEqual("desired_key_word", s);

и другие варианты селекторов пробовала, ну ни как не выходит. Все время ошибка «элемент не найден».

Я буду вам безмерно признательна за статью, которая на примерах покажет, как писать тесты с Selenium WebDriver для приложений, написанных с AngularJS!

А пока посмотрю примеры ваших тестов на гитхабе. Спасибо за них!
Разобралась со своим кодом. работает так:
var checkbox = _webDriver.FindElements(By.CssSelector("[radio='Model.application.type']"))[1];
var s = _webDriver.ExecuteJavaScript<string>("return angular.element(arguments[0]).scope().Model.application.type;", checkbox);
Assert.AreEqual("desired_key_word", s);


Посмотрела примеры ваши.
Нашла полезными следующие методы базового класса страницы:
public object ExecuteJavaScript(string javaScript, params object[] args)
{
var javaScriptExecutor = (IJavaScriptExecutor)Driver;
return javaScriptExecutor.ExecuteScript(javaScript, args);
}
public void ScrollToElement(IWebElement element)
{
ExecuteJavaScript("arguments[0].scrollIntoView(true);", element);
}
public void ScrollToTop()
{
ExecuteJavaScript("window.scrollTo(0,0);");
}


Копаюсь в направлении того, чтобы находить веб-элементы ява скриптом.

Коллеги подсказали, что AngularJS придумали как раз для увеличения тестируемости веб-приложений. Пока звучит сомнительно, надо почитать документацию к нему. А вы как думаете? Если есть преимущество приложения с AngularJS в плане тестирования, то в чем же оно?
Ох… только решил авторизоваться на хабре, увидел ваши комментарии.
Очень рад, что мои наработки вам пригодились!
Sign up to leave a comment.

Articles