<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[playwright]]></title><description><![CDATA[playwright]]></description><link>https://howtoplaywright.online</link><generator>RSS for Node</generator><lastBuildDate>Fri, 17 Apr 2026 23:53:55 GMT</lastBuildDate><atom:link href="https://howtoplaywright.online/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I Usually Start Automating User Stories in Playwright]]></title><description><![CDATA[When a new story comes for automation, I don’t touch the code immediately. I follow a simple and steady approach that helps avoid rework and confusion.
1. Functional understanding comes first
My first step is always on the functional side. I read the...]]></description><link>https://howtoplaywright.online/how-i-usually-start-automating-user-stories-in-playwright</link><guid isPermaLink="true">https://howtoplaywright.online/how-i-usually-start-automating-user-stories-in-playwright</guid><category><![CDATA[Playwright Automation ]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Thu, 19 Feb 2026 06:17:05 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1771482277100/fd5b45b6-51cf-4284-85e6-ba7c6a5b84b3.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When a new story comes for automation, I don’t touch the code immediately. I follow a simple and steady approach that helps avoid rework and confusion.</p>
<h2 id="heading-1-functional-understanding-comes-first">1. Functional understanding comes first</h2>
<p>My first step is always on the <strong>functional side</strong>. I read the user story fully and check if all acceptance criteria make sense. If anything is unclear or has gaps, I talk to the <strong>developer or product owner</strong> and clarify it.<br />It's better to clear doubts early instead of assuming something and later rewriting automation.</p>
<h2 id="heading-2-write-manual-test-cases">2. Write manual test cases</h2>
<p>Once the functionality is clear, I write <strong>manual test cases</strong> covering all possible scenarios:</p>
<ul>
<li><p>happy paths</p>
</li>
<li><p>edge cases</p>
</li>
<li><p>negative cases</p>
</li>
<li><p>functional and UI validations</p>
</li>
</ul>
<p>This gives me confidence that I have covered the story from all angles.</p>
<h2 id="heading-3-manual-execution-before-automation">3. Manual execution before automation</h2>
<p>Next, I execute these manual test cases on the application.</p>
<ul>
<li><p>If I find bugs, I log them and wait for fixes.</p>
</li>
<li><p>If everything works fine, then I move towards automation.</p>
</li>
</ul>
<p>This avoids writing automation for a broken feature.</p>
<h2 id="heading-4-if-there-is-a-blocker-still-make-partial-progress">4. If there is a blocker, still make partial progress</h2>
<p>Sometimes the story is half-working or blocked. In those cases, I don’t sit idle. I do whatever is possible:</p>
<ul>
<li><p>open all reachable screens</p>
</li>
<li><p>capture locators</p>
</li>
<li><p>design the page objects</p>
</li>
<li><p>plan which class should hold which method</p>
</li>
<li><p>create placeholder/dummy methods without implementations</p>
</li>
</ul>
<p>This saves time later because the structure is already ready.</p>
<h2 id="heading-5-when-the-story-is-stable-start-automation">5. When the story is stable, start automation</h2>
<p>Once the feature is stable, automation becomes straightforward. I start implementing the test using the existing framework conventions and project structure.</p>
<h2 id="heading-6-use-codegen-for-a-quick-head-start">6. Use codegen for a quick head start</h2>
<p>I run the same scenarios using <strong>Playwright codegen</strong>. Codegen gives raw steps which are not production-ready, but it helps me quickly get all actions and locators.</p>
<p>I then pass this generated script to <strong>AI tools</strong> and ask them to rewrite it following:</p>
<ul>
<li><p>Page Object Model</p>
</li>
<li><p>DRY (Don’t Repeat Yourself)</p>
</li>
<li><p>proper naming</p>
</li>
<li><p>reusable functions</p>
</li>
</ul>
<p>This gives a good starting structure. I only use AI as an assistant, not as a final answer.</p>
<h2 id="heading-7-keep-an-md-guide-in-the-repository">7. Keep an <code>.md</code> guide in the repository</h2>
<p>Maintaining a <code>.md</code> file with good examples of:</p>
<ul>
<li><p>page structure</p>
</li>
<li><p>locator strategy</p>
</li>
<li><p>fixtures usage</p>
</li>
<li><p>naming conventions<br />  is very helpful.<br />  Anyone new in the team can learn faster, and even I can reuse patterns easily.</p>
</li>
</ul>
<h2 id="heading-8-replace-weak-locators-manually">8. Replace weak locators manually</h2>
<p>AI or codegen cannot always give good locators. They usually pick long CSS or nth-child types, which are flaky.<br />So I manually rewrite locators using:</p>
<ul>
<li><p><code>getByRole</code></p>
</li>
<li><p><code>getByTestId</code></p>
</li>
<li><p>stable attributes</p>
</li>
</ul>
<p>This improves long‑term stability.</p>
<h2 id="heading-9-write-meaningful-assertions">9. Write meaningful assertions</h2>
<p>Codegen cannot create strong assertions. It only does basic checks like:</p>
<ul>
<li><p>element visible</p>
</li>
<li><p>text present</p>
</li>
</ul>
<p>But real automation needs:</p>
<ul>
<li><p>data validation</p>
</li>
<li><p>API + UI comparison</p>
</li>
<li><p>sorting logic</p>
</li>
<li><p>complex workflows</p>
</li>
</ul>
<p>These I write manually based on business rules.</p>
<h2 id="heading-final-thoughts">Final thoughts</h2>
<p>AI is helpful, but it cannot understand the product like a human tester. It can speed up repetitive parts, but functional thinking and assertion design still need human effort.<br />This is the simple and practical process I follow for automating one story in a stable and clean way.</p>
]]></content:encoded></item><item><title><![CDATA[How to Manage Unexpected Popups in Playwright Testing]]></title><description><![CDATA[Sometimes when you're testing a website, things don't go as planned. You might be filling out a form or clicking a button, and suddenly — boom — a popup appears out of nowhere. It could be a newsletter signup, a discount offer when you're about to le...]]></description><link>https://howtoplaywright.online/how-to-manage-unexpected-popups-in-playwright-testing</link><guid isPermaLink="true">https://howtoplaywright.online/how-to-manage-unexpected-popups-in-playwright-testing</guid><category><![CDATA[playwright]]></category><category><![CDATA[Playwright Automation ]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Mon, 27 Oct 2025 05:45:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1761543822262/c0528c72-fbdc-45c6-ab9f-876fb995de0b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<p>Sometimes when you're testing a website, things don't go as planned. You might be filling out a form or clicking a button, and suddenly — boom — a popup appears out of nowhere. It could be a newsletter signup, a discount offer when you're about to leave the page, a survey, or even a live chat window asking if you need help. These overlays can block the main content and interrupt your test flow.</p>
<blockquote>
<p>The tricky part? These popups don’t always show up. Sometimes they appear, sometimes they don’t. That makes it hard to write stable tests because you can’t predict when they’ll show up.</p>
</blockquote>
<p>That’s where Playwright’s special function comes in — <code>addLocatorHandler</code>.</p>
<p>This function lets you set up a handler that watches for a specific popup or overlay. If it appears, the handler kicks in and takes care of it (like clicking the close button), so your test can continue without getting stuck.</p>
<p>In this blog, I’ll show you how to use this feature to handle those unexpected overlays and keep your tests running smoothly.</p>
<hr />
<h2 id="heading-demo">Demo:</h2>
<p>let me show you a demo. I have created a very simple site, and which might show subscribe newsletter popup 60% of the time.</p>
<p>To launch a site locally, follow these steps:</p>
<ol>
<li>save this file with .html extension</li>
</ol>
<pre><code class="lang-xml"><span class="hljs-meta">&lt;!DOCTYPE <span class="hljs-meta-keyword">html</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">html</span> <span class="hljs-attr">lang</span>=<span class="hljs-string">"en"</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">head</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">meta</span> <span class="hljs-attr">charset</span>=<span class="hljs-string">"UTF-8"</span> /&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">title</span>&gt;</span>Newsletter Popup Demo<span class="hljs-tag">&lt;/<span class="hljs-name">title</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">style</span>&gt;</span><span class="css">
      <span class="hljs-selector-tag">body</span> {
        <span class="hljs-attribute">font-family</span>: Arial, sans-serif;
        <span class="hljs-attribute">margin</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">overflow</span>: auto;
      }

      <span class="hljs-selector-class">.overlay</span> {
        <span class="hljs-attribute">display</span>: none;
        <span class="hljs-attribute">position</span>: fixed;
        <span class="hljs-attribute">top</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">left</span>: <span class="hljs-number">0</span>;
        <span class="hljs-attribute">width</span>: <span class="hljs-number">100vw</span>;
        <span class="hljs-attribute">height</span>: <span class="hljs-number">100vh</span>;
        <span class="hljs-attribute">background-color</span>: <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.6</span>);
        <span class="hljs-attribute">z-index</span>: <span class="hljs-number">1000</span>;
        <span class="hljs-attribute">justify-content</span>: center;
        <span class="hljs-attribute">align-items</span>: center;
      }

      <span class="hljs-selector-class">.modal</span> {
        <span class="hljs-attribute">background</span>: white;
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">30px</span>;
        <span class="hljs-attribute">border-radius</span>: <span class="hljs-number">10px</span>;
        <span class="hljs-attribute">width</span>: <span class="hljs-number">300px</span>;
        <span class="hljs-attribute">text-align</span>: center;
        <span class="hljs-attribute">box-shadow</span>: <span class="hljs-number">0</span> <span class="hljs-number">0</span> <span class="hljs-number">15px</span> <span class="hljs-built_in">rgba</span>(<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0.3</span>);
        <span class="hljs-attribute">position</span>: relative;
      }

      <span class="hljs-selector-class">.modal</span> <span class="hljs-selector-tag">input</span><span class="hljs-selector-attr">[type=<span class="hljs-string">"email"</span>]</span> {
        <span class="hljs-attribute">width</span>: <span class="hljs-number">90%</span>;
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span>;
        <span class="hljs-attribute">margin</span>: <span class="hljs-number">10px</span> <span class="hljs-number">0</span>;
      }

      <span class="hljs-selector-class">.modal</span> <span class="hljs-selector-tag">button</span> {
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">10px</span> <span class="hljs-number">20px</span>;
        <span class="hljs-attribute">margin-top</span>: <span class="hljs-number">10px</span>;
      }

      <span class="hljs-selector-class">.close-btn</span> {
        <span class="hljs-attribute">position</span>: absolute;
        <span class="hljs-attribute">top</span>: <span class="hljs-number">10px</span>;
        <span class="hljs-attribute">right</span>: <span class="hljs-number">15px</span>;
        <span class="hljs-attribute">font-size</span>: <span class="hljs-number">20px</span>;
        <span class="hljs-attribute">font-weight</span>: bold;
        <span class="hljs-attribute">color</span>: <span class="hljs-number">#999</span>;
        <span class="hljs-attribute">cursor</span>: pointer;
      }

      <span class="hljs-selector-class">.close-btn</span><span class="hljs-selector-pseudo">:hover</span> {
        <span class="hljs-attribute">color</span>: <span class="hljs-number">#333</span>;
      }

      <span class="hljs-selector-class">.content</span> {
        <span class="hljs-attribute">padding</span>: <span class="hljs-number">20px</span>;
      }

      <span class="hljs-selector-class">.hidden</span> {
        <span class="hljs-attribute">display</span>: none;
      }
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">style</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">head</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">body</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"overlay"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"newsletterOverlay"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"modal"</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">span</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"close-btn"</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"closeOverlay()"</span>&gt;</span>×<span class="hljs-tag">&lt;/<span class="hljs-name">span</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">h2</span>&gt;</span>Subscribe to our Newsletter<span class="hljs-tag">&lt;/<span class="hljs-name">h2</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Get the latest updates and offers!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"email"</span> <span class="hljs-attr">placeholder</span>=<span class="hljs-string">"Enter your email"</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">onclick</span>=<span class="hljs-string">"subscribe()"</span>&gt;</span>Subscribe<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">div</span> <span class="hljs-attr">class</span>=<span class="hljs-string">"content"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"mainContent"</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">h1</span>&gt;</span>Welcome to Our Awesome Site!<span class="hljs-tag">&lt;/<span class="hljs-name">h1</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>Enjoy browsing our content. You might get a surprise!<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span>

      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Your name:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"name"</span> /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Your age:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"number"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"age"</span> /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Your profession:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"profession"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Your city:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"city"</span> /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Your state:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"state"</span> /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Your country:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"country"</span> /&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">div</span>&gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">label</span>&gt;</span>Your continent:<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"continent"</span> /&gt;</span>
      <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>
      <span class="hljs-tag">&lt;<span class="hljs-name">br</span> /&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">div</span>&gt;</span>

    <span class="hljs-tag">&lt;<span class="hljs-name">script</span>&gt;</span><span class="javascript">
      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">showOverlay</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-keyword">const</span> shouldShow = <span class="hljs-built_in">Math</span>.random() &lt; <span class="hljs-number">0.5</span>; <span class="hljs-comment">// 50% chance</span>
        <span class="hljs-keyword">if</span> (shouldShow) {
          <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"newsletterOverlay"</span>).style.display = <span class="hljs-string">"flex"</span>;
          <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"mainContent"</span>).classList.add(<span class="hljs-string">"hidden"</span>);
          <span class="hljs-built_in">document</span>.body.style.overflow = <span class="hljs-string">"hidden"</span>;
        }
      }

      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">closeOverlay</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"newsletterOverlay"</span>).style.display = <span class="hljs-string">"none"</span>;
        showForm();
      }

      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">subscribe</span>(<span class="hljs-params"></span>) </span>{
        alert(<span class="hljs-string">"Thanks for subscribing!"</span>);
        closeOverlay();
      }

      <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">showForm</span>(<span class="hljs-params"></span>) </span>{
        <span class="hljs-built_in">document</span>.getElementById(<span class="hljs-string">"mainContent"</span>).classList.remove(<span class="hljs-string">"hidden"</span>);
        <span class="hljs-built_in">document</span>.body.style.overflow = <span class="hljs-string">"auto"</span>;
      }

      <span class="hljs-built_in">window</span>.onload = <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
        showForm(); <span class="hljs-comment">// Show form initially</span>
        <span class="hljs-built_in">setTimeout</span>(showOverlay, <span class="hljs-number">2000</span>); <span class="hljs-comment">// Show popup after 2 seconds</span>
      };
    </span><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">body</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">html</span>&gt;</span>
</code></pre>
<ol start="2">
<li>run the .html file with live server in vscode</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761542936817/3a40a69c-23b9-48c6-8ca9-89459eaa8a36.png" alt class="image--center mx-auto" /></p>
<ol start="3">
<li>Copy the url. Same should be used inside the page.goto() method</li>
</ol>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1761546848222/fdd71e5b-83c1-4c29-9a44-99db7028fa07.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-playwright-code">Playwright code:</h2>
<p>Logic is simple</p>
<blockquote>
<p>Some popups might appear (the popup locator is provided to Playwright). If this locator appears, then do the following:</p>
</blockquote>
<p>We have set up a handler to detect if the given locator appears on the screen using the addLocatorHandler function. then do this action mentioned in the async function</p>
<p>Then it fills data in each textbox, and I have set a 1-second timeout for each fill. This allows time for the newsletter popup to appear; otherwise, the automation might finish before the popup shows up.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { test } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

test(<span class="hljs-string">"custom site with overlay handler"</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; {
  <span class="hljs-comment">// Register a handler to close the newsletter popup if it appears</span>
  <span class="hljs-keyword">await</span> page.addLocatorHandler(
    page.getByText(<span class="hljs-string">"Subscribe to our Newsletter"</span>),
    <span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"span.close-btn"</span>).click();
    }
  );

  <span class="hljs-comment">// Proceed with the test</span>
  <span class="hljs-keyword">await</span> page.goto(<span class="hljs-string">"http://127.0.0.1:5501/over.html"</span>); 
  <span class="hljs-comment">// you have to change this url. steps are available in below</span>
  <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"#name"</span>).fill(<span class="hljs-string">"Your Name"</span>);
  <span class="hljs-keyword">await</span> page.waitForTimeout(<span class="hljs-number">1000</span>); 
  <span class="hljs-comment">//given timeout otherwise test will be completed before the overlay appears</span>
  <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"#age"</span>).fill(<span class="hljs-string">"30"</span>);
  <span class="hljs-keyword">await</span> page.waitForTimeout(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"#profession"</span>).fill(<span class="hljs-string">"Software Engineer"</span>);
  <span class="hljs-keyword">await</span> page.waitForTimeout(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"#city"</span>).fill(<span class="hljs-string">"Bangalore"</span>);
  <span class="hljs-keyword">await</span> page.waitForTimeout(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"#state"</span>).fill(<span class="hljs-string">"Karnataka"</span>);
  <span class="hljs-keyword">await</span> page.waitForTimeout(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"#country"</span>).fill(<span class="hljs-string">"India"</span>);
  <span class="hljs-keyword">await</span> page.waitForTimeout(<span class="hljs-number">1000</span>);
  <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">"#continent"</span>).fill(<span class="hljs-string">"Asia"</span>);
});
</code></pre>
<hr />
<h2 id="heading-conclusion">Conclusion:</h2>
<p>Dealing with unexpected popups during test automation can be frustrating, especially when they don’t always show up. But with Playwright’s <code>addLocatorHandler</code>, we now have a clean way to handle these overlays without breaking our tests. It helps us keep our scripts smooth and focused on the actual task, even when something tries to interrupt. Try it out in your tests and get rid of all flaky tests.</p>
]]></content:encoded></item><item><title><![CDATA[“Advanced Assertions in Playwright: poll() vs toPass() Explained”]]></title><description><![CDATA[I am sure you might have come across the scenario where there is a synchronous step present, and you want to assert for some condition but unfortunately the condition will become true after a while but not instantly.

Here is the scenario:
A report i...]]></description><link>https://howtoplaywright.online/advanced-assertions-in-playwright-poll-vs-topass-explained</link><guid isPermaLink="true">https://howtoplaywright.online/advanced-assertions-in-playwright-poll-vs-topass-explained</guid><category><![CDATA[playwright]]></category><category><![CDATA[Playwright Automation ]]></category><category><![CDATA[Assertions]]></category><category><![CDATA[automation]]></category><category><![CDATA[automation testing ]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Wed, 15 Oct 2025 06:03:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1760508110660/3ec59336-b22e-4e49-8c1c-c42ad60a593d.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I am sure you might have come across the scenario where there is a synchronous step present, and you want to assert for some condition but unfortunately the condition will become true after a while but not instantly.</p>
<blockquote>
<p>Here is the scenario:</p>
<p>A report is expected to come from some source, and it takes time to receive.</p>
<p>Since this step which is verifying the report file’s presence is a synchronous one and it is not going to retry and fail in first attempt itself</p>
</blockquote>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {test,expect} <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-keyword">import</span> { existsSync } <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;

