Автор: А Сян
предисловие
КёнсуПроект (бывшая покупка JD.com) как стратегический бизнес JD.com имеет десятки миллионов порталов трафика. В целях обеспечения стабильной работы онлайн-бизнеса ежемесячно проводятся учения по аварийному восстановлению интерфейса, в основном включающие небольшие программы и версии H5, требующие соответствующей даунгрейд-обработки для каждой страницы и модуля в нештатных условиях и отсутствия пустых окон. , неупорядоченные стили и т. д. Необоснованные сообщения об ошибках и другие проблемы с опытом. Исходный процесс аварийного восстановления: апплет (режим связи изменен на Https) и H5 модифицируют возврат интерфейса через Whistle, чтобы имитировать нештатные ситуации, и убедиться, что обработка понижения каждой страницы и модуля соответствует ожиданиям. Упражнение по аварийному восстановлению — это длительная и непрерывная работа, включающая в себя множество функций и сценариев страницы.Аномальная симуляция сценариев ручного переключения приводит к низкой эффективности упражнения.Поэтому мы хотим повысить эффективность НИОКР путем разработки инструментов автоматизированного тестирования. , так что аварийное восстановление может быть выполнено в любое время. Сценарии Jingxi H5 и мини-программы сильно различаются, поэтому процесс автоматизированного тестирования делится на H5 и мини-программы, начиная с H5.
Таким образом, мы надеемся, что инструменты автоматизированного тестирования Jingxi H5 могут обеспечить следующие функции:
- Посетите целевую страницу и сделайте скриншот страницы;
- Установить UA (имитировать разные каналы: WeChat, мобильный QQ, другие браузеры и т. д.);
- Имитировать пользовательские операции щелчка и скольжения страницы;
- Перехват сети и имитация нештатных ситуаций (код ответа интерфейса 500, аномальные данные, возвращаемые интерфейсом);
- Манипуляции с кэшированными данными (моделирование сценариев с кэшем или без него и т. д.).
Технический отбор
Когда дело доходит до автоматического тестирования Интернета, многие люди знакомы с Selenium 2.0 (Selenium WebDriver), который поддерживает несколько платформ, несколько языков и несколько браузеров (браузеры управляются различными драйверами браузера) и предоставляет многофункциональный Интерфейс API. С развитием интерфейсной технологии Selenium 2.0 постепенно демонстрирует недостатки, связанные со сложной установкой среды, недружественными вызовами API и низкой производительностью. Новое поколение инструментов автоматизированного тестирования, Puppeteer, проще в установке, имеет лучшую производительность и эффективнее, чем среда Selenium WebDriver.API для выполнения Javascript в браузере проще, а также предоставляет такие функции, как перехват сети.
Puppeteer— это библиотека Node, предоставляющая набор высокоуровневых API-интерфейсов, управляемых протоколом Devtools.
Chromium
илиChrome
браузер.Puppeteer
По умолчаниюHeadless
режим, но режим «заголовок» можно запустить, изменив файл конфигурации.
Официально описанные функции:
- создать страницу PDF;
- Возьмите SPA (одностраничное приложение) и создайте предварительно обработанный контент (например, «SSR», рендеринг на стороне сервера);
- Автоматическая отправка форм, тестирование пользовательского интерфейса, ввод с клавиатуры и т. д.;
- Создайте постоянно обновляемую автоматизированную среду тестирования для выполнения тестов непосредственно в последней версии Chrome с использованием JavaScript и новейших функций браузера;
- Захватите трассировку временной шкалы веб-сайта, чтобы помочь проанализировать проблемы с производительностью;
- Протестируйте расширение браузера.
Puppeteer предоставляет способ запуска экземпляра Chromium. Когда Puppeteer подключается к экземпляру Chromium, он создает объект Browser через puppeteer.launch или puppeteer.connect , создает экземпляр Page через Browser, переходит к URL-адресу и сохраняет снимок экрана. Экземпляр браузера может иметь несколько экземпляров страницы. Вот типичный пример автоматизации с Puppeteer:
const puppeteer = require('puppeteer');
puppeteer.launch().then(async browser => {
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'screenshot.png'});
await browser.close();
});
Подводя итог, мы решили разработать инструмент автоматического тестирования для упражнения по аварийному восстановлению на домашней странице Jingxi на основе Puppeteer, С помощью ряда API-интерфейсов, предоставляемых Puppeteer, мы можем автоматизировать процесс доступа к целевым страницам, моделирования ненормальных сценариев и создания скриншоты. Наконец, вручную сравните снимки экрана, чтобы определить, соответствует ли обработка понижения версии страницы ожиданиям и является ли взаимодействие с пользователем удобным.
План реализации
Мы разделяем процесс аварийного восстановления на две части: автоматизированный процесс и ручная работа.
Автоматизированный процесс:
- Имитировать доступ пользователя к операции страницы;
- Перехватывать сетевые запросы, модифицировать данные, возвращаемые интерфейсом, и моделировать аномальные сценарии (интерфейс возвращает 500, аномальные данные и т. д.);
- Создание скриншотов.
Ручная операция:
После выполнения автоматизированного сценария вручную сравните скриншоты каждой сцены, чтобы определить, соответствует ли она ожиданиям.
Схема блок-схемы:
Рекорд развития
Установите Puppeteer, с чем вы можете столкнуться
После инициализации проекта через npm init вы можете установить зависимости Puppeteer:
npm i puppeteer
: автоматическая загрузка последней версии Chromium при установке.
или
(Невозможно создать скриншоты)npm i puppeteer-core
: Chromium не загружается автоматически при установке.
Кроме того, в процессе установки может появиться сообщение об ошибке из-за загрузки Chromium.Официальный сайт рекомендует пройтиnpm i --save puppeteer --ignore-scripts
Заблокируйте загрузку Chromium, а затем загрузите его вручнуюChromium.
После загрузки вручную необходимо настроить указанный путь и изменить файл index.js.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({
// 运行 Chromium 或 Chrome 可执行文件的路径(相对路径)
executablePath: './chrome-mac/Chromium.app/Contents/MacOS/Chromium',
headless: false
});
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({path: 'screenshot.png'});
browser.close();
})();
Быстро создавайте тестовые случаи
Чтобы улучшить ремонтопригодность и масштабируемость тестового сценария, мы настраиваем информацию тестового примера в файлы JSON, чтобы при написании тестового сценария нам нужно было сосредоточиться только на реализации процесса тестирования.
Конфигурация данных тестового примера JSON включает公用数据(global)
а также私有数据
:
公用数据(global)
: данные, необходимые для каждого теста, такие как адрес целевой страницы, имя, описание, тип устройства и т. д. для моделируемого доступа.
私有数据
: данные, характерные для каждого тестового случая, такие как информация о тестовом модуле, адрес API, тестовый сценарий, ожидаемый результат, имя снимка экрана и другие данные.
{
"global": {
"url": "https://wqs.jd.com/xxx/index.shtml",
"pageName": "index",
"pageDesc": "首页",
"device": "iPhone 7"
},
"homePageApi": {
"id": 1,
"module": "home_page_api",
"moduleDesc": "首页主接口",
"api": "https://wqcoss.jd.com/xxx",
"operation": "模拟响应码 500",
"expectRules": [
"1. 显示异常信息、刷新按钮",
"2. 点击刷新按钮,显示异常信息",
"3. 恢复网络,点击刷新按钮,显示正常数据"
],
"screenshot": [
{
"name": "normal",
"desc": "正常场景"
},
{
"name": "500_cache",
"desc": "有缓存-返回500"
},
{
"name": "500_no_cache",
"desc": "无缓存-返回500"
},
{
"name": "500_no_cache_reload",
"desc": "无缓存-返回500-点击刷新按钮"
},
{
"name": "500_no_cache_recover",
"desc": "无缓存-返回500-恢复网络"
}
]
},
…
}
Пишите тестовые скрипты
В качестве примера мы возьмем тестовый пример основного интерфейса домашней страницы Jingxi и используем интерфейс модуля, чтобы вернуть ненормальный сценарий кода ответа 500, чтобы проверить, совершенен ли механизм обработки исключений основного интерфейса и удобен ли пользовательский интерфейс. .
ожидаемый результат:
- Когда есть кеш, отображать кешированные данные
- Отображать аномальную информацию и кнопку обновления без кеша
- Нажмите кнопку обновления, чтобы отобразить информацию об исключении.
- Восстановите сеть, нажмите кнопку обновления и отобразите обычные данные.
Процесс тестирования:
Реализация сценария:
В соответствии с процессом тестирования и настроенной информацией о тестовом примере напишите тестовый сценарий для реализации сценария тестового примера:
- посетить страницу
await page.goto(url)
- Создать скриншот
await page.screenshot({
path: './screenshot/index_home_page_500.png'
})
- Перехват запросов интерфейса
async test () => {
... // 创建 Page 实例,访问首页
await page.setRequestInterception(true) // 设置拦截请求
page.on("request", interceptionEvent) // 监听请求事件,当请求发起后页面会触发这个事件
... // 刷新页面,触发请求拦截,生成测试场景截图
}
Если тестовый пример должен перехватывать разные запросы или моделировать несколько сценариев, вам необходимо настроить несколько событий прослушивателя запросов. И после завершения выполнения события прослушиватель событий должен быть удален, прежде чем можно будет продолжить следующий прослушиватель событий.
Добавить прослушиватель событий:page.on("request", eventFunction)
Удалить прослушиватели событий:page.off("request", eventFunction)
// 设置拦截请求
await page.setRequestInterception(true)
const iconInterception1 = requestInterception(api, "body")
// 添加事件 1 监听
page.on("request", iconInterception1)
await page.goto(url)
await page.screenshot({
path: './screenshot/1.png'
})
// 移除事件 1 监听
page.off("request", iconInterception1)
const iconInterception2 = requestInterception(api, "body", )
// 添加事件 2 监听
page.on("request", iconInterception2)
await page.goto(url)
await page.screenshot({
path: './screenshot/2.png'
})
// 移除事件 2 监听
page.off("request", iconInterception2)
- Моделируйте сценарии ненормальных данных и создавайте фиктивные данные.
function requestInterception (api, setProps, setValue) {
let mockData
switch (setProps) {
case "status": // 修改返回状态码
mockData = {
status: setValue
}
break
case "contentType": // 修改返回内容类型
mockData = {
contentType: setValue
}
break
case "body": // 修改返回数据
mockData = {
contentType: getMockResponse(setValue)
}
break
default:
break
}
return async req => {
// 如果是需要拦截的 API,则通过 req.respond(mockData) 修改返回数据,否则 continue 继续请求别的
if (req.url().includes(api)) { // 拦截 API
req.respond(mockData) // 修改返回数据
return false // 处理完了某个请求必须退出,不再执行 continue
}
req.continue()
}
Аналоговый интерфейс возвращает 500:
const interception500 = requestInterception(api, 'status', 500)
page.on("request", interception500) // 当请求发起后页面会触发这个事件
Смоделируйте аномальные данные:
const iconInterception = requestInterception(api, "body", {
"data": {
"modules": [{
"tpl": "3000",
"content": []
}]
}
})
page.on("request", iconInterception)
Существует две схемы реализации генерирования фиктивных данных, которые можно определить в зависимости от реальной ситуации:
- Чтобы сгенерировать фиктивные данные напрямую, изменив данные, фактически возвращаемые интерфейсом, вам необходимо сначала получить возвращаемые данные в реальном времени из интерфейса.
- Сохраняйте полные данные интерфейса локально и создавайте фиктивные данные, изменяя локально сохраненные данные (случаи, описанные в этой статье, основаны на этом решении).
Если вы выберете первое решение, вам нужно сначала перехватить запрос интерфейса, получить возвращаемые данные интерфейса в реальном времени с помощью req.response() и изменить возвращаемые данные в реальном времени как фиктивные данные в соответствии с тестовым сценарием.
Поскольку данные, возвращаемые интерфейсом страницы Jingxi H5, имеют формат JSONP, при моделировании возвращаемых данных информация обратного вызова JSONP должна быть сначала перехвачена, а затем возвращена после объединения с имитируемыми данными;
function requestInterception (api, setProps, setValue) {
let mockData
switch (setProps) {
case "status":
mockData = {
status: setValue
}
break
case "contentType":
mockData = {
contentType: setValue
}
break
default:
break
}
return async req => {
if (req.url().includes(api)) {
if (setProps === "body") {
const callback = getUrlParam("callback", req.url()) // 获取 callback 信息
const localData = getLocalMockResponse(api) // 匹配 API ,获取本地存储数据
mockData = {
body: getResponseMockLocalData(localData, setValue, callback, api) // 生成 mock 数据
}
}
req.respond(mockData) // 设置返回数据
return false
}
req.continue()
}
}
- очистить кэш
page.evaluate(() => {
try {
localStorage.clear()
sessionStorage.clear()
} catch (e) {
console.log(e)
}
})
- Нажмите кнопку обновления
await page.waitFor(".page-error__refresh-btn") // 可以传 CSS 选择器,也可以传时间(单位毫秒)
await page.click(".page-error__refresh-btn")
Прежде чем имитировать нажатие кнопки обновления, вам нужно дождаться завершения рендеринга кнопки, прежде чем инициировать нажатие кнопки. (Чтобы предотвратить отображение DOM после обновления страницы, будет сообщено об ошибке, поскольку DOM не может быть найден)
- Разблокировать, восстановить сеть
await page.setRequestInterception(false)
Запустите скрипт и отладку
Поскольку инструменты тестирования на первом этапе еще не платформизированы, процесс автоматизированного тестирования сначала запускается вводом командной строки в терминале и запуском скрипта.
В файле package.json вашего проекта используйте поле scripts для определения команд скрипта:
"scripts": {
"test:real": "node ./pages/index/index.js",
"test:mock": "node ./pages/index-mock/index.js"
},
бегать:
Вырежьте путь к корневому каталогу проекта в терминале, введите следующую командную строку, вы можете запустить инструмент тестирования и запустить тестовый скрипт.
- npm run test:real // 接口真实返回的数据测试
- npm run test:mock // 使用本地 mock 数据测试
отладка:
Перед включением режима отладки необходимо понятьHeadless Chrome
.
Headless Chrome
, безголовый режим, безинтерфейсная форма браузера, вы можете запускать тестовый скрипт в командной строке, не открывая браузер, и можете выполнять все пользовательские операции точно так же, как в реальном браузере, не беспокоясь о браузере при запуске тестового скрипта. внешние помехи, нет необходимости использовать какое-либо устройство отображения, чтобы сделать автоматизированное тестирование более стабильным.
Puppeteer
По умолчанию работает в безголовом режиме.
Затем для включения режима отладки необходимо отменить безголовый режим и выполнить автоматизированное тестирование в сценарии открытия браузера. Поэтому в скрипт командной строки добавляются параметры «Отменить безголовый режим» и «Открыть инструменты разработчика», а тестовый скрипт через полученные параметры определяет, включать ли режим отладки.
const headless = process.argv[2] !== 'head' // 获取是否开启无头模式参数
const devtools = process.argv[3] === 'dev' // 获取是否打开开发者工具参数
const browser = await puppeteer.launch({
executablePath: browserPath,
headless,
devtools
})
Переключитесь на путь к корневому каталогу проекта в терминале и введите следующую командную строку, чтобы включить режим отладки и запустить тестовый скрипт.
- npm run test:mock head // 打开 Chromium 窗口
- npm run test:mock head dev // 打开 Chromium 窗口 和 开发者工具窗口
-
head
Параметры: отменить безголовый режим, открыть окно Chromium для запуска скрипта; -
head dev
Параметры: Запустите скрипт в открытом окне Chromium, откройте окно Devtools и включите режим отладки.
Результаты теста
Результаты ручного сравнения скриншотов:
Пример запуска скрипта:
Дополнительные тестовые сценарии для реализации
1. Захватить область от верха страницы до указанного DOM (контент может превышать длинное изображение на одном экране)
Puppeteer предоставляет четыре метода создания скриншотов:
(1)截取一屏内容(默认普通截屏);
(2)截取指定 DOM;
(3)截取全屏;
(4)指定裁剪区域,可设置 x、y、width、height。 x, y 是相对页面左上角。但只能截取一屏的内容,超出一屏不展示。
Дооснащение по четвертому способу:
- Получите значения координат X, Y указанного DOM через сетевой метод JavaScript GetBoundingClientRect ();
- Сбросить высоту окна просмотра через page.setViewport();
- Вызовите API снимков экрана для создания снимков экрана.
async function screenshotToElement (page, selector, path) {
try {
await page.waitForSelector(selector)
let clip = await page.evaluate(selector => {
const element = document.querySelector(selector)
let { x, y, width, height } = element.getBoundingClientRect()
return {
x: 0,
y: 0,
width,
height: M(y),
}
}, selector)
await page.setViewport(clip)
await page.screenshot({
path: path,
clip: clip
})
} catch (e) {
console.log(e)
}
}
-
height: y
: в начало указанного DOM, исключая DOM; -
height: y + height
: в конец указанного DOM, включая DOM; - Собственный метод Javascript getBoundingClientRect() получает позиционирование элемента DOM, а значения ширины и высоты могут быть десятичными, в то время как метод setViewport() Puppeteer не поддерживает десятичные числа, поэтому полученную информацию о позиционировании элемента DOM необходимо округлить.
2. Смоделируйте различные каналы, такие как мобильные сценарии QQ:
// 设置 UA
await page.setUserAgent("Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27 QQ/6.7.1.416 V1_IPH_SQ_6.7.1_1_APP_A Pixel/750 Core/UIWebView NetType/4G QBWebViewType/1")
3. Прокрутите страницу
await page.evaluate((top) => {
window.scrollTo(0, top)
}, top)
page.evaluate(pageFunction, …args): выполнение кода JavaScript в контексте текущего экземпляра страницы.
4. Следите за событиями сбоя страницы
// 当页面崩溃时触发
page.on('error', (e) => {
console.log(e)
})
Эпилог
Первая фаза автоматизации H5 подошла к концу, и отработка аварийного восстановления была полуавтоматической.Запуская тестовые сценарии на терминале, имитируя ненормальные сценарии, можно автоматически генерировать снимки экрана, а затем с помощью ручного сравнения снимков экрана, можно судить о том, соответствуют ли результаты учений ожиданиям. Он был использован в ежемесячных учениях по аварийному восстановлению.
С итерацией бизнеса Jingxi страница также будет обновляться и пересматриваться, поэтому тестовые примеры также необходимо поддерживать и постоянно обновлять. В дальнейшем мы продолжим оптимизировать автоматизированные инструменты, делиться тестовыми сценариями, автоматически сравнивать результаты тестов на основе сгенерированных снимков экрана, чтобы увидеть, соответствуют ли они ожиданиям, сохранять данные, преобразовывать результаты тестов в документы и автоматически отправлять электронные письма. Автоматизированное тестирование, основанное на упражнениях по аварийному восстановлению, также может расширить мониторинг рекламных площадок, отчетность по данным и автоматизированное тестирование...
Путь к автоматизированному тестированию на главной странице Jingxi далек от завершения, и есть еще много мест для оптимизации и расширения.Далее мы продолжим поэтапно оптимизировать инструменты автоматического тестирования, так что следите за обновлениями!
Ссылки по теме
Добро пожаловать в блог Bump Labs:aotu.io
Или обратите внимание на официальный аккаунт AOTULabs и время от времени публикуйте статьи: