<?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: Will Drygla</title>
    <description>The latest articles on DEV Community by Will Drygla (@will_drygla).</description>
    <link>https://dev.to/will_drygla</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%2F1620116%2F69e57916-b6cf-4462-823a-90e36890f2ec.jpg</url>
      <title>DEV Community: Will Drygla</title>
      <link>https://dev.to/will_drygla</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/will_drygla"/>
    <language>en</language>
    <item>
      <title>Missing Reports in Nightwatch with GGR + Selenoid</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sat, 13 Sep 2025 18:53:18 +0000</pubDate>
      <link>https://dev.to/will_drygla/missing-reports-in-nightwatch-with-ggr-selenoid-378</link>
      <guid>https://dev.to/will_drygla/missing-reports-in-nightwatch-with-ggr-selenoid-378</guid>
      <description>&lt;p&gt;&lt;strong&gt;Missing Reports in Nightwatch with GGR + Selenoid&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Recently I noticed that in some cases, when running Nightwatch with GGR and Selenoid, some reports were missing. The test would reach the end, but at the reporting moment, the last one (sometimes the last 2 or 3) were not in the reports.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My investigation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I started my investigation by checking if unresolved promises during the tests could cause the process to be lost and not report, but soon I discovered that this was not the case.&lt;/p&gt;

&lt;p&gt;I also investigated possible session-ending requests before the report arrived, but I found out that Nightwatch handles this well with its command queue — even if a promise is not awaited, the next command will only execute after the previous one finishes, so that wasn’t a problem either.&lt;/p&gt;

&lt;p&gt;I tried forcing long calls in the after of each test, and even then Nightwatch handled it fine.&lt;/p&gt;

&lt;p&gt;So I decided to go deeper and forked the repository, figured out how it worked, and started validating hypotheses until I finally found the issue.&lt;/p&gt;

&lt;p&gt;I’ll make a summary here, but you can read it in full on the discussion:&lt;br&gt;
👉 Nightwatch Issue #4416&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What’s happening under the hood&lt;/strong&gt;&lt;br&gt;
The process of running tests in parallel creates several promises. Each promise is a child process, and inside each child process, during the end of the test suite, data is transmitted to the parent process through IPC messages. Then the test-end event is emitted, and the process ends.&lt;/p&gt;

&lt;p&gt;The parent process, after all promises are resolved, moves on to the report stage.&lt;/p&gt;

&lt;p&gt;The problem is that IPC messages don’t control delivery, only sending. This creates a race condition because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The child process sends the IPC message&lt;/li&gt;
&lt;li&gt;The child process emits the test-end event&lt;/li&gt;
&lt;li&gt;All promises resolved → report step&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;What rarely happens is:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;IPC message with test data is sent&lt;/li&gt;
&lt;li&gt;Test-end event is emitted&lt;/li&gt;
&lt;li&gt;Parent process moves to the report step since all promises are done&lt;/li&gt;
&lt;li&gt;Report step collects the available data &lt;/li&gt;
&lt;li&gt;Report is generated&lt;/li&gt;
&lt;li&gt;IPC message content arrives afterward&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Propose solution&lt;/strong&gt;&lt;br&gt;
Since there is no way to wait for the message to be delivered, I proposed a solution where the child process would send the message and wait for a response from the parent process confirming that the message was received.&lt;/p&gt;

</description>
      <category>nightwatch</category>
      <category>selenoid</category>
      <category>automation</category>
      <category>programming</category>
    </item>
    <item>
      <title>Screenplay Pattern for Test automation: Actions - What an user can do</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 12:20:33 +0000</pubDate>
      <link>https://dev.to/will_drygla/screenplay-pattern-for-test-automation-actions-what-an-user-can-do-2h0c</link>
      <guid>https://dev.to/will_drygla/screenplay-pattern-for-test-automation-actions-what-an-user-can-do-2h0c</guid>
      <description>&lt;p&gt;🎬 Screenplay Pattern - Part 2: Actions&lt;br&gt;
In the Screenplay pattern, once you've defined your actor, the next question is:&lt;br&gt;
What can this actor do?&lt;/p&gt;

&lt;p&gt;That's where Actions come in.&lt;/p&gt;

&lt;p&gt;🛠️ What are Actions?&lt;br&gt;
Actions are the building blocks of your test.&lt;br&gt;
They define how an actor interacts with the system — but in a reusable and readable way.&lt;/p&gt;

&lt;p&gt;Instead of spreading low-level commands all over your tests, you create named tasks that express intention, like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="nx"&gt;bankCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enterLoginCredentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It tells you exactly what’s happening — not how it’s implemented.&lt;/p&gt;

&lt;p&gt;How the actions works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I use an class for each feature of our system:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginActions&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;enterLoginCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;email&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;password&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForPageToLoad&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getEmailInputSelector&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginPage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPasswordInputSelector&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;:visible&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;should&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;be.enabled&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;The UI selectors are stored in a separate file. By keeping selectors in their own file, they can be reused across different actions.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;After this, the actors who can perform this actions, only need:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankCustomer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginActions&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;LoginActions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginActions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So when you read a test and see an action, it tells you what is being done — not how it's done. If you want to check the implementation, just search for the action name or look it up in the documentation. (Screenplay makes documenting tests easier — more on that later!)&lt;/p&gt;

&lt;p&gt;✅ Why use Actions?&lt;br&gt;
Readable: Your test reads like a user story.&lt;/p&gt;

&lt;p&gt;Reusable: One action can be used across many scenarios.&lt;/p&gt;

