{"id":369087,"date":"2024-05-21T03:56:08","date_gmt":"2024-05-21T03:56:08","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=369087"},"modified":"-0001-11-30T00:00:00","modified_gmt":"2024-05-21T03:56:08","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=369087","title":{"rendered":"<span>\u041f\u0438\u0448\u0435\u043c \u0441\u043b\u043e\u0436\u043d\u044b\u0439 Page object \u0434\u043b\u044f playwright \u0442\u0435\u0441\u0442\u043e\u0432 \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 Dorama<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0443\u0434\u043e\u0431\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c Page Object \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Playwright \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Dorama (\u0441\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u0430\u044f). <\/p>\n<p>\u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0432\u0435\u0431-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0438\u043c\u0435\u044e\u0442 \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u043c\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438. \u041f\u0440\u0438\u0447\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445, \u0442\u0430\u043a \u0438 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u0430\u0436\u043d\u043e \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c POM \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043d\u0430\u0447\u0430\u043b\u0430, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0434\u043e \u043b\u044e\u0431\u043e\u0433\u043e \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u0430 \u043b\u044e\u0431\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u0443\u043b\u0443\u0447\u0448\u0438\u0442 \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430. \u041f\u0440\u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435, \u0442\u0430\u043a \u0438 \u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u044e. \u041f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0434\u0435\u043b\u0443.<\/p>\n<p>\u041f\u0438\u0441\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u043c \u043d\u0430 typescript. \u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 playwright \u0441 \u043d\u0443\u043b\u044f, \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0432\u0441\u0435 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c POM. \u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u043c <a href=\"http:\/\/candlapp.com\" rel=\"noopener noreferrer nofollow\">candlapp.com<\/a><\/p>\n<p>\u0418\u0442\u0430\u043a, \u043f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443 \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<pre><code class=\"bash\">mkdir dorama-test cd dorama-test<\/code><\/pre>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c:<\/p>\n<pre><code class=\"bash\">npm init playwright@latest<\/code><\/pre>\n<p>\u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u044f\u0437\u044b\u043a TypeScript \u0438 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e \u0434\u0435\u0444\u043e\u043b\u0442\u0443<\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c Dorama:<\/p>\n<pre><code class=\"bash\">npm i dorama<\/code><\/pre>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u0440\u0430\u0437\u0443 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c<\/p>\n<pre><code class=\"bash\">git init<\/code><\/pre>\n<p>\u0423\u0434\u0430\u043b\u044f\u0435\u043c \u043f\u0430\u043f\u043a\u0443 test-example \u0441 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u043c\u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u043e\u0442 playwright, \u043e\u043d\u0430 \u043d\u0430\u043c \u043d\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043f\u0435\u0440\u0432\u044b\u0439 commit:<\/p>\n<pre><code class=\"bash\">git add . git commit -m \"init\"<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c:<\/p>\n<pre><code class=\"bash\">tsc -init<\/code><\/pre>\n<p>\u042d\u0442\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0444\u0430\u0439\u043b tsconfig.json, \u0434\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u0430 TypeScript. \u0418\u0449\u0435\u043c \u0432 \u044d\u0442\u043e\u043c \u0444\u0430\u0439\u043b\u0435 baseUrl \u0438 paths \u0438 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u0438\u0445 \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"json\">\"baseUrl\": \".\/\", \"paths\": {   \"@pages\/*\": [     \"page_object\/pages\/*\"   ],   \"@components\/*\": [     \"page_object\/components\/*\"   ] }<\/code><\/pre>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0438 \u0431\u0443\u0434\u0443\u0449\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u044b\u0445 \u043f\u0443\u0442\u0435\u0439, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0433\u043e\u0440\u043e\u0434\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u044b \u0432\u0438\u0434\u0430 ..\/..\/..\/..\/..\/base.page.ts<\/p>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c playwright.config.ts \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c baseURL: &#8216;https:\/\/www.candlapp.com&#8217; \u0432 use \u0441\u0435\u043a\u0446\u0438\u044e. \u0412\u0441\u0435 \u043f\u0443\u0442\u0438 \u0434\u043e \u043d\u0430\u0448\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0431\u0443\u0434\u0443\u0442 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u044d\u0442\u043e\u0433\u043e baseUrl. \u042f \u0442\u0430\u043a \u0436\u0435 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0441\u0440\u0430\u0437\u0443 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c eslint \u0438 prettier, \u043d\u043e \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043d\u0430 \u044d\u0442\u043e\u043c \u043d\u0435 \u0431\u0443\u0434\u0435\u043c. \u041d\u0430 \u044d\u0442\u043e\u043c \u0441 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u043e\u0439 \u0437\u0430\u043a\u043e\u043d\u0447\u0435\u043d\u043e, \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 commit<\/p>\n<pre><code class=\"bash\">git add . g commit -m \"config\"<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044e PO. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443 page_object \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0412\u043d\u0443\u0442\u0440\u0438 page_object \u0441\u0440\u0430\u0437\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u043c 2 \u043f\u0430\u043f\u043a\u0438 \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 &#8212; components \u0438 pages. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Header, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c header. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b header.component.ts \u0432 \u043f\u0430\u043f\u043a\u0435 components:<\/p>\n<pre><code class=\"typescript\">import { Dorama } from 'dorama';  export class HeaderComponent extends Dorama.Component {   readonly signInButton = this.locator('.menu a', { hasText: 'SIGN IN' }); }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c Dorama \u0438 \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c \u043d\u0430\u0448 \u043a\u043b\u0430\u0441\u0441 HeaderComponent \u043e\u0442 <em>Dorama<\/em>.<em>Component. \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0440\u0430\u0437\u0443 1 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043b\u043e\u043a\u0430\u0442\u043e\u0440 <\/em>signInButton. \u041b\u043e\u043a\u0430\u0442\u043e\u0440 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u0438\u0441\u044c this.locator, \u043d\u043e \u043e\u043d \u0438\u043c\u0435\u0435\u0442 \u0442\u0430\u043a\u0443\u044e \u0436\u0435 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u043a\u0430\u043a \u0438 locator playwright. \u0418 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u043d \u0442\u043e\u0436\u0435 Locator, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 \u0441 signInButton \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u0430\u043a \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u043c playwright.<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0430\u043f\u043a\u0438 pages &#8212; base.page.ts. \u0412 \u044d\u0442\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435 \u0431\u0443\u0434\u0443\u0442 \u043b\u0435\u0436\u0430\u0442\u044c \u0432\u0441\u0435 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u0441\u0430\u0439\u0442\u0443 (header, footer, \u0434\u0438\u0430\u043b\u043e\u0433\u0438 \u0438 \u0442\u0434). \u0422\u0430\u043a, \u043a \u044d\u0442\u0438\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u0441 \u043b\u044e\u0431\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 BasePage \u043d\u0435 \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0435\u043b\u0430\u0435\u043c \u0435\u0433\u043e abstract:<\/p>\n<pre><code class=\"typescript\">import { Dorama } from \"dorama\"; import { HeaderComponent } from \"@components\/header.component\";  export abstract class BasePage extends Dorama.Page {  readonly header = this.component(HeaderComponent, \"header\"); }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c <em>Dorama, \u0430 \u0442\u0430\u043a \u0436\u0435 \u043d\u0430\u0448 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <\/em>HeaderComponent<em>. \u041d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c \u043d\u0430\u0448\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 <\/em>BasePage \u043e\u0442 <em>Dorama<\/em>.<em>Page. Dorama<\/em>.<em>Page \u043e\u0431\u044f\u0437\u0443\u0435\u0442 \u043d\u0430\u0441 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 url, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443. \u041e\u0434\u043d\u0430\u043a\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u0448 \u043a\u043b\u0430\u0441\u0441 \u0442\u0430\u043a\u0436\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u044b\u043c, \u0437\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e. \u0421\u044e\u0434\u0430 \u0436\u0435 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0448 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <\/em>header \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u0438\u0441\u044c this.component(HeaderComponent, &#171;header\u00bb), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 HeaderComponent. \u0412\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 &#8212; \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a signInButton \u0447\u0435\u0440\u0435\u0437 header, \u043b\u043e\u043a\u0430\u0442\u043e\u0440 \u0434\u043e signInButton \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 header, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432\u0442\u043e\u0440\u044b\u043c \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u0432 this.component. \u0412\u0441\u0435 \u043d\u0430\u0448\u0438 \u0431\u0443\u0434\u0443\u0449\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u043e\u0442 BasePage.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c IndexPage:<\/p>\n<pre><code class=\"typescript\">import { BasePage } from \"@pages\/base.page\";  export class IndexPage extends BasePage {   url = () => \"\/\";    readonly startNowButton = this.locator(\"a\"); }<\/code><\/pre>\n<p>\u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 IndexPage \u043d\u0430\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u0441 \u043e\u0431\u044f\u0437\u044b\u0432\u0430\u044e\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 url. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u00ab\/\u00bb \u0434\u043b\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0414\u0430\u043b\u0435\u0435 \u043c\u044b \u0443\u0432\u0438\u0434\u0438\u043c \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 url.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u043e \u0432\u0441\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u043d\u0430\u0448\u0435\u0433\u043e POM, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0439 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u043b\u0438 \u043f\u0443\u0442\u044c \u0432 url. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b <a href=\"https:\/\/www.candlapp.com\/users\/sign_in\" rel=\"noopener noreferrer nofollow\">https:\/\/www.candlapp.com\/users\/sign_in<\/a>, \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0430\u043f\u043a\u0443 user, \u0432\u043d\u0443\u0442\u0440\u0438 \u043d\u0435\u0435 sign_in \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0442\u0430\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 signIn.page.ts:<\/p>\n<pre><code class=\"typescript\">import { BasePage } from \"@pages\/base.page\"; import { test } from \"@playwright\/test\";  export class SignInPage extends BasePage {   url = () => \"\/users\/sign_in\";    readonly emailField = this.locator(\"#user_email\");   readonly passwordField = this.locator(\"#user_password\");    readonly signInButton = this.locator('input[type=\"submit\"]');    loginAs = async (email: string, password: string) => {     await test.step(`Login with credentials ${email}\/${password}`, async () => {       await this.emailField.fill(email);       await this.passwordField.fill(password);        await this.signInButton.click();     });   }; }<\/code><\/pre>\n<p>\u0422\u0443\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.\u00a0<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 <a href=\"https:\/\/www.candlapp.com\/app\/#\/author\/author_id\" rel=\"noopener noreferrer nofollow\">https:\/\/www.candlapp.com\/app\/#\/author\/author_id<\/a>:<\/p>\n<pre><code class=\"typescript\">import { BasePage } from \"@pages\/base.page\"; import { BookItemComponent } from \"@components\/books\/bookItem.component\";  export class AuthorPage extends BasePage {   url = (routes: { authorId: string }) => `\/app\/#\/author\/${routes.authorId}`;    readonly loader = this.locator(\".loader\");    readonly booksItems = this.components(     BookItemComponent,     \".author-book-list .list-book-item\",   ); }<\/code><\/pre>\n<p>\u041c\u0435\u0442\u043e\u0434 url() \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 routes: { authorId: string }), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0430\u0432\u0442\u043e\u0440\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <a href=\"https:\/\/www.candlapp.com\/app\/#\/author\/48\" rel=\"noopener noreferrer nofollow\">https:\/\/www.candlapp.com\/app\/#\/author\/48<\/a>\u00a0<\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u0430 loader, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 booksItems. \u041c\u0435\u0442\u043e\u0434 this.components() \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043b\u043f\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 Components, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u0441\u0435\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 BookItemComponent, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043c\u043d\u043e\u0433\u043e. Components \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u043e\u0435 api \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043e \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 &#8212; filter, find, map \u0438 \u0442\u0434. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0443\u0434\u043e\u0431\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0432 \u0442\u0435\u0441\u0442\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"typescript\">const hellBentBook = await authorPage.booksItems.find(async book => (await book.title.innerText()) === \"Hell Bent\"); await hellBentBook.title.click();<\/code><\/pre>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c, \u043d\u0430\u0445\u043e\u0434\u0438\u043c \u0441\u0440\u0435\u0434\u0438 \u0432\u0441\u0435\u0445 \u043a\u043d\u0438\u0433 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043d\u0443\u0436\u043d\u0443\u044e \u0438 \u043a\u043b\u0438\u043a\u0430\u0435\u043c \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430 \u043d\u0435\u0435. \u041c\u044b \u0442\u0430\u043a \u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043b\u044e\u0431\u0443\u044e \u043a\u043d\u0438\u0433\u0443 \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443:<\/p>\n<pre><code class=\"typescript\">await authorPage.booksItems.nth(5).title.click();<\/code><\/pre>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 BookItemComponent \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"typescript\">import { Dorama } from \"dorama\";  export class BookItemComponent extends Dorama.Component {   readonly cover = this.locator(\".cover\");   readonly title = this.locator(\".list-book-item-title\");   readonly data = this.locator(\".book-info-data\"); }<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0441\u0442\u0440\u043e\u044f\u0442\u0441\u044f \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e, \u0441 \u043b\u044e\u0431\u043e\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0439 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u044e. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 (this.loctor()), \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 (this.component()), \u0441\u043f\u0438\u0441\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 (this.components()). \u0422\u0430\u043a\u0436\u0435, \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u0438 \u043a\u043e\u0434\u0430, \u0435\u0441\u0442\u044c \u043c\u0435\u0442\u043e\u0434 this.locators(), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 (\u043d\u043e \u0432\u043e\u043e\u0431\u0449\u0435-\u0442\u043e \u043e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 locator.all()). \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u044b \u0441\u0442\u0440\u043e\u0438\u043c \u043d\u0430\u0448\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u0438.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0442\u0435\u043f\u0435\u0440\u044c \u043a \u0442\u0435\u0441\u0442\u0430\u043c. \u0420\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438 \u0431\u0443\u0434\u0435\u043c \u0447\u0435\u0440\u0435\u0437 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u044b. \u0412 \u043f\u0430\u043f\u043a\u0435 tests \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c base.test.ts \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u043c \u0435\u0433\u043e:<\/p>\n<pre><code class=\"typescript\">import { test as base } from \"@playwright\/test\"; import { IndexPage } from \"@pages\/index.page\"; import { SignInPage } from \"@pages\/users\/sign_in\/signIn.page\"; import { AuthorPage } from \"@pages\/app\/authors\/author.page\";  type Pages = {   indexPage: IndexPage;   signInPage: SignInPage;   authorPage: AuthorPage; };  export const test = base.extend&lt;Pages>({   indexPage: async ({ page }, use) => {     await use(new IndexPage(page));   },   signInPage: async ({ page }, use) => {     await use(new SignInPage(page));   },   authorPage: async ({ page }, use) => {     await use(new AuthorPage(page));   }, });<\/code><\/pre>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c playwright test \u0438 \u043d\u0430\u0448\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u0438\u043f Pages, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043d\u0430\u0448\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u0434\u0435\u043b\u0430\u0435\u043c extend \u043e\u0431\u044a\u0435\u043a\u0442\u0430 test playwright-a. \u0412\u043d\u0443\u0442\u0440\u0438 \u0444\u0438\u043a\u0441\u0442\u0443\u0440 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u043d\u0430\u0448\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446.<\/p>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0435\u0441\u0442 (\u0441\u0430\u043c \u0442\u0435\u0441\u0442 \u0441\u043c\u044b\u0441\u043b\u0430 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442, \u043d\u043e \u0434\u043b\u044f \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0433\u043e\u0434\u0438\u0442\u0441\u044f):<\/p>\n<pre><code class=\"typescript\">import { test } from \".\/base.test\"; import { expect } from \"@playwright\/test\";  const login = \"\"; const password = \"\";  test(\"Login and go to author 48\", async ({   indexPage,   signInPage,   authorPage,   page, }) => {   await test.step(`Login as user`, async () => {     await indexPage.goto();      await indexPage.header.signInButton.click();     await expect(page).toHaveURL(signInPage.url());      await signInPage.loginAs(login, password);   });    await test.step(`Go to author and check books`, async () => {     await authorPage.goto({ authorId: \"48\" });      await authorPage.loader.waitFor({ state: \"hidden\" });      const actualBooksCount = await authorPage.booksItems.count();     expect(actualBooksCount).toBe(100);   }); });<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u043c\u0435\u0442\u043e\u0434 goto() \u043c\u044b \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b\u0438. \u0415\u0433\u043e \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 Dorama, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 url, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445. \u0412\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 &#8212; \u0447\u0442\u043e\u0431\u044b typescript \u043f\u043e\u043d\u0438\u043c\u0430\u043b \u0438 \u043f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u043b \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432 goto() (authorId \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0432\u044b\u0448\u0435), \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438. \u041c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0439\u0442\u0435 BasePage \u043a \u0442\u0430\u043a\u043e\u043c\u0443 \u0432\u0438\u0434\u0443:<\/p>\n<pre><code class=\"typescript\">export abstract class BasePage&lt;T extends Record&lt;string, string> = Record&lt;string, string>> extends Dorama.Page<\/code><\/pre>\n<p>\u0430 AuthorPage \u043a \u0442\u0430\u043a\u043e\u043c\u0443:<\/p>\n<pre><code class=\"typescript\">type UrlType = { authorId: string };  export class AuthorPage extends BasePage&lt;UrlType> {   url = (routes: UrlType) => `\/app\/#\/author\/${routes.authorId}`; }<\/code><\/pre>\n<p>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/p>\n<p>\u0413\u043e\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0441 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u043c\u043e\u0436\u043d\u043e \u043d\u0430\u0439\u0442\u0438 <a href=\"https:\/\/github.com\/PaulTriandafilov\/dorama-test\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a><\/p>\n<p>\u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 API Dorama <a href=\"https:\/\/www.npmjs.com\/package\/dorama\" rel=\"noopener noreferrer nofollow\">\u0442\u0443\u0442<\/a><\/p>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/795925\/\"> https:\/\/habr.com\/ru\/articles\/795925\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>\u0412\u0441\u0435\u043c \u043f\u0440\u0438\u0432\u0435\u0442! \u0421\u0435\u0433\u043e\u0434\u043d\u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 \u043e \u0442\u043e\u043c, \u043a\u0430\u043a \u0443\u0434\u043e\u0431\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c Page Object \u0434\u043b\u044f \u0431\u043e\u043b\u044c\u0448\u043e\u0433\u043e \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c Playwright \u0438 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 Dorama (\u0441\u0430\u043c\u043e\u043f\u0438\u0441\u043d\u0430\u044f). <\/p>\n<p>\u0411\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u0432\u0435\u0431-\u043f\u0440\u043e\u0435\u043a\u0442\u043e\u0432 \u0438\u043c\u0435\u044e\u0442 \u0441\u043b\u043e\u0436\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u043c\u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430\u043c\u0438. \u041f\u0440\u0438\u0447\u0435\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043f\u0435\u0440\u0435\u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043a\u0430\u043a \u043d\u0430 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445, \u0442\u0430\u043a \u0438 \u0432 \u0440\u0430\u043c\u043a\u0430\u0445 \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u041f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432\u0430\u0436\u043d\u043e \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0444\u043e\u0440\u043c\u0438\u0442\u044c POM \u0441 \u0441\u0430\u043c\u043e\u0433\u043e \u043d\u0430\u0447\u0430\u043b\u0430, \u0447\u0442\u043e\u0431\u044b \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u043b\u043e \u0434\u043e\u0431\u0440\u0430\u0442\u044c\u0441\u044f \u0434\u043e \u043b\u044e\u0431\u043e\u0433\u043e \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u0430 \u043b\u044e\u0431\u043e\u0433\u043e \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435. \u042d\u0442\u043e \u0443\u043f\u0440\u043e\u0441\u0442\u0438\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u0443\u043b\u0443\u0447\u0448\u0438\u0442 \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430. \u041f\u0440\u0438 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u0430\u043a \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u0435, \u0442\u0430\u043a \u0438 \u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u044e. \u041f\u0435\u0440\u0435\u0439\u0434\u0435\u043c \u043a \u0434\u0435\u043b\u0443.<\/p>\n<p>\u041f\u0438\u0441\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u043c \u043d\u0430 typescript. \u0420\u0430\u0437\u0432\u0435\u0440\u043d\u0435\u043c \u043f\u0440\u043e\u0435\u043a\u0442 \u043d\u0430 playwright \u0441 \u043d\u0443\u043b\u044f, \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0432\u0441\u0435 \u0434\u043b\u044f \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u0440\u0430\u0431\u043e\u0442\u044b \u0438 \u0440\u0435\u0430\u043b\u0438\u0437\u0443\u0435\u043c POM. \u0422\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0431\u0443\u0434\u0435\u043c <a href=\"http:\/\/candlapp.com\" rel=\"noopener noreferrer nofollow\">candlapp.com<\/a><\/p>\n<p>\u0418\u0442\u0430\u043a, \u043f\u0435\u0440\u0432\u044b\u043c \u0434\u0435\u043b\u043e\u043c \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443 \u0434\u043b\u044f \u043f\u0440\u043e\u0435\u043a\u0442\u0430:<\/p>\n<pre><code class=\"bash\">mkdir dorama-test cd dorama-test<\/code><\/pre>\n<p>\u0412\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c:<\/p>\n<pre><code class=\"bash\">npm init playwright@latest<\/code><\/pre>\n<p>\u0412\u044b\u0431\u0438\u0440\u0430\u0435\u043c \u044f\u0437\u044b\u043a TypeScript \u0438 \u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u043c \u0432\u0441\u0435 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u0435 \u043f\u043e \u0434\u0435\u0444\u043e\u043b\u0442\u0443<\/p>\n<p>\u0423\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0435\u043c Dorama:<\/p>\n<pre><code class=\"bash\">npm i dorama<\/code><\/pre>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0441\u0440\u0430\u0437\u0443 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u043c<\/p>\n<pre><code class=\"bash\">git init<\/code><\/pre>\n<p>\u0423\u0434\u0430\u043b\u044f\u0435\u043c \u043f\u0430\u043f\u043a\u0443 test-example \u0441 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u043c\u0438 \u0442\u0435\u0441\u0442\u043e\u0432 \u043e\u0442 playwright, \u043e\u043d\u0430 \u043d\u0430\u043c \u043d\u0435 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u0441\u044f. \u0421\u0434\u0435\u043b\u0430\u0435\u043c \u043f\u0435\u0440\u0432\u044b\u0439 commit:<\/p>\n<pre><code class=\"bash\">git add . git commit -m \"init\"<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u043c:<\/p>\n<pre><code class=\"bash\">tsc -init<\/code><\/pre>\n<p>\u042d\u0442\u0430 \u043a\u043e\u043c\u0430\u043d\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u0441\u0442 \u0444\u0430\u0439\u043b tsconfig.json, \u0434\u043b\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u0430 TypeScript. \u0418\u0449\u0435\u043c \u0432 \u044d\u0442\u043e\u043c \u0444\u0430\u0439\u043b\u0435 baseUrl \u0438 paths \u0438 \u0437\u0430\u043c\u0435\u043d\u044f\u0435\u043c \u0438\u0445 \u043d\u0430 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435:<\/p>\n<pre><code class=\"json\">\"baseUrl\": \".\/\", \"paths\": {   \"@pages\/*\": [     \"page_object\/pages\/*\"   ],   \"@components\/*\": [     \"page_object\/components\/*\"   ] }<\/code><\/pre>\n<p>\u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0438 \u0431\u0443\u0434\u0443\u0449\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u0443\u043a\u0430\u0437\u0430\u043d\u044b\u0445 \u043f\u0443\u0442\u0435\u0439, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0433\u043e\u0440\u043e\u0434\u0438\u0442\u044c \u0438\u043c\u043f\u043e\u0440\u0442\u044b \u0432\u0438\u0434\u0430 ..\/..\/..\/..\/..\/base.page.ts<\/p>\n<p>\u041e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c playwright.config.ts \u0438 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c baseURL: &#8216;https:\/\/www.candlapp.com&#8217; \u0432 use \u0441\u0435\u043a\u0446\u0438\u044e. \u0412\u0441\u0435 \u043f\u0443\u0442\u0438 \u0434\u043e \u043d\u0430\u0448\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 \u0431\u0443\u0434\u0443\u0442 \u0444\u043e\u0440\u043c\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u044d\u0442\u043e\u0433\u043e baseUrl. \u042f \u0442\u0430\u043a \u0436\u0435 \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0441\u0440\u0430\u0437\u0443 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c eslint \u0438 prettier, \u043d\u043e \u043e\u0441\u0442\u0430\u043d\u0430\u0432\u043b\u0438\u0432\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u043e \u043d\u0430 \u044d\u0442\u043e\u043c \u043d\u0435 \u0431\u0443\u0434\u0435\u043c. \u041d\u0430 \u044d\u0442\u043e\u043c \u0441 \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u043a\u043e\u0439 \u0437\u0430\u043a\u043e\u043d\u0447\u0435\u043d\u043e, \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043d\u043e\u0432\u044b\u0439 commit<\/p>\n<pre><code class=\"bash\">git add . g commit -m \"config\"<\/code><\/pre>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434\u0438\u043c \u043a \u043e\u0444\u043e\u0440\u043c\u043b\u0435\u043d\u0438\u044e PO. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u043f\u0430\u043f\u043a\u0443 page_object \u0432 \u043a\u043e\u0440\u043d\u0435 \u043f\u0440\u043e\u0435\u043a\u0442\u0430. \u0412\u043d\u0443\u0442\u0440\u0438 page_object \u0441\u0440\u0430\u0437\u0443 \u0441\u043e\u0437\u0434\u0430\u0435\u043c 2 \u043f\u0430\u043f\u043a\u0438 \u0434\u043b\u044f \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446 &#8212; components \u0438 pages. \u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0434\u043b\u044f \u043d\u0430\u0447\u0430\u043b\u0430 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Header, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0442\u044c header. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0444\u0430\u0439\u043b header.component.ts \u0432 \u043f\u0430\u043f\u043a\u0435 components:<\/p>\n<pre><code class=\"typescript\">import { Dorama } from 'dorama';  export class HeaderComponent extends Dorama.Component {   readonly signInButton = this.locator('.menu a', { hasText: 'SIGN IN' }); }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c Dorama \u0438 \u043d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c \u043d\u0430\u0448 \u043a\u043b\u0430\u0441\u0441 HeaderComponent \u043e\u0442 <em>Dorama<\/em>.<em>Component. \u0414\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0440\u0430\u0437\u0443 1 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u043b\u043e\u043a\u0430\u0442\u043e\u0440 <\/em>signInButton. \u041b\u043e\u043a\u0430\u0442\u043e\u0440 \u0441\u043e\u0437\u0434\u0430\u0435\u0442\u0441\u044f \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u0438\u0441\u044c this.locator, \u043d\u043e \u043e\u043d \u0438\u043c\u0435\u0435\u0442 \u0442\u0430\u043a\u0443\u044e \u0436\u0435 \u0441\u0438\u0433\u043d\u0430\u0442\u0443\u0440\u0443 \u043a\u0430\u043a \u0438 locator playwright. \u0418 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043e\u043d \u0442\u043e\u0436\u0435 Locator, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 \u0441 signInButton \u043c\u043e\u0436\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u043a\u0430\u043a \u0441 \u043e\u0431\u044b\u0447\u043d\u044b\u043c \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u043c playwright.<\/p>\n<p>\u0414\u0430\u043b\u0435\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u0431\u0430\u0437\u043e\u0432\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u0432\u043d\u0443\u0442\u0440\u0438 \u043f\u0430\u043f\u043a\u0438 pages &#8212; base.page.ts. \u0412 \u044d\u0442\u043e\u043c \u043a\u043b\u0430\u0441\u0441\u0435 \u0431\u0443\u0434\u0443\u0442 \u043b\u0435\u0436\u0430\u0442\u044c \u0432\u0441\u0435 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u043f\u043e \u0432\u0441\u0435\u043c\u0443 \u0441\u0430\u0439\u0442\u0443 (header, footer, \u0434\u0438\u0430\u043b\u043e\u0433\u0438 \u0438 \u0442\u0434). \u0422\u0430\u043a, \u043a \u044d\u0442\u0438\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430\u043c \u0431\u0443\u0434\u0435\u0442 \u0434\u043e\u0441\u0442\u0443\u043f \u0441 \u043b\u044e\u0431\u044b\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446. \u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0435 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 BasePage \u043d\u0435 \u043f\u0440\u0435\u0434\u043f\u043e\u043b\u0430\u0433\u0430\u0435\u0442\u0441\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u0434\u0435\u043b\u0430\u0435\u043c \u0435\u0433\u043e abstract:<\/p>\n<pre><code class=\"typescript\">import { Dorama } from \"dorama\"; import { HeaderComponent } from \"@components\/header.component\";  export abstract class BasePage extends Dorama.Page {  readonly header = this.component(HeaderComponent, \"header\"); }<\/code><\/pre>\n<p>\u0417\u0434\u0435\u0441\u044c \u043c\u044b \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c <em>Dorama, \u0430 \u0442\u0430\u043a \u0436\u0435 \u043d\u0430\u0448 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <\/em>HeaderComponent<em>. \u041d\u0430\u0441\u043b\u0435\u0434\u0443\u0435\u043c \u043d\u0430\u0448\u0443 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 <\/em>BasePage \u043e\u0442 <em>Dorama<\/em>.<em>Page. Dorama<\/em>.<em>Page \u043e\u0431\u044f\u0437\u0443\u0435\u0442 \u043d\u0430\u0441 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 url, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443. \u041e\u0434\u043d\u0430\u043a\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u0448 \u043a\u043b\u0430\u0441\u0441 \u0442\u0430\u043a\u0436\u0435 \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0442\u043d\u044b\u043c, \u0437\u0434\u0435\u0441\u044c \u043d\u0430\u043c \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0442\u044c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e. \u0421\u044e\u0434\u0430 \u0436\u0435 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u043d\u0430\u0448 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 <\/em>header \u0447\u0435\u0440\u0435\u0437 \u0437\u0430\u043f\u0438\u0441\u044c this.component(HeaderComponent, &#171;header\u00bb), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 HeaderComponent. \u0412\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 &#8212; \u043a\u043e\u0433\u0434\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043e\u0431\u0440\u0430\u0449\u0430\u0442\u044c\u0441\u044f \u043a signInButton \u0447\u0435\u0440\u0435\u0437 header, \u043b\u043e\u043a\u0430\u0442\u043e\u0440 \u0434\u043e signInButton \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u0441\u0442\u0440\u043e\u0435\u043d \u043e\u0442\u043d\u043e\u0441\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043a\u043e\u043d\u0442\u0435\u0439\u043d\u0435\u0440\u0430 header, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0432\u0442\u043e\u0440\u044b\u043c \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u0432 this.component. \u0412\u0441\u0435 \u043d\u0430\u0448\u0438 \u0431\u0443\u0434\u0443\u0449\u0438\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u0442\u044c \u043e\u0442 BasePage.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0442\u0435\u043f\u0435\u0440\u044c \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c IndexPage:<\/p>\n<pre><code class=\"typescript\">import { BasePage } from \"@pages\/base.page\";  export class IndexPage extends BasePage {   url = () => \"\/\";    readonly startNowButton = this.locator(\"a\"); }<\/code><\/pre>\n<p>\u042d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 IndexPage \u043d\u0430\u043c \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u0440\u0438\u0434\u0435\u0442\u0441\u044f, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0430\u0441 \u043e\u0431\u044f\u0437\u044b\u0432\u0430\u044e\u0442 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 url. \u0412 \u0434\u0430\u043d\u043d\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u0435\u0442\u043e\u0434 \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u00ab\/\u00bb \u0434\u043b\u044f \u0438\u043d\u0434\u0435\u043a\u0441\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0414\u0430\u043b\u0435\u0435 \u043c\u044b \u0443\u0432\u0438\u0434\u0438\u043c \u043a\u0430\u043a \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e\u0442 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0441 \u0434\u0438\u043d\u0430\u043c\u0438\u0447\u0435\u0441\u043a\u0438\u043c\u0438 url.<\/p>\n<p>\u0427\u0442\u043e\u0431\u044b \u0431\u044b\u043b\u043e \u0443\u0434\u043e\u0431\u043d\u043e \u043e\u0440\u0438\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u043e \u0432\u0441\u0435\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445 \u043d\u0430\u0448\u0435\u0433\u043e POM, \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u0435\u0442\u0441\u044f \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u0430\u0442\u044c \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u044e \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u0439 \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u043e\u043d\u0438 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u043b\u0438 \u043f\u0443\u0442\u044c \u0432 url. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0434\u043b\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b <a href=\"https:\/\/www.candlapp.com\/users\/sign_in\" rel=\"noopener noreferrer nofollow\">https:\/\/www.candlapp.com\/users\/sign_in<\/a>, \u043d\u0443\u0436\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0430\u043f\u043a\u0443 user, \u0432\u043d\u0443\u0442\u0440\u0438 \u043d\u0435\u0435 sign_in \u0438 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u044c \u0442\u0430\u043c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 signIn.page.ts:<\/p>\n<pre><code class=\"typescript\">import { BasePage } from \"@pages\/base.page\"; import { test } from \"@playwright\/test\";  export class SignInPage extends BasePage {   url = () => \"\/users\/sign_in\";    readonly emailField = this.locator(\"#user_email\");   readonly passwordField = this.locator(\"#user_password\");    readonly signInButton = this.locator('input[type=\"submit\"]');    loginAs = async (email: string, password: string) => {     await test.step(`Login with credentials ${email}\/${password}`, async () => {       await this.emailField.fill(email);       await this.passwordField.fill(password);        await this.signInButton.click();     });   }; }<\/code><\/pre>\n<p>\u0422\u0443\u0442 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u043c\u0435\u0442\u043e\u0434 \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.\u00a0<\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u043e\u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0441\u043b\u043e\u0436\u043d\u0443\u044e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 <a href=\"https:\/\/www.candlapp.com\/app\/#\/author\/author_id\" rel=\"noopener noreferrer nofollow\">https:\/\/www.candlapp.com\/app\/#\/author\/author_id<\/a>:<\/p>\n<pre><code class=\"typescript\">import { BasePage } from \"@pages\/base.page\"; import { BookItemComponent } from \"@components\/books\/bookItem.component\";  export class AuthorPage extends BasePage {   url = (routes: { authorId: string }) => `\/app\/#\/author\/${routes.authorId}`;    readonly loader = this.locator(\".loader\");    readonly booksItems = this.components(     BookItemComponent,     \".author-book-list .list-book-item\",   ); }<\/code><\/pre>\n<p>\u041c\u0435\u0442\u043e\u0434 url() \u043f\u0440\u0438\u043d\u0438\u043c\u0430\u0435\u0442 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440 routes: { authorId: string }), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u043d\u0430\u043c \u0437\u0430\u0445\u043e\u0434\u0438\u0442\u044c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u043e\u0433\u043e \u0430\u0432\u0442\u043e\u0440\u0430, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440 <a href=\"https:\/\/www.candlapp.com\/app\/#\/author\/48\" rel=\"noopener noreferrer nofollow\">https:\/\/www.candlapp.com\/app\/#\/author\/48<\/a>\u00a0<\/p>\n<p>\u041f\u043e\u043c\u0438\u043c\u043e \u043f\u0440\u043e\u0441\u0442\u043e\u0433\u043e \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u0430 loader, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 booksItems. \u041c\u0435\u0442\u043e\u0434 this.components() \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u044d\u043a\u0437\u0435\u043c\u043b\u043f\u044f\u0440 \u043a\u043b\u0430\u0441\u0441\u0430 Components, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u0441\u043f\u0438\u0441\u043e\u043a \u0432\u0441\u0435\u0445 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 BookItemComponent, \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043c\u043d\u043e\u0433\u043e. Components \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u0435\u0442 \u0443\u0434\u043e\u0431\u043d\u043e\u0435 api \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441\u043e \u0441\u043f\u0438\u0441\u043a\u043e\u043c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432, \u0432 \u0442\u043e\u043c \u0447\u0438\u0441\u043b\u0435 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0435 &#8212; filter, find, map \u0438 \u0442\u0434. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u0442 \u0443\u0434\u043e\u0431\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u0442\u044c \u0441\u043f\u0438\u0441\u043e\u043a \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 \u0432 \u0442\u0435\u0441\u0442\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440:<\/p>\n<pre><code class=\"typescript\">const hellBentBook = await authorPage.booksItems.find(async book => (await book.title.innerText()) === \"Hell Bent\"); await hellBentBook.title.click();<\/code><\/pre>\n<p>\u0422\u043e \u0435\u0441\u0442\u044c, \u043d\u0430\u0445\u043e\u0434\u0438\u043c \u0441\u0440\u0435\u0434\u0438 \u0432\u0441\u0435\u0445 \u043a\u043d\u0438\u0433 \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435 \u043d\u0443\u0436\u043d\u0443\u044e \u0438 \u043a\u043b\u0438\u043a\u0430\u0435\u043c \u0438\u043c\u0435\u043d\u043d\u043e \u043d\u0430 \u043d\u0435\u0435. \u041c\u044b \u0442\u0430\u043a \u0436\u0435 \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043b\u044e\u0431\u0443\u044e \u043a\u043d\u0438\u0433\u0443 \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443:<\/p>\n<pre><code class=\"typescript\">await authorPage.booksItems.nth(5).title.click();<\/code><\/pre>\n<p>\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 BookItemComponent \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c:<\/p>\n<pre><code class=\"typescript\">import { Dorama } from \"dorama\";  export class BookItemComponent extends Dorama.Component {   readonly cover = this.locator(\".cover\");   readonly title = this.locator(\".list-book-item-title\");   readonly data = this.locator(\".book-info-data\"); }<\/code><\/pre>\n<p>\u041e\u0441\u0442\u0430\u043b\u044c\u043d\u044b\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0441\u0442\u0440\u043e\u044f\u0442\u0441\u044f \u0430\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e, \u0441 \u043b\u044e\u0431\u043e\u0439 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0439 \u0432\u043b\u043e\u0436\u0435\u043d\u043d\u043e\u0441\u0442\u044c\u044e. \u041f\u043e\u043b\u0443\u0447\u0430\u0435\u0442\u0441\u044f, \u0447\u0442\u043e \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0431\u0443\u0434\u0443\u0442 \u0441\u043e\u0441\u0442\u043e\u044f\u0442\u044c \u0438\u0437 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 (this.loctor()), \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 (this.component()), \u0441\u043f\u0438\u0441\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u043e\u0432 (this.components()). \u0422\u0430\u043a\u0436\u0435, \u0434\u043b\u044f \u0447\u0438\u0442\u0430\u0435\u043c\u043e\u0441\u0442\u0438 \u043a\u043e\u0434\u0430, \u0435\u0441\u0442\u044c \u043c\u0435\u0442\u043e\u0434 this.locators(), \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 \u043c\u0430\u0441\u0441\u0438\u0432 \u043f\u0440\u043e\u0441\u0442\u044b\u0445 \u043b\u043e\u043a\u0430\u0442\u043e\u0440\u043e\u0432 (\u043d\u043e \u0432\u043e\u043e\u0431\u0449\u0435-\u0442\u043e \u043e\u043d \u043f\u0440\u043e\u0441\u0442\u043e \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u0435\u0442 locator.all()). \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u044b \u0441\u0442\u0440\u043e\u0438\u043c \u043d\u0430\u0448\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u044b \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043d\u0430\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f \u0438 \u043a\u043e\u043c\u043f\u043e\u0437\u0438\u0446\u0438\u0438.<\/p>\n<p>\u0414\u0430\u0432\u0430\u0439\u0442\u0435 \u0442\u0435\u043f\u0435\u0440\u044c \u043a \u0442\u0435\u0441\u0442\u0430\u043c. \u0420\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u043c\u0438 \u0431\u0443\u0434\u0435\u043c \u0447\u0435\u0440\u0435\u0437 \u0444\u0438\u043a\u0441\u0442\u0443\u0440\u044b. \u0412 \u043f\u0430\u043f\u043a\u0435 tests \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c base.test.ts \u0438 \u0437\u0430\u043f\u043e\u043b\u043d\u0438\u043c \u0435\u0433\u043e:<\/p>\n<pre><code class=\"typescript\">import { test as base } from \"@playwright\/test\"; import { IndexPage } from \"@pages\/index.page\"; import { SignInPage } from \"@pages\/users\/sign_in\/signIn.page\"; import { AuthorPage } from \"@pages\/app\/authors\/author.page\";  type Pages = {   indexPage: IndexPage;   signInPage: SignInPage;   authorPage: AuthorPage; };  export const test = base.extend&lt;Pages>({   indexPage: async ({ page }, use) => {     await use(new IndexPage(page));   },   signInPage: async ({ page }, use) => {     await use(new SignInPage(page));   },   authorPage: async ({ page }, use) => {     await use(new AuthorPage(page));   }, });<\/code><\/pre>\n<p>\u0418\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u0443\u0435\u043c playwright test \u0438 \u043d\u0430\u0448\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u0421\u043e\u0437\u0434\u0430\u0435\u043c \u0442\u0438\u043f Pages, \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u044e\u0449\u0438\u0439 \u043d\u0430\u0448\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u0434\u0435\u043b\u0430\u0435\u043c extend \u043e\u0431\u044a\u0435\u043a\u0442\u0430 test playwright-a. \u0412\u043d\u0443\u0442\u0440\u0438 \u0444\u0438\u043a\u0441\u0442\u0443\u0440 \u043f\u0440\u043e\u0441\u0442\u043e \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u044b \u043d\u0430\u0448\u0438\u0445 \u0441\u0442\u0440\u0430\u043d\u0438\u0446.<\/p>\n<p>\u041f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u043c, \u043a\u0430\u043a \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0435\u0441\u0442 (\u0441\u0430\u043c \u0442\u0435\u0441\u0442 \u0441\u043c\u044b\u0441\u043b\u0430 \u043d\u0435 \u0438\u043c\u0435\u0435\u0442, \u043d\u043e \u0434\u043b\u044f \u0434\u0435\u043c\u043e\u043d\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u0433\u043e\u0434\u0438\u0442\u0441\u044f):<\/p>\n<pre><code class=\"typescript\">import { test } from \".\/base.test\"; import { expect } from \"@playwright\/test\";  const login = \"\"; const password = \"\";  test(\"Login and go to author 48\", async ({   indexPage,   signInPage,   authorPage,   page, }) => {   await test.step(`Login as user`, async () => {     await indexPage.goto();      await indexPage.header.signInButton.click();     await expect(page).toHaveURL(signInPage.url());      await signInPage.loginAs(login, password);   });    await test.step(`Go to author and check books`, async () => {     await authorPage.goto({ authorId: \"48\" });      await authorPage.loader.waitFor({ state: \"hidden\" });      const actualBooksCount = await authorPage.booksItems.count();     expect(actualBooksCount).toBe(100);   }); });<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u043c\u0435\u0442\u043e\u0434 goto() \u043c\u044b \u043d\u0438\u0433\u0434\u0435 \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u043b\u0438. \u0415\u0433\u043e \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 Dorama, \u043a\u043e\u0442\u043e\u0440\u0430\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 url, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043c\u044b \u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u043c \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0445. \u0412\u0430\u0436\u043d\u044b\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 &#8212; \u0447\u0442\u043e\u0431\u044b typescript \u043f\u043e\u043d\u0438\u043c\u0430\u043b \u0438 \u043f\u043e\u0434\u0441\u0432\u0435\u0447\u0438\u0432\u0430\u043b \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u0432 goto() (authorId \u0432 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u0432\u044b\u0448\u0435), \u043d\u0443\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0442\u0438\u043f\u0438\u0437\u0430\u0446\u0438\u0438. \u041c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0443\u0439\u0442\u0435 BasePage \u043a \u0442\u0430\u043a\u043e\u043c\u0443 \u0432\u0438\u0434\u0443:<\/p>\n<pre><code class=\"typescript\">export abstract class BasePage&lt;T extends Record&lt;string, string> = Record&lt;string, string>> extends Dorama.Page<\/code><\/pre>\n<p>\u0430 AuthorPage \u043a \u0442\u0430\u043a\u043e\u043c\u0443:<\/p>\n<pre><code class=\"typescript\">type UrlType = { authorId: string };  export class AuthorPage extends BasePage&lt;UrlType> {   url = (routes: UrlType) =><\/code><\/pre>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-369087","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/369087","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=369087"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/369087\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=369087"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=369087"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=369087"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}