Статьи

Советы, чтобы избежать хрупких тестов пользовательского интерфейса

В прошлой статье я рассказал о нескольких идеях и шаблонах, таких как шаблон Page Object, которые помогают писать поддерживаемые тесты пользовательского интерфейса. В этой статье мы собираемся обсудить несколько расширенных тем, которые могут помочь вам написать более надежные тесты, и устранить их в случае неудачи:

  • Мы обсуждаем, почему добавление фиксированных задержек в тестах пользовательского интерфейса — плохая идея, и как вы можете избавиться от них.
  • Среды автоматизации браузера нацелены на элементы пользовательского интерфейса с помощью селекторов, и очень важно использовать хорошие селекторы, чтобы избежать хрупких тестов. Поэтому я даю вам несколько советов по выбору правильных селекторов и элементов таргетинга, когда это возможно.
  • Тесты пользовательского интерфейса не проходят чаще, чем другие типы тестов, так как мы можем отладить сломанный тест пользовательского интерфейса и выяснить, что вызвало сбой? В этом разделе я покажу вам, как вы можете сделать снимок экрана и исходный HTML-код страницы в случае сбоя теста пользовательского интерфейса, чтобы вам было легче исследовать его.

Я собираюсь использовать Selenium для тем автоматизации браузера, обсуждаемых в этой статье.

Как и в предыдущей статье, концепции и решения, обсуждаемые в этой статье, применимы независимо от языка и структуры пользовательского интерфейса, которые вы используете. Прежде чем идти дальше, прочитайте предыдущую статью, так как я собираюсь сослаться на нее и ее пример кода несколько раз. Не волнуйся; Я подожду здесь.


Добавление Thread.Sleep (или, как правило, задержки) выглядит как неизбежный хак, когда дело доходит до тестирования пользовательского интерфейса. У вас есть тест, который периодически прерывается, и после некоторого расследования вы прослеживаете его до случайных задержек в ответе; Например, вы переходите на страницу и ищете или утверждаете что-то до того, как страница полностью загружена, и ваша платформа автоматизации браузера выдает исключение, указывающее, что элемент не существует. Многое может способствовать этой задержке. Например:

  • Веб-сервер, база данных и / или сеть перегружены и заняты другими запросами.
  • Тестируемая страница работает медленно, потому что загружает много данных и / или запрашивает множество таблиц.
  • Вы ждете какого-то события на странице, которое занимает время.

Или сочетание этих и других вопросов.

Допустим, у вас есть страница, которая обычно загружается менее чем за секунду, но тесты, выполняющие ее, время от времени терпят неудачу из-за случайной задержки в ответе. У вас есть несколько вариантов:

  • Вы не добавляете задержку: в этом случае тесты, попадающие на эту страницу, иногда терпят неудачу, что снижает ваше доверие к тестам.
  • Вы добавляете задержку в одну секунду к тестам, попадающим на эту страницу: в этом случае все эти тесты всегда будут занимать одну секунду дольше, даже если страница загружается быстро, но даже в этом случае тесты не гарантированно пройдут как страница Иногда загрузка может занять больше времени, чем секунда.
  • Вы можете решить добавить задержку в несколько секунд: это гарантирует, что страница всегда будет загружена, но теперь ваши тесты пользовательского интерфейса занимают все больше и больше времени.

Видите ли, выигрыша с произвольными задержками не бывает: вы получаете медленный или хрупкий набор тестов. Здесь я собираюсь показать вам, как избежать вставки фиксированных задержек в ваши тесты. Мы собираемся обсудить два типа задержек, которые должны охватывать практически все случаи, с которыми вам приходится иметь дело: добавление глобальной задержки и ожидание того, что произойдет.

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

Неявное ожидание — указать WebDriver опросить DOM в течение определенного времени при попытке найти элемент или элементы, если они не доступны сразу. Значение по умолчанию равно 0. После установки устанавливается неявное ожидание в течение срока службы экземпляра объекта WebDriver.

Вот как вы устанавливаете неявное ожидание:

1
2
WebDriver driver = new FirefoxDriver();
driver.Manage().Timeouts().ImplicitlyWait(TimeSpan.FromSeconds(5));

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

1
2
driver.Url = «http://somedomain/url_that_delays_loading»;
IWebElement myDynamicElement = driver.FindElement(By.Id(«someDynamicElement»));

вместо того:

1
2
3
driver.Url = «http://somedomain/url_that_delays_loading»;
Thread.Sleep(5000);
IWebElement myDynamicElement = driver.FindElement(By.Id(«someDynamicElement»));

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

Как только неявное ожидание установлено на вашем экземпляре WebDriver оно применяется ко всем действиям в драйвере; так что вы можете избавиться от множества Thread.Sleep в вашем коде.

5 секунд — это ожидание, которое я наверстал для этой статьи — вы должны найти оптимальное неявное ожидание для вашего приложения и сделать его как можно более коротким. Из документации API:

Увеличение неявного тайм-аута следует использовать разумно, так как это отрицательно скажется на времени выполнения теста, особенно при использовании с более медленными стратегиями определения местоположения, такими как XPath.

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

Использование неявного ожидания — отличный способ избавиться от многих жестко заданных задержек в вашем коде; но вы все равно окажетесь в ситуации, когда вам нужно добавить некоторые фиксированные задержки в ваш код, потому что вы ожидаете, что что-то произойдет: страница медленнее, чем все другие страницы, и вам придется ждать дольше, вы ожидание завершения вызова AJAX или появления или исчезновения элемента со страницы и т. д. Здесь вам необходимо явное ожидание.

Итак, вы установили неявное ожидание на 5 секунд, и оно работает для многих ваших тестов; но есть еще несколько страниц, которые иногда загружаются более 5 секунд и приводят к неудачным тестам.

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

В случае медленной страницы вы можете заменить фиксированные задержки явным ожиданием :

Явное ожидание — это код, который вы определили для ожидания определенного условия, прежде чем продолжить работу в коде.

Вы можете применить явное ожидание, используя класс WebDriverWait . WebDriverWait в сборке WebDriver.Support и может быть установлен с помощью Selenium.Support nuget:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
/// <summary>
/// Provides the ability to wait for an arbitrary condition during test execution.
/// </summary>
public class WebDriverWait : DefaultWait<IWebDriver>
{
  /// <summary>
  /// Initializes a new instance of the <see cref=»T:OpenQA.Selenium.Support.UI.WebDriverWait»/> class.
  /// </summary>
  /// <param name=»driver»>The WebDriver instance used to wait.</param><param name=»timeout»>The timeout value indicating how long to wait for the condition.</param>
  public WebDriverWait(IWebDriver driver, TimeSpan timeout);
 
  /// <summary>
  /// Initializes a new instance of the <see cref=»T:OpenQA.Selenium.Support.UI.WebDriverWait»/> class.
  /// </summary>
  /// <param name=»clock»>An object implementing the <see cref=»T:OpenQA.Selenium.Support.UI.IClock»/> interface used to determine when time has passed.</param><param name=»driver»>The WebDriver instance used to wait.</param><param name=»timeout»>The timeout value indicating how long to wait for the condition.</param><param name=»sleepInterval»>A <see cref=»T:System.TimeSpan»/> value indicating how often to check for the condition to be true.</param>
  public WebDriverWait(IClock clock, IWebDriver driver, TimeSpan timeout, TimeSpan sleepInterval);
}

Вот пример того, как вы можете использовать WebDriverWait в своих тестах:

1
2
3
driver.Url = «http://somedomain/url_that_takes_a_long_time_to_load»;
WebDriverWait wait = new WebDriverWait(driver, TimeSpan.FromSeconds(10));
var myDynamicElement = wait.Until(d => d.FindElement(By.Id(«someElement»)));

Мы говорим Selenium, что хотим, чтобы он ждал эту конкретную страницу / элемент до 10 секунд.

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

1
2
3
4
5
public IWebElement FindElementWithWait(By by, int secondsToWait = 10)
{
    var wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(secondsToWait));
    return wait.Until(d => d.FindElement(by));
}

Тогда вы можете использовать этот метод как:

