Тестирование производительности фронтенда с помощью Cypress

Существует множество способов измерения производительности. В сегодняшней статье мы поговорим об одном из самых простых. Представьте себе следующий сценарий:

  1. Пользователь нажимает на кнопку.
  2. Появляется модальное окно.

Наш тест может выглядеть примерно так:

  cy.visit('/board/1')

  // wait for loading to finish
  cy.getDataCy('loading')
    .should('not.exist')

  cy.getDataCy('card')
    .click()

Это модальное окно может получать некоторые данные от сервера, перегруппировывать или фильтровать их. Кроме того, оно может выполнять и другие действия, например, отрисовку изображений и т.д. Все эти действия занимают какое-то время, и мы, как тестировщики, хотим ускорить этот процесс.

Содержание

Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ

API performance.mark()

Во всех современных браузерах API производительности доступен для объекта window. Мы можем получить доступ к этому API, используя функцию cy.window(), и затем вызвав метод. Для начала измерения производительности можно создать метку, которая будет обозначать начало нашего измерения.

  cy.visit('/board/1')

  // wait for loading to finish
  cy.getDataCy('loading')
    .should('not.exist')

  cy.window()
    .its('performance')
    .invoke('mark', 'modalOpen')

  cy.getDataCy('card')
    .click()

Выделенная часть нашего кода делает фактически то же самое, как если бы мы набрали window.performance.mark('modalOpen') в консоли DevTools. ModalOpen — это просто метка, и ей можно дать любое имя.

API performance.measure()

Теперь, когда мы обозначили начало нашей метрики, давайте выполним следующие шаги. Когда мы кликаем по карточке, открывается модальное окно. Сначала мы хотим убедиться, что достигли желаемого результата. Добавим проверку видимости модального окна:

  cy.visit('/board/1')

  // wait for loading to finish
  cy.getDataCy('loading')
    .should('not.exist')

  cy.window()
    .its('performance')
    .invoke('mark', 'modalOpen')

  cy.getDataCy('card')
    .click()

  cy.getDataCy('card-detail')
    .should('be.visible')

После этого мы можем вызвать функцию performance.measure() для проведения измерения. Аргументом функции measure будет наша метка modalOpen. Причина передачи этого аргумента заключается в том, что мы можем добавить несколько меток в наш тест, и нам необходимо указать, какую из них измерять. Чтобы вызвать функцию измерения, мы, как и раньше, выполняем набор функций Cypress:

  cy.visit('/board/1')

  // wait for loading to finish
  cy.getDataCy('loading')
    .should('not.exist')

  cy.window()
    .its('performance')
    .invoke('mark', 'modalOpen')

  cy.getDataCy('card')
    .click()

  cy.getDataCy('card-detail')
    .should('be.visible')

  cy.window()
    .its('performance')
    .invoke('measure', 'modalOpen')

В результате выполнения команды invoke будет получен объект со всевозможными результатами:

Объект PerformanceMeasure

В этой команде мы можем выбрать свойство этого объекта с помощью команды .its(). Поскольку нам нет необходимости делать вторую попытку, мы можем установить таймаут равным 0 и сразу же сделать наше утверждение. В нашем примере мы сделаем утверждение о том, что модальное окно не должно загружаться дольше 2 секунд (2000 в миллисекундах).

  cy.visit('/board/1')

  // wait for loading to finish
  cy.getDataCy('loading')
    .should('not.exist')

  cy.window()
    .its('performance')
    .invoke('mark', 'modalOpen')

  cy.getDataCy('card')
    .click()

  cy.getDataCy('card-detail')
    .should('be.visible')

  cy.window()
    .its('performance')
    .invoke('measure', 'modalOpen')
    .its('duration', { timeout: 0 })
    .should('be.lessThan', 2000)

Создание пользовательской команды

Теперь, когда мы знаем, что делать, мы можем создать из этого пользовательскую команду. Здесь очень много TypeScript, поэтому давайте подробнее разберемся в том, что происходит. Строки 1-9 – это объявление типа. Таким образом мы сообщаем компилятору TypeScript о том, что добавили новую команду cy.mark() в библиотеку команд cy. Библиотека называется Chainable и содержит все команды cy. Она является частью более крупного пространства – namespace Cypress.

Строки 11 – 29 – это функция, которая содержит нашу цепочку команд из предыдущего примера. Кроме того, здесь скрыты журналы трех наших команд и добавлен другой журнал, который можно увидеть в строках 15 – 24.

Наконец, в строке 31 мы добавляем эту функцию в библиотеку Cypress. В то время как строки 1-9 добавляют нашу команду в пространство имен Cypress, которое может распознать наш компилятор TypeScript, функция Cypress.Commands.addAll() добавит ее в сам Cypress.

declare namespace Cypress {
  interface Chainable<Subject = any> {
      /**
       * Add a measurment marker. Used with cy.measure() command
       * @example cy.mark('modalWindow')
       */
       mark: typeof mark
  }
}

const mark = (markName: string): Cypress.Chainable<any> => {

  const logFalse = { log: false }

  Cypress.log({
    name: 'mark',
    message: markName,
    consoleProps() {
      return {
        command: 'mark',
        'mark name': markName
      }
    }
  })

  return cy.window(logFalse)
    .its('performance', logFalse)
    .invoke(logFalse, 'mark', markName)
}

Cypress.Commands.addAll({ mark })

Аналогичным образом мы можем добавить команду cy.measure():

declare namespace Cypress {
  interface Chainable<Subject = any> {
      /**
       * Add a measurment marker. Used with cy.measure() command
       * @example cy.measure('modalWindow')
       */
       measure: typeof measure
  }
}

const measure = (markName: string): Cypress.Chainable<number> => {

  const logFalse = { log: false }

  let measuredDuration: number
  let log = Cypress.log({
    name: 'measure',
    message: markName,
    autoEnd: false,
    consoleProps() {
      return {
        command: 'measure',
        'mark name': markName,
        yielded: measuredDuration
      }
    }
  })

  return cy.window(logFalse)
    .its('performance', logFalse)
    .invoke(logFalse, 'measure', markName)
    .then( ({ duration }) => {
      measuredDuration = duration
      log.end()
      return duration
    })
}

Cypress.Commands.addAll({ measure })

Небольшое отличие от нашей cy.mark() заключается в том, что на этот раз тип возврата будет number, поскольку наша функция возвращает число. Кроме того, вместо того чтобы использовать функцию .its(), мы возвращаем ее из функции .then(), так как хотим использовать ее и в детализации нашей консольной команды.

Тестирование производительности в Cypress

При проведении любого вида тестирования производительности необходимо обращать пристальное внимание на среду, в которой оно проводится. Находимся ли мы в реальной среде? Находится ли она в данный момент под большой нагрузкой? Если мы тестируем на стейджинг-сервере, то полностью ли он совпадает с продом или мы тестируем уменьшенную версию? Используем ли мы браузер для тестирования производительности? Какой именно? Какую версию? Все эти и другие вопросы должны быть заданы, чтобы обеспечить контекст для показателей производительности.

В нашем случае мы работаем в браузере, в котором открыты два iframe. Один для нашего приложения, другой для скрипта Cypress. Это может повлиять на наше тестирование, что немаловажно. В документации Cypress предупреждается об этом. Это вовсе не значит, что измерение производительности в Cypress бесполезно. Это лишь означает, что при рассмотрении метрик необходимо учитывать контекст.

Перевод статьи «Testing frontend performance with Cypress».

1 комментарий к “Тестирование производительности фронтенда с помощью Cypress”

  1. Пингбэк: Большой учебник по Cypress

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *