17 февраля 2014 г.

Adobe Genie + Webdriver + TestNG (часть 1)

Доброго вам времени суток.
Это будет моя самая первая запись.

     Наверное всем QA инженерам известна проблема автоматизации flash-приложений. Копий здесь сломано немало, и так сложилось, что новые инструменты на этом поприще я рассматриваю с предубеждением: "или платный, или сложно установить, или неудобный".

     И вот пару недель назад я узнал про Automated UI Tester for Adobe ActionScript или если кратко - Genie. Как понятно из названия – это инструмент тестирования флеш - приложений. В этот раз предубеждения были лишними, Genie - весьма интересный инструмент. Его легко установить, он бесплатный и довольно удобный. Я не хочу повторяться в описаниях продукта и, если вы не знакомы с Genie, тогда, пожалуйста, почитайте вот эти замечательные статьи:

Или, например, мануал.
Там вы найдёте плюсы продукта, его установку и азы работы.
     А если уже прочитали, то давайте продолжим. Мы уже знаем, как запустить отдельный скрипт Genie. Но давайте представим, что мы тестируем онлайн-игру. Или несколько игр. Нам бы хотелось сначала открыть страницу с игрой, прокрутить скрипт, а потом как-то посмотреть, что флешка корректно взаимодействует со страницей. Тогда хорошо бы и Genie сервер запускать и останавливать автоматически. Да и вообще неплохо бы обернуть всё нами написанное в тесты под какими-нибудь jUnit или TestNG.      Давайте попробуем. Пусть логику флеша исполняет Genie, логику веб страницы поручим Webdriver’у, и всё это обернём в тесты под TestNG.

1. Автоматический запуск и остановка Genie сервера.
     Давайте изначально условимся, что наш проект лежит по вот такому пути: C:\Projects\Java\GenieTestNG. Чтобы запустить сервер Genie, нам достаточно запустить файл GenieSocketServer.jar. Этот файл входит в пакет файлов Genie (папочка GenieServer). Можно создать в нашем проекте папочку Libs и скопировать туда всю папку GenieServer. После этого добавим в проект командный файлик runServer.cmd. Он будет запускать файл GenieSocketServer.jar. Наш командный файл будет выглядеть так:

cd Libs\GenieServer\start "GenieServer" java -Dfile.encoding=UTF-8 -Xms512M -Xmx512M -XX:MinHeapFreeRatio=20 -XX:MaxHeapFreeRatio=40 -jar GenieSocketServer.jar
exit 0

     Поступим точно также и с остановкой сервера. Напишем командный файлик stopServer.cmd:

taskkill /FI "WINDOWTITLE eq GenieServer"
exit 0
     Команда exit 0 нужна для того, чтобы окошко командной строки закрылось после выполнения всех команд. Иначе оно будет висеть и раздражать нас.
Теперь сделаем методы запуска и остановки startGenieServer и stopGenieServer. Будем вызывать их из традиционных конфигурационных методов Setup и TearDown (туда же можно добавить запуск браузера и разные прочие приготовления). Пометим их аннотациями
@BeforeTest и @AfterTest.
     Эти аннотации означают, что метод будет запущен один раз перед проходом всех тестов (не перед каждым тестом, а именно 1 раз перед всеми тестами) и один раз после прохода всех тестов. Больше про аннотации можно почитать здесь.
     Тесты, как и базовый класс сложим в пакет com.blogspot.testerstorehouse.tests

package com.blogspot.testerstorehouse.tests;

import java.io.IOException;
import org.testng.annotations.AfterTest;
import org.testng.annotations.BeforeTest;

public class TestsBase {
   @BeforeTest
   public void Setup(){
      startGenieServer();
   }
   
   @AfterTest
   public void TearDown(){
      stopGenieServer();
   }
 
   private void startGenieServer() {
      ExecuteBatch("cmd /c start runServer.cmd");
   }

   private void stopGenieServer() {
      ExecuteBatch("cmd /c start stopServer.cmd");
   }

   private void ExecuteBatch(String command){
      try {
         Runtime.getRuntime().exec(command);
      } catch (IOException e) {
         e.printStackTrace();
      }
   }
}