1
2
var slowPage = new SlowPage(«http://somedomain/url_that_takes_a_long_time_to_load»);
var element = slowPage.FindElementWithWait(By.Id(«someElement»));

Это надуманный пример, показывающий, как метод может выглядеть и как его можно использовать. В идеале вы должны перенести все взаимодействия страниц на объекты вашей страницы.

Давайте посмотрим еще один пример явного ожидания. Иногда страница полностью загружена, но элемент еще не загружен, потому что она позже загружается в результате запроса AJAX. Возможно, это не тот элемент, который вы ожидаете, а просто хотите дождаться завершения взаимодействия AJAX, прежде чем вы сможете сделать утверждение, скажем, в базе данных. Опять же, именно здесь большинство разработчиков используют Thread.Sleep чтобы убедиться, что, например, этот вызов AJAX выполнен и запись находится в базе данных, прежде чем они перейдут к следующей строке теста. Это можно легко исправить с помощью выполнения JavaScript!

Большинство сред автоматизации браузера позволяют запускать JavaScript в активном сеансе, и Selenium не является исключением . В Selenium есть интерфейс IJavaScriptExecutor с двумя методами:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/// <summary>
/// Defines the interface through which the user can execute JavaScript.
/// </summary>
public interface IJavaScriptExecutor
{
    /// <summary>
    /// Executes JavaScript in the context of the currently selected frame or window.
    /// </summary>
    /// <param name=»script»>The JavaScript code to execute.</param<<param name=»args»<The arguments to the script.</param>
    /// <returns>
    /// The value returned by the script.
    /// </returns>
    object ExecuteScript(string script, params object[] args);
 
    /// <summary>
    /// Executes JavaScript asynchronously in the context of the currently selected frame or window.
    /// </summary>
    /// <param name=»script»>The JavaScript code to execute.</param<<param name=»args»<The arguments to the script.</param>
    /// <returns>
    /// The value returned by the script.
    /// </returns>
    object ExecuteAsyncScript(string script, params object[] args);
}

Этот интерфейс реализован RemoteWebDriver который является базовым классом для всех реализаций веб-драйверов. Таким образом, в вашем экземпляре веб-драйвера вы можете вызвать ExecuteScript для запуска сценария JavaScript. Вот метод, который вы можете использовать для ожидания завершения всех вызовов AJAX (при условии, что вы используете jQuery):

1
2
3
4
5
6
// This is assumed to live in a class that has access to the active `WebDriver` instance through `WebDriver` field/property.
public void WaitForAjax(int secondsToWait = 10)
{
    var wait = new WebDriverWait(WebDriver, TimeSpan.FromSeconds(secondsToWait));
    wait.Until(d => (bool)((IJavaScriptExecutor)d).ExecuteScript(«return jQuery.active == 0»));
}

Объедините ExecuteScript с WebDriverWait и вы сможете избавиться от Thread.Sleep добавленного для вызовов AJAX.

jQuery.active возвращает количество активных вызовов AJAX, инициированных jQuery ; поэтому, когда он равен нулю, AJAX-вызовы не выполняются. Этот метод, очевидно, работает, только если все запросы AJAX инициированы jQuery. Если вы используете другие библиотеки JavaScript для связи AJAX, вам следует обратиться к его документации API для эквивалентного метода или отследить вызовы AJAX самостоятельно.

При явном ожидании вы можете установить условие и дождаться его выполнения или истечения времени ожидания. Мы увидели, как мы можем проверить, чтобы AJAX-вызовы заканчивались — еще один пример — проверка видимости элемента. Как и в случае проверки AJAX, вы можете написать условие, которое проверяет видимость элемента; но есть более простое решение для этого, называемое ExpectedCondition .

Из документации Selenium :

Есть некоторые общие условия, которые часто встречаются при автоматизации веб-браузеров.

Если вы используете Java, вам повезло, потому что класс ExpectedCondition в Java довольно обширный и имеет множество удобных методов. Вы можете найти документацию здесь .

Разработчикам .Net не так повезло. В сборке WebDriver.Support все еще есть класс ExpectedConditions WebDriver.Support здесь ), но он очень минимален:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public sealed class ExpectedConditions
{
     /// <summary>
     /// An expectation for checking the title of a page.
     /// </summary>
     /// <param name=»title»>The expected title, which must be an exact match.</param>
     /// <returns>
     /// <see langword=»true»/> when the title matches;
     /// </returns>
     public static Func<IWebDriver, bool> TitleIs(string title);
 
     /// <summary>
     /// An expectation for checking that the title of a page contains a case-sensitive substring.
     /// </summary>
     /// <param name=»title»>The fragment of title expected.</param>
     /// <returns>
     /// <see langword=»true»/> when the title matches;
     /// </returns>
     public static Func<IWebDriver, bool> TitleContains(string title);
 
     /// <summary>
     /// An expectation for checking that an element is present on the DOM of a
     /// page.
     /// </summary>
     /// <param name=»locator»>The locator used to find the element.</param>
     /// <returns>
     /// The <see cref=»T:OpenQA.Selenium.IWebElement»/> once it is located.
     /// </returns>
     public static Func<IWebDriver, IWebElement> ElementExists(By locator);
 
     /// <summary>
     /// An expectation for checking that an element is present on the DOM of a page
     /// and visible.
     /// also has a height and width that is greater than 0.
     /// </summary>
     /// <param name=»locator»>The locator used to find the element.</param>
     /// <returns>
     /// The <see cref=»T:OpenQA.Selenium.IWebElement»/> once it is located and visible.
     /// </returns>
     public static Func<IWebDriver, IWebElement> ElementIsVisible(By locator);
}

