- Оригинальный адрес:Testing your React App with Puppeteer and Jest
- Оригинальный автор:Rajat S
- Перевод с:Программа перевода самородков
- Постоянная ссылка на эту статью:GitHub.com/rare earth/gold-no…
- Переводчик:jonjia
- Корректор:sunhaokk старый профессор
Как использовать 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
файл, мы создалиdebug
script, который установит 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
.
Если вы сейчас запуститеtest
script, вы обнаружите, что тест не пройден, потому что возвращается неопределенное значение. Теперь, чтобы решить эту проблему.
существует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,внешний интерфейс,задняя часть,блокчейн,товар,дизайн,искусственный интеллектЕсли вы хотите видеть более качественные переводы, пожалуйста, продолжайте обращать вниманиеПрограмма перевода самородков,официальный Вейбо,Знай колонку.