|
|
|
# Метод сбора скриншотов
|
|
|
|
|
|
|
|
## Каталогизация скриншотов
|
|
|
|
|
|
|
|
Разделяйте скриншоты на разделы и подразделы, создав иерархию.
|
|
|
|
Ориентируйтесь на структуру сайта и связь функционала страниц.
|
|
|
|
Офорите её в виде дерева директорий в img.
|
|
|
|
|
|
|
|
Именуйте скриншоты в формате `<раздел>/<страница>_<аспект/состояние>`, где
|
|
|
|
* `<раздел>` — путь к директории относительно img,
|
|
|
|
* `<страница>` — название страницы или группы страниц, объединённых общей функциональностью и интерфейсом,
|
|
|
|
* `<аспект/состояние>` — название выделенного объекта на странице, описание состояния, в котором находится система, или описание ситуации, которая демонстрируется скриншотом (может отсутствовать).
|
|
|
|
|
|
|
|
Например,
|
|
|
|
* `admin/login` — страница авторизации (открытая в рамках демонстрации административной компоненты)
|
|
|
|
* `admin/login_fill` — страница авторизации с заполненными полями данными админской учётной записи
|
|
|
|
* `admin/login_success` — страница, показанная после успешной авторизации как админа
|
|
|
|
* `admin/index_auth` — главная страница сайта с выделенной облатью с данными авторизованного администратора
|
|
|
|
|
|
|
|
## Скрипты сбора скриншотов
|
|
|
|
|
|
|
|
Под скриптом сбора здесь и далее понимается асинхронная функция, которая оперирует одной страницей и собирает скриншоты.
|
|
|
|
Рассмотрим на примере функции
|
|
|
|
```javascript
|
|
|
|
async function loginAsAdmin(page) {
|
|
|
|
console.log('# login as admin');
|
|
|
|
await page.goto(domain + '/login.php');
|
|
|
|
await page.waitForSelector('input');
|
|
|
|
await takeSS('login_page', page, { boxedElement: '.form-signin' });
|
|
|
|
await page.type('[name="user"]', params.user);
|
|
|
|
await page.type('[name="pass"]', params.password);
|
|
|
|
await page.waitForTimeout(100);
|
|
|
|
const submitBtn = '.form-signin .btn';
|
|
|
|
await takeSS('login_fill', page, { hostElement: '.form-signin', boxedElement: submitBtn });
|
|
|
|
page.on('dialog', async (dialog) => { throw dialog.message(); });
|
|
|
|
await page.click(submitBtn);
|
|
|
|
await page.waitForTimeout(1000);
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Использование скриптов
|
|
|
|
|
|
|
|
Добавьте вызов написанного скрипта в функцию `run()`:
|
|
|
|
```javascript
|
|
|
|
async function run() {
|
|
|
|
await startBrowser();
|
|
|
|
await loginAsAdmin(mainPage); // <-
|
|
|
|
await takeIndexSS(mainPage);
|
|
|
|
await stopBrowser();
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
Можно объединять несколько скриптов в один составной средствами JavaScript:
|
|
|
|
```javascript
|
|
|
|
async takeUserSS() {
|
|
|
|
await takeUserNewsSS();
|
|
|
|
await takeUserHelpSS();
|
|
|
|
// ...
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
### Структура скрипта
|
|
|
|
|
|
|
|
Начинайте скрипт строкой вида
|
|
|
|
```javascript
|
|
|
|
console.log('# login as admin');
|
|
|
|
```
|
|
|
|
для того, чтобы видеть прогресс во время работы програмы и контекст при возникновении ошибки.
|
|
|
|
Следует придерживаться соответствия текстовки названию скрипта.
|
|
|
|
|
|
|
|
Старайтесь не создавать новые страницы (`await browser.newPage()`).
|
|
|
|
|
|
|
|
Перейдите на требуемую страницу:
|
|
|
|
```javascript
|
|
|
|
await page.goto(domain + '/login.php');
|
|
|
|
```
|
|
|
|
Старайтесь, если это разумно, в рамках одного скрипта работать с одной страницей.
|
|
|
|
|
|
|
|
Типичные действия в скриптах
|
|
|
|
* переход на страницу;
|
|
|
|
* заполнение полей;
|
|
|
|
* активные действия (нажатие на кнопку, симуляция наведения мыши и т.п.);
|
|
|
|
* снятие скриншотов.
|
|
|
|
|
|
|
|
### Управления задержками и ожиданием
|
|
|
|
|
|
|
|
После всех действий, которые могут требовать время (загрузка страницы, отработка события и т.п.),
|
|
|
|
выполняйте задержку, чтобы гарантировать, что страница готова к продолжению работы.
|
|
|
|
Несмотря на асинхронность всех действий (оператор await),
|
|
|
|
управление асинхронно возвращается не тогда, когда все последствия действия проявятся, а когда браузер совершил само действие.
|
|
|
|
Основные методы:
|
|
|
|
* `await wait(1000)` — задержка на 1с, осуществляемая таймером основной программы;
|
|
|
|
* `await page.waitForTimeout(1000)` — задержка на 1с, осуществляемая таймером запущенного браузера (ввиду однопоточности JS разница может проявляться);
|
|
|
|
* `await page.waitForSelector('.form-signin .btn:first-child')` — задержка до появления хотя бы одного элемента, подходящего под указанный селектор;
|
|
|
|
* `await page.waitForXPath(xpath)` — по-видимому, аналогичен `waitForSelector`;
|
|
|
|
* `await page.waitForNavigation()` — задержка до наступления определённого события (загрузка DOM, прекращение сетевой активности и т.п.), подробноти в [документации](https://devdocs.io/puppeteer/index#pagewaitfornavigationoptions), где написано, как следует использовать именно этот тип ожидания.
|
|
|
|
|
|
|
|
Как правило, характерные времена ожидания небольшие, таким образом, привести любой скрипт в рабочий вид можно, расставив `waitForTimeout` с достаточно большим временем.
|
|
|
|
Такой подход, однако, не удобен из-за увеличения времени работы всей программы, поэтому по возможности следует отказываться в пользу `waitForSelector` или другого метода.
|
|
|
|
|
|
|
|
Методы задержки можно комбинировать.
|
|
|
|
|
|
|
|
### Скриншоты
|
|
|
|
|
|
|
|
Есть стандартный способ снимать скриншоты — метод `page.screenshot(pathToFile)`.
|
|
|
|
|
|
|
|
Можно использовать функцию `takeSS`, определённую в модуль `utils`.
|
|
|
|
Базовое использование:
|
|
|
|
```javascript
|
|
|
|
await takeSS('name', page);
|
|
|
|
```
|
|
|
|
Первый аргумент — имя скриншота (см. раздел выше).
|
|
|
|
Файл сохраняется по пути `img/name.png`.
|
|
|
|
Имя может содержать слеши, тогда сохранение будет происходить в соответстующую директорию.
|
|
|
|
Второй аргумент — страница, которой параметризован скрипт.
|
|
|
|
|
|
|
|
Дополнительные параметры передаются третьи объектом.
|
|
|
|
Есть три типичные ситуации:
|
|
|
|
1. Скриншот отображаемой страницы в соответствии с установленным viewport
|
|
|
|
```javascript
|
|
|
|
await takeSS('page', page, { fullPage: false });
|
|
|
|
```
|
|
|
|
`fullPage` можно опустить, по умолчанию `false`.
|
|
|
|
2. Скриншот всей страницы, включая всё содержимое
|
|
|
|
```javascript
|
|
|
|
await takeSS('page', page, { fullPage: true });
|
|
|
|
```
|
|
|
|
3. Скриншот фрагмента страницы, соответствующему элементу `hostElement`
|
|
|
|
```javascript
|
|
|
|
await takeSS('page', page, { hostElement: '.form' });
|
|
|
|
```
|
|
|
|
|
|
|
|
Не известно, конфликтуют `hostElement` с `fullPage` или нет. Рекомендуется придерживаться одного из этих способов использования.
|
|
|
|
|
|
|
|
Дополнительные параметры: `boxedElement`, `boxPaddingFrac`, `lineWidthFrac`.
|
|
|
|
* `boxedElement` — это элемент на странице или внутри `hostElement`, который будет выделен красным прямоугольником.
|
|
|
|
* `boxPaddingFrac` — отступ между красным прямоугольником и границами элемента (границы определяются браузером, они могут не включать особым образом спозиционированные подэлементы). Указывается в долях от ширины скриншота (т.е. ширины страницы или `hostElement`).
|
|
|
|
* `lineWidthFrac` — толщина прямоугольника, в долях от ширины скриншота (т.е. ширины страницы или `hostElement`).
|
|
|
|
|
|
|
|
`hostElement` и `boxedElement` могут задаваться как Puppeteer-представлениями элементов, например,
|
|
|
|
```javascript
|
|
|
|
await await takeSS('page', page, { hostElement: await page.$('.form') });
|
|
|
|
```
|
|
|
|
так и строкой-селектором.
|
|
|
|
Первый способ более гибкий, поскольку им возможно обозначить элемент по признаку, который не выразим селектором CSS, например, (селекторомм XPath)[https://devdocs.io/puppeteer/index#pagexexpression].
|
|
|
|
|
|
|
|
#### Примеры
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
await takeSS('login_page', page, { boxedElement: '.form-signin' });
|
|
|
|
```
|
|
|
|
Скриншот с именем login\_page, сохраняемый в файл img/login\_page.png, содержит страницу со стандартным размером.
|
|
|
|
Отображаемая область зависит от предшествующих действия, после загрузки страницы это верхняя часть.
|
|
|
|
На скриншоте выделен элемент с классом `form-signin`.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
await takeSS('login_fill', page, { hostElement: '.form-signin', boxedElement: '.form-signin .btn' });
|
|
|
|
```
|
|
|
|
Скриншот с именем login\_fill, сохраняемый в файл img/login\_fill.png, содержит элемент страницы с классом `form-signing`, на котором выделен первый дочерний элемент с классом `btn`.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
await takeSS('admin/index_full', page, { fullPage: true });
|
|
|
|
```
|
|
|
|
Скриншот с именем admin/index\_full, сохраняемый в файл admin/index\_full.png.
|
|
|
|
Скриншот содержит всю страницу целиком, размер определяется содержимым страницы.
|
|
|
|
|
|
|
|
## Ввод данных
|
|
|
|
|
|
|
|
Заполнение текстовых полей:
|
|
|
|
```javascript
|
|
|
|
await page.type('[name="user"]', params.user);
|
|
|
|
await page.type('[name="pass"]', params.password);
|
|
|
|
```
|
|
|
|
|
|
|
|
В этом примере данные берутся из глобального объекта `params`.
|
|
|
|
|
|
|
|
Если скрипт создаёт случайные данные или в ходе работы принимает от сервера новые данные, следует сохранять их в глобальный объект `sideeffects`,
|
|
|
|
чтобы в будущем иметь возможность ими оперировать.
|
|
|
|
|
|
|
|
## Активные действия
|
|
|
|
|
|
|
|
Нажатие на кнопку:
|
|
|
|
```javascript
|
|
|
|
await page.click('.btn');
|
|
|
|
```
|
|
|
|
|
|
|
|
## Обработка модальных сообщений
|
|
|
|
|
|
|
|
Если страница может показать встроенное модальное окно (alert, prompt), следует добавить их обработку.
|
|
|
|
|
|
|
|
```javascript
|
|
|
|
page.on('dialog', async (dialog) => { throw dialog.message(); });
|
|
|
|
```
|
|
|
|
Эта команда добавляет обработчик модальных окон, который создаёт исключительную ситуацию, предполагая, что при нормальном взаимодействии модальных окон быть не должно.
|
|
|
|
Если же модальные окна всё-таки являются частью UI, расширьте функцию дополнительной логикой, например,
|
|
|
|
```javascript
|
|
|
|
page.on('dialog', async (dialog) => {
|
|
|
|
console.log('alert: ' + dialog.message());
|
|
|
|
if (dialog.message() === "Вопрос сохранён на сервере") {
|
|
|
|
await dialog.accept();
|
|
|
|
} else {
|
|
|
|
throw new Error('Неожиданное сообщение: ' + dialog.message());
|
|
|
|
}
|
|
|
|
});
|
|
|
|
``` |