<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Roman Orlov</title>
    <description>The latest articles on DEV Community by Roman Orlov (@10-minutes-qa-story).</description>
    <link>https://dev.to/10-minutes-qa-story</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1073017%2F9a3b81b1-b782-4e2b-93a4-60c89aa92d29.jpeg</url>
      <title>DEV Community: Roman Orlov</title>
      <link>https://dev.to/10-minutes-qa-story</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/10-minutes-qa-story"/>
    <language>en</language>
    <item>
      <title>Have your Page Object avoid these pitfalls?</title>
      <dc:creator>Roman Orlov</dc:creator>
      <pubDate>Sun, 25 Feb 2024 11:18:18 +0000</pubDate>
      <link>https://dev.to/10-minutes-qa-story/have-your-page-object-avoid-these-pitfalls-58f6</link>
      <guid>https://dev.to/10-minutes-qa-story/have-your-page-object-avoid-these-pitfalls-58f6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome!&lt;/p&gt;

&lt;p&gt;In the realm of software test automation, Page Objects are indispensable. They serve as a bridge between the intricacies of the applications and the automated test scripts that ensure their functionality and reliability. When implemented effectively, Page Objects can simplify the creation and maintenance of test automation scripts, making them more robust and adaptable to changes in the user interface (UI).&lt;/p&gt;

&lt;p&gt;However, like any powerful tool, Page Objects are not immune to pitfalls that can hinder the effectiveness of your test automation efforts. These pitfalls, if left unaddressed, can lead to brittle tests, increased maintenance overhead, and frustrated automation teams. In this article, we’ll delve into the world of Page Objects and explore the common mistakes and missteps that can plague their implementation.&lt;/p&gt;

&lt;p&gt;So, fasten your seatbelts and get ready to navigate the terrain of Page Object best practices. Feel free to follow me on &lt;a href="https://t.me/ta_stories" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt; as well where you can find more content about QA and development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep locators open for interaction
&lt;/h2&gt;

&lt;p&gt;Once I got a rather simple implementation task. Something changed in a simple user action on a certain page. An additional click was added or removed — it doesn’t matter, the focus is on changes in the behavior of our page.&lt;/p&gt;

&lt;p&gt;“Well, what could be complicated here, you just need to change the corresponding method” — I thought. And I was right… Partly…&lt;/p&gt;

&lt;p&gt;As soon as the changes were made, I ran all the tests to make sure they would apply correctly. And as a result… several tests kept failing.&lt;/p&gt;

&lt;p&gt;After a little research, it turned out that the locators on this page were open for external interaction and someone took advantage of this, bypassing the Page Object layer in the test itself. So the test failures after correction are a consequence of violating the encapsulation principle of the page-object.&lt;/p&gt;

&lt;p&gt;Let me show you an example of interaction with a &lt;a href="https://sweetshop.netlify.app/" rel="noopener noreferrer"&gt;simple application&lt;/a&gt; how it looked:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Main page Page Object class&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainPage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;WebDriver&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="nd"&gt;@FindBy&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;how&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;How&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CSS&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;using&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"a.btn-primary.sweets"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;WebElement&lt;/span&gt; &lt;span class="n"&gt;buttonBrowse&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;MainPage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebDriver&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="nc"&gt;PageFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;initElements&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;clickBrowseButton&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;buttonBrowse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code, you can see that the locator is open for interaction directly from the test. This is a bit puzzling, because now I have two options for interacting with the element:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Call the Selenium click() method for buttonBrowse&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Call the clickBrowseButton() method&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AnXjsJ3Wn79gzoognU-YU9Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AnXjsJ3Wn79gzoognU-YU9Q.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Take a pause and think about which way you would choose to implement and why.&lt;/p&gt;

&lt;p&gt;I still prefer the second one, because the first one does not take into account the following points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I don’t know exactly what should happen when you click on a button. This can be either a simple click or a complex logic like “wait until the button is active and then click on it”. The click() method only covers the first option. It's better to delegate the click logic to a special page object that is "trained" to work with elements correctly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Let’s now think about what will happen if we would need to rename the element. If I used the fields from the Page Object directly, every test that uses this page and calls this field from it will fall into the list of changes when renaming. This will lead to a significant increase in the number of files in the Pull Request for a [relatively simple] task, which will complicate the Code Review process&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And what if the click logic changes? For example, the button is now not immediately displayed on the page, but is loaded depending on the data received from the API. And again, we return to the fact that we will have to change the logic in all the tests this field is used in. In addition to this, the use of “direct” interaction in tests does not give an understanding from the code of how this logic works (unlike the method, which I can open and see that we are waiting for a response from the API)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Store elements, not locators in class fields
&lt;/h2&gt;

&lt;p&gt;One day I decided to improve our page objects to provide more readable and accessible functionality to develop automated tests. My suggestion was to have a “Pages hub” — a place where all pages are stored. It seemed very convenient to call the pages like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Pages&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;clickBrowseButton&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main advantage was not to keep in mind all the pages set I have in project (and remember all the class names). Now I have only one class and can easily find the required one.&lt;/p&gt;

&lt;p&gt;When I refactored the code my tests became fail if the test number was more than 1. After a small research I understood that problem was related to proxy pattern using for initializing the elements so you need to be accurate with the pages hub initialization (where and how you do it).&lt;/p&gt;

&lt;p&gt;The final solution I came up to was to store locators instead of elements in page objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AjaxDataPage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;By&lt;/span&gt; &lt;span class="n"&gt;buttonAjax&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;By&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ajaxButton"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;By&lt;/span&gt; &lt;span class="n"&gt;ajaxContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;By&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;cssSelector&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#content &amp;gt; p"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loadContent&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findElement&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buttonAjax&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The approach is pretty much the same but:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Can extract WebDriver to somewhere else and I do not need to create a constructor for every page object created (e.g. create BasePage class and initialize driver there or write a singletone class for WebDriver)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No more PageFactory.init() methods to initialize the page object&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No @FindBy annotation for each field and it looks much more cleaner&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No more headeache about proxy pattern Selenium annotations use&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pretty sure the case is arguable as it’s rare. Anyway, the page object class looks cleaner and it simplify the work with such an objects. The next step could be to create a wrap class to operate with the element where complicated logic can be stored, not only clicks, sending keys etc. But let’s get back to our main topic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Page Object contains the full page URL
&lt;/h2&gt;

&lt;p&gt;One of the advantages Page Object pattern could provide for us is to open the page directly. But for using such an advantage Page Object should store the URL page can be opened by. The obvious trick here (at least I hope so 🙂) is not to store the URL in absolute manner, but the relative one.&lt;/p&gt;

&lt;p&gt;Let me tell you what happend to absolute URLs. There was only one evironment for tests so at the first look it does not matter to have configuration class or not. There also was someone who had chosen the most complicated way to open the required page in the tests — to open the main one and then click to the required. Worth it to say such an approach leads to increasing execution time compared to open the page directly by the link? In such a situation you’are intended to use full URL for the page to open, but please don’t.&lt;/p&gt;

&lt;p&gt;To avoid the hardcode you need to store the base (application) URL in the config file that is reading before test execution and can be dynamically applied to the Page Object. We can manage what environment is chosen by passing the ENV variable while test running or the parameter in CI/CD server for the specific build.&lt;/p&gt;

&lt;p&gt;Another one profit you can get by parameterize the page URL is “easy to move” between the applications (especially if you have multi-domain architecture).&lt;/p&gt;

&lt;p&gt;Let’s see the example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ajax page we're going to interact with&lt;/span&gt;
&lt;span class="c1"&gt;// Page domain has changed? Just replace the UiTestingPlaygroundPage class with the new one&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AjaxDataPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;UiTestingPlaygroundPage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;AjaxDataPage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WebDriver&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"/ajax"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Base page (application page)&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UiTestingPlaygroundPage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Not using hardcode but the config file instead&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBaseTestingPlaygroundBaseUrl&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Keep this method in base page so we can use it in any page with the specific base url&lt;/span&gt;
    &lt;span class="c1"&gt;// Basically, it's worth to create a "Base" class for all the pages and implement the common methods&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s%s"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;baseUrl&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Test example&lt;/span&gt;