Вы можете использовать этот класс в сочетании с WebDriverWait :

1
2
var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(3))
var element = wait.Until(ExpectedConditions.ElementExists(By.Id(«foo»)));

Как вы можете видеть из подписи класса выше, вы можете проверить заголовок или его части, а также наличие и видимость элементов, используя ExpectedCondition . Стандартная поддержка в .Net может быть очень минимальной; но этот класс — не что иное как обертка вокруг некоторых простых условий. Вы также можете легко реализовать другие общие условия в классе и использовать его с WebDriverWait из ваших тестовых сценариев.

Еще одна жемчужина только для разработчиков Java — FluentWait . На странице документации FluentWait

Реализация интерфейса ожидания, для которого могут быть настроены время ожидания и интервал опроса на лету. Каждый экземпляр FluentWait определяет максимальное время ожидания условия, а также частоту проверки условия. Кроме того, пользователь может настроить время ожидания для игнорирования определенных типов исключений во время ожидания, таких как NoSuchElementExceptions, при поиске элемента на странице.

В следующем примере мы пытаемся найти элемент с идентификатором foo на странице, опрашивающий каждые пять секунд на срок до 30 секунд:

01
02
03
04
05
06
07
08
09
10
11
12
// Waiting 30 seconds for an element to be present on the page, checking
// for its presence once every five seconds.
Wait<WebDriver> wait = new FluentWait<WebDriver>(driver)
    .withTimeout(30, SECONDS)
    .pollingEvery(5, SECONDS)
    .ignoring(NoSuchElementException.class);
 
WebElement foo = wait.until(new Function<WebDriver, WebElement>() {
  public WebElement apply(WebDriver driver) {
    return driver.findElement(By.id(«foo»));
  }
});

В FluentWait есть две выдающиеся вещи: во-первых, это позволяет вам указать интервал опроса, который может улучшить производительность вашего теста, и, во-вторых, позволяет игнорировать исключения, которые вас не интересуют.

FluentWait довольно крутой, и было бы здорово, если бы в .Net существовал аналог. Тем не менее, это не так сложно реализовать с помощью WebDriverWait .


У вас есть свои Page Objects, у вас есть хороший DRY-поддерживаемый тестовый код, и вы также избегаете фиксированных задержек в ваших тестах; но ваши тесты все равно не пройдены!

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

Не используйте нечеткие селекторы и не полагайтесь на структуру вашей страницы.

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

Допустим, у нас есть страница со следующим списком:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<ul id=»album-list»>
    <li>
        <a href=»/Store/Details/6″>
            <span>The Best Of Men At Work
        </a>
    </li>
    <li>
        <a href=»/Store/Details/12″>
            <span>For Those About To Rock We Salute You
        </a>
    </li>
    <li>
        <a href=»/Store/Details/35″>
            <span>Let There Be Rock
        </a>
    </li>
