Тестирование производительности фронтенда с помощью 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 performance доступен для объекта 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()

Вход в полноэкранный режим Выход из полноэкранного режима

Цепочка, начинающаяся с cy.window(), фактически делает то же самое, что если бы мы набрали 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. Причина передачи этого аргумента заключается в том, что мы можем добавить несколько меток в наш тест, и нам нужно указать, какую из них измерять. Для вызова функции measure мы, в основном, выполняем тот же набор функций 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 даст объект со всевозможными результатами:

Внутри этой команды мы можем выбрать свойство из этого объекта с помощью команды .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. Я обычно сохраняю свои пользовательские команды в папке cypress/support/commands/ и делаю import ../commands/mark.ts внутри файла cypress/support/index.ts.

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, который я сделал ранее.

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

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

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

Оцените статью
devanswers.ru
Добавить комментарий