суббота, 28 апреля 2012 г.

Заставляем браузер ждать пока существует определенный элемент.

Для реализации такой функции в наших тестах нам потребуется класс WebDriverWait и  класс Predicate.

Пишем следующую функцию:

public void waitUntilElementPresent(final String xpath){
        new WebDriverWait(this.driver, 10).until(new Predicate<WebDriver>() {
            public boolean apply(WebDriver arg0) {
                return !arg0.findElement(By.xpath(xpath)).isDisplayed();
            }
        });
    }

При вызове этой функции браузер будет ждать до тех пор пока элемент с xpath, который мы передали, существует.

PS: Небольшая поправка, в данном случае браузер будет ждать 10 секунд.

пятница, 20 апреля 2012 г.

Отделяем данные от тестов с помощью сериализации

Если вы хотите написать тест не зависящий от данных, или вам при тестировании приходится часто менять тестовые данные - эта статья для вас.

1. В чем заключается проблема ?
Тестовые данные могут изменяться и жестко заносить их в код тестов не самая лучшая идея. Гораздо лучшей идеей было -бы хранить эти данные отдельно и предоставить к ним доступ всем необходимым тестам.

2. Как этого добиться ?
Я усматриваю несколько путей к реализации такого доступа.
1) Хранить данные в БД
2) Properties
3) Сериализация

Я не буду рассматривать все плюсы и минуса каждого подхода, а конкретно поговорю о последнем.

Сериализация - процесс перевода какой-либо структуры данных в последовательность битов. Обратной к операции сериализации является операция десериализации — восстановление начального состояния структуры данных из битовой последовательности.

Выглядит вполне интересно... Но обычная сериализация переводит обьект в совсем уж неудобоваримый формат, который править руками нет никакой возможности.

Есть решение этого. Сериализация в XML файл. В итоге такой сериализации мы получим на выходе читабельный и редактируемый файл.

 Рассмотрим пример. Напишем класс который хранит в себе логин и пароль пользователя.

public class LoginInfo{
  String login = "name";
  String password = "pass";
}

В классе всего 2 строковых поля. Как данный класс будет выглядеть в сериализованном виде ?
Давайте посмотрим.
Для сериализации я использую библиотеку XStream.

LoginInfo info= new LoginInfo();
System.out.println(new XStream().toXml(info));

Выполнение этих двух строчек кода выведет нам в консоль примерно следующее

<LoginInfo>
 <login>name</login>
 <password>pass</password>
</LoginInfo>

сохранив эту информация в xml файл мы можем восстановить объект оттуда.

LoginInfo info2= (LoginInfo) new XStream().fromXml(new FileInputStream("file.xml"));

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

пример теста использующего этот подход:

LoginInfo info=null;

@BeforeMethod
public void before(){
 info=(LoginInfo) new XStream().fromXml(new FileInputStream("file.xml"));
}

@Test
public void test(){
 FirefosDriver firefox = new FirefoxDriver();
firefox.findElement(By...).sendKeys(info.login);
firefox.findElement(By...).sendKeys(info.password);
}



 

вторник, 17 апреля 2012 г.

Строим Web Testing Framework за 20 минут

На первой онлайн-конференции ConfeT&QA 2011 Андрей Дзыня покорил сердца слушателей тем, что не побоялся показывать вживую, "в прямом эфире", как создаются тесты -- от первых шагов (запись действий пользователя в рекордере), через все этапы построения фреймворка с гибкой архитектурой, до запуска тестов в системе непрерывной интеграции. И всё это за каких-то 20 минут! Мы предлагаем вам самим посмотреть, как это происходило. И, кстати, для построения тестов использовался замечательный инструмент Selenium, так что рекомендуем посмотреть эту запись также тем, кто начинает осваивать этот инструмент.


понедельник, 16 апреля 2012 г.

Запись выполнения тестов

При выполнении тестов может возникнуть такая неприятная ситуация, тест как будто бы провален, но нет ясности о причинах провала теста.

В такой ситуации нам бы очень помогла запись того как исполнялся тест.

Слава богу есть такая возможность )))))

Нам следует обратиться вот к этому проекту.

Собственно весь проект нам не нужен. Скачиваем файлы screen-recorder-r2.2.jar и screen-player-r2.2.jar.

Добавляем их в build path нашего проекта с тестами.

Создаем в проекте класс Recorder.

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

import org.one.stone.soup.screen.recorder.DesktopScreenRecorder;
import org.one.stone.soup.screen.recorder.ScreenRecorder;
import org.one.stone.soup.screen.recorder.ScreenRecorderListener;

public class Recorder implements ScreenRecorderListener {
    private ScreenRecorder recorder;

    public void start(String filename) throws FileNotFoundException {
        FileOutputStream oStream = new FileOutputStream(filename);
        recorder = new DesktopScreenRecorder(oStream, this);
        recorder.startRecording();
    }

    public void stop() {
        recorder.stopRecording();
    }

    public void frameRecorded(boolean fullFrame) throws IOException {
       
    }

    public void recordingStopped() {
       
    }

}

Данный класс при вызове метода start() будет записывать все что происходит на рабочем столе, до момента пока мы не вызовем stop().

