Cypress — чудовий інструмент тестування E2E. Ось кілька вагомих причин розглянути це:
- Можлива ізольована установка.
- Поставляється з підтримкою TypeScript із коробки.
- Забезпечує гарний інтерактивний досвід налагодження google chrome. Це дуже схоже на те, як розробники інтерфейсу користувача здебільшого працюють вручну.
- Має поділ команди на виконання, що забезпечує більш потужне налагодження та тестування стабільності (докладніше про це нижче).
- Містить неявні твердження для забезпечення більш значущого досвіду налагодження з менш крихкими тестами (докладніше про це в порадах нижче).
- Надає можливість легко створювати та спостерігати за серверними XHR, не змінюючи код програми (докладніше про це в порадах нижче).
Кроки, надані в цьому процесі інсталяції, дадуть вам гарну папку e2e
, яку ви можете скопіювати/вставити або як шаблон для вашої організації.
Ті самі дії представлені у відеоформаті на моєму youtube channel.
Створіть каталог e2e, встановіть cypress, TypeScript і налаштуйте файли конфігурації typescript і cypress:
mkdir e2e
cd e2e
npm init -y
npm install cypress typescript
npx tsc --init --types cypress --lib dom,es6
echo {} > cypress.json
Ось кілька причин для створення окремої папки
e2e
спеціально для cypress:
- Створення окремого каталогу для
e2e
полегшує ізоляцію його залежностейpackage.json
від решти вашого проекту. Це призводить до зменшення конфліктів залежностей. - Фреймворки тестування мають звичку забруднювати глобальний простір імен такими речами, як
describe
it
expect
. Найкраще зберігатиtsconfig.json
іnode_modules
e2e у цій спеціальній папціe2e
, щоб запобігти глобальним конфліктам визначення типу.
Додайте кілька сценаріїв до файлу e2e/package.json
:
"scripts": {
"cypress:open": "cypress open",
"cypress:run": "cypress run"
},
Напишіть свій перший тест у cypress/integration/basic.ts
:
it('should perform basic google search', () => {
cy.visit('https://google.com');
cy.get('[name="q"]')
.type('subscribe')
.type('{enter}');
});
Тепер запустіть npm run cypress:open
під час розробки та npm run cypress:run
на вашому сервері збірки 🌹
У папці e2e
у вас є такі файли:
/cypress.json
: Налаштувати cypress. За умовчанням порожній, і це все, що вам потрібно.- Підпапки
/cypress
:/integration
: усі ваші тести.- Не соромтеся створювати тести у вкладених папках для кращої організації, напр.
/someFeatureFolder/something.spec.ts
.
- Не соромтеся створювати тести у вкладених папках для кращої організації, напр.
- створіть файл
/cypress/integration/first.ts
з таким вмістом:
describe('google search', () => {
it('should work', () => {
cy.visit('http://www.google.com');
cy.get('#lst-ib').type('Hello world{enter}')
});
});
Відкрийте IDE cypress за допомогою такої команди.
npm run cypress:open
І виберіть тест для запуску.
Ви можете запустити тести Cypress у ci режимі за допомогою наступної команди.
npm run cypress:run
Тести Cypress скомпільовані / упаковані та запускаються в браузері. Тому сміливо імпортуйте будь-який код проекту у свій тест.
Наприклад, ви можете поділитися значеннями ідентифікатора між інтерфейсом користувача та тестами, щоб переконатися, що селектори CSS не зламаються:
import { Ids } from '../../../src/app/constants';
// Later
cy.get(`#${Ids.username}`)
.type('john')
Створення об’єктів, які надають зручний вказівник для всіх взаємодій, які різні тести повинні робити зі сторінкою, є загальною умовою тестування. Ви можете створювати об’єкти сторінки за допомогою класів TypeScript із геттерами та методами, наприклад.
import { Ids } from '../../../src/app/constants';
class LoginPage {
visit() {
cy.visit('/login');
}
get username() {
return cy.get(`#${Ids.username}`);
}
}
const page = new LoginPage();
// Later
page.visit();
page.username.type('john');
Cypress поставляється з (вбудованими) бібліотеками тверджень chai та chai-query, які допомагають тестувати веб-сторінки. Ви використовуєте їх із командою .should
, яка передається в ланцюжок як рядок, замінюючи .to.foo
на should('foo')
, наприклад. з chai-jquery ви використовуєте expect($(#foo)).to.have.text('something')
, з cypress це було cy.get('#foo').should('have.text', 'something')
:
cy.get('#foo')
.should('have.text', 'something')
Ви отримуєте інтелектуальну інформацію для ланцюжків
should
, оскільки Cypress поставляється з правильними визначеннями TypeScript 👍🏻
Повний список ланцюжків доступний тут: https://docs.cypress.io/guides/references/assertions.html
Якщо ви хочете щось складне, ви можете навіть використати should(callback)
і, наприклад,
cy.get('div')
.should(($div) => {
expect($div).to.have.length(1);
expect($div[0].className).to.contain('heading');
})
// Це лише приклад. Зазвичай ви б використали `.should('have.class', 'heading')
ПОРАДА: cypress також виконує автоматичні повторні спроби під час зворотного виклику, тому вони так само вільні від розшарування, як і стандартні цепочки рядків.
Кожен виклик функції в ланцюжку Cypress є command
. Команда should
є твердженням. Традиційно починати окрему category ланцюжків і дій окремо, наприклад.
// Don't do this
cy.get(/**something*/)
.should(/**something*/)
.click()
.should(/**something*/)
.get(/**something else*/)
.should(/**something*/)
// Prefer separating the two gets
cy.get(/**something*/)
.should(/**something*/)
.click()
.should(/**something*/)
cy.get(/**something else*/)
.should(/**something*/)
Деякі інші бібліотеки оцінюють і запускають код одночасно. Ці бібліотеки змушують вас мати єдиний ланцюжок, який може бути кошмаром для налагодження з перемішаними селекторами та твердженнями.
Команди Cypress по суті є деклараціями для середовища виконання cypress для виконання команд пізніше. Прості слова: Cypress робить це легше.
Нижче показано приклад:
cy.get('#foo')
// Коли #foo знайдено наступне:
.contains('Надіслати')
.click()
// ^ продовжуватиме шукати те, що містить текст `Надіслати`, і не вдасться, якщо мине час.
// ^ Після того, як його знайдено, клацніть вузол HTML, який містить текст `Надіслати`.
Cypress автоматично чекатиме (і повторюватиме спроби) для багатьох асинхронних речей, наприклад.
// Якщо немає запиту на псевдонім `foo`, cypress автоматично чекатиме 4 секунди
cy.wait('@foo')
// Якщо немає елемента з ідентифікатором #foo, cypress автоматично чекатиме 4 секунди та повторюватиме спроби
cy.get('#foo')
Це позбавляє вас від необхідності постійно додавати довільну логіку очікування (і повторних спроб) у потік тестового коду.
Cypress має концепцію неявного твердження. Вони спрацьовують, якщо майбутня команда має помилку через попередню команду. наприклад наступне буде помилкою в contains
(після автоматичних повторних спроб, звичайно), оскільки нічого знайденого не можна click
:
cy.get('#foo')
// Once #foo is found the following:
.contains('Submit')
.click()
// ^ Error: #foo does not have anything that `contains` `'Submit'`
У традиційних фреймворках ви отримаєте жахливу помилку, наприклад, click
не існує на null
. У Cypress ви отримуєте гарну помилку #foo
не містить Submit
. Ця помилка є формою неявного твердження.
Багато тестів традиційно були крихкими через усі довільні тайм-аути, необхідні для XHR, які створює програма. cy.server
полегшує це
- створити псевдонім для внутрішніх викликів
- дочекатися їх появи
cy.server()
.route('POST', 'https://example.com/api/application/load')
.as('load') // create an alias
// Start test
cy.visit('/')
// wait for the call
cy.wait('@load')
// Now the data is loaded
Ви також можете легко імітувати відповідь на запит за допомогою route
:
cy.server()
.route('POST', 'https://example.com/api/application/load', /* Example payload response */{success:true});
Ви можете стверджувати запити без mocking, використовуючи route
onRequest
/ onResponse
, наприклад.
cy.route({
method: 'POST',
url: 'https://example.com/api/application/load',
onRequest: (xhr) => {
// Example assertion
expect(xhr.request.body.data).to.deep.equal({success:true});
}
})
Ви можете використовувати wait
, щоб призупинити тест на деякий час, наприклад. щоб перевірити екран автоматичного сповіщення "Ви збираєтеся вийти з системи":
cy.visit('/');
cy.wait(waitMilliseconds);
cy.get('#logoutNotification').should('be.visible');
Однак рекомендується імітувати час за допомогою cy.clock
і пересилати час за допомогою cy.tick
.
cy.clock();
cy.visit('/');
cy.tick(waitMilliseconds);
cy.get('#logoutNotification').should('be.visible');
Ви також можете використовувати cypress для модульного тестування коду програми в ізоляції.
import { once } from '../../../src/app/utils';
// Later
it('should only call function once', () => {
let called = 0;
const callMe = once(()=>called++);
callMe();
callMe();
expect(called).to.equal(1);
});
Якщо ви використовуєте модульне тестування модулів у своїй програмі, ви можете надати макети за допомогою cy.stub
, наприклад. якщо ви хочете переконатися, що navigate
викликається у функції foo
:
foo.ts
import { navigate } from 'takeme';
export function foo() {
navigate('/foo');
}
- Ви можете зробити це, як у
some.spec.ts
:
/// <reference types="cypress"/>
import { foo } from '../../../src/app/foo';
import * as takeme from 'takeme';
describe('should work', () => {
it('should stub it', () => {
cy.stub(takeme, 'navigate');
foo();
expect(takeme.navigate).to.have.been.calledWith('/foo');
});
});
Коли ви викликаєте команду cypress (або твердження), наприклад. cy.get('#something')
, функція негайно повертає результат без фактичного виконання дії. Що він робить, це інформує тестувальника cypress, що вам потрібно буде виконати дію (у цьому випадку get
) у якийсь момент.
По суті, ви створюєте список команд, який потім буде виконувати ранер. Ви можете перевірити цю команду - відокремлення виконання за допомогою простого тесту, зауважте, що ви побачите, що оператори start / between / end
console.log
виконуються безпосередньо перед тим, як програма запуску почне виконувати команди:
/// <reference types="cypress"/>
describe('Hello world', () => {
it('demonstrate command - execution separation', () => {
console.log('start');
cy.visit('http://www.google.com');
console.log('between');
cy.get('.gLFyf').type('Hello world');
console.log('end');
});
});
Це розділення виконання команд має дві великі переваги:
- Виконувач може виконувати команди в flake-resistant спосіб з автоматичними повторними спробами та неявними твердженнями.
- Дозволяє писати асинхронний код у синхронному режимі без постійного chaining, що призводить до складності підтримки коду.
Автоматичні snapshots + журнал команд, згенерований тестом cypress, чудово підходять для налагодження. При цьому ви можете призупинити виконання тесту, якщо хочете.
Спочатку переконайтеся, що у вас відкриті інструменти розробника Chrome (з любов’ю — dev tools) у програмі тестування («CMD + ALT + i» на mac / F12
на Windows). Коли інструменти розробника відкриються, ви можете повторно запустити тест, і інструменти розробника залишаться відкритими. Якщо у вас відкриті інструменти розробника, ви можете призупинити виконання тесту двома способами:
- Точки зупинки коду програми: використовуйте оператор
debugger
у коді програми, і програма виконання тестів зупиниться на цьому, як і стандартна веб-розробка. - Точки зупинки тестового коду: ви можете використати команду
.debug()
, і виконання тесту cypress зупиниться на ній. Крім того, ви можете використати операторdebugger
у зворотному виклику команди.then
, щоб викликати паузу. наприклад.then(() => { debugger })
. Ви навіть можете використовувати його, щоб отримати якийсь елементcy.get('#foo').then(($ /* посилання на елемент dom */) => { debugger; })
або мережевий виклик, наприклад.cy.request('https://someurl').then((res /* відповідь мережі */) => { налагоджувач });
. Однак ідіоматичним способом єcy.get('#foo').debug()
, а потім, коли програму тестування призупинено наdebug
, ви можете натиснутиget
у журналі команд, щоб автоматичноconsole.log
будь-яка інформація, яка вам може знадобитися про команду.get('#foo')
(та аналогічно для будь-яких інших команд, які ви хочете налагодити).
Якщо вам потрібно запустити локальний сервер перед виконанням тестів, ви можете додати start-server-and-test
https://github.com/bahmutov/start-server-and-test як залежність. Це вимагає наступних аргументів
- сценарій npm для запуску сервера (він же сервер)
- кінцева точка, щоб перевірити, чи завантажився сервер (він же старт)
- сценарій npm для початку тестування (він же тест)
Приклад package.json:
{
"scripts": {
"start-server": "npm start",
"run-tests": "mocha e2e-spec.js",
"ci": "start-server-and-test start-server http://localhost:8080 run-tests"
}
}
- Веб-сайт: https://www.cypress.io/
- Напишіть свій перший cypress тест (подає чудову екскурсію по cypress IDE): https://docs.cypress.io/guides/getting-started/writing-your-first-test.html
- Налаштування середовища CI (наприклад, наданий образ докера, який одразу працює з
cypress run
): https://docs.cypress.io/guides/guides/continuous-integration.html - Рецепти (перелічує рецепти з описами. Натисніть на заголовки, щоб перейти до вихідного коду рецепту): https://docs.cypress.io/examples/examples/recipes.html
- Візуальне тестування: https://docs.cypress.io/guides/tooling/visual-testing.html
- Додатково встановіть
baseUrl
у cypress.json, щоб prevent an initial reload that happens after first оvisit
. - Покриття коду за допомогою Cypress: Webcast