E2E-тестирование форм

E2E-тестирование (End-to-End) проверяет работу формы целиком: от ввода данных пользователем до отправки значения на сервер. Для библиотек форматирования ввода, таких как Cleave.js, такой подход особенно важен, поскольку форматирование происходит динамически во время печати.

Типичные проблемы, выявляемые только E2E-тестами:

  • некорректное позиционирование курсора;
  • ошибки при вставке текста;
  • потеря символов при быстром вводе;
  • конфликты с мобильной клавиатурой;
  • неверная сериализация значений;
  • расхождение между value и rawValue;
  • ошибки при автозаполнении браузером;
  • сбои при работе масок дат и кредитных карт;
  • неправильное поведение при удалении символов.

Особенности тестирования Cleave.js

Главная особенность библиотеки заключается в том, что DOM-значение изменяется автоматически во время ввода.

Пример:

<input id="card">
new Cleave('#card', {
    creditCard: true
});

При вводе:

4111111111111111

в поле отображается:

4111 1111 1111 1111

Однако внутреннее необработанное значение:

input.rawValue

будет содержать:

4111111111111111

E2E-тесты обязаны проверять оба состояния:

  1. отображаемое значение;
  2. необработанное значение.

Инструменты для E2E-тестирования

Наиболее распространённые решения:

Инструмент Особенности
Cypress Простота тестирования UI
Playwright Высокая скорость и мультибраузерность
Selenium Классический WebDriver
WebdriverIO Гибкая архитектура
TestCafe Автоматическое ожидание
Puppeteer Управление Chromium

На практике Cleave.js чаще тестируется через:

  • Cypress;
  • Playwright.

Подготовка тестовой среды

Установка Cypress

npm install cypress --save-dev

Установка Playwright

npm install @playwright/test --save-dev

Тестирование обычного форматирования

Тестируемое поле

<input id="phone">
new Cleave('#phone', {
    phone: true,
    phoneRegionCode: 'US'
});

Тестирование в Cypress

Проверка отображаемого значения

describe('Phone formatting', () => {

    it('formats phone number', () => {

        cy.visit('/');

        cy.get('#phone')
            .type('1234567890')
            .should('have.value', '(123) 456-7890');
    });

});

Проверка rawValue

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.

Проверка paste

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 библиотека должна:

  • удалить символ;
  • сохранить маску;
  • корректно перестроить курсор.

Cypress-тест удаления

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.

Ошибки курсора особенно заметны:

  • в Safari;
  • на iOS;
  • в Android Chrome.

Получение позиции курсора

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 и Cleave.js

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');

});

Проверка rawValue в Playwright

const rawValue = await page.evaluate(() => {
    return window.cardCleave.getRawValue();
});

expect(rawValue)
    .toBe('4111111111111111');

Проверка быстрого ввода

Некоторые ошибки Cleave.js проявляются только при высокой скорости печати.


Имитация быстрого набора

await page.locator('#phone')
    .pressSequentially('1234567890', {
        delay: 5
    });

Проверка race conditions

await expect(
    page.locator('#phone')
).toHaveValue('(123) 456-7890');

Тестирование автозаполнения браузера

Автозаполнение способно обходить события клавиатуры.

Это приводит к:

  • отсутствию форматирования;
  • пропуску callback;
  • рассинхронизации состояния.

Эмуляция autofill

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.

Причины:

  • виртуальная клавиатура;
  • composition events;
  • автоисправления;
  • задержки input-событий.

Playwright mobile emulation

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-ввода

IME используется для:

  • японского;
  • китайского;
  • корейского ввода.

Во время composition Cleave.js может:

  • ломать символы;
  • обрывать ввод;
  • сбрасывать курсор.

Проверка composition events

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-приложений

В React Cleave.js обычно подключается через компонент.

Пример

<Cleave
    options={{
        creditCard: true
    }}
/>

Потенциальные проблемы

Проблема Причина
Двойной рендер StrictMode
Потеря значения controlled input
Конфликт state React reconciliation
Пересоздание Cleave повторный mount

E2E-проверка controlled input

await page.locator('input')
    .fill('4111111111111111');

await expect(
    page.locator('input')
).toHaveValue(
    '4111 1111 1111 1111'
);

Проверка сохранения state

await page.reload();

await expect(
    page.locator('input')
).toHaveValue(
    '4111 1111 1111 1111'
);