&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;testAjaxPage&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;AjaxDataPage&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AjaxDataPage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;open&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadContent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;isAjaxContentLoaded&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Operating with WebElements in complex methods
&lt;/h2&gt;

&lt;p&gt;When we talk about methods in Page Objects, keep in mind that the page object itself is a full-fledged entity, responsible for actions on this page. This formulation seems clear when it comes to simple interactions like a click or text input — you need to create separate methods for these operations, not call fields from the page object in the test. However, what to do when it comes to non-atomic actions?&lt;/p&gt;

&lt;p&gt;A good example is user login. Being on one page, we perform several actions — we enter the login, password, and press the login button. An example of code describing this function could be as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;span class="c1"&gt;// locators, constructors etc.&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usernameTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;passwordTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loginButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, such an organization repeats the mistake from the previous point and carries the same consequences — if something changes in the behavior of the application we would have to change the actions given over one or another element in each places this element used in.&lt;/p&gt;

&lt;p&gt;Let’s imagine that I have a second method that is responsible for the user’s login by saving session data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// locators, constructors etc.&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usernameTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;passwordTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loginButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// Epic naming, I know ...&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loginForever&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usernameTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;passwordTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rememberMeCheckbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loginButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if something changes in the login behavior, I will need to update the code in 2 methods.&lt;/p&gt;

&lt;p&gt;This example is quite trivial, it is easy to understand, but in practice there are significantly more complex cases that will require more mental effort from you.&lt;/p&gt;

