18 июня 2014 г.

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

4. Связка с TestNG.

   Теперь попробуем увязать нашу логику и TestNG.
   Первая мысль - «Надо просто сделать метод и пометить его аннотацией @Test». Так:
   @Test
   public void testGame(){
      WebdriverHelper.openUrl("http://testsite/gamepage/");
      GenieScriptsExecutor.ExecuteScript("path to Genie script .class");
      WebdriverHelper.waitforElementDisplayed(By.id("gameEnd"));
   }
   Но здесь есть проблема.    Genie написан так, что прогон скрипта осуществляется в отдельном потоке. Все эксепшны, которые образуются при прогоне скрипта перехватываются и обрабатываются самим Genie в том же потоке.    Таким образом, даже если при выполнении команды GenieScriptsExecutor.ExecuteScript("path_to_geniescript.class");
возникнут какие-то ошибки, то они будут обработаны внутри потока и TestNG будет считать, что всё в порядке.

Что делать?

- Можно переписать сам Genie, добавить проброску RuntimeException-ов, ловить их и пробрасывать выше. Но перекидывание исключениями между потоками не считается хорошим тоном.
- Можно пойти другим путём. Результаты прогона скрипта Genie держит в xml файлике.
   Мы можем парсить этот файлик на предмет наличия там сообщений об ошибках. Велосипед тот ещё, но я не вижу других вариантов решения проблемы. 
   Итак, парсим. Сначала разберёмся, как нам получить xml с результатами прогона скрипта.   Если посмотреть в класс Executor на метод startExecution, то можно видеть, что он возвращает некий GenieExecutionResult.
public static GenieExecutionResult startExecution(String[] args)
   Заглянем в класс GenieExecutionResult. Внутри его есть две приватные переменные
private String execResultXML;
private boolean finalResult;
   А также геттер getTestResultXML(); Именно это нам и нужно.   Есть разные варианты парсинга xml файлов. Можно просто грубо ковыряться в DOM-е. Можно, например, попробовать преобразовать xml в объект. Программисты называют это десериализацией и мы её сейчас сделаем.

   Разберём работу по пунктам:
  • Создаём модельный класс
  • Преобразуем (десериализуем) xml в объект
  • Просматриваем объект

    4.1 Создаём модельный класс

       Я вынесу все, что касается парсинга в отдельный пакет. Назовём его com.blogspot.testerstorehouse.parser.   Модельный класс это по сути — объектное представление xml файла.
    package com.blogspot.testerstorehouse.parser;

    import java.util.ArrayList;
    import javax.xml.bind.annotation.XmlAttribute;
    import javax.xml.bind.annotation.XmlElement;
    import javax.xml.bind.annotation.XmlRootElement;
    import com.blogspot.testerstorehouse.parser.TestResultsModel.TestCase.TestResults.TestParameter;


    @XmlRootElement(name = "TestLog")
    public class TestResultsModel {
         @XmlElement(name = "TestSettings")
         public TestSettings testSettings;
     
       @XmlElement(name = "TestCase")
       public ArrayList<TestCase> testCases;

       public static class TestSettings{
          @XmlElement(name = "TestEnvironment")
          TestEnvironment testEnvironment;

          public static class TestEnvironment{
             @XmlElement(name = "TestEnvironment")
             TestMachine testMachine;
             @XmlElement(name = "GenieVersionInfo")
             GenieVersionInfo genieVersionInfo;
             
             public static class TestMachine{
                @XmlAttribute(name = "availableMemory")
                String availableMemory;

                @XmlAttribute(name = "osArchitecture")
                String osArchitecture;

                @XmlAttribute(name = "physicalMemory")
                String physicalMemory;
             
                @XmlAttribute(name = "processsorCount")
                String processorCount;

                @XmlElement(name = "TestSetup")
                TestSetup testSetup;

                @XmlElement(name = "JavaSetup")
                JavaSetup javaSetup;

                public static class TestSetup{
                       @XmlAttribute(name = "locale")
                       String locale;

                       @XmlAttribute(name = "os")
                       String os;

                       @XmlAttribute(name = "osVersion")
                       String osVersion;

                       @XmlAttribute(name = "platform")
                       String platform;
                       
                       @XmlAttribute(name = "userTimezone")
                       String userTimezone;
               }
                       public static class JavaSetup{
                       @XmlAttribute(name = "javaRuntimeVersion")
                       String javaRuntimeVersion;

                       @XmlAttribute(name = "javaVersion")
                       String javaVersion;
                       }
             }
             
             public static class GenieVersionInfo{
                      @XmlElement(name = "ServerVersion")
                      String ServerVersion;

                      @XmlElement(name = "ExecutorVersion")
                      String ExecutorVersion;

                      @XmlElement(name = "PluginVersion")
                      String PluginVersion;

             }
        }
    }
    public static class TestCase{
             @XmlElement(name = "TestScript")
             TestScript testScript;
     
             public static class TestScript{
             @XmlAttribute(name = "name")
             String name;
             
             @XmlElement(name = "Messages")
             String messages;
             
             @XmlElement(name = "TestResults")
             TestResults testresults;
     
             @XmlElement(name = "TestStep")
             public ArrayList<TestStep>testSteps;
             }
             
             public static class TestResults{
                      @XmlAttribute(name = "status")
                      String status;
                      
                      @XmlElement(name = "TestTime")
                      TestTime testTime;
     
                      @XmlElement(name = "TestParameter")
                      TestParameter testParameter;
                      
                      public static class TestTime{
                                @XmlAttribute(name = "duration")
                               String duration;
                               
                                @XmlAttribute(name = "end")
                               String end;
             
                               @XmlAttribute(name = "start")
                               String start;
                      }
                      public static class TestParameter{
                                @XmlAttribute(name = "name")
                               String name;
     
                                @XmlAttribute(name = "type")
                               String type;
     
                                @XmlAttribute(name = "value")
                               String value;
                      }
                      
                      public static class TestStep{
                      }
        }
             public static class TestStep{
             @XmlElement(name = "TestResults")
             TestResults testresults;
             
             @XmlElement(name = "TestParameter")
             TestParameter testParameter;
     
             @XmlElement(name = "Message")
             Message message;
     
             public static class Message{
                       @XmlAttribute(name = "message")
                      String messagetext;
     
                       @XmlAttribute(name = "type")
                      String type;
             }
         }
       }
    }
       Создадим также класс, в котором будут лежать нужные нам результаты.
    package com.blogspot.testerstorehouse.parser;

    public class ShortTestResult {

       private String message = "";
       private String executionStatus = "Passed";
       
       public String getExecutionStatus() {
          return executionStatus;
       }

       public void setExecutionStatus(String status) {
          this.executionStatus = status;
       }
       
       public String getMessage() {
          return message;
       }

       public void setMessage(String resultmessage) {
          this.message = resultmessage;
       }

    }
       По сути, нам нужны только сообщения об ошибках и общий результат выполнения скрипта.

    4.2 Преобразуем (десериализуем) xml документ в объект

       Если вы помните, то результат работы скрипта возвращается в виде строки. Это ещё не документ. Создадим ещё один класс — Parser. В нём сложим методы, непосредственно преобразующие строку в документ и документ в объект. Напишем метод getXMLFromString который преобразует строку в документ. После этого преобразуем документ в объект. Воспользуемся библиотечкой JAXB для этой цели. Там процес десериализации называется демаршализацией. Пусть так, главное, что работает. Смотрим метод DeserializeXML

    4.3 Просматриваем объект

       Просматривать объект мы будем банально в цикле. Смотрите метод getShortExecutionResult ниже.

    package com.blogspot.testerstorehouse.parser;

    import java.io.StringReader;
    import java.util.ArrayList;
    import javax.xml.bind.JAXBContext;
    import javax.xml.bind.JAXBException;
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import org.w3c.dom.Document;
    import org.xml.sax.InputSource;
    import com.blogspot.testerstorehouse.parser.TestResultsModel.TestCase;
    import com.blogspot.testerstorehouse.parser.TestResultsModel.TestCase.TestStep;

    public class Parser {

           Document filetoparse = null;

           //Просматриваем объект и делаем вытяжку в короткий результат ShortTestResult
           public static ShortTestResult getShortExecutionResult(String xml){
                  ShortTestResult str = new ShortTestResult();
                  TestResultsModel parsedresult = DeserializeXML(xml);
                  ArrayList<TestCase> testcases = parsedresult.testCases;
                  String errorMessages = "";
                  for (TestCase testCase : testcases) {
                         ArrayList<TestStep> testSteps = testCase.testScript.testSteps;
                         for (TestStep testStep : testSteps) {
                                String teststatus = testStep.testresults.status;
                                if(teststatus.equals("Failed")){
                                str.setExecutionStatus(teststatus);//если хоть один шаг упал, то мы будем считать весь тест упавшим
                                }
                                if (testStep.message!=null){ //Собираем все сообщения об ошибках
                                errorMessages += testStep.message.messagetext +"\n";
                                }
                         }
                  }
                  str.setMessage(errorMessages);
                  return str;
           }

           //Превращаем документ в объект
           private static TestResultsModel DeserializeXML(String xml){
                  TestResultsModel testResultsModel=null;
                  InputSource is = new InputSource(new StringReader(xml));
                  JAXBContext jaxbContext;
                  try {
                         jaxbContext = JAXBContext.newInstance(TestResultsModel.class);
                         javax.xml.bind.Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
                         testResultsModel = (TestResultsModel) jaxbUnmarshaller.unmarshal(is);
                  } catch (JAXBException e) {
                         e.printStackTrace();
                  }

           return testResultsModel;
    }

           //Преобразуем строку в документ
           public static Document getXMLFromString(String xml) throws Exception{
                  DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
                  DocumentBuilder builder = factory.newDocumentBuilder();
                  InputSource is = new InputSource(new StringReader(xml));
                  Document document = builder.parse(is);
                  document.normalize();
          return document;
           }
    }

       Теперь добавим парсинг результатов в наш метод ExecuteScript.

    package com.blogspot.testerstorehouse.geniehelper;

    import com.adobe.genie.executor.Executor;
    import com.adobe.genie.executor.objects.GenieExecutionResult;
    import com.blogspot.testerstorehouse.parser.Parser;
    import com.blogspot.testerstorehouse.parser.ShortTestResult;

    public class GenieScriptsExecutor {

    public static ShortTestResult ExecuteScript(String path) {
           String[] path_arr = new String[]{path};
           ShortTestResult result = new ShortTestResult();
          try {
                  GenieExecutionResult scriptExecResult = Executor.startExecution(path_arr);
                 return Parser.getShortExecutionResult(scriptExecResult.getTestResultXML());
           }
          catch (ClassNotFoundException e) {
                  e.printStackTrace();
           }
           return result;
           }
    }

    5. Тесты

    Теперь у нас всё готово и осталось только написать тесты.

    package com.blogspot.testerstorehouse.tests;

    import org.openqa.selenium.By;
    import org.testng.Assert;
    import org.testng.annotations.Test;
    import com.blogspot.testerstorehouse.geniehelper.GenieScriptsExecutor;
    import com.blogspot.testerstorehouse.parser.ShortTestResult;
    import com.blogspot.testerstorehouse.webdriverhelper.WebdriverHelper;

    public class GameTests extends TestsBase{

           @Test
           public void testGame(){
                  WebdriverHelper.openUrl("http://testsite/gamepage/");
                  ShortTestResult testres = GenieScriptsExecutor.ExecuteScript("");
                  Assert.assertTrue(testres.getExecutionStatus().equals("Passed"), testres.getMessage());
                  WebdriverHelper.waitforElementDisplayed(By.id("gameEnd"));
           }
    }