{"id":317736,"date":"2021-02-09T15:00:50","date_gmt":"2021-02-09T15:00:50","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=317736"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=317736","title":{"rendered":"\u041f\u0435\u0440\u0435\u0441\u0442\u0430\u043d\u044c\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Page Objects (\u0420\u041e) \u0438 \u043d\u0430\u0447\u043d\u0438\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c App Actions"},"content":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<blockquote>\n<p><em>\u041f\u0440\u0438\u0432\u0435\u0442, \u0445\u0430\u0431\u0440\u043e\u0432\u0447\u0430\u043d\u0435. \u0414\u043b\u044f \u0431\u0443\u0434\u0443\u0449\u0438\u0445 \u0441\u0442\u0443\u0434\u0435\u043d\u0442\u043e\u0432 <\/em><a href=\"https:\/\/otus.pw\/vx0y\/\"><em>\u043a\u0443\u0440\u0441\u0430 \u00abJavaScript QA Engineer\u00bb<\/em><\/a><em> \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438  \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u0433\u043b\u0430\u0448\u0430\u0435\u043c \u0432\u0441\u0435\u0445 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0445 \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u0438\u0435 \u0432 <\/em><a href=\"https:\/\/otus.pw\/29EC\/\"><em>\u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u0435\u0431\u0438\u043d\u0430\u0440\u0435 \u043d\u0430 \u0442\u0435\u043c\u0443 \u00ab\u0427\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0437\u043d\u0430\u0442\u044c \u043e JS \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0443\u00bb.<\/em><\/a><em> \u041d\u0430 \u0437\u0430\u043d\u044f\u0442\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u044b \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 JS, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0441\u0451 \u0432\u0440\u0435\u043c\u044f \u043d\u0443\u0436\u043d\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0433\u043e\u043b\u043e\u0432\u0435 \u043f\u0440\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432.<\/em><\/p>\n<\/blockquote>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9ce\/da0\/021\/9ceda0021fa73d348289f85fb976e806\" width=\"780\" height=\"439\"><figcaption><\/figcaption><\/figure>\n<hr>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u2014 \u044d\u0442\u043e \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430. \u0427\u0430\u0441\u0442\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u0441\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0439 page objects, \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043e\u0431\u0449\u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044e, \u0447\u0442\u043e page objects \u2014 \u044d\u0442\u043e \u043f\u043b\u043e\u0445\u0430\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430, \u0438 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042d\u0442\u043e \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c test runner Cypress.io, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<h3>Page objects<\/h3>\n<p>Page Objects <a href=\"https:\/\/github.com\/SeleniumHQ\/selenium\/wiki\/PageObjects\"><u>1<\/u><\/a>, <a href=\"https:\/\/www.linkedin.com\/pulse\/page-object-pattern-javascript-vladim%C3%ADr-gorej\/\"><u>2 <\/u><\/a>\u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u044b \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0447\u0438\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u0438 \u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u0432 \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u0438. \u0412\u043c\u0435\u0441\u0442\u043e ad-hoc \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0446\u0438\u0439 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439, \u0442\u0435\u0441\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0437\u0434\u0435\u0441\u044c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0445\u043e\u0434\u0430 \u0432\u0437\u044f\u0442\u0430 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b Selenium Wiki.<\/p>\n<pre><code class=\"javascript\">public class LoginPage {   private final WebDriver driver;    public LoginPage(WebDriver driver) {     this.driver = driver;      \/\/ Check that we're on the right page.     if (!\"Login\".equals(driver.getTitle())) {       \/\/ Alternatively, we could navigate to the       \/\/ login page, perhaps logging out first       throw new IllegalStateException(\"This is not the login page\");     }   }    \/\/ The login page contains several HTML elements   \/\/ that will be represented as WebElements.   \/\/ The locators for these elements should only be defined once.   By usernameLocator = By.id(\"username\");   By passwordLocator = By.id(\"password\");   By loginButtonLocator = By.id(\"login\");    \/\/ The login page allows the user to type their   \/\/ username into the username field   public LoginPage typeUsername(String username) {     \/\/ This is the only place that \"knows\" how to enter a username     driver.findElement(usernameLocator).sendKeys(username);      \/\/ Return the current page object as this action doesn't     \/\/ navigate to a page represented by another PageObject     return this;   }   \/\/ other methods   \/\/  - typePassword   \/\/  - submitLogin   \/\/  - submitLoginExpectingFailure   \/\/  - loginAs }<\/code><\/pre>\n<h4>Page Objects \u0438\u043c\u0435\u044e\u0442 \u0434\u0432\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430:<\/h4>\n<ol>\n<li>\n<p>\u041e\u043d\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u0432\u0441\u0435 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432 \u043e\u0434\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435<\/p>\n<\/li>\n<li>\n<p>\u041e\u043d\u0438 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0438\u0437\u0438\u0440\u0443\u044e\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0438\u043f\u043e\u0432\u043e\u0439 \u0442\u0435\u0441\u0442 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 Page Objects:<\/p>\n<pre><code class=\"javascript\">public void testLogin() {   LoginPage login = new LoginPage(driver);   login.typeUsername('username')   login.typePassword('username')   login.submitLogin() }<\/code><\/pre>\n<p>\u041c\u0430\u0440\u0442\u0438\u043d \u0424\u0430\u0443\u043b\u0435\u0440 \u0432 \u0441\u0432\u043e\u0435\u0439 <a href=\"https:\/\/martinfowler.com\/bliki\/PageObject.html\"><u>\u0441\u0442\u0430\u0442\u044c\u0435 <\/u><\/a>\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 PageObjects \u043a\u0430\u043a \u0435\u0449\u0435 \u043e\u0434\u0438\u043d API \u043f\u043e\u043c\u0438\u043c\u043e HTML. \u041a\u043e\u043d\u0446\u0435\u043f\u0442\u0443\u0430\u043b\u044c\u043d\u043e PageObjects \u0434\u043e\u043f\u043e\u043b\u043d\u044f\u044e\u0442 HTML.<\/p>\n<pre><code>Tests -----------------   Page Objects ~ ~ ~ ~ ~ ~ ~ ~ ~     HTML UI ----------------- Application code<\/code><\/pre>\n<p>\u0427\u0435\u0442\u044b\u0440\u0435&nbsp; \u0443\u0440\u043e\u0432\u043d\u044f \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435 \u0432\u044b\u0448\u0435 \u0438\u043c\u0435\u044e\u0442 \u0442\u0440\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0441 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c \u0443\u0440\u043e\u0432\u043d\u0435\u043c \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0441\u0442\u0438.&nbsp;<\/p>\n<p>1. \u0421\u0432\u044f\u0437\u044c \u043c\u0435\u0436\u0434\u0443 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 HTML \u0432\u044b\u0441\u043e\u043a\u0430\u044f.&nbsp;<\/p>\n<p>2. \u041c\u0435\u0436\u0434\u0443 HTML \u0438 page objects \u043d\u0438\u0437\u043a\u0430\u044f.&nbsp;<\/p>\n<p>3. \u041c\u0435\u0436\u0434\u0443 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0438 PO \u0432\u044b\u0441\u043e\u043a\u0430\u044f<\/p>\n<p><strong>\u0421\u0432\u044f\u0437\u043d\u043e\u0441\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 HTML UI \u043e\u0447\u0435\u043d\u044c \u0432\u044b\u0441\u043e\u043a\u0430\u044f,<\/strong> \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043a\u043e\u0434 \u0432\u044b\u0432\u043e\u0434\u0438\u0442 HTML-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0432 DOM \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 render&nbsp; \u0432 \u043a\u043e\u0434\u0435&nbsp; \u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u043c-\u0432\u044b\u0432\u043e\u0434\u0430 DOM \u0441\u0432\u044f\u0437\u044c one to one.&nbsp; \u0421\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0442\u0438\u043f\u044b \u0438 \u043b\u0438\u043d\u0442\u0435\u0440\u044b \u043f\u043e\u043c\u043e\u0433\u0430\u044e\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 HTML.<\/p>\n<p><strong>Page objects \u0438 HTML \u0441\u043b\u0430\u0431\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u044b<\/strong>, \u0432\u043e\u0442 \u043f\u043e\u0447\u0435\u043c\u0443 \u044f \u043f\u0440\u043e\u0432\u0435\u043b \u0433\u0440\u0430\u043d\u0438\u0446\u0443, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f ~ ~. \u041e\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u041d\u0415 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442\u0441\u044f \u043a\u0430\u043a\u0438\u043c-\u043b\u0438\u0431\u043e \u043b\u0438\u043d\u0442\u0435\u0440\u043e\u043c \u0438\u043b\u0438 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0440\u043e\u043c \u043a\u043e\u0434\u0430. \u041a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u043b\u044e\u0431\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f, \u0432\u044b\u0434\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u0443\u044e DOM \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0435\u0440\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0431\u0435\u0437 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u044f.<\/p>\n<p><em>\u042d\u0442\u043e \u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043b\u0438\u043d\u0442\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0442\u044c \u0441\u0432\u044f\u0437\u044c \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c&nbsp; HTML \u0438 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445.<\/em><\/p>\n<p><strong>\u0418 \u043d\u0430\u043a\u043e\u043d\u0435\u0446, \u0441\u0432\u044f\u0437\u044c \u043c\u0435\u0436\u0434\u0443 page objects \u0438 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u043e\u0447\u0435\u043d\u044c \u0432\u044b\u0441\u043e\u043a\u0430\u044f<\/strong> \u2014 \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u0431\u0430 \u0443\u0440\u043e\u0432\u043d\u044f \u0432 \u0442\u043e\u043c \u0436\u0435 \u0441\u0430\u043c\u043e\u043c \u043a\u043e\u0434\u0435 \u0438 \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c\u0441\u044f \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u044b\u043b\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<h3>Page objects \u0432 Cypress<\/h3>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441 \u043b\u0435\u0433\u043a\u043e\u0441\u0442\u044c\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Page Objects \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 Cypress. \u0412\u043e\u0442 \u0442\u0438\u043f\u043e\u0432\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u201c<a href=\"https:\/\/medium.com\/reactbrasil\/deep-diving-pageobject-pattern-and-using-it-with-cypress-e60b9d7d0d91\">Deep diving PageObject pattern and using it with Cypress<\/a>\u201d. \u0422\u0438\u043f\u043e\u0432\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 PageObject SignInPage \u0441\u0445\u043e\u0436 \u0441 LoginPage \u0438\u0437 Selenium, \u0447\u0442\u043e \u0431\u044b\u043b \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u0432\u044b\u0448\u0435.<\/p>\n<pre><code class=\"javascript\">class SignInPage {   visit() {     cy.visit('\/signin');   }    getEmailError() {     return cy.get(`[data-testid=SignInEmailError]`);   }    getPasswordError() {     return cy.get(`[data-testid=SignInPasswordError]`);   }    fillEmail(value) {     const field = cy.get(`[data-testid=SignInEmailField]`);     field.clear();     field.type(value);      return this;   }    fillPassword(value) {     const field = cy.get(`[data-testid=SignInPasswordField]`);     field.clear();     field.type(value);      return this;   }    submit() {     const button = cy.get(`[data-testid=SignInSubmitButton]`);     button.click();   } }  export default SignInPage;<\/code><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043c\u044b \u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0442\u0435\u0441\u0442 \u0434\u043b\u044f \u201cHome page\u201d, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0435\u0449\u0435 \u0440\u0430\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c&nbsp;<code>SignInPage<\/code> \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e Page Object.<\/p>\n<pre><code class=\"javascript\">import Header from '.\/Headers'; import SignInPage from '.\/SignIn';  class HomePage {   constructor() {     this.header = new Header();   }    visit() {     cy.visit('\/');   }    getUserAvatar() {     return cy.get(`[data-testid=UserAvatar]`);   }    goToSignIn() {     const link = this.header.getSignInLink();     link.click();      const signIn = new SignInPage();     return signIn;   } }  export default HomePage;<\/code><\/pre>\n<p>\u042d\u0442\u043e \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u2014 \u0432\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u0430 <code>PageObject<\/code>, \u0433\u0434\u0435 \u0432\u0441\u0435 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0440\u0430\u0437\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f \u0438\u0445, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f object-oriented \u0434\u0438\u0437\u0430\u0439\u043d. \u0422\u0438\u043f\u043e\u0432\u043e\u0439 \u0442\u0435\u0441\u0442 \u0442\u043e\u0433\u0434\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"javascript\">import HomePage from '..\/elements\/pages\/HomePage';  describe('Sign In', () =&gt; {   it('should show an error message on empty input', () =&gt; {     const home = new HomePage();     home.visit();      const signIn = home.goToSignIn();      signIn.submit();      signIn.getEmailError()       .should('exist')       .contains('Email is required');      signIn       .getPasswordError()       .should('exist')       .contains('Password is required');   });    \/\/ more tests });<\/code><\/pre>\n<p>Cypress \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0431\u0430\u043d\u0442\u043b\u0435\u0440 JavaScript, \u0438 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u043e\u0434, \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0432\u044b\u0448\u0435.<\/p>\n<p>\u0412\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c object-oriented PageObject. \u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0442\u0438\u043f\u0438\u0447\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u0440\u0435\u0436\u0438\u043c \u043c\u043d\u043e\u0433\u043e\u0440\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f <a href=\"https:\/\/on.cypress.io\/custom-commands\">Cypress Custom Commands<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0438\u043c\u0435\u044e\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u201clogin\u201d.<\/p>\n<pre><code class=\"javascript\">\/\/ in cypress\/support\/commands.js Cypress.Commands.add('login', (username, password) =&gt; {   cy.get('#login-username').type(username)   cy.get('#login-password').type(password)   cy.get('#login').submit() })<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434, \u0442\u0435\u0441\u0442\u044b \u0438\u0445 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0436\u0435, \u043a\u0430\u043a \u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>built-in<\/code>.<\/p>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/spec.js it('logs in', () =&gt; {   cy.visit('\/login')   cy.login('username', 'password') })<\/code><\/pre>\n<p>\u0417\u0430\u043c\u0435\u0442\u044c\u0442\u0435, \u0447\u0442\u043e \u0432\u0430\u043c \u043d\u0435 \u043d\u0430\u0434\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438 JavaScript (\u043c\u043e\u0436\u0435\u0442 \u044d\u0442\u043e \u0434\u0430\u0436\u0435 \u0438 \u043b\u0443\u0447\u0448\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0438\u043f check step \u043c\u043e\u0436\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/util.js export const login = (username, password) =&gt; {   cy.get('#login-username').type(username)   cy.get('#login-password').type(password)   cy.get('#login').submit() }<\/code><\/pre>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/spec.js import { login } from '.\/util'  it('logs in', () =&gt; {   cy.visit('\/login')   login('username', 'password') })<\/code><\/pre>\n<h4>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u044b Page Objects<\/h4>\n<p>\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0440\u0430\u0437\u0434\u0435\u043b\u0430\u0445 \u044f \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u044e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u044b, \u0433\u0434\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d <code>PageObject<\/code> \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0445\u043e\u0440\u043e\u0448\u0438\u0445 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<ul>\n<li>\n<p>\u041e\u0431\u044a\u0435\u043a\u0442\u044b <code>PageObject<\/code> \u0442\u0440\u0443\u0434\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c, \u0438 \u044d\u0442\u043e \u043e\u0442\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0440\u0435\u043c\u044f \u0443 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042f \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0432\u0438\u0434\u0435\u043b <code>PageObjects<\/code> \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u043c\u043e\u0447\u044c \u0432 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<\/li>\n<li>\n<p><code>PageObject<\/code> \u0432\u043d\u043e\u0441\u0438\u0442 \u0432 \u0442\u0435\u0441\u0442\u044b \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0442\u0434\u0435\u043b\u0435\u043d\u043e \u043e\u0442 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042d\u0442\u043e \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u044f\u0435\u0442 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u0441\u0431\u043e\u044f\u043c..<\/p>\n<\/li>\n<li>\n<p><code>PageObject<\/code> \u043f\u044b\u0442\u0430\u044e\u0442\u0441\u044f \u0432\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044f\u0441\u044c \u043a \u0443\u0441\u043b\u043e\u0432\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0435 \u2014 \u043e\u0433\u0440\u043e\u043c\u043d\u044b\u0439, \u043d\u0430 \u043d\u0430\u0448 \u0432\u0437\u0433\u043b\u044f\u0434, <a href=\"https:\/\/on.cypress.io\/conditional-testing\"><u>\u0430\u043d\u0442\u0438\u043f\u0430\u0442\u0442\u0435\u0440\u043d<\/u><\/a>.<\/p>\n<\/li>\n<li>\n<p><code>PageObject<\/code> \u0434\u0435\u043b\u0430\u044e\u0442 \u0442\u0435\u0441\u0442\u044b \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u043c\u0438, \u0442\u0430\u043a \u043a\u0430\u043a \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0442\u0435\u0441\u0442\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0435 \u043e\u0442\u0447\u0430\u0438\u0432\u0430\u0439\u0442\u0435\u0441\u044c! \u042f \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043a\u0430\u0436\u0443 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0443 Page Objects, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043d\u0430\u0437\u044b\u0432\u0430\u044e &#171;App Actions&#187;, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0438 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b. \u042f \u0441\u0447\u0438\u0442\u0430\u044e, App Actions \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0440\u043e\u0448\u043e \u0440\u0435\u0448\u0430\u044e\u0442 \u0432\u044b\u0448\u0435\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0434\u0435\u043b\u0430\u044f \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0431\u044b\u0441\u0442\u0440\u044b\u043c\u0438 \u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432<\/h4>\n<p>\u0412\u043e\u0437\u044c\u043c\u0435\u043c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0442\u0435\u0441\u0442\u044b <a href=\"http:\/\/todomvc.com\/\"><u>TodoMVC<\/u><\/a>. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u043c\u043e\u0436\u0435\u0442 \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0432\u0432\u043e\u0434\u0438\u0442\u044c todos. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Cypress \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u2014 \u0442\u043e\u0447\u043d\u043e \u0442\u0430\u043a \u0436\u0435, \u043a\u0430\u043a \u0438 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b.<\/p>\n<pre><code class=\"javascript\">describe('TodoMVC', function () {   \/\/ set up these constants to match what TodoMVC does   let TODO_ITEM_ONE = 'buy some cheese'   let TODO_ITEM_TWO = 'feed the cat'   let TODO_ITEM_THREE = 'book a doctors appointment'    beforeEach(function () {     cy.visit('\/')   })    context('New Todo', function () {     it('should allow me to add todo items', function () {       cy.get('.new-todo').type(TODO_ITEM_ONE).type('{enter}')       cy.get('.todo-list li').eq(0).find('label').should('contain', TODO_ITEM_ONE)       cy.get('.new-todo').type(TODO_ITEM_TWO).type('{enter}')       cy.get('.todo-list li').eq(1).find('label').should('contain', TODO_ITEM_TWO)     })      \/\/ more tests for adding items     \/\/ - adds items     \/\/ - should clear text input field when an item is added     \/\/ - should append new items to the bottom of the list     \/\/ - should trim text input     \/\/ - should show #main and #footer when items added   }) })<\/code><\/pre>\n<p>\u0412\u0441\u0435 \u044d\u0442\u0438 \u0442\u0435\u0441\u0442\u044b \u0432\u043d\u0443\u0442\u0440\u0438 \u0431\u043b\u043e\u043a\u0430 \u201cNew Todo\u201d \u0432\u0432\u043e\u0434\u044f\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044f <code>&lt;input class=\"new-todo\" \/&gt;<\/code> \u0431\u0435\u0437&nbsp;shortcuts. \u0412\u043e\u0442 \u0437\u0434\u0435\u0441\u044c \u0442\u0435\u0441\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0441\u0430\u043c\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442\u0441\u044f.<\/p>\n<p><iframe id=\"602264558b25a4d2350c145f\" src=\"https:\/\/embedd.srv.habr.com\/iframe\/602264558b25a4d2350c145f\" class=\"embed_video embed__content\" allowfullscreen=\"true\"><\/iframe><\/p>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0442\u0435\u0441\u0442 \u043d\u0430\u0447\u0438\u043d\u0430\u0435\u0442\u0441\u044f \u0441 \u0442\u0435\u043a\u0441\u0442\u0430 \u201cbuy some cheese\u201d (\u043a\u0443\u043f\u0438\u0442\u044c \u043d\u0435\u043c\u043d\u043e\u0433\u043e \u0441\u044b\u0440\u0430), \u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0434\u0440\u0443\u0433\u0438\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043a\u0430\u043a \u0435\u0441\u043b\u0438 \u0431\u044b \u043d\u0430\u0448 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0442\u0435\u043b\u044c\u043d\u043e \u043b\u044e\u0431\u0438\u043b \u0441\u044b\u0440.<\/p>\n<p><strong>\u0417\u0430\u0432\u0435\u0440\u0448\u0430\u044f \u0434\u0430\u043d\u043d\u044b\u0439 \u043f\u0440\u0438\u043c\u0435\u0440&nbsp;<\/strong><\/p>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0434\u0430\u0432\u0430\u0439\u0442\u0435 \u043f\u0440\u043e\u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e &#171;\u043c\u0430\u0440\u043a\u0438\u0440\u043e\u0432\u043a\u0430 \u0432\u0441\u0435\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043a\u0430\u043a \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0445&#187; (\u201cMark all as completed\u201d). \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043c\u043e\u0436\u0435\u0442 \u043d\u0430\u0436\u0430\u0442\u044c \u043d\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0441 \u0440\u0430\u0437\u043c\u0435\u0442\u043a\u043e\u0439, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u0432\u0441\u0435 \u0442\u0435\u043a\u0443\u0449\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b.<\/p>\n<pre><code class=\"javascript\">&lt;input   className='toggle-all'   type='checkbox'   onChange={this.toggleAll}   checked={activeTodoCount === 0} \/&gt;<\/code><\/pre>\n<p>\u0418\u0442\u0430\u043a \u0432\u043e\u043f\u0440\u043e\u0441 \u043d\u0430 \u043c\u0438\u043b\u043b\u0438\u043e\u043d \u0434\u043e\u043b\u043b\u0430\u0440\u043e\u0432 \u2014 \u043a\u0430\u043a \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c todo \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0434\u043e \u0442\u043e\u0433\u043e \u043a\u0430\u043a <em>\u043a\u043b\u0438\u043a\u0430\u0442\u044c \u043d\u0430<\/em> <code>.toggle-all<\/code>? \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0442\u0430\u043a\u0443\u044e \u043a\u0430\u043a <code>cy.createDefaultTodos().as('todos')<\/code>, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443 UI \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043f\u043e \u0441\u0443\u0442\u0438, \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u0438\u0440\u0443\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432.<\/p>\n<pre><code class=\"javascript\">\/\/ cypress\/support\/commands.js const TODO_ITEM_ONE = 'buy some cheese' const TODO_ITEM_TWO = 'feed the cat' const TODO_ITEM_THREE = 'book a doctors appointment'  Cypress.Commands.add('createDefaultTodos', function () {   cy.get('.new-todo')     .type(`${TODO_ITEM_ONE}{enter}`)     .type(`${TODO_ITEM_TWO}{enter}`)     .type(`${TODO_ITEM_THREE}{enter}`)     .get('.todo-list li') })<\/code><\/pre>\n<p>\u041c\u044b \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u044d\u0442\u0443 \u043d\u043e\u0432\u0443\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>createDefaultTodos<\/code> \u0434\u043e \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u0431\u043b\u043e\u043a\u0435.<\/p>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/spec.js context('Mark all as completed', function () {   beforeEach(function () {     cy.createDefaultTodos().as('todos')   })    it('should allow me to mark all items as completed', function () {     \/\/ complete all todos     \/\/ we use 'check' instead of 'click'     \/\/ because that indicates our intention much clearer     cy.get('.toggle-all').check()      \/\/ get each todo li and ensure its class is 'completed'     cy.get('@todos').eq(0).should('have.class', 'completed')     cy.get('@todos').eq(1).should('have.class', 'completed')     cy.get('@todos').eq(2).should('have.class', 'completed')   })    \/\/ more tests   \/\/ - should allow me to clear the complete state of all items   \/\/ - complete all checkbox should update state when items are completed \/ cleared })<\/code><\/pre>\n<p>\u0412\u043e\u0442 \u043f\u0435\u0440\u0432\u044b\u0439 \u0442\u0435\u0441\u0442<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/207\/01c\/db9\/20701cdb9f939a5fb8116adb5ef0ab98.gif\" width=\"600\" height=\"264\"><figcaption><\/figcaption><\/figure>\n<p>\u041d\u043e \u0443\u0447\u0442\u0438\u0442\u0435 \u0434\u0432\u0435 \u0432\u0435\u0449\u0438:<\/p>\n<ol>\n<li>\n<p>\u041c\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0447\u0435\u0440\u0435\u0437 UI \u2014 \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u044f \u0442\u043e, \u0447\u0442\u043e \u0434\u0435\u043b\u0430\u043b \u043a\u0430\u0436\u0434\u044b\u0439 \u0442\u0435\u0441\u0442 \u0432 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 &#171;New Todo&#187;.<\/p>\n<\/li>\n<li>\n<p>\u0411\u043e\u043b\u044c\u0448\u0430\u044f \u0447\u0430\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u0440\u0430\u0431\u043e\u0442\u044b \u0442\u0435\u0441\u0442\u0430 \u0437\u0430\u043d\u044f\u0442\u0430 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u0435\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u0430 \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u043e\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430.<\/p>\n<\/li>\n<\/ol>\n<p>\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u0430\u0436\u0435\u043d \u2014 \u043d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u0435 \u0438\u0437-\u0437\u0430 \u0432\u043d\u0435\u0441\u0435\u043d\u0438\u044f \u0442\u0440\u0435\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u0435\u0440\u0435\u0434 \u043a\u0430\u0436\u0434\u044b\u043c \u0442\u0435\u0441\u0442\u043e\u043c. \u0422\u0440\u0438 \u0442\u0435\u0441\u0442\u0430 \u0432 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u043c \u0432\u044b\u0448\u0435 \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u0435 &#171;\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e&#187;&nbsp; (\u201cMark all as completed\u201d)&nbsp; \u043e\u0431\u044b\u0447\u043d\u043e \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0442 \u043e\u0442 4 \u0434\u043e 5 \u0441\u0435\u043a\u0443\u043d\u0434.<\/p>\n<h3>App Actions<\/h3>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434<\/p>\n<p><em>\u041e\u043a\u043e\u043d\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 \u0438\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434 \u044d\u0442\u043e\u0439 \u0437\u0430\u043f\u0438\u0441\u0438 \u0431\u043b\u043e\u0433\u0430 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0439\u0442\u0438 \u0432 <\/em><a href=\"https:\/\/github.com\/cypress-io\/cypress-example-recipes#application-actions\"><em><u>Application Actions<\/u><\/em><\/a><em>. \u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043e\u0434\u043d\u0438 \u0438 \u0442\u0435 \u0436\u0435 \u0442\u0435\u0441\u0442\u044b, \u0440\u0435\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432 \u0440\u0430\u0437\u043d\u044b\u0445 \u0441\u0442\u0438\u043b\u044f\u0445, \u0432\u043a\u043b\u044e\u0447\u0430\u044f Page Object \u0438 App Actions \u0432 repo <\/em><a href=\"https:\/\/github.com\/bahmutov\/test-todomvc-using-app-actions\"><em><u>bahmutov\/test-todomvc-using-app-actions.<\/u><\/em><\/a><\/p>\n<p>\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u0441\u0435\u0431\u0435, \u0447\u0442\u043e \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u043d\u043e \u0432\u043d\u043e\u0441\u0438\u0442\u044c \u043d\u043e\u0432\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0438\u0437 \u043d\u0430\u0448\u0435\u0433\u043e \u0442\u0435\u0441\u0442\u0430. \u0422\u0430\u043a \u043a\u0430\u043a \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 Cypress \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u043e\u0432\u0430\u0442\u044c \u0441 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u044b\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c, \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e. \u0412\u0441\u0435, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c, \u044d\u0442\u043e \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0441\u0441\u044b\u043b\u043e\u0447\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0443 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u0438\u0432 \u0435\u0435, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0443 window.<\/p>\n<pre><code class=\"javascript\">\/\/ app.jsx code var model = new app.TodoModel('react-todos');  if (window.Cypress) {   window.model = model }<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u043c\u044b \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u043c \u0441\u0441\u044b\u043b\u043e\u0447\u043d\u0443\u044e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u0443\u044e model \u043a\u0430\u043a \u043e\u0431\u044a\u0435\u043a\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f window \u044d\u0442\u043e \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0432\u0430\u0435\u0442 \u043d\u0430\u0448\u0438\u043c \u0442\u0435\u0441\u0442\u0430\u043c \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>model.addTodo<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0435\u0441\u0442\u044c \u0432 <code>js\/todoModel.js<\/code>.<\/p>\n<pre><code class=\"javascript\">\/\/ js\/todoModel.js \/\/ Model: keeps all todos and has methods to act on them app.TodoModel = function (key) {   this.key = key   this.todos = Utils.store(key)   this.onChanges = [] } app.TodoModel.prototype.addTodo = function (title) {   this.todos = this.todos.concat({     id: Utils.uuid(),     title: title,     completed: false   });    this.inform(); }; app.TodoModel.prototype.inform = ... app.TodoModel.prototype.toggleAll = ... \/\/ other methods<\/code><\/pre>\n<p>\u0412\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443 Page Object, \u0434\u043b\u044f \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f&nbsp;<code>todos<\/code> \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a <code>cy.createDefaultTodos().as('todos')<\/code> \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>model.addTodo<\/code>, \u0447\u0442\u043e\u0431\u044b \u0441\u0440\u0430\u0437\u0443 \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442\u044b, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u201capi\u201d \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0412 \u043a\u043e\u0434\u0435 \u043d\u0438\u0436\u0435 \u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e <a href=\"https:\/\/on.cypress.io\/window\">cy.window()<\/a> , \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u043e\u043a\u043d\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0437\u0430\u0442\u0435\u043c \u0441\u0432\u043e\u0439\u0441\u0442\u0432\u043e&nbsp;<code>model<\/code> \u0438 \u0437\u0430\u0442\u0435\u043c <a href=\"https:\/\/on.cypress.io\/invoke\">.invoke()<\/a> , \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>addTodo<\/code> \u043d\u0430 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043c\u043e\u0434\u0435\u043b\u0438.<\/p>\n<pre><code class=\"javascript\">beforeEach(function () {   cy.window().its('model').invoke('addTodo', TODO_ITEM_ONE)   cy.window().its('model').invoke('addTodo', TODO_ITEM_TWO)   cy.window().its('model').invoke('addTodo', TODO_ITEM_THREE)   cy.get('.todo-list li').as('todos') })<\/code><\/pre>\n<p>\u041f\u0440\u0438 \u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u0439 \u0432\u044b\u0448\u0435 \u0441\u0445\u0435\u043c\u0435 \u043d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442 \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435 \u2014 \u0432\u0441\u0435 \u0442\u0440\u0438 \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u044e\u0442\u0441\u044f \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c \u0437\u0430 1 \u0441\u0435\u043a\u0443\u043d\u0434\u0443, \u0443\u0436\u0435 \u0432 3 \u0440\u0430\u0437\u0430 \u0431\u044b\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043c \u0440\u0430\u043d\u044c\u0448\u0435. \u041d\u043e \u0434\u0430\u0436\u0435 \u0432\u044b\u0448\u0435\u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0439 \u043a\u043e\u0434 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u0435\u0435, \u0447\u0435\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u2014 \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u043c\u0430\u043d\u0434 Cypress \u0434\u043b\u044f \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u0447\u0442\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u043c\u0443 \u0432\u0440\u0435\u043c\u0435\u043d\u0438. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c <code>TodoModel.prototype.addTodo<\/code>, \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043e\u0434\u043d\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e.<\/p>\n<pre><code class=\"javascript\">\/\/ js\/todoModel.js app.TodoModel.prototype.addTodo = function (...titles) {   titles.forEach(title =&gt; {     this.todos = this.todos.concat({       id: Utils.uuid(),       title: title,       completed: false     });   })    this.inform(); };<\/code><\/pre>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/spec.js beforeEach(function () {   cy.window().its('model').invoke('addTodo',     TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE)   cy.get('.todo-list li').as('todos') })<\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/0a7\/699\/ed9\/0a7699ed9c951ef5949b1e9f6ba245d1.gif\" width=\"600\" height=\"264\"><figcaption><\/figcaption><\/figure>\n<p>\u0412\u044b \u0437\u0430\u043c\u0435\u0442\u0438\u043b\u0438, \u0447\u0442\u043e \u043d\u0430\u0448 \u0442\u0435\u0441\u0442 \u0441\u0442\u0430\u043b \u043b\u0443\u0447\u0448\u0435? \u041c\u044b \u041d\u0415 \u0438\u0437\u043c\u0435\u043d\u0438\u043b\u0438 \u043a\u043e\u0434 \u0442\u0435\u0441\u0442\u0430, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u043c\u044b \u0443\u043b\u0443\u0447\u0448\u0438\u043b\u0438 \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0435 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438\u0437 \u043d\u0430\u0448\u0438\u0445 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432, \u043c\u044b \u043e\u0431\u044f\u0437\u0430\u043d\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043b\u0443\u0447\u0448\u0435 \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043d\u0430\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432! \u041d\u0430\u0448\u0438 \u0443\u0441\u0438\u043b\u0438\u044f \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043f\u0440\u0438\u0432\u043e\u0434\u044f\u0442 \u043a \u043f\u0440\u043e\u044f\u0441\u043d\u0435\u043d\u0438\u044e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043c\u043e\u0434\u0435\u043b\u0438, \u0434\u0435\u043b\u0430\u044f \u0435\u0435 \u0431\u043e\u043b\u0435\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c\u043e\u0439, \u043b\u0443\u0447\u0448\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0438 \u0434\u0435\u043b\u0430\u044f \u0435\u0435 \u0431\u043e\u043b\u0435\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043a\u043e\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<p>\u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c App Actions \u0438\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 DevTools \u043d\u0430\u043f\u0440\u044f\u043c\u0443\u044e, \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u044f \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u0432 &#171;Your app&#187;, \u0441\u043c. \u0441\u043d\u0438\u043c\u043e\u043a \u044d\u043a\u0440\u0430\u043d\u0430 \u043d\u0438\u0436\u0435.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/9b7\/a24\/f18\/9b7a24f18fc0ec075b631bb888a77489\" width=\"715\" height=\"307\"><figcaption><\/figcaption><\/figure>\n<h3>\u041f\u0440\u043e\u0441\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u0438<\/h3>\n<p>\u041c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u043d\u0435\u0441\u0442\u0438 \u043b\u043e\u0433\u0438\u043a\u0443 app actions \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0437\u0430\u043c\u0435\u043d\u0438\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0434\u043b\u044f \u043c\u0430\u043d\u0438\u043f\u0443\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041d\u043e \u044f \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u044e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043d\u0435\u0431\u043e\u043b\u044c\u0448\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u043d\u043e\u0433\u043e\u043a\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f, \u0430 \u043d\u0435 \u043f\u0440\u0438\u043a\u0440\u0435\u043f\u043b\u044f\u0442\u044c \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043c\u0435\u0442\u043e\u0434\u044b \u043a \u043e\u0431\u044a\u0435\u043a\u0442\u0443 <code>cy <\/code>.<\/p>\n<pre><code class=\"javascript\">const addDefaultTodos = () =&gt; {   cy.window().its('model').invoke('addTodo',     TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE)   cy.get('.todo-list li').as('todos') }  beforeEach(addDefaultTodos)<\/code><\/pre>\n<p>\u0422\u0430\u043a \u043a\u0430\u043a Cypress \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0431\u0430\u043d\u0434\u043b\u0435\u0440, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c <code>addDefaultTodos<\/code> \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u0441 \u0443\u0442\u0438\u043b\u0438\u0442\u0430\u043c\u0438 \u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c&nbsp; require \u0438\u043b\u0438 import \u0434\u0438\u0440\u0435\u043a\u0442\u0438\u0432\u044b, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c\u0441\u044f \u0438\u043c\u0438 \u0432 spec-\u0444\u0430\u0439\u043b\u0435. \u0410 \u0442\u0430\u043a\u0436\u0435 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c <code>addDefaultTodos<\/code>, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0441\u043e\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 JSDoc, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043a\u0440\u0430\u0441\u0438\u0432\u043e\u0435 \u043f\u0440\u043e\u0434\u0443\u043c\u0430\u043d\u043d\u043e\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0435 \u043a\u043e\u0434\u0430 \u0432 \u043d\u0430\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0444\u0430\u0439\u043b\u0430\u0445.<\/p>\n<pre><code class=\"javascript\">\/\/ utils.js const TODO_ITEM_ONE = 'buy some cheese' const TODO_ITEM_TWO = 'feed the cat' const TODO_ITEM_THREE = 'book a doctors appointment'  \/**  * Creates default todo items using application action.  * @example  *  import { addDefaultTodos } from '.\/utils'  *  beforeEach(addDefaultTodos)  *\/ export const addDefaultTodos = () =&gt; {   cy.window().its('model').invoke('addTodo',     TODO_ITEM_ONE, TODO_ITEM_TWO, TODO_ITEM_THREE)   cy.get('.todo-list li').as('todos') }<\/code><\/pre>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/1ee\/04e\/498\/1ee04e498e06f96e5c5cd04cb12b190a.png\" width=\"717\" height=\"295\"><figcaption><\/figcaption><\/figure>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 app actions \u2014 \u044d\u0442\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 JavaScript-\u0444\u0443\u043d\u043a\u0446\u0438\u0439, \u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u0444\u0443\u043d\u043a\u0446\u0438\u0439 \u2014 \u043f\u0440\u043e\u0441\u0442\u043e.<\/p>\n<h3>\u041f\u0440\u0438\u043c\u0435\u0440 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u0441\u0442\u0432\u0430&nbsp;<\/h3>\n<p>\u0415\u0441\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u043f\u0440\u0438\u043c\u0435\u0440 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 TodoMVC, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u0438 \u0437\u0430\u0434\u0430\u043d\u0438\u044f \u043d\u0430\u0447\u0430\u043b\u044c\u043d\u044b\u0445 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0439 \u0438 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439. \u0422\u0435\u0441\u0442 \u043d\u0430 \u043f\u043e\u0441\u0442\u043e\u044f\u043d\u0441\u0442\u0432\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u0434\u0432\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430, \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u0442 \u043d\u0430 \u043e\u0434\u0438\u043d \u0438\u0437 \u043d\u0438\u0445, \u0437\u0430\u0442\u0435\u043c \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0436\u0430\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0443. \u0414\u0432\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u0434\u043e\u043b\u0436\u043d\u044b \u0431\u044b\u0442\u044c \u0442\u0430\u043c \u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043e. \u041e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0442\u0435\u0441\u0442 \u043d\u0430 Cypress \u0434\u0435\u043b\u0430\u0435\u0442 \u0432\u0441\u0435 \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441.<\/p>\n<pre><code class=\"javascript\">context('Persistence', function () {   it('should persist its data', function () {     \/\/ mimicking TodoMVC tests     \/\/ by writing out this function     function testState () {       cy.get('@firstTodo').should('contain', TODO_ITEM_ONE)         .and('have.class', 'completed')       cy.get('@secondTodo').should('contain', TODO_ITEM_TWO)         .and('not.have.class', 'completed')     }      cy.createTodo(TODO_ITEM_ONE).as('firstTodo')     cy.createTodo(TODO_ITEM_TWO).as('secondTodo')     cy.get('@firstTodo').find('.toggle').check()     .then(testState)      .reload()     .then(testState)   }) })<\/code><\/pre>\n<p>\u0424\u0443\u043d\u043a\u0446\u0438\u044f \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a\u0430 <code>testState<\/code> \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u043e\u0431\u0430 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430 \u2014 \u043f\u0435\u0440\u0432\u044b\u0439 \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d, \u0430 \u0432\u0442\u043e\u0440\u043e\u0439 \u2014 \u043d\u0435\u0442. \u041f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u043c \u043f\u0435\u0440\u0435\u0434 \u043f\u0435\u0440\u0435\u0437\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438 \u043f\u043e\u0441\u043b\u0435.<\/p>\n<p>\u041d\u043e \u0437\u0430\u0447\u0435\u043c \u043c\u044b \u0432\u043e\u043e\u0431\u0449\u0435 \u0441\u043e\u0437\u0434\u0430\u0435\u043c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u0438 \u0437\u0430\u0447\u0435\u043c \u043d\u0430\u0436\u0438\u043c\u0430\u0435\u043c \u043d\u0430 \u043f\u0435\u0440\u0432\u044b\u0435, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u0438\u0445 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435? \u041c\u044b \u0437\u043d\u0430\u0435\u043c, \u0447\u0442\u043e \u044d\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442! \u0423 \u043d\u0430\u0441 \u0435\u0441\u0442\u044c \u0435\u0449\u0435 \u043e\u0434\u0438\u043d \u0442\u0435\u0441\u0442, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0443\u0436\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0434\u043b\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430. \u042d\u0442\u043e\u0442 \u0442\u0435\u0441\u0442 \u043d\u0430\u0437\u044b\u0432\u0430\u043b\u0441\u044f <code>Item \u2014 should allow me to mark items as complete<\/code>, \u0438 \u043e\u043d \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u043f\u043e\u0447\u0442\u0438 \u0432 \u0442\u043e\u0447\u043d\u043e\u0441\u0442\u0438 \u0442\u0430\u043a \u0436\u0435:<\/p>\n<pre><code class=\"javascript\">context('Item', function () {   it('should allow me to mark items as complete', function () {     cy.createTodo(TODO_ITEM_ONE).as('firstTodo')     cy.createTodo(TODO_ITEM_TWO).as('secondTodo')      cy.get('@firstTodo').find('.toggle').check()     cy.get('@firstTodo').should('have.class', 'completed')      cy.get('@secondTodo').should('not.have.class', 'completed')     cy.get('@secondTodo').find('.toggle').check()      cy.get('@firstTodo').should('have.class', 'completed')     cy.get('@secondTodo').should('have.class', 'completed')   }) })<\/code><\/pre>\n<p>\u041c\u044b \u041d\u0415 \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u0434\u043b\u044f \u043e\u0434\u043d\u0438\u0445 \u0438 \u0442\u0435\u0445 \u0436\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c. \u041c\u044b \u041d\u0415 \u0434\u043e\u043b\u0436\u043d\u044b \u043f\u043e\u0432\u0442\u043e\u0440\u044f\u0442\u044c \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u0441 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u043c, \u0434\u0430\u0436\u0435 \u0435\u0441\u043b\u0438 \u043c\u044b \u0441\u043b\u0435\u0434\u0443\u0435\u043c \u043b\u0443\u0447\u0448\u0438\u043c \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430\u043c \u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u0438, \u0430 \u043d\u0435 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044e \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u0438\u0434\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440\u043e\u0432 \u0438 \u0445\u043e\u0440\u043e\u0448\u0435\u0439 \u0431\u0438\u0431\u043b\u0438\u043e\u0442\u0435\u043a\u0438 \u043f\u043e\u043c\u043e\u0449\u043d\u0438\u043a\u0430, \u0442\u0430\u043a\u043e\u0439 \u043a\u0430\u043a <a href=\"https:\/\/github.com\/kentcdodds\/cypress-testing-library\"><u>cypress-testing-library <\/u><\/a>&#8212; \u0432\u0441\u0435 \u0440\u0430\u0432\u043d\u043e \u043f\u0440\u0438\u0432\u044f\u0437\u044b\u0432\u0430\u0435\u0442 \u043d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043a \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0438 \u044d\u0442\u043e \u043c\u043e\u0436\u0435\u0442 \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0441\u044f.<\/p>\n<p>\u0412\u043e\u0442 \u043d\u0430\u0448\u0430 \u0438\u0441\u0445\u043e\u0434\u043d\u0430\u044f \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u044f \u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430.<\/p>\n<pre><code>cy.createTodo(TODO_ITEM_ONE).as('firstTodo') cy.createTodo(TODO_ITEM_TWO).as('secondTodo') cy.get('@firstTodo').find('.toggle').check()<\/code><\/pre>\n<p>\u0410 \u0441\u0435\u0439\u0447\u0430\u0441 \u043c\u044b \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u043c \u0442\u043e, \u043a\u0430\u043a \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u0434\u0435\u043b\u0430\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c app actions. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c <code>addTodo<\/code> \u0434\u043b\u044f \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0438 \u0432\u0441\u0435 \u0435\u0449\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0444\u043b\u0430\u0436\u043e\u043a <code>class=\"toggle\"<\/code>, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u043f\u0435\u0440\u0432\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442, \u043a\u0430\u043a \u00ab\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0439\u00bb.<\/p>\n<pre><code class=\"javascript\">\/\/ spec.js import { addTodos } from '.\/utils';  addTodos(TODO_ITEM_ONE, TODO_ITEM_TWO) cy.get('.todo-list li').eq(0).find('.toggle').check()<\/code><\/pre>\n<p>\u0414\u0430\u043b\u0435\u0435 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u043e\u0434\u0435\u043b\u0438 \u0432 \u043c\u0435\u0442\u043e\u0434\u0430\u0445 <code>todoModel.js<\/code>, \u0447\u0442\u043e\u0431\u044b \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043a\u0430\u043a \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043e\u0431\u044a\u0435\u043a\u0442 <code>todo<\/code>.<\/p>\n<pre><code class=\"javascript\">app.TodoModel.prototype.toggle = function (todoToToggle) {   this.todos = this.todos.map(function (todo) {     return todo !== todoToToggle ?       todo :       Utils.extend({}, todo, {completed: !todo.completed});   });    this.inform(); };<\/code><\/pre>\n<p>\u041c\u043e\u0436\u0435\u043c \u043b\u0438 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043c\u0435\u0442\u043e\u0434 <code>model.toggle<\/code>, \u0447\u0442\u043e\u0431\u044b \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u0444\u043b\u0430\u0433 <code>completed<\/code>? Cypress \u043c\u043e\u0436\u0435\u0442 \u0432\u0441\u0435 \u0447\u0442\u043e \u0443\u0433\u043e\u0434\u043d\u043e, \u0447\u0442\u043e \u0434\u043e\u0441\u0442\u0443\u043f\u043d\u043e \u0432 DevTools. \u0418\u0442\u0430\u043a, \u0435\u0449\u0435 \u0440\u0430\u0437, \u043c\u044b \u043e\u0442\u043a\u0440\u044b\u0432\u0430\u0435\u043c DevTools \u0438\u0437 <code>test runner<\/code>, \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0430\u0435\u043c\u0441\u044f \u043d\u0430 \u201cYour App\u201d \u0438 \u043f\u0440\u043e\u0431\u0443\u0435\u043c. \u0417\u0430\u043c\u0435\u0442\u044c\u0442\u0435, \u043a\u0430\u043a \u043f\u043e\u0441\u043b\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u0430, \u044f \u0432\u044b\u0437\u0432\u0430\u043b <code>model.toggle(model.todos[0])<\/code> \u0438 \u043f\u0435\u0440\u0432\u044b\u0439 \u043e\u0431\u044a\u0435\u043a\u0442 \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0432\u043d\u043e\u0432\u044c \u0441\u0442\u0430\u043b \u00ab\u043d\u0435\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u043c\u00bb.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/46a\/5a8\/357\/46a5a83574360deb1ba2e4ed53cb8a94\" width=\"667\" height=\"429\"><figcaption><\/figcaption><\/figure>\n<p>\u041d\u0430\u043f\u0438\u0448\u0435\u043c \u0444\u0443\u043d\u043a\u0446\u0438\u044e \u0443\u0442\u0438\u043b\u0438\u0442\u043e\u0432 \u0434\u043b\u044f \u0432\u044b\u0437\u043e\u0432\u0430 app actions \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e toggle. \u0414\u043b\u044f \u043d\u0430\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u043c\u044b, \u043d\u0430\u0432\u0435\u0440\u043d\u043e\u0435, \u0445\u043e\u0442\u0438\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043d\u0435 \u043f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u043e\u0439, \u0430 \u043f\u043e \u0438\u043d\u0434\u0435\u043a\u0441\u0443.<\/p>\n<pre><code class=\"javascript\">\/**  * Toggle given todo item. Returns chain so you can attach more Cypress commands  * @param {number} k index of the todo item to toggle, 0 - first item  * @example  import { addTodos, toggle } from '.\/utils'  it('completes an item', () =&gt; {    addTodos('first')    toggle(0)  })  *\/ export const toggle = (k = 0) =&gt;   cy.window().its('model')   .then(model =&gt; {     expect(k, 'check item index').to.be.lessThan(model.todos.length)     model.toggle(model.todos[k])   })<\/code><\/pre>\n<p>\u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u044b \u043c\u044b \u043c\u043e\u0433\u043b\u0438 \u0431\u044b \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0444\u0443\u043d\u043a\u0446\u0438\u044e toggle \u0432 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0447\u0442\u043e\u0431\u044b \u0432\u0437\u044f\u0442\u044c \u0438\u043d\u0434\u0435\u043a\u0441 \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u0430. \u0412\u0438\u0434\u0438\u0442\u0435, \u043a\u0430\u043a \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0442\u0435\u043f\u0435\u0440\u044c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f &#171;\u043a\u043b\u0438\u0435\u043d\u0442\u043e\u043c&#187; \u043a\u043e\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u0432\u043b\u0438\u044f\u0442\u044c \u043d\u0430 \u0430\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0443 \u0438 \u0434\u0438\u0437\u0430\u0439\u043d \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f?<\/p>\n<p>\u041d\u0430\u0448 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u043d\u044b\u0439 \u0442\u0435\u0441\u0442 \u0441\u043e\u0437\u0434\u0430\u0435\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u043f\u0435\u0440\u0432\u044b\u0439, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0431\u044b\u0441\u0442\u0440\u043e.<\/p>\n<pre><code class=\"javascript\">context('Persistence', function () {   \/\/ mimicking TodoMVC tests   \/\/ by writing out this function   function testState () {     cy.get('.todo-list li').eq(0)       .should('contain', TODO_ITEM_ONE).and('have.class', 'completed')     cy.get('.todo-list li').eq(1)       .should('contain', TODO_ITEM_TWO).and('not.have.class', 'completed')   }    it('should persist its data', function () {     addTodos(TODO_ITEM_ONE, TODO_ITEM_TWO)     toggle(0)     .then(testState)      .reload()     .then(testState)   }) })<\/code><\/pre>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u044f \u043c\u043e\u0433\u0443 \u043f\u0440\u043e\u0439\u0442\u0438 \u0447\u0435\u0440\u0435\u0437 \u0434\u0440\u0443\u0433\u0438\u0435 \u0442\u0435\u0441\u0442\u044b \u0438 \u0437\u0430\u043c\u0435\u043d\u0438\u0442\u044c \u043a\u0430\u0436\u0434\u044b\u0439&nbsp; <code>cy.get('.todo-list li').eq(k).find('.toggle').check() \u0441 toggle(k)<\/code>. \u0411\u044b\u0441\u0442\u0440\u0435\u0435 \u0438 \u043d\u0430\u0434\u0435\u0436\u043d\u0435\u0435.<\/p>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u043c\u0430\u0440\u0448\u0440\u0443\u0442\u044b \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432, \u0447\u0442\u043e\u0431\u044b \u041d\u0415 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043f\u0440\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f app actions. \u0412 \u0442\u043e \u0436\u0435 \u0432\u0440\u0435\u043c\u044f \u043c\u044b \u043a\u043b\u0438\u043a\u0430\u0435\u043c \u043f\u043e \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u043c \u0441\u0441\u044b\u043b\u043a\u0430\u043c, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u044b \u0442\u0435\u0441\u0442\u0438\u0440\u0443\u0435\u043c \u043a\u0430\u043a \u0435\u0441\u0442\u044c \u2014 \u0438 \u0442\u0435\u0441\u0442 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442, \u0447\u0442\u043e \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441 \u0442\u0435\u043a\u0441\u0442\u043e\u043c &#171;Active&#187; \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442!<\/p>\n<pre><code class=\"javascript\">context('Routing', function () {   beforeEach(addDefaultTodos) \/\/ app action    it('should allow me to display active items', function () {     toggle(1) \/\/ app action     \/\/ the UI feature we are actually testing - the \"Active\" link     cy.get('.filters').contains('Active').click()     cy.get('@todos').eq(0).should('contain', TODO_ITEM_ONE)     cy.get('@todos').eq(1).should('contain', TODO_ITEM_THREE)   })   \/\/ more tests })<\/code><\/pre>\n<p>\u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435, \u0447\u0442\u043e \u0432\u0441\u044f\u043a\u0438\u0439 \u0440\u0430\u0437, \u043a\u043e\u0433\u0434\u0430 \u0432\u044b \u0437\u0430\u043a\u0430\u043d\u0447\u0438\u0432\u0430\u0435\u0442\u0435 \u043f\u0438\u0441\u0430\u0442\u044c \u0447\u0443\u0442\u044c \u0431\u043e\u043b\u0435\u0435 \u0434\u043b\u0438\u043d\u043d\u044b\u0439 utility \u0442\u0435\u0441\u0442, \u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, toggle, \u044d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u0438\u043d\u0434\u0438\u043a\u0430\u0442\u043e\u0440 \u0442\u043e\u0433\u043e, \u0447\u0442\u043e, \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e, \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0438\u0437\u043c\u0435\u043d\u0438\u0442\u044c \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0432\u043c\u0435\u0441\u0442\u043e \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u043f\u0438\u0441\u0430\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0445 \u043a\u043e\u0434\u043e\u0432!<\/p>\n<pre><code class=\"javascript\">\/\/ hmm, maybe we need to add a `model.toggleIndex()` method? export const toggle = (k = 0) =&gt;   cy.window().its('model')     .then(model =&gt; {       expect(k, 'check item index').to.be.lessThan(model.todos.length)       model.toggle(model.todos[k])     })<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0432\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u0435 \u043c\u0435\u0442\u043e\u0434 <code>model.toggleIndex<\/code> \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u0442\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 , \u0431\u0443\u0434\u0435\u0442 \u043b\u0435\u0433\u0447\u0435 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0441\u044f, \u0438 \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u043b\u0443\u0447\u0448\u0435 \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0432 \u0431\u0443\u0434\u0443\u0449\u0435\u043c. \u0422\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0442\u043e\u0436\u0435 \u0431\u0443\u0434\u0435\u0442 \u0443\u043f\u0440\u043e\u0449\u0435\u043d.<\/p>\n<h3>DRY \u043a\u043e\u0434\u043e\u0432\u044b\u0439 \u0442\u0435\u0441\u0442<\/h3>\n<p>\u041a\u0430\u0436\u0434\u044b\u0439 \u0431\u043b\u043e\u043a \u0442\u0435\u0441\u0442\u043e\u0432 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0435. \u041c\u044b \u043c\u043e\u0436\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u044d\u0442\u043e \u0432 \u043d\u0430\u0448\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0430\u0445 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e app actions. \u0421\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432, \u043f\u0435\u0440\u0435\u0434\u0430\u043d\u043d\u044b\u0435 \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u0431\u043b\u043e\u043a, \u0431\u0443\u0434\u0443\u0442 \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u044b \u0432 \u044d\u0442\u043e\u043c \u0431\u043b\u043e\u043a\u0435. \u042d\u0442\u043e \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b \u043b\u043e\u043a\u0430\u043b\u0438\u0437\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438 \u043f\u0440\u0438 \u043a\u0430\u0436\u0434\u043e\u043c \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f. \u0412\u0441\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0435 \u0431\u043b\u043e\u043a\u0438 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c app actions \u0438 \u043d\u0435 \u043d\u0443\u0436\u0434\u0430\u044e\u0442\u0441\u044f \u0432 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u0435. \u0412 \u043f\u0440\u0438\u043c\u0435\u0440\u0435 \u043d\u0438\u0436\u0435 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0435\u0442\u044c \u043d\u0430 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b&nbsp; <code>NEWTODO<\/code><em> \u0438 <\/em><code>TOGGLEALL<\/code>.<\/p>\n<pre><code class=\"javascript\">describe('TodoMVC', function () {   \/\/ testing item input   context('New Todo', function () {     \/\/ selector to enter new todo item is private to these tests     const NEW_TODO = '.new-todo'      it('should allow me to add todo items', function () {       cy.get(NEW_TODO)         .type(TODO_ITEM_ONE)         .type('{enter}')       \/\/ more commands     })     \/\/ more tests that use NEW_TODO selector   })    \/\/ testing toggling all items   context('Mark all as completed', function () {     \/\/ selector to toggle all items is private to these tests     const TOGGLE_ALL = '.toggle-all'      beforeEach(addDefaultTodos)      it('should allow me to mark all items as completed', function () {       cy.get(TOGGLE_ALL).check()       \/\/ more commands     })     \/\/ more tests that use TOGGLE_ALL selector   }) })<\/code><\/pre>\n<p>\u0422\u0435\u0441\u0442\u044b \u0432\u044b\u0448\u0435 \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u044e\u0442 \u0447\u0442\u043e \u043a\u0430\u0436\u0434\u044b\u0439 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440&nbsp; \u043f\u0440\u0438\u0432\u0430\u0442\u0435\u043d \u0432 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u043d\u043e\u043c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u0431\u043b\u043e\u043a\u0435. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 <code>const NEWTODO = '.new-todo'<\/code><em> \u043f\u0440\u0438\u0432\u0430\u0442\u043d\u044b\u0439 \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u0431\u043b\u043e\u043a\u0435 &#171;New Todo&#187;, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 <\/em><code>const TOGGLEALL = '.toggle-all'<\/code> \u0432 \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u043c \u0431\u043b\u043e\u043a\u0435 &#171;Mark all as completed&#187;. \u0414\u043b\u044f \u0434\u0440\u0443\u0433\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0447\u0442\u043e\u0431\u044b \u0434\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u043a\u0430\u043a\u0438\u0435-\u043b\u0438\u0431\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0438\u043b\u0438 \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u0432\u0441\u0435 \u043a\u0430\u043a \u00ab\u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043d\u043e\u0435\u00bb \u2014 \u0432\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c app actions .<\/p>\n<p>\u041d\u043e \u0432 \u043d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0441\u0438\u0442\u0443\u0430\u0446\u0438\u044f\u0445 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u0445\u043e\u0442\u0435\u0442\u044c \u043f\u043e\u0434\u0435\u043b\u0438\u0442\u044c\u0441\u044f \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u043e\u043c. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u043d\u043e\u0433\u0438\u0435 \u0442\u0435\u0441\u0442\u044b \u0438\u0437 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u0431\u043b\u043e\u043a\u043e\u0432 \u043c\u043e\u0433\u0443\u0442 \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u0438\u0442\u044c\u0441\u044f, \u0447\u0442\u043e\u0431\u044b \u0441\u043e\u0431\u0440\u0430\u0442\u044c \u0432\u0441\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b Todo \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435, \u0438 \u043e\u0442 \u044d\u0442\u043e\u0433\u043e \u043d\u0438\u043a\u0443\u0434\u0430 \u043d\u0435 \u0434\u0435\u0442\u044c\u0441\u044f. \u041c\u044b \u0432\u0441\u0435 \u0435\u0449\u0435 \u043c\u043e\u0436\u0435\u043c \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 \u0431\u0435\u0437 \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u044f <code>page objects<\/code> \u0442\u0430\u043a\u0438\u0445 \u043a\u0430\u043a \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 <code>ALL_ITEMS<\/code>.<\/p>\n<pre><code class=\"javascript\">describe('TodoMVC', function () {   \/\/ common selector used across many tests   const ALL_ITEMS = '.todo-list li'    context('New Todo', function () {     const NEW_TODO = '.new-todo'      it('should allow me to add todo items', function () {       cy.get(NEW_TODO)         .type(TODO_ITEM_ONE)         .type('{enter}')       cy.get(ALL_ITEMS)         .eq(0)         .find('label')         .should('contain', TODO_ITEM_ONE)     })     \/\/ more tests   })    context('Mark all as completed', function () {     const TOGGLE_ALL = '.toggle-all'      beforeEach(addDefaultTodos)      it('should allow me to mark all items as completed', function () {       cy.get(TOGGLE_ALL).check()       cy.get(ALL_ITEMS)         .eq(0)         .should('have.class', 'completed')     })     \/\/ more tests   }) })<\/code><\/pre>\n<p>\u0412 \u043f\u0440\u0438\u043c\u0435\u0440\u0430\u0445 \u0432\u044b\u0448\u0435 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 <code>const ALL_ITEMS = '.todo-list li'<\/code> \u0432 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430\u0445. \u042f \u0434\u0430\u0436\u0435 \u043f\u0440\u0435\u0434\u043f\u043e\u0447\u0438\u0442\u0430\u044e \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u0443\u044e utility \u0444\u0443\u043d\u043a\u0446\u0438\u044e <code>allItems<\/code>, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0440\u043d\u0443\u0442\u044c \u0432\u0441\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0441\u043f\u0438\u0441\u043a\u0430, \u0430 \u043d\u0435 \u0440\u0430\u0437\u0434\u0435\u043b\u044f\u0442\u044c \u043a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u0443 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u0430.<\/p>\n<pre><code class=\"javascript\">describe('TodoMVC', function () {   const ALL_ITEMS = '.todo-list li'    \/**    * Returns all todo items    *\/   const allItems = () =&gt; cy.get(ALL_ITEMS)    context('New Todo', function () {     const NEW_TODO = '.new-todo'     it('should allow me to add todo items', function () {       cy.get(NEW_TODO)         .type(TODO_ITEM_ONE)         .type('{enter}')       allItems()         .eq(0)         .find('label')         .should('contain', TODO_ITEM_ONE)     })     \/\/ more tests   })    context('Mark all as completed', function () {     const TOGGLE_ALL = '.toggle-all'      beforeEach(addDefaultTodos)      it('should allow me to mark all items as completed', function () {       cy.get(TOGGLE_ALL).check()       allItems()         .eq(0)         .should('have.class', 'completed')     })     \/\/ more tests   }) })<\/code><\/pre>\n<p>\u041f\u043e \u043c\u0435\u0440\u0435 \u0440\u043e\u0441\u0442\u0430 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0442\u0435\u0441\u0442\u043e\u0432 \u043c\u044b, \u0435\u0441\u0442\u0435\u0441\u0442\u0432\u0435\u043d\u043d\u043e, \u043c\u043e\u0436\u0435\u043c \u0440\u0430\u0437\u0434\u0435\u043b\u0438\u0442\u044c \u043d\u0430\u0448 \u0435\u0434\u0438\u043d\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 spec-\u0444\u0430\u0439\u043b \u043d\u0430 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e spec-\u0444\u0430\u0439\u043b\u043e\u0432. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u0438\u043b\u043e \u0431\u044b \u043d\u0430\u0448\u0435\u043c\u0443 \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u043d\u0435\u043f\u0440\u0435\u0440\u044b\u0432\u043d\u043e\u0439 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0438 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c \u0432\u0441\u0435 \u0442\u0435\u0441\u0442\u044b&nbsp; <a href=\"https:\/\/on.cypress.io\/parallelization\">\u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e<\/a>.<\/p>\n<p>\u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c utility \u0444\u0443\u043d\u043a\u0446\u0438\u044e allItems \u0438 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440 ALL_ITEMS \u0432 \u043e\u0431\u0449\u0438\u0439 \u0444\u0430\u0439\u043b \u0443\u0442\u0438\u043b\u0438\u0442\u0430 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c <code>allItems<\/code> \u0438\u0437 \u0432\u0441\u0435\u0445 specs \u0444\u0430\u0439\u043b\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u044b.<\/p>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/utils.js const ALL_ITEMS = '.todo-list li'  \/**  * Returns all todo items  * @example     import {allItems} from '.\/utils'     allItems().should('not.exist')  *\/ export const allItems = () =&gt; cy.get(ALL_ITEMS)  \/\/ cypress\/integration\/spec.js import { allItems } from '.\/utils'  describe('TodoMVC', function () {   context('New Todo', function () {     const NEW_TODO = '.new-todo'      it('should allow me to add todo items', function () {       cy.get(NEW_TODO)         .type(TODO_ITEM_ONE)         .type('{enter}')       allItems()         .eq(0)         .find('label')         .should('contain', TODO_ITEM_ONE)     })     \/\/ more tests   })    context('Mark all as completed', function () {     const TOGGLE_ALL = '.toggle-all'      beforeEach(addDefaultTodos)      it('should allow me to mark all items as completed', function () {       cy.get(TOGGLE_ALL).check()       allItems()         .eq(0)         .should('have.class', 'completed')     })     \/\/ more tests   }) })<\/code><\/pre>\n<p>\u0426\u0435\u043b\u044c \u0441\u043e\u0441\u0442\u043e\u0438\u0442 \u0432 \u0442\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043b\u0435\u0433\u043a\u043e \u0447\u0438\u0442\u0430\u0435\u043c\u044b\u043c\u0438, \u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u0434\u043b\u044f \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u044f \u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u043c\u0438 \u0434\u043b\u044f \u043a\u0430\u043a\u0438\u0445-\u043b\u0438\u0431\u043e \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0439 \u043f\u0440\u0438 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u0438.<\/p>\n<h3>\u0426\u0435\u043b\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438<\/h3>\n<p>\u041e\u0434\u043d\u0438\u043c \u0438\u0437 \u043f\u0440\u0435\u043a\u0440\u0430\u0441\u043d\u044b\u0445 \u0434\u043e\u0441\u0442\u043e\u0438\u043d\u0441\u0442\u0432 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f app actions \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0434\u0435\u043d\u0438\u044f \u0442\u0435\u0441\u0442\u043e\u0432 \u044f\u0432\u043b\u044f\u044e\u0442\u0441\u044f \u0446\u0435\u043b\u0435\u043d\u0430\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 \u043e\u0448\u0438\u0431\u043a\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u043b\u0435\u043d \u0444\u043b\u0430\u0436\u043e\u043a \u0434\u043b\u044f \u043f\u0435\u0440\u0435\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u043f\u043e \u043c\u0435\u0440\u0435 \u0438\u0445 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f. \u041a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"javascript\">if (todos.length) {   main = (     &lt;section className='main'&gt;       &lt;input         className='toggle-all'         type='checkbox'         onChange={this.toggleAll}         checked={activeTodoCount === 0}       \/&gt;       &lt;ul className='todo-list'&gt;{todoItems}&lt;\/ul&gt;     &lt;\/section&gt;   ) }<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u044f \u0443\u0434\u0430\u043b\u044f\u044e \u044d\u043b\u0435\u043c\u0435\u043d\u0442 <code>&lt;input className='toggle-all' \u2026 \/&gt;<\/code>, \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u0441\u0442\u044b \u0432\u043d\u0443\u0442\u0440\u0438 \u0431\u043b\u043e\u043a\u0430&nbsp; \u00ab\u041e\u0442\u043c\u0435\u0442\u0438\u0442\u044c \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u043e\u00bb (\u201cMark all as completed\u201d) \u043f\u0440\u0435\u0440\u044b\u0432\u0430\u044e\u0442\u0441\u044f.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/b2b\/031\/107\/b2b031107924aea046cd55f3ed7d1c9f.png\" width=\"2000\" height=\"1195\"><figcaption><\/figcaption><\/figure>\n<p>\u041d\u0438\u043a\u0430\u043a\u043e\u0439 \u0434\u0440\u0443\u0433\u043e\u0439 \u0442\u0435\u0441\u0442 \u043d\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442 \u0447\u0435\u0440\u0435\u0437 \u044d\u0442\u043e\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430, \u043f\u043e\u044d\u0442\u043e\u043c\u0443 \u043d\u0438\u043a\u0430\u043a\u0438\u0435 \u0434\u0440\u0443\u0433\u0438\u0435 \u0442\u0435\u0441\u0442\u044b \u043d\u0435 \u043f\u0440\u0435\u0440\u044b\u0432\u0430\u044e\u0442\u0441\u044f.<\/p>\n<p>\u0410\u043d\u0430\u043b\u043e\u0433\u0438\u0447\u043d\u043e, \u043a\u0430\u0436\u0434\u044b\u0439 \u044d\u043b\u0435\u043c\u0435\u043d\u0442 <code>Todo<\/code> \u0432\u044b\u0432\u043e\u0434\u0438\u0442 \u0444\u043b\u0430\u0436\u043e\u043a \u0434\u043b\u044f \u043e\u0431\u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u0441\u0432\u043e\u0435\u0433\u043e \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u043e\u0433\u043e \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u043e\u0433\u043e \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u0430. \u041a\u043e\u0434 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a.<\/p>\n<pre><code class=\"javascript\">&lt;input   className=\"toggle\"   type=\"checkbox\"   checked={this.props.todo.completed}   onChange={this.props.onToggle} \/&gt;<\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u044f \u0443\u0431\u0435\u0440\u0443 <code>onChange={this.props.onToggle}<\/code> :<\/p>\n<pre><code class=\"javascript\">&lt;input   className='toggle'   type='checkbox'   checked={this.props.todo.completed}   \/\/ onChange={this.props.onToggle} \/&gt;<\/code><\/pre>\n<p>\u0422\u043e\u0433\u0434\u0430 \u043f\u0440\u0435\u0440\u0432\u0443\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u0442\u0435\u0441\u0442\u044b \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0445 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/7cb\/80d\/5a9\/7cb80d5a9b4faef6f80615a2b3d30f60.png\" width=\"2000\" height=\"1195\"><figcaption><\/figcaption><\/figure>\n<p>\u042f \u043e\u0447\u0435\u043d\u044c \u0440\u0430\u0434, \u0447\u0442\u043e \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u043e\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u043b\u0438\u044f\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043d\u0430 \u043e\u0434\u0438\u043d \u043d\u0430\u0431\u043e\u0440 \u0442\u0435\u0441\u0442\u043e\u0432. \u042d\u0442\u043e \u0434\u043e\u043b\u0433\u043e\u0436\u0434\u0430\u043d\u043d\u043e\u0435 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435 \u043f\u043e \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u044e \u0441 \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u043c \u00ab\u043a\u0430\u043a\u0438\u043c-\u0442\u043e \u043c\u0430\u043b\u0435\u043d\u044c\u043a\u0438\u043c UI \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0435\u043c \u2014 \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u043b\u043e\u0432\u0438\u043d\u0430 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u043a\u0440\u0430\u0441\u043d\u043e\u0433\u043e \u0446\u0432\u0435\u0442\u0430\u00bb.<\/p>\n<p>\u0415\u0441\u043b\u0438 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c TypeScript \u0434\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u043d\u0430\u0448\u0435\u0433\u043e \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043d\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u0434\u0430\u0436\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u0435 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0445 \u043c\u0435\u0442\u043e\u0434\u043e\u0432. \u042d\u0442\u043e \u0443\u0441\u043a\u043e\u0440\u0438\u0442 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u0442\u0438\u043f\u043e\u0432 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0442 \u0441\u0440\u0430\u0437\u0443 \u0436\u0435 \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433\u043e\u0432\u0430\u0442\u044c \u0432\u0441\u0435 \u043c\u0435\u0441\u0442\u0430, \u0433\u0434\u0435 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u0432 \u043a\u043e\u0434\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<h3>\u041e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u0438\u044f App Actions<\/h3>\n<p><strong>\u0421\u043b\u0438\u0448\u043a\u043e\u043c \u043c\u043d\u043e\u0433\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439 \u0441\u043b\u0438\u0448\u043a\u043e\u043c \u0431\u044b\u0441\u0442\u0440\u043e<\/strong><\/p>\n<p>\u041f\u0440\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0438 app actions \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u0445 \u043e\u043f\u0435\u0440\u0430\u0446\u0438\u0439, \u0432\u0430\u0448\u0438 \u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0442\u044c\u0441\u044f \u0440\u0430\u043d\u044c\u0448\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0435\u0441\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u0435\u0442 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u044b\u0435 todos \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0435 \u0434\u043e \u0438\u0445 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u0438\u044f, \u0432\u044b \u043d\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0440\u0430\u0437\u0443 \u043f\u043e\u043c\u0435\u0442\u0438\u0442\u044c \u0438\u0445 \u043a\u0430\u043a \u00ab\u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u043d\u044b\u0435\u00bb.<\/p>\n<pre><code class=\"javascript\">\/\/ model app.TodoModel.prototype.addTodo = function (...todos) {   \/\/ make XHR to the server to save todos   ajax({     method: 'POST',     url: '\/todos',     data: todos   }).then(() =&gt;     then update local state     this.saveTodos(todos)   ).then(() =&gt;     \/\/ this triggers DOM render     this.inform()   ) } \/\/ spec.js it('completes all items', () =&gt; {   addDefaultTodos()   toggle(1) \/\/ marks item completed   \/\/ click on \"Completed\" link   \/\/ assert there is 1 completed item })<\/code><\/pre>\n<\/p>\n<p>\u0412\u044b\u0448\u0435\u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0442\u0435\u0441\u0442 completes all items, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u0432\u0435\u0440\u0448\u0430\u0435\u0442 \u0432\u0441\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u0441\u043a\u043e\u0440\u0435\u0435 \u0432\u0441\u0435\u0433\u043e, \u0438\u043d\u043e\u0433\u0434\u0430 \u043f\u0440\u043e\u0439\u0434\u0435\u0442, \u0430 \u0438\u043d\u043e\u0433\u0434\u0430 \u0438 \u043d\u0435 \u043f\u0440\u043e\u0439\u0434\u0435\u0442. \u0418 \u0432\u0441\u0435 \u043f\u043e\u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u0442\u0435\u0441\u0442 \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0431\u044b\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f.<\/p>\n<p>\u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u0442\u043e \u0432\u0440\u0435\u043c\u044f \u043a\u0430\u043a \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u0441\u0435 \u0435\u0449\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u043e\u0432\u044b\u0435 <code>todos<\/code> \u0432\u043d\u0443\u0442\u0440\u0438 \u043c\u0435\u0442\u043e\u0434\u0430 <code>addTodo<\/code>, \u0442\u0435\u0441\u0442 \u0443\u0436\u0435 \u043f\u043e\u0441\u044b\u043b\u0430\u0435\u0442 <code>toggle action<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u043f\u044b\u0442\u0430\u0442\u044c\u0441\u044f \u0437\u0430\u0432\u0435\u0440\u0448\u0438\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 todo \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c 1. \u041c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c, \u043a\u043e\u0433\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044e \u0434\u0430\u043d\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u0440\u0435\u043c\u0435\u043d\u0438, \u0447\u0442\u043e\u0431\u044b \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a <code>todo<\/code> \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u0443\u0441\u0442\u0430\u043d\u043e\u0432\u0438\u0442\u044c \u0438\u0445 \u0432 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e\u043c \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0438 \u2014 \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u0442\u0435\u0441\u0442 \u043f\u0440\u043e\u0439\u0434\u0435\u0442. \u041d\u043e \u0431\u043e\u043b\u044c\u0448\u0443\u044e \u0447\u0430\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u0441\u0435 \u0435\u0449\u0435 \u0436\u0434\u0435\u0442 \u043e\u0442\u0432\u0435\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u2014 \u0432 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u044b\u0439 \u0441\u043f\u0438\u0441\u043e\u043a \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0432\u0441\u0435 \u0435\u0449\u0435 \u043f\u0443\u0441\u0442 \u0438 \u043f\u043e\u043f\u044b\u0442\u043a\u0430 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0441 \u0438\u043d\u0434\u0435\u043a\u0441\u043e\u043c 1 \u0441\u043f\u0440\u043e\u0432\u043e\u0446\u0438\u0440\u0443\u0435\u0442 \u043e\u0448\u0438\u0431\u043a\u0443.<\/p>\n<p>\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0434\u043b\u044f \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c, \u043c\u044b \u043e\u0442\u043e\u0448\u043b\u0438 \u043e\u0442 \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435. \u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u043d\u0435 \u0441\u043c\u043e\u0436\u0435\u0442 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442 \u0434\u043e \u0442\u043e\u0433\u043e, \u043a\u0430\u043a \u043e\u043d \u0431\u0443\u0434\u0435\u0442 \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044e \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435. \u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u043d\u0430\u0448\u0438\u043c \u0442\u0435\u0441\u0442\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u043f\u043e\u044f\u0432\u043b\u0435\u043d\u0438\u044f \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u043c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0435, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0442\u044c <code>toggle(1)<\/code>. \u041e\u043f\u044f\u0442\u044c \u0436\u0435 \u043f\u0440\u043e\u0441\u0442\u043e\u0439 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043c\u043d\u043e\u0433\u043e\u043a\u0440\u0430\u0442\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0434\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e.<\/p>\n<pre><code class=\"javascript\">it('completes all items', () =&gt; {   addDefaultTodos()   allItems().should('have.length', 3)   toggle(1) \/\/ marks item completed   \/\/ click on \"Completed\" link   \/\/ assert there is 1 completed item })<\/code><\/pre>\n<p>\u042f \u043d\u0430\u0441\u0442\u043e\u044f\u0442\u0435\u043b\u044c\u043d\u043e \u0440\u0435\u043a\u043e\u043c\u0435\u043d\u0434\u0443\u044e \u0441\u0445\u0435\u043c\u0443, \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043d\u0443\u044e \u0432\u044b\u0448\u0435 \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c app action \u0438 \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0434\u043e \u0436\u0435\u043b\u0430\u0435\u043c\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f \u043f\u0443\u0442\u0435\u043c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f, \u0437\u0430\u0442\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 app action, \u0438 \u0441\u043d\u043e\u0432\u0430 \u0434\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430. \u042d\u0442\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e Cypress \u043c\u043e\u0436\u0435\u0442 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u044c \u0437\u0430 DOM \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u044c \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0435\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435, \u043a\u0430\u043a \u0442\u043e\u043b\u044c\u043a\u043e \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u043f\u0440\u043e\u0439\u0434\u0443\u0442.<\/p>\n<p>\u0412\u044b \u043d\u0435 \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u044b \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c\u044e \u043d\u0430\u0431\u043b\u044e\u0434\u0435\u043d\u0438\u044f \u0437\u0430 DOM \u2014 \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0442\u0430\u043a \u0436\u0435 \u043b\u0435\u0433\u043a\u043e \u0448\u043f\u0438\u043e\u043d\u0438\u0442\u044c \u0437\u0430 \u0441\u0435\u0442\u0435\u0432\u044b\u043c\u0438 \u0437\u0432\u043e\u043d\u043a\u0430\u043c\u0438. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0448\u043f\u0438\u043e\u043d\u0438\u0442\u044c \u0437\u0430 <code>POST \/todos XHR<\/code>, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442\u0441\u044f \u043e\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 \u0438 \u043e\u0436\u0438\u0434\u0430\u0435\u0442 \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430, \u043f\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 <code>toggle(1)<\/code>.<\/p>\n<pre><code class=\"javascript\">it('completes all items', () =&gt; {   cy.server()   cy.route('POST', '\/todos').as('save')   addDefaultTodos()   cy.wait('@save') \/\/ waits for XHR POST \/todos before test continues   toggle(1) \/\/ marks item completed   \/\/ click on \"Completed\" link   \/\/ assert there is 1 completed item })<\/code><\/pre>\n<p>\u0415\u0449\u0435 \u043b\u0443\u0447\u0448\u0435 \u2014 \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0448\u043f\u0438\u043e\u043d\u0438\u0442\u044c \u0437\u0430 \u043c\u0435\u0442\u043e\u0434\u0430\u043c\u0438 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432 \u043d\u0430\u0448\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438! \u0422\u0430\u043a \u043a\u0430\u043a \u043d\u0430\u0448\u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0435\u0442 <code>model.inform<\/code>, \u043a\u043e\u0433\u0434\u0430 \u0431\u0443\u0434\u0435\u0442 \u0441\u0434\u0435\u043b\u0430\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435 \u0441\u0442\u0430\u0442\u0443\u0441\u0430, \u044d\u0442\u043e \u0445\u043e\u0440\u043e\u0448\u0438\u0439 \u0437\u043d\u0430\u043a, \u0447\u0442\u043e \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0432\u044b\u0437\u0432\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u043e\u0439 app action.<\/p>\n<pre><code class=\"javascript\">it('completes all items', () =&gt; {   cy.window()     .its('model')     .then(model =&gt; {       cy.spy(model, 'inform').as('inform')     })   addDefaultTodos()   \/\/ wait until the spy is called once   cy.get('@inform').should('have.been.calledOnce')   toggle(1) \/\/ marks item completed   \/\/ click on \"Completed\" link   \/\/ assert there is 1 completed item })<\/code><\/pre>\n<p>\u0418\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 Cypress UI \u043f\u043e\u043a\u0430\u0437\u044b\u0432\u0430\u0435\u0442 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043a\u0430\u0436\u0434\u043e\u043c \u043c\u0435\u0442\u043e\u0434\u0435, \u0437\u0430 \u043a\u043e\u0442\u043e\u0440\u044b\u043c \u043c\u044b \u0441\u043b\u0435\u0434\u0438\u043c, \u0432 \u0436\u0443\u0440\u043d\u0430\u043b\u0435 \u043a\u043e\u043c\u0430\u043d\u0434.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/406\/e4e\/a00\/406e4ea003edf25be81e49d9ed0f72f6.png\" width=\"2000\" height=\"967\"><figcaption><\/figcaption><\/figure>\n<p>\u041f\u043e\u0434\u0432\u043e\u0434\u044f \u0438\u0442\u043e\u0433: app actions \u043c\u043e\u0433\u0443\u0442 \u0431\u044b\u0442\u044c \u0432\u044b\u0437\u0432\u0430\u043d\u044b \u0438\u0437 \u0442\u0435\u0441\u0442\u0430 \u0431\u044b\u0441\u0442\u0440\u0435\u0435, \u0447\u0435\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043c\u043e\u0436\u0435\u0442 \u0438\u0445 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c. \u0412 \u044d\u0442\u043e\u043c \u0441\u043b\u0443\u0447\u0430\u0435, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u043d\u0442\u0435\u0440\u043f\u0440\u0435\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442\u044b \u043a\u0430\u043a \u043d\u0435\u043d\u0430\u0434\u0451\u0436\u043d\u044b\u0435 \u0438\u0437-\u0437\u0430 \u0437\u0430\u0434\u0435\u0440\u0436\u043a\u0438 \u043c\u0435\u0436\u0434\u0443 \u0442\u0435\u0441\u0442\u043e\u043c \u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435\u043c. \u041a \u0441\u0447\u0430\u0441\u0442\u044c\u044e, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0442\u0435\u0441\u0442 \u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u0441\u043f\u043e\u0441\u043e\u0431\u0430\u043c\u0438. \u0422\u0435\u0441\u0442 \u043c\u043e\u0436\u0435\u0442:<\/p>\n<ol>\n<li>\n<p>\u0414\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f DOM, \u043a\u0430\u043a \u0438 \u043e\u0436\u0438\u0434\u0430\u043b\u043e\u0441\u044c.<\/p>\n<\/li>\n<li>\n<p>\u041d\u0430\u0431\u043b\u044e\u0434\u0430\u0442\u044c \u0437\u0430 \u0441\u0435\u0442\u0435\u0432\u044b\u043c \u0442\u0440\u0430\u0444\u0438\u043a\u043e\u043c \u0438 \u0436\u0434\u0430\u0442\u044c \u043e\u0436\u0438\u0434\u0430\u0435\u043c\u043e\u0433\u043e \u0432\u044b\u0437\u043e\u0432\u0430 XHR.<\/p>\n<\/li>\n<li>\n<p>\u0428\u043f\u0438\u043e\u043d\u0438\u0442\u044c \u0437\u0430 \u043c\u0435\u0442\u043e\u0434\u043e\u043c \u0432 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0438 \u0438 \u043f\u0440\u043e\u0434\u043e\u043b\u0436\u0430\u0442\u044c \u0440\u0430\u0431\u043e\u0442\u0443, \u043a\u043e\u0433\u0434\u0430 \u043e\u043d\u043e \u0431\u0443\u0434\u0435\u0442 \u0432\u044b\u0437\u0432\u0430\u043d\u043e.<\/p>\n<\/li>\n<\/ol>\n<p><strong>\u041a\u043e\u0433\u0434\u0430 \u043a\u0430\u043a\u0438\u0435-\u043b\u0438\u0431\u043e \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u044f \u043e\u0433\u0440\u0430\u043d\u0438\u0447\u0435\u043d\u044b<\/strong><\/p>\n<p>\u0418\u043d\u043e\u0433\u0434\u0430 \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0435 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 \u0436\u0435\u043b\u0430\u0435\u043c\u043e\u0433\u043e \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u0430. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432 \u043e\u0431\u0441\u0443\u0436\u0434\u0435\u043d\u0438\u0438 <a href=\"https:\/\/www.youtube.com\/watch?v=5XQOK0v_YRE\">Cypress Best Practices<\/a> Brian Mann \u0437\u0430\u044f\u0432\u0438\u043b:<\/p>\n<ul>\n<li>\n<p>\u041f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0442\u0430\u043a \u0436\u0435, \u043a\u0430\u043a \u044d\u0442\u043e \u0434\u0435\u043b\u0430\u0435\u0442 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c<\/p>\n<\/li>\n<li>\n<p>\u041f\u0440\u0438 \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u0438 \u043b\u044e\u0431\u043e\u0433\u043e \u0434\u0440\u0443\u0433\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043f\u043e\u0442\u043e\u043a\u0430, \u0442\u0440\u0435\u0431\u0443\u044e\u0449\u0435\u0433\u043e \u0432\u0445\u043e\u0434\u0430 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443, \u0442\u0435\u0441\u0442 \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u0440\u0430\u0437\u0443 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0432\u0445\u043e\u0434 (\u043d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u044b <a href=\"https:\/\/on.cypress.io\/request\">cy.request()<\/a>), \u0438 \u043d\u0435 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441\u043d\u043e\u0432\u0430 \u0438 \u0441\u043d\u043e\u0432\u0430.<\/p>\n<\/li>\n<\/ul>\n<p>\u0412 \u0432\u044b\u0448\u0435\u043f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u043e\u0439 \u0440\u0435\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u0438 \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0435 \u043c\u043e\u0436\u0435\u0442 \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0432\u0445\u043e\u0434 \u0432 \u0441\u0438\u0441\u0442\u0435\u043c\u0443 \u0442\u0435\u043c \u0436\u0435 \u043c\u0435\u0442\u043e\u0434\u043e\u043c, \u0447\u0442\u043e \u0438<strong> <\/strong><code>cy.request<\/code><strong>. <\/strong>\u0422\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c, \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c<strong> <\/strong><code>cy.request()<\/code><strong>,<\/strong> \u0430 \u043d\u0435 \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c app actions. \u042d\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0438\u0437\u0431\u0435\u0436\u0430\u0442\u044c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0448\u0430\u0431\u043b\u043e\u043d\u0430 \u043e\u0431\u044a\u0435\u043a\u0442\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u2014&nbsp; \u0438 \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0443\u044e \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u0438\u043b\u0438 \u043f\u0440\u043e\u0441\u0442\u0443\u044e \u0444\u0443\u043d\u043a\u0446\u0438\u044e.<\/p>\n<h3>\u0424\u0438\u043d\u0430\u043b\u044c\u043d\u044b\u0435 \u043c\u044b\u0441\u043b\u0438<\/h3>\n<p>\u041f\u0435\u0440\u0435\u0445\u043e\u0434 \u043e\u0442 Page Objects, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u0440\u043e\u0445\u043e\u0434\u044f\u0442 \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043a App Actions, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u0438\u0440\u0443\u044e\u0442 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435 \u0447\u0435\u0440\u0435\u0437 \u0435\u0433\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 API \u043c\u043e\u0434\u0435\u043b\u0438, \u043f\u0440\u0438\u043d\u043e\u0441\u0438\u0442 \u043c\u043d\u043e\u0433\u043e \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432.<\/p>\n<ul>\n<li>\n<p>\u0422\u0435\u0441\u0442\u044b \u0441\u0442\u0430\u043d\u043e\u0432\u044f\u0442\u0441\u044f \u043d\u0430\u043c\u043d\u043e\u0433\u043e \u0431\u044b\u0441\u0442\u0440\u0435\u0435. \u0414\u0430\u0436\u0435 \u043f\u0440\u043e\u0441\u0442\u044b\u0435 \u0442\u0435\u0441\u0442\u044b TodoMVC, \u0437\u0430\u043f\u0443\u0449\u0435\u043d\u043d\u044b\u0435 \u043b\u043e\u043a\u0430\u043b\u044c\u043d\u043e \u0432 \u0441\u0440\u0430\u0432\u043d\u0435\u043d\u0438\u0438 \u0441 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u043e\u043c Cypress Electron, \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u043d\u0435 \u0437\u0430 34 \u0441\u0435\u043a\u0443\u043d\u0434, \u0430 \u0437\u0430 17 \u0441\u0435\u043a\u0443\u043d\u0434 \u043f\u043e\u0441\u043b\u0435 \u043f\u0435\u0440\u0435\u0445\u043e\u0434\u0430 \u043e\u0442 \u043f\u0440\u043e\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u044f \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043a \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044e&nbsp;App Actions \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0435\u0442\u0441\u044f \u043d\u0430 50%.<\/p>\n<\/li>\n<li>\n<p>\u0422\u0435\u043f\u0435\u0440\u044c \u0442\u0435\u0441\u0442\u044b \u0432\u043b\u0438\u044f\u044e\u0442 \u043d\u0430 \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0432\u044b\u0438\u0433\u0440\u044b\u0432\u0430\u044e\u0442 \u043e\u0442 \u0435\u0433\u043e \u0440\u0435\u0444\u0430\u043a\u0442\u043e\u0440\u0438\u043d\u0433\u0430. \u0427\u0435\u043c \u0440\u0430\u0437\u0443\u043c\u043d\u0435\u0435 \u0438 \u043b\u0443\u0447\u0448\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u0442\u0435\u043c \u043f\u0440\u043e\u0449\u0435 \u0431\u0443\u0434\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u0434\u043b\u044f \u043d\u0438\u0445 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b.<\/p>\n<\/li>\n<li>\n<p>\u041b\u0443\u0447\u0448\u0435 \u0438\u0437\u0431\u0435\u0433\u0430\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0441\u043b\u0430\u0431\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0433\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043b\u043e\u044f \u043a\u043e\u0434\u0430 \u0434\u043b\u044f \u0441\u043b\u043e\u0436\u043d\u044b\u0445 \u0438 \u043d\u0435\u0441\u0442\u0430\u0431\u0438\u043b\u044c\u043d\u044b\u0445 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u043e\u0432. \u0412\u043c\u0435\u0441\u0442\u043e \u044d\u0442\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442\u0441\u044f \u0438 \u0432\u044b\u043f\u043e\u043b\u043d\u044f\u044e\u0442\u0441\u044f \u0434\u043b\u044f \u0431\u043e\u043b\u0435\u0435 \u0434\u043b\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u043c\u043e\u0434\u0435\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0430 \u0441\u0430\u043c\u043e\u043c \u0434\u0435\u043b\u0435, \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0443\u0442\u0438\u043b\u0438\u0442\u0430, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043c\u043d\u0435 \u043f\u0440\u0438\u0445\u043e\u0434\u0438\u043b\u043e\u0441\u044c \u043f\u0438\u0441\u0430\u0442\u044c \u043b\u0438\u0448\u044c \u043e\u0442\u043e\u0431\u0440\u0430\u0436\u0430\u044e\u0442 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u0442\u0435\u0441\u0442\u0430 \u0434\u043b\u044f App Actions \u0430 \u0431\u043e\u043b\u044c\u0448\u0438\u043d\u0441\u0442\u0432\u043e \u0438\u0437 \u043d\u0438\u0445 \u043f\u0440\u043e\u0441\u0442\u043e \u043d\u0435 \u0438\u043c\u0435\u044e\u0442 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441\u0430.<\/p>\n<pre><code class=\"javascript\">export const addTodos = (...todos) =&gt; {   cy.window().its('model').invoke('addTodo', ...todos) }<\/code><\/pre>\n<p>\u041d\u0435\u0442 \u043f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u043e\u0433\u043e \u0441\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u044f (\u0432\u043d\u0443\u0442\u0440\u0438 page objects), \u043d\u0435\u0442 \u043b\u043e\u0433\u0438\u043a\u0438 \u0443\u0441\u043b\u043e\u0432\u043d\u043e\u0433\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f \u2014 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u0440\u044f\u043c\u0430\u044f \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u0430\u043a \u044d\u0442\u043e \u043c\u043e\u0436\u043d\u043e \u0441\u0434\u0435\u043b\u0430\u0442\u044c \u0438\u0437 \u043a\u043e\u043d\u0441\u043e\u043b\u0438 DevTools.<\/p>\n<p><strong>\u0411\u043e\u043b\u044c\u0448\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438<\/strong><\/p>\n<p>\u0411\u043e\u043b\u044c\u0448\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043f\u043e \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u0432 <a href=\"https:\/\/github.com\/cypress-io\/cypress-example-recipes#application-actions\">Application Actions<\/a>. \u0422\u0430\u043a\u0436\u0435 \u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u0435 \u0442\u0430\u043a\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u0432 \u0434\u0440\u0443\u0433\u0438\u0445 \u0441\u0442\u0438\u043b\u044f\u0445, \u0432\u043a\u043b\u044e\u0447\u0430\u044f Page Object \u0438 App Actions \u0432 repo <a href=\"https:\/\/github.com\/bahmutov\/test-todomvc-using-app-actions\">bahmutov\/test-todomvc-using-app-actions<\/a>.<\/p>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0443 \u0436\u0435 \u0438\u0434\u0435\u044e App Actions, \u0447\u0442\u043e\u0431\u044b \u0432\u0435\u0441\u0442\u0438 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0432\u0430\u0448\u0438\u0445 \u0442\u0435\u0441\u0442\u043e\u0432. \u0412 \u043f\u0440\u0435\u0434\u044b\u0434\u0443\u0449\u0435\u043c \u0431\u043b\u043e\u0433\u0435 \u044f \u0443\u0436\u0435 \u0433\u043e\u0432\u043e\u0440\u0438\u043b \u043e:<\/p>\n<ul>\n<li>\n<p><a href=\"https:\/\/cypress-io.ghost.io\/blog\/2018\/11\/14\/testing-redux-store\/\">Dispatch Redux actions<\/a>&nbsp; \u0438 \u0434\u0435\u043b\u0438\u043b\u0441\u044f \u043a\u043e\u0434\u043e\u043c \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439&nbsp;<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/cypress-io.ghost.io\/blog\/2017\/11\/28\/testing-vue-web-application-with-vuex-data-store-and-rest-backend\/\">Dispatch Vuex actions<\/a> \u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u043b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f DOM updates \u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u044b \u0441\u0435\u0442\u0438<\/p>\n<\/li>\n<\/ul>\n<p>\u041f\u0440\u0438\u0432\u0435\u0434\u0435\u043d\u043d\u044b\u0435 \u0432\u044b\u0448\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u044b \u0438 \u043f\u043e\u0441\u0442\u044b \u0432 \u0431\u043b\u043e\u0433\u0435 \u043f\u043e\u043c\u043e\u0433\u043b\u0438 \u043c\u043d\u0435 \u0443\u0432\u0438\u0434\u0435\u0442\u044c \u043d\u0435\u0434\u043e\u0441\u0442\u0430\u0442\u043a\u0438 Page Objects, \u0430 \u0442\u0430\u043a\u0436\u0435 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430 App Actions \u0432 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u0430\u0445.<\/p>\n<hr>\n<blockquote>\n<p><a href=\"https:\/\/otus.pw\/vx0y\/\">\u0423\u0437\u043d\u0430\u0442\u044c \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u043e \u043a\u0443\u0440\u0441\u0435<\/a> \u00abJavaScript QA Engineer\u00bb.<\/p>\n<p><a href=\"https:\/\/otus.pw\/29EC\/\">\u041f\u0440\u0438\u043d\u044f\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u0438\u0435 \u0432 \u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u0435\u0431\u0438\u043d\u0430\u0440\u0435<\/a> \u043d\u0430 \u0442\u0435\u043c\u0443 \u00ab\u0427\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0437\u043d\u0430\u0442\u044c \u043e JS \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0443\u00bb. <\/p>\n<\/blockquote>\n<\/div>\n<p> \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\/company\/otus\/blog\/541596\/\"> https:\/\/habr.com\/ru\/company\/otus\/blog\/541596\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text_v2\" id=\"post-content-body\">\n<blockquote>\n<p><em>\u041f\u0440\u0438\u0432\u0435\u0442, \u0445\u0430\u0431\u0440\u043e\u0432\u0447\u0430\u043d\u0435. \u0414\u043b\u044f \u0431\u0443\u0434\u0443\u0449\u0438\u0445 \u0441\u0442\u0443\u0434\u0435\u043d\u0442\u043e\u0432 <\/em><a href=\"https:\/\/otus.pw\/vx0y\/\"><em>\u043a\u0443\u0440\u0441\u0430 \u00abJavaScript QA Engineer\u00bb<\/em><\/a><em> \u043f\u043e\u0434\u0433\u043e\u0442\u043e\u0432\u0438\u043b\u0438  \u043f\u0435\u0440\u0435\u0432\u043e\u0434 \u043f\u043e\u043b\u0435\u0437\u043d\u043e\u0433\u043e \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0430.<\/p>\n<p>\u0422\u0430\u043a\u0436\u0435 \u043f\u0440\u0438\u0433\u043b\u0430\u0448\u0430\u0435\u043c \u0432\u0441\u0435\u0445 \u0436\u0435\u043b\u0430\u044e\u0449\u0438\u0445 \u043f\u0440\u0438\u043d\u044f\u0442\u044c \u0443\u0447\u0430\u0441\u0442\u0438\u0435 \u0432 <\/em><a href=\"https:\/\/otus.pw\/29EC\/\"><em>\u043e\u0442\u043a\u0440\u044b\u0442\u043e\u043c \u0432\u0435\u0431\u0438\u043d\u0430\u0440\u0435 \u043d\u0430 \u0442\u0435\u043c\u0443 \u00ab\u0427\u0442\u043e \u043d\u0443\u0436\u043d\u043e \u0437\u043d\u0430\u0442\u044c \u043e JS \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0443\u00bb.<\/em><\/a><em> \u041d\u0430 \u0437\u0430\u043d\u044f\u0442\u0438\u0438 \u0431\u0443\u0434\u0435\u0442 \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u0435\u043d\u044b \u043e\u0441\u043e\u0431\u0435\u043d\u043d\u043e\u0441\u0442\u0438 JS, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0432\u0441\u0451 \u0432\u0440\u0435\u043c\u044f \u043d\u0443\u0436\u043d\u043e \u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0432 \u0433\u043e\u043b\u043e\u0432\u0435 \u043f\u0440\u0438 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432.<\/em><\/p>\n<\/blockquote>\n<figure class=\"full-width\"><figcaption><\/figcaption><\/figure>\n<hr>\n<p>\u041d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0445 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432 \u2014 \u044d\u0442\u043e \u0441\u043b\u043e\u0436\u043d\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430. \u0427\u0430\u0441\u0442\u043e \u0442\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0449\u0438\u043a\u0438 \u0441\u043e\u0437\u0434\u0430\u044e\u0442 \u0434\u0440\u0443\u0433\u043e\u0439 \u043a\u043e\u0441\u0432\u0435\u043d\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u0432\u0435\u0431-\u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u043d\u0430\u0437\u044b\u0432\u0430\u0435\u043c\u044b\u0439 page objects, \u0434\u043b\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u043e\u0431\u0449\u0438\u0445 \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0439. \u0412 \u044d\u0442\u043e\u0439 \u0441\u0442\u0430\u0442\u044c\u0435 \u044f \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0430\u044e, \u0447\u0442\u043e page objects \u2014 \u044d\u0442\u043e \u043f\u043b\u043e\u0445\u0430\u044f \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0430, \u0438 \u043f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u043e\u0431\u0440\u0430\u0442\u0438\u0442\u044c \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0438\u0439 \u0430\u043b\u0433\u043e\u0440\u0438\u0442\u043c \u0440\u0430\u0431\u043e\u0442\u044b \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042d\u0442\u043e \u043e\u0442\u043b\u0438\u0447\u043d\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u0441 \u0441\u043e\u0432\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c test runner Cypress.io, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u0435\u0442 \u0442\u0435\u0441\u0442\u043e\u0432\u044b\u0439 \u043a\u043e\u0434 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0432\u043c\u0435\u0441\u0442\u0435 \u0441 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<h3>Page objects<\/h3>\n<p>Page Objects <a href=\"https:\/\/github.com\/SeleniumHQ\/selenium\/wiki\/PageObjects\"><u>1<\/u><\/a>, <a href=\"https:\/\/www.linkedin.com\/pulse\/page-object-pattern-javascript-vladim%C3%ADr-gorej\/\"><u>2 <\/u><\/a>\u043f\u0440\u0435\u0434\u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u044b \u0434\u043b\u044f \u0442\u043e\u0433\u043e, \u0447\u0442\u043e\u0431\u044b \u0434\u0435\u043b\u0430\u0442\u044c \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0447\u0438\u0442\u0430\u0431\u0435\u043b\u044c\u043d\u044b\u043c\u0438 \u0438 \u043f\u0440\u043e\u0441\u0442\u044b\u043c\u0438 \u0432 \u044d\u043a\u0441\u043f\u043b\u0443\u0430\u0442\u0430\u0446\u0438\u0438. \u0412\u043c\u0435\u0441\u0442\u043e ad-hoc \u0438\u043d\u0442\u0435\u0440\u0430\u043a\u0446\u0438\u0439 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439, \u0442\u0435\u0441\u0442 \u0443\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u0441\u043e\u0431\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0437\u0434\u0435\u0441\u044c \u0430\u0431\u0441\u0442\u0440\u0430\u043a\u0446\u0438\u044f \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432\u0445\u043e\u0434\u0430 \u0432\u0437\u044f\u0442\u0430 \u043d\u0435\u043f\u043e\u0441\u0440\u0435\u0434\u0441\u0442\u0432\u0435\u043d\u043d\u043e \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b Selenium Wiki.<\/p>\n<pre><code class=\"javascript\">public class LoginPage {   private final WebDriver driver;    public LoginPage(WebDriver driver) {     this.driver = driver;      \/\/ Check that we're on the right page.     if (!\"Login\".equals(driver.getTitle())) {       \/\/ Alternatively, we could navigate to the       \/\/ login page, perhaps logging out first       throw new IllegalStateException(\"This is not the login page\");     }   }    \/\/ The login page contains several HTML elements   \/\/ that will be represented as WebElements.   \/\/ The locators for these elements should only be defined once.   By usernameLocator = By.id(\"username\");   By passwordLocator = By.id(\"password\");   By loginButtonLocator = By.id(\"login\");    \/\/ The login page allows the user to type their   \/\/ username into the username field   public LoginPage typeUsername(String username) {     \/\/ This is the only place that \"knows\" how to enter a username     driver.findElement(usernameLocator).sendKeys(username);      \/\/ Return the current page object as this action doesn't     \/\/ navigate to a page represented by another PageObject     return this;   }   \/\/ other methods   \/\/  - typePassword   \/\/  - submitLogin   \/\/  - submitLoginExpectingFailure   \/\/  - loginAs }<\/code><\/pre>\n<h4>Page Objects \u0438\u043c\u0435\u044e\u0442 \u0434\u0432\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u044b\u0445 \u043f\u0440\u0435\u0438\u043c\u0443\u0449\u0435\u0441\u0442\u0432\u0430:<\/h4>\n<ol>\n<li>\n<p>\u041e\u043d\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u0432\u0441\u0435 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0432 \u043e\u0434\u043d\u043e\u043c \u043c\u0435\u0441\u0442\u0435<\/p>\n<\/li>\n<li>\n<p>\u041e\u043d\u0438 \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u0438\u0437\u0438\u0440\u0443\u044e\u0442 \u0432\u0437\u0430\u0438\u043c\u043e\u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0441\u043e \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0435\u0439<\/p>\n<\/li>\n<\/ol>\n<p>\u0422\u0438\u043f\u043e\u0432\u043e\u0439 \u0442\u0435\u0441\u0442 \u0431\u0443\u0434\u0435\u0442 \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u044c \u0442\u0430\u043a\u0438\u0435 Page Objects:<\/p>\n<pre><code class=\"javascript\">public void testLogin() {   LoginPage login = new LoginPage(driver);   login.typeUsername('username')   login.typePassword('username')   login.submitLogin() }<\/code><\/pre>\n<p>\u041c\u0430\u0440\u0442\u0438\u043d \u0424\u0430\u0443\u043b\u0435\u0440 \u0432 \u0441\u0432\u043e\u0435\u0439 <a href=\"https:\/\/martinfowler.com\/bliki\/PageObject.html\"><u>\u0441\u0442\u0430\u0442\u044c\u0435 <\/u><\/a>\u043e\u043f\u0438\u0441\u044b\u0432\u0430\u0435\u0442 PageObjects \u043a\u0430\u043a \u0435\u0449\u0435 \u043e\u0434\u0438\u043d API \u043f\u043e\u043c\u0438\u043c\u043e HTML. \u041a\u043e\u043d\u0446\u0435\u043f\u0442\u0443\u0430\u043b\u044c\u043d\u043e PageObjects \u0434\u043e\u043f\u043e\u043b\u043d\u044f\u044e\u0442 HTML.<\/p>\n<pre><code>Tests -----------------   Page Objects ~ ~ ~ ~ ~ ~ ~ ~ ~     HTML UI ----------------- Application code<\/code><\/pre>\n<p>\u0427\u0435\u0442\u044b\u0440\u0435&nbsp; \u0443\u0440\u043e\u0432\u043d\u044f \u043d\u0430 \u0434\u0438\u0430\u0433\u0440\u0430\u043c\u043c\u0435 \u0432\u044b\u0448\u0435 \u0438\u043c\u0435\u044e\u0442 \u0442\u0440\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0430 \u0441 \u0440\u0430\u0437\u043b\u0438\u0447\u043d\u044b\u043c \u0443\u0440\u043e\u0432\u043d\u0435\u043c \u0441\u0432\u044f\u0437\u0430\u043d\u043d\u043e\u0441\u0442\u0438.&nbsp;<\/p>\n<p>1. \u0421\u0432\u044f\u0437\u044c \u043c\u0435\u0436\u0434\u0443 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 HTML \u0432\u044b\u0441\u043e\u043a\u0430\u044f.&nbsp;<\/p>\n<p>2. \u041c\u0435\u0436\u0434\u0443 HTML \u0438 page objects \u043d\u0438\u0437\u043a\u0430\u044f.&nbsp;<\/p>\n<p>3. \u041c\u0435\u0436\u0434\u0443 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u0438 PO \u0432\u044b\u0441\u043e\u043a\u0430\u044f<\/p>\n<p><strong>\u0421\u0432\u044f\u0437\u043d\u043e\u0441\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u043a\u043e\u0434\u043e\u043c \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 HTML UI \u043e\u0447\u0435\u043d\u044c \u0432\u044b\u0441\u043e\u043a\u0430\u044f,<\/strong> \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u043a\u043e\u0434 \u0432\u044b\u0432\u043e\u0434\u0438\u0442 HTML-\u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u0432 DOM \u2014 \u0442\u043e \u0435\u0441\u0442\u044c \u043c\u0435\u0436\u0434\u0443 \u0444\u0443\u043d\u043a\u0446\u0438\u0435\u0439 render&nbsp; \u0432 \u043a\u043e\u0434\u0435&nbsp; \u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u043c-\u0432\u044b\u0432\u043e\u0434\u0430 DOM \u0441\u0432\u044f\u0437\u044c one to one.&nbsp; \u0421\u0442\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0435 \u0442\u0438\u043f\u044b \u0438 \u043b\u0438\u043d\u0442\u0435\u0440\u044b \u043f\u043e\u043c\u043e\u0433\u0430\u044e\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u043d\u043e\u0441\u0442\u044c \u043a\u043e\u0434\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0441\u043e\u0434\u0435\u0440\u0436\u0430\u0442\u0435\u043b\u044c\u043d\u044b\u0439 HTML.<\/p>\n<p><strong>Page objects \u0438 HTML \u0441\u043b\u0430\u0431\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u044b<\/strong>, \u0432\u043e\u0442 \u043f\u043e\u0447\u0435\u043c\u0443 \u044f \u043f\u0440\u043e\u0432\u0435\u043b \u0433\u0440\u0430\u043d\u0438\u0446\u0443, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f ~ ~. \u041e\u043d\u0438 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e\u0442 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u044b, \u0447\u0442\u043e\u0431\u044b \u043d\u0430\u0439\u0442\u0438 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u041d\u0415 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u044e\u0442\u0441\u044f \u043a\u0430\u043a\u0438\u043c-\u043b\u0438\u0431\u043e \u043b\u0438\u043d\u0442\u0435\u0440\u043e\u043c \u0438\u043b\u0438 \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0440\u043e\u043c \u043a\u043e\u0434\u0430. \u041a\u043e\u0434 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0432 \u043b\u044e\u0431\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u043c\u043e\u0436\u0435\u0442 \u043f\u043e\u043c\u0435\u043d\u044f\u0442\u044c\u0441\u044f, \u0432\u044b\u0434\u0430\u0442\u044c \u0434\u0440\u0443\u0433\u0443\u044e DOM \u0441\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0443 \u0438\u043b\u0438 \u0434\u0440\u0443\u0433\u0438\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b \u043a\u043b\u0430\u0441\u0441\u043e\u0432, \u0430 \u0442\u0430\u043a\u0436\u0435 \u0442\u0435\u0441\u0442\u044b \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u0435\u0440\u0432\u0430\u0442\u044c\u0441\u044f \u0432\u043e \u0432\u0440\u0435\u043c\u044f \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0438\u044f \u0431\u0435\u0437 \u043f\u0440\u0435\u0434\u0443\u043f\u0440\u0435\u0436\u0434\u0435\u043d\u0438\u044f.<\/p>\n<p><em>\u042d\u0442\u043e \u043e\u0442\u043b\u0438\u0447\u043d\u0430\u044f \u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442\u044c \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442 \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043b\u0438\u043d\u0442\u0435\u0440, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u0443\u0434\u0435\u0442 \u0443\u0432\u0435\u043b\u0438\u0447\u0438\u0432\u0430\u0442\u044c \u0441\u0432\u044f\u0437\u044c \u043c\u0435\u0436\u0434\u0443 \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442\u043e\u043c&nbsp; HTML \u0438 \u0441\u0435\u043b\u0435\u043a\u0442\u043e\u0440\u043e\u0432, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u043c\u044b\u0445 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445.<\/em><\/p>\n<p><strong>\u0418 \u043d\u0430\u043a\u043e\u043d\u0435\u0446, \u0441\u0432\u044f\u0437\u044c \u043c\u0435\u0436\u0434\u0443 page objects \u0438 \u0442\u0435\u0441\u0442\u0430\u043c\u0438 \u043e\u0447\u0435\u043d\u044c \u0432\u044b\u0441\u043e\u043a\u0430\u044f<\/strong> \u2014 \u0442\u0430\u043a \u043a\u0430\u043a \u043e\u0431\u0430 \u0443\u0440\u043e\u0432\u043d\u044f \u0432 \u0442\u043e\u043c \u0436\u0435 \u0441\u0430\u043c\u043e\u043c \u043a\u043e\u0434\u0435 \u0438 \u043c\u043e\u0433\u0443\u0442 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0442\u044c\u0441\u044f \u043a\u043e\u043c\u043f\u0438\u043b\u044f\u0442\u043e\u0440\u043e\u043c, \u0447\u0442\u043e\u0431\u044b \u043d\u0435 \u0431\u044b\u043b\u043e \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u043d\u044b\u0445 \u043e\u0448\u0438\u0431\u043e\u043a.<\/p>\n<h3>Page objects \u0432 Cypress<\/h3>\n<p>\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0441 \u043b\u0435\u0433\u043a\u043e\u0441\u0442\u044c\u044e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Page Objects \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 Cypress. \u0412\u043e\u0442 \u0442\u0438\u043f\u043e\u0432\u043e\u0439 \u043f\u0440\u0438\u043c\u0435\u0440 \u0438\u0437 \u0441\u0442\u0430\u0442\u044c\u0438 \u201c<a href=\"https:\/\/medium.com\/reactbrasil\/deep-diving-pageobject-pattern-and-using-it-with-cypress-e60b9d7d0d91\">Deep diving PageObject pattern and using it with Cypress<\/a>\u201d. \u0422\u0438\u043f\u043e\u0432\u043e\u0439 \u043a\u043b\u0430\u0441\u0441 PageObject SignInPage \u0441\u0445\u043e\u0436 \u0441 LoginPage \u0438\u0437 Selenium, \u0447\u0442\u043e \u0431\u044b\u043b \u043f\u043e\u043a\u0430\u0437\u0430\u043d \u0432\u044b\u0448\u0435.<\/p>\n<pre><code class=\"javascript\">class SignInPage {   visit() {     cy.visit('\/signin');   }    getEmailError() {     return cy.get(`[data-testid=SignInEmailError]`);   }    getPasswordError() {     return cy.get(`[data-testid=SignInPasswordError]`);   }    fillEmail(value) {     const field = cy.get(`[data-testid=SignInEmailField]`);     field.clear();     field.type(value);      return this;   }    fillPassword(value) {     const field = cy.get(`[data-testid=SignInPasswordField]`);     field.clear();     field.type(value);      return this;   }    submit() {     const button = cy.get(`[data-testid=SignInSubmitButton]`);     button.click();   } }  export default SignInPage;<\/code><\/pre>\n<p>\u041a\u043e\u0433\u0434\u0430 \u043c\u044b \u0433\u043e\u0442\u043e\u0432\u0438\u043c \u0442\u0435\u0441\u0442 \u0434\u043b\u044f \u201cHome page\u201d, \u043c\u044b \u043c\u043e\u0436\u0435\u043c \u0435\u0449\u0435 \u0440\u0430\u0437 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c&nbsp;<code>SignInPage<\/code> \u0438\u0437 \u0434\u0440\u0443\u0433\u043e\u0433\u043e Page Object.<\/p>\n<pre><code class=\"javascript\">import Header from '.\/Headers'; import SignInPage from '.\/SignIn';  class HomePage {   constructor() {     this.header = new Header();   }    visit() {     cy.visit('\/');   }    getUserAvatar() {     return cy.get(`[data-testid=UserAvatar]`);   }    goToSignIn() {     const link = this.header.getSignInLink();     link.click();      const signIn = new SignInPage();     return signIn;   } }  export default HomePage;<\/code><\/pre>\n<p>\u042d\u0442\u043e \u0442\u0438\u043f\u0438\u0447\u043d\u044b\u0439 \u0441\u0446\u0435\u043d\u0430\u0440\u0438\u0439 \u2014 \u0432\u0430\u043c \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u043d\u0430\u043f\u0438\u0441\u0430\u0442\u044c \u043f\u043e\u043b\u043d\u043e\u0441\u0442\u044c\u044e \u0438\u0435\u0440\u0430\u0440\u0445\u0438\u044e \u043a\u043b\u0430\u0441\u0441\u0430 <code>PageObject<\/code>, \u0433\u0434\u0435 \u0432\u0441\u0435 \u0447\u0430\u0441\u0442\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044e \u0440\u0430\u0437\u043d\u044b\u0435 \u043e\u0431\u044a\u0435\u043a\u0442\u044b \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u044b, \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u044f \u0438\u0445, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f object-oriented \u0434\u0438\u0437\u0430\u0439\u043d. \u0422\u0438\u043f\u043e\u0432\u043e\u0439 \u0442\u0435\u0441\u0442 \u0442\u043e\u0433\u0434\u0430 \u0432\u044b\u0433\u043b\u044f\u0434\u0438\u0442 \u0442\u0430\u043a:<\/p>\n<pre><code class=\"javascript\">import HomePage from '..\/elements\/pages\/HomePage';  describe('Sign In', () =&gt; {   it('should show an error message on empty input', () =&gt; {     const home = new HomePage();     home.visit();      const signIn = home.goToSignIn();      signIn.submit();      signIn.getEmailError()       .should('exist')       .contains('Email is required');      signIn       .getPasswordError()       .should('exist')       .contains('Password is required');   });    \/\/ more tests });<\/code><\/pre>\n<p>Cypress \u0432\u043a\u043b\u044e\u0447\u0430\u0435\u0442 \u0432 \u0441\u0435\u0431\u044f \u0431\u0430\u043d\u0442\u043b\u0435\u0440 JavaScript, \u0438 \u0442\u0430\u043a\u0438\u043c \u043e\u0431\u0440\u0430\u0437\u043e\u043c \u0440\u0430\u0431\u043e\u0442\u0430\u0435\u0442 \u043a\u043e\u0434, \u043f\u043e\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0439 \u0432\u044b\u0448\u0435.<\/p>\n<p>\u0412\u0430\u043c \u043d\u0435 \u043d\u0443\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c object-oriented PageObject. \u0412\u044b \u0442\u0430\u043a\u0436\u0435 \u043c\u043e\u0436\u0435\u0442\u0435 \u043f\u0435\u0440\u0435\u043c\u0435\u0441\u0442\u0438\u0442\u044c \u043f\u0435\u0440\u0435\u0432\u0435\u0441\u0442\u0438 \u0442\u0438\u043f\u0438\u0447\u043d\u0443\u044e \u043b\u043e\u0433\u0438\u043a\u0443 \u0432 \u0440\u0435\u0436\u0438\u043c \u043c\u043d\u043e\u0433\u043e\u0440\u0430\u0437\u043e\u0432\u043e\u0433\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f <a href=\"https:\/\/on.cypress.io\/custom-commands\">Cypress Custom Commands<\/a>, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0435 \u0438\u043c\u0435\u044e\u0442 \u043d\u0438\u043a\u0430\u043a\u043e\u0433\u043e \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u043f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u0438 \u043f\u0440\u043e\u0441\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043a\u043e\u0434 \u043f\u043e\u0432\u0442\u043e\u0440\u043d\u043e. \u041d\u0430\u043f\u0440\u0438\u043c\u0435\u0440, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c \u043a\u043e\u043c\u0430\u043d\u0434\u0443 \u201clogin\u201d.<\/p>\n<pre><code class=\"javascript\">\/\/ in cypress\/support\/commands.js Cypress.Commands.add('login', (username, password) =&gt; {   cy.get('#login-username').type(username)   cy.get('#login-password').type(password)   cy.get('#login').submit() })<\/code><\/pre>\n<p>\u041f\u043e\u0441\u043b\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0445 \u043a\u043e\u043c\u0430\u043d\u0434, \u0442\u0435\u0441\u0442\u044b \u0438\u0445 \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0442\u0430\u043a\u0436\u0435, \u043a\u0430\u043a \u0438 \u043a\u043e\u043c\u0430\u043d\u0434\u0443 <code>built-in<\/code>.<\/p>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/spec.js it('logs in', () =&gt; {   cy.visit('\/login')   cy.login('username', 'password') })<\/code><\/pre>\n<p>\u0417\u0430\u043c\u0435\u0442\u044c\u0442\u0435, \u0447\u0442\u043e \u0432\u0430\u043c \u043d\u0435 \u043d\u0430\u0434\u043e \u0432\u0441\u0435\u0433\u0434\u0430 \u0441\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043a\u043e\u043c\u0430\u043d\u0434\u044b, \u0430 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043f\u0440\u043e\u0441\u0442\u043e \u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0441 \u0444\u0443\u043d\u043a\u0446\u0438\u044f\u043c\u0438 JavaScript (\u043c\u043e\u0436\u0435\u0442 \u044d\u0442\u043e \u0434\u0430\u0436\u0435 \u0438 \u043b\u0443\u0447\u0448\u0435, \u043f\u043e\u0442\u043e\u043c\u0443 \u0447\u0442\u043e \u0442\u0438\u043f check step \u043c\u043e\u0436\u0435\u0442 \u043e\u043f\u0440\u0435\u0434\u0435\u043b\u044f\u0442\u044c \u0438\u043d\u0434\u0438\u0432\u0438\u0434\u0443\u0430\u043b\u044c\u043d\u044b\u0435 \u043f\u043e\u0434\u043f\u0438\u0441\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438.<\/p>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/util.js export const login = (username, password) =&gt; {   cy.get('#login-username').type(username)   cy.get('#login-password').type(password)   cy.get('#login').submit() }<\/code><\/pre>\n<pre><code class=\"javascript\">\/\/ cypress\/integration\/spec.js import { login } from '.\/util'  it('logs in', () =&gt; {   cy.visit('\/login')   login('username', 'password') })<\/code><\/pre>\n<h4>\u041f\u0440\u043e\u0431\u043b\u0435\u043c\u044b Page Objects<\/h4>\n<p>\u0412 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0440\u0430\u0437\u0434\u0435\u043b\u0430\u0445 \u044f \u0440\u0430\u0441\u0441\u043c\u043e\u0442\u0440\u044e \u043a\u043e\u043d\u043a\u0440\u0435\u0442\u043d\u044b\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u044b, \u0433\u0434\u0435 \u043f\u0430\u0442\u0442\u0435\u0440\u043d <code>PageObject<\/code> \u043d\u0435 \u0441\u043e\u043e\u0442\u0432\u0435\u0442\u0441\u0442\u0432\u0443\u0435\u0442 \u0442\u043e\u043c\u0443, \u0447\u0442\u043e \u043d\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u0434\u043b\u044f \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f \u0445\u043e\u0440\u043e\u0448\u0438\u0445 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0445 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<ul>\n<li>\n<p>\u041e\u0431\u044a\u0435\u043a\u0442\u044b <code>PageObject<\/code> \u0442\u0440\u0443\u0434\u043d\u043e \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0442\u044c, \u0438 \u044d\u0442\u043e \u043e\u0442\u043d\u0438\u043c\u0430\u0435\u0442 \u0432\u0440\u0435\u043c\u044f \u0443 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042f \u043d\u0438\u043a\u043e\u0433\u0434\u0430 \u043d\u0435 \u0432\u0438\u0434\u0435\u043b <code>PageObjects<\/code> \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u0445\u043e\u0440\u043e\u0448\u043e \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u043c\u0438, \u0447\u0442\u043e\u0431\u044b \u0440\u0435\u0430\u043b\u044c\u043d\u043e \u043f\u043e\u043c\u043e\u0447\u044c \u0432 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u0438 \u0442\u0435\u0441\u0442\u043e\u0432.<\/p>\n<\/li>\n<li>\n<p><code>PageObject<\/code> \u0432\u043d\u043e\u0441\u0438\u0442 \u0432 \u0442\u0435\u0441\u0442\u044b \u0434\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435, \u043a\u043e\u0442\u043e\u0440\u043e\u0435 \u043e\u0442\u0434\u0435\u043b\u0435\u043d\u043e \u043e\u0442 \u0432\u043d\u0443\u0442\u0440\u0435\u043d\u043d\u0435\u0433\u043e \u0437\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f. \u042d\u0442\u043e \u0437\u0430\u0442\u0440\u0443\u0434\u043d\u044f\u0435\u0442 \u043f\u043e\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0442\u0435\u0441\u0442\u043e\u0432 \u0438 \u043f\u0440\u0438\u0432\u043e\u0434\u0438\u0442 \u043a \u0441\u0431\u043e\u044f\u043c..<\/p>\n<\/li>\n<li>\n<p><code>PageObject<\/code> \u043f\u044b\u0442\u0430\u044e\u0442\u0441\u044f \u0432\u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432 \u0432 \u0435\u0434\u0438\u043d\u044b\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441, \u0432\u043e\u0437\u0432\u0440\u0430\u0449\u0430\u044f\u0441\u044c \u043a \u0443\u0441\u043b\u043e\u0432\u043d\u043e\u0439 \u043b\u043e\u0433\u0438\u043a\u0435 \u2014 \u043e\u0433\u0440\u043e\u043c\u043d\u044b\u0439, \u043d\u0430 \u043d\u0430\u0448 \u0432\u0437\u0433\u043b\u044f\u0434, <a href=\"https:\/\/on.cypress.io\/conditional-testing\"><u>\u0430\u043d\u0442\u0438\u043f\u0430\u0442\u0442\u0435\u0440\u043d<\/u><\/a>.<\/p>\n<\/li>\n<li>\n<p><code>PageObject<\/code> \u0434\u0435\u043b\u0430\u044e\u0442 \u0442\u0435\u0441\u0442\u044b \u043c\u0435\u0434\u043b\u0435\u043d\u043d\u044b\u043c\u0438, \u0442\u0430\u043a \u043a\u0430\u043a \u0437\u0430\u0441\u0442\u0430\u0432\u043b\u044f\u044e\u0442 \u0442\u0435\u0441\u0442\u044b \u0432\u0441\u0435\u0433\u0434\u0430 \u043f\u0440\u043e\u0445\u043e\u0434\u0438\u0442\u044c \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f.<\/p>\n<\/li>\n<\/ul>\n<p>\u041d\u0435 \u043e\u0442\u0447\u0430\u0438\u0432\u0430\u0439\u0442\u0435\u0441\u044c! \u042f \u0442\u0430\u043a\u0436\u0435 \u043f\u043e\u043a\u0430\u0436\u0443 \u0430\u043b\u044c\u0442\u0435\u0440\u043d\u0430\u0442\u0438\u0432\u0443 Page Objects, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u044f \u043d\u0430\u0437\u044b\u0432\u0430\u044e &#171;App Actions&#187;, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0433\u0443\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043d\u0430\u0448\u0438 \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b. \u042f \u0441\u0447\u0438\u0442\u0430\u044e, App Actions \u043e\u0447\u0435\u043d\u044c \u0445\u043e\u0440\u043e\u0448\u043e \u0440\u0435\u0448\u0430\u044e\u0442 \u0432\u044b\u0448\u0435\u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u043d\u044b\u0435 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u044b, \u0434\u0435\u043b\u0430\u044f \u0441\u043a\u0432\u043e\u0437\u043d\u044b\u0435 \u0442\u0435\u0441\u0442\u044b \u0431\u044b\u0441\u0442\u0440\u044b\u043c\u0438 \u0438 \u043f\u0440\u043e\u0434\u0443\u043a\u0442\u0438\u0432\u043d\u044b\u043c\u0438.<\/p>\n<h4>\u041f\u0440\u0438\u043c\u0435\u0440 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f \u043e\u0431\u044a\u0435\u043a\u0442\u043e\u0432<\/h4>\n<p>\u0412\u043e\u0437\u044c\u043c\u0435\u043c \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0442\u0435\u0441\u0442\u044b <a href=\"http:\/\/todomvc.com\/\"><u>TodoMVC<\/u><\/a>. \u0421\u043d\u0430\u0447\u0430\u043b\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043c, \u043c\u043e\u0436\u0435\u0442 \u043b\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0432\u0432\u043e\u0434\u0438\u0442\u044c todos. \u041c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Cypress \u0434\u043b\u044f \u044d\u0442\u043e\u0433\u043e \u0447\u0435\u0440\u0435\u0437 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u2014 \u0442\u043e\u0447\u043d\u043e \u0442\u0430\u043a \u0436\u0435, \u043a\u0430\u043a \u0438 \u0440\u0435\u0430\u043b\u044c\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0431\u0443\u0434\u0435\u0442 \u0432\u0432\u043e\u0434\u0438\u0442\u044c \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b.<\/p>\n<pre><code class=\"javascript\">describe('TodoMVC', function () {   \/\/ set up these constants to match what TodoMVC does   let TODO_ITEM_ONE = 'buy some cheese'   let TODO_ITEM_TWO = 'feed the cat'   let TODO_ITEM_THREE = 'book a doctors appointment'    beforeEach(function () {     cy.visit('\/')   })    context('New Todo', function () {     it('should allow me to add todo items', function () {       cy.get('.new-todo').type(TODO_ITEM_ONE).type('{enter}')       cy.get('.todo-list li').eq(0).find('label').should('contain', TODO_ITEM_ONE)       cy.get('.new-todo').type(TODO_ITEM_TWO).type('{enter}')       cy.get('.todo-list li').eq(1).find('label').should('contain', TODO_ITEM_TWO)     })      \/\/ more tests for adding items     \/\/ - adds items     \/\/ - should clear text input field when an item is added     \/\/ - should append new items to the bottom of the list     \/\/ - should trim text input     \/\/ - should show #main and #footer when items added   }) })<\/code><\/pre>\n<p>\u0412\u0441\u0435 \u044d\u0442\u0438 \u0442\u0435\u0441\u0442\u044b \u0432\u043d\u0443\u0442\u0440\u0438 \u0431\u043b\u043e\u043a\u0430 \u201cNew Todo\u201d \u0432\u0432\u043e\u0434\u044f\u0442 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b, \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u044f <code>&lt;input class=\"new-todo\" \/&gt;<\/code> \u0431\u0435\u0437&nbsp;shortcuts. \u0412\u043e\u0442 <\/p>\n<\/hr>\n<\/blockquote>\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-317736","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/317736","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=317736"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/317736\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=317736"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=317736"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=317736"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}