&lt;p&gt;Maintainable: If the UI changes, you update the action, not every test.&lt;/p&gt;

&lt;p&gt;In short:&lt;br&gt;
Actions help you write tests that describe behavior, not code.&lt;br&gt;
They're a key piece in making automation feel like a conversation between the user and the system.&lt;/p&gt;

&lt;p&gt;Next post: ❓Questions – how do we verify what happened?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>automation</category>
      <category>screenplay</category>
    </item>
    <item>
      <title>Screenplay Pattern for Test automation: Actors - Who uses our software</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 03 Aug 2025 12:08:13 +0000</pubDate>
      <link>https://dev.to/will_drygla/screenplay-pattern-for-test-automation-actors-who-uses-our-software-43kd</link>
      <guid>https://dev.to/will_drygla/screenplay-pattern-for-test-automation-actors-who-uses-our-software-43kd</guid>
      <description>&lt;p&gt;🎭 Screenplay Pattern - Part 1: Actors&lt;br&gt;
If you're diving into the Screenplay pattern for test automation, the first concept to understand is the Actor.&lt;/p&gt;

&lt;p&gt;In Screenplay, actors represent the users or roles interacting with your application.&lt;br&gt;
They don’t know how to do things — they just know what they want to achieve.&lt;/p&gt;

&lt;p&gt;💡 Think of them like personas:&lt;/p&gt;

&lt;p&gt;A customer logs in and transfers money.&lt;/p&gt;

&lt;p&gt;An admin reviews reports.&lt;/p&gt;

&lt;p&gt;A guest browses the site.&lt;/p&gt;

&lt;p&gt;Why use Actors?&lt;br&gt;
✅ Readable scripts:&lt;br&gt;
Instead of reading raw implementation, you read intent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;bankCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enterLoginCredentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;✅ Modular logic:&lt;br&gt;
Actors separate who is performing actions from how they're done. It keeps your actions reusable and test logic clean.&lt;/p&gt;

&lt;p&gt;✅ Test from the user's POV:&lt;br&gt;
You shift focus from UI details to user goals. That helps both devs and stakeholders understand the test’s purpose.&lt;/p&gt;