2. Запуск Genie скриптов.
    Вариант запуска скриптов, предложенный в мануале, нас не вполне устраивает. Нам не нужна отдельная конфигурация запуска под каждый Genie-скрипт. Нужно чтобы мы могли запускать любой скрипт, внутри тестов, передавая путь к скрипту, как параметр. Давайте создадим класс GenieScriptsExecutor и в нём метод ExecuteScript. Создадим их в отдельном пакете com.blogspot.testerstorehouse.genieHelper.

package com.blogspot.testerstorehouse.geniehelper;

import com.adobe.genie.executor.Executor;

public class GenieScriptsExecutor {

   public static void ExecuteScript(String path) {
      String[] path_arr = new String[]{path};
   try {
      Executor.startExecution(path_arr);
   }
   catch (ClassNotFoundException e) {
      e.printStackTrace();
   }
   }
} 

    Я умолчал, что предварительно, нужно было добавить файл Executor.jar в Buildpath нашего проекта. Этот файлик находится среди других файлов пакета Genie: Genie-binary\Genie\GenieScripts\Executor.jar.
    Вернёмся к коду. Команда выполнения скрипта будет теперь выглядеть так:
GenieScriptsExecutor.ExecuteScript("Path to GenieScript.class");

3. Связка с Webdriver.
    Мы уже умеем выполнять нужные нам скрипты внутри кода. Можно оборачивать их в любую нужную нам логику. Например - в тесте нужно открыть страницу с игрой, сделать несколько действий во флешке, затем проверить, что на странице появился нужный нам элемент с локатором id=“gameEnd”.     В отдельном пакете com.blogspot.testerstorehouse.webdriverHelper создадим класс WebdriverHelper с вспомогательными методами:
package com.blogspot.testerstorehouse.webdriverhelper;

import org.openqa.selenium.By;
import org.openqa.selenium.Capabilities;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.firefox.FirefoxDriver;
import org.openqa.selenium.remote.DesiredCapabilities;

public class WebdriverHelper {

private static WebDriver driver = null;
private static Capabilities HelperCapabilities=null;
   //Метод позволяет запустить браузер с какими-то особыми настройками
   public static void createCapabilities(DesiredCapabilities capabilities){
      HelperCapabilities=capabilities;
   }
 
   //Метод ленивого обращения к драйверу
   public static WebDriver getDriver(){
      if (driver==null){
         driver = new FirefoxDriver(HelperCapabilities);
      }
   return driver;
   }
 
   //Остановка вебдрайвера, закрытие браузера
   public static void teardown(){
      if(getDriver()!=null){
         getDriver().close();
         getDriver().quit();
      }
   }
 
   //Поиск элемента
   public static WebElement findElement(By locator){
      return getDriver().findElement(locator);
   }

   //Проверка наличия элемента на странице
   public static boolean isElementPresent(By locator){
      if (getDriver().findElements(locator).size()>0){
         return true;
      }
   return false;
   }

   //Ожидание появления элемента
   public static void waitforElementDisplayed(By locator) {
      try {
         for (int second = 0; second<10; second++) {
            if (isElementPresent(locator)){
               if (getDriver().findElement(locator).isDisplayed()){
                  break;
               }
            }
         Thread.sleep(1000);
         }
      }
      catch (Exception e) {e.printStackTrace();}
   }

//Открытие страницы
   public static void openUrl(String testUrl){
      getDriver().get(testUrl);
   }
}

        Добавим запуск и остановку вебдрайвера в наши Setup и Teardown:

   public WebDriver driver;
   @BeforeTest
   public void Setup(){
      startGenieServer();
      driver = WebdriverHelper.getDriver();
   }

   @AfterTest
   public void TearDown(){
      stopGenieServer();
      driver.close();
      driver.quit();
   }

    Теперь в пакете com.blogspot.testerstorehouse.tests cоздадим класс GameTests с тестовыми методами:
package com.blogspot.testerstorehouse.tests;

import org.openqa.selenium.By;
import com.blogspot.testerstorehouse.geniehelper.GenieScriptsExecutor;
import com.blogspot.testerstorehouse.webdriverhelper.WebdriverHelper;