test(<span class="hljs-string">'file presence check'</span>, <span class="hljs-function">() =&gt;</span> {
  <span class="hljs-keyword">const</span> exists = existsSync(<span class="hljs-string">'downloads/report.pdf'</span>); 
  <span class="hljs-comment">// as per scenario , file is going to arrive late at this destination</span>
  expect(exists).toBeTruthy();
});
</code></pre>
<h2 id="heading-bad-solution">Bad solution:</h2>
<p>waiting for 1 minutes by default.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {test,expect,page} <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-keyword">import</span> { existsSync } <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;

test(<span class="hljs-string">'file presence check'</span>,<span class="hljs-keyword">async</span> ({page}) =&gt; {
  <span class="hljs-comment">// as per scenario , file is going to arrive late at this destination</span>
  <span class="hljs-keyword">await</span> page.waitForTimeout(<span class="hljs-number">1</span>*<span class="hljs-number">60</span>*<span class="hljs-number">1000</span>);
 <span class="hljs-keyword">const</span> exists = existsSync(<span class="hljs-string">'downloads/report.pdf'</span>); 
  expect(exists).toBeTruthy();
});
</code></pre>
<ol>
<li><p>If file arrives early, still test will wait for 1 minutes.</p>
</li>
<li><p>Using complex for loops or while loops</p>
<p> unnecessary code complexity</p>
</li>
</ol>
<hr />
<h2 id="heading-topass-assertion">ToPass() assertion</h2>
<p>This assertion is going to retry executing a block of code until all the assertions inside the block is passed.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {test,expect,page} <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
<span class="hljs-keyword">import</span> { existsSync } <span class="hljs-keyword">from</span> <span class="hljs-string">'fs'</span>;

test(<span class="hljs-string">'file presence check'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> expect(<span class="hljs-function">() =&gt;</span> {
    <span class="hljs-keyword">const</span> exists = existsSync(<span class="hljs-string">'downloads/report.pdf'</span>);
    expect(exists).toBeTruthy();
<span class="hljs-comment">//here i have only one assertion</span>
  }).toPass();
});
</code></pre>
<p>Why it is better:</p>
<ol>
<li><p>Assertion is going to pass and move to next steps for execution so timing is saved</p>
</li>
<li><p>No complex looping code is required.</p>
</li>
</ol>
<hr />
<h2 id="heading-poll-assertion">Poll() assertion</h2>
<p>It is similar to the Pass() but here it works on single assertion. it returns a value, and assertion is done on it.</p>
<p><strong>Scenario:</strong></p>
<p>let’s assume you have a field and it is value is changing and you want to check if it is greater than 5</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {test,expect,page} <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

test(<span class="hljs-string">'Number validateion'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">const</span> count = <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'.notification-badge'</span>).textContent();
  <span class="hljs-keyword">const</span> countInNumber = <span class="hljs-built_in">parseInt</span>(count || <span class="hljs-string">'0'</span>);
  expect(countInNumber).toBeGreaterThan(<span class="hljs-number">5</span>);
});
</code></pre>
<blockquote>
<p>Here also it is not wise to use the waitforTimeout for any other loops for the same reason we discussed before</p>
</blockquote>
<h2 id="heading-the-better-solution-would-be">The better solution would be</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> {test,expect,page} <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

test(<span class="hljs-string">'Number validateion'</span>, <span class="hljs-keyword">async</span> () =&gt; {
  <span class="hljs-keyword">await</span> expect.poll(<span class="hljs-keyword">async</span> () =&gt; {
      <span class="hljs-keyword">const</span> count = <span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'.notification-badge'</span>).textContent();
      <span class="hljs-keyword">return</span> <span class="hljs-built_in">parseInt</span>(count || <span class="hljs-string">'0'</span>);
  }).toBeGreaterThan(<span class="hljs-number">5</span>);
});
</code></pre>
<hr />
<h2 id="heading-understanding-the-difference">Understanding the difference</h2>
<p>This 2 may seem almost close with no difference but based on my experience this is what i have understood as the differences between this 2.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong><mark>Poll()</mark></strong></td><td><strong><mark>ToPass()</mark></strong></td></tr>
</thead>
<tbody>
<tr>
<td>1. it returns single value</td><td>it doesn’t return value.</td></tr>
<tr>
<td>2.The assertion is done on the returned value</td><td>Multiple assertions can be placed inside this block</td></tr>
<tr>
<td>3.Cleaner for value tracking</td><td>Suitable for multiple assertions</td></tr>
</tbody>
</table>
</div><hr />
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/EkhSMoT01_8">https://youtu.be/EkhSMoT01_8</a></div>
<p> </p>
<p>Thanks for reading my blog and hope you have learnt something new here.</p>
<p>please subscribe to my newsletter for more.</p>
]]></content:encoded></item><item><title><![CDATA[🧪 Understanding Browser, Context, and Page in Playwright]]></title><description><![CDATA[When working in playwright , it is very important to understand the in-built fixtures such as Browser , context, page

Just imagine how you will access a particular website.

You will open a browser . This does the below tasks in the background

Open...]]></description><link>https://howtoplaywright.online/understanding-browser-context-and-page-in-playwright</link><guid isPermaLink="true">https://howtoplaywright.online/understanding-browser-context-and-page-in-playwright</guid><category><![CDATA[playwright fixture]]></category><category><![CDATA[page,browser,context]]></category><category><![CDATA[playwright]]></category><category><![CDATA[Fixtures]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Fri, 10 Oct 2025 07:16:09 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/JySoEnr-eOg/upload/b2d8f634aa3b4f30812318f0e2feab62.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When working in playwright , it is very important to understand the in-built fixtures such as Browser , context, page</p>
<blockquote>
<p>Just imagine how you will access a particular website.</p>
<ol>
<li><p>You will open a browser . This does the below tasks in the background</p>
<ol>
<li><p>Opens up your profile (so cookies, logged in sessions can be reused)</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760077527827/4a227c64-7c95-4fb3-ac87-18006ef9d896.png" alt class="image--center mx-auto" /></p>
</li>
<li><p>Creates a new tab automatically</p>
<p> <img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760077621226/05b8db82-1655-4eee-81a0-af9939e128f6.png" alt class="image--center mx-auto" /></p>
</li>
</ol>
</li>
<li><p>you will hit the url in the url textbox</p>
<ol>
<li><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760077706896/73988002-125b-43b9-83ee-0ff3b72be627.png" alt class="image--center mx-auto" /></li>
</ol>
</li>
</ol>
</blockquote>
<hr />
<p>just like above, we can do the things manually in the playwright code but why not preferred</p>
<pre><code class="lang-typescript">test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({}) =&gt; {
  <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> chromium.launch({ headless: <span class="hljs-literal">false</span> });
  <span class="hljs-keyword">const</span> context = <span class="hljs-keyword">await</span> browser.newContext();
  <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> context.newPage();
})
</code></pre>
<ol>
<li><p>Playwright creates browser and context behind the scenes because all tests need the page for sure, code redundancy is removed with this.</p>
</li>
<li><p>This will overwrite the project configuration from playwright.config.json file and will execute in that browser mentioned in the test only . here it will be chromium. you have to make code change to run it in firefox, webkit</p>
</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { test, expect } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({ page }) =&gt; {
  <span class="hljs-keyword">await</span> page.goto(
    <span class="hljs-string">"https://opensource-demo.orangehrmlive.com/web/index.php/dashboard/index"</span>
  );
});
</code></pre>
<blockquote>
<p>Playwright decides which browser to open based on the playwright.config.json file</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760079218807/c1966fc8-e7e3-4f63-9c4e-d9f399a8c0b2.png" alt class="image--center mx-auto" /></p>
<hr />
<p>Now let’s explore the Browser, context, page with examples</p>
<ol>
<li><h1 id="heading-browser">Browser</h1>
</li>
</ol>
<p>This below code creates a browser but if you run this, you won’t see anything on the screen. because playwright is super-fast</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { test, chromium, firefox, webkit } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;
test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({}) =&gt; {
 <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> chromium.launch({ headless: <span class="hljs-literal">false</span> });
 <span class="hljs-keyword">const</span> fireFox = <span class="hljs-keyword">await</span> firefox.launch({ headless: <span class="hljs-literal">false</span> });
 <span class="hljs-keyword">const</span> Safari = <span class="hljs-keyword">await</span> webkit.launch({ headless: <span class="hljs-literal">false</span> });
})
</code></pre>
<ol start="2">
<li><h1 id="heading-context">Context</h1>
</li>
</ol>
<p>This creates profile so each tests run in a new profile hence so isolated. But again this code won’t show anything on the screen</p>
<pre><code class="lang-typescript">test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({}) =&gt; {
  <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> chromium.launch({ headless: <span class="hljs-literal">false</span> });
  <span class="hljs-keyword">const</span> context = <span class="hljs-keyword">await</span> browser.newContext();
})
</code></pre>
<ol start="3">
<li><h1 id="heading-page">Page</h1>
</li>
</ol>
<p>Now when you create a page which is equal to a tab then it will show something on the UI</p>
<pre><code class="lang-typescript">test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({}) =&gt; {
  <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> chromium.launch({ headless: <span class="hljs-literal">false</span> });
  <span class="hljs-keyword">const</span> context = <span class="hljs-keyword">await</span> browser.newContext();
  <span class="hljs-keyword">await</span> context.newPage();
})
</code></pre>
<p>But that also will be closed very quickly.</p>
<p>Run this to see the 3 browsers open</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { test, chromium, firefox, webkit } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({}) =&gt; {
  <span class="hljs-keyword">const</span> chrome = <span class="hljs-keyword">await</span> chromium.launch({ headless: <span class="hljs-literal">false</span> });
  <span class="hljs-keyword">const</span> fireFox = <span class="hljs-keyword">await</span> firefox.launch({ headless: <span class="hljs-literal">false</span> });
  <span class="hljs-keyword">const</span> Safari = <span class="hljs-keyword">await</span> webkit.launch({ headless: <span class="hljs-literal">false</span> });

  <span class="hljs-keyword">const</span> chromeCntext= <span class="hljs-keyword">await</span> chrome.newContext();
  <span class="hljs-keyword">const</span> fireCntext= <span class="hljs-keyword">await</span> fireFox.newContext();
  <span class="hljs-keyword">const</span> safariCntext= <span class="hljs-keyword">await</span> Safari.newContext();

  <span class="hljs-keyword">await</span> chromeCntext.newPage();
  <span class="hljs-keyword">await</span> fireCntext.newPage();
  <span class="hljs-keyword">await</span> safariCntext.newPage();

});
</code></pre>
<hr />
<p>Now comes the fun part, let’s see it visually the execution.</p>
<h2 id="heading-scenario1-playing-with-multiple-page-tab">Scenario1: playing with multiple page (tab)</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { test, chromium, expect } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({}) =&gt; {
  <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> chromium.launch({ headless: <span class="hljs-literal">false</span> });
  <span class="hljs-keyword">const</span> context = <span class="hljs-keyword">await</span> browser.newContext();
  <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> context.newPage(); <span class="hljs-comment">//creating first page</span>

  <span class="hljs-keyword">await</span> page.goto(
    <span class="hljs-string">"https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"</span>
  );
  <span class="hljs-keyword">await</span> page.getByPlaceholder(<span class="hljs-string">"Username"</span>).fill(<span class="hljs-string">"Admin"</span>);
  <span class="hljs-keyword">await</span> page.getByPlaceholder(<span class="hljs-string">"Password"</span>).fill(<span class="hljs-string">"admin123"</span>);
  <span class="hljs-keyword">await</span> page.getByRole(<span class="hljs-string">"button"</span>, { name: <span class="hljs-string">"Login"</span> }).click();
  <span class="hljs-keyword">await</span> expect(
    page.getByText(<span class="hljs-string">"Employee Distribution by Sub Unit"</span>)
  ).toBeVisible();

 <span class="hljs-keyword">const</span> page2 = <span class="hljs-keyword">await</span> context.newPage(); <span class="hljs-comment">//creating second page using same context</span>
 <span class="hljs-keyword">await</span> page2.goto(
    <span class="hljs-string">"https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"</span>
  );
 <span class="hljs-keyword">await</span> expect(
    page2.getByText(<span class="hljs-string">"Employee Distribution by Sub Unit"</span>)
  ).toBeVisible();

});
</code></pre>
<h3 id="heading-code-explanation">Code explanation</h3>
<ol>
<li><p>Have hit the HRM site</p>
</li>
<li><p>Logged in the site in the first page</p>
</li>
<li><p>Created a second page (Tab)</p>
</li>
<li><p>hitting the logged in URL in second page</p>
<ol>
<li><p>It got logged in already rather than asking me to login.</p>
</li>
<li><p><strong>Because the sessions are shared between tabs</strong></p>
</li>
</ol>
</li>
</ol>
<p><strong>Output:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760078573711/68f00bf5-fb83-4797-b812-61c4bb79bb09.png" alt class="image--center mx-auto" /></p>
<p>you see there are 2 tabs, the home page is visible in both the screens rather than login screen</p>
<hr />
<h2 id="heading-scenario2-playing-with-multiple-context-profile-and-page-tab">Scenario2: playing with multiple context (profile) and page (tab)</h2>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { test, chromium, expect } <span class="hljs-keyword">from</span> <span class="hljs-string">"@playwright/test"</span>;

