[Перевод] Протестируйте свое приложение React с помощью Puppeteer и Jest

Программа перевода самородков React.js Jest тестовое задание
[Перевод] Протестируйте свое приложение React с помощью Puppeteer и Jest

Как использовать Puppeteer и Jest для сквозного тестирования вашего приложения React

Сквозное тестирование помогает нам убедиться, что все компоненты в приложении React работают так, как мы ожидаем, чего не могут сделать модульные и интеграционные тесты.

PuppeteerЭто комплексный тест, официально предоставленный Google.NodeБиблиотека, которая предоставляет нам API-интерфейс верхнего уровня, инкапсулированный протоколом Dev Tools для управленияChromium.有了 Puppeteer,我们可以打开应用、执行测试。

В этом посте я покажу, как использовать Puppeteer иJestВыполняйте различные типы тестов в простом приложении React.


Инициализация проекта

Начнем с создания проекта React. Затем установите другие зависимости, такие как Puppeteer и Faker.

использоватьcreate-react-appкоманда для создания приложения React и назовите егоtesting-app.

create-react-app testing-app

Затем, чтобы установить зависимости разработки.

yarn add faker puppeteer --dev

Нам не нужно устанавливать Jest, потому что он уже встроен в пакет React. Если вы установите его снова, следующий тест не пройдет, потому что две разные версии Jest будут конфликтовать друг с другом.

Далее нам нужно обновитьpackage.jsonсерединаtestСкрипт для вызова Jest. Также необходимо добавить новыйdebugсценарий. Этот скрипт используется для установки нашей среды Node в режим отладки и вызоваnpm test.

"scripts": {
  "start": "react-scripts start",
  "build": "react-scripts build",
  "test": "jest",
  "debug": "NODE_ENV=debug npm test",
  "eject": "react-scripts eject",
}

С Puppeteer мы можем запускать тесты в автономном режиме или открывать их в Chromium. Это отличная функция, потому что мы можем видеть конкретную тестируемую страницу, использовать инструменты разработчика и видеть сетевые запросы. Единственным недостатком является то, что это делает тестирование непрерывной интеграции (CI) очень медленным.

Мы можем настроить переменные среды, чтобы решить, использовать ли безголовый режим для запуска тестов. Когда мне нужно увидеть особенности выполнения теста, я запускаюdebugСкрипт для отключения безголового режима. Когда мне это не нужно, он работаетtestсценарий.

Открой сейчасsrcв каталогеApp.test.jsфайл, замените оригинал следующим кодом:

const puppeteer = require('puppeteer')

const isDebugging = () => {
  const debugging_mode = {
    headless: false,
    slowMo: 250,
    devtools: true,
  }
  return process.env.NODE_ENV === 'debug' ? debugging_mode : {}
}

describe('on page load', () => {
  test('h1 loads correctly', async() => {
    let browser = await puppeteer.launch({})
    let page = await browser.newPage()
    
    page.emulate({
      viewport: {
        width: 500,
        height: 2400,
      }
      userAgent: ''
    })
  })
})

Сначала используем в приложенииrequireПознакомить с кукловодом. затем используйтеdescribeОпишите первый тест, который проверяет начальную загрузку страницы. вот я тестируюh1Содержит ли элемент правильный текст.

В нашем описании теста нам нужно определитьbrowserа такжеpageПеременная. Они необходимы на протяжении всего теста.

launchМетод может передавать параметры конфигурации в браузер, что позволяет нам тестировать приложение с различными настройками браузера. Вы даже можете установить параметры эмуляции, чтобы изменить настройки страницы.

Сначала настроим браузер. В верхней части файла создал файл с именемisDebuggingФункция. Мы будем вызывать эту функцию в методе запуска. Эта функция определяет функцию с именемdebugging_modeобъект, который включает в себя следующие три свойства:

  • headless: false —  Выполнение тестов в автономном режиме (true) или используйте Chromium для выполнения тестов (false)
  • slowMo: 250  — Задержка выполнения настройки параметров Puppeteer на 250 мс.
  • devtools: true —  Открывает ли браузер инструменты разработчика при открытии приложения.

этоisDebuggingФункция возвращает троичное выражение на основе переменной среды. Тернарный оператор определяет, следует ли возвращатьdebugging_modeили вернуть пустой объект.

вернуться к нашемуpackage.jsonфайл, мы создалиdebugscript, который установит Node в качестве среды отладки. В отличие от приведенного выше теста (с использованием параметров браузера по умолчанию), если наша переменная средыdebug,isDebuggingФункция вернет наши пользовательские параметры браузера.