Все это очень хорошо укладывается в тесты при использовании TestNG и его методов @AfterMethod и @BeforeMethod

После того как тест прошел и файл с записью создан мы можем проиграть его с помощью проигрывателя

java- -jar screen-player.jar (file-name)




пятница, 13 апреля 2012 г.

BDD и Java

Недавно я загорелся идеей еще более дистанцироваться от правки кода авто тестов и собственно написания самих тестов (набивания кода).

Захотелось отойти от самого кода и описывать все в виде простых фраз и предложений.
Для этих целей я решил обратиться к BDD.

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

Мой взор обратился на JBehave.

Вот тут их официальный сайт.
Статья на сайте IBM.

Как только появятся реальные результаты применения этого подхода вместе с Selenium обязательно напишу статью.

четверг, 12 апреля 2012 г.

Релиз Selenium 2.21

Состоялся релиз Selenium 2.21

Вот официальный changelog по изменениям

v2.21.0
=======

WebDriver:
  * Safaridriver has now been added !
  * Issue 3489, maximize the browser window
  * Significant stability improvements in firefoxdriver.
  * Issue 3357 log-path for chromedriver
  * IE driver issue 3360
  * Android driver issue 3504
  * Firefox issue 3559, max-connections-per-server
  * Issue 3520
  * Issue 2826
  * Native events support for Firefox 11
  * Dropped native events support for Firefoxes 4-9

Grid:
  * Significant changes in the timeout strategy0 between hub, nodes and browser.
    See http://code.google.com/p/selenium/wiki/Grid2 section on timeouts.
    Compatibility note: It is recommended to at least update the nodes
    to 2.21. Upgrading only the hub will effectively disable the 
    browser-death timeout and is *not* recommended.
  * Improved error messages on console
  * Content length issue solved, Issue 2362
  * Issues 3328,3366
  * Improvements in thread dumps in both hub and node,
    possible to corrolate the two.
  * Added hub api to query parameters

Server:
  * -browserTimeout <timeoutInSeconds> added to
    differentiate between browser hang and client gone (which is
    -timeout <timeout>)
    - System property -Dselenium.server.timeout removed, use -timeout instead.
    See http://code.google.com/p/selenium/wiki/RemoteWebDriverServer 
    for further details.
    * Issue 3577
 

вторник, 10 апреля 2012 г.

WebDriverJS

Решил я значит посмотреть на javascript версию WebDriver. Так для своего интереса.

Нет, лукавлю, не для интереса. Была надежда на то что скрипт можно будет запускать прямо из браузера для автоматизации внутри этого же браузера !
Моим надеждам не суждено было оправдаться.....

Кратко расскажу почему.

1. Компиляция WebDriverJs.

Следую инструкции отсюда начал компиляцию, в процессе которой вылезла ошибка недостаточного heap для явы.
Для ее исправления правим файл go.bat
Полученная строка будет выглядеть так:

java -Xmx512m -XX:MaxPermSize=284m -XX:ReservedCodeCacheSize=128m -client -jar third_party\jruby\jruby-complete.jar -X-C -S rake %*

Теперь запуск команды

>go webdriverjs

проходит успешно. На выходе в папке build мы получаем папку с webdriver.js файлом.
Этот файл и есть нужная нам библиотека.

2.Использование

Первые попытки запуска примеров потерпели неудачу. После недельных изысканий и выноса мозга свои коллегам было найдено наконец решение.

В файле библиотеки необходимо заменить последнюю строку на следующую:

;for(var key in webdriver)this[key]=webdriver[key];delete this.webdriver;}).call(typeof exports!=='undefined'&&exports===this?exports:this.webdriver=this.webdriver||{})

После этого все примеры начали работать.

3.Разочарование

Больше всего надежд я возлагал на метод использования описанный тут

Controlling the Host Browser

Launching a browser to run a WebDriver test against another browser is a tad redundant (compared to simply using node). Instead, using WebDriverJS in the browser is intended for automating the browser actually running the script. This can be accomplished as long as the URL for the server and session ID for the browser are known. While these values may be passed to the builder directly, they may also be defined using the wdurl and wdsid "environment variables", which are parsed from the loading page's URL query data:
<!-- Assuming HTML URL is /test.html?wdurl=http://localhost:4444/wd/hub&wdsid=foo1234 -->
<!DOCTYPE html>
<script src="webdriver.js"></script>
<input id="input" type="text"/>
<script>
  // Attaches to the server and session controlling this browser.
  var driver = new webdriver.Builder().build();

  var input = driver.findElement(webdriver.By.tagName('input'));
  input.sendKeys('foo bar baz').then(function() {
    assertEquals('foo bar baz',
        document.getElementById('input').value);
  });
</script>
 
Казалось бы все круто... Мы открываем браузер и нашу страницу test.html и тест 
сразу же начинает выполняться...
Но не тут то было!!!
 
Для начала нам необходимо передать параметры хоста где запущен сервер и сессию для
которой все это делать 
то есть результирующий урл запуска будет:  
 
/test.html?wdurl=http://localhost:4444/wd/hub&wdsid=foo1234 
 
Выяснилась еще одна проблема, как создать сессию ... так как сам новую сессию он не
 включает. ((((
Ну хорошо, для создания сессии идем на хост где запущен сервер 
 http://localhost:4444/wd/hub/static/resource/hub.html
нажимаем Create Session, выбираем браузер... 
У нас запускается браузер и мы видим сессию в которой он запущен

Отлично берем сессию и вставляем его как параметр нашей страницы
 
результирующий урл запуска будет: 
 
 /test.html?wdurl=http://localhost:4444/wd/hub&wdsid=1334043676191  
 
или что-то типа того.
 
Снова запускаем наш тест... и.... опять провал !!!! почему ? 
да потому что тест наш надо запускать в браузере запущенном самим сервером, так 
как в нем установлено дополнение которое взаимодействует с сервером.
 
Хорошо, запустим там.... вуаля ... все сработало как надо ...
 
Но это не то что я искал, я надеялся что сервер в этом процессе участвовать не 
будет и браузер где мы запускаем тесты может быть на машине клиента... А так он 
все равно на стороне сервера.

Разочарование ((((
 
 

 
   

вторник, 3 апреля 2012 г.

Что ждет нас в новом Selenium 2.21

* IE 6 support will be terminated as of 2.21
 * 2.21 will be the last version to support firefox versions below v10. All
 the major linux versions are releasing new LTS versions this month, most of
 which will be based on v10.
 * Selenium RC support will finally be abandoned, as has been announced since
 v2.0. For 2.21 this will include grid/selenium-server, the browsers will
 follow suite for v2.22/2.23. This decision was based on maven central
 download statistics showing less than 5% remaining rc usage.
 * Internally the project will go back to using maven as its primary build
 system. This is because most of the original authors of the current build
 solution have realized it was more crazy than fun. Additionally most of them
 get enough craziness with the recent baby boom in the core development team.
 The revert back to maven will happen this evening.

Использование рефлексии при написании тестов на Selenium

Сегодня я расскажу об использовании рефлексии языка при написании тестов.

Что это и для чего нужно ?

Рефлексия - процесс, во время которого программа может отслеживать и модифицировать собственную структуру и поведение во время выполнения.

Это бывает необходимо если вы хотите создать достаточно гибкую систему тестов, которая будет легко подвергаться настройке и изменения без внесения изменений в код.

Пример 

Предположим вы написали некий класс, описывающий веб-страницу с использованием Selenium PageFactory
В ходе выполнения тестов выяснилось что местоположение и идентификаторы элементов за аннотированных как @FindBy могут часто меняться. 
Как тогда быть ? 
Постоянно переписывать все аннотации ? 
Или вынести все аннотации по внешний файл с настройками и менять их там без перекомпиляции кода ?

Последний вариант на мой взгляд наиболее приемлем.

 Реализация

Для реализации нашего варианта нам и потребуется рефлексия. 

Будем использовать библиотеку Javassist.
Ее использование дает нам самые широкие возможности по изменению кода в рантайме. Вплоть до изменения аннотаций !!!

пусть у нас в некоем классе описан элемент:

@FindBy(how = How.ID, using = "element_id")
    private WebElement element;

мы хотим в рантайме менять аттрибуты how и using у аннотации.
значения using будем подгружать из файла настроек.

Для этого напишем класс-адаптер:

public class InterfaceAdaptor {

public static Class adapt(String classname) {
        Properties props=new Properties();
        props.load(new FileInputStream(new File(classname+".props"))); //загружаем файл с настройками
        ClassPool pool = ClassPool.getDefault(); // получаем пулл классов

        CtClass cc = pool.getCtClass(classname); // получаем наш класс из пула
        CtField[] fields=cc.getDeclaredFields(); // получаем список всех полей
        ClassFile ccFile = cc.getClassFile(); // получаем сам класс-файл
        ConstPool constpool = ccFile.getConstPool();
        for(CtField f:fields){
            if(props.containsKey(f.getName())){ 
                // если в пропертис находим значение соответствующее имени поля
                // устанавливаем для этого поля аннотацию с нужными нам параметрами
                //новая аннотация перезаписывает старую
                 AnnotationsAttribute attr = new AnnotationsAttribute(constpool, AnnotationsAttribute.visibleTag);
                 Annotation annot = new Annotation("org.openqa.selenium.support.FindBy", constpool);
                 EnumMemberValue e=new EnumMemberValue(ccFile.getConstPool());
                 e.setType("org.openqa.selenium.support.How");
                 e.setValue("XPATH");
                 annot.addMemberValue("how",e );
                 annot.addMemberValue("using", new      StringMemberValue(props.getProperty(f.getName()),ccFile.getConstPool()));
               
                 attr.addAnnotation(annot); //добавляем аннотация
                 f.getFieldInfo().addAttribute(attr); //добавляем аттрибуты
                //
            }
        }
        return cc.toClass(); // возвращаем новый исправленный класс
    }
}

После того как мы получили класс мы можем опять таки через рефлексию получить его инстанс.
InterfaceAdaptor.adapt("org.your.ClassName").getConstructors()[0].newInstance();