[{"data":1,"prerenderedAt":61},["ShallowReactive",2],{"wiki-test-automation-page-object-and-object-repository-en":3},{"meta":4,"html":8,"headings":9,"words":60},{"title":5,"description":6,"published":7,"updated":7},"Object repository and the Page Object pattern","Object repository and Page Object: moving UI locators out of tests, page and component objects, pages and elements as data, maintenance, Screenplay.","2026-11-03","\u003Cp>An \u003Cstrong>object repository\u003C/strong> and a \u003Cstrong>page object\u003C/strong> are two ways to keep the knowledge of a user interface in one place instead of repeating it in every automated test. A page object is a class in the test code that represents a page or a part of a page and offers tests its services: enter the credentials, open the inbox, read the error message. An object repository is a store of element descriptions kept as data, organized as pages and their elements, to which test steps refer by name. In both cases a change of the interface is reflected in one place, and the tests stay the same.\u003C/p>\n\u003Cp>Martin Fowler first described the idea under the name Window Driver. The name page object was popularized by the Selenium project, and in September 2013 Fowler summarized the pattern in the article &quot;PageObject&quot; on his site. Today the pattern is described in the Selenium documentation (&quot;Page object models&quot;) and in the Playwright documentation (&quot;Page object models&quot;). The ISTQB Advanced Level Test Automation Engineering syllabus (version 2.0, 2024) lists the page object model among the design patterns of test automation. The object repository comes from the practice of test automation tools with keyword-driven and low-code test steps; keyword-driven testing itself is standardized in ISO/IEC/IEEE 29119-5 (current edition 2024).\u003C/p>\n\u003Ch2 id=\"the-problem-of-duplicated-locators\" class=\"wiki-heading\">The problem of duplicated locators\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"the-problem-of-duplicated-locators\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>A UI test finds elements with \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"locator\">locators\u003C/span> and performs actions on them. The kinds of locators and the order in which to choose them are described in the article \u003Ca href=\"/en/wiki/test-automation/ui-test-locators\" class=\"wiki-link wiki-link--article\">Locators in UI tests\u003C/a>. When locators are written directly in the tests, every test that passes through a page repeats them.\u003C/p>\n\u003Cp>Two tests of a sign-in page in Playwright with locators inline:\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=\"Copied\">\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\">Copy\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Copied\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>The three locators of the form appear in both tests. A suite usually has dozens of tests that sign in first. If the button label changes from &quot;Sign in&quot; to &quot;Log in&quot;, every one of them fails and has to be edited. The test also mixes two kinds of knowledge: what is checked and how the page is built. The Selenium documentation names both problems in its own example: locators are mixed into the test, and the same locators are repeated in every test that uses the page.\u003C/p>\n\u003Cp>The effect on maintenance was measured in an industrial case study by Leotta, Clerissi, Ricca and Spadaro (ICSTW 2013). Two suites of 25 \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"test-case\">test cases\u003C/span> for the same web application were realigned after a new release. The suite without page objects took 124 minutes and 90 changed lines of code. The suite with 19 page objects took 43 minutes and 11 changed lines. Building the suite with page objects took more code at the start: 3320 lines against 2891.\u003C/p>\n\u003Ch2 id=\"the-page-object-pattern\" class=\"wiki-heading\">The Page Object pattern\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"the-page-object-pattern\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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=\"services-instead-of-markup\" class=\"wiki-heading\">Services instead of markup\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"services-instead-of-markup\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>Fowler describes a page object as a wrapper around an HTML page or a fragment with an application-specific API. It should allow a test to do and to see anything that a human user can. The methods of the page object speak in terms of the application: &quot;sign in as&quot;, &quot;number of unread messages&quot;, not &quot;find the element with this selector and click it&quot;. A text field is exposed as a string, a check box as a boolean, a button as an action.\u003C/p>\n\u003Cp>The Selenium documentation describes the same class from two sides. Toward the tests it offers services. Toward the page it holds the only deep knowledge of the HTML structure. As a result, there is only one place in the \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"test-suite\">test suite\u003C/span> that knows how a particular page is built.\u003C/p>\n\u003Cp>A page object does not have to cover a whole page. Fowler recommends building objects for the significant elements of the interface, following the structure that makes sense to a user rather than the layout markup.\u003C/p>\n\u003Ch3 id=\"navigation-and-returned-objects\" class=\"wiki-heading\">Navigation and returned objects\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"navigation-and-returned-objects\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>An action that moves the user to another page returns the page object of that page. A successful sign-in returns the inbox page; a failed one returns the sign-in page itself. The Selenium documentation recommends modeling different outcomes of the same action as different methods: in its example these are \u003Ccode>loginAs\u003C/code> and \u003Ccode>loginAsExpectingError\u003C/code>. When a method changes its return type, the tests that rely on the old navigation stop compiling, and the error is found before the run.\u003C/p>\n\u003Cp>Fowler notes an alternative in which page objects return a generic browser context and the test decides which page object to create next. This keeps the knowledge of navigation in the test.\u003C/p>\n\u003Ch3 id=\"assertions-inside-page-objects\" class=\"wiki-heading\">Assertions inside page objects\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"assertions-inside-page-objects\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>Sources disagree on whether a page object may contain assertions.\u003C/p>\n\u003Cdiv class=\"wiki-table wiki-table--cols-2\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Source\u003C/th>\n\u003Cth>Position\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Fowler, &quot;PageObject&quot; (2013)\u003C/td>\n\u003Ctd>Favors no assertions in page objects; checks of invariants of the page are an acceptable exception\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Selenium documentation\u003C/td>\n\u003Ctd>Page objects should never make verifications; the one exception is checking that the correct page has loaded\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Playwright documentation\u003C/td>\n\u003Ctd>Its example page object calls \u003Ccode>expect\u003C/code> inside the method \u003Ccode>getStarted\u003C/code>\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Supporters of assertions, as summarized by Fowler\u003C/td>\n\u003Ctd>Assertions in page objects reduce duplication and give better error messages\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\u003C/div>\n\u003Cp>The argument against is the separation of responsibilities. A page object provides access to the page; the test decides what the \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"expected-result\">expected result\u003C/span> is. Fowler suggests shared assertion helpers for checks that repeat. When a page object asserts, a test that wants to check the opposite outcome cannot reuse the method.\u003C/p>\n\u003Ch2 id=\"example-with-playwright\" class=\"wiki-heading\">Example with Playwright\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"example-with-playwright\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>The same two tests with page objects. The sign-in page knows its locators and offers two actions with different outcomes:\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=\"Copied\">\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\">Copy\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Copied\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>The inbox page exposes only what the tests need:\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=\"Copied\">\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\">Copy\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Copied\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>The tests describe the scenario and the expected result. Assertions stay in the tests:\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=\"Copied\">\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\">Copy\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Copied\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>When the button label changes, one line of \u003Ccode>LoginPage\u003C/code> changes. The tests are not edited. The class exposes locators as read-only properties so that the tests can use Playwright web-first assertions, which wait for the expected state; the Playwright documentation does the same in its example.\u003C/p>\n\u003Ch2 id=\"components-and-page-fragments\" class=\"wiki-heading\">Components and page fragments\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"components-and-page-fragments\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>Many parts of an interface repeat on several pages: the header with search and the account menu, a table with pagination, a product card in a list. Fowler wrote that &quot;panel object&quot; might have been a better name than page object, because the pattern applies to any significant part of the screen. The Selenium documentation calls such classes page component objects. A component can be included in a page object and can contain other components.\u003C/p>\n\u003Cp>In Playwright a component is conveniently built on a root locator, and all its locators are searched inside that root:\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=\"Copied\">\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\">Copy\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Copied\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>A page object creates the component with its root, for example \u003Ccode>new HeaderComponent(page.getByRole('banner'))\u003C/code>. The same class serves every page that has the header. The ISTQB CTAL-TAE syllabus also describes a flow model: an additional facade over page objects that stores user actions spanning several pages, so that test steps are reused between scripts.\u003C/p>\n\u003Ch2 id=\"object-repository\" class=\"wiki-heading\">Object repository\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"object-repository\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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=\"pages-and-elements-as-data\" class=\"wiki-heading\">Pages and elements as data\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"pages-and-elements-as-data\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>In an object repository the description of the interface is not code but data. The repository is organized as a hierarchy: folders, pages, elements. Each element has a name that is unique within its page and a locator. Test steps refer to an element by page and name, and the tool resolves the locator at run time.\u003C/p>\n\u003Cp>An illustrative structure of a repository for the sign-in example; formats differ between tools, and no common standard for them was found:\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=\"Copied\">\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\">Copy\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Copied\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>The documentation of OpenText Functional Testing (formerly UFT One) describes its object repository as a storehouse of test objects, each with a set of description properties. It distinguishes a local repository, tied to one action, and a shared repository that several tests use in read-only mode. The same documentation recommends a shared repository for keyword-driven tests and for objects whose properties change often.\u003C/p>\n\u003Ch3 id=\"keyword-driven-test-steps\" class=\"wiki-heading\">Keyword-driven test steps\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"keyword-driven-test-steps\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>The ISTQB glossary defines keyword-driven testing as a scripting technique in which test scripts contain high-level keywords and supporting files contain the low-level scripts that implement them. ISO/IEC/IEEE 29119-5 describes a reference approach to it, requirements for frameworks and tools, a common data exchange format, levels of hierarchical keywords and an initial list of generic low-level keywords, such as entering data or checking values. The CTAL-TAE syllabus adds that keyword-driven test cases are lists or tables of steps and that test analysts and business analysts can take part in writing them, while the keywords themselves are implemented by test automation engineers.\u003C/p>\n\u003Cp>The second test of the example, written as keyword-driven steps that refer to the repository:\u003C/p>\n\u003Cdiv class=\"wiki-table wiki-table--cols-5\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>Step\u003C/th>\n\u003Cth>Keyword\u003C/th>\n\u003Cth>Page\u003C/th>\n\u003Cth>Element\u003C/th>\n\u003Cth>Value\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>The steps contain no locators. The keyword &quot;Click&quot; is implemented once in the framework; the element &quot;Sign in button&quot; is described once in the repository. A tester who does not write code can compose new tests from existing keywords and elements.\u003C/p>\n\u003Ch3 id=\"maintenance-when-the-interface-changes\" class=\"wiki-heading\">Maintenance when the interface changes\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"maintenance-when-the-interface-changes\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>The main purpose of the repository is that a change of the interface is handled in one place. Typical changes and the work they require:\u003C/p>\n\u003Cul>\n\u003Cli>\u003Cstrong>changed label or attribute\u003C/strong>: the locator of one element is edited, and all steps that refer to it use the new value on the next run;\u003C/li>\n\u003Cli>\u003Cstrong>new element\u003C/strong>: it is added to its page once and then becomes available to all tests;\u003C/li>\n\u003Cli>\u003Cstrong>removed element\u003C/strong>: before deletion, the tool or the team finds all steps that refer to it, because those steps would fail;\u003C/li>\n\u003Cli>\u003Cstrong>moved element\u003C/strong>: it is moved to another page or component, and the references in the steps are updated;\u003C/li>\n\u003Cli>\u003Cstrong>new version of the interface\u003C/strong>: the repository is versioned together with the tests, so that a \u003Cspan class=\"wiki-term\" tabindex=\"0\" data-wiki-term=\"test-run\">test run\u003C/span> uses the descriptions that match the build under test.\u003C/li>\n\u003C/ul>\n\u003Cp>A repository is reviewed like code. A change of one locator affects every test that uses the element, and a wrong locator breaks all of them at once.\u003C/p>\n\u003Ch3 id=\"naming-conventions\" class=\"wiki-heading\">Naming conventions\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"naming-conventions\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>Names in a repository are read by people who write test steps, so they describe the element and not its implementation. Common rules:\u003C/p>\n\u003Cul>\n\u003Cli>a page is named as the user sees it: &quot;Login page&quot;, &quot;Order details&quot;, not after a template file;\u003C/li>\n\u003Cli>an element name combines meaning and type: &quot;Sign in button&quot;, &quot;Email field&quot;, &quot;Error message&quot;;\u003C/li>\n\u003Cli>the locator is not repeated in the name: &quot;button#submit-2&quot; stops being true after the first change of markup;\u003C/li>\n\u003Cli>names are unique within a page, and the same element on different pages has the same name;\u003C/li>\n\u003Cli>repeated parts (header, menu, dialogs) are kept as separate pages or components rather than copied into every page.\u003C/li>\n\u003C/ul>\n\u003Ch2 id=\"screenplay-pattern\" class=\"wiki-heading\">Screenplay pattern\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"screenplay-pattern\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>The Screenplay pattern is an alternative to page objects in which a test is described as actors performing tasks. According to an article by its authors (InfoQ, 2016), Antony Marcano devised it in 2007-2008, Andy Palmer refined it from 2009, and Jan Molak gave it the name Journey Pattern in 2013. The authors present it as the application of SOLID design principles to automated acceptance tests. They argue that large page objects violate the single responsibility and open-closed principles, because new tests often require changes to existing page classes.\u003C/p>\n\u003Cp>The Serenity BDD documentation describes the elements of the pattern: actors, abilities (for example, browsing the web), tasks, interactions and questions. A task is composed of interactions:\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=\"Copied\">\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\">Copy\u003C/span>\u003Cspan class=\"wiki-code__label wiki-code__label--done\">Copied\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>A test then reads as \u003Ccode>toby.attemptsTo(Login.asAStandardUser())\u003C/code>. Serenity BDD also stores locators as named targets, for example \u003Ccode>Target.the(&quot;User name&quot;).locatedBy(&quot;.user-name&quot;)\u003C/code>, declared in separate classes and reused by tasks and questions. Screenplay therefore changes the organization of actions, while locators are still kept in one place.\u003C/p>\n\u003Ch2 id=\"inline-locators-page-object-and-object-repository\" class=\"wiki-heading\">Inline locators, page object and object repository\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"inline-locators-page-object-and-object-repository\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>The table compares the three ways of placing locators.\u003C/p>\n\u003Cdiv class=\"wiki-table wiki-table--cols-4\">\u003Ctable>\n\u003Cthead>\n\u003Ctr>\n\u003Cth>\u003C/th>\n\u003Cth>Locators inline\u003C/th>\n\u003Cth>Page object\u003C/th>\n\u003Cth>Object repository\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Where locators live\u003C/td>\n\u003Ctd>In every test\u003C/td>\n\u003Ctd>In page and component classes\u003C/td>\n\u003Ctd>In a data store of pages and elements\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Form\u003C/td>\n\u003Ctd>Code\u003C/td>\n\u003Ctd>Code\u003C/td>\n\u003Ctd>Data, edited in a tool or a file\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Who edits\u003C/td>\n\u003Ctd>Test developer\u003C/td>\n\u003Ctd>Test developer\u003C/td>\n\u003Ctd>Test developer or tester without code\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Reuse of actions\u003C/td>\n\u003Ctd>Copying\u003C/td>\n\u003Ctd>Methods of page objects\u003C/td>\n\u003Ctd>Keywords of the framework\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Change of a locator\u003C/td>\n\u003Ctd>In every test\u003C/td>\n\u003Ctd>In one class\u003C/td>\n\u003Ctd>In one element\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Assertions\u003C/td>\n\u003Ctd>In the test\u003C/td>\n\u003Ctd>In the test, by most sources\u003C/td>\n\u003Ctd>In check steps of the test\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Typical use\u003C/td>\n\u003Ctd>Small suites, prototypes\u003C/td>\n\u003Ctd>Code-based frameworks\u003C/td>\n\u003Ctd>Keyword-driven and low-code tools\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\u003C/div>\n\u003Cp>A page object and an object repository solve the same problem at different levels. A page object stores locators and actions together, in code. An object repository stores only element descriptions, and the actions are generic keywords. Page objects can express complex behavior of a page: waiting for a specific state, composite operations, different outcomes. A repository makes the description of the interface available to people who do not write code.\u003C/p>\n\u003Cp>The approaches are combined. A code-based framework can load locators from a repository file into page objects, and a keyword-driven tool can group elements into components that resemble page objects. In both cases tests do not contain locators, and a change of the interface does not require editing the tests.\u003C/p>\n\u003Ch2 id=\"terms\" class=\"wiki-heading\">Terms\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"terms\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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>Term\u003C/th>\n\u003Cth>Meaning\u003C/th>\n\u003C/tr>\n\u003C/thead>\n\u003Ctbody>\n\u003Ctr>\n\u003Ctd>Page object\u003C/td>\n\u003Ctd>Class in test code that represents a page or a part of a page and offers tests its services, hiding locators\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Page component object\u003C/td>\n\u003Ctd>Page object for a repeated part of a page, such as a header or a product card; can be included in pages and in other components\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Object repository\u003C/td>\n\u003Ctd>Store of descriptions of interface elements, organized as pages and elements, to which test steps refer by name\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Keyword-driven testing\u003C/td>\n\u003Ctd>Scripting technique in which test steps consist of high-level keywords implemented by supporting low-level scripts\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Flow model\u003C/td>\n\u003Ctd>Facade over page objects that stores user actions spanning several pages for reuse in tests\u003C/td>\n\u003C/tr>\n\u003Ctr>\n\u003Ctd>Screenplay pattern\u003C/td>\n\u003Ctd>Test design pattern in which actors with abilities perform tasks composed of interactions and ask questions about the system\u003C/td>\n\u003C/tr>\n\u003C/tbody>\n\u003C/table>\u003C/div>\n\u003Ch2 id=\"sources\" class=\"wiki-heading\">Sources\u003Cbutton type=\"button\" class=\"wiki-heading__anchor\" data-wiki-anchor=\"sources\" aria-label=\"Copy link to section\" data-copied-label=\"Link to section copied\">\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\">(opens in a new tab)\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 documentation\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\">(opens in a new tab)\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 documentation\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\">(opens in a new tab)\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\">(opens in a new tab)\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\">(opens in a new tab)\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 Glossary (imbus mirror)\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\">(opens in a new tab)\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\">(opens in a new tab)\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 Help\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\">(opens in a new tab)\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\">(opens in a new tab)\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 documentation\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\">(opens in a new tab)\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},"the-problem-of-duplicated-locators",2,"The problem of duplicated locators",{"id":15,"level":12,"text":16},"the-page-object-pattern","The Page Object pattern",{"id":18,"level":19,"text":20},"services-instead-of-markup",3,"Services instead of markup",{"id":22,"level":19,"text":23},"navigation-and-returned-objects","Navigation and returned objects",{"id":25,"level":19,"text":26},"assertions-inside-page-objects","Assertions inside page objects",{"id":28,"level":12,"text":29},"example-with-playwright","Example with Playwright",{"id":31,"level":12,"text":32},"components-and-page-fragments","Components and page fragments",{"id":34,"level":12,"text":35},"object-repository","Object repository",{"id":37,"level":19,"text":38},"pages-and-elements-as-data","Pages and elements as data",{"id":40,"level":19,"text":41},"keyword-driven-test-steps","Keyword-driven test steps",{"id":43,"level":19,"text":44},"maintenance-when-the-interface-changes","Maintenance when the interface changes",{"id":46,"level":19,"text":47},"naming-conventions","Naming conventions",{"id":49,"level":12,"text":50},"screenplay-pattern","Screenplay pattern",{"id":52,"level":12,"text":53},"inline-locators-page-object-and-object-repository","Inline locators, page object and object repository",{"id":55,"level":12,"text":56},"terms","Terms",{"id":58,"level":12,"text":59},"sources","Sources",2365,1793718917930]