Тестирование Vue и Cleave.js

Во Vue проблемы часто связаны с:

  • v-model;
  • watcher;
  • реактивностью;
  • повторной инициализацией.

Проверка реактивного обновления

await page.locator('#amount')
    .fill('1000000');

await expect(
    page.locator('#amount')
).toHaveValue('1,000,000');

Проверка серверной отправки

E2E-тест должен проверять не только UI, но и отправляемые данные.


Перехват запроса в Cypress

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;
  • сериализация идёт напрямую из DOM.

Проверка reset формы

Тест

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 создают поля динамически.


Проверка lazy initialization

cy.get('#open-modal').click();

cy.get('#card')
    .type('4111111111111111');

cy.get('#card')
    .should(
        'have.value',
        '4111 1111 1111 1111'
    );

Проверка destroy/init

cy.get('#destroy').click();

cy.get('#init').click();

cy.get('#card')
    .type('4111111111111111');

cy.get('#card')
    .should(
        'have.value',
        '4111 1111 1111 1111'
    );

Тестирование edge cases

Пустой ввод

cy.get('#phone')
    .clear()
    .should('have.value', '');

Ввод спецсимволов

cy.get('#phone')
    .type('abc!@#')
    .should('have.value', '');

Очень длинный ввод

cy.get('#phone')
    .type('12345678901234567890');

Проверяются:

  • отсутствие зависаний;
  • отсутствие переполнения;
  • корректное ограничение длины.

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

Большие формы с десятками Cleave-инстансов могут вызывать:

  • лаги ввода;
  • блокировку UI;
  • множественные reflow;
  • утечки памяти.

Массовая инициализация

for (let i = 0; i < 100; i++) {

    new Cleave(`#field-${i}`, {
        numeral: true
    });

}

Проверка responsiveness

const start = performance.now();

await page.locator('#field-1')
    .fill('1000000');

const end = performance.now();

expect(end - start)
    .toBeLessThan(100);

Тестирование памяти

Особенно важно для SPA.

Типичная проблема:

  • Cleave уничтожается;
  • слушатели остаются;
  • память растёт после переходов.

Проверка утечек

for (let i = 0; i < 100; i++) {

    await page.goto('/form');

    await page.goto('/');

}

После цикла анализируются:

  • heap snapshots;
  • detached nodes;
  • event listeners.

Проверка accessibility

Форматирование не должно ломать доступность.


Проверка screen reader compatibility

cy.get('#phone')
    .should(
        'have.attr',
        'aria-label'
    );

Проверка tab navigation

cy.get('#phone').focus();

cy.realPress('Tab');

cy.get('#next-field')
    .should('be.focused');

Проблемы flaky-тестов

Cleave.js часто создаёт нестабильные тесты из-за:

  • асинхронного форматирования;
  • debounce;
  • requestAnimationFrame;
  • browser repaint.

Способы стабилизации

Ожидание завершения input

cy.get('#phone')
    .type('1234567890');

cy.get('#phone')
    .should(
        'have.value',
        '(123) 456-7890'
    );

Использование retry

await expect(
    page.locator('#card')
).toHaveValue(
    '4111 1111 1111 1111'
);

Playwright автоматически повторяет проверку.


Тестирование в разных браузерах

Поведение Cleave.js различается между:

  • Chrome;
  • Firefox;
  • Safari;
  • Edge;
  • мобильными WebView.

Конфигурация Playwright

projects: [
    {
        name: 'chromium'
    },
    {
        name: 'firefox'
    },
    {
        name: 'webkit'
    }
]

Наиболее проблемные браузеры

Браузер Типичные проблемы
Safari caret position
Firefox paste handling
iOS Safari composition events
Android Chrome virtual keyboard
Edge autofill

Стратегия построения E2E-набора

Практически эффективный набор тестов обычно включает:

  1. базовое форматирование;
  2. удаление символов;
  3. paste-сценарии;
  4. отправку формы;
  5. reset формы;
  6. mobile input;
  7. cross-browser проверки;
  8. autofill;
  9. rawValue validation;
  10. accessibility-тесты.

Минимальный production-ready набор

Обязательные проверки

✓ format on type
✓ format on paste
✓ rawValue correctness
✓ form submit
✓ reset
✓ backspace
✓ mobile viewport
✓ autofill
✓ dynamic mount
✓ cross-browser

Пример комплексного E2E-теста

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');

});