public class GameTests extends TestsBase{
   public void testGame(){
      WebdriverHelper.openUrl("http://testsite/gamepage/");
      GenieScriptsExecutor.ExecuteScript("Path to Genie Script .class");
      WebdriverHelper.waitforElementDisplayed(By.id("gameEnd"));
   }
}

    Во второй части статьи мы свяжем всю эту логику и TestNG. 

8 комментариев:

  1. Спасибо вам, Дмитрий, за статью.
    Столкнулась с проблемой автоматизации флеша, а тут такое прекрасное описание.

    Но, как у человека далекого от программирования, у меня появилось несколько вопросов.
    К какому классу принадлежат наши методы startGenieServer и stopGenieServer?
    Несмотря на то, что я добавила Executor.jar в Buildpath, Executor все равно не может быть выполненным. Что я пропустила из виду?

    Была бы очень признательна за помощь.

    ОтветитьУдалить
  2. Добрый день.
    Не за что.
    Вообще сейчас, после некоторого времени работы с Genie, я понял, что статью надо переписать. Есть куча недочётов. Например, тестнг не будет обрабатывать ошибки в скриптах в такой реализации, но есть вариант - парсить результаты скрипта. Я перепишу эту статью, с пояснением. Есть ещё некоторые моменты, которые надо осветить и раз вам это нужно, то я потороплюсь.

    Давайте сейчас разберёмся с вашей проблемой.
    Методы запуска и остановки джини можно положить прямо в том же классе, где и остальные тесты. Можно вынести в отдельный класс TestBase. Ваши классы с тестами могут наследовать класс TestBase, к примеру. Там же сложите какие-то общие для всех тестов действия.

    Например вот так


    public class TestBase {

    @BeforeTest //Аннотация означает, что этот метод будет выполняться только один раз, перед всей группой тестов.
    public void testSetup() throws Exception{
    startGenieServer();
    }


    @AfterTest
    public void testTeardown(){
    stopGenieServer();
    }
    }

    Ваши тестовые классы будут наследовать класс TestBase:

    public class TestsChat extends TestBase{
    @Test
    public void TestSomething(){
    //Здесь тестовая логика
    }

    }

    ОтветитьУдалить
  3. Касательно второго вопроса.
    Можете чуть детальнее описать проблему и ошибку?

    ОтветитьУдалить
  4. Спасибо за ответ. А то я было уже отчаялась решить проблему.


    Касательно второго вопроса.
    Я создала класс GenieScriptsExecutor с методом ExecuteScript.
    ошибка вываливается на строке:
    Executor.startExecution(path_arr);
    "Executor can not be resolved"

    Хотя сам файлик executor.jar добавила в Buildpath проекта

    ОтветитьУдалить
  5. проблема с executor решилась. нужно было просто перезагрузить eclipse )

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

    ОтветитьУдалить
  6. Пишите, спрашивайте :)

    Я успел утром дописать ещё одну статейку по Джини. Посмотрите, может вам будет интересно:
    http://testerstorehouse.blogspot.com/2014/06/genie-class.html

    ОтветитьУдалить
  7. Дмитрий, прошла неделя, и мне вновь нужен ваш совет )

    Возможно ли применять аннотацию testNG внутри самого скрипта джини?

    Столкнулась со сл.проблемой: внутри джинни нужно проверить условие, и при его невыполнении остановить тест с ошибкой. Есть ли такая возможность у джинни?

    ОтветитьУдалить
  8. Доброго дня. Я как раз сегодня обновил эту статью. Форматирование ещё пока кривое, правда, но я его поправлю :)

    Смотрите, какая штука.
    Скрипт Genie исполняется внутри своего отдельного потока.Стало быть, любые пойманные им эксепшны перехватываются там же внутри.
    Я бы предложил вам использовать
    Genie.EXIT_ON_FAILURE = true;
    Проверять условия можно с помощью ассертов. Они есть в самом джини
    GenieAssertion.assertTrue(message, condition);

    Если в Ассерте не выполнится условия, джини обработает эксепшн и остановит выполнение, если .EXIT_ON_FAILURE = true.

    Посмотрите вторую часть этой статьи, там есть описание велосипеда, которым можно обернуть джини в тестнг:
    http://testerstorehouse.blogspot.com/2014/06/adobe-genie-webdriver-testng-2.html

    Пишите, спрашивайте.




    ОтветитьУдалить