test.only(<span class="hljs-string">"validate orange hrm portal"</span>, <span class="hljs-keyword">async</span> ({}) =&gt; {
  <span class="hljs-keyword">const</span> browser = <span class="hljs-keyword">await</span> chromium.launch({ headless: <span class="hljs-literal">false</span> });
  <span class="hljs-keyword">const</span> context = <span class="hljs-keyword">await</span> browser.newContext();
  <span class="hljs-keyword">const</span> page = <span class="hljs-keyword">await</span> context.newPage();
  <span class="hljs-keyword">await</span> page.goto(
    <span class="hljs-string">"https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"</span>
  );
  <span class="hljs-keyword">await</span> page.getByPlaceholder(<span class="hljs-string">"Username"</span>).fill(<span class="hljs-string">"Admin"</span>);
  <span class="hljs-keyword">await</span> page.getByPlaceholder(<span class="hljs-string">"Password"</span>).fill(<span class="hljs-string">"admin123"</span>);
  <span class="hljs-keyword">await</span> page.getByRole(<span class="hljs-string">"button"</span>, { name: <span class="hljs-string">"Login"</span> }).click();
  <span class="hljs-keyword">await</span> expect(
    page.getByText(<span class="hljs-string">"Employee Distribution by Sub Unit"</span>)
  ).toBeVisible();
 <span class="hljs-keyword">const</span> context2 = <span class="hljs-keyword">await</span> browser.newContext(); <span class="hljs-comment">//creating 2nd context here</span>
  <span class="hljs-keyword">const</span> page2 = <span class="hljs-keyword">await</span> context2.newPage(); <span class="hljs-comment">//creating 2nd page using 2nd context</span>
 <span class="hljs-keyword">await</span> page2.goto(
    <span class="hljs-string">"https://opensource-demo.orangehrmlive.com/web/index.php/auth/login"</span>
  );
 <span class="hljs-keyword">await</span> expect(
    page2.getByText(<span class="hljs-string">"Employee Distribution by Sub Unit"</span>)
  ).toBeVisible();
});
</code></pre>
<h3 id="heading-code-explanation-1">Code explanation</h3>
<ol>
<li><p>Have hit the HRM site</p>
</li>
<li><p>Logged in the site in the first page</p>
</li>
<li><p>Created new context (context2) which is like new profile or incognito session</p>
</li>
<li><p>Created a second page (Tab) using the second context</p>
</li>
<li><p>hitting the logged in URL in second page</p>
<ol>
<li><p>It is asking me to login.</p>
</li>
<li><p><strong>Because the sessions are</strong> <em>not</em> <strong>shared between tabs</strong></p>
</li>
</ol>
</li>
</ol>
<p><strong>Output:</strong></p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1760079060744/2fcf6389-f877-4b2e-b176-708cccc25868.png" alt class="image--center mx-auto" /></p>
<p>you see there are 2 browser contexts opened, the home page is visible in the first page but the second page is on the login screen</p>
<hr />
<h2 id="heading-youtube-link-with-demo">Youtube link with demo</h2>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://youtu.be/Ty4CDIZwwlA?si=9B74odm0wkd6xng7">https://youtu.be/Ty4CDIZwwlA?si=9B74odm0wkd6xng7</a></div>
<p> </p>
<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">I hope you learnt something new in here. Please subscribe to my channel and newsletter to encourage me to write more and more</div>
</div>]]></content:encoded></item><item><title><![CDATA[🎯Understanding page.locator().click() in Playwright]]></title><description><![CDATA[What is locator.click()?


In Playwright, locator.click() is a high-level API used to simulate a mouse click on a web element. It’s part of the Locator API, which provides a robust way to interact with elements on the page.
await page.getByRole('butt...]]></description><link>https://howtoplaywright.online/understanding-pagelocatorclick-in-playwright</link><guid isPermaLink="true">https://howtoplaywright.online/understanding-pagelocatorclick-in-playwright</guid><category><![CDATA[playwright]]></category><category><![CDATA[automation testing ]]></category><category><![CDATA[Software Testing]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Tue, 07 Oct 2025 10:39:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/dFwtwXRQ2yQ/upload/c9df284c64b8f80c7725bc34a8e732fd.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<hr />
<ol>
<li><h2 id="heading-what-is-locatorclickhttplocatorclick">What is <a target="_blank" href="http://locator.click"><code>locator.click</code></a><code>()</code>?</h2>
</li>
</ol>
<p>In Playwright, <a target="_blank" href="http://locator.click"><code>locator.click</code></a><code>()</code> is a high-level API used to simulate a <strong>mouse click</strong> on a web element. It’s part of the <code>Locator</code> API, which provides a robust way to interact with elements on the page.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.getByRole(<span class="hljs-string">'button'</span>).click();
</code></pre>
<p><strong>This command does the following:</strong></p>
<ul>
<li><p>1.1 Finds the element matching the selector.</p>
</li>
<li><p>1.2 Auto-Waiting Mechanism</p>
<p>  Playwright <strong>automatically waits</strong> for the element to be:</p>
<ul>
<li><p><strong>Attached</strong> to the DOM</p>
</li>
<li><p><strong>Visible</strong> (not hidden via CSS or layout)</p>
</li>
<li><p><strong>Enabled</strong> (not disabled)</p>
</li>
<li><p><strong>Stable</strong> (not animating or moving)</p>
</li>
<li><p>This avoids flaky tests caused by premature clicks.</p>
</li>
</ul>
</li>
<li><p>1.3 Scrolls it into view if needed.</p>
<ul>
<li><p>If the element is outside the viewport, Playwright scrolls it into view using <code>element.scrollIntoViewIfNeeded()</code>.</p>
</li>
<li><p>This mimics how a real user would interact with off-screen elements.</p>
</li>
</ul>
</li>
<li><p>1.4 Performs a click action.</p>
<ul>
<li><p>When you use the click() method , it does the following actions in sequence to mimic a real user clicking experience.</p>
<ul>
<li><p><code>mousedown</code> —&gt; Like when the mouse is pressed, the button goes down</p>
</li>
<li><p><code>mouseup</code> —&gt; Like when the mouse is stopped pressing, the button comes up</p>
</li>
<li><p><code>click</code> —&gt; click is needed though the down and up is triggered otherwise the click won’t be triggered</p>
</li>
</ul>
</li>
</ul>
</li>
</ul>
<hr />
<ol start="2">
<li><h2 id="heading-options-you-can-pass-to-the-click-function"><strong>Options You Can Pass to the click function</strong></h2>
</li>
</ol>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'button'</span>).click({
  position: { x: <span class="hljs-number">10</span>, y: <span class="hljs-number">5</span> }, <span class="hljs-comment">// click at specific offset</span>
  button: <span class="hljs-string">'right'</span>, <span class="hljs-comment">// 'left' | 'middle' | 'right'</span>
  clickCount: <span class="hljs-number">2</span>,   <span class="hljs-comment">// for double-click</span>
  delay: <span class="hljs-number">100</span>,      <span class="hljs-comment">// ms between mousedown and mouseup</span>
  force: <span class="hljs-literal">true</span>,     <span class="hljs-comment">// bypass checks</span>
  modifiers: [<span class="hljs-string">'Alt'</span>, <span class="hljs-string">'Shift'</span>], <span class="hljs-comment">// keyboard modifiers</span>
  noWaitAfter: <span class="hljs-literal">true</span><span class="hljs-comment">// doesnt wait for navigation if click opens new page/elements</span>
  timeout: <span class="hljs-number">5000</span>,   <span class="hljs-comment">// override default timeout</span>
  trial: <span class="hljs-literal">true</span>
});
</code></pre>
<h3 id="heading-21-position">2.1 position</h3>
<p>Playwright calculates the <strong>clickable point</strong>:</p>
<ul>
<li><p>It finds the center of the visible bounding box.</p>
</li>
<li><p>If <code>position</code> is specified, it uses that offset.</p>
</li>
<li><p>It ensures the point is not obscured by another element.</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> locator.click({ position: { x: <span class="hljs-number">10</span>, y: <span class="hljs-number">5</span> } });
</code></pre>
<hr />
<h3 id="heading-22-right-clickleft-click-middle-click">2.2 Right click/Left click / Middle click</h3>
<p>When right click is needed on a element, this will do the trick.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'canvas'</span>).click({ button: <span class="hljs-string">'right'</span> }); <span class="hljs-comment">// Right-click</span>

<span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'canvas'</span>).click({ button: <span class="hljs-string">'middle'</span> }); <span class="hljs-comment">// Middle-click</span>
</code></pre>
<hr />
<h3 id="heading-23-double-click-or-more">2.3 Double click or more</h3>
<p>Sometimes double click will trigger an event, this option can be used in that scenario.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'text="Open File"'</span>).click({ clickCount: <span class="hljs-number">2</span> });
</code></pre>
<hr />
<h3 id="heading-24-delay">2.4 Delay</h3>
<p>it's used to <strong>introduce a pause between the</strong> <code>mousedown</code> and <code>mouseup</code> events discussed in 1.4</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'button'</span>).click({ delay: <span class="hljs-number">500</span> }); <span class="hljs-comment">// 500ms delay</span>
</code></pre>
<hr />
<h3 id="heading-25-force">2.5 force</h3>
<p>this option is used to <strong>bypass all actionability checks</strong> and <strong>force the click</strong>, even if the element is:</p>
<ul>
<li><p>Not visible</p>
</li>
<li><p>Not attached to the DOM</p>
</li>
<li><p>Covered by another element</p>
</li>
<li><p>Disabled</p>
</li>
<li><p>Outside the viewport</p>
</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'#hidden-button'</span>).click({ force: <span class="hljs-literal">true</span> });
</code></pre>
<p><strong>Sometimes, elements are technically present but:</strong></p>
<ul>
<li><p>Hidden by overlays or modals</p>
</li>
<li><p>Slightly off-screen</p>
</li>
<li><p>Temporarily disabled due to animations or transitions</p>
</li>
</ul>
<p>Using <code>force: true</code> allows you to click them anyway.</p>
<blockquote>
<p>Be cautious about this - it skips safety checks.</p>
</blockquote>
<hr />
<h3 id="heading-26-shiftclick">2.6 Shift+click</h3>
<p>lets you simulate <strong>keyboard modifier keys</strong> being held down during the click — like <strong>Shift</strong>, <strong>Ctrl</strong>, <strong>Alt</strong>, or <strong>Meta (Cmd on Mac)</strong>.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'.item'</span>).click({ modifiers: [<span class="hljs-string">'Shift'</span>] }); 
<span class="hljs-comment">//"Alt" | "Control" | "ControlOrMeta" | "Meta" | "Shift”</span>
</code></pre>
<hr />
<h3 id="heading-27-nowaitafter">2.7 NowaitAfter</h3>
<p>This is used to <strong>prevent Playwright from waiting for potential page navigations or asynchronous events</strong> that might be triggered by the click.</p>
<blockquote>
<p>Deprecated and defaults to false</p>
</blockquote>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> page.locator(<span class="hljs-string">'a#delayed-link'</span>).click({ noWaitAfter: <span class="hljs-literal">true</span> });
</code></pre>
<blockquote>
<p>will default to true in future releases</p>
</blockquote>
<hr />
<h3 id="heading-28-timeout">2.8 timeout</h3>
<p>it controls How Long Playwright Waits for actionability checks</p>
<p>If you don’t specify <code>timeout</code>, Playwright uses the <strong>default timeout</strong> (usually 30 seconds).</p>
<p>When a element takes time to load and you want to extend or limit the timeout different from gloabl timeout then this can be used.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> locator.click({ timeout: <span class="hljs-number">5000</span> }); <span class="hljs-comment">// custom timeout</span>
</code></pre>
<hr />
<h3 id="heading-29-trial-click"><strong>2.9 Trial Click</strong></h3>
<p>You can simulate a click <strong>without actually clicking</strong> using <code>trial: true</code>. This is useful for checking if an element is clickable.</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">await</span> locator.click({ trial: <span class="hljs-literal">true</span> });
</code></pre>
<hr />
<ol start="3">
<li><h2 id="heading-common-pitfalls"><strong>Common Pitfalls</strong></h2>
</li>
</ol>
<p>Some common failures and the causes</p>
<p>Playwright throws a <strong>detailed error</strong> with suggestions and a trace.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Problem</td><td>Cause</td><td>Fix</td></tr>
</thead>
<tbody>
<tr>
<td>Timeout error</td><td>Element not ready for click</td><td>Use <code>expect(locator).toBeVisible()</code> before click</td></tr>
<tr>
<td>Element not found</td><td>Wrong selector</td><td>Refine selector</td></tr>
<tr>
<td>Click has no effect</td><td>Page not hydrated</td><td>Wait for hydration marker or <code>networkidle</code></td></tr>
<tr>
<td>Click intercepted</td><td>Overlapping elements</td><td>Use <code>scrollIntoViewIfNeeded()</code> or <code>force: true</code></td></tr>
</tbody>
</table>
</div><hr />
<h2 id="heading-conclusion"><strong>Conclusion</strong></h2>
<p><code>page.locator().click()</code> is one of the most powerful and reliable ways to simulate user interaction in Playwright. By understanding its behavior, options, and best practices, you can write stable, maintainable, and robust automation scripts.</p>
<hr />
]]></content:encoded></item><item><title><![CDATA[🎯 Mastering getByRole in Playwright: Write Reliable and Accessible Tests]]></title><description><![CDATA[When writing UI tests, choosing the right selector can make or break your test suite. Fragile selectors like class names or IDs often lead to flaky tests that break with minor UI changes. That’s where getByRole comes in—a powerful, accessibility-firs...]]></description><link>https://howtoplaywright.online/mastering-getbyrole-in-playwright-write-reliable-and-accessible-tests</link><guid isPermaLink="true">https://howtoplaywright.online/mastering-getbyrole-in-playwright-write-reliable-and-accessible-tests</guid><category><![CDATA[playwright locators]]></category><category><![CDATA[playwright]]></category><category><![CDATA[Playwright Automation ]]></category><category><![CDATA[#Playwright #AutomationScript #WebTesting #Chromium #BrowserAutomation #CodeGeneration]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Fri, 03 Oct 2025 05:28:57 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759469663397/70a573ee-d0de-4587-b6be-9778f039534b.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When writing UI tests, choosing the right selector can make or break your test suite. Fragile selectors like class names or IDs often lead to flaky tests that break with minor UI changes. That’s where <code>getByRole</code> comes in—a powerful, accessibility-first query method that helps you write <strong>robust, readable, and maintainable tests</strong>.</p>
<p>In this post, we’ll explore how <code>getByRole</code> works, why it’s better than traditional selectors, and how to use it effectively in your Playwright tests.</p>
<hr />
<h2 id="heading-what-is-getbyrole">🔍 What is <code>getByRole</code>?</h2>
<p><code>getByRole</code> is a query method from the Testing Library family, integrated into Playwright via <code>@playwright/experimental-ct-*</code> packages. It allows you to select elements based on their ARIA roles, which describe the purpose of an element in the UI.</p>
<h2 id="heading-why-use-getbyrole">✅ Why Use <code>getByRole</code>?</h2>
<p>Here are some compelling reasons to use <code>getByRole</code>:</p>
<ul>
<li><p><strong>Accessibility-first</strong>: Encourages semantic HTML and accessible components.</p>
</li>
<li><p><strong>Resilient to UI changes</strong>: Doesn’t break when class names or IDs change.</p>
</li>
<li><p><strong>Readable</strong>: Clearly communicates the intent of the test.</p>
</li>
<li><p><strong>Built-in filtering</strong>: Supports options like <code>name</code>, <code>level</code>, and <code>hidden</code>.</p>
</li>
</ul>
<h2 id="heading-basic-usage">🧪 Basic Usage</h2>
<p>Here’s a simple example using Playwright Component Testing with React:</p>
<pre><code class="lang-typescript"><span class="hljs-keyword">import</span> { test, expect } <span class="hljs-keyword">from</span> <span class="hljs-string">'@playwright/experimental-ct-react'</span>;
<span class="hljs-keyword">import</span> LoginForm <span class="hljs-keyword">from</span> <span class="hljs-string">'./LoginForm'</span>;

test(<span class="hljs-string">'should submit login form'</span>, <span class="hljs-keyword">async</span> ({ mount }) =&gt; {
  <span class="hljs-keyword">const</span> component = <span class="hljs-keyword">await</span> mount(&lt;LoginForm /&gt;);

  <span class="hljs-keyword">const</span> usernameInput = component.getByRole(<span class="hljs-string">'textbox'</span>, { name: <span class="hljs-string">'Username'</span> });
  <span class="hljs-keyword">const</span> passwordInput = component.getByRole(<span class="hljs-string">'textbox'</span>, { name: <span class="hljs-string">'Password'</span> });
  <span class="hljs-keyword">const</span> submitButton = component.getByRole(<span class="hljs-string">'button'</span>, { name: <span class="hljs-string">'Login'</span> });

  <span class="hljs-keyword">await</span> usernameInput.fill(<span class="hljs-string">'gunasekaran'</span>);
  <span class="hljs-keyword">await</span> passwordInput.fill(<span class="hljs-string">'securepassword'</span>);
  <span class="hljs-keyword">await</span> submitButton.click();

  <span class="hljs-keyword">await</span> expect(component).toContainText(<span class="hljs-string">'Welcome, gunasekaran!'</span>
</code></pre>
<h2 id="heading-did-you-know-the-name-parameter-can-come-from-multiple-sources-not-just-visible-text-here-are-the-list-of-scenarios-that-name-can-handle">Did you know the <code>name</code> parameter can come from <strong>multiple sources</strong>, not just visible text?. here are the list of scenarios that name can handle.</h2>
<ol>
<li><strong>Visible text</strong></li>
</ol>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span>&gt;</span>Submit<span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>

await page.getByRole('button', { name: 'Submit' }).click();
</code></pre>
<ol start="2">
<li><code>aria-label</code></li>
</ol>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">button</span> <span class="hljs-attr">aria-label</span>=<span class="hljs-string">"Submit Form"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">button</span>&gt;</span>
await page.getByRole('button', { name: 'Submit Form' }).click();
</code></pre>
<ol start="3">
<li><code>aria-labelledby</code></li>
</ol>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">label</span> <span class="hljs-attr">id</span>=<span class="hljs-string">"lblUser"</span>&gt;</span>Username<span class="hljs-tag">&lt;/<span class="hljs-name">label</span>&gt;</span>
<span class="hljs-tag">&lt;<span class="hljs-name">input</span> <span class="hljs-attr">type</span>=<span class="hljs-string">"text"</span> <span class="hljs-attr">aria-labelledby</span>=<span class="hljs-string">"lblUser"</span>&gt;</span>

await page.getByRole('textbox', { name: 'Username' }).fill('admin');
</code></pre>
<ol start="4">
<li><strong>Alt text for images</strong></li>
</ol>
<pre><code class="lang-html"><span class="hljs-tag">&lt;<span class="hljs-name">img</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"logo.png"</span> <span class="hljs-attr">alt</span>=<span class="hljs-string">"Company Logo"</span>&gt;</span>
await page.getByRole('img', { name: 'Company Logo' }).click();
</code></pre>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>DOM Example</strong></td><td><strong>Accessible Name</strong></td><td><strong>Playwright Locator</strong></td><td><strong>Note</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>&lt;button&gt;Submit&lt;/button&gt;</code></td><td><code>"Submit"</code></td><td><code>page.getByRole('button', { name: 'Submit' })</code></td><td>Simple button with visible text.</td></tr>
<tr>
<td><code>&lt;button&gt;Submit   &lt;label&gt;form&lt;/lable&gt;   &lt;/button&gt;</code></td><td><code>"Submit form"</code></td><td><code>page.getByRole('button', { name: 'Submit form' })</code></td><td>button tag contains nested label tag. both can be concatenated in the name and used for identifying</td></tr>
<tr>
<td><code>&lt;input type="text" aria-labelledby="lblUser"&gt;&lt;label id="lblUser"&gt;Username&lt;/label&gt;</code></td><td><code>"Username"</code></td><td><code>page.getByRole('textbox', { name: 'Username' })</code></td><td>Label references input using <code>aria-labelledby</code>.</td></tr>
<tr>
<td><code>&lt;button aria-label="Save Changes"&gt;&lt;/button&gt;</code></td><td><code>"Save Changes"</code></td><td><code>page.getByRole('button', { name: 'Save Changes' })</code></td><td><code>aria-label</code> provides accessible name, visible text not required.</td></tr>
<tr>
<td><code>&lt;img src="logo.png" alt="Company Logo"&gt;</code></td><td><code>"Company Logo"</code></td><td><code>page.getByRole('img', { name: 'Company Logo' })</code></td><td><code>alt</code> attribute used for accessible name.</td></tr>
<tr>
<td><code>&lt;div role="checkbox" aria-checked="true"&gt;Accept Terms&lt;/div&gt;</code></td><td><code>"Accept Terms"</code></td><td><code>page.getByRole('checkbox', { name: 'Accept Terms', checked: true })</code></td><td>Checkbox with text inside div, state can be filtered.</td></tr>
<tr>
<td><code>&lt;h1&gt;Welcome User&lt;/h1&gt;</code></td><td><code>"Welcome User"</code></td><td><code>page.getByRole('heading', { name: 'Welcome User', level: 1 })</code></td><td>Heading with plain text, no nested tags.</td></tr>
<tr>
<td><code>&lt;h1 name='greeting'&gt;Welcome User&lt;/h1&gt;</code></td><td><code>"Welcome User"</code>  </td></tr>
</tbody>
</table>
</div><p><code>"greeting"</code> —&gt; wrong | <code>page.getByRole('heading', { name: 'Welcome User' })</code> | Dont get confused with name attribute<br /><code>name</code> <strong>attribute ≠ accessible name</strong> in Playwright. |</p>
<p>There are html elements that has implicit role so it won’t have the role attribute manually but still can be identified using role</p>
<h3 id="heading-implicit-aria-roles-in-native-html-elements">✅ Implicit ARIA Roles in Native HTML Elements</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Implicit ARIA Role</strong></td><td><strong>HTML Elements</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>link</code></td><td><code>&lt;a href&gt;</code>, <code>&lt;area href&gt;</code></td></tr>
<tr>
<td><code>button</code></td><td><code>&lt;button&gt;</code>, <code>&lt;input type="button"&gt;</code>, <code>&lt;input type="submit"&gt;</code>, <code>&lt;input type="reset"&gt;</code>, <code>&lt;input type="image"&gt;</code></td></tr>
<tr>
<td><code>checkbox</code></td><td><code>&lt;input type="checkbox"&gt;</code></td></tr>
<tr>
<td><code>radio</code></td><td><code>&lt;input type="radio"&gt;</code></td></tr>
<tr>
<td><code>slider</code></td><td><code>&lt;input type="range"&gt;</code></td></tr>
<tr>
<td><code>textbox</code></td><td><code>&lt;input type="email"&gt;</code>, <code>&lt;input type="tel"&gt;</code>, <code>&lt;input type="text"&gt;</code>, <code>&lt;input type="url"&gt;</code>, <code>&lt;textarea&gt;</code></td></tr>
<tr>
<td><code>listbox</code></td><td><code>&lt;select&gt;</code></td></tr>
<tr>
<td><code>option</code></td><td><code>&lt;option&gt;</code></td></tr>
<tr>
<td><code>progressbar</code></td><td><code>&lt;progress&gt;</code></td></tr>
<tr>
<td><code>text</code></td><td><code>&lt;b&gt;</code>, <code>&lt;i&gt;</code>, <code>&lt;u&gt;</code>, <code>&lt;strong&gt;</code>, <code>&lt;em&gt;</code>, <code>&lt;dfn&gt;</code>, <code>&lt;abbr&gt;</code>, <code>&lt;span&gt;</code></td></tr>
<tr>
<td><code>meter</code></td><td><code>&lt;meter&gt;</code></td></tr>
<tr>
<td><code>form</code></td><td><code>&lt;form&gt;</code></td></tr>
<tr>
<td><code>table</code></td><td><code>&lt;table&gt;</code></td></tr>
<tr>
<td><code>columnheader</code> / <code>rowheader</code></td><td><code>&lt;th&gt;</code></td></tr>
<tr>
<td><code>cell</code></td><td><code>&lt;td&gt;</code></td></tr>
<tr>
<td><code>row</code></td><td><code>&lt;tr&gt;</code></td></tr>
<tr>
<td><code>rowgroup</code></td><td><code>&lt;thead&gt;</code>, <code>&lt;tbody&gt;</code>, <code>&lt;tfoot&gt;</code></td></tr>
<tr>
<td><code>list</code></td><td><code>&lt;ul&gt;</code>, <code>&lt;ol&gt;</code></td></tr>
<tr>
<td><code>listitem</code></td><td><code>&lt;li&gt;</code></td></tr>
<tr>
<td><code>dialog</code></td><td><code>&lt;dialog&gt;</code></td></tr>
<tr>
<td><code>heading</code></td><td><code>&lt;h1&gt;</code>, <code>&lt;h2&gt;</code>, <code>&lt;h3&gt;</code>, <code>&lt;h4&gt;</code>, <code>&lt;h5&gt;</code>, <code>&lt;h6&gt;</code></td></tr>
<tr>
<td><code>document</code></td><td><code>&lt;iframe&gt;</code>, <code>&lt;code&gt;</code>, <code>&lt;pre&gt;</code>, <code>&lt;cite&gt;</code>, <code>&lt;time&gt;</code></td></tr>
<tr>
<td><code>img</code></td><td><code>&lt;img&gt;</code> <em>(if</em> <code>alt</code> attribute is present)</td></tr>
<tr>
<td><code>banner</code></td><td><code>&lt;header&gt;</code></td></tr>
<tr>
<td><code>contentinfo</code></td><td><code>&lt;footer&gt;</code></td></tr>
<tr>
<td><code>main</code></td><td><code>&lt;main&gt;</code></td></tr>
<tr>
<td><code>article</code></td><td><code>&lt;article&gt;</code></td></tr>
<tr>
<td><code>complementary</code></td><td><code>&lt;aside&gt;</code></td></tr>
<tr>
<td><code>navigation</code></td><td><code>&lt;nav&gt;</code></td></tr>
<tr>
<td><code>region</code></td><td><code>&lt;section&gt;</code>, <code>&lt;blockquote&gt;</code>, <code>&lt;address&gt;</code></td></tr>
<tr>
<td><code>figure</code></td><td><code>&lt;figure&gt;</code></td></tr>
<tr>
<td><code>caption</code></td><td><code>&lt;figcaption&gt;</code></td></tr>
<tr>
<td><code>group</code></td><td><code>&lt;summary&gt;</code>, <code>&lt;details&gt;</code></td></tr>
</tbody>
</table>
</div><h2 id="heading-conclusion">📚 Conclusion</h2>
<p>Using <code>getByRole</code> in Playwright helps you write tests that are not only more reliable but also promote better accessibility. It’s a win-win for developers and users alike. Whether you're testing components or full pages, adopting role-based queries will elevate the quality of your test suite.</p>
]]></content:encoded></item><item><title><![CDATA[🎯Event handling in playwright]]></title><description><![CDATA[What is an event?
An event is a signal that something has happened in the system or application — like a user clicking a button, a page loading, or a dialog appearing.
An event represents a specific action or occurrence that you can listen for and re...]]></description><link>https://howtoplaywright.online/event-handling-in-playwright</link><guid isPermaLink="true">https://howtoplaywright.online/event-handling-in-playwright</guid><category><![CDATA[playwright]]></category><category><![CDATA[automation testing ]]></category><category><![CDATA[Software Testing]]></category><category><![CDATA[Automated Testing]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Fri, 19 Sep 2025 06:50:46 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1758264624613/02f79556-ec9d-4c19-b55b-3d36a5d25b22.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-an-event">What is an event?</h2>
<p>An <strong>event</strong> is a signal that something has happened in the system or application — like a user clicking a button, a page loading, or a dialog appearing.</p>
<p>An <strong>event</strong> represents a specific action or occurrence that you can <strong>listen for</strong> and <strong>respond to</strong> in your code.</p>
<h2 id="heading-here-is-the-list-of-events-supported-in-the-playwright">✅ Here is the list of events supported in the playwright</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Event Name</strong></td><td><strong>Description</strong></td></tr>
</thead>
<tbody>
<tr>
<td><code>'close'</code></td><td>Fired when the page is closed.</td></tr>
<tr>
<td><code>'console'</code></td><td>Fired when the page logs a message to the console.</td></tr>
<tr>
<td><code>'crash'</code></td><td>Fired when the page crashes.</td></tr>
<tr>
<td><code>'dialog'</code></td><td>Fired when an alert, confirm, prompt, or beforeunload dialog is triggered.</td></tr>
<tr>
<td><code>'domcontentloaded'</code></td><td>Fired when the DOM content is fully loaded.</td></tr>
<tr>
<td><code>'download'</code></td><td>Fired when a file download is initiated.</td></tr>
<tr>
<td><code>'filechooser'</code></td><td>Fired when a file input is activated.</td></tr>
<tr>
<td><code>'frameattached'</code></td><td>Fired when a frame is attached to the page.</td></tr>
<tr>
<td><code>'framedetached'</code></td><td>Fired when a frame is removed from the page.</td></tr>
<tr>
<td><code>'framenavigated'</code></td><td>Fired when a frame navigates to a new URL.</td></tr>
<tr>
<td><code>'load'</code></td><td>Fired when the page finishes loading.</td></tr>
<tr>
<td><code>'pageerror'</code></td><td>Fired when a JavaScript error occurs.</td></tr>
<tr>
<td><code>'popup'</code></td><td>Fired when a new page (popup or tab) is opened.</td></tr>
<tr>
<td><code>'request'</code></td><td>Fired when a network request is made.</td></tr>
<tr>
<td><code>'requestfailed'</code></td><td>Fired when a network request fails.</td></tr>
<tr>
<td><code>'requestfinished'</code></td><td>Fired when a network request completes.</td></tr>
<tr>
<td><code>'response'</code></td><td>Fired when a network response is received.</td></tr>
<tr>
<td><code>'websocket'</code></td><td>Fired when a WebSocket is created.</td></tr>
<tr>
<td><code>'worker'</code></td><td>Fired when a web worker is created.</td></tr>
</tbody>
</table>
</div><p>Let’s pick the event “<strong>dialog</strong>” for the demo</p>
<ol>
<li><h3 id="heading-await-promise">await promise</h3>
</li>
</ol>
<p>Before the event occurs on the browser, we have to instruct the playwright to be ready to handle the event</p>
<p>so the sequence is</p>
<ul>
<li><p>store promise by using waitforevent method without await keyword</p>
</li>
<li><p>you do the action which triggers the popup</p>
</li>
<li><p>you wait for the stored promise to fulfil (captured in first step)</p>
</li>
<li><p>you take action on the event.</p>
<pre><code class="lang-typescript">  <span class="hljs-comment">// Start waiting for the dialog before triggering it</span>
  <span class="hljs-keyword">const</span> dialogPromise = page.waitForEvent(<span class="hljs-string">'dialog'</span>);

  <span class="hljs-comment">// Trigger the dialog (e.g., clicking a button that opens an alert/confirm/prompt)</span>
  <span class="hljs-keyword">await</span> page.getByText(<span class="hljs-string">'Trigger Dialog'</span>).click();

  <span class="hljs-comment">// Wait for the dialog to appear</span>
  <span class="hljs-keyword">const</span> dialog = <span class="hljs-keyword">await</span> dialogPromise;

  <span class="hljs-comment">// Accept the dialog</span>
  <span class="hljs-keyword">await</span> dialog.accept();

  <span class="hljs-comment">// Dismiss the dialog (uncomment to use instead of accept)</span>
  <span class="hljs-comment">// await dialog.dismiss();</span>
</code></pre>
</li>
</ul>
<ol start="2">
<li><h2 id="heading-promiseall">Promise.all</h2>
</li>
</ol>
<ul>
<li>(same as above but no need (await dialogPromise;) statement)</li>
</ul>
<pre><code class="lang-typescript"><span class="hljs-comment">// Start waiting for the dialog and trigger it at the same time</span>
<span class="hljs-keyword">const</span> [dialog] = <span class="hljs-keyword">await</span> <span class="hljs-built_in">Promise</span>.all([
  page.waitForEvent(<span class="hljs-string">'dialog'</span>),
  page.getByText(<span class="hljs-string">'Trigger Dialog'</span>).click()
]);

<span class="hljs-comment">// Accept the dialog</span>
<span class="hljs-keyword">await</span> dialog.accept();

<span class="hljs-comment">// Dismiss the dialog (uncomment to use instead of accept)</span>
<span class="hljs-comment">// await dialog.dismiss();</span>
</code></pre>
<ol start="3">
<li><h3 id="heading-pageondialog-callback-method">✅ <code>page.on('dialog', callback)</code> method</h3>
<ul>
<li><p><strong>Purpose</strong>: Listens for <strong>all dialog events</strong> continuously and performs the actions defined.</p>
</li>
<li><p><strong>Usage</strong>: Ideal when dialogs can appear <strong>multiple times</strong> during a test.</p>
</li>
</ul>
</li>
</ol>
<pre><code class="lang-typescript">    <span class="hljs-comment">// Set up a listener for dialog events</span>
    page.on(<span class="hljs-string">'dialog'</span>, <span class="hljs-keyword">async</span> dialog =&gt; {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Dialog message: <span class="hljs-subst">${dialog.message()}</span>`</span>);

      <span class="hljs-comment">// Accept the dialog</span>
      <span class="hljs-keyword">await</span> dialog.accept();

      <span class="hljs-comment">// Dismiss the dialog (uncomment to use instead of accept)</span>
      <span class="hljs-comment">// await dialog.dismiss();</span>
    });

    <span class="hljs-comment">// Trigger the dialog (e.g., clicking a button that opens an alert/confirm/prompt)</span>
    <span class="hljs-keyword">await</span> page.getByText(<span class="hljs-string">'Trigger Dialog'</span>).click();
</code></pre>
<ol start="4">
<li><h3 id="heading-pageoncedialog-callback">✅ <code>page.once('dialog', callback)</code></h3>
<ul>
<li><p><strong>Purpose</strong>: Listens for <strong>only the next dialog event</strong> &amp; does whatever mentioned in the once block</p>
</li>
<li><p><strong>Usage</strong>: Cleaner than <code>page.on</code> when you expect <strong>just one dialog</strong>.</p>
</li>
</ul>
</li>
</ol>
<pre><code class="lang-typescript">    <span class="hljs-comment">// Set up a one-time listener for the next dialog event</span>
    page.once(<span class="hljs-string">'dialog'</span>, <span class="hljs-keyword">async</span> dialog =&gt; {
      <span class="hljs-built_in">console</span>.log(<span class="hljs-string">`Dialog message: <span class="hljs-subst">${dialog.message()}</span>`</span>);

      <span class="hljs-comment">// Accept the dialog</span>
      <span class="hljs-keyword">await</span> dialog.accept();

      <span class="hljs-comment">// Dismiss the dialog (uncomment to use instead of accept)</span>
      <span class="hljs-comment">// await dialog.dismiss();</span>
    });

    <span class="hljs-comment">// Trigger the dialog (e.g., clicking a button that opens an alert/confirm/prompt)</span>
    <span class="hljs-keyword">await</span> page.getByText(<span class="hljs-string">'Trigger Dialog'</span>).click();
</code></pre>
]]></content:encoded></item><item><title><![CDATA[🎯Playwright: A Modern Framework for Web Automation Testing]]></title><description><![CDATA[Overview
Playwright is an open-source automation framework developed by Microsoft, designed for end-to-end testing of modern web applications. Released in January 2020, it has quickly gained popularity due to its cross-browser, cross-platform, and cr...]]></description><link>https://howtoplaywright.online/playwright-a-modern-framework-for-web-automation-testing</link><guid isPermaLink="true">https://howtoplaywright.online/playwright-a-modern-framework-for-web-automation-testing</guid><category><![CDATA[playwright]]></category><category><![CDATA[Testing]]></category><category><![CDATA[test-automation]]></category><category><![CDATA[Software Testing]]></category><dc:creator><![CDATA[Gross geek]]></dc:creator><pubDate>Fri, 19 Sep 2025 03:49:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1759469705521/2c34d483-5842-4234-91ca-3d6c7ac0d16a.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h3 id="heading-overview"><strong>Overview</strong></h3>
<p><strong>Playwright</strong> is an open-source automation framework developed by <strong>Microsoft</strong>, designed for <strong>end-to-end testing</strong> of modern web applications. Released in <strong>January 2020</strong>, it has quickly gained popularity due to its <strong>cross-browser</strong>, <strong>cross-platform</strong>, and <strong>cross-language</strong> capabilities.</p>
<h3 id="heading-key-features"><strong>Key Features</strong></h3>
<ul>
<li><p><strong>Cross-Browser Support</strong>: Automates Chromium (Chrome, Edge), Firefox, and WebKit (Safari).</p>
</li>
<li><p><strong>Multi-Language Support</strong>: Works with JavaScript, TypeScript, Python, Java, and .NET.</p>
</li>
<li><p><strong>Headless Mode</strong>: Enables fast, GUI-less testing ideal for CI/CD pipelines.</p>
</li>
<li><p><strong>Automatic Waiting</strong>: Waits for elements to be actionable, reducing flaky tests.</p>
</li>
<li><p><strong>Browser Contexts</strong>: Simulates multiple users with isolated sessions.</p>
</li>
<li><p><strong>Device Emulation</strong>: Tests responsive designs across various screen sizes.</p>
</li>
<li><p><strong>Network Interception</strong>: Mocks API responses and simulates network conditions.</p>
</li>
<li><p><strong>Built-in Debugging Tools</strong>: Includes codegen, trace viewer, and inspector.</p>
</li>
</ul>
<h3 id="heading-architecture"><strong>Architecture</strong></h3>
<p>Playwright uses <strong>WebSockets</strong> for communication with browsers, unlike Selenium’s HTTP-based approach. This allows faster and more reliable interactions. Each test runs in a separate <strong>browser context</strong>, ensuring full isolation and faster execution.</p>
<h3 id="heading-advantages-over-other-frameworks"><strong>Advantages Over Other Frameworks</strong></h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Feature</td><td>Playwright</td><td>Selenium</td><td>Cypress</td></tr>
</thead>
<tbody>
<tr>
<td>Supported Languages</td><td>JS, Python, Java, .NET</td><td>JS, Python, Java, Ruby</td><td>JS/TS</td></tr>
<tr>
<td>Browser Support</td><td>Chrome, Firefox, Safari</td><td>Chrome, Firefox, IE</td><td>Chrome, Firefox</td></tr>
<tr>
<td>Headless Mode</td><td>Yes</td><td>Yes</td><td>Yes</td></tr>
<tr>
<td>Multi-Tab Support</td><td>Yes</td><td>No</td><td>Yes</td></tr>
<tr>
<td>Built-in Debugging</td><td>Yes</td><td>No</td><td>Yes</td></tr>
<tr>
<td>Automatic Wait</td><td>Yes</td><td>No</td><td>Yes</td></tr>
<tr>
<td>Shadow DOM Support</td><td>Yes</td><td>Limited</td><td>Limited</td></tr>
</tbody>
</table>
</div><p>Playwright stands out for its <strong>simplicity</strong>, <strong>speed</strong>, and <strong>robust API</strong>, making it ideal for modern web apps.</p>
<h3 id="heading-limitations"><strong>Limitations</strong></h3>
<ul>
<li><p><strong>No support for native mobile apps</strong>.</p>
</li>
<li><p><strong>Limited language support</strong> compared to Selenium.</p>
</li>
<li><p><strong>No support for legacy browsers like IE11</strong>.</p>
</li>
</ul>
<h3 id="heading-getting-started"><strong>Getting Started</strong></h3>
<p>To install and run Playwright:</p>
<pre><code class="lang-plaintext">npm init playwright@latest
# or
pip install playwright
</code></pre>
<p>Example test in TypeScript:</p>
<pre><code class="lang-plaintext">import { test, expect } from '@playwright/test';

test('has title', async ({ page }) =&gt; {
  await page.goto('https://playwright.dev/');
  await expect(page).toHaveTitle(/Playwright/);
});
</code></pre>
<h3 id="heading-best-practices"><strong>Best Practices</strong></h3>
<ul>
<li><p><strong>Modularize tests</strong> for maintainability.</p>
</li>
<li><p><strong>Use assertions</strong> to validate behavior.</p>
</li>
<li><p><strong>Run tests in parallel</strong> to speed up execution.</p>
</li>
<li><p><strong>Integrate with CI tools</strong> like Jenkins or GitHub Actions.</p>
</li>
</ul>
]]></content:encoded></item></channel></rss>