&lt;p&gt;How an actor works:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I use an base-actor, that have the methods to perform actions and questions:
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IBaseActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ActionMethods&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;QuestionMethods&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     *
     * @param action receives the name of a valid action for the actor
     * @param parameters this parameters will be passed to the action
     */&lt;/span&gt;
    &lt;span class="nf"&gt;makeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     *
     * @param question receives the name of a valid question for the actor
     * @param parameters this parameters will be passed to the question
     */&lt;/span&gt;
    &lt;span class="nf"&gt;makeQuestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;question&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;Then, I create the test users extending the base one, and pass to super,  the available actions that the user can do ( next post will be about actions, but actions are separated by feature):
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BankCustomer&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;BaseActor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginActions&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;LoginActions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registrationActions&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;RegistrationActions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountActions&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;AccountActions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactionActions&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;TransactionActions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;actionsInstances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loginActions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registrationActions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountActions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transactionActions&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginQuestions&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;LoginQuestions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;registrationQuestions&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;RegistrationQuestions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accountQuestions&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;AccountQuestions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;transactionQuestions&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;TransactionQuestions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;questionsInstances&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loginQuestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;registrationQuestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;accountQuestions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;transactionQuestions&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionsInstances&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;questionsInstances&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;But, to this work, I had to make an proxy on the base-actor, because I'm passing to him actions and questions from different features, and I don't wanna do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;            &lt;span class="nx"&gt;bankCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clickLoginButton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wanna that all actions be available on the same level, not separated by feature ( for the actor, an action is an action, the separation by feature is only for code organization ), so, I do an proxy that make all works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actionsInstances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;questionsInstances&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actions&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;Proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;{},&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;

                    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;actionsInstances&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;instance&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                        &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Action &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; not found.`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this, actors can access all its actions and questions only passing the action name.&lt;br&gt;
Example of an test using actors:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt; &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should successfully log in with valid credentials using BankCustomer actor&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLocalStorageUser&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
            &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bankCustomer&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;BankCustomer&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

            &lt;span class="nx"&gt;bankCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;enterLoginCredentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;validUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="p"&gt;});&lt;/span&gt;

            &lt;span class="nx"&gt;bankCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;clickLoginButton&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="nx"&gt;bankCustomer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeQuestion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;isUserLoggedInSuccessfully&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In short:&lt;br&gt;
Actors are the core of Screenplay because they bring meaning to your tests. They make scripts readable, reusable, and realistic.&lt;/p&gt;

&lt;p&gt;Next post: 🎬 Actions – what can an actor do?&lt;/p&gt;

</description>
      <category>testing</category>
      <category>codequality</category>
      <category>automation</category>
      <category>screenplay</category>
    </item>
    <item>
      <title>How to create a test ecosystem</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Fri, 28 Feb 2025 15:38:04 +0000</pubDate>
      <link>https://dev.to/will_drygla/how-to-create-a-test-ecosystem-4ahl</link>
      <guid>https://dev.to/will_drygla/how-to-create-a-test-ecosystem-4ahl</guid>
      <description>&lt;p&gt;As a QA Engineer, I have developed a robust test ecosystem that ensures the reliability and efficiency of our software. This ecosystem comprises multiple layers of testing, supported by shared libraries and exported modules. Below, I will explain the architecture, its components, and how they interact to maintain a high-quality standard in our software testing strategy.&lt;/p&gt;

&lt;p&gt;The test ecosystem consists of four top-level testing strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Tests&lt;/li&gt;
&lt;li&gt;Performance Tests&lt;/li&gt;
&lt;li&gt;End-to-End (E2E) Tests&lt;/li&gt;
&lt;li&gt;Manual Load Tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These top-level tests leverage a set of shared libraries and exported modules to facilitate efficient test execution and data management.&lt;/p&gt;

&lt;p&gt;Shared Libraries (LIBS)&lt;br&gt;
These are essential utilities and tools that provide core functionalities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;QA-Utils: A collection of utilities for authentication, database connections, and entity management.&lt;/li&gt;
&lt;li&gt;DB Operations: Handles database interactions including seeding and querying.&lt;/li&gt;
&lt;li&gt;Massive Data Generator: Automates large-scale data generation for performance and load tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exported Modules&lt;br&gt;
 These modules encapsulate specific functionalities and are consumed across different tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DB Operations - Seeder: Inserts test data into the database.&lt;/li&gt;
&lt;li&gt;DB Operations - Queries: Validates and retrieves test data from the database.&lt;/li&gt;
&lt;li&gt;QA-Utils - Entities: Defines data structures used across different tests.&lt;/li&gt;
&lt;li&gt;QA-Utils - DB Connection: Manages the database connection as a singleton instance.&lt;/li&gt;
&lt;li&gt;QA-Utils - Cognito Auth: Handles authentication using AWS Cognito.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each type of test consumes specific modules based on its requirements.&lt;/p&gt;

&lt;p&gt;API Tests&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Utilize QA-Utils - Cognito Auth to validate user tokens.&lt;/li&gt;
&lt;li&gt;Use DB Operations - Seeder to insert initial test data.&lt;/li&gt;
&lt;li&gt;Leverage DB Operations - Queries to validate test results in the database.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Performance Tests&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Utilize QA-Utils - Cognito Auth to validate user tokens.&lt;/li&gt;
&lt;li&gt;Use Massive Data Generator to create initial test data for performance evaluation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;E2E Tests&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Utilize QA-Utils - Cognito Auth to validate user tokens.&lt;/li&gt;
&lt;li&gt;Use DB Operations - Seeder to create initial test data required for automated workflows.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Manual Load Tests&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use Massive Data Generator to create the necessary volume of test data to simulate real-world load conditions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To streamline data creation and validation, the following internal dependencies exist:&lt;/p&gt;

&lt;p&gt;Massive Data Generator relies on the Seeder module to insert bulk data efficiently.&lt;/p&gt;

&lt;p&gt;Seeder consumes both DB Connection and Entities from QA-Utils to maintain consistency and reliability in test data setup.&lt;/p&gt;

&lt;p&gt;Conclusion&lt;/p&gt;

&lt;p&gt;This well-structured test ecosystem ensures that testing is automated, scalable, and efficient. By modularizing key functionalities and utilizing shared libraries, we achieve a seamless integration between different types of tests. This approach not only reduces redundancy but also enhances maintainability, making our software testing process robust and future-proof.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiud7syyq92ba5094fyr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiud7syyq92ba5094fyr5.png" alt="This mind map shows how the top level uses interacts with the libs" width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jest</category>
      <category>api</category>
      <category>testing</category>
      <category>codequality</category>
    </item>
    <item>
      <title>How to insert test context inside XML of jest</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 23 Feb 2025 16:48:20 +0000</pubDate>
      <link>https://dev.to/will_drygla/how-to-insert-test-context-inside-xml-of-jest-53da</link>
      <guid>https://dev.to/will_drygla/how-to-insert-test-context-inside-xml-of-jest-53da</guid>
      <description>&lt;p&gt;Have you ever checked an XML of test reports, and think that could be more info inside it?&lt;/p&gt;

&lt;p&gt;Today I will show you an way to insert test context inside the XML of jest (you could use any XML)&lt;/p&gt;

&lt;p&gt;In my article: &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/will_drygla" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1620116%2F69e57916-b6cf-4462-823a-90e36890f2ec.jpg" alt="will_drygla"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/will_drygla/test-context-a-powerfull-way-to-upgrade-our-tests-3eg2" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Test context: A powerfull way to debug our tests&lt;/h2&gt;
      &lt;h3&gt;Will Drygla ・ Feb 23&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#api&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#automation&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#codequality&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#testing&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;
 I talk about test context, now, we will put this context inside the XML.

&lt;p&gt;I use this XML on my pipelines, after test runs.&lt;/p&gt;

&lt;p&gt;Steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;While test run:&lt;br&gt;
1.1 - Collect test context&lt;br&gt;
1.2 - Save context to a JSON file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When tests are finished&lt;br&gt;
2.1 - Read the json file&lt;br&gt;
2.2 - Read the XML file&lt;br&gt;
2.3 - Find failures on XML&lt;br&gt;
2.4 - Find an context the matchs XML failure (I match then based on test name)&lt;br&gt;
2.5 - Write XML with the test context&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 1:&lt;br&gt;
&lt;code&gt;afterEach(async () =&amp;gt; {&lt;br&gt;
        const { assertionCalls, numPassingAsserts, currentTestName } = expect.getState();&lt;br&gt;
        const failedTest = assertionCalls &amp;gt; numPassingAsserts;&lt;br&gt;
        if (failedTest) {&lt;br&gt;
            createContextJsonFile(currentTestName, testContext);&lt;br&gt;
        }    });&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Step 2:&lt;br&gt;
    2.1: For each JSON on the 'reports' folder, whe call the function that save the data to XML report:&lt;br&gt;
&lt;code&gt;jsonFiles.forEach((file) =&amp;gt; {&lt;br&gt;
            const filePath = path.join(folderPath, file);&lt;br&gt;
            fs.readFile(filePath, 'utf8', (err, data) =&amp;gt; {&lt;br&gt;
                    const jsonData = JSON.parse(data);&lt;br&gt;
                    saveJsonToXml(jsonData)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2.4: Find an failure that match our JSON test name and write test context to it:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;let extraMessage = Context of test ${data.testName};&lt;br&gt;
    for (const key of Object.keys(data.context)) {&lt;br&gt;
        extraMessage = extraMessage.concat(\n${key}: ${JSON.stringify(data.context[key])});&lt;br&gt;
    }&lt;br&gt;
        let fileContent = fs.readFileSync(filePath, 'utf8');&lt;br&gt;
        const doc = new DOMParser().parseFromString(fileContent, 'text/xml');&lt;br&gt;
        const testCases = doc.getElementsByTagName('testcase');&lt;br&gt;
        for (let index = 0; index &amp;lt; testCases.length; index++) {&lt;br&gt;
            const testCase = testCases[index];&lt;br&gt;
            if (testCase.getAttribute('classname') === data.testName) {&lt;br&gt;
                const failureNode = testCase.getElementsByTagName('failure')[0];&lt;br&gt;
                if (failureNode) {&lt;br&gt;
                    failureNode.textContent = failureNode.textContent + extraMessage;&lt;br&gt;
&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;2.5: Write the XML with addition of new content:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;fileContent = new XMLSerializer().serializeToString(doc);&lt;br&gt;
        fs.writeFileSync(filePath, fileContent);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The result will be an XML like this:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpnwyltd4otbdofcg1bd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpnwyltd4otbdofcg1bd.png" alt="the image shows an xml with addition of text context, generate using the script described on this post" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>jest</category>
      <category>api</category>
      <category>testing</category>
      <category>codequality</category>
    </item>
    <item>
      <title>Test context: A powerfull way to debug our tests</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 23 Feb 2025 16:24:30 +0000</pubDate>
      <link>https://dev.to/will_drygla/test-context-a-powerfull-way-to-upgrade-our-tests-3eg2</link>
      <guid>https://dev.to/will_drygla/test-context-a-powerfull-way-to-upgrade-our-tests-3eg2</guid>
      <description>&lt;p&gt;In this post I will share with you guys an upgrade that I do to my API tests!&lt;/p&gt;

&lt;p&gt;To test our system, I use system tests (integration / API / service) where I create a seed of data on database, then, make a request to an endpoint, and later check the results on the database.&lt;/p&gt;

&lt;p&gt;The problem: When a test fails, on general, we don´t have much context about how things happened, and in general, we re run the tests locally to collect some info.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Also, this tests are great to be used when devs make big refactors that doesn´t change the application rules&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My solution: I create on each test, a context variable, that, during test execution, is filled with Id's, responses, data from database, and, after the test execution, if the test failed, we add this data to the XML report.&lt;/p&gt;

&lt;p&gt;How I do this:&lt;br&gt;
Before the test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a variable named testContext&lt;/li&gt;
&lt;li&gt;Fill this variable with basic data from our Seed (the principal data the test handle)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;During the test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Collect API responses and save to the testContext&lt;/li&gt;
&lt;li&gt;Collect data from database and save to the testContext before validations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the test fail, capture necessary info (test suite and name, failed reason) and add to testContext&lt;/li&gt;
&lt;li&gt;Save the testContext as a JSON file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This helps our debug, since, when a test fail, we could check this JSON, to see if the initial data is correct, if API responses was OK and how the data was saved into the database, all this inside a JSON of 50 lines hahaha. &lt;br&gt;
With a JSON like this, checking the logs, would be easy find the reason why it failed&lt;/p&gt;

&lt;p&gt;I will give you an example of this JSON:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw84jxnvg9crwwvtqxf36.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw84jxnvg9crwwvtqxf36.png" alt="The image shows and json, with test context" width="760" height="351"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After this if the test is running on pipeline, we add the JSON inside the test XML (I will make an post only about the XML handle)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u9jzuwbc9jbv4p0f7r9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0u9jzuwbc9jbv4p0f7r9.png" alt="The image shows an XML generate by jest on the end of tests, where we add the test context" width="800" height="318"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>automation</category>
      <category>codequality</category>
      <category>testing</category>
    </item>
    <item>
      <title>Using K6 to check the performance of our system</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 23 Feb 2025 15:58:57 +0000</pubDate>
      <link>https://dev.to/will_drygla/using-k6-to-check-the-performance-of-our-system-43b0</link>
      <guid>https://dev.to/will_drygla/using-k6-to-check-the-performance-of-our-system-43b0</guid>
      <description>&lt;p&gt;Today, I come here to talk about a project I have been doing for the past few weeks. I was working with performance tests, and I wanna share how I am taking metrics out of my tests. My challenge was to identify endpoints that, when the system was loaded above normal, presented problems or excessive slowness.&lt;/p&gt;

&lt;p&gt;I use Azure to run my tests, and export the results to Datadog (also export as XML to Azure, only to see basic info). The setup to run tests is simple: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Install K6, docker and other libs ( I use a custom AWS agent )&lt;/li&gt;
&lt;li&gt;Configure the project and bundle&lt;/li&gt;
&lt;li&gt;Start the Datadog docker ( who receives the data )&lt;/li&gt;
&lt;li&gt;Run tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And, in my configuration of scenarios in K6, I tag them to later I be able make custom dashboards, to extract data I use three tags:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test run ID: to identify the test run in general&lt;/li&gt;
&lt;li&gt;Scenario ID: separated by scenario, this could be the action that we are simulating or the screen we are simulating loads&lt;/li&gt;
&lt;li&gt;Request ID: this has the name of the request&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these three tags, I created a custom dashboard with three tables, each one with data based on a tag, and also, general graphs ( timing, status code). Using these tables, we can analyze the results of each request, but also analyze the groups/actions/screen results. So, after this, it was only about running tests, increasing the number of users or iterations, and extract metrics from the dashboard. &lt;/p&gt;

&lt;p&gt;Reading the graphs, we can extract some data:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; Using &lt;code&gt;requests per second&lt;/code&gt; + &lt;code&gt;HTTP request duration&lt;/code&gt;: we can see the increase of the duration of requests as the number of requests gets bigger&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;requests per second&lt;/code&gt; + &lt;code&gt;Status code&lt;/code&gt;: we can determine the number of requests per second that causes errors on the system&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;REQ_AVG_DURATION&lt;/code&gt; and &lt;code&gt;REQ_MAX_DURATION&lt;/code&gt;: analyzing these two metrics allows us to assess both the typical performance and the stability of response times. A large gap between these metrics can indicate sporadic spikes or outliers, suggesting potential instability under certain conditions. Together, they help pinpoint whether performance issues are consistent or isolated, guiding us in optimizing both the average experience and managing worst-case scenarios.
All this information must be analyzed alongside test duration, stage configurations, user loads, and other settings to gain a deeper understanding of system performance. This analysis allows us to identify areas for improvement or develop new hypotheses for further testing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these metrics, I was able to talk with our team and define a plan to improve these endpoints, to be more performative. I will let some prints of the dashboard ( the prints are individual of each graph). With three or four runs, you should be able to collect enough data to make a good presentation, or map some improvements.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnee2hksiih0m48r0mh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fucnee2hksiih0m48r0mh.png" alt="The image shows a dashboard of the test results on datadog, showing number of users, requests per second" width="800" height="905"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>k6</category>
      <category>performance</category>
      <category>codequality</category>
      <category>testing</category>
    </item>
    <item>
      <title>My favorite K6 executors</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 23 Feb 2025 15:55:56 +0000</pubDate>
      <link>https://dev.to/will_drygla/my-favorite-k6-executors-3aal</link>
      <guid>https://dev.to/will_drygla/my-favorite-k6-executors-3aal</guid>
      <description>&lt;p&gt;Today, it’s a beautiful day for me, raining, cold and I’m off work, so, I decided to make a 'chimarrão' and study a little bit, and I choose a great theme, k6 scenarios executors!&lt;/p&gt;

&lt;p&gt;When we define scenarios in the k6 options, we can define the executor, and, with a brief reading about them, I wasn’t able to understand much, so I decided to do my best, and create a mind map with the basics about them.&lt;/p&gt;

&lt;p&gt;With the mind map, I achieve a better understanding of each one, and i decided to share two of them with you guys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;constant-vus&lt;br&gt;
This executor is simple and really useful, it makes constant iterations based on the number of VUs and duration that we configured. This is great to simulate a number of users making the same action at the same time. In real life, we could use this to test a stock trading system, and check how the system handles the amount of users buying or selling stocks at the same time.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ramping-arrival-rate&lt;br&gt;
This executor starts on a rate ( rate of interactions / time ) and for each stage it updates the rate based on what we configure. This is really useful, like, we could use this to make tests of system capacity, but instead of simulate a number of users using the system, we configure the number of iterations on each time, for example, we could define a start in 10 iterations per second and ramp up to 100 iterations per second, and see how the system handle it. In real life, we could use this to test an e-commerce system, simulating a slow start of sales, then, a ramp up simulates a lot of users buying at the same time, and after, ramp down.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm really excited about studying performance testing and k6 (I already have some experience with JMeter). It's great to learn how we can use these tests to ensure a system meets user needs and stakeholder expectations, while also confirming that the system can handle potential high usage or load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb1ewdyedaoartwqwkgf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb1ewdyedaoartwqwkgf.png" alt="The image shows a mind map about k6 executors and some info about each of them" width="800" height="422"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>k6</category>
      <category>performance</category>
    </item>
    <item>
      <title>Uploading screenshots to Azure tests</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 23 Feb 2025 15:52:10 +0000</pubDate>
      <link>https://dev.to/will_drygla/uploading-screenshots-to-azure-tests-55me</link>
      <guid>https://dev.to/will_drygla/uploading-screenshots-to-azure-tests-55me</guid>
      <description>&lt;p&gt;I wanna share a solution that I have developed to improve my debugging with Cypress and Azure. Cypress takes screenshots of test failures, which give us a lot of context about the failure, but, I didn’t find a way to visualize these screenshots with Azure. So, I looked over the internet and found some articles, I took this as a base, and developed a custom solution. I will bring here step-by-step how I do it.&lt;/p&gt;

&lt;p&gt;Basically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Grab all Cypress screenshots and simplify they name&lt;/li&gt;
&lt;li&gt; Get all test runs for that release&lt;/li&gt;
&lt;li&gt; Then Get all failed tests to that release &lt;/li&gt;
&lt;li&gt; For each test find a matching screenshot and upload it
Let's see:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;1- We need to get all screenshots and save its path on a variable to use later, here, comes our first problem, depending of how the test fails, the screenshot name changes, appears a 'before each', 'after each', to make this work, I made a function to simplify the name, it receives the full path, remove unnecessary data, and save to a pathMap, then, after we use the same function to simplify the test case name and look for a match:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;function simplifyName(filename: string): string {&lt;br&gt;
 return filename&lt;br&gt;
 .replace(/ --/g, '')&lt;br&gt;
 .replace(/"/g, '')&lt;br&gt;
 .replace(/ \(failed\)\.png/g, '')&lt;br&gt;
 .replace(/before each hook/g, '')&lt;br&gt;
 .replace(/after each hook/g, '')&lt;br&gt;
 .replace(/for/g, '')&lt;br&gt;
 .replace(/ /g, '');&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;async function collectPngFiles(directory: string, pathMap: Map&amp;lt;string, string&amp;gt;): Promise&amp;lt;void&amp;gt; {&lt;br&gt;
 const entries = await fs.readdir(directory, { withFileTypes: true });&lt;br&gt;
 for (const entry of entries) {&lt;br&gt;
 const fullPath = path.join(directory, entry.name);&lt;br&gt;
 if (entry.isDirectory()) {&lt;br&gt;
 await collectPngFiles(fullPath, pathMap);&lt;br&gt;
 } else if (entry.isFile() &amp;amp;&amp;amp; entry.name.endsWith('.png')) {&lt;br&gt;
 const simplifiedName = simplifyName(entry.name);&lt;br&gt;
 pathMap.set(simplifiedName, fullPath);&lt;br&gt;
 }&lt;br&gt;
 }&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These two functions save the simplified name and its corresponding full path to a variable pathMap.&lt;/p&gt;

&lt;p&gt;2- Now, we need to get all failed tests on the release of Azure. To this, I make a get on Azure, that returns all test runs from that release, with that in hand, for each test run, I make a new get, but this time, only searching for failed tests. Then, for each failed test, I apply the same algorithm to simplify its name, then we need to check if there is a screenshot who matches the test case name on the pathMap .&lt;/p&gt;

&lt;p&gt;&lt;code&gt;const testRunResponse = await axios.get(testRunUrl, {&lt;br&gt;
 headers: getHeaders()&lt;br&gt;
 })&lt;br&gt;
 const listOfRuns = testRunResponse.data.value&lt;br&gt;
 for (const testRun of listOfRuns[]) {&lt;br&gt;
 const testResultsUrl = ${teamFoundationCollectionUri}${teamProjectId}/_apis/test/runs/${testRun.id}/results?api-version=5.1&amp;amp;outcomes=Failed;&lt;br&gt;
 const testResultsResponse = await axios.get(testResultsUrl, { headers: getHeaders() });&lt;br&gt;
 for (const testResult of testResultsResponse.data.value) {&lt;br&gt;
 let testCaseName = simplifyName(testResult.testCase.name);&lt;br&gt;
 const testResultId = testResult.id;&lt;br&gt;
 const screenshotPath = pathMap.get(testCaseName);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;3- When we find a match, between the screenshots taken by Cypress and failed tests on Azure, we need to upload it to Azure on that test run, to be available to check on azure:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;if (screenshotPath) {&lt;br&gt;
 const screenshotFilenameWithoutPath = path.basename(screenshotPath);&lt;br&gt;
 const createTestResultsAttachmentUrl = ${teamFoundationCollectionUri}${teamProjectId}/_apis/test/runs/${testRun.id}/results/${testResultId}/attachments?api-version=5.1-preview.1;&lt;br&gt;
 const base64string = await fs.readFile(screenshotPath, { encoding: 'base64' });&lt;br&gt;
 const jsonBody = {&lt;br&gt;
 fileName: screenshotFilenameWithoutPath,&lt;br&gt;
 comment: 'Attaching screenshot',&lt;br&gt;
 attachmentType: 'GeneralAttachment',&lt;br&gt;
 stream: base64string&lt;br&gt;
 };&lt;br&gt;
 const response = await axios.post(createTestResultsAttachmentUrl, jsonBody, {&lt;br&gt;
 headers: {&lt;br&gt;
 Authorization: Basic ${btoa(:${personalAccessToken}&lt;/code&gt;)}&lt;code&gt;,&lt;br&gt;
 'Content-Type': 'application/json'&lt;br&gt;
 }&lt;br&gt;
 });&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then we only need to deal with Azure response.&lt;/p&gt;

&lt;p&gt;This is a Typescript script, that is the same of the tests, it runs after tests on Azure. I made this a few months ago, and this is really helping me debug, since I don’t need to run the test locally again to see what is wrong, the context given by the screenshots really helps the understanding.&lt;/p&gt;

&lt;p&gt;I hope it helps somebody with a similar problem!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5carfcbl32cwo0rjhke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc5carfcbl32cwo0rjhke.png" alt="Screenshot that show how the uploaded screenshot of cypress appears on the Azure test page" width="800" height="270"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cypress</category>
      <category>typescript</category>
      <category>automation</category>
      <category>test</category>
    </item>
    <item>
      <title>Screenplay: another way to think about e2e tests</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 19 Jan 2025 23:10:58 +0000</pubDate>
      <link>https://dev.to/will_drygla/screenplay-another-way-to-think-about-e2e-tests-ioh</link>
      <guid>https://dev.to/will_drygla/screenplay-another-way-to-think-about-e2e-tests-ioh</guid>
      <description>&lt;h2&gt;
  
  
  Lately I have been looking for alternatives for the 'commum' way of writing automated tests, the automated tests code in my view, care too much with code, and little with behavior.
&lt;/h2&gt;

&lt;p&gt;To change this, I found the Screenplay pattern, where, basically, we will write test code thinking on behavior, results, and the code will only be a tool to this.&lt;/p&gt;

&lt;p&gt;The Screenplay focus on Actors, this Actors have Abilities, make Actions and asks Questions, with this, we can abstract our code to be outside the test.&lt;/p&gt;

&lt;h3&gt;
  
  
  I implemented the Screenplay pattern on Cypress and Typescript like this, for a software of warehouse management:
&lt;/h3&gt;

&lt;p&gt;Let's imaginate a warehouse management system, in this system, we have two types of users, the managers and the workers.&lt;br&gt;
The workers, receive new products, make purchase orders and label new products, the managers, approve purchase orders, generate reportes and more.&lt;br&gt;
Let's see how can we automate the tests for this software using Screenplay&lt;/p&gt;

&lt;p&gt;PS: I will simplify the code for a better understanding&lt;/p&gt;

&lt;h2&gt;
  
  
  Actors:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. I use a base-actor:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;in the base-actor has a constructor, that receives a list of actions and questions&lt;/li&gt;
&lt;li&gt;there is the methods &lt;code&gt;makeAction&lt;/code&gt; and &lt;code&gt;makeQuestion&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Each user of tests extends this base-actor passing to him necessary Actions and Questions:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
actors/warehouseManager.ts
    WarehouseManager{ 
        actions: 
            approvePurchaseOrder,
            generateReports,
            planShipments,
            assignTasks,
            createProducts
        questions:
            wasPurchaseOrderApproved,
            wasReportGenerated,
            wasShipmentPlanned
            wasTaskAssigned,
            wasProductCreated
    }
actors/warehouseWorker.ts
     WarehouseWorker{ 
        actions: 
            makePurchaseOrder,
            receivesDeliveries,
            generateProductLabels
        questions:
            wasPurchaseOrderGenerated,
            wasDeliveryReceived,
            wasProductLabelsGenerated
    }

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Actions:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  - Actor make Action to interact with the system
&lt;/h3&gt;

&lt;h3&gt;
  
  
  - I divide Actions based on features
&lt;/h3&gt;

&lt;h3&gt;
  
  
  - Actions can receive parameters
&lt;/h3&gt;

&lt;h3&gt;
  
  
  - Actions describe a behavior of user on the feature
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
actions/purchaseOrder.ts
    purchaseOrderActions{
        makePurchaseOrder(input: NewPurchaseOrderInput){
            cy.get('#btn-new-purchase-order').click()
            for(const item of input.items){
                cy.get('#order-new-item').click()
                cy.get('#input-search-items').type(item)
                cy.get('#select-option').contain(item)
            }
            ...continues
        }
        approvePurchaseOrder(input: ApprovePurchaseOrderInput){
            cy.get(`#purchase-order-${input.orderId}`)
            cy.get('#btn-approve-order').click()
        }
    }
