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

Inputmask часто используется в формах для стандартизации пользовательского ввода: телефоны, даты, номера документов, банковские реквизиты. При E2E тестировании подобных форм возникает специфический слой сложности, связанный не только с DOM-логикой, но и с динамической трансформацией введённого значения.

Механизм работы Inputmask заключается в перехвате пользовательского ввода и преобразовании его в заданный формат. Внутренне это выражается в постоянной модификации value input-элемента, а также в обработке событий keydown, input, paste, blur.

Ключевая особенность:

отображаемое значение ≠ введённое значение

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

При E2E проверках это проявляется в следующих аспектах:

  • асинхронное применение маски после события ввода
  • перерасчёт значения после blur
  • различие между raw value и formatted value
  • вмешательство автокоррекции браузера на мобильных устройствах

Подходы к E2E тестированию форм с масками

Стабилизация состояния поля ввода

Перед выполнением проверок необходимо учитывать, что Inputmask может применять преобразования не мгновенно. В тестах это выражается в необходимости ожидания стабилизации DOM-состояния.

В рамках Cypress часто используется стратегия повторной проверки значения:

cy.get('input[name="phone"]')
  .type('77001234567')
  .should('have.value', '+7 (700) 123-45-67')

Ключевой момент — использование should, которое автоматически ретраит проверку до стабилизации результата.

В Playwright аналогичный подход реализуется через ожидания:

import { expect } from '@playwright/test';

await page.fill('input[name="phone"]', '77001234567');

await expect(page.locator('input[name="phone"]'))
  .toHaveValue('+7 (700) 123-45-67');

Разделение raw value и formatted value

Inputmask часто предоставляет доступ к «чистому» значению через свойства элемента или API. В тестах важно различать:

  • formatted value — отображаемое пользователю
  • unmasked value — значение без форматирования

Типичная ошибка E2E сценариев заключается в проверке только value, игнорируя внутреннюю модель.

Пример проверки через DOM:

const input = document.querySelector('input[name="date"]');

console.log(input.value); 
// 31.12.2025 (formatted)

В некоторых конфигурациях Inputmask добавляет доступ к raw значению через dataset или API плагина, что требует отдельной проверки через evaluate в браузерном контексте.

Работа с событиями ввода

Inputmask активно реагирует на последовательность событий:

  • keydown — первичная обработка символа
  • input — применение маски
  • change — фиксация значения
  • blur — финальная нормализация

E2E тесты должны учитывать порядок событий, особенно при симуляции ввода через type() или fill().

Пример проблемного сценария:

cy.get('input[name="card"]')
  .type('4111111111111111')
  .blur();

В момент выполнения blur значение может быть ещё не нормализовано, если маска имеет задержку или асинхронную логику.

Обработка вставки (paste) и clipboard событий

Paste-события часто обрабатываются иначе, чем посимвольный ввод. Inputmask может применять отдельный pipeline для вставленного текста.

В E2E тестах это проявляется следующим образом:

cy.get('input[name="phone"]').invoke('val', '77001234567').trigger('input');

или в Playwright:

await page.locator('input[name="phone"]').fill('77001234567');

Однако при использовании clipboard API возможны расхождения, так как часть масок фильтрует символы до попадания в value.

Проверка различных типов масок

Телефонные номера

Типичный кейс — международные форматы:

  • +7 (___) ___-__-__
  • +1 (___) ___-____

Проверка должна учитывать:

  • наличие префикса
  • позиционные разделители
  • защиту от лишних символов
cy.get('input[name="phone"]')
  .type('1234567890')
  .should('have.value', '+1 (123) 456-7890');

Даты

Маски дат часто включают автоматическую подстановку разделителей:

cy.get('input[name="date"]')
  .type('31122025')
  .should('have.value', '31/12/2025');

Особенность заключается в том, что некоторые реализации Inputmask валидируют диапазоны (например, месяц ≤ 12), что должно учитываться в негативных тестах.

Кредитные карты

Маски банковских карт часто группируют цифры:

cy.get('input[name="card"]')
  .type('4111111111111111')
  .should('have.value', '4111 1111 1111 1111');

Дополнительно проверяется:

  • запрет букв
  • ограничение длины
  • корректная группировка

Негативные сценарии

E2E тестирование масок включает не только позитивные кейсы, но и проверку устойчивости к некорректному вводу:

  • ввод букв в числовое поле
  • вставка символов !@#$%
  • превышение длины
  • частичный ввод и уход с поля
cy.get('input[name="phone"]')
  .type('abc!!!123')
  .should('have.value', '+1 (123');

Асинхронность и flaky-тесты

Одной из основных проблем является нестабильность тестов, вызванная:

  • задержкой применения маски
  • повторным форматированием после blur
  • реакцией на debounce внутри Inputmask

Стабилизация достигается через:

  • использование should / expect с ретраями
  • добавление явных ожиданий состояния DOM
  • проверку конечного состояния, а не промежуточного

Пример нестабильного подхода:

cy.get('input').type('123');
cy.get('input').should('have.value', '123');

Пример устойчивого подхода:

cy.get('input')
  .type('123')
  .should('have.value', '(123) ___-____');

Селекторы и изоляция тестов

Для форм с Inputmask критично использовать стабильные селекторы:

  • data-testid
  • data-cy
  • data-qa

Пример:

<input data-testid="phone-input" />
cy.get('[data-testid="phone-input"]').type('70001234567');

Использование CSS-селекторов по классам нежелательно из-за высокой вероятности изменения разметки при интеграции маски.

Пользовательские команды в тестах

При большом количестве форм с масками формируется слой абстракции:

Cypress.Commands.add('typeMaskedPhone', (selector, value) => {
  cy.get(selector)
    .clear()
    .type(value)
    .blur();
});

Использование подобных команд снижает дублирование логики и уменьшает влияние изменений Inputmask-конфигурации на тестовый код.

Поведение в разных браузерах

E2E тесты должны учитывать различия:

  • Chrome: стабильная работа input events
  • Firefox: возможные отличия в paste handling
  • Safari: особенности автокоррекции и форматирования
  • Mobile WebView: вмешательство системной клавиатуры

Inputmask может вести себя по-разному в зависимости от event loop браузера, что требует кроссбраузерной проверки через Playwright или Selenium Grid.

Интеграция с CI

В CI окружениях часто проявляются дополнительные проблемы:

  • увеличенная задержка рендера DOM
  • различие шрифтов и input rendering
  • headless-режим, влияющий на paste/input события

Рекомендуемые практики:

  • фиксация таймаутов ожиданий
  • отключение нестабильных анимаций
  • использование headless mode с одинаковой конфигурацией браузера
  • повтор прогонов flaky-тестов

Поведение при программном изменении значения

Inputmask реагирует не только на пользовательский ввод, но и на:

input.value = '123456';
input.dispatchEvent(new Event('input'));

В E2E сценариях это используется для:

  • подготовки состояния формы
  • имитации серверных автозаполнений
  • тестирования восстановления данных

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