В этой статье я объясню,как легко делать скриншоты приложений, используя Транспортир и безголовый экземпляр Chrome.
Настраивать
Я предполагаю, что у вас есть приложение Angular, настроенное и работающее. В моем случае я использовал Ionic, сложную структуру мобильных приложений, поддерживающую Angular и Cordova.
Мы будем использовать сгенерированное для нас e2e-приложение Angular по умолчанию, расположенное в e2e/
.
В вашем e2e/protractor.conf.js
добавьте следующую конфигурацию:
// Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts const { SpecReporter } = require('jasmine-spec-reporter'); exports.config = { // ... capabilities: { browserName: 'chrome', chromeOptions: { args: ['--headless', '--disable-gpu'], mobileEmulation: { deviceName: 'Galaxy S5' }, prefs: { intl: { accept_languages: 'de-DE' }, } } }, // ... };
Это гарантирует, что при запуске ng e
chrome будет использовать функцию эмуляции мобильных устройств для имитации Galaxy S5 с немецким языком. В зависимости от того, как вы реализовали интернационализацию, ваше приложение должно отображаться на немецком языке.
Поскольку вы, вероятно, захотите сделать несколько снимков экрана (например, на разных устройствах и на разных языках), мы создадим больше файлов конфигурации транспортира От этого. В файле e2e/protractor.iphoneX.conf.js
сохраните следующий код:
const { config } = require('./protractor.conf.js'); exports.config = { ...config, capabilities: { ...config.capabilities, chromeOptions: { ...config.capabilities.chromeOptions, mobileEmulation: { deviceName: 'iPhone X' } } } };
Вы можете запустить ng e --protractorConfig e2e/protractor.iphoneX.conf.js
, чтобы запустить набор спецификаций во время эмуляции iPhone X.
Вы можете расширить это, чтобы иметь различные конфигурации для большего количества устройств и языков.
Вы можете найти доступные устройства (и добавить новые), если зайдете в Настройки › Устройства в Chrome DevTools в верхнем правом меню.
Создание скриншотов
Мы напишем простой набор спецификаций e2e, который использует takeScreenshot
Protractor для снятия снимка экрана и сохранения его в папку для снимков экрана. Вот пример кода:
import { AppPage } from './app.po'; import * as fs from 'fs'; import { browser, by, element } from 'protractor'; async function writeScreenShot(data, filename: string) { fs.mkdirSync('screenshots', { recursive: true }); const config = await browser.getProcessedConfig(); const stream = fs.createWriteStream( `screenshots/${filename} (${config.capabilities.chromeOptions.mobileEmulation.deviceName.replace(/\//g, '')}).png` ); stream.write(Buffer.from(data, 'base64')); stream.end(); } describe('App', () => { let page: AppPage; beforeEach(() => page = new AppPage()); it('start', async () => { await page.navigateTo('/start'); await writeScreenShot(await browser.takeScreenshot(), '1 Start'); }); });
Давайте рассмотрим это шаг за шагом: мы пишем функцию, которая получает данные и имя файла и сохраняет снимок экрана в screenshots
(или создает этот каталог, если он еще не существует). . К имени файла добавляется эмулируемое устройство, например 1 Start (iPhone X).png
. Мы также заменяем косую черту, если она существует в имени устройства (например, для "iPhone 6/7/8"
).
При выполнении с ng e
эта спецификация уже создает снимок экрана. Попробуйте!
(ионические) причуды
Я столкнулся с двумя основными проблемами при использовании этого подхода при использовании Ionic:
- Анимации приводили к созданию снимков экрана, когда приложение переходило на другой маршрут, или открывалось модальное окно.
- Мобильная эмуляция Chrome не устанавливала переменные среды CSS, что приводило к отсутствию отступов верхней и нижней безопасных областей.
Следующие две проблемы не были вызваны Ionic:
- Иногда данные не загружались, когда Protractor уже сделал снимок экрана.
- Protractor ожидает, пока асинхронный код «станет стабильным», в конечном итоге ожидая завершения промисов и наблюдаемых объектов перед выполнением тестового кода. В моем случае я делал снимки экрана во время подписки на долговременный наблюдаемый объект (получая сообщения чата), что приводило к тайм-ауту с Protractor.
Отключение анимации
Я представил новую среду под названием test
, добавив ее в массив конфигураций в моем angular.json
. Я просто скопировал производственную конфигурацию в app.architect.build.configurations
моего приложения и изменил ее так, чтобы она использовала файл environment.test.ts
. Затем я добавил в конфигурацию ключ animated
:
export const environment = { animated: false, // ... };
Не забудьте также добавить это в другие среды и установить для него значение true
.
Затем я устанавливаю параметр в зависимости от среды в импортированном IonicModule
в моем корневом модуле (app-module.ts
): IonicModule.forRoot({ animated: environment.animated })
.
Исправить отсутствующую безопасную зону
Используя Protractor executeScript
, я установил некоторые переменные, которые Ionic использует для определения безопасной области для верха и низа. Мне не удалось найти способ напрямую установить переменные среды CSS в chromedriver/chrome. В app.po.ts
я подключился к методу navigateTo
(который должен присутствовать):
import { browser, by, element } from 'protractor'; export class AppPage { async navigateTo(destination) { await browser.get(destination); // NB: safe area for iPhone X const config = await browser.getProcessedConfig(); if (config.capabilities.chromeOptions.mobileEmulation.deviceName === 'iPhone X') { await browser.executeScript(`return document.documentElement.style.setProperty('--ion-safe-area-top', '40px')`); await browser.executeScript(`return document.documentElement.style.setProperty('--ion-safe-area-bottom', '40px')`); } } }
Таким образом, верхние и нижние безопасные области соблюдаются (что было важно для меня, потому что я помещал скриншоты в рамки устройства). Вы можете расширить это, чтобы поддерживать безопасные зоны и для других устройств.
Ожидание видимых элементов
В своей тестовой конфигурации я настроил приложение на использование нашего промежуточного API. Я создал некоторые данные, которые использовались исключительно на скриншотах приложения. Однако, конечно, была некоторая задержка, из-за которой мои скриншоты иногда не содержали данных, которые должны были иногда. Я использовал ExpectedConditions
и wait
Protractor для ожидания удаленного контента.
it('new report', async () => { await browser.wait(browser.ExpectedConditions.elementToBeClickable(element(by.css('ion-button[title="Neue Meldung"]')))); // ... });
Не дожидаясь, пока Angular/NgZone станет стабильной
Я использовал давно работающий Observable для получения новых сообщений чата через подписку GraphQL. Поскольку Protractor ждет завершения всего асинхронного кода перед выполнением тестового кода, я получил тайм-аут:
it('messages', async () => { browser.waitForAngularEnabled(false); // this ExpectedCondition would timeout without the line above await browser.wait( browser.ExpectedConditions.elementToBeClickable( element(by.cssContainingText('ion-label', 'Gartentor schließt nicht mehr')) ) ); await element(by.cssContainingText('ion-label', 'Gartentor schließt nicht mehr')).click(); await browser.wait(browser.ExpectedConditions.elementToBeClickable(element(by.cssContainingText('ion-label', 'Nachrichten')))); await element(by.cssContainingText('ion-label', 'Nachrichten')).click(); await browser.wait(browser.ExpectedConditions.presenceOf(element(by.cssContainingText('ion-label', 'Super')))); await writeScreenShot(await browser.takeScreenshot(), '5 Messages'); browser.waitForAngularEnabled(true); });
Надеюсь, вы смогли чему-то научиться из этой истории. Если у вас есть вопросы или замечания, дайте мне знать в ответ.