&lt;p&gt;What to do? The answer is obvious: create atomic methods for each operation. This will transfer some of the responsibility for repeating actions to separate methods and give more flexibility, since you will be able to use atomic methods in tests in specific cases where it is not required to perform a long scenario (but be careful, I do not recommend storing long scenarios in page objects, all page object methods must be short and logically completed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// locators, constructors etc.&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;fillLogin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;usernameTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;fillPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;passwordTextbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;clickLogin&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loginButton&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;setRememberMe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bool&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; 
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rememberMeCheckbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;check&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rememberMeCheckbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;uncheck&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fillLogin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;fillPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;clickLogin&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;loginForever&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fillLogin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;fillPassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;setRememberMe&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;clickLogin&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can assemble complex actions like a constructor from simple ones. At the same time when changing the behavior of a simple method, you will only need to correct one place, the other methods will remain untouched.&lt;/p&gt;

&lt;h2&gt;
  
  
  Asserting in Page Object
&lt;/h2&gt;

&lt;p&gt;At first look, this idea seems logical. We have a page object, why can’t we delegate to it the verification of the presence of a certain element/text/something else? It seems that such delegation is a continuation of the previous point, where we make a more flexible structure of pages by creating atomic methods and constructing more complex ones from them.&lt;/p&gt;

&lt;p&gt;Okay, let’s say we go this way. What could go wrong?&lt;/p&gt;

&lt;p&gt;Firstly, we “share” verifications in the test across our framework. If we shift part of the verification logic to the page object, then the library for running tests will have to be pulled up depending on the classes of page objects. Not convincing? Probably.&lt;/p&gt;

&lt;p&gt;The second point to consider is the redundant methods that will inevitably appear when transferring the layer of checks to the page object. I’ll show this with an example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomePage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BasePage&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// locators, constructors etc.&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;getHeaderText&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getText&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;checkHeaderText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getHeaderText&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;assertEquals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"expected text: '"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"', but was: '"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;actual&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"'"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It seems like our structure has become slightly overloaded. Instead of having one method to retrieve some data from the page, we’re adding another one responsible for validation. But what if we need to add a check that the text differs from the given one, contains a specific sequence of characters, etc.? For each variation, we’ll have to write a separate method in the page object. As a result, we’ll end up with quite a large chunk of code.&lt;/p&gt;

&lt;p&gt;This argument looks more convincing already. Is there another one?&lt;/p&gt;

&lt;p&gt;Certainly. It’s worth mentioning that the Selenium developers themselves &lt;a href="https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/#assertions-in-page-objects" rel="noopener noreferrer"&gt;recommend&lt;/a&gt; to extract validations in the tests themselves, rather than keep them to the page objects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe same elements in different Page Objects
&lt;/h2&gt;

&lt;p&gt;Modern web applications are built on frameworks such as React, Vue, Angular (add your own to this list 🙂). This means that all pages use components and layouts to display various elements, i.e., during development, a piece of code is written once and then embedded into the necessary pages.&lt;/p&gt;

&lt;p&gt;I’ve also encountered this in my practice. Instead of following a similar approach in tests, a framework was created that adhered to the principle of “tests should be as simple as possible.” I’m not disputing this statement by the way, but it’s important to distinguish between “simple” and “clumsy” 🙂&lt;/p&gt;

&lt;p&gt;As expected, the structure of the page changed at some point, and all tests using a certain component started failing. Do I need to explain how much time was spent changing the old logic in all pages of the project and how large the Pull Request became afterwards? This could have been easily avoided by not duplicating elements on the page.&lt;/p&gt;

&lt;p&gt;So what should we do? As mentioned above, reusable page elements should be extracted into components and then connected to the page itself as dependencies. As for layouts, I prefer the solution of inheriting from specific page objects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
We inherit the layout here to get access to common admin elements like Edit/Delete buttons, Sidebars etc
Do it for every admin page with the same layout
*/&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactListPage&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;AdminLayout&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// And here we have a component injection&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;ContactListComponent&lt;/span&gt; &lt;span class="n"&gt;contactsList&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContactListComponent&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Example of component code&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ContactListComponent&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;By&lt;/span&gt; &lt;span class="n"&gt;selectAllCheckbox&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;By&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sssSelector&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"#select-all"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;selectAllContacts&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;selectAllCheckbox&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;click&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;allContactsDelete&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;ContactListPage&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ContactListPage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// driver is already initialized &lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;contactList&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;selectAll&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="cm"&gt;/*
    This is a common component for admin view so we can use it thanks to AdminLayout
    inheritance.
    */&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;adminBar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;delete&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Actually, using components opens up more possibilities for us. For example, we can use this approach for easy automation of responsive design or implementation of the strategy pattern if we’re writing E2E tests in a microservices architecture. I understand that it may not sound very clear, but that’s not the goal of this article. If you’re interested in what is meant by easy automation of responsive design and the strategy pattern then 👏, I’ll definitely delve into these topics in the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not using DSL for Page Object methods
&lt;/h2&gt;

&lt;p&gt;I really like the idea of OOP. Mostly because we write programs not as we were taught in school (scripts, a set of commands), but as if we interact with the world through objects. From the outside, we don’t know how a particular object behaves, but we know that we can fully trust it to solve a certain task and not interfere, altering its behavior with our logic. This approach significantly changes the way we live and think, but that’s not the point right now 🙂&lt;/p&gt;

&lt;p&gt;Since a Page Object is also an object, it should be autonomous and independent. This requirement imposes certain restrictions on the naming of methods within. For example, if we consider an object as just an instance of a class, then the method name clickStartButton() doesn't seem so bad. However, when we perceive an object as an autonomous unit capable of solving tasks for us, such naming doesn't seem like a good idea anymore.&lt;/p&gt;

&lt;p&gt;And why is that? Let me try to explain. A page object is created to solve tasks related to interacting with the page. But clicking a button itself is not a fully fledged task; rather, it’s an action. It’s more important to ask the question: “Why are we clicking the button? What the result do we want to achieve by pressing it?”&lt;/p&gt;

&lt;p&gt;If we pay attention to these two questions during the design and implementation of the page object, our methods automatically become more deliberate, giving the user (QA automation engineer) more information about what we are doing and why. Compare these two method names: clickStartButton() and startQuiz(). Which one provides more context? Which one represents a logically complete business action?&lt;/p&gt;

&lt;p&gt;Try writing a test using this technique, and you’ll be surprised how pleasant it is to read afterwards. Try letting someone who is unfamiliar with the product read this test and ask him what it does. I bet $10 they’ll understand much more from the DSL description than by interpreting a sequence of clicks and data entry.&lt;/p&gt;

&lt;p&gt;When I talk about DSL approach in tests, I’m not just voicing my thoughts, which are the result of long search and work on numerous projects, but I also refer to Selenium developers. They also &lt;a href="https://www.selenium.dev/documentation/test_practices/encouraged/domain_specific_language/" rel="noopener noreferrer"&gt;recommend&lt;/a&gt; using this approach when designing Page Objects. If you’re hearing about this for the first time, I highly recommend exploring and trying it out.&lt;/p&gt;

&lt;h2&gt;
  
  
  Don’t utilize Fluent API pattern
&lt;/h2&gt;

&lt;p&gt;I don’t know what’s the matter but for some reason automation testers don’t seem to like using this pattern on real projects. In my opinion, it’s very unwarranted. Chaining methods is much more convenient than dragging a bunch of page objects into a test or scenario class and “losing context” while writing the test itself.&lt;/p&gt;

&lt;p&gt;I already have a couple of articles on this topic, just take a look at how much neater the code becomes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/10-minutes-qa-story/fluent-api-pattern-implementation-with-playwright-and-javascript-typescript-b39a38091159" rel="noopener noreferrer"&gt;Fluent API pattern for Playwright framework&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/10-minutes-qa-story/let-me-introduce-the-fluent-interface-pattern-66fbc61e647c" rel="noopener noreferrer"&gt;Building objects with Fluent API pattern&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://medium.com/10-minutes-qa-story/redirect-logic-in-page-object-f7a998efa86" rel="noopener noreferrer"&gt;Redirection logic with Fluent API pattern&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we’re talking about testing, this pattern has another huge advantage. When our code “knows” which page should open after a certain action, we stop thinking about it. This means that we can focus on the test logic instead of constantly returning to the question “Which page will open after the action I perform?” Just try it, and you’ll see how convenient it is 🙂&lt;/p&gt;

&lt;p&gt;This pattern seems extremely simple to me, but I rarely encounter such implementation in practice. Am I mistaken, and does it actually cause difficulties in work? 🤔 Share your opinion in the comments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Today we’ve discussed typical mistakes in designing page objects. If you want to recap the thoughts, please keep in mind that I described errors under the subtitles, so the theses should be read as non-. For example, if the subtitle says “Describe same elements in different Page Objects” then you need to add negation to it to get a good practice instead of a bad one: “Don’t describe same elements in different Page Objects”&lt;/p&gt;

&lt;p&gt;Don’t forget to share your thoughts in the comments, and keep automating! Bye for now!&lt;/p&gt;

</description>
      <category>qa</category>
      <category>selenium</category>
      <category>testing</category>
    </item>
    <item>
      <title>Fluent API pattern implementation with Playwright and Javascript/Typescript</title>
      <dc:creator>Roman Orlov</dc:creator>
      <pubDate>Sun, 11 Feb 2024 12:53:56 +0000</pubDate>
      <link>https://dev.to/10-minutes-qa-story/fluent-api-pattern-implementation-with-playwright-and-javascripttypescript-2lk1</link>
      <guid>https://dev.to/10-minutes-qa-story/fluent-api-pattern-implementation-with-playwright-and-javascripttypescript-2lk1</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2760%2F1%2AH17r3yZaQTaEJOzgl7aGyw.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2760%2F1%2AH17r3yZaQTaEJOzgl7aGyw.jpeg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Welcome!&lt;/p&gt;

&lt;p&gt;In today’s post, I’ll talk about one of my favorite patterns for UI testing. I won’t go into detail about what it is and why you should use it. My goal today is to demonstrate the implementation of this pattern when working with Playwright and Javascript/Typescript. If after reading and analyzing the implementation examples you still have questions, I recommend reading more about this pattern &lt;a href="https://java-design-patterns.com/patterns/fluentinterface/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you liked the post, support it with a like and a comment, and also join the &lt;a href="https://t.me/ta_stories" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt;, where I post a little more about my tasks and thoughts on the topic of software development and testing.&lt;/p&gt;

&lt;p&gt;So, let’s go 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  Java first
&lt;/h2&gt;

&lt;p&gt;I really like it when I don’t have to keep in mind what step of the scenario I am at and which page is open at the moment while I’m writing automated test. It seems like a small thing, but actually it complicates development due to so-called information noise. First and foremost, when I am busy with writing a test, I want to focus on the test logic test, not keeping a heap of service information in my head.&lt;/p&gt;

&lt;p&gt;This problem can be easily solved in typed programming languages (such as Java, C#). I will show you an example and you will immediately understand that we are talking about the Fluent API pattern:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    @Test
    void testExample() {
        HomePage page = Pages.login.open()
            .fillUsernameInput(Config.correctUsername)
            .fillUsernamePassword(Confid.correctPass)
            .clickLoginButton();

            assertTrue(page.isInventoryListVisible());
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Aside from the code looking super readable, there’s another side effect: I don’t have to think about where I am in the application after a certain action, all the transition logic (essentially the graph of our application) is described within the page objects. Each Page Object method returns the page type that is expected after performing the action.&lt;/p&gt;

&lt;h2&gt;
  
  
  There is a problem though…
&lt;/h2&gt;

&lt;p&gt;Unlike the synchronous code in the above-mentioned languages, Javascript or Typescript are not synchronous by the nature, which means such code cannot be written because all methods of interacting with the page will return &lt;code&gt;Promise&amp;lt;T&amp;gt;&lt;/code&gt;. Let me show you this with the example of the &lt;code&gt;open()&lt;/code&gt; method of some page (it will be useful to those who are not very familiar with &lt;code&gt;Promise&lt;/code&gt; or those who are just beginning to understand the basics of Javascript):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    export class LoginPage {
        // locators, constructors etc.

        /*
        Here we need to return this page as a result as we know we're on current
        page after it's open
        */
        public async open(): Promise&amp;lt;LoginPage&amp;gt; {
            await this.page.goto(this.url);
            return this;
        }
    }


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And now I cannot utilize Fluent API patter as of Javascript asynchronousness:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Ae6pWwfwLjJ9hMgCfwpGkRQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2Ae6pWwfwLjJ9hMgCfwpGkRQ.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What implementation options do we have?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The simpliest one
&lt;/h3&gt;

&lt;p&gt;The most obvious way is to wait until the action is done and next Page Object is returned step by step:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    let loginPage: LoginPage;

    test.beforeEach(({page}) =&amp;gt; {
        loginPage = new LoginPage(page);
    });

    test('User can login', async () =&amp;gt; {
        let login = await loginPage.open();
        login = await loginPage.setUsername('standard_user');
        login = await loginPage.setPassword('secret_sauce');
        let home = await loginPage.clickLoginButton();

        expect(await home.isInventoryListVisible()).toBeTruthy();
    });


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;To be honest, it doesn’t look so good 😕. Need to mention that such a code doesn’t solve the context problem as well. I still have to remember which page I’m on at any given moment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next step — using then()
&lt;/h2&gt;

&lt;p&gt;The Promise class allows us to use the then() method and the return value to build the call chains. Let's tweak our code a bit and see what we've got:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    let loginPage: LoginPage;

    test.beforeEach(({page}) =&amp;gt; {
        loginPage = new LoginPage(page);
    });

    test('User can login', async () =&amp;gt; {
        await loginPage.open()
            .then(async (page) =&amp;gt; await loginPage.setUsername('standard_user'))
            .then(async (page) =&amp;gt; await loginPage.setPassword('secret_sauce'))
            .then(async (page) =&amp;gt; await loginPage.clickLoginButton())
            .then(async (page) =&amp;gt; expect(await page.isInventoryListVisible()).toBeTruthy());
    });


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It’s better now, no need to remember the current page. Basically, you can use it, but it still doesn’t look nice.&lt;/p&gt;

&lt;h2&gt;
  
  
  The graceful way
&lt;/h2&gt;

&lt;p&gt;First of all, I want to say thank you to &lt;a href="https://medium.com/@anthonygore" rel="noopener noreferrer"&gt;Anthony Gore&lt;/a&gt; — it was he who introduced me to the wonderful library that helps solve the problem of calling a chain of methods.&lt;/p&gt;

&lt;p&gt;So, the first thing to do is to add a new dependency to the project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    npm i proxymise


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you are working with a Javascript project, everything is okay, you can use it.&lt;/p&gt;

&lt;p&gt;If the project works with Typescript, additional actions will be required:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Check that the command &lt;code&gt;tsc -v&lt;/code&gt; works. If not, you need to install an additional dependency: &lt;code&gt;npm i tsc&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Then we execute &lt;code&gt;tsc --init&lt;/code&gt; and look at the result. It will also be written there how to fix errors if there is any. If the command was executed successfully, a &lt;code&gt;tsconfig.json&lt;/code&gt; file should appear in your project.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now it is necessary to connect proxymise in the page object files and wrap the class so that it can be used from tests in the Fluent API format. The full project code looks like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

    // pages/login-page.ts
    import {Locator, Page} from "@playwright/test";
    import {HomePage} from "./home-page";
    import proxymise from "proxymise";

    export class LoginPage {
        private page: Page;
        // Make this field static to use in static method
        private static url = 'https://www.saucedemo.com/';

        private usernameField: Locator;
        private passwordField: Locator;
        private loginButton: Locator;

        constructor(page: Page) {
            this.page = page;

            this.usernameField = this.page.locator('#user-name');
            this.passwordField = this.page.locator('#password');
            this.loginButton = this.page.locator('#login-button');
        }

        // This method is static now. Necessary for proxymise correct work
        public static async open(page: Page): Promise&amp;lt;LoginPage&amp;gt; {
            await page.goto(LoginPage.url);
            return new LoginPage(page);
        }

        public async setUsername(name: string): Promise&amp;lt;LoginPage&amp;gt; {
            await this.usernameField.fill(name);
            return this;
        }

        public async setPassword(pass: string): Promise&amp;lt;LoginPage&amp;gt; {
            await this.passwordField.fill(pass);
            return this;
        }

        public async clickLoginButton(): Promise&amp;lt;HomePage&amp;gt; {
            await this.loginButton.click();
            return new HomePage(this.page);
        }
    }

    // Wrap this class with proxymise to avoid it in tests code
    export default proxymise(LoginPage);

    // pages/home-page.ts
    import {Locator, Page} from "@playwright/test";
    import proxymise from "proxymise";

    export class HomePage {
        private page: Page;
        private static url = 'https://www.saucedemo.com/inventory.html';

        private inventoryList: Locator;

        constructor(page: Page) {
            this.page = page;

            this.inventoryList = page.locator('div.inventory_list');
        }

        public static async open(page: Page): Promise&amp;lt;HomePage&amp;gt; {
            await page.goto(HomePage.url);
            return new HomePage(page);
        }

        public async isInventoryListVisible(): Promise&amp;lt;boolean&amp;gt; {
            return await this.inventoryList.isVisible();
        }
    }

    export default proxymise(HomePage);

    // tests/saucedemo.spec.ts
    import {test, expect} from '@playwright/test';
    import LoginPage from "../pages/login-page";

    test('User can login', async ({page}) =&amp;gt; {
        const isInventoryShown = await LoginPage.open(page)
            .setUsername('standard_user')
            .setPassword('secret_sauce')
            .clickLoginButton()
            .isInventoryListVisible();

        expect(isInventoryShown).toBeTruthy();
    });


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Pay attention to how concise our test has become. This solution is no different from the standard Fluent API in Java or C#.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;This example is basic for understanding how to achieve Fluent API in tests written in Playwright and Typescript. But it is not final, it can be further improved by adding new features and concepts to facilitate developer and code maintainance. In any case, doing such an exercise would be useful for the development of coding skills 🙂&lt;/p&gt;

&lt;p&gt;Good luck with your projects and don’t stop to automate!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>playwright</category>
      <category>testing</category>
    </item>
    <item>
      <title>Evolution of My Attitude Towards E2E Testing Over Time</title>
      <dc:creator>Roman Orlov</dc:creator>
      <pubDate>Fri, 19 Jan 2024 14:39:36 +0000</pubDate>
      <link>https://dev.to/10-minutes-qa-story/evolution-of-my-attitude-towards-e2e-testing-over-time-10nf</link>
      <guid>https://dev.to/10-minutes-qa-story/evolution-of-my-attitude-towards-e2e-testing-over-time-10nf</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;It took quite some time for me to realize that this is perhaps the most interesting type of testing&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hello, everyone!&lt;/p&gt;

&lt;p&gt;Looking back on my past, I realize that there have been different periods in my professional life. I never really thought about how these periods reflected on my attitude towards testing and E2E testing in particular. It’s time to change that.&lt;/p&gt;

&lt;p&gt;In this article, I want to analyze how my attitude towards E2E testing has evolved and what events caused certain changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 1. Introduction
&lt;/h2&gt;

&lt;p&gt;I started as a manual tester and remained one for several years.&lt;/p&gt;

&lt;p&gt;But sooner or later, there comes a time to move on and explore something new. It doesn’t mean that I stopped enjoying testing or stopped seeing prospects in this profession. On the other hand, routine manual testing processes became tiresome, and there was a trend towards test automation. Many tasks required a lot of time for rechecking. In other words, everything pointed towards getting involved in programming and moving towards automation.&lt;/p&gt;

&lt;p&gt;I remember how I got introduced to my first project of automated tests. It wasn’t complicated, and it was utilizing fairly basic concepts. I believe nowadays, one can acquire all this knowledge through an automated testing course or by digging a bit into the internet.&lt;/p&gt;

&lt;p&gt;Getting acquainted with code didn’t seem like magic; rather, it felt like a task worth diving into to hone my object-oriented programming skills. Nevertheless, it was an exciting feeling, and I eagerly watched how my first tests have been passing.&lt;/p&gt;

&lt;p&gt;During this project, we shifted the implementation paradigm from Page Actions to Page Objects and started using a Cucumber-like framework for closer integration with the business. Our team was focused on delivering a quality product, leading to a maximum efficiency focus on processes.&lt;/p&gt;

&lt;p&gt;The company’s culture reiterated every day that involving the business in testing is a good practice. I saw many confirmations of this: the entire team’s engagement with the product, early feedback from automated tests when delivering new code changes, a deep understanding of the product and its features. To this day, I consider that team to have given me a strong foundation, both technically and process-wise, and they were the best I’ve ever worked with.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 2. Development
&lt;/h2&gt;

&lt;p&gt;Time doesn’t stand still. Technologies move forward, and so did I when I decided to move away from testing a monolith and look towards microservices architecture.&lt;/p&gt;

&lt;p&gt;As you can understand, the approach to testing such a product significantly differs from testing a monolith at all levels. The process of shifting thinking, I wouldn’t say, took much time, but it brought new knowledge and principles to testing organization. What is considered a must-have today (for example, preparing test data through API) was new to me back then, and I encountered such a test organization for the first time. A natural question for me was “Why weren’t we using this before?” (there is actually an answer to this question, but it’s not the focus of this article). Some data couldn’t be generated based on the API; we had to figure out how to use a mock server to emulate such cases and verify the required functionality.&lt;/p&gt;

&lt;p&gt;There was also a separate discussion about the test set for automation — what cases to cover at the unit test level, what at the integration level, and which scenarios to write in E2E format; how to assess test coverage at the business level, technical level etc.&lt;/p&gt;

&lt;p&gt;In summary of this stage of work, it was an era of new interesting tasks and a more conscious approach to E2E tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 3. Stagnation
&lt;/h2&gt;

&lt;p&gt;At some point, the excitement of applying new approaches wore off, and I began to feel like I was stuck in a place. This was a relatively long period during which I changed several projects. The essence of the projects remained the same — first, you write Page Objects (in different project structures and architectures, it seemed like monotonous routine), then you apply some best practices that you’ve encountered before. In the end, the test projects seemed essentially the same; the only difference seemed to be where Page Objects were stored and how you used them.&lt;/p&gt;

&lt;p&gt;I became very disappointed in E2E testing. I tried to find information on how to further develop as an automated test engineer, but all articles and courses seemed to revolve around the same basics that I was already familiar with.&lt;/p&gt;

&lt;p&gt;The worst happened — I stopped seeing perspectives. It was at this time that I became interested in other areas (again, new to me) — testing mobile applications, developing backend and frontend applications, delving deeper into API testing from a project architecture perspective.&lt;/p&gt;

&lt;p&gt;As it turned out later, this stage was not in vain. My experience in application development and broadening my horizons added to my understanding of testing strategies. I looked at test suite composition, their effectiveness in the software development process in a different way, but let’s call it a day and talk about that separately.&lt;/p&gt;

&lt;p&gt;Fortunately, everything eventually comes to an end, and this stage was no exception.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stage 4. Second Breath
&lt;/h2&gt;

&lt;p&gt;One day, another enterprise project came to me, requiring the organization of the test automation process. “Here we go again” - I thought…&lt;/p&gt;

&lt;p&gt;We started working, and everything developed in a standard way — again, the same Page Objects, organizing approaches. However, as we digged into the product nuances, new details emerged, adding new dimensions. For example, depending on the starting point and user type, we could follow the process in two different ways (hello, strategy pattern), or depending on the testing environment, we had to load pages for specific devices/browser resolutions at runtime. These are quite basic examples, but they are pretty common in most projects we, QAs, could work on.&lt;/p&gt;

&lt;p&gt;At some point, I realized that the unique architecture I built for testing was not an exception or a result of my “unusual” approach but rather the consequences of distinct business traits — domain model, interaction with the environment, team communications, application concepts, etc.&lt;/p&gt;

&lt;p&gt;Reviewing my past experience, I understood that each project was unique. In reality, the essence of E2E test code lies not in standard things taught by Selenium or automation courses but in business features and team architecture, the people we interact with, and the products, services we build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What next?
&lt;/h2&gt;

&lt;p&gt;Now I can definitely say that E2E tests are the most interesting aspect of an automation engineer’s work because they encompass different areas of interaction (UI, API, sometimes services and databases), require knowledge from related fields (such as software development, design patterns, business domain model), and teamwork (who would have thought 🙂).&lt;/p&gt;

&lt;p&gt;To recap all of the above I can say that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Like any process, test automation has a lifecycle that we all have to go through. It sounds grand, but I think it’s true. If you have examples of a different experience, write in the comments; it would be interesting to know if I’m wrong when projecting my experience onto everyone&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To write E2E tests effectively, a considerable amount of knowledge is required, including from software development&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;E2E tests currently seem like a very interesting task, involving not only interaction with the UI but also with backend applications and the most of design patterns utilization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The stagnation period can be used to broaden your horizons and acquire new knowledge&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you like this article, feel free to join my &lt;a href="https://t.me/ta_stories"&gt;Telegram channel&lt;/a&gt;, there I publish more thoughts about software testing and development.&lt;/p&gt;

&lt;p&gt;See you next time, and thanks for listening 🙂&lt;/p&gt;

&lt;p&gt;And, of course, don’t stop to automate!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>learning</category>
      <category>career</category>
    </item>
    <item>
      <title>API Testing from scratch. Setup test automation flow</title>
      <dc:creator>Roman Orlov</dc:creator>
      <pubDate>Wed, 04 Oct 2023 10:04:32 +0000</pubDate>
      <link>https://dev.to/10-minutes-qa-story/api-testing-from-scratch-setup-test-automation-flow-1ndc</link>
      <guid>https://dev.to/10-minutes-qa-story/api-testing-from-scratch-setup-test-automation-flow-1ndc</guid>
      <description>&lt;p&gt;Welcome! This is a series of stories about API testing. For your convenience, I have added all of them to the &lt;a href="https://medium.com/@helltester666/list/api-testing-8ac51f335f38" rel="noopener noreferrer"&gt;separate list&lt;/a&gt;. Here, we will be learning the following things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;API structure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test design and approaches to testing the API layer of an application&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How to write extensible and readable code&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Libraries that help us create a Test Automation Framework for API testing&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other helpful libraries&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Contract testing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My goal here is to provide as many examples as possible and keep the articles short. This way, it will be convenient to save the articles and return to the required one if needed.&lt;/p&gt;

&lt;p&gt;Feel free to join my &lt;a href="https://t.me/ta_stories" rel="noopener noreferrer"&gt;Telegram channel&lt;/a&gt; as well, there I publish more thoughts about software testing and development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 5. Starting a test automation project with Java
&lt;/h2&gt;

&lt;p&gt;To start the first project, you need to install the following programs on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Java SDK (JDK). You can find the instructions &lt;a href="https://www.geeksforgeeks.org/download-and-install-java-development-kit-jdk-on-windows-mac-and-linux/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. JDK allows you to run any Java program on your computer. Even though you might think that you don’t have a program, the tests themselves are programs that execute and verify something. Without JDK, you won’t be able to start at all.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Maven. You can find the instructions &lt;a href="https://www.baeldung.com/install-maven-on-windows-linux-mac" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Maven is a build tool that allows you to manage project dependencies and control package versions. It is needed because any Java project is like a “Lego” — you use specific packages to solve separate tasks. These packages provide you with the features you need, and you just need to write code to use those features. In this article, we’ll see an example of such a package — the JUnit library, which is responsible for organizing the test flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;IDE of your choice. I’ll develop the code with VSCode because it’s free and extensible. It also supports a lot of plugins for different programming languages and frameworks in a flexible way. However, if you don’t like it, feel free to choose another IDE such as &lt;a href="https://www.jetbrains.com/idea/download" rel="noopener noreferrer"&gt;Intellij IDEA&lt;/a&gt;, &lt;a href="https://netbeans.apache.org/download/nb19/" rel="noopener noreferrer"&gt;NetBeans&lt;/a&gt;, or &lt;a href="https://www.eclipse.org/downloads/" rel="noopener noreferrer"&gt;Eclipse&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you’re done with the preparation, let’s move on to the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Starting a new project
&lt;/h2&gt;

&lt;p&gt;The detailed instructions are available at &lt;a href="https://code.visualstudio.com/docs/java/java-tutorial" rel="noopener noreferrer"&gt;https://code.visualstudio.com/docs/java/java-tutorial&lt;/a&gt;. Just follow it and create a first file (Hello.java). Then Ctrl+Shift+P Java: Create Java Project command. In the list select Maven option:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AXRoT8Pn5t5kdrHuJ2Y6L2w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AXRoT8Pn5t5kdrHuJ2Y6L2w.png" alt="Create a Java project. Step 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and No Archetype then:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AoB4EMCUDAf9Q5cFVrwb5Cw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AoB4EMCUDAf9Q5cFVrwb5Cw.png" alt="Create a Java project. Step 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next, we need to specify the root folder name for our code. Typically, it’s the same as the application we’re going to test, but in reverse. For example, if the application under test is &lt;a href="http://gorest.co.in/" rel="noopener noreferrer"&gt;gorest.co.in&lt;/a&gt;, the root folder name would be &lt;a href="http://in.co/" rel="noopener noreferrer"&gt;in.co&lt;/a&gt;.&lt;a href="https://gorest.co.in/" rel="noopener noreferrer"&gt;gorest&lt;/a&gt;. Please enter this name in the textbox.&lt;/p&gt;

&lt;p&gt;Next, you’ll be prompted to provide the artifactId. For now, we don’t need to worry about what it is, as it will be discussed in a separate article in the future. Just think of this parameter as a project name, and choose any value you prefer. I’ve used “apitests” as my value, but feel free to choose whatever you like.&lt;/p&gt;

&lt;p&gt;In the final step, VSCode will ask you to choose the folder where you want to store the project files. Select the correct folder, and you’ll have the following project structure:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A0P10UktRqlqIcFEhsy1A4Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2A0P10UktRqlqIcFEhsy1A4Q.png" alt="Project structure"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding test runner
&lt;/h2&gt;

&lt;p&gt;Okay, the hardest part is over. Now it’s time to add the dependencies to our project.&lt;/p&gt;

&lt;p&gt;Open the pom.xml file to edit it. This file describes the specifics of our project, such as the language version we use and the libraries included. First, let’s find and change the Java version we are using (initially it should be 1.8):&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;properties&amp;gt;
    &amp;lt;maven.compiler.source&amp;gt;11&amp;lt;/maven.compiler.source&amp;gt;
    &amp;lt;maven.compiler.target&amp;gt;11&amp;lt;/maven.compiler.target&amp;gt;
&amp;lt;/properties&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, we want to add the JUnit library (finally!). This library allows us to run tests, so it is essential for any automation project. To add it, we simply need to include a dependency in our project. Each specific dependency should be stored in the dependencies node of the XML document.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
    // Need to specify a dependency here
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, let’s learn how to find a JUnit library to include. To do this, open the &lt;a href="https://mvnrepository.com/" rel="noopener noreferrer"&gt;Maven Central Repository&lt;/a&gt;, which is a website that contains information about public packages. In the search field, type “JUnit” and open the corresponding result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2688%2F1%2A1XIGlfnueg5zfEE-_9Dh0g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2688%2F1%2A1XIGlfnueg5zfEE-_9Dh0g.png" alt="Maven central JUnit search results"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the central tab, you will find multiple versions of the library. Select the latest version and copy the code snippet for adding the Maven dependency (located in the Maven tab). Paste this snippet into the dependencies block of your pom.xml file. Your dependencies XML node should now look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;dependencies&amp;gt;
    &amp;lt;dependency&amp;gt;
        &amp;lt;groupId&amp;gt;org.junit.jupiter&amp;lt;/groupId&amp;gt;
        &amp;lt;artifactId&amp;gt;junit-jupiter-api&amp;lt;/artifactId&amp;gt;
        &amp;lt;version&amp;gt;5.10.0&amp;lt;/version&amp;gt;
        &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;/dependency&amp;gt;
&amp;lt;/dependencies&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And the full pom.xml file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;?xml version="1.0" encoding="UTF-8"?&amp;gt;
&amp;lt;project xmlns="&amp;lt;http://maven.apache.org/POM/4.0.0&amp;gt;"
    xmlns:xsi="&amp;lt;http://www.w3.org/2001/XMLSchema-instance&amp;gt;"
    xsi:schemaLocation="&amp;lt;http://maven.apache.org/POM/4.0.0&amp;gt; &amp;lt;http://maven.apache.org/xsd/maven-4.0.0.xsd&amp;gt;"&amp;gt;
    &amp;lt;modelVersion&amp;gt;4.0.0&amp;lt;/modelVersion&amp;gt;

    &amp;lt;groupId&amp;gt;in.co.gorest&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;api-tests&amp;lt;/artifactId&amp;gt;
    &amp;lt;version&amp;gt;1.0-SNAPSHOT&amp;lt;/version&amp;gt;

    &amp;lt;properties&amp;gt;
        &amp;lt;maven.compiler.source&amp;gt;11&amp;lt;/maven.compiler.source&amp;gt;
        &amp;lt;maven.compiler.target&amp;gt;11&amp;lt;/maven.compiler.target&amp;gt;
    &amp;lt;/properties&amp;gt;

    &amp;lt;dependencies&amp;gt;
        &amp;lt;dependency&amp;gt;
            &amp;lt;groupId&amp;gt;org.junit.jupiter&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;junit-jupiter-api&amp;lt;/artifactId&amp;gt;
            &amp;lt;version&amp;gt;5.10.0&amp;lt;/version&amp;gt;
            &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
        &amp;lt;/dependency&amp;gt;
    &amp;lt;/dependencies&amp;gt;
&amp;lt;/project&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Create a test class
&lt;/h2&gt;

&lt;p&gt;The test class is a special Java class that contains test scripts and can be run by JUnit (which we have already added). Let’s create the first test class in our project. Navigate to the src/test/java folder and create a new file named FirstTest.java.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ATMXJqoe1j6LKXLo_XnpymQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2ATMXJqoe1j6LKXLo_XnpymQ.png" alt="Create a new file"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding test flow annotations
&lt;/h2&gt;

&lt;p&gt;Now we’re ready to prepare methods to start testing. Let’s explore the available methods. Copy and paste the following code into the created file:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package apitests.src.test.java;


public class FirstTest {
    @BeforeAll
    public static void globalSetup() {
        System.out.println("This is before class method");
    }
    @AfterAll
    public static void globalTeardown() {
        System.out.println("This is after class method");
    }

    @BeforeEach
    public void setup() {
        System.out.println("This is before each method");
    }
    @AfterEach
    public void tearDown() {
        System.out.println("This is after each method");
    }
    @Test
    public void test1() {
        System.out.println("This is test #1");
    }
    @Test
    public void test2() {
        System.out.println("This is test #2");
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here we have 2 dummy tests and several methods that should be executed before or after all tests in this class, or before or after test methods in this class. However, we currently have several errors.&lt;/p&gt;

&lt;p&gt;To resolve this, let’s click on the “Testing” tab of VSCode and enable our tests for the JUnit Jupiter engine.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AjqvjH7adFOGHwjEcz2WC_A.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AjqvjH7adFOGHwjEcz2WC_A.png" alt="Setup VSCode for using JUnit. Step 1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AEYe46j9ZfL2kTkCPnYrnIw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AEYe46j9ZfL2kTkCPnYrnIw.png" alt="Setup VSCode for using JUnit. Step 2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After installing all the necessary components, navigate to the error lines (annotations). Use the Ctrl+. combination to access the context menu when the cursor is on the annotation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2APDCuFn4I8I7dUGXoG0Ps4g.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2APDCuFn4I8I7dUGXoG0Ps4g.png" alt="Resolve annotaion"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try this trick for every annotation present in the file.&lt;/p&gt;

&lt;p&gt;Once all errors are fixed, we are ready to run our tests. Expand all the tests in the test explorer and click on the Play button for the entire class.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AC6RWSI16qOBWhhSZVDsxRg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AC6RWSI16qOBWhhSZVDsxRg.png" alt="Running tests"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you have done everything correctly, you will see the results of the execution in the Debug Console in VSCode.&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;This is before class method&lt;br&gt;
This is before each method&lt;br&gt;
This is test #1&lt;br&gt;
This is after each method&lt;br&gt;
This is before each method&lt;br&gt;
This is test #2&lt;br&gt;
This is after each method&lt;br&gt;
This is after class method&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Order of execution&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Read our logs carefully. There are several important points here to understand:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The method marked with the BeforeAll/AfterAll annotation is executed only once before/after all tests in the &lt;strong&gt;class&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AeecFI1B3s0wFYRF-uUK95Q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2AeecFI1B3s0wFYRF-uUK95Q.png" alt="Before/After class methods"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Methods marked with the BeforeEach/AfterEach annotation are executed before/after each automated test that we have written:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2APzNe2ylk8J4Mnib4u0Si3w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn-images-1.medium.com%2Fmax%2F2000%2F1%2APzNe2ylk8J4Mnib4u0Si3w.png" alt="Before/After test methods"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The methods with the Test annotation are executed in between other methods in the class.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What is the benefit of such a structure? Setup and Teardown methods are usually used for test preparation: reading configuration files to determine the testing environment, creating required test data, and cleaning it up after the test is passed, and setting up utility objects like HTTP clients. The advantage is that we avoid duplicated code in test methods, so we do not need to modify all tests if there are changes in the preparation logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next steps
&lt;/h2&gt;

&lt;p&gt;If your project is working fine (like mine 🙂), then we are done here and ready to add more dependencies to start API test automation.&lt;/p&gt;

&lt;p&gt;Here is a useful link that can help you use Java with the VSCode IDE: &lt;a href="https://code.visualstudio.com/docs/java/java-build" rel="noopener noreferrer"&gt;Java in Visual Studio Code&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Good luck and keep automating!&lt;/p&gt;

</description>
      <category>testing</category>
      <category>vscode</category>
      <category>java</category>
      <category>qa</category>
    </item>
    <item>
      <title>Handling Pop-Up Windows in Selenium</title>
      <dc:creator>Roman Orlov</dc:creator>
      <pubDate>Sun, 27 Aug 2023 10:32:42 +0000</pubDate>
      <link>https://dev.to/10-minutes-qa-story/handling-pop-up-windows-in-selenium-32k4</link>
      <guid>https://dev.to/10-minutes-qa-story/handling-pop-up-windows-in-selenium-32k4</guid>
      <description>&lt;p&gt;Welcome! Today we’re going to look into the pop-up windows handling using Selenium. If you like this article please feel free to follow me on &lt;a href="https://t.me/ta_stories"&gt;Telegram channel&lt;/a&gt;, there are many more thought related to development, processes and test automation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Sooner or later in the life of any testing automation specialist (regardless of the programming language you work with), a situation arises where it becomes necessary to handle pop-up windows in the browser. One might think that in the era of reactive applications, phenomena like pop-up windows should have taken a back seat, as modern tools offer a rich variety of implementing modal windows through HTML code on the page rather than relying on the browser’s features. However, it’s important not to forget that our world is not perfect, and not all projects are built using React/Vue/Angular. Therefore, it’s reasonable to expect that dealing with browser pop-up windows is still something we might encounter.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Are Pop-Up Windows?
&lt;/h3&gt;

&lt;p&gt;Pop-up windows are a browser feature that has come to us from the distant years when web development wasn’t yet mainstream. However, pop-up windows still exist even in modern versions of JavaScript. Developers have taken care of backward compatibility to avoid breaking projects that were written long ago.&lt;/p&gt;

&lt;p&gt;Pop-up windows serve two main purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;User Interaction: When we need to convey important information to the user or request certain data without which further progress is impossible, a pop-up window comes to the rescue.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Preventing User Interaction: They prevent users from interacting with the web page’s interface until the user responds to a message by clicking a button.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Types of Pop-Up Messages
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Simple Alert
&lt;/h3&gt;

&lt;p&gt;This message contains a single button — OK. By clicking on it, we simply close the pop-up window and can continue working with the web interface. Let’s see what it looks like.&lt;/p&gt;

&lt;p&gt;Open the developer console in your browser (F12) and navigate to the Console tab:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--vyZ1emu4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxj1syplaa5o8v9v3uos.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--vyZ1emu4--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kxj1syplaa5o8v9v3uos.png" alt="Developer's console" width="752" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We have the JavaScript command line before us, where we can write code that the browser will execute. Enter the command alert('This is alert'); in the interpreter and press Enter. As a result of execution, we see a pop-up window with the message 'This is alert':&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Uw8bXecz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fc0y7gpsct0r7mt9qazg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Uw8bXecz--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fc0y7gpsct0r7mt9qazg.png" alt="Alert pop-up" width="453" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Such a window can be used to display important information to the user for their awareness, without giving them the option to make any choice.&lt;/p&gt;

&lt;h3&gt;
  
  
  Confirmation Window
&lt;/h3&gt;

&lt;p&gt;This window presents a message with two possible actions — OK and Cancel. The behavior of such a window is more complex than that of a simple alert. Different code fragments will be executed based on the option chosen by the user. The purpose of this article is to learn how to handle such pop-up windows using Selenium tools, so we won’t delve into the detailed implementation of different user choices.&lt;/p&gt;

&lt;p&gt;To invoke a confirmation window, we need to perform the same steps as we did to bring up an alert, but use a different command: confirm('Choose your destiny:');. As a result of executing this code, the following window will be displayed:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2CJFVfpT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znaksoifc2j1v7uzkv48.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2CJFVfpT--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/znaksoifc2j1v7uzkv48.png" alt="Confirm pop-up" width="456" height="135"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Input window
&lt;/h3&gt;

&lt;p&gt;And finally, the last type of pop-up window is an input window, where the user can not only choose from two available options but also write their own response. This type of window can also be triggered from JavaScript by executing the following command: prompt('What is your favorite test automation library?', 'Selenium');. The prompt command takes two arguments - the message that will be displayed to the user and the default response (in our case, Selenium). As a result of executing this code, the following window will be shown with the suggested response (though we can, of course, enter our own response or even decline to provide any data by clicking the Cancel button):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--UDu5NHiK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6m8gj972dibnwb50co6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--UDu5NHiK--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6m8gj972dibnwb50co6.png" alt="Prompt pop-up" width="453" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Handling Pop-Up Windows in Selenium is Necessary
&lt;/h2&gt;

&lt;p&gt;To answer this question, let’s perform a small exercise for all three types of pop-up windows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;While on any webpage in your browser, open the developer console and trigger a pop-up window.&lt;/li&gt;
&lt;li&gt;As soon as the window appears on the screen, try interacting with the elements on the page (images, links, etc.).&lt;/li&gt;
&lt;li&gt;Close the pop-up windows using various methods (by clicking available buttons or by providing information in the prompt window). Take note of what is displayed in the developer console when closing the windows in different ways.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’ve done everything correctly, the answer to why handling pop-up windows in test automation is necessary becomes quite clear:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A pop-up window prevents the user from interacting with the elements on the page. Consequently, while the window is open, a Selenium automated test cannot proceed further.&lt;/li&gt;
&lt;li&gt;By clicking different buttons and entering information (only for prompt-type windows), we obtain different values. This means we can emulate various user scenarios. Thus, we can create a set of automated tests that cover all possible user interaction scenarios.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Interacting with Pop-Up Windows in Selenium
&lt;/h2&gt;

&lt;p&gt;Now that we understand where pop-up windows come from and why they are important, let’s practice and learn how to work with them in Selenium. We already have a web page that is perfect for this purpose: &lt;a href="https://testpages.herokuapp.com/styled/alerts/alert-test.html"&gt;https://testpages.herokuapp.com/styled/alerts/alert-test.html&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Switching to a Pop-Up Window
&lt;/h3&gt;

&lt;p&gt;Let’s write a simple test that clicks a button and triggers the appearance of a pop-up window. All examples are written in the Java programming language using the selenium-java library. However, the code can be easily adapted to other programming languages since the Selenium library interface is similar across all languages. In the code example below, the WebDriver initialization is omitted, as we are focusing on working with windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Clicking on the button that causes alert message
driver.findElement(By.id("alertexamples")).click();

// Switching WebDriver context to the pop-up window
driver.switchTo().alert();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retrieving Text from a Pop-Up Window
&lt;/h3&gt;

&lt;p&gt;In the example above, in the last line, we switched Selenium’s focus to the alert (this will also work for confirm or prompt windows). Calling the alert() method returns a special class called Alert, which helps us work with pop-up windows.&lt;/p&gt;

&lt;p&gt;Now we can write a complete test that verifies the text of the alert against the expected text. To do this, we will use the getText() method in the Alert class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testAlert() {
    driver.findElement(By.id("alertexamples")).click();

    // Get pop-up window text message
    String message = driver.switchTo().alert().getText();

    assertEquals("I am an alert box!", message);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Closing a Pop-Up Window
&lt;/h3&gt;

&lt;p&gt;Our example is quite simple. In practice, it’s often not enough to just retrieve text from a window, you might need to close pop-up windows to continue working. To achieve this, you need to learn how to close pop-up windows using Selenium.&lt;/p&gt;

&lt;p&gt;For practice, let’s try the second type of pop-up window — confirm. On our test page, the user’s choice is displayed in a separate message that appears below the button. We will use this to test various methods of interacting with pop-up windows.&lt;/p&gt;

&lt;p&gt;The Alert class has two methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;accept()&lt;/code&gt;: This method makes Selenium click the "OK" button of the pop-up window of any type.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;dismiss()&lt;/code&gt;: Similar effect, but it will click the "Cancel" button of the pop-up window.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testAcceptConfirm() {
    driver.findElement(By.id("confirmexample")).click();

    // Accept the confirm (OK)
    driver.switchTo().alert().accept();

    boolean result = Boolean.parseBoolean(driver.findElement(By.id("confirmreturn")).getText());
    String clickedButton = driver.findElement(By.id("confirmexplanation")).getText();

    assertTrue(result);
    assertTrue(clickedButton.contains("You clicked OK"));
}

@Test
void testDismissConfirm() {
    driver.findElement(By.id("confirmexample")).click();

    // Dismiss the confirm (Cancel)
    driver.switchTo().alert().dismiss();

    boolean result = Boolean.parseBoolean(driver.findElement(By.id("confirmreturn")).getText());
    String clickedButton = driver.findElement(By.id("confirmexplanation")).getText();

    assertFalse(result);
    assertTrue(clickedButton.contains("You clicked Cancel"));
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Entering User Input
&lt;/h3&gt;

&lt;p&gt;We’ve come a long way, but there’s one last scenario to cover: entering our own message into a pop-up window. Let’s see how we can achieve this, especially considering that the last button on our web page remains untested.&lt;/p&gt;

&lt;p&gt;So, here are our two test scenarios:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the button, enter a message, and click “OK.”&lt;/li&gt;
&lt;li&gt;Click the button, enter a message, and click “Cancel.”&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To implement these scenarios, you need to familiarize yourself with a new method of the Alert class:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sendKeys()&lt;/code&gt; - used to enter text into a prompt-type window. Let's look at the tests that utilize this method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@Test
void testAcceptPrompt() {
    driver.findElement(By.id("promptexample")).click();
    String text = "I like test automation!";

    // Enter text and press ОК
    driver.switchTo().alert().sendKeys(text);
    driver.switchTo().alert().accept();

    String message = driver.findElement(By.id("promptreturn")).getText();
    String clickedButton = driver.findElement(By.id("promptexplanation")).getText();

    assertEquals(message, text);
    assertTrue(clickedButton.contains("You clicked OK"));
}

@Test
void testDismissPrompt() {
    driver.findElement(By.id("promptexample")).click();
    String text = "I like test automation!";

    // Enter text and press Cancel
    driver.switchTo().alert().sendKeys(text);
    driver.switchTo().alert().dismiss();

    String message = driver.findElement(By.id("promptreturn")).getText();
    String clickedButton = driver.findElement(By.id("promptexplanation")).getText();

    assertEquals(message, "");
    assertTrue(clickedButton.contains("You clicked Cancel"));
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;In this article, we covered working with pop-up windows in Selenium:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We learned that different types of pop-up windows exist: alert, confirm, prompt.&lt;/li&gt;
&lt;li&gt;We learned how to trigger the appearance of pop-up windows using JavaScript commands.&lt;/li&gt;
&lt;li&gt;We discovered that Selenium has a special class, Alert, for working with pop-up windows.&lt;/li&gt;
&lt;li&gt;Using the getText() method, we learned how to retrieve the message within a pop-up window.&lt;/li&gt;
&lt;li&gt;With the accept() and dismiss() methods of the Alert class, we learned how to click "OK" and "Cancel" buttons in pop-up windows, thus deciding the user's path.&lt;/li&gt;
&lt;li&gt;Using the sendKeys() method, we learned how to input our own message into prompt-type windows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As seen from the code, working with pop-up windows isn’t as complex as it might seem. The “magic” has already been written by the developers of the Selenium library, and we simply need to utilize the provided methods. Keep these code examples, use them in your daily work, and, of course, don’t stop to automate!&lt;/p&gt;

</description>
      <category>qa</category>
      <category>programming</category>
      <category>selenium</category>
      <category>testing</category>
    </item>
    <item>
      <title>My "Postman 30 day challenge for developers" expression</title>
      <dc:creator>Roman Orlov</dc:creator>
      <pubDate>Sat, 03 Jun 2023 12:15:11 +0000</pubDate>
      <link>https://dev.to/10-minutes-qa-story/my-postman-30-day-challenge-for-developers-expression-3llb</link>
      <guid>https://dev.to/10-minutes-qa-story/my-postman-30-day-challenge-for-developers-expression-3llb</guid>
      <description>&lt;p&gt;Welcome! In this post I'd like to share my experience regarding to &lt;a href="https://blog.postman.com/introducing-30-days-of-postman-coding-challenge/"&gt;postman challenge for developers&lt;/a&gt; and advice to get it or not 🙂&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I know pretty much about postman and I worked a lot with it. I can say that I used not only basic features but have been writing complicated scenarios like any language automation stuff. All my tests with postman are usually flexible, parameterized and decoupled from the implementation layer like the environment under testing. And this is what I learned from this challenge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenge structure
&lt;/h2&gt;

&lt;p&gt;Pretty simple. All you need is a postman account and create a public workspace as you need to fork the “template” collections and then work there. Every day the postman team provides to you a task that is estimated to take 20-45 minutes and I can believe it assuming you’re new to postman.&lt;/p&gt;

&lt;p&gt;Every task contains several tests to verify yourself and make fixes if required so this is great, but the error message if you’re failing the test is not so informative and sometimes I had to look into the test script to understand what exactly is wrong in my solution. This was not annoying at all but if you’re new to postman… I Think, you got my point&lt;/p&gt;

&lt;p&gt;Challenges&lt;br&gt;
First several days the postman team tries to explain the basic concepts of postman like how to fork collection, what is the request and environments, how you can make your data reusable by providing variables, authorization flow in postman. This is pretty easy to finish and kinda obvious as postman (in my opinion) has a convenient interface.&lt;/p&gt;

&lt;p&gt;Then you’ll be learning how to write and debug tests, run collections sharing response data within. These skills are crucial for working with postman and creating tests as we’re preparing a robust base to run our tests in CI/CD (or locally 🙂).&lt;br&gt;
After this part is done you’re making acquaintance with the postman features like mock server, monitoring tool. In these exercises I learned something new, thanks for that.&lt;/p&gt;

&lt;p&gt;Newman. I’m not sure why this lesson is not right after the collections running lesson. Anyway, if you want to store your collections with tests in a repository and run it with the CI/CD pipelines on a daily basis, this tool is a must have.&lt;/p&gt;

&lt;p&gt;The OAuth lesson is about getting familiar with various services and how to handle OAuth authorization using Github. I think this example is nice to have for the case you forget how to implement it 🙂&lt;/p&gt;

&lt;p&gt;Next bunch of tasks is for a specific purpose but I think every QA guy has faced it at least once. So postman team teaches us to write the logs (all responses to a file), to implement pagination for endpoints, to visualize the data (this feature I haven’t aware of allows us to show the response data in HTML format, to represent it in readable way, wow), work with API specification, GraphQL requests and request documentation.&lt;/p&gt;

&lt;p&gt;And, finally, we’re learning to handle WebSockets, parse HTML responses, use data files for huge amounts of tests, testing the scenarios. The UI testing folder is also included but I’d like to say this is not UI testing in common understanding (clicking elements, fill out the input fields etc) but mostly about performance testing using the special web service for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions
&lt;/h2&gt;

&lt;p&gt;Regarding my technical expectations - this challenge is a good point to start learning postman with. It contains all the lessons for every day work and all the crucial skills you need to implement postman automation from scratch. Great job!&lt;/p&gt;

&lt;p&gt;Regarding new knowledge I got - yes, I would recommend this challenge. If you think you know a postman from A to Z, then just try it. I was pretty sure I would not learn anything new, but I’ve mistaken.&lt;/p&gt;

&lt;p&gt;In general, I have fun finishing it and &lt;a href="https://badgr.com/public/assertions/RQsChGMJRROMjcanuPIbtA"&gt;getting my badge&lt;/a&gt; so I can say I recommend completing it, no matter if you're a novice or expert.&lt;/p&gt;

&lt;p&gt;If you like this post, don’t forget to subscribe (in &lt;a href="https://t.me/ta_stories"&gt;Telegram&lt;/a&gt; as well), new content is coming.&lt;br&gt;
And don't stop to automate!&lt;/p&gt;

</description>
      <category>postman</category>
      <category>testing</category>
      <category>api</category>
      <category>testdev</category>
    </item>
  </channel>
</rss>