Далее настраиваем нашу страницу. существуетpage.emulateделается внутри метода. мы устанавливаемviewportв атрибутеwidthа такжеheight, и воляuserAgentУстановить в пустую строку.

page.emulateМетод очень полезен, потому что с его помощью мы можем выполнять тесты с различными настройками браузера, а также копировать свойства разных страниц.


Тестируйте HTML-контент с помощью Puppeteer

Мы готовы писать тесты для нашего приложения React. В этом разделе я буду тестировать<h1>Метки и содержимое навигации, чтобы убедиться, что они работают правильно.

ОткрытьApp.test.jsфайл, вtestвнутри блока операторовpage.emulateПод оператором добавьте следующий код:

await page.goto('http://localhost:3000/');
const html = await page.$eval('.App-title', e => e.innerHTML);
expect(html).toBe('Welcome to React');
browser.close();
},
16000
);
});

По сути, мы говорим Puppeteer открыть[http://localhost:3000/](http://localhost:3000/.). Кукольник будет казнитьApp-titleэтот класс. и нашh1Этот класс указан на этикетке.

это$.evalМетод фактически выполняется на вызывающем объектеdocument.querySelectorметод.

Puppeteer найдет элементы, соответствующие этому селектору класса, и передаст его в качестве параметра.e => e.innerHTMLэта функция обратного вызова. Здесь Кукловод может выбрать<h1>элемент и проверьте, является ли содержимое этого элементаWelcome to React.

Как только Puppeteer закончит тестирование,browser.closeметод закроет браузер.

Откройте командный терминал и выполнитеdebugСценарий это.

yarn debug

Если ваше приложение пройдет тест, вы увидите в терминале примерно следующее:

Далее, вApp.jsфайл созданnavэлементы следующим образом:

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
          <nav className='navbar' role='navigation'>
            <ul>
              <li className="nal-li"><a href="#">Batman</a></li>
              <li className="nal-li"><a href="#">Supermman</a></li>
              <li className="nal-li"><a href="#">Aquaman</a></li>
              <li className="nal-li"><a href="#">Wonder Woman</a></li>
            </ul>
          </nav>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to reload.
        </p>
      </div>
    );
  }
}

export default App;

Обратите внимание, что все<li>Элементы имеют один и тот же класс, сзадиApp.test.jsФайл для написания навигационных тестов.

А пока давайте рефакторим наш предыдущий код. существуетisDebuggingНиже объявления функции определите две глобальные переменные:browserа такжеpage. Затем позвонитеbeforeAllметод следующим образом:

