{"id":3590,"date":"2020-03-02T07:00:11","date_gmt":"2020-03-02T07:00:11","guid":{"rendered":"https:\/\/www.diogonunes.com\/blog\/?p=3590"},"modified":"2020-05-15T14:22:43","modified_gmt":"2020-05-15T13:22:43","slug":"cypress-pageobjects-vs-appactions","status":"publish","type":"post","link":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/","title":{"rendered":"Cypress: PageObjects vs AppActions"},"content":{"rendered":"<figure id=\"attachment_3654\" aria-describedby=\"caption-attachment-3654\" style=\"width: 1902px\" class=\"wp-caption aligncenter\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" src=\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?resize=580%2C610&#038;ssl=1\" alt=\"\" width=\"580\" height=\"610\" class=\"size-full wp-image-3654\" srcset=\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?w=1902&amp;ssl=1 1902w, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?resize=380%2C400&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?resize=974%2C1024&amp;ssl=1 974w, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?resize=768%2C808&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?resize=1461%2C1536&amp;ssl=1 1461w, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?resize=1200%2C1262&amp;ssl=1 1200w, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?w=1740&amp;ssl=1 1740w\" sizes=\"auto, (max-width: 580px) 100vw, 580px\" \/><figcaption id=\"caption-attachment-3654\" class=\"wp-caption-text\">Photo by Jason Dent on Unsplash<\/figcaption><\/figure>\n<p>I use Selenium to write most of my automated checks, and the <a href=\"https:\/\/thefriendlytester.co.uk\/2014\/05\/pageobject-pattern-why-how-and-more.html\">PageObjects pattern<\/a> is a must. My current team is using <a href=\"https:\/\/www.cypress.io\/\">Cypress<\/a> and, to my surprise, this test framework recommends <code>AppActions<\/code> instead of <code>PageObjects<\/code>. So I decided to benchmark both patterns using the following criteria:<\/p>\n<ul>\n<li>Can it abstract page selectors?<\/li>\n<li>Can it abstract page actions?<\/li>\n<li>Is it easy to write and maintain those abstractions?<\/li>\n<li>Is it easy to write tests?<\/li>\n<\/ul>\n<p><!--more--><\/p>\n<h3>A) PageObjects<\/h3>\n<p>A page looks like this&#8230;<\/p>\n<p><strong>page.js<\/strong><\/p>\n<pre><code class=\"js\">\/\/ Here you list the page selectors that you are currently using\nconst cssSearchBar = \".locationsContainer\"\nconst cssSearchField = \".select2-dropdown .select2-search__field\"\nconst cssSearchResults = \".select2-results__options\"\nconst cssSearchResultRow = \".select2-results__option\"\nconst cssFoundAdsTotal = \".offers-index &gt; strong\"\n\n\/\/ Each \"page object\" is a function with the name of the page\nexport function searchBuyFlatPage() {\n\n  \/\/ Read routes config files\n  return cy.fixture(ConfigHelper.getRoutesPath()).then(routes =&gt; {\n    return {\n\n      \/\/ Each page action is another function\n      visit() {\n        cy.visit(routes.buy.listFlats)\n      },\n\n      \/\/ and so on...\n      previewSearch(text) {\n        cy.get(cssSearchBar).click()\n        cy.get(cssSearchField).type(text)\n        cy.get(cssSearchResults).should(\"be.visible\")\n      },\n\n      getAutocompleteHints() {\n        return cy.get(searchResults)\n      }\n    }\n  })\n}\n<\/code><\/pre>\n<p>A test looks like this&#8230;<\/p>\n<p><strong>test.spec.js<\/strong><\/p>\n<pre><code class=\"js\">import { searchBuyFlatPage } from \"..\/..\/pages\/searchBuyFlatPage\"\n\nlet keywords\nbefore(() =&gt; {\n  cy.fixture(ConfigHelper.getKeywordsPath()).then(c =&gt; {\n    keywords = c\n  })\n})\n\ndescribe(\"Autocomplete\", function() {\n  it(\"displays results at different hierarchical levels\", function() {\n\n    \/\/ there's no page constructor, you just call a function with the name of the page, and then...\n    searchBuyFlatPage().then(page =&gt; {\n\n      \/\/ you use \"page\" to call actions\n      page.visit()\n\n      \/\/ and again, and so on\n      page.previewSearch(keywords.locationWithMultipleHierarchy)\n\n      page.getAutocompleteHints().should(\"contain\", keywords.locationWithMultipleHierarchy)\n      page.getAutocompleteHints().should(\"contain\", `(${keywords.hierarchyLevel2})`)\n      page.getAutocompleteHints().should(\"contain\", `(${keywords.hierarchyLevel3})`)\n    })\n  })\n})\n<\/code><\/pre>\n<hr \/>\n<h3>B) AppActions<\/h3>\n<p>An app action looks like this&#8230;<\/p>\n<p><strong>commands.js<\/strong><\/p>\n<pre><code class=\"js\">Cypress.Commands.add(\"searchFlatForBuy\", searchTerm =&gt; {\n  cy.log(\"searchFlatForBuy\")\n\n  const cssSearchBar = \".locationsContainer\" \/\/ code duplication of selectors (A)\n  const cssSearchField = \".select2-dropdown .select2-search__field\"\n  const cssAutocompleteHints = \".select2-results__options\"\n\n  cy.fixture(ConfigHelper.getRoutesPath()).then(routes =&gt; { \/\/ code duplication of fixture loading (B)\n    cy.visit(routes.buy.listFlats)\n  })\n  cy.get(cssSearchBar).click()\n  cy.get(cssSearchField).type(searchTerm)\n  return cy.get(cssAutocompleteHints).as(\"result\")\n})\n\nCypress.Commands.add(\"searchFlatForBuyUsingTree\", () =&gt; {\n  cy.log(\"searchFlatForBuyUsingTree\")\n\n  const cssSearchBar = \".locationsContainer\" \/\/ code duplication of selectors (A)\n  const cssAutocompleteHintRow = \".select2-results__option\"\n\n  cy.fixture(ConfigHelper.getRoutesPath()).then(routes =&gt; { \/\/ code duplication of fixture loading (B)\n    cy.visit(routes.buy.listFlats)\n  })\n\n  cy.fixture(ConfigHelper.getFixturesPath()).then(fixtures =&gt; {\n    const hierarchyLevels = fixtures.locations.hierarchyLevels\n\n    cy.get(cssSearchBar).click()\n    for (let i = 0; i &lt; hierarchyLevels; i++) {\n      cy.get(cssAutocompleteHintRow)\n        .eq(2)\n        .click()\n    }\n  })\n})\n<\/code><\/pre>\n<p>A test looks like this&#8230;<\/p>\n<p><strong>test.spec.js<\/strong><\/p>\n<pre><code class=\"js\">import { ConfigHelper } from \"..\/..\/support\/utils\/configHelper\"\n\nlet keywords\nbefore(() =&gt; {\n  cy.fixture(ConfigHelper.getKeywordsPath()).then(c =&gt; {\n    keywords = c\n  })\n})\n\ndescribe(\"Autocomplete\", function() {\n  it(\"displays results at different hierarchical levels\", function() {\n    \/\/ calls page action\n    cy.searchFlatForBuy(keywords.locationWithMultipleHierarchy).as(\"autocompleteHints\")\n    \/\/ and then asserts\n    cy.get(\"@autocompleteHints\").should(\"be.visible\")\n    cy.get(\"@autocompleteHints\").should(\"contain\", keywords.locationWithMultipleHierarchy)\n    cy.get(\"@autocompleteHints\").should(\"contain\", `(${keywords.hierarchyLevel2})`)\n    cy.get(\"@autocompleteHints\").should(\"contain\", `(${keywords.hierarchyLevel3})`)\n  })\n})\n<\/code><\/pre>\n<h3>Conclusions<\/h3>\n<blockquote>\n<p>Can it abstract page selectors?<\/p>\n<\/blockquote>\n<ul>\n<li><strong>PageObjects<\/strong>\n<ul>\n<li>\u2705 Encapsulated and reused inside each PageObject<\/li>\n<\/ul>\n<\/li>\n<li><strong>AppActions<\/strong>\n<ul>\n<li>\u274c Either duplicated selectors on each AppAction or long enumeration on <code>commands.js<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<blockquote>\n<p>Can it abstract page actions?<\/p>\n<\/blockquote>\n<ul>\n<li><strong>PageObjects<\/strong>\n<ul>\n<li>\u2705 Encapsulated inside each PageObject<\/li>\n<li>\u2705 Intuitive usage: <code>homepage.searchAds(\"Lisbon\")<\/code><\/li>\n<\/ul>\n<\/li>\n<li><strong>AppActions<\/strong>\n<ul>\n<li>\u2705 Encapsulated inside each AppAction<\/li>\n<li>\u26a0\ufe0f Not so intuitive usage: <code>cy.searchAds(\"Lisbon\")<\/code> \u2192 everything is <code>cy.*<\/code><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<blockquote>\n<p>Is it easy to maintain pages?<\/p>\n<\/blockquote>\n<ul>\n<li><strong>PageObjects<\/strong>\n<ul>\n<li>\u2705 Each page has a single file, named accordingly<\/li>\n<li>\u26a0\ufe0f Some UI changes will fail tests until the affected PageObjects are updated<\/li>\n<\/ul>\n<\/li>\n<li><strong>AppActions<\/strong>\n<ul>\n<li>\u274c Pages are used ad hoc inside actions; you might need to &#8220;Find\/Replace&#8221; changes to a page<\/li>\n<li>\u274c Fixtures load is duplicated on each command<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<blockquote>\n<p>Is it easy to write tests?<\/p>\n<\/blockquote>\n<ul>\n<li><strong>PageObjects<\/strong>\n<ul>\n<li>\u2705 IDE will autocomplete page actions<\/li>\n<li>\u2705 If pages and their actions are modular enough, tests are quite easy to write and understand<\/li>\n<\/ul>\n<\/li>\n<li><strong>AppActions<\/strong>\n<ul>\n<li>\u274c o IDE autocomplete, you need to skim the existing custom <code>commands.js<\/code> and decide which one works for you<\/li>\n<li>\u274c There might be a tendency to reinvent the wheel, because actions are blackboxes of functionality. Some devs might breakdown that functionality differently, which might lead to slightly diff duplicates of a single AppAction.<\/li>\n<li>\u26a0\ufe0f This syntax is more oriented for E2E, if you use it to write UI tests you will have a hard time \u2013 since you only care about user actions and not the underlying pages.<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h4>Notes<\/h4>\n<ul>\n<li>IDE Autocomplete issue\n<ul>\n<li><a href=\"https:\/\/github.com\/cypress-io\/add-cypress-custom-command-in-typescript\">This dependency<\/a> did make IDE autocomplete work for custom commands<\/li>\n<li>Based on <a href=\"https:\/\/github.com\/cypress-io\/cypress\/issues\/2293#issuecomment-412034813\">this comment<\/a> it seems like we need to write commands in TypeScript to have autocompletion<\/li>\n<\/ul>\n<\/li>\n<li>Selector issue\n<ul>\n<li>You can extract all selectors to a single <code>selectors.js<\/code> file and then&#8230; <code>import {searchBar} from '.\/common-selectors'<\/code> (<a href=\"https:\/\/github.com\/cypress-io\/testing-workshop-cypress\/blob\/master\/slides\/03-selector-playground\/PITCHME.md#cypress-is-just-javascript\">source<\/a>)<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>I use Selenium to write most of my automated checks, and the PageObjects pattern is a must. My current team is using Cypress and, to my surprise, this test framework recommends AppActions instead of PageObjects. So I decided to benchmark both patterns using the following criteria: Can it abstract page selectors? Can it abstract page [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3654,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[2],"tags":[29,74,22,55],"class_list":["post-3590","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tech","tag-coding","tag-cypress","tag-review","tag-testing"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.6 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Cypress: PageObjects vs AppActions - The Geeky Gecko<\/title>\n<meta name=\"description\" content=\"I use Selenium for most of my automated checks and PageObjects is a must. With Cypress they recommend AppActions. This is a benchmark between both patterns.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Cypress: PageObjects vs AppActions - The Geeky Gecko\" \/>\n<meta property=\"og:description\" content=\"I use Selenium for most of my automated checks and PageObjects is a must. With Cypress they recommend AppActions. This is a benchmark between both patterns.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/\" \/>\n<meta property=\"og:site_name\" content=\"The Geeky Gecko\" \/>\n<meta property=\"article:published_time\" content=\"2020-03-02T07:00:11+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2020-05-15T13:22:43+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1\" \/>\n\t<meta property=\"og:image:width\" content=\"1902\" \/>\n\t<meta property=\"og:image:height\" content=\"2000\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Diogo Nunes\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@dialexnunes\" \/>\n<meta name=\"twitter:site\" content=\"@dialexnunes\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Diogo Nunes\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"4 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/\"},\"author\":{\"name\":\"Diogo Nunes\",\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c\"},\"headline\":\"Cypress: PageObjects vs AppActions\",\"datePublished\":\"2020-03-02T07:00:11+00:00\",\"dateModified\":\"2020-05-15T13:22:43+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/\"},\"wordCount\":378,\"publisher\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c\"},\"image\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1\",\"keywords\":[\"coding\",\"cypress\",\"review\",\"testing\"],\"articleSection\":[\"Technology\"],\"inLanguage\":\"en-US\"},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/\",\"url\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/\",\"name\":\"Cypress: PageObjects vs AppActions - The Geeky Gecko\",\"isPartOf\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1\",\"datePublished\":\"2020-03-02T07:00:11+00:00\",\"dateModified\":\"2020-05-15T13:22:43+00:00\",\"description\":\"I use Selenium for most of my automated checks and PageObjects is a must. With Cypress they recommend AppActions. This is a benchmark between both patterns.\",\"breadcrumb\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage\",\"url\":\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1\",\"contentUrl\":\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1\",\"width\":1902,\"height\":2000,\"caption\":\"Photo by Jason Dent on Unsplash\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/www.diogonunes.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Cypress: PageObjects vs AppActions\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#website\",\"url\":\"https:\/\/www.diogonunes.com\/blog\/\",\"name\":\"The Geeky Gecko\",\"description\":\"The Geeky Gecko\",\"publisher\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/www.diogonunes.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":[\"Person\",\"Organization\"],\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c\",\"name\":\"Diogo Nunes\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2026\/04\/Geeky-Gecko-2026-v2.png?fit=799%2C799&ssl=1\",\"contentUrl\":\"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2026\/04\/Geeky-Gecko-2026-v2.png?fit=799%2C799&ssl=1\",\"width\":799,\"height\":799,\"caption\":\"Diogo Nunes\"},\"logo\":{\"@id\":\"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/image\/\"},\"sameAs\":[\"http:\/\/www.diogonunes.com\",\"https:\/\/x.com\/dialexnunes\"],\"url\":\"https:\/\/www.diogonunes.com\/blog\/author\/diogo-nunes\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Cypress: PageObjects vs AppActions - The Geeky Gecko","description":"I use Selenium for most of my automated checks and PageObjects is a must. With Cypress they recommend AppActions. This is a benchmark between both patterns.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/","og_locale":"en_US","og_type":"article","og_title":"Cypress: PageObjects vs AppActions - The Geeky Gecko","og_description":"I use Selenium for most of my automated checks and PageObjects is a must. With Cypress they recommend AppActions. This is a benchmark between both patterns.","og_url":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/","og_site_name":"The Geeky Gecko","article_published_time":"2020-03-02T07:00:11+00:00","article_modified_time":"2020-05-15T13:22:43+00:00","og_image":[{"width":1902,"height":2000,"url":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1","type":"image\/jpeg"}],"author":"Diogo Nunes","twitter_card":"summary_large_image","twitter_creator":"@dialexnunes","twitter_site":"@dialexnunes","twitter_misc":{"Written by":"Diogo Nunes","Est. reading time":"4 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#article","isPartOf":{"@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/"},"author":{"name":"Diogo Nunes","@id":"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c"},"headline":"Cypress: PageObjects vs AppActions","datePublished":"2020-03-02T07:00:11+00:00","dateModified":"2020-05-15T13:22:43+00:00","mainEntityOfPage":{"@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/"},"wordCount":378,"publisher":{"@id":"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c"},"image":{"@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1","keywords":["coding","cypress","review","testing"],"articleSection":["Technology"],"inLanguage":"en-US"},{"@type":"WebPage","@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/","url":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/","name":"Cypress: PageObjects vs AppActions - The Geeky Gecko","isPartOf":{"@id":"https:\/\/www.diogonunes.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage"},"image":{"@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage"},"thumbnailUrl":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1","datePublished":"2020-03-02T07:00:11+00:00","dateModified":"2020-05-15T13:22:43+00:00","description":"I use Selenium for most of my automated checks and PageObjects is a must. With Cypress they recommend AppActions. This is a benchmark between both patterns.","breadcrumb":{"@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#primaryimage","url":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1","contentUrl":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1","width":1902,"height":2000,"caption":"Photo by Jason Dent on Unsplash"},{"@type":"BreadcrumbList","@id":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-vs-appactions\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.diogonunes.com\/blog\/"},{"@type":"ListItem","position":2,"name":"Cypress: PageObjects vs AppActions"}]},{"@type":"WebSite","@id":"https:\/\/www.diogonunes.com\/blog\/#website","url":"https:\/\/www.diogonunes.com\/blog\/","name":"The Geeky Gecko","description":"The Geeky Gecko","publisher":{"@id":"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.diogonunes.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":["Person","Organization"],"@id":"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/a6fa79b293f22912664654fcfbd2da0c","name":"Diogo Nunes","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2026\/04\/Geeky-Gecko-2026-v2.png?fit=799%2C799&ssl=1","contentUrl":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2026\/04\/Geeky-Gecko-2026-v2.png?fit=799%2C799&ssl=1","width":799,"height":799,"caption":"Diogo Nunes"},"logo":{"@id":"https:\/\/www.diogonunes.com\/blog\/#\/schema\/person\/image\/"},"sameAs":["http:\/\/www.diogonunes.com","https:\/\/x.com\/dialexnunes"],"url":"https:\/\/www.diogonunes.com\/blog\/author\/diogo-nunes\/"}]}},"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/02\/jason-dent-JVD3XPqjLaQ-unsplash.jpg?fit=1902%2C2000&ssl=1","jetpack-related-posts":[{"id":3978,"url":"https:\/\/www.diogonunes.com\/blog\/framework-review-cypress\/","url_meta":{"origin":3590,"position":0},"title":"Framework review: Cypress","author":"Diogo Nunes","date":"4 January, 2021","format":false,"excerpt":"Fast and reliable testing for anything that runs in a browser. It uses Javascript to make setting up, writing, running and debugging tests easy \u2014 for QAs and developers. \u2014 Official website Code Example of automation at GitHub. Use cases \ud83e\udd47 Automate end-to-end (E2E) tests using the UI or the\u2026","rel":"","context":"In &quot;Technology&quot;","block_context":{"text":"Technology","link":"https:\/\/www.diogonunes.com\/blog\/category\/tech\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/09\/herbert-goetsch-vImJ5GYMMqQ-unsplash-1.jpg?fit=1200%2C800&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/09\/herbert-goetsch-vImJ5GYMMqQ-unsplash-1.jpg?fit=1200%2C800&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/09\/herbert-goetsch-vImJ5GYMMqQ-unsplash-1.jpg?fit=1200%2C800&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/09\/herbert-goetsch-vImJ5GYMMqQ-unsplash-1.jpg?fit=1200%2C800&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/09\/herbert-goetsch-vImJ5GYMMqQ-unsplash-1.jpg?fit=1200%2C800&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3702,"url":"https:\/\/www.diogonunes.com\/blog\/how-to-build-docker-image-cypress-tests\/","url_meta":{"origin":3590,"position":1},"title":"How to build a Docker image ready to run Cypress tests","author":"Diogo Nunes","date":"11 May, 2020","format":false,"excerpt":"My team decided to build a Docker image that contained Cypress, dependencies and all our end-to-end (E2E) tests. That way, anyone could simply pull the image and with a single command it was ready to run tests. Also, the developers and the CI pipeline we both using the same image,\u2026","rel":"","context":"In &quot;Technology&quot;","block_context":{"text":"Technology","link":"https:\/\/www.diogonunes.com\/blog\/category\/tech\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/cypress-on-docker.png?fit=1200%2C488&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/cypress-on-docker.png?fit=1200%2C488&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/cypress-on-docker.png?fit=1200%2C488&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/cypress-on-docker.png?fit=1200%2C488&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/cypress-on-docker.png?fit=1200%2C488&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3942,"url":"https:\/\/www.diogonunes.com\/blog\/cypress-tips-tricks\/","url_meta":{"origin":3590,"position":2},"title":"Tips &#038; Tricks for Cypress","author":"Diogo Nunes","date":"18 January, 2021","format":false,"excerpt":"\ud83c\udfc6 This post was featured in Software Testing Weekly, issue 55 This is a collection of simple and recurring scenarios when writing Cypress tests. For more complex recipes, check the official doc. Setup Abort cypress after first failed test Read a test file from fixtures Assertions Assert the text of\u2026","rel":"","context":"In &quot;Technology&quot;","block_context":{"text":"Technology","link":"https:\/\/www.diogonunes.com\/blog\/category\/tech\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/08\/hunter-haley-s8OO2-t-HmQ-unsplash-scaled.jpg?fit=1200%2C800&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/08\/hunter-haley-s8OO2-t-HmQ-unsplash-scaled.jpg?fit=1200%2C800&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/08\/hunter-haley-s8OO2-t-HmQ-unsplash-scaled.jpg?fit=1200%2C800&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/08\/hunter-haley-s8OO2-t-HmQ-unsplash-scaled.jpg?fit=1200%2C800&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/08\/hunter-haley-s8OO2-t-HmQ-unsplash-scaled.jpg?fit=1200%2C800&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3709,"url":"https:\/\/www.diogonunes.com\/blog\/cypress-automation-template\/","url_meta":{"origin":3590,"position":3},"title":"Cypress Sapling (automation template)","author":"Diogo Nunes","date":"18 May, 2020","format":false,"excerpt":"No need to start with the seed \u2013 plant the sapling! Get it? Because \"Cypress\" is a tree... \ud83e\udd13 On my last project we chose Cypress to automate our E2E tests. During a year and a half we constantly tweaked and improved our test repository. We added more functionary on\u2026","rel":"","context":"In &quot;Technology&quot;","block_context":{"text":"Technology","link":"https:\/\/www.diogonunes.com\/blog\/category\/tech\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/tzingtao-chow-J8oxnYHBpWM-unsplash-scaled.jpg?fit=960%2C1200&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/tzingtao-chow-J8oxnYHBpWM-unsplash-scaled.jpg?fit=960%2C1200&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/tzingtao-chow-J8oxnYHBpWM-unsplash-scaled.jpg?fit=960%2C1200&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/tzingtao-chow-J8oxnYHBpWM-unsplash-scaled.jpg?fit=960%2C1200&ssl=1&resize=700%2C400 2x"},"classes":[]},{"id":3685,"url":"https:\/\/www.diogonunes.com\/blog\/asserting-text-using-cypress\/","url_meta":{"origin":3590,"position":4},"title":"Asserting text using Cypress","author":"Diogo Nunes","date":"6 July, 2020","format":false,"excerpt":"If you're using Cypress, eventually you will have to assert some text. However, they provide at least three methods to do that, and from the documentation is not clear the difference between: .should(\"have.text\", \"expected text goes here\") .should(\"include.text\", \"expected text goes here\") .should(\"contain.text\", \"expected text goes here\") Do they all\u2026","rel":"","context":"In &quot;Technology&quot;","block_context":{"text":"Technology","link":"https:\/\/www.diogonunes.com\/blog\/category\/tech\/"},"img":{"alt_text":"Photo by Sunyu on Unsplash","src":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/sunyu-jtjS4F8Q7sY-unsplash-scaled.jpg?fit=1200%2C798&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/sunyu-jtjS4F8Q7sY-unsplash-scaled.jpg?fit=1200%2C798&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/sunyu-jtjS4F8Q7sY-unsplash-scaled.jpg?fit=1200%2C798&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/sunyu-jtjS4F8Q7sY-unsplash-scaled.jpg?fit=1200%2C798&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/sunyu-jtjS4F8Q7sY-unsplash-scaled.jpg?fit=1200%2C798&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3717,"url":"https:\/\/www.diogonunes.com\/blog\/cypress-pageobjects-inheritance-js\/","url_meta":{"origin":3590,"position":5},"title":"Cypress: PageObjects using inheritance","author":"Diogo Nunes","date":"1 June, 2020","format":false,"excerpt":"Scenario: Your web site is deployed on several countries. The behaviour of the page you want to test (e.g. sign up) is mostly the same across countries, however some business rules change per country. You are using the PageObjects pattern to encapsulate the details of each page. You want to\u2026","rel":"","context":"In &quot;Technology&quot;","block_context":{"text":"Technology","link":"https:\/\/www.diogonunes.com\/blog\/category\/tech\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/68747470733a2f2f65726c616e67656e776c6164696d69722e66696c65732e776f726470726573732e636f6d2f323031352f30352f6d6174726a6f7363686b612d332e6a7067.jpeg?fit=1200%2C800&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/68747470733a2f2f65726c616e67656e776c6164696d69722e66696c65732e776f726470726573732e636f6d2f323031352f30352f6d6174726a6f7363686b612d332e6a7067.jpeg?fit=1200%2C800&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/68747470733a2f2f65726c616e67656e776c6164696d69722e66696c65732e776f726470726573732e636f6d2f323031352f30352f6d6174726a6f7363686b612d332e6a7067.jpeg?fit=1200%2C800&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/68747470733a2f2f65726c616e67656e776c6164696d69722e66696c65732e776f726470726573732e636f6d2f323031352f30352f6d6174726a6f7363686b612d332e6a7067.jpeg?fit=1200%2C800&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.diogonunes.com\/blog\/wp-content\/uploads\/2020\/04\/68747470733a2f2f65726c616e67656e776c6164696d69722e66696c65732e776f726470726573732e636f6d2f323031352f30352f6d6174726a6f7363686b612d332e6a7067.jpeg?fit=1200%2C800&ssl=1&resize=1050%2C600 3x"},"classes":[]}],"jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/posts\/3590","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/comments?post=3590"}],"version-history":[{"count":3,"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/posts\/3590\/revisions"}],"predecessor-version":[{"id":3663,"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/posts\/3590\/revisions\/3663"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/media\/3654"}],"wp:attachment":[{"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/media?parent=3590"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/categories?post=3590"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.diogonunes.com\/blog\/wp-json\/wp\/v2\/tags?post=3590"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}