Статьи

Снимок экрана, когда пользовательский интерфейс тестирует написанный в Geb Fail

Это второй пост о  Гебе  за короткое время. На этот раз мы узнаем, как настроить Geb для создания снимка экрана каждый раз, когда наш тест пользовательского интерфейса не проходит. Изображение страницы в момент, когда тест не пройден, часто очень полезно при отладке загадочно провального теста пользовательского интерфейса на сервере CI и может сэкономить много времени при попытке решить такие проблемы.

Эта проблема

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

Решение

Мы должны найти какой-то тип слушателя, который ожидает разные результаты теста и запускает правильный метод, когда тест не пройден. В  TestNG  у нас есть  TestListenerAdapter,  поэтому, вероятно, у Geb или Spock должно быть что-то похожее.

После некоторых копаний я смог найти  IRunListener  с методом, который мне нужен:

public interface IRunListener {
    void beforeSpec(org.spockframework.runtime.model.SpecInfo specInfo);
 
    void beforeFeature(org.spockframework.runtime.model.FeatureInfo featureInfo);
 
    void beforeIteration(org.spockframework.runtime.model.IterationInfo iterationInfo);
 
    void afterIteration(org.spockframework.runtime.model.IterationInfo iterationInfo);
 
    void afterFeature(org.spockframework.runtime.model.FeatureInfo featureInfo);
 
    void afterSpec(org.spockframework.runtime.model.SpecInfo specInfo);
 
    void error(org.spockframework.runtime.model.ErrorInfo errorInfo); // < -- this is it
 
    void specSkipped(org.spockframework.runtime.model.SpecInfo specInfo);
 
    void featureSkipped(org.spockframework.runtime.model.FeatureInfo featureInfo);
}

Простая версия прослушивателя неудачных тестов
К счастью, этот интерфейс поставляется с абстрактным классом с именем  AbstractRunListener,  который позволяет нам реализовывать только те методы, которые мы хотим, вместо выполнения всего контракта, указанного IRunListener.

Таким образом, наш первый шаг будет выводить что-то на консоль каждый раз, когда наш тест не пройден:

class TakeScreenshotOnTestFailureListener extends AbstractRunListener {
 
   def void error(ErrorInfo error) {
       System.out.println("Error in method " + error.method.name);
   }
}

Добавление пользовательского слушателя во все тесты с расширением Spock.
Но кроме реализации слушателя, мы должны добавить этого слушателя в наши тесты. В настоящее время в Споке это возможно только с расширениями. Опять же, я должен был найти правильное расширение для использования и закончил интерфейсом IGlobalExtension, который имеет только один метод, посещающий каждую спецификацию (тестовый метод) в нашей кодовой базе:

public interface IGlobalExtension {
  void visitSpec(SpecInfo spec);
}

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

class GlobalSpecExtension implements IGlobalExtension {
 
    @Override
    void visitSpec(SpecInfo specInfo) {
        specInfo.addListener(new TakeScreenshotOnTestFailureListener())
    }
}

Регистрация глобального расширения Spock
Каждое глобальное расширение должно быть зарегистрировано в Spock. Это может быть достигнуто путем размещения текстового файла с легко запоминающимся именем в каталоге META-INF / services. Файл должен иметь имя  org.spockframework.runtime.extension.IGlobalExtension  и должен содержать полное имя нашей реализации интерфейса IGlobalExtension:

com.smscpl.mc5.uitests.geb_extensions.GlobalSpecExtension

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

Сделать скриншот с помощью драйвера Selenium довольно просто, это один вызов метода, но перед этим мы должны получить экземпляр драйвера в нашем классе слушателя. Прямой доступ к драйверу напрямую из слушателя невозможен, поэтому нам нужно хранить его в глобально доступном объекте. Самое простое решение — сохранить ссылку на драйвер при его создании в нашем файле GebConfig. Итак, давайте создадим синглтон, который будет содержать наш браузер:

@Singleton
class BrowserInstance {
    def FirefoxDriver browser;
}

и инициализируем его в скрипте GebConfig.groovy, расположенном в корневом пакете нашего проекта:

driver =
    {
        newDriver = new FirefoxDriver()
        BrowserInstance.instance.browser = newDriver
        return newDriver
    }

Теперь мы можем получить доступ к драйверу из нашего объекта слушателя, поэтому окончательная логика скриншота довольно проста:

class TakeScreenshotOnTestFailureListener extends AbstractRunListener {
 
   def void error(ErrorInfo error) {
       File screenFile = BrowserInstance.instance.browser.getScreenshotAs(OutputType.FILE); // take a screenshot
 
       String fileName = createFileName(error) // generate file name based on test class and method names
       File destinationFile = new File(System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + fileName)
       Files.copy(screenFile, destinationFile)
 
       System.out.println("##teamcity[publishArtifacts '" + destinationFile.getAbsolutePath() + "']"); // publish as artifact in TeamCity
   }
 
    def private String createFileName(ErrorInfo error) {
        String specName = error.method.parent.name
        String testName = error.method.name
        String milliseconds = new Date().getTime()
        String fileName = specName + ": '" + testName + "' " + milliseconds + ".png"
        fileName
    }
}

И это все. Каждый сбой теста теперь будет отображаться с соответствующим скриншотом из браузера.