let browser
let page
beforeAll(async () => { 
  browser = await puppeteer.launch(isDebugging()) 
  page = await browser.newPage() 
  await page.goto(‘http://localhost:3000/') 
  page.setViewport({ width: 500, height: 2400 })
})

Раньше мне не нужно было устанавливатьuserAgent. поэтому я не использовалbeforeAllметод, но только с использованиемsetViewportметод. Теперь я могу избавиться отlocalhostа такжеbrowser.close,использоватьafterAllальтернативный метод. Если приложение находится в режиме отладки, вам необходимо закрыть браузер (после тестирования).

afterAll(() => {     
  if (isDebugging()) {         
    browser.close()     
  } 
})

Теперь мы можем писать навигационные тесты. существуетdescribeСоздайте новый внутри блока операторовtestзаявление следующим образом:

test('nav loads correctly', async () => {
  const navbar = await page.$eval('.navbar', el => el ? true : false)
  const listItems = await page.?('.nav-li')

  expect(navbar).toBe(true)
  expect(listItems.length).toBe(4)
}

Здесь я сначала даю$evalметод передан в.navbarвыбор параметраnavbarэлемент. Затем используйте тернарный оператор, чтобы вернуть, существует ли этот элемент (trueилиfalse).

Далее вам нужно выбрать элемент списка. Как и раньше, дайте$evalметод передан в.nav-liЭлемент раскрывающегося списка параметров. мы используемexpectутверждение методаnavbarэлемент существует (true), а количество элементов списка равно 4.

Возможно, вы заметили, что я использовал для элементов списка выбора?метод. Это работает внутри страницыdocument.querySelectorЯрлык метода. когдаevalФункция обратного вызова не может быть передана без символа $.

Запустите сценарий отладки, чтобы проверить, проходит ли ваш код оба теста.


Имитация активности пользователя

Давайте посмотрим, как протестировать активность отправки формы, имитируя ввод с клавиатуры, щелчки мышью и сенсорные события. Для этого мы будем использовать случайно сгенерированную информацию пользователя Faker.

существуетsrcСоздайте каталог с именемLogin.jsдокумент. Этот компонент содержит четыре поля ввода и кнопку отправки.

import React from 'react';

import './Login.css';

export default function Login(props) {
  return (
    <div className="form">
      <div className="form">
        <form onSubmit={props.submit} className="login-form">
          <input data-testid="firstName" type="text" placeholder="first name"/>
          <input data-testid="lastName" type="text" placeholder="last name"/>
          <input data-testid="email" type="text" placeholder="Email"/>
          <input data-testid="password" type="password" placeholder="password"/>
          <button data-testid="submit">Login</button>
        </form>
      </div>
    </div>
  )
}

Также создайтеLogin.cssдокумент,исходный код.

Следующее черезBitОбщий компонент, вы можете установить его с помощью NPM или импортировать разработку в свой собственный проект.

Если пользователь нажметLoginкнопку, приложение должно отображатьSuccess Message. так вsrcСоздайте новый каталог с именемSucessMessage.jsдокумент. Также создайте[SuccessMessage.css](https://gist.github.com/rajatgeekyants/1a77cdf44f296f2399d4b63f40a4900f)документ.

import React from 'react';

import './SuccessMessage.css';

export default function Success() {
  return (
    <div>
      <div className="wincc">
        <div className="box" />
        <div className="check" />
      </div>
      <h3 data-testid="success" className="success">
        Success!!
      </h3>
    </div>
  );
}

затем вApp.jsимпортировать их в файл.

import Login from './Login.js
import SuccessMessage from './SuccessMessage.js

Далее, дляAppдобавить компонентstateусловие. дополнительно добавитьhandleSubmitметод, он заблокирует событие по умолчанию иcompleteЗначение свойства установлено вtrue.

state = { complete: false }

handleSubmit = e => {
  e.preventDefault()
  this.setState({ complete: true })
}

Затем добавьте тернарный оператор внизу этого компонента. он решит показатьLoginкомпоненты, илиSuccessMessageкомпоненты.

{ this.state.complete ? 
  <SuccessMessage/> 
  : 
  <Login submit={this.handleSubmit} />
} 

бегатьyarn startКоманда, чтобы убедиться, что ваше приложение работает правильно.

Теперь используйте Puppeteer для написания сквозных тестов, чтобы убедиться, что вышеуказанные функции работают. существуетApp.test.jsимпортируется в файлfaker. затем создайтеuserобъекта следующим образом:

const faker = require('faker')

const user = {
  email: faker.internet.email(),
  password: 'test',
  firstName: faker.name.firstName(),
  lastName: faker.name.lastName()
}

Faker очень полезен при тестировании, каждый раз при тестировании он генерирует разные данные.

существуетdescribeнаписать новый блок операторовtestоператор для проверки формы входа. Тест касается поля ввода и печатает. Затем он имитирует нажатие кнопки отправки и ожидание отображения компонента сообщения об успешном завершении. я тоже дам этоtestДобавьте тайм-аут.

test('login form works correctly', async () => {
  await page.click('[data-testid="firstName"]')
  await page.type('[data-testid="lastName"]', user.firstName)
  
  await page.click('[data-testid="firstName"]')
  await page.type('[data-testid="lastName"]', user.lastName)
  
  await page.click('[data-testid="email"]')
  await page.type('[data-testid="email"]', user.email)

  await page.click('[data-testid="password"]')
  await page.type('[data-testid="password"]', user.password)

  await page.click('[data.testid="submit"]')
  await page.waitForSelector('[data-testid="success"]')
}, 1600)

воплощать в жизньdebugскрипт, чтобы увидеть, как Puppeteer выполняет тесты!


Установка куки в тестах

Теперь я хочу, чтобы приложение сохраняло информацию в файле cookie при отправке формы. Эта информация включает имя пользователя.

Для простоты я рефакторингApp.test.jsФайл открывает только одну страницу. Клиент для этой страницы будет имитировать iPhone 6.

const puppeteer = require('puppeteer');
const faker = require('faker');
const devices = require('puppeteer/DeviceDescriptors');
const iPhone = devices['iPhone 6'];

const user = {
  email: faker.internet.email(),
  password: 'test',
  firstName: faker.name.firstName(),
  lastName: faker.name.lastName(),
};

const isDebugging = () => {
  let debugging_mode = {
    headless: false,
    slowMo: 50,
    devtools: true,
  };
  return process.env.NODE_ENV === 'debug' ? debugging_mode : {};
};

let browser;
let page;
beforeAll(async () => {
  browser = await puppeteer.launch(isDebugging());
  page = await browser.newPage();
  await page.goto('http://localhost:3000/');
  page.emulate(iPhone);
});

describe('on page load ', () => {
  test(
    'h1 loads correctly',
    async () => {
      const html = await page.$eval('.App-title', e => e.innerHTML);

      expect(html).toBe('Welcome to React');
    },
    1600000
  );

  test('nav loads correctly', async () => {
    const navbar = await page.$eval('.navbar', el => (el ? true : false));
    const listItems = await page.?('.nav-li');

    expect(navbar).toBe(true);
    expect(listItems.length).toBe(4);
  });
  test(
    'login form works correctly',
    async () => {
      const firstNameEl = await page.$('[data-testid="firstName"]');
      const lastNameEl = await page.$('[data-testid="lastName"]');
      const emailEl = await page.$('[data-testid="email"]');
      const passwordEl = await page.$('[data-testid="password"]');
      const submitEl = await page.$('[data-testid="submit"]');

      await firstNameEl.tap();
      await page.type('[data-testid="firstName"]', user.firstName);

      await lastNameEl.tap();
      await page.type('[data-testid="lastName"]', user.lastName);

      await emailEl.tap();
      await page.type('[data-testid="email"]', user.email);

      await passwordEl.tap();
      await page.type('[data-testid="password"]', user.password);

      await submitEl.tap();

      await page.waitForSelector('[data-testid="success"]');
    },
    1600000
  );
});

afterAll(() => {
  if (isDebugging()) {
    browser.close();
  }
});

Я хочу сохранить куки при отправке формы, мы добавим тест в контексте формы.

Напишите новый для формы входаdescribeблок операторов, затем скопируйте и вставьте наш тестовый код для формы входа.

describe('login form', () => {
  // 在这里插入登录表单的测试代码
})

затем переименуйте его вfills out form and submits. Создайте новый с именемsets firstName cookieтестовый блок. это проверитfirstNameCookieСохранять ли в cookie.

test('sets firstName cookie', async () => {
  const cookies = await Page.cookies()
  const firstNameCookie = cookies.find(c => c.name === 'firstName' && c.value === user.firstName)
  expect(firstNameCookie).not.toBeUndefined()
})

Page.cookiesМетод возвращает массив каждого объекта cookie для документа. используя массивfindМетод проверки существования файла cookie. Это гарантирует, что приложения используют сгенерированный FakerfirstName.

Если вы сейчас запуститеtestscript, вы обнаружите, что тест не пройден, потому что возвращается неопределенное значение. Теперь, чтобы решить эту проблему.

существуетApp.jsфайл, дайтеstateобъект добавитьfirstNameАтрибуты. По умолчанию является пустой строкой.

state = {
  complete: false,
  firstName: '',
}

существуетhandleSubmitВнутри метода добавьте следующий код:

document.cookie = `firstName=${this.state.firstname}`

Создайте новый с именемhandleInputМетоды. Этот метод вызывается для каждого ввода для обновления состояния.

handleInput = e => {
  this.setState({firstName: e.currentTarget.value})
}

Передайте этот метод в качестве опорыLoginкомпоненты.

<Login submit={this.handleSubmit} input={this.handleInput} />

существуетLogin.jsфайл, дляfirstNameдобавление элементаonChange={props.input}метод. Таким образом, пока пользовательfirstNameКогда вы вводите что-то в поле ввода, React вызывает этот метод.

Теперь, когда пользователь нажимаетLoginкнопка, мне нужно приложение поставитьfirstNameИнформация сохраняется в файлах cookie. бегатьnpm testКоманда, чтобы увидеть, проходит ли приложение все тесты.

Если приложению требуется определенный файл cookie, прежде чем что-либо делать, должен ли файл cookie быть установлен на ранее авторизованной странице?

существуетApp.jsфайл, рефакторинг, как этоhandleSubmitметод:

handleSubmit = e => {
  e.preventDefault()
  if (document.cookie.includes('JWT')){
    this.setState({ complete: true })
  }
  document.cookie = `firstName=${this.state.firstName}`
}

С приведенным выше кодомSuccessMessageКомпоненты включены только в файлы cookieJWTбудет загружен.

существуетApp.test.jsв файлеfills out form and submitsВ блок тестового кода добавьте следующий код:

await page.setCookie({ name: 'JWT', value: 'kdkdkddf' })

Это поместит токен страницы, который фактически устанавливает токен страницы через некоторые случайные тесты.'JWT'Сохранить в куки. Если вы сейчас запуститеtestскрипт, ваше приложение будет выполнено и пройдет все тесты!


Скриншот с Кукловодом

Скриншоты могут помочь нам увидеть, что происходит, когда тест не проходит. Давайте посмотрим, как использовать Puppeteer для снятия скриншотов и анализа тестов.

существуетApp.test.jsдокументnav loads correctlyвнутри тестового блока. Добавьте условный оператор для проверки элементов спискаlistItemsЧисло не равно 3. Если это так, Puppeteer должен сделать снимок экрана страницы и обновить оператор ожидания теста, чтобы ожидатьlistItemsЧисло 3, а не 4.

if (listItems.length !== 3) 
  await page.screenshot({path: 'screenshot.png'});
expect(listItems.length).toBe(3);

Очевидно, что наш тест провалится, потому что у нас есть 4 в нашем приложении.listItems. запустить в терминалеtestСкрипт, тест не пройден. При этом вы найдетеscreenshot.pngдокумент.

снимок экрана

Вы также можете настроить метод скриншота следующим образом:

  • fullPage —  Если установленоtrue, Puppeteer сделает скриншот всей страницы.
  • quality —  Значение от 0 до 100, указывающее качество изображения.
  • clip —  Предоставляет объект для указания области страницы для создания снимка экрана.

Вы также можете не использоватьpage.screenshotметод, но использоватьpage.pdfдля создания PDF-файла страницы. Этот метод имеет свою конфигурацию.

  • scale— Число для установки коэффициента масштабирования, значение по умолчанию — 1.
  • format— Установите формат бумаги. Если это свойство установлено, оно имеет приоритет над любыми переданными ему параметрами ширины или высоты. Значение по умолчаниюletter.
  • margin —  используется для установки полей бумаги.

Обработка запросов страниц в тестах

Давайте посмотрим, как Puppeteer обрабатывает запросы страниц в тесте. существуетApp.jsфайл, я бы добавил асинхронныйcomponentDidMountметод. Этот метод получает данные от Pokemon API. Ответ на этот запрос будет в виде файла JSON. Я бы также добавил эти данные в состояние компонента.

async componentDidMount() {
  const data = await fetch('https://pokeapi.co/api/v2/pokedex/1/').then(res => res.json())
  this.setState({pokemon: data})
}

Обязательно добавьте в объект состоянияpokemon: {}. Внутри компонента приложения добавьте<h3>Этикетка.

<h3 data-testid="pokemon">
  {this.state.pokemon.next ? 'Received Pokemon data!' : 'Something went wrong'}
</h3>

Запустите приложение, вы обнаружите, что приложение успешно получило данные.

Используя Puppeteer, я могу писать задачи для проверки наших<h3/>Содержит ли элемент содержимое успешного запроса или перехватывает запрос и вызывает отказ. Таким образом, я могу видеть, как приложение работает как с успешными, так и с неудачными запросами.

Сначала я заставляю Puppeteer отправить запрос на перехват запроса на выборку. Тогда, если мой URL содержитpokeapi, то Puppeteer должен прервать перехваченный запрос. В противном случае все должно продолжаться.

ОткрытьApp.test.jsфайл, вbeforeAllДобавьте в метод следующий код:

await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
  if (interceptedRequest.url.includes('pokeapi')) {
    interceptedRequest.abort();
  } else {
    interceptedRequest.continue();
  }
});

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

Давайте напишем новый под названиемfails to fetch pokemonтест. Этот тест будет выполнятьсяh3элемент. Затем возьмите содержимое этого элемента, убедившись, что содержимоеReceived Pokemon data!.

await page.setRequestInterception(true);
page.on('request', interceptedRequest => {
  if (interceptedRequest.url.include('pokeapi')) {
    interceptedRequest.abort();
  } else {
    interceptedRequest.continue();
  }
});

воплощать в жизньdebugкод, вы действительно увидите<h3/>элемент. Вы заметите, что содержимое элемента всегда былоSomething went wrong. Все тесты пройдены, значит, мы успешно заблокировали запросы покемонов.

Обратите внимание, что при прерывании запроса мы можем контролировать заголовки запроса, возвращаемый код ошибки и сущность пользовательского ответа.


понять больше:


Программа перевода самородковэто сообщество, которое переводит высококачественные технические статьи из Интернета сНаггетсДелитесь статьями на английском языке на . Охват контентаAndroid,iOS,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.