actions/products.ts
    productActions{
        createProducts(input: NewProductInput){
            cy.get('#btn-new-product').click()
            cy.get('#input-product-name').type(input.productName)
            ...continues
        }
        generateProductLabels(input: GenerateProductLabelInput){
            cy.get('#btn-generate-product-label').click()
            cy.get('#select-label-type').click()
            cy.get(`#option-${input.labelType}`).click()
            for(const product of input.products){
                cy.get('#select-products').click()
                cy.get(`#option-${product}`).click()
            }
            ...continues
        }
    }

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Questions:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  - Actor make Questions to validate system state
&lt;/h3&gt;

&lt;h3&gt;
  
  
  - Questions are also based on features
&lt;/h3&gt;

&lt;h3&gt;
  
  
  - Questions can receive parameters
&lt;/h3&gt;

&lt;h3&gt;
  
  
  - A Question is a 'expected' on the end of code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
questions/purchaseOrder.ts
    purchaseOrderQuestions{
        wasPurchaseOrderApproved(input: WasPurchasedOrderApprovedInput){
            cy.get(`#orders-li-${input.orderId}).click()
            cy.get('#order-status').should('be.eql', input.expectedStatus);
        }
        wasPurchaseOrderGenerated(input: WasPurchaseOrderGeneratedInput){
            cy.get(`#orders-li-${input.orderId}).should('be.visible')
        }
    }
questions/products.ts
    productQuestions{
        wasProductCreated(input: WasProductCreatedInput){
            cy.get('#li-products').should('contain', input.productName)
            ...continues
        }
        wasProductLabelsGenerated(input: WasProductLabelsGeneratedInput){
            cy.get('#btn-product-labels').click()
            for(const product of input.products){
                cy.get(#li-product).should('contain', product)
            }
            ...continues
        }
    }

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

&lt;/div&gt;



&lt;h2&gt;
  
  
  With this design it's easy to write test code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For example:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
e2e/cypress/purchaseOrders/approvePurchaseOrder.ts
describe('approval of purchase orders', () =&amp;gt; {
    beforeEach(() =&amp;gt; {
        const databaseAdministrator = new DatabaseAdministrator();
        databaseAdministrator.makeAction('generateData', seed);
    })
    it('warehouse manager should approve a purchase order that is pending', () =&amp;gt;{
        warehouseManager = new WarehouseManager()
        warehouseManager.makeAction('visitScreen', 'orders');
        warehouseManager.makeAction('approvePurchaseOrder', 'new-order-01');
        warehouseManager.makeQuestion('wasPurchaseOrderApproved', 'new-order-01')
    })
})
e2e/cypress/products/generateProductLabels.ts
describe('generate product labels', () =&amp;gt; {
    beforeEach(() =&amp;gt; {
        ...handle test preparation
    })
    it('warehouse worker should generate product labels', () =&amp;gt;{
        warehouseWorker = new WarehouseWorker()
        warehouseWorker.makeAction('visitScreen', 'products');
        warehouseWorker.makeAction('generateProductLabels', ['item-01', 'item-19', 'item-2523']);
        warehouseWorker.makeQuestion('wasProductLabelsGenerated',  ['item-01', 'item-19', 'item-2523'])
    })
})

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;remember, the code it's simplified to better understanding, real code need more details&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Points:
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. With this pattern, we create tests that are easy to understand and maintain.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Tests written in the screenplay pattern use a structured, human-readable format, reducing the learning curve for new team members.&lt;/li&gt;
&lt;li&gt;The separation of concerns in this pattern minimizes redundancy and simplifies updates when the system changes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Writing frontend code with good test identifiers facilitates the creation of automated tests.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Well-named selectors and identifiers make it easier to map test steps to system functionality, improving readability.&lt;/li&gt;
&lt;li&gt;By merely reviewing the Actions in the tests, stakeholders can grasp how the system behaves without needing in-depth technical knowledge.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Instead of using Cypress env to manage commands, we use Classes, like common code.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;This approach aligns test code structure with the development team's patterns, fostering consistency across the project.&lt;/li&gt;
&lt;li&gt;Using classes promotes reusability and makes the test code more modular and scalable.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Inside Actions, there could be other commands, like 'selectOption' or 'visitScreen'.
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;For smaller, reusable commands, Cypress.Commands.add is a practical choice to encapsulate these behaviors.&lt;/li&gt;
&lt;li&gt;This ensures that the primary logic in Actions remains clean and focused, while Cypress env handles auxiliary functionalities.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvqh7hs67327mj92zg8l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkvqh7hs67327mj92zg8l.png" alt=" " width="320" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Understanding Performance Testing: A Guide to Testing System Capacity</title>
      <dc:creator>Will Drygla</dc:creator>
      <pubDate>Sun, 20 Oct 2024 12:11:04 +0000</pubDate>
      <link>https://dev.to/will_drygla/understanding-performance-testing-a-guide-to-testing-system-capacity-lh4</link>
      <guid>https://dev.to/will_drygla/understanding-performance-testing-a-guide-to-testing-system-capacity-lh4</guid>
      <description>&lt;p&gt;Helloo, today, I decided to talk a little bit about performance tests. In the last few days I have been working with performance tests, and I’d like to share how I approach these tests to ensure system performance. &lt;/p&gt;

&lt;p&gt;While it may be tempting to overload the system just to see how it handles failure, it is crucial to gather meaningful metrics during testing.&lt;/p&gt;

&lt;p&gt;Performance testing can vary significantly depending on the specific goals and expectations set by the company. In this article, I’ll focus on a few scenarios aimed at evaluating the system’s capacity.&lt;/p&gt;

&lt;p&gt;I start identifying the baseNumberOfUsers, let’s say we are testing a system of a bank agency, that uses the same system for all users, let’s divide the users into three types: Operators, ATM users and APP users, so, imagine that the agency has 10 ATM’s and 30 Operators, with a average number of users in the app of 50.&lt;/p&gt;

&lt;p&gt;We could say that the baseNumberOfAtmUsers is 1, baseNumberOfOperators is 3, and baseNumberOfAppUsers is 5.&lt;/p&gt;

&lt;p&gt;And I  use a variable to set the scalability, which I’ll call scalabilityMultiplier. The scalabilityMultiplier allows me to scale the number of users proportionally, simulating different levels of system load&lt;/p&gt;

&lt;p&gt;For the scenarios, I am writing three for each type of user, in general, two simple and one more complex, for example, let's write the scenarios for the ATM user:&lt;/p&gt;

&lt;p&gt;One: Withdrawing money ( 4 GET requests + 3 POST requests );&lt;br&gt;
Two: Transferring money ( 4 GET requests + 3 POST requests );&lt;br&gt;
Three: Performing a deposit, payment, and withdrawal ( 12 GET requests + 6 POST requests );&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfo1fkdnaik0pdzg4lbm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkfo1fkdnaik0pdzg4lbm.png" alt=" " width="660" height="710"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, we can test our system and take some metrics of it.&lt;/p&gt;

&lt;p&gt;appUsers = baseNumberOfAppUsers * scalabilityMultiplier&lt;/p&gt;

&lt;p&gt;operators = baseNumberOfOperators * scalabilityMultiplier&lt;/p&gt;

&lt;p&gt;atmUsers= baseNumberOfAtmUsers * scalabilityMultiplier&lt;/p&gt;

&lt;p&gt;We begin by testing with one-third of the total users and gradually ramp up to the maximum.&lt;/p&gt;

&lt;p&gt;Let’s get  two metrics I use:&lt;/p&gt;

&lt;p&gt;percentageOfSuccess&lt;br&gt;
avgReqDuration&lt;/p&gt;

&lt;p&gt;With this, we could test the limits, checking both, like, even if the percentageOfSuccess remains above 99.9%, a high avgReqDuration indicates the system is reaching its performance limits at that particular user load.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzfcu4j08ri13e5o3od5.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmzfcu4j08ri13e5o3od5.jpg" alt=" " width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
