Одна из самых замечательных задач, с которыми когда-либо сталкивался отдел QA EastBanc Technologies, — создание автоматизированной системы тестирования веб-сайтов.
Это электронная газета, реализованная как информационно-новостной портал.
Основная причина необходимости создания системы автоматизированного тестирования заключалась в том, что приложение планировалось перейти на новую CMS (так называемый PageBuilder), которая должна заменить несколько других CMS, ранее использовавшихся для публикации контента в различных разделах сайта.
При такой миграции очень важно избегать ошибок, чтобы контент, публикуемый через новую CMS на различных типах страниц, выглядел уместно.
Перед нами не стоит задача проверить все страницы на соответствие нашим тестам.
Наша задача — выявить ошибки PageBuilder, проверить надежность верстки страниц, созданных свежеиспеченным PageBuilder, и обратить внимание редакции Washington Post на те нюансы наполнения конкретной страницы контентом, которые могут привести к потенциальным проблемам с отображением страниц.
.
Создание системы тестирования находится в активной разработке, но некоторые моменты, которые нам кажутся интересными, уже могут быть представлены широкой публике.
Прежде чем мы это сделаем, необходимо отметить одну особенность проекта: все наше тестирование происходит «снаружи».
Те.
Мы, как и любой другой пользователь, используем для тестирования боевую версию сайта.
Выбор инструментов тестирования макета
Изучив Интернет, мы остановились на следующих подходах и инструментах.Для тестирования фрейма страницы мы использовали фреймворк Гален , который позже был интегрирован с testNG. Естественно, прохождение теста Галена для страничного фрейма не означает, что макет действителен.
Помимо расположения блоков, вам также необходимо проверить отображение различных элементов внутри блока.
Мы решили протестировать внутреннее содержимое блоков, сравнивая скриншоты.
Скриншот-тесты включают в себя различные логотипы, кнопки, какие-то блоки со специфическим отображением — все, до чего Гален не может дотянуться и что сложно/невозможно проверить функциональными тестами.
Лазурный цвет - протестировано Галеном, заполнен зеленым - скриншот тестов: Осторожно! Большая фотография Скрытый текст
Тесты Галена и Скриншота могут успешно заменить некоторые функциональные тесты, выигрывая иногда в ясности, иногда в скорости, а иногда и в том, и в другом.
Метод тестирования для конкретного случая мы подбираем путем коллективного обсуждения тестового сценария для каждого типа страниц исходя из критериев производительности, простоты поддержки, полноты тестового покрытия и наглядности.
Например, есть 2 блока, для проверки которых мы изначально написали функциональные тесты: «Наиболее читаемый» и «Информационный».
Теперь проверяем первый скриншотами, а второй тестом Галена.
Блок MostRead, скриншот-тест:
По поводу функционального теста: строк кода стало значительно меньше, полнота тестового покрытия возросла, а обновление теста при изменении внешнего вида того или иного блока на странице не занимает много времени.
Тестирование этого блока описано в главе, посвященной методу скриншотов.
Информационный блок WaPo:
Гален легко справляется с проверкой соответствия текста и ссылок данного блока: сами ссылки указываются в локаторе, а соответствие текста осуществляется внутренней галеновой проверкой.
Что касается функционального теста, то полнота тестового покрытия не изменилась, но за счет того, что проверки проводятся в рамках одного теста, мы существенно экономим время.
В нашей системе автоматизированного тестирования используются: Java, Maven, TestNG, Селен ВебДрайвер Селеновая сетка Гален Фреймворк .
Кроссплатформенный набор утилит активно помогает нам в создании тестов на основе скриншотов.
Сразу хочу отметить, что код теста мы пишем на Java, используя паттерн PageObject и фреймворк Яндекса — HTML-элементы .
Maven и testNG используются для запуска тестов.
Чтобы было проще запускать тесты, просматривать историю запусков тестов и просматривать отчеты без привлечения высококвалифицированных специалистов, мы разрабатываем отдельное приложение — Dashboard. Стоит подчеркнуть, что сейчас мы все еще находимся на стадии исследования того, как правильно организовать весь процесс тестирования, и еще не все подходы до конца освоены и изучены.
Тестирование с помощью Galen Framework
Galen Framework имеет множество несомненных преимуществ: это гибкий, простой в использовании инструмент с обширными возможностями тестирования адаптивного дизайна.Кроме того, он хорошо документирован и в настоящий момент активно развивается.
Galen Framework уже достаточно подробно описан в одном из статьи .
Если кратко описать принцип работы с Galen, то он выглядит примерно так: вы пишете спецификацию страницы (так называемый spec-файл), используя специальный, хорошо документированный и интуитивно понятный синтаксис.
Файл спецификации описывает относительное положение, размер, отступы, вложенность элементов страницы и некоторые другие параметры и условия, которым должен соответствовать макет страницы; вы даже можете проверить целостность текста внутри элемента.
И все эти проверки будут применяться в зависимости от указанных нами тегов.
Теги в файле спецификации можно установить следующим образом:
Гален выполняет все проверки, а затем формирует визуальный отчет в виде html-файла.
В отчете указывается, какие именно проверки не прошли проверку для данного теста, и для каждой из неудавшихся проверок вы можете увидеть полный скриншот тестируемой страницы, где будут выделены элементы, не прошедшие конкретную проверку.
Например, неудавшийся тест на расстояние между соседними элементами будет выглядеть в отчете так:
При нажатии на галочку, выделенную красным, отображается скриншот всей проверяемой страницы с выделенными элементами следующим образом:
Galen Framework принимает на вход следующие параметры:
- браузер, в котором будет происходить проверка
- разрешение, с которым можно запустить тест
- URL тестируемой страницы
- Javascript-файл, который необходимо (при необходимости) применить к запускаемой странице перед проверкой .
spec-файла (например, если нужно проверить отображение страницы для пользователя, авторизовавшегося на сайте)
- имя файла .
spec, который нужно запустить
- теги, которые необходимо применить к проверкам файла .
spec (например: рабочий стол, все, если мы тестируем макет рабочего стола).
После того, как мы определились с инструментом для тестирования фреймворка сайта, следующей задачей стал выбор схемы, которая бы обеспечивала максимальное покрытие страницы тестами Галена.
Выбор страницы для тестирования из подмножества страниц одного типа
Какие страницы выбрать для тестирования верстки, если тест предполагает проверку множества похожих страниц? Мы решили, не особо заморачиваясь, выбирать случайную страницу из подмножества каждый раз, когда запускаем набор тестов (т. е.для тестирования подмножества страниц с рецептами мы выбрали один из рецептов и передали его URL-адрес всем тестам макета).
Поскольку проверять все страницы задания не стоит, поэтому оптимальным показался вариант выбора случайной страницы.
URL-адрес случайной страницы из подмножества тестируемых страниц передается Галену с использованием метода, общего для всех тестов в нашей автоматизированной системе тестирования веб-сайтов (кроме тестов тестирования верстки, у нас также есть функциональные тесты и скриншоты).
Например, есть 2 варианта отображения однотипных страниц - страниц кулинарных рецептов, один из которых содержит ошибку в верстке:
Из примера видно, что блок «Самые читаемые», который должен располагаться в правой колонке страницы, на левой странице расположен в основной части, а не в правой.
Чтобы убедиться, что таких проблем нет, нужно проверить большое количество страниц и учесть множество факторов.
При каких разрешениях следует запускать тесты?
Сначала пришла идея выбрать наиболее распространенные устройства и использовать их разрешения для запуска тестов.Однако отчетливо просматривающаяся тенденция ускоренной мобилизации планеты не позволяет выявить (и тем более прогнозировать) каких-либо безусловных лидеров в этой области.
Устройств, позволяющих просматривать веб-приложения, существует великое множество, и унифицировать разрешения для таких устройств сегодня совершенно немодно.
Внезапная идея о том, что адаптивный дизайн должен корректно отображаться при любом разрешении, спасла наши умы и остановила дальнейшие исследования в этой области.
Решение было принято: тестируем верстку на всех допустимых разрешениях.
Все разрешения от минимальной ширины области просмотра = 241 пикселей (браузер не уменьшается) до максимальной ширины области просмотра = 1920 пикселей (верхний предел — это простая сила воли) были назначены действительными разрешениями.
У нас еще не было страниц, где высота области просмотра для целей автоматического тестирования была определяющим параметром, поэтому мы пока не обращаем внимания на высоту.
Как протестировать макет во всех разрешениях? Для начала весь диапазон допустимых разрешений был разбит на диапазоны разных раскладок.
Сами макеты «резиновые», но разное расположение блоков позволяет сделать различие.
Определить границы макетов несложно — тянем за угол браузера и смотрим, в какой граничной точке меняются блоки страницы: их относительное положение, количество и/или поведение.
Для простоты мы учитываем только ширину области просмотра.
В результате получается следующая таблица: НАСТОЛЬНЫЙ СТОЛ: максимум 1920 пикселей, минимум 1018 пикселей; НОУТБУК: максимум 1017 пикселей, минимум 769 пикселей; ПЛАНШЕТ: максимум 768 пикселей, минимум 481 пиксель; МОБИЛЬНЫЙ: максимум 480 пикселей, минимум 361 пиксель; SMALL_MOBILE: максимум 360 пикселей, минимум 280 пикселей.
Кстати, макет SMALL_MOBILE мы решили пока не тестировать, так как количество пользователей, просматривающих Washington Post на устройствах с таким разрешением, катастрофически мало (вывод умозрительный, и проблем с добавлением его при тестировании в будущем не будет) .
Для тестирования осталось 4 диапазона, с разной планировкой.
Ниже приведен код для запуска теста Галена для разрешения рабочего стола: Скрытый текст
Метод ignoreGalenActions предоставляет Галену все предварительные условия в форме, которая может быть обработана этой структурой:@Test(groups = { "Galen" }) @WebTest(value = "Verify that layout of Article page is not broken on desktop screen resolution.", bugs = {"#5599", "#5601", "#5600"}) public void testArticlepageLayoutOnDesktop() throws Exception { GalenActionsBuilder builder = new GalenActionsBuilder() //advertisement frames become visible only if advertisement placeholder is visible .
waitForVisible(5, ".
pb-f-ad-leaderboard > div> div > div > iframe") .
scrollToElement(".
pb-f-ad-flex") .
waitForVisible(5, ".
pb-f-ad-flex > div > div > iframe") .
scrollToElement(".
pb-f-ad-flex-2") .
waitForVisible(5, ".
pb-f-ad-flex-2 > div > div > iframe") .
scrollToElement(".
pb-f-ad-flex-3") .
waitForVisible(5, ".
pb-f-ad-flex-3 > div > div > iframe") .
injectJavascript("/js/scroll_to_top.js") .
waitSeconds(2) .
check("/article.spec", Arrays.asList("all", "desktop")); invokeGalenActions(ArticlePage.getRandomArticlePage(), builder.build(), getRandomResolution(DESKTOP)); }
protected void invokeGalenActions(String url, List<GalenPageAction> actions, Dimension. sizes) throws Exception {
run(url, actions, recalculateSizes(sizes));
}
GalenActionBuilder позволяет легко добавлять выполнение как собственных предварительных условий Galen (.
waitForVisible(5, ".
pb-f-ad-leaderboard > div> div > div > iframe"), так и наших собственных (.
scrollToElement(".
pb- f -ad-flex")): public class GalenActionsBuilder {
private boolean built;
private final List<GalenPageAction> actions = new ArrayList<>();
public GalenActionsBuilder waitFor(Integer seconds, GalenPageActionWait.UntilType type, Locator locator) {
checkUsed();
GalenPageActionWait.Until u = new GalenPageActionWait.Until(type, locator);
GalenPageActionWait a = new GalenPageActionWait();
a.setTimeout(seconds * 1000);
a.setUntilElements(Lists.newArrayList(u));
a.setOriginalCommand("wait " + seconds + "s until " + type.toString() + " " + locator.getLocatorValue());
actions.add(a);
return this;
}
public GalenActionsBuilder waitSeconds(Integer seconds) {
checkUsed();
GalenPageActionWait a = new GalenPageActionWait();
a.setTimeout(seconds * 1000);
a.setOriginalCommand("wait " + seconds + "s");
actions.add(a);
return this;
}
public GalenActionsBuilder waitForExist(Integer seconds, String cssSelector) {
return waitFor(seconds, GalenPageActionWait.UntilType.EXIST, Locator.css(cssSelector));
}
public GalenActionsBuilder waitForVisible(Integer seconds, String cssSelector) {
return waitFor(seconds, GalenPageActionWait.UntilType.VISIBLE, Locator.css(cssSelector));
}
public GalenActionsBuilder waitForHidden(Integer seconds, String cssSelector) {
return waitFor(seconds, GalenPageActionWait.UntilType.HIDDEN, Locator.css(cssSelector));
}
public GalenActionsBuilder waitForGone(Integer seconds, String cssSelector) {
return waitFor(seconds, GalenPageActionWait.UntilType.GONE, Locator.css(cssSelector));
}
public GalenActionsBuilder waitForExist(Integer seconds, Locator locator) {
return waitFor(seconds, GalenPageActionWait.UntilType.EXIST, locator);
}
public GalenActionsBuilder waitForVisible(Integer seconds, Locator locator) {
return waitFor(seconds, GalenPageActionWait.UntilType.VISIBLE, locator);
}
public GalenActionsBuilder waitForHidden(Integer seconds, Locator locator) {
return waitFor(seconds, GalenPageActionWait.UntilType.HIDDEN, locator);
}
public GalenActionsBuilder waitForGone(Integer seconds, Locator locator) {
return waitFor(seconds, GalenPageActionWait.UntilType.GONE, locator);
}
public GalenActionsBuilder withCookies(String. cookies) {
checkUsed();
GalenPageActionCookie a = new GalenPageActionCookie()
.
withCookies(cookies); a.setOriginalCommand("cookie " + Joiner.on("; ").
join(cookies)); actions.add(a); return this; } public GalenActionsBuilder injectJavascript(String javascriptFilePath) { checkUsed(); GalenPageActionInjectJavascript a = new GalenPageActionInjectJavascript(javascriptFilePath); a.setOriginalCommand("inject " + javascriptFilePath); actions.add(a); return this; } public GalenActionsBuilder runJavascript(String javascriptFilePath) { checkUsed(); GalenPageActionRunJavascript a = new GalenPageActionRunJavascript(javascriptFilePath); actions.add(a); return this; } public GalenActionsBuilder runJavascript(String javascriptFilePath, String jsonArgs) { checkUsed(); GalenPageActionRunJavascript a = new GalenPageActionRunJavascript(javascriptFilePath) .
withJsonArguments(jsonArgs); actions.add(a); return this; } public GalenActionsBuilder check(String specFile, List<String> tags) { checkUsed(); GalenPageActionCheck a = new GalenPageActionCheck() .
withSpecs(Arrays.asList(specFile)) .
withIncludedTags(tags); actions.add(a); return this; } public GalenActionsBuilder resize(int width, int height) { checkUsed(); GalenPageActionResize a = new GalenPageActionResize(width, height); a.setOriginalCommand("resize " + GalenUtils.formatScreenSize(new Dimension(width, height))); actions.add(a); return this; } public GalenActionsBuilder open(String url) { checkUsed(); GalenPageActionOpen a = new GalenPageActionOpen(url); a.setOriginalCommand("open " + url); actions.add(a); return this; } private void checkUsed() { if (built) throw new IllegalStateException("Incorrect builder usage error. build() method has been already called"); } public List<GalenPageAction> build() { built = true; return actions; } public GalenActionsBuilder scrollToElement(String locator) { String content = String.format("jQuery(\"%s\")[0].
scrollIntoView(true);", locator); Properties properties = new Properties(); try (InputStream is = getClass().
getResourceAsStream("/test.properties")){
properties.load(is);
} catch (Exception e) {
throw new RuntimeException("I/O Exception during loading configuration", e);
}
String workDirPath = properties.getProperty("work_dir");
String tempDirPath = workDirPath + "\\temp";
String auxJsFile = String.format("%s\\%s.js", tempDirPath, locator.hashCode());
File tempDir = new File(tempDirPath);
tempDir.mkdirs();
try {
PrintWriter writer = new PrintWriter(auxJsFile, "UTF-8");
writer.println(content);
writer.close();
} catch (Exception e) {
throw new RuntimeException("Exception during creating file", e);
}
injectJavascript(auxJsFile);
return this;
}
}
При запуске каждого теста Галену предоставляется случайное разрешение из диапазона для данного макета ( getRandomResolution(DESKTOP)): protected Dimension getRandomResolution(Dimension[] d) {
return getRandomDimensionBetween(d[0], d[1]);
}
private Dimension getRandomDimensionBetween(Dimension d1, Dimension d2) {
double k = Math.random();
int width = (int) (k * (Math.abs(d1.getWidth() - d2.getWidth()) + 1) + Math.min(d1.getWidth(), d2.getWidth()));
int height = (int) (k * (Math.abs(d1.getHeight() - d2.getHeight()) + 1) + Math.min(d1.getHeight(), d2.getHeight()));
return new Dimension(width, height);
}
И, по сути, диапазон разрешения задан так: public static final Dimension[] DESKTOP = {new Dimension(1920, 1080), new Dimension(1018, 1080)};
Таким образом, тестирование путем случайного выбора разрешения из допустимого диапазона и тестовой страницы из подмножества похожих страниц превращается в вероятностный процесс.
Чем чаще мы его запускаем, тем больше разных ошибок находим.
При единственном успешном тесте мы можем только сказать, что данная конкретная страница в данном конкретном разрешении действительна.
Но после 500 успешных запусков можно сказать, что макет в основном жизнеспособен.
Скажем сразу, «500 успешных запусков» — это умозрительная оценка, и здесь нужно смотреть на контент и количество эквивалентных страниц.
Запуск со случайным разрешением очень быстро окупился и сразу выявил одну интересную ошибку, которую мы, скорее всего, не заметили бы при запуске тестов с фиксированным разрешением.
Давайте посмотрим, как нам помогает этот подход, на примере тестирования страницы рецептов.
Тест каркаса страницы рецептов выполняется для диапазона разрешения (ширины области просмотра) от 768 до 1017 пикселей.
Давайте возьмем эту страницу в качестве примера: www.washingtonpost.com/pb/recipes/maple-banana-frozen-yogurt/14143 Тест в граничных точках макета Ноутбука (1017 пикселей и 768 пикселей) не выявил ошибок.
Однако после того, как мы начали запускать тест в случайном разрешении, примерно в половине случаев тесты вылетали, а на скриншотах было видно, что блоки из правого столбца уползают под основной контент.
Правильный взгляд: Осторожно! Большая фотография Скрытый текст
Схема нарушена: Осторожно! Большая фотография Скрытый текст
Метод тестирования на основе снимков экрана
Вдохновленный статья , мы решили использовать метод тестирования на основе скриншотов.Кстати, для тестирования верстки мы изначально опирались на этот метод. Те.
идея заключалась в том, чтобы сравнить полноразмерные скриншоты страницы с заранее подготовленной моделью, заменив все потенциально изменяющиеся элементы заглушками (в качестве заглушки берется заранее выбранное произвольное изображение).
Эти элементы включали изображения, флэш-рекламу и текст. Идея провалилась в основном из-за того, что страницы содержали множество блоков, которые загружались динамически, в результате чего физические размеры сделанных скриншотов и расположение блоков менялись от запуска теста к запуску.
Кроме того, с некоторых пор Chrome потерял возможность делать полноразмерные скриншоты, что также создало ряд проблем.
Тесты на основе скриншотов теперь проверяют те отдельные элементы и блоки на странице, для которых важно отображение, и/или проверка которых с помощью функциональных или функциональных тестов затруднена или невозможна.
Например:
Вот как выглядит блок MostRead на главной страницеwashingtonpost.com (слева) и модель, с которой мы будем сравнивать скриншот этого блока (справа):
Тестовый код выглядит следующим образом: @Test(groups = { "ScreenshotBased" })
@WebTest("Verifies that 'Post Most' block is displayed properly")
public void testMakeupForPostMost() {
HomePage page = new HomePage().
open();
page.preparePostMostForScreenshot();
screenshotHelper.shootAndVerify(page, page.thePostMost, "_thePostMost");
}
Для хранения снимков экрана используется следующая структура каталогов: /models/HomePage/firefox/HomePage_thePostMost.png
Как видно отсюда, для разных браузеров сделаны разные модельные скриншоты нужного блока.
Метод ShootAndVerify() находит путь к модели на основе класса переданной страницы и имени браузера, в котором выполняется тест. Забегая вперед, скажем, работает вполне хорошо, а затем опишем некоторые детали процесса с оговоркой, что не все до конца отлажено.
Как оказывается, снимок необходимого блока может зависеть от многих факторов, таких как:
- версия операционной системы
- тема операционной системы
- браузер и его версия
- Различные варианты сглаживания шрифтов и аппаратное ускорение.
Чтобы размеры блоков, а значит и скриншотов, были одинаковыми, нужно запускать браузер с постоянными размерами.
Вы можете изменить размер окна браузера, используя соответствующий метод веб-драйвера: driver.manage().
window().
setSize(requiredSize).
Но таким образом мы задаем размер окна, а не размер нужной нам видимой области — области просмотра.
Вертикальная полоса прокрутки, кстати, тоже влияет на размер области просмотра, а ее толщина тоже зависит от темы Windows, поэтому нужно это учитывать.
Решением проблемы стал метод калибровки, подгоняющий размер области просмотра под заданные размеры.
После запуска первого теста разница между шириной окна и шириной области просмотра сохраняется в специальном параметре и повторно используется при последующих запусках.
Вторая проблема, с которой мы столкнулись, — разное отображение шрифтов в браузерах из-за настроек сглаживания.
Мы попытались решить проблему, установив различные настройки браузера, такие как: слои.
ускорение.
отключено gfx.font_rendering.cleartype_params.rendering_mode gfx.direct2d.disabled Но, к сожалению, это не помогло.
Кроме того, для сравнения скриншотов утилита ImageMagick использует такой параметр, как fuzz, который задает максимально возможное расхождение между скриншотами.
Мы попытались решить эту проблему, экспериментируя с этим параметром.
Маленький коэффициент размытости не решил проблему, так как количество разных пикселей было очень велико из-за того, что текста было много, а большой коэффициент приводил к тому, что отсутствие некоторых элементов в блоках не решало проблему.
повлиять на тест и привести к потенциально пропущенным ошибкам.
Решением стало дублирование всех настроек различных браузеров на всех виртуальных машинах, на которых проводились тесты, и дублирование самих настроек операционной системы.
Например, тест, проверяющий блок социальных кнопок, в котором не загрузилось одно из изображений.
В отчете доступны следующие ссылки:
картина-модель
скриншот тестируемого устройства:
Результат сравнения этих двух изображений:
CommandException сообщает нам, что сравниваемые изображения отличаются на 251 пиксель:
Также бывают ситуации, когда размеры скриншотов не совпадают. В этом случае мы получим следующий отчет:
Иногда по неизвестным причинам элементы внутри тестируемого блока слегка смещены.
Для таких случаев мы сравниваем не с одной моделью, а с группой моделей, соответствующих маске, т.е.
у нас может быть несколько моделей блокаPostMost со следующими названиями HomePage_thePostMost.png, HomePage_thePostMost(1).
png, и мы рассматриваем все модели действительны.
К счастью, количество таких вариантов конечно, обычно не более 2.
Технические аспекты
Как уже говорилось выше, для написания и запуска тестов используется стек технологий: Java, Maven, TestNG, Selenium, Galen Framework. Кроме того, результаты испытаний отправляются на графит. Тесты запускаются напрямую с помощью Jenkins CIS. Мы не будем подробно останавливаться на том, почему был выбран именно этот набор.Опишем вкратце, как это все взаимосвязано.
Selenium Grid в настоящее время развернут локально на четырех виртуальных машинах Windows 7, на которых работают узлы сетки, и на машине Linux, на которой работает концентратор.
Каждый узел имеет по три экземпляра браузера Firefox и Chrome. Кроме того, на машине с Linux также развернуты Jenkins и Graphite. Тесты Галена запускаются в общем тестовом прогоне благодаря интеграции с TestNG. Для этого был написан соответствующий класс, позволяющий использовать API jav Galen. При реализации взаимодействия TestNG с Galen мы столкнулись с некоторыми проблемами, которые были оперативно решены благодаря взаимодействию с разработчиком Galen. Сам разработчик Galena готов к сотрудничеству и регулярно выпускает обновления для этого инструмента, расширяющие его возможности и делающие его еще удобнее.
Он сам планирует написать документацию по интеграции Galen с TestNG. Функциональные, галенические и скриншотовые тесты разделяются с помощью соответствующего группового параметра, назначенного аннотации «Тест», и их можно запускать отдельно.
Наши выводы
Оба подхода — метод сравнения скриншотов и тестирование с помощью Galen Framework — применимы для тестирования макета страницы.Они удачно дополняют друг друга.
Метод сравнения скриншотов более применим, когда вам нужно протестировать отображение какого-либо отдельного элемента или блока, например, панели обмена в социальных сетях или главного меню в шапке.
Блок может содержать много значков Теги: #qa #тестирование #тестирование с использованием #java #galen framework #метод тестирования на основе снимков экрана #тестирование #инструменты тестирования #адаптивный дизайн #тестирование ИТ-систем
-
Армстронг, Эдвин Говард
19 Oct, 24 -
Выбор Приоритета Запроса Пользователя
19 Oct, 24 -
Приручение Greasemonkey
19 Oct, 24 -
Кнопка «Новый Файл» На Github
19 Oct, 24 -
Стандарт Разработки Приложений Codeigniter
19 Oct, 24