В этой статье я объясню,как легко делать скриншоты приложений, используя Транспортир и безголовый экземпляр 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);
    });

Надеюсь, вы смогли чему-то научиться из этой истории. Если у вас есть вопросы или замечания, дайте мне знать в ответ.