21 октября 2014 г.

Маленький, но очень полезный трюк. Webdriver IsElementPresent

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

bool exists = driver.FindElements(locator).Count != 0;

Это работает.
Но долго.
Иногда страшно долго (если мы ищем по xpath, и  элемента на странице всё таки нет).


Можно сказать драйверу чтобы не особо напрягался поисками элемента.
Реализация в С# будет так:

public static bool IsElementPresent(By locator)
        {
            var driver = GetDriver();
            driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromMilliseconds(0));
            bool exists = driver.FindElements(locator).Count != 0;
            driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(3));
            return exists;
        }

19 августа 2014 г.

Вебдрайвер и TestNG. Запуск тестов. 1. Командная строка.

Разберёмся, как запускать тесты с TestNG. 
Пойдём от простого к сложному и сначала рассмотрим простой запуск из командной строки.
Нужно указать в classpath все используемые нами библиотеки, а также классы с тестами.

Можно сделать вот такой вот .bat файлик

set ProjectPath=D:\Projects\Java\TestProject\bin
set SeleniumPath=C:\Libs\Java\Selenium\selenium-2.42.2\selenium-java-2.42.2.jar
set WebdriverPath=C:\Libs\Java\Selenium\selenium-2.42.2\libs\*
set TestNGPath=C:\eclipse\plugins\org.testng.eclipse_6.8.6.20141201_2240\lib\testng.jar
set classpath=%ProjectPath%;%TestNGPath%;%SeleniumPath%;%WebdriverPath%
java org.testng.TestNG testng.xml


Построчно:
Первая строчка - указание пути к бинам самого проекта.
Вторая и третья - пути к java-клиенту селениума и библиотечкам вебдрайвера
Четвёртая - это путь к самому TestNG. У меня он установлен в качестве плагина к эклипсу, потому путь выглядит вот так.
Пятая строчка - формирование classpath из заданных выше переменных.
Ну и последняя - сам запуск.

Здесь нужно напомнить, что  мы ссылаемся на файлик testng.xml. Там описано, как запускать тесты и вообще с его помощью можно наворотить делов очень тонко настроить выполнение наших тестов. Посмотрите на документацию TestNG. Там всё классно описано.
Можно не морочиться и создать такой дефолтный testng.xml из самого эклипса. Кликаем на проекте правой кнопкой мыши. TestNG - Convert to TestNG.

И ещё одна маленькая подсказка:
Используйте команду pause, чтобы окошко не закрывалось при возникновении ошибки исполнения batch файла.

18 августа 2014 г.

Webdriver. Полезные фокусы. C#


1. Относительный Xpath

Часто необходимо найти элемент с помощью Xpath внутри уже найденного вебдрайвером WebElement-а. Используйте для этого относительный Xpath (точка вначале выражения).
Вот так:
var questionDiv = driver.FindElement(By.XPath("//div[@class='ac-item']"));
questionDiv.FindElement(By.XPath(".//label[@class='ac-title']")).Click();

2. Несколько примеров работы функции contains() в Xpath выражениях:

//span[contains(.,'Как узнать информацию')] -Будут найдены элементы span, в тексте которых содержится строка 'Как узнать информацию'.

//label[contains(@class,'ac-title')] -Будут найдены все элементы label, содержащие в названии класса строку ac-title.

//label[contains(span,'Как узнать информацию')] -Будут найдены все элемент label, у которых есть дочерний элемент span, содержащий в тексте строку 'Как узнать информацию'.

//label[contains(@class,'ac-title') and contains(span,'Как узнать информацию')]  -Будут найдены все элементы label, у которых есть класс, содержащий в названии строку 'ac-title' и дочерний элемент span, содержащий в тексте строку 'Как узнать информацию'.

3. Задача - получить текст элемента, который есть в DOM-модели, но не виден. 

Использование функции Text() не решает задачу - результат будет null.
Можно выкрутиться c помощью GetAttribute("textContent"):
driver.FindElement(By.XPath("//label[@class='ac-title']")).GetAttribute("textContent");

PhantomJS и SSL. C#

Одна из первых проблем с которой я столкнулся при работе с "безголовым" браузером PhantomJS, - это открытие https-страничек.
Оказывается, по дефолту фантом их просто не открывает.

Обойти эту проблему можно так:

private static IWebDriver _driver;

public static IWebDriver GetDriver()
{
 if (_driver == null)
 {
  var driverPath = Utils.GetPhantomJSPath(); //Метод определяет путь к папке с драйвером phantomjs.exe 

  PhantomJSDriverService service = PhantomJSDriverService.CreateDefaultService(driverPath);
  service.IgnoreSslErrors = true;
  service.ProxyType = "none";
  _driver = new PhantomJSDriver(service);
  _driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(10));
 }
return _driver;
}


Ключевая строчка: service.IgnoreSslErrors = true;

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"));
           }
    }