E2E-тестирование (End-to-End) проверяет работу формы целиком: от ввода данных пользователем до отправки значения на сервер. Для библиотек форматирования ввода, таких как Cleave.js, такой подход особенно важен, поскольку форматирование происходит динамически во время печати.
Типичные проблемы, выявляемые только E2E-тестами:
value и rawValue;Главная особенность библиотеки заключается в том, что DOM-значение изменяется автоматически во время ввода.
Пример:
<input id="card">
new Cleave('#card', {
creditCard: true
});
При вводе:
4111111111111111
в поле отображается:
4111 1111 1111 1111
Однако внутреннее необработанное значение:
input.rawValue
будет содержать:
4111111111111111
E2E-тесты обязаны проверять оба состояния:
Наиболее распространённые решения:
| Инструмент | Особенности |
|---|---|
| Cypress | Простота тестирования UI |
| Playwright | Высокая скорость и мультибраузерность |
| Selenium | Классический WebDriver |
| WebdriverIO | Гибкая архитектура |
| TestCafe | Автоматическое ожидание |
| Puppeteer | Управление Chromium |
На практике Cleave.js чаще тестируется через:
npm install cypress --save-dev
npm install @playwright/test --save-dev
<input id="phone">
new Cleave('#phone', {
phone: true,
phoneRegionCode: 'US'
});
describe('Phone formatting', () => {
it('formats phone number', () => {
cy.visit('/');
cy.get('#phone')
.type('1234567890')
.should('have.value', '(123) 456-7890');
});
});
Cleave.js хранит очищенное значение внутри экземпляра.
Пример:
const cleave = new Cleave('#phone', {
phone: true,
phoneRegionCode: 'US'
});
Проблема заключается в том, что экземпляр часто недоступен из теста.
Распространённое решение:
window.phoneCleave = new Cleave('#phone', {
phone: true,
phoneRegionCode: 'US'
});
Тогда тест:
cy.window().then((win) => {
expect(
win.phoneCleave.getRawValue()
).to.equal('1234567890');
});
Paste-сценарии являются критически важными для Cleave.js.
cy.get('#phone')
.invoke('val', '1234567890')
.trigger('input');
cy.get('#phone')
.should('have.value', '(123) 456-7890');
new Cleave('#card', {
creditCard: true
});
cy.get('#card')
.type('4111111111111111')
.should(
'have.value',
'4111 1111 1111 1111'
);
Cleave.js умеет определять платёжную систему.
new Cleave('#card', {
creditCard: true,
onCreditCardTypeChanged(type) {
window.cardType = type;
}
});
Тест:
cy.get('#card')
.type('4111111111111111');
cy.window().its('cardType')
.should('eq', 'visa');
new Cleave('#date', {
date: true,
datePattern: ['d', 'm', 'Y']
});
cy.get('#date')
.type('01012025')
.should('have.value', '01/01/2025');
Удаление — один из самых сложных сценариев для масок ввода.
12/34/5678
При Backspace библиотека должна:
cy.get('#date')
.type('01012025')
.type('{backspace}')
.should('have.value', '01/01/202');
cy.get('#date')
.type('01012025')
.type('{backspace}{backspace}{backspace}')
.should('have.value', '01/01/2');
Cleave.js активно управляет caret position.
Ошибки курсора особенно заметны:
cy.get('#phone').then(($input) => {
const input = $input[0];
expect(input.selectionStart)
.to.equal(14);
});
cy.get('#phone')
.focus()
.invoke('val', '1234567890')
.trigger('input')
.then(($input) => {
const input = $input[0];
expect(input.selectionStart)
.to.equal(14);
});
Playwright обеспечивает более реалистичную эмуляцию браузера.
import { test, expect } from '@playwright/test';
test('formats card number', async ({ page }) => {
await page.goto('/');
const input = page.locator('#card');
await input.fill('4111111111111111');
await expect(input)
.toHaveValue('4111 1111 1111 1111');
});
const rawValue = await page.evaluate(() => {
return window.cardCleave.getRawValue();
});
expect(rawValue)
.toBe('4111111111111111');
Некоторые ошибки Cleave.js проявляются только при высокой скорости печати.
await page.locator('#phone')
.pressSequentially('1234567890', {
delay: 5
});
await expect(
page.locator('#phone')
).toHaveValue('(123) 456-7890');
Автозаполнение способно обходить события клавиатуры.
Это приводит к:
await page.evaluate(() => {
const input = document.querySelector('#phone');
input.value = '1234567890';
input.dispatchEvent(
new Event('input', { bubbles: true })
);
});
await expect(
page.locator('#phone')
).toHaveValue('(123) 456-7890');
Мобильные браузеры особенно сложны для Cleave.js.
Причины:
test.use({
viewport: {
width: 390,
height: 844
},
isMobile: true
});
await page.locator('#phone')
.fill('1234567890');
await expect(
page.locator('#phone')
).toHaveValue('(123) 456-7890');
IME используется для:
Во время composition Cleave.js может:
await page.evaluate(() => {
const input = document.querySelector('#field');
input.dispatchEvent(
new CompositionEvent('compositionstart')
);
input.value = 'テスト';
input.dispatchEvent(
new CompositionEvent('compositionend')
);
});
await expect(
page.locator('#field')
).toHaveValue('テスト');
В React Cleave.js обычно подключается через компонент.
<Cleave
options={{
creditCard: true
}}
/>
| Проблема | Причина |
|---|---|
| Двойной рендер | StrictMode |
| Потеря значения | controlled input |
| Конфликт state | React reconciliation |
| Пересоздание Cleave | повторный mount |
await page.locator('input')
.fill('4111111111111111');
await expect(
page.locator('input')
).toHaveValue(
'4111 1111 1111 1111'
);
await page.reload();
await expect(
page.locator('input')
).toHaveValue(
'4111 1111 1111 1111'
);
Во Vue проблемы часто связаны с:
v-model;await page.locator('#amount')
.fill('1000000');
await expect(
page.locator('#amount')
).toHaveValue('1,000,000');
E2E-тест должен проверять не только UI, но и отправляемые данные.
cy.intercept('POST', '/submit')
.as('submitForm');
cy.get('#card')
.type('4111111111111111');
cy.get('form').submit();
cy.wait('@submitForm')
.its('request.body.card')
.should(
'eq',
'4111111111111111'
);
Частая проблема:
4111 1111 1111 1111
отправляется вместо:
4111111111111111
Причины:
value;rawValue;cy.get('#card')
.type('4111111111111111');
cy.get('form').trigger('reset');
cy.get('#card')
.should('have.value', '');
cy.get('#card')
.type('4111111111111111');
cy.get('form').trigger('reset');
cy.get('#card')
.type('5555555555554444');
cy.get('#card')
.should(
'have.value',
'5555 5555 5555 4444'
);
Многие SPA создают поля динамически.
cy.get('#open-modal').click();
cy.get('#card')
.type('4111111111111111');
cy.get('#card')
.should(
'have.value',
'4111 1111 1111 1111'
);
cy.get('#destroy').click();
cy.get('#init').click();
cy.get('#card')
.type('4111111111111111');
cy.get('#card')
.should(
'have.value',
'4111 1111 1111 1111'
);
cy.get('#phone')
.clear()
.should('have.value', '');
cy.get('#phone')
.type('abc!@#')
.should('have.value', '');
cy.get('#phone')
.type('12345678901234567890');
Проверяются:
Большие формы с десятками Cleave-инстансов могут вызывать:
for (let i = 0; i < 100; i++) {
new Cleave(`#field-${i}`, {
numeral: true
});
}
const start = performance.now();
await page.locator('#field-1')
.fill('1000000');
const end = performance.now();
expect(end - start)
.toBeLessThan(100);
Особенно важно для SPA.
Типичная проблема:
for (let i = 0; i < 100; i++) {
await page.goto('/form');
await page.goto('/');
}
После цикла анализируются:
Форматирование не должно ломать доступность.
cy.get('#phone')
.should(
'have.attr',
'aria-label'
);
cy.get('#phone').focus();
cy.realPress('Tab');
cy.get('#next-field')
.should('be.focused');
Cleave.js часто создаёт нестабильные тесты из-за:
cy.get('#phone')
.type('1234567890');
cy.get('#phone')
.should(
'have.value',
'(123) 456-7890'
);
await expect(
page.locator('#card')
).toHaveValue(
'4111 1111 1111 1111'
);
Playwright автоматически повторяет проверку.
Поведение Cleave.js различается между:
projects: [
{
name: 'chromium'
},
{
name: 'firefox'
},
{
name: 'webkit'
}
]
| Браузер | Типичные проблемы |
|---|---|
| Safari | caret position |
| Firefox | paste handling |
| iOS Safari | composition events |
| Android Chrome | virtual keyboard |
| Edge | autofill |
Практически эффективный набор тестов обычно включает:
✓ format on type
✓ format on paste
✓ rawValue correctness
✓ form submit
✓ reset
✓ backspace
✓ mobile viewport
✓ autofill
✓ dynamic mount
✓ cross-browser
test('complete card flow', async ({ page }) => {
await page.goto('/checkout');
const card = page.locator('#card');
await card.fill('4111111111111111');
await expect(card)
.toHaveValue(
'4111 1111 1111 1111'
);
const raw = await page.evaluate(() => {
return window.cardCleave.getRawValue();
});
expect(raw)
.toBe('4111111111111111');
await page.click('#submit');
});