[{"data":1,"prerenderedAt":61},["ShallowReactive",2],{"wiki-test-automation-page-object-and-object-repository-ru":3},{"meta":4,"html":8,"headings":9,"words":60},{"title":5,"description":6,"published":7,"updated":7,"sourceUpdated":7},"Репозиторий объектов и Page Object","Репозиторий объектов и Page Object: вынос локаторов из тестов, объекты страниц и компонентов, страницы и элементы как данные, поддержка, Screenplay.","2026-11-03","\u003Cp>\u003Cstrong>Репозиторий объектов\u003C/strong> (object repository) и \u003Cstrong>объект страницы\u003C/strong> (page object) - два способа хранить знание о пользовательском интерфейсе в одном месте, а не повторять его в каждом автотесте. Объект страницы - класс в коде тестов, который представляет страницу или её часть и предоставляет тестам её услуги: ввести учётные данные, открыть входящие, прочитать сообщение об ошибке. Репозиторий объектов - хранилище описаний элементов в виде данных, устроенное как страницы и их элементы; шаги тестов ссылаются на элементы по имени. В обоих случаях изменение интерфейса отражается в одном месте, а тесты остаются прежними.\u003C/p>\n\u003Cp>Мартин Фаулер впервые описал эту идею под названием Window Driver. Название page object распространил проект Selenium, а в сентябре 2013 года Фаулер изложил паттерн в статье «PageObject» на своём сайте. Сейчас паттерн Page Object описан в документации Selenium («Page object models») и в документации Playwright («Page object models»). Программа ISTQB Advanced Level Test Automation Engineering (версия 2.0, 2024 год) называет модель объектов страниц среди паттернов проектирования автоматизации тестирования. Репозиторий объектов пришёл из практики инструментов автоматизации, где шаги тестов строятся из ключевых слов или собираются без кода; само тестирование на основе ключевых слов стандартизировано в ISO/IEC/IEEE 29119-5 (текущая редакция 2024 года).\u003C/p>\n\u003Ch2 id=\"проблема-дублирования-локаторов\" class=\"wiki-heading\">Проблема дублирования локаторов\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"проблема-дублирования-локаторов\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Cp>UI-тест находит элементы с помощью \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"locator\">локаторов\u003C/span> и выполняет над ними действия. Виды локаторов и порядок их выбора описаны в статье \u003Ca href=\"/ru/wiki/test-automation/ui-test-locators\" class=\"wiki-link wiki-link--article\">Локаторы в UI-автотестах\u003C/a>. Когда локаторы записаны прямо в тестах, каждый тест, который проходит через страницу, повторяет их.\u003C/p>\n\u003Cp>Два теста страницы входа на Playwright с локаторами внутри тестов:\u003C/p>\n\u003Cdiv class=\"wiki-code\" data-language=\"typescript\">\u003Cdiv class=\"wiki-code__header\">\u003Cspan class=\"wiki-code__language\">typescript\u003C/span>\u003Cbutton type=\"button\" class=\"wiki-code__copy\" data-wiki-copy data-copied-label=\"Скопировано\">\u003Csvg class=\"wiki-code__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/>\u003C/svg>\u003Csvg class=\"wiki-code__icon wiki-code__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003Cspan class=\"wiki-code__label\">Копировать\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Скопировано\u003C/span>\u003C/button>\u003C/div>\u003Cpre>\u003Ccode>import { test, expect } from '@playwright/test';\n\ntest('user signs in with valid credentials', async ({ page }) =&gt; {\n  await page.goto('https://example.com/login');\n  await page.getByLabel('Email').fill('anna@example.com');\n  await page.getByLabel('Password').fill('correct-password');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await expect(page.getByRole('heading', { name: 'Inbox' })).toBeVisible();\n});\n\ntest('user sees an error with a wrong password', async ({ page }) =&gt; {\n  await page.goto('https://example.com/login');\n  await page.getByLabel('Email').fill('anna@example.com');\n  await page.getByLabel('Password').fill('wrong-password');\n  await page.getByRole('button', { name: 'Sign in' }).click();\n  await expect(page.getByRole('alert')).toHaveText('Invalid email or password');\n});\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Три локатора формы повторяются в обоих тестах. В наборе обычно десятки тестов, которые сначала выполняют вход. Если подпись кнопки меняется с «Sign in» на «Log in», все они падают, и каждый приходится править. Кроме того, тест смешивает два вида знания: что проверяется и как устроена страница. Документация Selenium называет обе проблемы на собственном примере: локаторы перемешаны с тестом, и одни и те же локаторы повторяются в каждом тесте, который использует страницу.\u003C/p>\n\u003Cp>Влияние на поддержку измерено в промышленном исследовании Леотты, Клериси, Риччи и Спадаро (ICSTW 2013). Два набора по 25 \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"test-case\">тест-кейсов\u003C/span> для одного веб-приложения приводили в соответствие с новым релизом. Набор без объектов страниц потребовал 124 минуты и 90 изменённых строк кода. Набор с 19 объектами страниц - 43 минуты и 11 строк. На старте набор с объектами страниц потребовал больше кода: 3320 строк против 2891.\u003C/p>\n\u003Ch2 id=\"паттерн-page-object\" class=\"wiki-heading\">Паттерн Page Object\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"паттерн-page-object\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Ch3 id=\"услуги-вместо-разметки\" class=\"wiki-heading\">Услуги вместо разметки\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"услуги-вместо-разметки\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h3>\n\u003Cp>Фаулер описывает объект страницы как обёртку над HTML-страницей или её фрагментом с API в терминах приложения. Он должен позволять тесту делать и видеть всё, что может пользователь. Методы объекта страницы говорят языком приложения: «войти как», «число непрочитанных писем», а не «найти элемент по такому селектору и нажать на него». Текстовое поле представлено строкой, флажок - логическим значением, кнопка - действием.\u003C/p>\n\u003Cp>Документация Selenium описывает тот же класс с двух сторон. Тестам он предоставляет услуги. По отношению к странице он единственный глубоко знает структуру её HTML. В результате во всём \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"test-suite\">наборе тестов\u003C/span> есть только одно место, которое знает, как устроена конкретная страница.\u003C/p>\n\u003Cp>Объект страницы не обязан охватывать всю страницу. Фаулер рекомендует строить объекты для значимых элементов интерфейса и следовать структуре, понятной пользователю, а не разметке вёрстки.\u003C/p>\n\u003Ch3 id=\"навигация-и-возвращаемые-объекты\" class=\"wiki-heading\">Навигация и возвращаемые объекты\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"навигация-и-возвращаемые-объекты\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h3>\n\u003Cp>Действие, которое переводит пользователя на другую страницу, возвращает объект этой страницы. Успешный вход возвращает страницу входящих, неуспешный - саму страницу входа. Документация Selenium рекомендует моделировать разные исходы одного действия разными методами: в её примере это \u003Ccode>loginAs\u003C/code> и \u003Ccode>loginAsExpectingError\u003C/code>. Когда метод меняет тип возвращаемого значения, тесты, которые опираются на старую навигацию, перестают компилироваться, и ошибка обнаруживается до запуска.\u003C/p>\n\u003Cp>Фаулер упоминает альтернативу: объекты страниц возвращают общий контекст браузера, а тест сам решает, какой объект страницы создать дальше. Так знание о навигации остаётся в тесте.\u003C/p>\n\u003Ch3 id=\"проверки-внутри-объектов-страниц\" class=\"wiki-heading\">Проверки внутри объектов страниц\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"проверки-внутри-объектов-страниц\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h3>\n\u003Cp>Источники расходятся в том, может ли объект страницы содержать проверки (assertions).\u003C/p>\n\u003Cdiv class=\"wiki-table wiki-table--cols-2\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Источник\u003C/th>\n\u003Cth>Позиция\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Фаулер, «PageObject» (2013)\u003C/td>\n\u003Ctd>Предпочитает объекты страниц без проверок; допустимое исключение - проверка инвариантов страницы\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Документация Selenium\u003C/td>\n\u003Ctd>Объекты страниц никогда не выполняют проверок; единственное исключение - проверка, что загрузилась нужная страница\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Документация Playwright\u003C/td>\n\u003Ctd>Её пример объекта страницы вызывает \u003Ccode>expect\u003C/code> внутри метода \u003Ccode>getStarted\u003C/code>\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Сторонники проверок, в изложении Фаулера\u003C/td>\n\u003Ctd>Проверки в объектах страниц уменьшают дублирование и дают более понятные сообщения об ошибках\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\u003C/div>\n\u003Cp>Довод против - разделение ответственности. Объект страницы даёт доступ к странице, а \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"expected-result\">ожидаемый результат\u003C/span> определяет тест. Для повторяющихся проверок Фаулер предлагает общие вспомогательные функции проверок. Если объект страницы проверяет сам, тест, которому нужно проверить противоположный исход, не может переиспользовать этот метод.\u003C/p>\n\u003Ch2 id=\"пример-на-playwright\" class=\"wiki-heading\">Пример на Playwright\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"пример-на-playwright\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Cp>Те же два теста с объектами страниц. Страница входа знает свои локаторы и предоставляет два действия с разными исходами:\u003C/p>\n\u003Cdiv class=\"wiki-code\" data-language=\"typescript\">\u003Cdiv class=\"wiki-code__header\">\u003Cspan class=\"wiki-code__language\">typescript\u003C/span>\u003Cbutton type=\"button\" class=\"wiki-code__copy\" data-wiki-copy data-copied-label=\"Скопировано\">\u003Csvg class=\"wiki-code__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/>\u003C/svg>\u003Csvg class=\"wiki-code__icon wiki-code__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003Cspan class=\"wiki-code__label\">Копировать\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Скопировано\u003C/span>\u003C/button>\u003C/div>\u003Cpre>\u003Ccode>import { type Locator, type Page } from '@playwright/test';\nimport { InboxPage } from './inbox-page';\n\nexport class LoginPage {\n  readonly page: Page;\n  readonly email: Locator;\n  readonly password: Locator;\n  readonly signInButton: Locator;\n  readonly errorMessage: Locator;\n\n  constructor(page: Page) {\n    this.page = page;\n    this.email = page.getByLabel('Email');\n    this.password = page.getByLabel('Password');\n    this.signInButton = page.getByRole('button', { name: 'Sign in' });\n    this.errorMessage = page.getByRole('alert');\n  }\n\n  async goto() {\n    await this.page.goto('https://example.com/login');\n  }\n\n  async signInAs(email: string, password: string): Promise&lt;InboxPage&gt; {\n    await this.submit(email, password);\n    return new InboxPage(this.page);\n  }\n\n  async signInExpectingError(email: string, password: string): Promise&lt;LoginPage&gt; {\n    await this.submit(email, password);\n    return this;\n  }\n\n  private async submit(email: string, password: string) {\n    await this.email.fill(email);\n    await this.password.fill(password);\n    await this.signInButton.click();\n  }\n}\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Страница входящих открывает только то, что нужно тестам:\u003C/p>\n\u003Cdiv class=\"wiki-code\" data-language=\"typescript\">\u003Cdiv class=\"wiki-code__header\">\u003Cspan class=\"wiki-code__language\">typescript\u003C/span>\u003Cbutton type=\"button\" class=\"wiki-code__copy\" data-wiki-copy data-copied-label=\"Скопировано\">\u003Csvg class=\"wiki-code__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/>\u003C/svg>\u003Csvg class=\"wiki-code__icon wiki-code__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003Cspan class=\"wiki-code__label\">Копировать\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Скопировано\u003C/span>\u003C/button>\u003C/div>\u003Cpre>\u003Ccode>import { type Locator, type Page } from '@playwright/test';\n\nexport class InboxPage {\n  readonly heading: Locator;\n\n  constructor(page: Page) {\n    this.heading = page.getByRole('heading', { name: 'Inbox' });\n  }\n}\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Тесты описывают сценарий и ожидаемый результат. Проверки остаются в тестах:\u003C/p>\n\u003Cdiv class=\"wiki-code\" data-language=\"typescript\">\u003Cdiv class=\"wiki-code__header\">\u003Cspan class=\"wiki-code__language\">typescript\u003C/span>\u003Cbutton type=\"button\" class=\"wiki-code__copy\" data-wiki-copy data-copied-label=\"Скопировано\">\u003Csvg class=\"wiki-code__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/>\u003C/svg>\u003Csvg class=\"wiki-code__icon wiki-code__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003Cspan class=\"wiki-code__label\">Копировать\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Скопировано\u003C/span>\u003C/button>\u003C/div>\u003Cpre>\u003Ccode>import { test, expect } from '@playwright/test';\nimport { LoginPage } from './pages/login-page';\n\ntest('user signs in with valid credentials', async ({ page }) =&gt; {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  const inbox = await loginPage.signInAs('anna@example.com', 'correct-password');\n  await expect(inbox.heading).toBeVisible();\n});\n\ntest('user sees an error with a wrong password', async ({ page }) =&gt; {\n  const loginPage = new LoginPage(page);\n  await loginPage.goto();\n  await loginPage.signInExpectingError('anna@example.com', 'wrong-password');\n  await expect(loginPage.errorMessage).toHaveText('Invalid email or password');\n});\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Когда меняется подпись кнопки, меняется одна строка \u003Ccode>LoginPage\u003C/code>. Тесты не правятся. Класс открывает локаторы как свойства только для чтения, чтобы тесты могли использовать веб-ориентированные проверки Playwright (web-first assertions), которые ждут ожидаемого состояния; документация Playwright делает так же в своём примере.\u003C/p>\n\u003Ch2 id=\"компоненты-и-фрагменты-страниц\" class=\"wiki-heading\">Компоненты и фрагменты страниц\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"компоненты-и-фрагменты-страниц\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Cp>Многие части интерфейса повторяются на нескольких страницах: шапка с поиском и меню аккаунта, таблица с постраничной навигацией, карточка товара в списке. Фаулер писал, что название «panel object» подошло бы лучше, чем page object, потому что паттерн применим к любой значимой части экрана. Документация Selenium называет такие классы объектами компонентов страницы (page component objects). Компонент можно включить в объект страницы, и он может содержать другие компоненты.\u003C/p>\n\u003Cp>В Playwright компонент удобно строить на корневом локаторе: все его локаторы ищутся внутри этого корня.\u003C/p>\n\u003Cdiv class=\"wiki-code\" data-language=\"typescript\">\u003Cdiv class=\"wiki-code__header\">\u003Cspan class=\"wiki-code__language\">typescript\u003C/span>\u003Cbutton type=\"button\" class=\"wiki-code__copy\" data-wiki-copy data-copied-label=\"Скопировано\">\u003Csvg class=\"wiki-code__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/>\u003C/svg>\u003Csvg class=\"wiki-code__icon wiki-code__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003Cspan class=\"wiki-code__label\">Копировать\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Скопировано\u003C/span>\u003C/button>\u003C/div>\u003Cpre>\u003Ccode>import { type Locator } from '@playwright/test';\n\nexport class HeaderComponent {\n  readonly searchBox: Locator;\n  readonly accountMenu: Locator;\n\n  constructor(root: Locator) {\n    this.searchBox = root.getByRole('searchbox');\n    this.accountMenu = root.getByRole('button', { name: 'Account' });\n  }\n\n  async search(query: string) {\n    await this.searchBox.fill(query);\n    await this.searchBox.press('Enter');\n  }\n}\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Объект страницы создаёт компонент со своим корнем, например \u003Ccode>new HeaderComponent(page.getByRole('banner'))\u003C/code>. Один и тот же класс обслуживает все страницы, где есть шапка. Программа ISTQB CTAL-TAE описывает также модель потока (flow model): дополнительный фасад над объектами страниц, который хранит действия пользователя, охватывающие несколько страниц, чтобы шаги тестов переиспользовались в разных сценариях.\u003C/p>\n\u003Ch2 id=\"репозиторий-объектов\" class=\"wiki-heading\">Репозиторий объектов\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"репозиторий-объектов\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Ch3 id=\"страницы-и-элементы-как-данные\" class=\"wiki-heading\">Страницы и элементы как данные\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"страницы-и-элементы-как-данные\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h3>\n\u003Cp>В репозитории объектов описание интерфейса - не код, а данные. Репозиторий устроен как иерархия: папки, страницы, элементы. У каждого элемента есть имя, уникальное в пределах страницы, и локатор. Шаги тестов ссылаются на элемент по странице и имени, а инструмент подставляет локатор во время выполнения.\u003C/p>\n\u003Cp>Пример структуры репозитория для сценария входа. Он условный: форматы у инструментов разные, общего стандарта для них не найдено.\u003C/p>\n\u003Cdiv class=\"wiki-code\" data-language=\"json\">\u003Cdiv class=\"wiki-code__header\">\u003Cspan class=\"wiki-code__language\">JSON\u003C/span>\u003Cbutton type=\"button\" class=\"wiki-code__copy\" data-wiki-copy data-copied-label=\"Скопировано\">\u003Csvg class=\"wiki-code__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/>\u003C/svg>\u003Csvg class=\"wiki-code__icon wiki-code__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003Cspan class=\"wiki-code__label\">Копировать\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Скопировано\u003C/span>\u003C/button>\u003C/div>\u003Cpre>\u003Ccode>{\n  &quot;pages&quot;: [\n    {\n      &quot;name&quot;: &quot;Login page&quot;,\n      &quot;path&quot;: &quot;/login&quot;,\n      &quot;elements&quot;: [\n        { &quot;name&quot;: &quot;Email field&quot;, &quot;locator&quot;: { &quot;kind&quot;: &quot;LABEL&quot;, &quot;value&quot;: &quot;Email&quot; } },\n        { &quot;name&quot;: &quot;Password field&quot;, &quot;locator&quot;: { &quot;kind&quot;: &quot;LABEL&quot;, &quot;value&quot;: &quot;Password&quot; } },\n        { &quot;name&quot;: &quot;Sign in button&quot;, &quot;locator&quot;: { &quot;kind&quot;: &quot;ROLE&quot;, &quot;value&quot;: &quot;button&quot;, &quot;name&quot;: &quot;Sign in&quot; } },\n        { &quot;name&quot;: &quot;Error message&quot;, &quot;locator&quot;: { &quot;kind&quot;: &quot;ROLE&quot;, &quot;value&quot;: &quot;alert&quot; } }\n      ]\n    },\n    {\n      &quot;name&quot;: &quot;Inbox page&quot;,\n      &quot;path&quot;: &quot;/inbox&quot;,\n      &quot;elements&quot;: [\n        { &quot;name&quot;: &quot;Inbox heading&quot;, &quot;locator&quot;: { &quot;kind&quot;: &quot;ROLE&quot;, &quot;value&quot;: &quot;heading&quot;, &quot;name&quot;: &quot;Inbox&quot; } }\n      ]\n    }\n  ]\n}\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Документация OpenText Functional Testing (прежнее название - UFT One) описывает свой репозиторий объектов как хранилище тестовых объектов, у каждого из которых есть набор описывающих свойств. Она различает локальный репозиторий, привязанный к одному действию, и общий репозиторий, которым несколько тестов пользуются в режиме только для чтения. Та же документация рекомендует общий репозиторий для тестов на основе ключевых слов и для объектов, свойства которых часто меняются.\u003C/p>\n\u003Ch3 id=\"шаги-на-основе-ключевых-слов\" class=\"wiki-heading\">Шаги на основе ключевых слов\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"шаги-на-основе-ключевых-слов\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h3>\n\u003Cp>Глоссарий ISTQB определяет тестирование на основе ключевых слов (keyword-driven testing) как технику написания сценариев, при которой тестовые сценарии содержат высокоуровневые ключевые слова, а вспомогательные файлы - низкоуровневые сценарии, которые их реализуют. ISO/IEC/IEEE 29119-5 описывает эталонный подход к нему, требования к фреймворкам и инструментам, общий формат обмена данными, уровни иерархических ключевых слов и начальный список общих низкоуровневых ключевых слов, например для ввода данных или проверки значений. Программа CTAL-TAE добавляет, что тест-кейсы на основе ключевых слов - это списки или таблицы шагов и что в их написании могут участвовать тест-аналитики и бизнес-аналитики, а сами ключевые слова реализуют инженеры по автоматизации тестирования.\u003C/p>\n\u003Cp>Второй тест примера в виде шагов с ключевыми словами, которые ссылаются на репозиторий:\u003C/p>\n\u003Cdiv class=\"wiki-table wiki-table--cols-5\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Шаг\u003C/th>\n\u003Cth>Ключевое слово\u003C/th>\n\u003Cth>Страница\u003C/th>\n\u003Cth>Элемент\u003C/th>\n\u003Cth>Значение\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>1\u003C/td>\n\u003Ctd>Open page\u003C/td>\n\u003Ctd>Login page\u003C/td>\n\u003Ctd>\u003C/td>\n\u003Ctd>\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>2\u003C/td>\n\u003Ctd>Enter text\u003C/td>\n\u003Ctd>Login page\u003C/td>\n\u003Ctd>Email field\u003C/td>\n\u003Ctd>anna@example.com\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>3\u003C/td>\n\u003Ctd>Enter text\u003C/td>\n\u003Ctd>Login page\u003C/td>\n\u003Ctd>Password field\u003C/td>\n\u003Ctd>wrong-password\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>4\u003C/td>\n\u003Ctd>Click\u003C/td>\n\u003Ctd>Login page\u003C/td>\n\u003Ctd>Sign in button\u003C/td>\n\u003Ctd>\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>5\u003C/td>\n\u003Ctd>Check text\u003C/td>\n\u003Ctd>Login page\u003C/td>\n\u003Ctd>Error message\u003C/td>\n\u003Ctd>Invalid email or password\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\u003C/div>\n\u003Cp>В шагах нет локаторов. Ключевое слово «Click» реализовано во фреймворке один раз, элемент «Sign in button» описан в репозитории один раз. Тестировщик, который не пишет код, может собирать новые тесты из готовых ключевых слов и элементов.\u003C/p>\n\u003Ch3 id=\"поддержка-при-изменении-интерфейса\" class=\"wiki-heading\">Поддержка при изменении интерфейса\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"поддержка-при-изменении-интерфейса\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h3>\n\u003Cp>Главное назначение репозитория - изменение интерфейса обрабатывается в одном месте. Типичные изменения и работа, которую они требуют:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>изменилась подпись или атрибут\u003C/strong>: правится локатор одного элемента, и все шаги, которые на него ссылаются, при следующем запуске используют новое значение;\u003C/li>\n\u003Cli>\u003Cstrong>новый элемент\u003C/strong>: он один раз добавляется на свою страницу и становится доступен всем тестам;\u003C/li>\n\u003Cli>\u003Cstrong>удалённый элемент\u003C/strong>: перед удалением инструмент или команда находит все шаги, которые на него ссылаются, потому что эти шаги упадут;\u003C/li>\n\u003Cli>\u003Cstrong>перенесённый элемент\u003C/strong>: он переносится на другую страницу или в компонент, ссылки в шагах обновляются;\u003C/li>\n\u003Cli>\u003Cstrong>новая версия интерфейса\u003C/strong>: репозиторий версионируется вместе с тестами, чтобы \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"test-run\">тестовый прогон\u003C/span> использовал описания, которые соответствуют тестируемой сборке.\u003C/li>\n\u003C/ul>\n\u003Cp>Изменения репозитория проходят ревью так же, как код. Изменение одного локатора затрагивает все тесты, которые используют элемент, и неверный локатор ломает их все сразу.\u003C/p>\n\u003Ch3 id=\"правила-именования\" class=\"wiki-heading\">Правила именования\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"правила-именования\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h3>\n\u003Cp>Имена в репозитории читают люди, которые пишут шаги тестов, поэтому имя описывает элемент, а не его реализацию. Распространённые правила:\u003C/p>\n\u003Cul>\n\u003Cli>страница называется так, как её видит пользователь: «Login page», «Order details», а не по имени файла шаблона;\u003C/li>\n\u003Cli>имя элемента сочетает смысл и тип: «Sign in button», «Email field», «Error message»;\u003C/li>\n\u003Cli>локатор не повторяется в имени: «button#submit-2» перестаёт быть правдой после первого изменения разметки;\u003C/li>\n\u003Cli>имена уникальны в пределах страницы, а один и тот же элемент на разных страницах называется одинаково;\u003C/li>\n\u003Cli>повторяющиеся части (шапка, меню, диалоги) хранятся как отдельные страницы или компоненты, а не копируются на каждую страницу.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"паттерн-screenplay\" class=\"wiki-heading\">Паттерн Screenplay\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"паттерн-screenplay\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Cp>Паттерн Screenplay - альтернатива объектам страниц, в которой тест описывается как действующие лица (actors), выполняющие задачи. По статье его авторов (InfoQ, 2016), Энтони Маркано придумал его в 2007-2008 годах, Энди Палмер развивал его с 2009 года, а Ян Молак дал ему название Journey Pattern в 2013 году. Авторы представляют его как применение принципов проектирования SOLID к автоматизированным приёмочным тестам. По их доводам, крупные объекты страниц нарушают принципы единственной ответственности и открытости-закрытости, потому что новые тесты часто требуют менять существующие классы страниц.\u003C/p>\n\u003Cp>Документация Serenity BDD описывает элементы паттерна: действующие лица, способности (abilities, например работа с браузером), задачи (tasks), взаимодействия (interactions) и вопросы (questions). Задача составляется из взаимодействий:\u003C/p>\n\u003Cdiv class=\"wiki-code\" data-language=\"java\">\u003Cdiv class=\"wiki-code__header\">\u003Cspan class=\"wiki-code__language\">java\u003C/span>\u003Cbutton type=\"button\" class=\"wiki-code__copy\" data-wiki-copy data-copied-label=\"Скопировано\">\u003Csvg class=\"wiki-code__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M216,28H88A12,12,0,0,0,76,40V76H40A12,12,0,0,0,28,88V216a12,12,0,0,0,12,12H168a12,12,0,0,0,12-12V180h36a12,12,0,0,0,12-12V40A12,12,0,0,0,216,28ZM156,204H52V100H156Zm48-48H180V88a12,12,0,0,0-12-12H100V52H204Z\"/>\u003C/svg>\u003Csvg class=\"wiki-code__icon wiki-code__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003Cspan class=\"wiki-code__label\">Копировать\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Скопировано\u003C/span>\u003C/button>\u003C/div>\u003Cpre>\u003Ccode>public class Login {\n    public static Task asAStandardUser() {\n        return Task.where(\n                Open.url(&quot;https://www.saucedemo.com/&quot;),\n                Enter.theValue(&quot;standard_user&quot;).into(&quot;#user-name&quot;),\n                Enter.theValue(&quot;secret_sauce&quot;).into(&quot;#password&quot;),\n                Click.on(&quot;#login-button&quot;)\n        );\n    }\n}\u003C/code>\u003C/pre>\u003C/div>\n\u003Cp>Тест после этого читается как \u003Ccode>toby.attemptsTo(Login.asAStandardUser())\u003C/code>. Serenity BDD также хранит локаторы как именованные цели (targets), например \u003Ccode>Target.the(&quot;User name&quot;).locatedBy(&quot;.user-name&quot;)\u003C/code>, которые объявляются в отдельных классах и переиспользуются задачами и вопросами. Таким образом, Screenplay меняет организацию действий, а локаторы по-прежнему хранятся в одном месте.\u003C/p>\n\u003Ch2 id=\"локаторы-в-тестах-объект-страницы-и-репозиторий-объектов\" class=\"wiki-heading\">Локаторы в тестах, объект страницы и репозиторий объектов\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"локаторы-в-тестах-объект-страницы-и-репозиторий-объектов\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Cp>Таблица сравнивает три способа размещения локаторов.\u003C/p>\n\u003Cdiv class=\"wiki-table wiki-table--cols-4\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>\u003C/th>\n\u003Cth>Локаторы в тестах\u003C/th>\n\u003Cth>Объект страницы\u003C/th>\n\u003Cth>Репозиторий объектов\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Где живут локаторы\u003C/td>\n\u003Ctd>В каждом тесте\u003C/td>\n\u003Ctd>В классах страниц и компонентов\u003C/td>\n\u003Ctd>В хранилище страниц и элементов\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Форма\u003C/td>\n\u003Ctd>Код\u003C/td>\n\u003Ctd>Код\u003C/td>\n\u003Ctd>Данные, правятся в инструменте или файле\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Кто правит\u003C/td>\n\u003Ctd>Разработчик тестов\u003C/td>\n\u003Ctd>Разработчик тестов\u003C/td>\n\u003Ctd>Разработчик тестов или тестировщик без кода\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Повторное использование действий\u003C/td>\n\u003Ctd>Копирование\u003C/td>\n\u003Ctd>Методы объектов страниц\u003C/td>\n\u003Ctd>Ключевые слова фреймворка\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Изменение локатора\u003C/td>\n\u003Ctd>В каждом тесте\u003C/td>\n\u003Ctd>В одном классе\u003C/td>\n\u003Ctd>В одном элементе\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Проверки\u003C/td>\n\u003Ctd>В тесте\u003C/td>\n\u003Ctd>В тесте, по большинству источников\u003C/td>\n\u003Ctd>В шагах проверки теста\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Типичное применение\u003C/td>\n\u003Ctd>Небольшие наборы, прототипы\u003C/td>\n\u003Ctd>Фреймворки на коде\u003C/td>\n\u003Ctd>Инструменты на ключевых словах и без кода\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\u003C/div>\n\u003Cp>Объект страницы и репозиторий объектов решают одну задачу на разных уровнях. Объект страницы хранит локаторы и действия вместе, в коде. Репозиторий объектов хранит только описания элементов, а действия - общие ключевые слова. Объекты страниц могут выражать сложное поведение страницы: ожидание определённого состояния, составные операции, разные исходы. Репозиторий делает описание интерфейса доступным людям, которые не пишут код.\u003C/p>\n\u003Cp>Подходы сочетаются. Фреймворк на коде может загружать локаторы из файла репозитория в объекты страниц, а инструмент на ключевых словах - группировать элементы в компоненты, похожие на объекты страниц. В обоих случаях в тестах нет локаторов, и изменение интерфейса не требует правки тестов.\u003C/p>\n\u003Ch2 id=\"термины\" class=\"wiki-heading\">Термины\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"термины\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Cdiv class=\"wiki-table wiki-table--cols-2\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Термин\u003C/th>\n\u003Cth>Значение\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Объект страницы (page object)\u003C/td>\n\u003Ctd>Класс в коде тестов, который представляет страницу или её часть и предоставляет тестам её услуги, скрывая локаторы\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Объект компонента страницы (page component object)\u003C/td>\n\u003Ctd>Объект страницы для повторяющейся части страницы, например шапки или карточки товара; включается в страницы и в другие компоненты\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Репозиторий объектов (object repository)\u003C/td>\n\u003Ctd>Хранилище описаний элементов интерфейса, устроенное как страницы и элементы, на которые шаги тестов ссылаются по имени\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Тестирование на основе ключевых слов (keyword-driven testing)\u003C/td>\n\u003Ctd>Техника написания сценариев, при которой шаги теста состоят из высокоуровневых ключевых слов, реализованных вспомогательными низкоуровневыми сценариями\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Модель потока (flow model)\u003C/td>\n\u003Ctd>Фасад над объектами страниц, который хранит действия пользователя, охватывающие несколько страниц, для повторного использования в тестах\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Паттерн Screenplay (Screenplay pattern)\u003C/td>\n\u003Ctd>Паттерн проектирования тестов, в котором действующие лица со способностями выполняют задачи из взаимодействий и задают вопросы о системе\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\u003C/div>\n\u003Ch2 id=\"источники\" class=\"wiki-heading\">Источники\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"источники\" aria-label=\"Скопировать ссылку на раздел\" data-copied-label=\"Ссылка на раздел скопирована\">\u003Csvg class=\"wiki-heading__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M117.18,188.74a12,12,0,0,1,0,17l-5.12,5.12A58.26,58.26,0,0,1,70.6,228h0A58.62,58.62,0,0,1,29.14,127.92L63.89,93.17a58.64,58.64,0,0,1,98.56,28.11,12,12,0,1,1-23.37,5.44,34.65,34.65,0,0,0-58.22-16.58L46.11,144.89A34.62,34.62,0,0,0,70.57,204h0a34.41,34.41,0,0,0,24.49-10.14l5.11-5.12A12,12,0,0,1,117.18,188.74ZM226.83,45.17a58.65,58.65,0,0,0-82.93,0l-5.11,5.11a12,12,0,0,0,17,17l5.12-5.12a34.63,34.63,0,1,1,49,49L175.1,145.86A34.39,34.39,0,0,1,150.61,156h0a34.63,34.63,0,0,1-33.69-26.72,12,12,0,0,0-23.38,5.44A58.64,58.64,0,0,0,150.56,180h.05a58.28,58.28,0,0,0,41.47-17.17l34.75-34.75a58.62,58.62,0,0,0,0-82.91Z\"/>\u003C/svg>\u003Csvg class=\"wiki-heading__icon wiki-heading__icon--done\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z\"/>\u003C/svg>\u003C/button>\u003C/h2>\n\u003Cul>\n\u003Cli>\u003Ca href=\"https://martinfowler.com/bliki/PageObject.html\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">PageObject, Martin Fowler, 10 September 2013\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">Page object models, документация Selenium\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://playwright.dev/docs/pom\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">Page object models, документация Playwright\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://www.istqb.org/wp-content/uploads/2024/11/ISTQB_CTAL-TAE_Syllabus_v2.0.pdf\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">ISTQB Certified Tester Advanced Level Test Automation Engineering Syllabus v2.0, ISTQB, 2024\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://www.iso.org/standard/87233.html\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">ISO/IEC/IEEE 29119-5:2024, Software testing, Part 5: Keyword-driven testing\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://www.imbus.de/en/glossar/term/keyword-driven-testing\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">Keyword-driven testing, глоссарий ISTQB (зеркало imbus)\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://sepl.dibris.unige.it/publications/2013-leotta-ICSTW.pdf\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">Leotta, Clerissi, Ricca, Spadaro. Improving Test Suites Maintainability with the Page Object Pattern: An Industrial Case Study, ICSTW 2013\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://admhelp.microfocus.com/uft/en/latest/UFT_Help/Content/User_Guide/z_Ch_MngTestObj_In_ObjRep.htm\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">Test objects in object repositories, справка OpenText Functional Testing\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://www.infoq.com/articles/Beyond-Page-Objects-Test-Automation-Serenity-Screenplay\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">Beyond Page Objects: Next Generation Test Automation with Serenity and the Screenplay Pattern, InfoQ, 2016\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003Cli>\u003Ca href=\"https://serenity-bdd.github.io/docs/screenplay/screenplay_fundamentals\" target=\"_blank\" rel=\"noopener\" class=\"wiki-link wiki-link--external\">Screenplay Fundamentals, документация Serenity BDD\u003Csvg class=\"wiki-link__icon\" viewBox=\"0 0 256 256\" width=\"16\" height=\"16\" fill=\"currentColor\" aria-hidden=\"true\" focusable=\"false\">\u003Cpath d=\"M228,104a12,12,0,0,1-24,0V69l-59.51,59.51a12,12,0,0,1-17-17L187,52H152a12,12,0,0,1,0-24h64a12,12,0,0,1,12,12Zm-44,24a12,12,0,0,0-12,12v64H52V84h64a12,12,0,0,0,0-24H48A20,20,0,0,0,28,80V208a20,20,0,0,0,20,20H176a20,20,0,0,0,20-20V140A12,12,0,0,0,184,128Z\"/>\u003C/svg>\u003Cspan class=\"wiki-visually-hidden\">(откроется в новой вкладке)\u003C/span>\u003C/a> (на английском)\u003C/li>\n\u003C/ul>\n",[10,14,17,21,24,27,30,33,36,39,42,45,48,51,54,57],{"id":11,"level":12,"text":13},"проблема-дублирования-локаторов",2,"Проблема дублирования локаторов",{"id":15,"level":12,"text":16},"паттерн-page-object","Паттерн Page Object",{"id":18,"level":19,"text":20},"услуги-вместо-разметки",3,"Услуги вместо разметки",{"id":22,"level":19,"text":23},"навигация-и-возвращаемые-объекты","Навигация и возвращаемые объекты",{"id":25,"level":19,"text":26},"проверки-внутри-объектов-страниц","Проверки внутри объектов страниц",{"id":28,"level":12,"text":29},"пример-на-playwright","Пример на Playwright",{"id":31,"level":12,"text":32},"компоненты-и-фрагменты-страниц","Компоненты и фрагменты страниц",{"id":34,"level":12,"text":35},"репозиторий-объектов","Репозиторий объектов",{"id":37,"level":19,"text":38},"страницы-и-элементы-как-данные","Страницы и элементы как данные",{"id":40,"level":19,"text":41},"шаги-на-основе-ключевых-слов","Шаги на основе ключевых слов",{"id":43,"level":19,"text":44},"поддержка-при-изменении-интерфейса","Поддержка при изменении интерфейса",{"id":46,"level":19,"text":47},"правила-именования","Правила именования",{"id":49,"level":12,"text":50},"паттерн-screenplay","Паттерн Screenplay",{"id":52,"level":12,"text":53},"локаторы-в-тестах-объект-страницы-и-репозиторий-объектов","Локаторы в тестах, объект страницы и репозиторий объектов",{"id":55,"level":12,"text":56},"термины","Термины",{"id":58,"level":12,"text":59},"источники","Источники",2057,1793718893591]