</ul>

В одном из моих тестов я хочу нажать на альбом «Let There Be Rock». Я бы попросил проблемы, если бы использовал следующий селектор:

1
By.XPath(«//ul[@id=’album-list’]/li[3]/a»)

По возможности вы должны добавлять ID к элементам и нацеливать их напрямую, не полагаясь на окружающие их элементы. Поэтому я собираюсь внести небольшое изменение в список:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<ul id=»album-list»>
    <li>
        <a id=»album-6″ href=»/Store/Details/6″>
            <span>The Best Of Men At Work
        </a>
    </li>
    <li>
        <a id=»album-12″ href=»/Store/Details/12″>
            <span>For Those About To Rock We Salute You
        </a>
    </li>
    <li>
        <a id=»album-35″ href=»/Store/Details/35″>
            <span>Let There Be Rock
        </a>
    </li>
</ul>

Я добавил атрибуты id для якорей, основываясь на уникальном идентификаторе альбомов, чтобы мы могли ориентироваться на ссылку напрямую, не просматривая элементы ul и li . Так что теперь я могу заменить хрупкий селектор на By.Id("album-35") который гарантированно будет работать, пока этот альбом находится на странице, что, кстати, тоже хорошее утверждение. Для создания этого селектора мне, очевидно, потребуется доступ к идентификатору альбома из тестового кода.

Однако не всегда возможно добавить уникальные идентификаторы к элементам, например, строки в сетке или элементы в списке. В таких случаях вы можете использовать классы CSS и атрибуты данных HTML, чтобы прикреплять отслеживаемые свойства к вашим элементам для облегчения выбора. Например, если на вашей странице было два списка альбомов, один в результате поиска пользователя, а другой — для предложенных альбомов, основанных на предыдущих покупках пользователя, вы можете различать их, используя класс CSS для элемента ul , даже если этот класс не используется для оформления списка:

1
2
<ul class=»suggested-albums»>
</ul>

Если вы предпочитаете не использовать неиспользуемые классы CSS, вы можете вместо этого использовать атрибуты данных HTML и изменить списки на:

1
2
<ul data-albums=»suggested»>
</ul>

и:

1
2
<ul data-albums=»search-result»>
</ul>

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

Независимо от причины ошибки и от того, получаете ли вы ее в журнале CI-сервера или в тестовой консоли настольного компьютера, NoSuchElementException (или подобное) не совсем полезно для определения того, что пошло не так, не так ли? Поэтому, если ваш тест не пройден, единственный способ устранить ошибку — это запустить ее снова и посмотреть, как она провалится. Есть несколько хитростей, которые потенциально могут спасти вас от повторного запуска медленных тестов пользовательского интерфейса для устранения неполадок. Одним из решений этой проблемы является создание снимка экрана при каждом сбое теста, чтобы мы могли вернуться к нему позже.

В Selenium есть интерфейс, который называется ITakesScreenshot :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
/// <summary>
/// Defines the interface used to take screen shot images of the screen.
/// </summary>
public interface ITakesScreenshot
{
  /// <summary>
  /// Gets a <see cref=»T:OpenQA.Selenium.Screenshot»/> object representing the image of the page on the screen.
  /// </summary>
  ///
  /// <returns>
  /// A <see cref=»T:OpenQA.Selenium.Screenshot»/> object containing the image.
  /// </returns>
  Screenshot GetScreenshot();
}

Этот интерфейс реализован классами веб-драйверов и может использоваться следующим образом:

1
2
var screenshot = driver.GetScreenshot();
screenshot.SaveAsFile(«<destination file’s full path>», ImageFormat.Png);

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

Хотя даже снимков экрана не всегда достаточно. Например, вы можете увидеть ожидаемый элемент на странице, но тест по-прежнему не проходит, сообщая, что не находит его, возможно, из-за неправильного селектора, который приводит к неудачному поиску элемента. Таким образом, вместо (или в дополнение) снимка экрана, вы также можете захватить источник страницы в виде HTML. В интерфейсе PageSource свойство IWebDriver (реализуемое всеми веб-драйверами):

01
02
03
04
05
06
07
08
09
10
11
12
13
/// <summary>
/// Gets the source of the page last loaded by the browser.
/// </summary>
/// <remarks>
/// If the page has been modified after loading (for example, by JavaScript)
/// there is no guarantee that the returned text is that of the modified page.
/// Please consult the documentation of the particular driver being used to
/// determine whether the returned text reflects the current state of the page
/// or the text last sent by the web server.
/// representation of the underlying DOM: do not expect it to be formatted
/// or escaped in the same way as the response sent from the web server.
/// </remarks>
string PageSource { get;

Как и в случае с ITakesScreenshot вы можете реализовать метод, который захватывает исходный код страницы и сохраняет его в файле для последующей проверки:

1
File.WriteAllText(«<Destination file’s full path>», driver.PageSource);

Вы действительно не хотите делать скриншоты и источники страниц всех страниц, которые вы посещаете, и для прохождения тестов; иначе вам придется пройти через тысячи из них, когда что-то пойдет не так. Вместо этого вы должны захватывать их только в случае сбоя теста или в противном случае, когда вам нужно больше информации для устранения неполадок. Чтобы избежать загрязнения кода слишком большим количеством блоков try-catch и во избежание дублирования кода, вы должны поместить все ваши элементы поиска и утверждения в один класс и обернуть их try-catch, а затем захватить скриншот и / или исходный код страницы в блоке catch , Вот немного кода, который вы могли бы использовать для выполнения действий с элементом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public void Execute(By by, Action<IWebElement> action)
{
    try
    {
        var element = WebDriver.FindElement(by);
        action(element);
    }
    catch
    {
        var capturer = new Capturer(WebDriver);
        capturer.CaptureScreenshot();
        capturer.CapturePageSource();
        throw;
    }
}

Класс Capturer может быть реализован как:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public class Capturer
{
    public static string OutputFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, «FailedTests»);
 
    private readonly RemoteWebDriver _webDriver;
 
    public Capturer(RemoteWebDriver webDriver)
    {
        _webDriver = webDriver;
    }
 
    public void CaptureScreenshot(string fileName = null)
    {
        var camera = (ITakesScreenshot) _webDriver;
        var screenshot = camera.GetScreenshot();
 
        var screenShotPath = GetOutputFilePath(fileName, «png»);
        screenshot.SaveAsFile(screenShotPath, ImageFormat.Png);
    }
 
    public void CapturePageSource(string fileName = null)
    {
        var filePath = GetOutputFilePath(fileName, «html»);
        File.WriteAllText(filePath, _webDriver.PageSource);
    }
 
    private string GetOutputFilePath(string fileName, string fileExtension)
    {
        if (!Directory.Exists(OutputFolder))
            Directory.CreateDirectory(OutputFolder);
 
        var windowTitle = _webDriver.Title;
        fileName = fileName ??
                   string.Format(«{0}{1}.{2}», windowTitle, DateTime.Now.ToFileTime(), fileExtension).Replace(‘:’, ‘.’);
        var outputPath = Path.Combine(OutputFolder, fileName);
        var pathChars = Path.GetInvalidPathChars();
        var stringBuilder = new StringBuilder(outputPath);
 
        foreach (var item in pathChars)
            stringBuilder.Replace(item, ‘.’);
 
        var screenShotPath = stringBuilder.ToString();
        return screenShotPath;
    }
}

Эта реализация сохраняет скриншот и исходный код HTML в папке FailedTests рядом с тестами, но вы можете изменить ее, если хотите другое поведение.

Хотя я показал только методы, специфичные для Selenium, подобные API существуют во всех известных мне средах автоматизации и могут быть легко использованы.


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

Большую часть кода, показанного в этой статье, можно найти в образце репозитория MvcMusicStore, который мы видели в прошлой статье. Стоит также отметить, что большая часть кода в MvcMusicStore была заимствована из базы кода Seleno , поэтому, если вы хотите увидеть много интересных трюков, вы можете попробовать Seleno. Отказ от ответственности: я являюсь соучредителем организации TestStack и участником Seleno.

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