Playwright strict mode violation: устранение дублей
Исправьте Playwright strict mode violation: проверьте совпадения, уточните нужный компонент и подтвердите результат. С исполняемым примером ошибки.
BotBrowser Team
Нужна структурированная документация по теме Начало работы?
Эта статья относится к редакционной библиотеке. Для пошаговой настройки, справки и постоянных обновлений переходите сразу в соответствующий раздел docs.
Ошибка Playwright strict mode violation означает, что операция, которой нужен один элемент, нашла несколько. Если сообщение содержит resolved to 2 elements, проверьте оба совпадения и ограничьте локатор нужным компонентом, прежде чем менять тайм-ауты. Параметр exact: true исключает частичные совпадения имени, но не различает два элемента управления с одинаковым именем. BotBrowser поддерживает явно заданные параметры окружения браузера для воспроизведения различий в макете; он не определяет, на какой из одинаковых элементов тест намеревался нажать.
TL;DR
- На шаге сбоя проверьте количество совпадений локатора и все найденные узлы: снимок экрана может не показать скрытый дубликат или элемент за пределами видимой области.
- Ограничьте поиск границей приложения, например диалогом с уникальным именем или конкретной записью, затем используйте точное доступное имя либо стабильный тестовый ID.
- Различайте
getByText()иgetByRole(): локаторы ролей по умолчанию исключают скрытые элементы, тогда как скрытый текст в примере продолжает совпадать. - Не исправляйте неоднозначную идентичность увеличением паузы,
force: trueили.first(). После нажатия проверяйте результат в приложении.
Содержание
- Прочитайте сообщение об ошибке до изменения локатора
- Воспроизведите два совпадения на локальной странице
- Проверьте кандидатов и уточните область поиска
- Отделите строгость от готовности и гонок
- Рассматривайте различия окружения отдельно
- Практический вывод
Прочитайте сообщение об ошибке до изменения локатора
Правило строгости Playwright относится к операциям, направленным на один элемент. Для click() нужна одна цель; count() намеренно описывает коллекцию. Несколько совпадений допустимы для проверки списка, поэтому принудительное сведение каждого локатора к одному элементу сломает и корректные тесты списков. Записывайте не только селектор, но и операцию, которая завершилась ошибкой.
| Наблюдение | Что проверить сначала | Направление исправления |
|---|---|---|
resolved to 2 elements | Оба найденных узла и содержащие их компоненты | Задайте уникальный локатор нужного компонента |
Save также находит Save draft | Режим сопоставления текста или доступного имени | Используйте exact: true, если контракт задаёт полное имя |
Два элемента управления с именем Save | Диалог, форму, строку или состояние приложения | Добавьте область поиска: точного имени недостаточно |
| На шаге сбоя совпадений нет | Текущую страницу, фрейм и условие готовности | Выберите правильный документ и дождитесь его сигнала |
| Совпадение одно, но нажатие превышает тайм-аут | Видимость, стабильность, доступность и получение событий | После установления идентичности проверьте готовность к действию |
Псевдонимы локаторов в ошибке помогают распознать кандидатов; это не указание вставить самый короткий сгенерированный селектор. Если навигация для компьютера и телефона содержит одну и ту же ссылку, выберите нужную навигационную область. Если в двух товарных строках есть Buy, сначала определите строку по идентичности записи. Запуск браузера и настройка извлечения данных относятся к настройке сбора данных с Playwright; успешный запуск не устанавливает смысл селектора.
Сохраните контекст сбоя
До редактирования селектора сохраните сообщение об ошибке, ревизию теста, версию пакета Playwright, категорию конечного URL страницы и цепочку локаторов, использованную действием. Цепочка вроде page.getByRole('dialog').getByText('Save') содержит больше информации, чем последний текстовый фрагмент. Если скопировать в отладчик только Save, получится другой набор совпадений, что может привести к неверному исправлению. Сохраните и выбор фрейма: локатор внутри frameLocator() ищет в другом документе, чем локатор страницы.
Число в сообщении об ошибке соответствует наблюдению именно для неудавшейся операции. Диагностический count(), выполненный через несколько секунд, уже даёт новое наблюдение; различие чисел свидетельствует об изменении DOM, а не доказывает ошибочность сообщения. Сохраните трассировку и соответствующий переход приложения вместо повторных обновлений страницы до исчезновения второго узла. Постоянная ошибка с двумя неизменными элементами требует исправить идентичность; непостоянное количество дублей требует также объяснить переход состояния.
Воспроизведите два совпадения на локальной странице
В пустом проекте Node установите playwright, установите его Chromium командой npx playwright install chromium и сохраните следующий код как strict.mjs. Пример не обращается к внешнему сайту. Он перехватывает ожидаемую ошибку строгости, затем доказывает, что нажатие в ограниченной области изменило правильный диалог.
import assert from 'node:assert/strict';
import { chromium } from 'playwright';
const browser = await chromium.launch();
try {
const page = await browser.newPage();
await page.setContent(`
<button style="display:none">Save</button>
<div role="dialog" aria-label="Edit profile">
<button onclick="this.parentElement.dataset.saved='yes'">Save</button>
</div>
`);
const ambiguous = page.getByText('Save', { exact: true });
assert.equal(await ambiguous.count(), 2);
await assert.rejects(() => ambiguous.click({ timeout: 1500 }), /strict mode violation/);
const dialog = page.getByRole('dialog', {
name: 'Edit profile',
exact: true,
});
const save = dialog.getByRole('button', { name: 'Save', exact: true });
assert.equal(await save.count(), 1);
await save.click();
assert.equal(await dialog.getAttribute('data-saved'), 'yes');
console.log('textMatches=2 scopedMatches=1 saved=yes');
} finally {
await browser.close();
}
Выполните node strict.mjs. Ожидаемая последняя строка: textMatches=2 scopedMatches=1 saved=yes; первое нажатие отклонено, а не выполнено. Удалите assert.rejects() и напрямую дождитесь неоднозначного нажатия, чтобы увидеть неперехваченную ошибку strict mode violation. Скрытая кнопка намеренно делает DOM отличным от того, что видно на снимке экрана.
Расширенный пример из репозитория был выполнен 2026-11-11 с Playwright 1.58.0, обычным Chrome 157.0.8103.51 и macOS arm64. Прошли семь проверок, включая фильтрацию скрытых элементов по роли, одинаковые диалоги и объединение or(). Эти данные подтверждают синтетический тест локаторов, а не запуск BotBrowser или поведение приложения во времени. Переносимый вариант запуска выше использует установленный Chromium Playwright; зафиксированный запуск использовал явно указанный исполняемый файл обычного Chrome.
Проверьте кандидатов и уточните область поиска
Размещайте диагностику непосредственно перед неудающейся операцией. Сохраняйте только атрибуты, необходимые для различения кандидатов: полный экспорт DOM может включать данные учётной записи. Такой снимок полезен как свидетельство конкретного момента, но не гарантирует, что DOM останется неизменным.
const target = page.getByText('Save', { exact: true });
console.log(await target.count());
console.table(
await target.evaluateAll(nodes =>
nodes.map(node => ({
tag: node.tagName,
id: node.id,
testId: node.getAttribute('data-testid'),
dialog: node.closest('[role="dialog"]')?.getAttribute('aria-label'),
rects: node.getClientRects().length,
}))
)
);
Поле rects служит диагностической подсказкой, а не полным расчётом видимости или доступности Playwright. У узла может быть геометрия, даже если его перекрывают; видимость CSS может отличаться от присутствия в дереве доступности. В справочнике локатора роли описан includeHidden; exact управляет доступным именем. В примере обычный локатор роли находит одну кнопку Save, а includeHidden: true находит две. Не переносите поведение текстового локатора на все механизмы поиска.
Предпочитайте уникальную область диалога, формы или записи, которая выражает причину правильности цели. Стабильный data-testid подходит, если приложение не предоставляет полезного семантического различия. И getByRole('dialog', { name: 'Edit profile', exact: true }), и его дочерняя кнопка Save должны идентифицировать нужный компонент. Если приложение отображает два диалога с одинаковым именем, дочерний локатор всё ещё может найти два совпадения; исправьте состояние приложения или добавьте настоящую идентичность вместо очередного позиционного селектора.
Для Save и Save draft точного совпадения достаточно, если требуемая надпись действительно Save. Для двух одинаковых надписей Save оно ничего не меняет. Фильтр видимости полезен, когда именно видимость является нужным отличием и закреплённая версия Playwright поддерживает такой фильтр; он всё равно не поможет, если видны обе копии. Проверяйте это условие на адаптивных макетах, а не считайте видимость синонимом правильности.
Повторяющимся записям нужна идентичность записи
В таблице сначала определите запись, а затем её действие. Если приложение намеренно предоставляет тестовый ID строки, локатор может выразить этот контракт без привязки к цене, индексу строки или посторонней ячейке, случайно содержащей те же слова. Следующий блок иллюстрирует тест собственной таблицы заказов, а не другой результат локального примера Save. Для него нужны @playwright/test и приложение, которое отображает указанную строку и подтверждение.
const row = page.getByTestId('order-1042');
await expect(row).toHaveCount(1);
const archive = row.getByRole('button', { name: 'Archive', exact: true });
await expect(archive).toHaveCount(1);
await archive.click();
await expect(page.getByTestId('archived-order-1042')).toBeVisible();
Важным инвариантом является идентичность заказа, а не случайное расположение цели в первой строке. Сортировка, фильтрация и новая поступившая запись могут переместить тот же заказ в другую позицию. Если виртуализированная таблица удаляет из DOM строки за пределами экрана, отсутствие строки означает отдельную проблему загрузки или навигации. Не заменяйте .getByTestId('order-1042') на .first(), чтобы скрыть это отсутствие; включите нужную запись в отображаемую коллекцию через обычные элементы управления приложения.
Если тестового ID нет, filter({ has: ... }) может выразить связь с уникально определённым потомком. Внутренний локатор вычисляется относительно элемента-кандидата; это не независимый запрос страницы, ищущий любое упоминание записи. Проверьте количество отфильтрованных строк: номер записи в подвале или вложенной строке подробностей может дать ещё одно совпадение. Предпочитайте доступную метку или осознанный тестовый контракт хрупкой цепочке предков, зависящей от текущих CSS-обёрток.
Отличайте фреймы от дубликатов элементов страницы
Iframe не становится частью родительского документа только потому, что виден на снимке экрана. Поэтому локатор страницы с нулём совпадений может корректно искать в неправильно выбранном документе. Сначала выберите нужный фрейм, например через page.frameLocator('iframe[title="Checkout"]'), затем найдите его элемент управления. И наоборот, два элемента фрейма, совпадающие с этим селектором, создают новую неоднозначность; уникальное имя кнопки внутри каждого фрейма не делает выбор фрейма однозначным.
Фиксируйте стабильную идентичность фрейма в приложении, а не используйте первый фрейм, возвращённый перечислением. Реклама, аналитика и встроенные предпросмотры могут менять порядок фреймов между запусками. Для тестовой интеграции собственного приложения с другим origin используйте поддерживаемые локаторы фреймов Playwright; не предполагайте, что DOM-запрос родителя может пройти во встроенный документ. Это отделяет отсутствие цели из-за границы документа от наличия двух целей внутри одного документа.
Проверяйте доступное имя, а не только видимый текст
Параметр name локатора роли задаёт вычисленное доступное имя. Оно может поступать из aria-label, aria-labelledby или нативной метки, а не из текста, нарисованного внутри кнопки. Поэтому два видимых значка могут иметь одно имя Save, хотя их изображения различаются. И наоборот, кнопка с надписью Save может иметь доступное имя Save profile. Прежде чем менять exact, проверьте информацию о доступности самого элемента управления в инструментах браузера; расширение шаблона имени может добавить кандидатов вместо восстановления нужного.
Если неверный aria-labelledby связывает несколько элементов управления с одной меткой, исправьте подписи компонента, когда приложение принадлежит вам. Селектор только для теста может помочь диагностике, но не исправляет путаницу в именах для пользователей вспомогательных технологий. Сохраните отдельную проверку доступности, если эти подписи являются частью контракта продукта.
Отделите строгость от готовности и гонок
Перед нажатием Playwright проверяет готовность к действию, включая единственность цели, видимость, стабильность, получение событий и разрешённое состояние. Автоожидание не определяет, какой из двух допустимых элементов относится к задаче. Увеличенный тайм-аут и фиксированные паузы не выражают этот выбор. Параметр force: true ослабляет некоторые проверки готовности, но не устраняет смысловую неоднозначность двух совпадающих элементов.
Во время перехода компонента старая и новая копии могут ненадолго сосуществовать. Если приложение предоставляет сигнал завершения, используйте повторяющую проверку веб-состояния вроде await expect(dialog).toHaveCount(1) из @playwright/test, затем нажмите нужный дочерний элемент и проверьте бизнес-результат. Отдельный count() даёт снимок и не повторяет попытки, как эта проверка. Даже проверка с последующим нажатием не является атомарной блокировкой: локаторы вычисляются заново, поэтому новый дубликат может обоснованно вызвать ошибку последующего действия.
Исследование трассировки Playwright предоставляет снимки действий и временную шкалу, позволяющие определить, были дубликаты постоянными или переходными. Сохраняйте трассировку фактического неудачного запуска: открыв текущий DOM после ошибки, можно полностью пропустить дубликат. Исчезновение индикатора загрузки полезно только тогда, когда приложение гарантирует, что оно означает завершение изменений соответствующего компонента.
Выражение a.or(b) объединяет альтернативы, а не задаёт приоритет. Если обе ветви находят совпадения, операция для одного элемента может завершиться ошибкой строгости. Когда могут появиться два разных состояния, дождитесь подходящего условия, проверьте фактическое состояние приложения и действуйте через его конкретный локатор. .first() или .nth() разумны лишь тогда, когда сам порядок является проверяемым требованием, например при выборе первого результата рейтинга. Для несвязанных одинаковых кнопок они могут незаметно нажать не тот элемент после изменения макета.
Дождитесь конкретного перехода и проверьте его результат
Предположим, закрытие редактора запускает анимацию, а другой редактор открывается до удаления старого из DOM. Локатор именованного диалога может ненадолго находить обе копии. Самый сильный контракт приложения выражается явным состоянием, например одним активным редактором конкретной записи. Если продукт вместо этого гарантирует, что старый диалог удаляется до готовности нового к действию, дождитесь исчезновения старого диалога именно этой записи и проверьте идентичность нового. Ожидать исчезновения всех диалогов неправильно, когда целевое состояние намеренно оставляет один открытым.
Повторные попытки должны сохранять эти различия. Повторное нажатие может отправить форму дважды, если первое удалось, но проверка результата выполнилась слишком рано. Используйте уникальный идентификатор операции или предоставляемую приложением метку завершения, когда рабочий процесс этого требует. Не подавляйте исключение строгости с переходом к постороннему уведомлению об успехе: оно может относиться к предыдущему действию. Пример Save проверяет локальный атрибут data-saved ограниченного диалога именно для того, чтобы посторонний узел не мог удовлетворить условие успеха.
После исправления селектора проверьте контрпример, а не только успешный сценарий: отобразите вторую запись с тем же названием кнопки, измените порядок записей и оставьте неактивный компонент в DOM. Исправленный локатор должен продолжать выбирать нужную запись или явно завершаться ошибкой, если её идентичность дублируется. Это проверки конкретного приложения, а не утверждение, что синтетический пример покрывает все фреймворки и переходы. Два одинаково названных диалога в расширенном примере намеренно показывают последнюю границу отказа.
Рассматривайте различия окружения отдельно
Точка перелома адаптивного макета может отобразить одновременно разметку для компьютера и телефона, а изменение шрифта или области просмотра может повлиять на вариант диалога. Воспроизведите проблему с той же версией браузера, областью просмотра, локалью и состоянием приложения, прежде чем приписывать разные наборы кандидатов поведению браузера. Новый профиль браузера не исправляет селектор, намеренно совпадающий с двумя строками учётных записей.
BotBrowser поддерживает явно заданные параметры экрана, шрифтов и окружения браузера, описанные в справочнике конфигурации профилей. Он не предоставляет идентичность компонента приложения, не удаляет скрытый дубликат и не гарантирует бизнес-результат нажатия. Сравнивайте окружения после диагностики локатора и состояния приложения; семь локальных проверок примера ничего не устанавливают об отпечатках или результате удалённого сервиса.
Вопросы и ответы
Это строгий режим JavaScript?
Нет. Директива 'use strict' управляет поведением языка JavaScript. Строгость локаторов Playwright определяет, сколько элементов принимает операция с единственной целью. Отключение строгого режима JavaScript не меняет набор совпадений локатора.
Подходит ли такая диагностика для Python Playwright?
Да, решение о выборе локатора такое же, хотя Python использует имена вроде get_by_role() и exact=True. При переносе кода сохраняйте область компонента. Исключения элементов в Java Selenium относятся к другому API, и их нельзя классифицировать по строке ошибки Playwright.
Почему count работает, а click завершается ошибкой?
count() описывает коллекцию и допускает несколько результатов. Для нажатия нужен один предполагаемый элемент. Поэтому число два означает успешное наблюдение неоднозначности, а не доказывает, что следующее действие будет принято.
Источники
- Строгость локаторов Playwright
- Готовность к действию и принудительные действия Playwright
- Параметры локатора роли Playwright
- Альтернативы локаторов Playwright с or()
- Конфигурация профилей BotBrowser
Практический вывод
При ошибке Playwright strict mode violation сохраните неудавшийся локатор, проверьте полный набор его совпадений и выразите нужный компонент в селекторе. Сначала запустите локальный пример, затем замените границу диалога реальной идентичностью приложения и проверьте результат нажатия. Исследуйте готовность или окружение браузера только после однозначного определения нужной цели.
Эта статья входит в руководство: Начало работы с автоматизацией браузера: карта
Похожие статьи
Selenium element not interactable: причины и исправление
Начало работыSelenium Shadow DOM: поиск и устаревшие корни
Начало работыОжидания Selenium WebDriver для стабильных тестов
Начало работыTrace Viewer Playwright для отладки разрешённых тестов
Начало работыПрокси Playwright: маршруты и аутентификация
Начало работыСбор данных с Playwright: браузер и профиль
Выполните ту же проверку в BotBrowser
Загрузите профиль, сохраните внутри браузера те сигналы, о которых только что прочитали, и подтвердите результат на своей машине до раскатки.