<?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: Faizal</title>
    <description>The latest articles on DEV Community by Faizal (@sshhfaiz).</description>
    <link>https://dev.to/sshhfaiz</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%2F3970793%2F98274403-df57-490b-b9dd-395fa04fb557.png</url>
      <title>DEV Community: Faizal</title>
      <link>https://dev.to/sshhfaiz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sshhfaiz"/>
    <language>en</language>
    <item>
      <title>10 Playwright Tips That Will Change How You Write Tests</title>
      <dc:creator>Faizal</dc:creator>
      <pubDate>Sat, 06 Jun 2026 11:18:23 +0000</pubDate>
      <link>https://dev.to/sshhfaiz/10-playwright-tips-that-will-change-how-you-write-tests-4mbj</link>
      <guid>https://dev.to/sshhfaiz/10-playwright-tips-that-will-change-how-you-write-tests-4mbj</guid>
      <description>&lt;p&gt;I remember the first week I seriously used Playwright.&lt;/p&gt;

&lt;p&gt;I was migrating a 200-test WDIO suite. I thought it would take two weeks. It took four days. Not because Playwright is magic — but because once you understand how it actually thinks, everything clicks into place faster than any other tool I have used.&lt;/p&gt;

&lt;p&gt;But I also made a lot of mistakes early on. I wrote tests the old way — the Selenium way — inside a tool that was designed for something better.&lt;/p&gt;

&lt;p&gt;These are the 10 tips that changed how I write Playwright tests. Not the basics you find in the docs. The real stuff.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 1 🎯 — Stop Using &lt;code&gt;waitForTimeout&lt;/code&gt;. Seriously.
&lt;/h2&gt;

&lt;p&gt;This is the first habit to break. If you are coming from Selenium or WDIO, you are used to sprinkling sleeps everywhere to handle timing issues.&lt;/p&gt;

&lt;p&gt;Playwright has auto-waiting built into every action. When you click an element, Playwright automatically waits for it to be visible, stable, and enabled before acting. You do not need to help it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Old habit — never do this&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Playwright way — just click, it handles the wait&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ When you genuinely need to wait for a condition&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#success-message&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;state&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every &lt;code&gt;waitForTimeout&lt;/code&gt; in your suite is a lie you are telling yourself. Replace them all.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 2 🔍 — Use &lt;code&gt;getByRole&lt;/code&gt; Over CSS Selectors
&lt;/h2&gt;

&lt;p&gt;Most engineers default to CSS selectors because that is what they know. But Playwright's locator methods like &lt;code&gt;getByRole&lt;/code&gt;, &lt;code&gt;getByText&lt;/code&gt;, and &lt;code&gt;getByLabel&lt;/code&gt; are more resilient and closer to how a real user interacts with your app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Fragile — breaks when a developer changes the class name&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.btn-primary-submit-checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Resilient — targets what the user actually sees&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Place Order&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Even better for forms&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email Address&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These locators survive UI refactors. A developer can change every class name in the codebase and your tests will still pass because you are targeting meaning, not implementation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 3 🏗️ — Use Page Object Model But Keep It Lean
&lt;/h2&gt;

&lt;p&gt;Page Object Model is still the right pattern in Playwright. But I see engineers over-engineer it — abstracting every single element into a getter, creating three layers of inheritance, building a framework that takes longer to navigate than the app itself.&lt;/p&gt;

&lt;p&gt;Keep it lean.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/LoginPage.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LoginPage&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="nx"&gt;page&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;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&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;emailInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&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;passwordInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&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;submitButton&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign In&lt;/span&gt;&lt;span class="dl"&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;errorMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;error-message&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;async&lt;/span&gt; &lt;span class="nf"&gt;login&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="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="k"&gt;await&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;emailInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&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="k"&gt;await&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;passwordInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fill&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="k"&gt;await&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;submitButton&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&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="c1"&gt;// In your test&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;loginPage&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;LoginPage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&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;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user@test.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123&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;One class per page. Actions as methods. Locators as properties. That is all you need.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 4 🌐 — Mock Your APIs for Faster, Stable Tests
&lt;/h2&gt;

&lt;p&gt;This is the most underused Playwright feature I see in real projects. Network interception is built-in, clean, and incredibly powerful. Use it to remove external dependencies from your UI tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Mock an API response directly in your test&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/api/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;route&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;await&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fulfill&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;99.99&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;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/products&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Test Product&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your UI tests should test UI behaviour. They should not fail because a third-party API was slow or returned unexpected data. Mock aggressively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 5 📸 — Use Trace Viewer When Tests Fail in CI
&lt;/h2&gt;

&lt;p&gt;When a test fails in CI, most engineers look at the error message and guess. Playwright gives you something far better — a full visual trace of everything that happened.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on-first-retry&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// captures trace only when test fails&lt;/span&gt;
    &lt;span class="na"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;only-on-failure&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;video&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;on-first-retry&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;Then open it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright show-trace trace.zip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get a full timeline — every action, every network request, every DOM snapshot, every screenshot — in a beautiful visual interface. Debugging a CI failure goes from 2 hours to 10 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 6 ⚙️ — Use &lt;code&gt;test.describe&lt;/code&gt; and &lt;code&gt;test.use&lt;/code&gt; for Context Isolation
&lt;/h2&gt;

&lt;p&gt;One of Playwright's most powerful features is the ability to configure context at the describe block level. Use it to run the same tests across different states without duplicating code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Run the same checkout tests as guest and logged-in user&lt;/span&gt;
&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Checkout — Guest User&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&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 show login prompt at checkout&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&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="c1"&gt;// test logic&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Checkout — Logged In User&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;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth/user.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nf"&gt;test&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 proceed to payment directly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&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="c1"&gt;// test logic&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;Clean, isolated, and no code duplication. This pattern alone will clean up suites that have grown messy over time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 7 🔐 — Save Authentication State and Reuse It
&lt;/h2&gt;

&lt;p&gt;Logging in before every test is one of the biggest performance killers in UI automation. Playwright makes it trivially easy to authenticate once and reuse that session across your entire suite.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// auth.setup.js — runs once before all tests&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;test&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;setup&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@playwright/test&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;setup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authenticate&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_EMAIL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Password&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Sign In&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForURL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/dashboard&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;context&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth/user.json&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;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// playwright.config.js&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;projects&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setup&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;testMatch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/auth.setup.js/&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tests&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;use&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;storageState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;auth/user.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;dependencies&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;setup&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="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;Log in once. Run everything. Your suite just got significantly faster.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 8 🧪 — Use &lt;code&gt;expect&lt;/code&gt; Soft Assertions for Multi-Step Validations
&lt;/h2&gt;

&lt;p&gt;Standard assertions stop the test the moment they fail. Sometimes you want to check multiple things on a page and see all failures at once rather than fixing them one by one. That is what soft assertions are for.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product page should display all details&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&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;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/products/1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Soft assertions — test continues even if one fails&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product-title&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product-price&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toContainText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;$&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;product-image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeVisible&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;soft&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByTestId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;add-to-cart&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;toBeEnabled&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="c1"&gt;// Hard assertion at the end — fails if any soft assertion failed&lt;/span&gt;
  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveLength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;One test run, all failures surfaced. Much faster feedback loop.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 9 📊 — Tag Your Tests and Run Subsets Smartly
&lt;/h2&gt;

&lt;p&gt;As your suite grows, running everything on every commit becomes too slow. Playwright's tagging system lets you run exactly the tests you need, when you need them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;critical checkout flow @smoke @critical&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&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="c1"&gt;// test logic&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;promo code validation @regression&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;page&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="c1"&gt;// test logic&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run only smoke tests on every commit&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; @smoke

&lt;span class="c"&gt;# Run full regression suite nightly&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--grep&lt;/span&gt; @regression
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Combine this with your CI/CD pipeline and you have a smart test execution strategy — fast feedback on commits, full coverage overnight.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tip 10 🛠️ — Use &lt;code&gt;codegen&lt;/code&gt; to Bootstrap Tests, Not to Write Them
&lt;/h2&gt;

&lt;p&gt;Playwright's code generator is a fantastic tool that most engineers either ignore completely or over-rely on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx playwright codegen https://yourapp.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It records your browser interactions and generates test code in real time. The mistake is treating that output as finished tests. It is not. It is a starting point.&lt;/p&gt;

&lt;p&gt;Use it to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Quickly capture the right locators for a new page&lt;/li&gt;
&lt;li&gt;Understand how Playwright sees your UI&lt;/li&gt;
&lt;li&gt;Bootstrap repetitive test scaffolding&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then go back and refactor — replace fragile selectors with &lt;code&gt;getByRole&lt;/code&gt;, extract repeated logic into page objects, add proper assertions. The codegen is your first draft, not your final test.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;Playwright is genuinely one of the best automation tools available right now. But like any tool, the difference between a messy suite and a clean one is not the tool itself — it is how deeply you understand it.&lt;/p&gt;

&lt;p&gt;These 10 tips took me months of real project work to internalize. Start applying even two or three of them today and you will notice the difference in how your tests feel to write, maintain, and debug.&lt;/p&gt;

&lt;p&gt;The best Playwright suite is one your whole team trusts. Build towards that.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by **Abdulfaizal Shaikh&lt;/em&gt;* — Senior Automation Engineer with 7+ years of experience building production automation frameworks across UI, API, and mobile platforms.*&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testautomation</category>
      <category>javascript</category>
      <category>qa</category>
    </item>
    <item>
      <title>WDIO vs Playwright — An Honest Comparison from Someone Who Has Used Both</title>
      <dc:creator>Faizal</dc:creator>
      <pubDate>Sat, 06 Jun 2026 10:18:59 +0000</pubDate>
      <link>https://dev.to/sshhfaiz/wdio-vs-playwright-an-honest-comparison-from-someone-who-has-used-both-3op0</link>
      <guid>https://dev.to/sshhfaiz/wdio-vs-playwright-an-honest-comparison-from-someone-who-has-used-both-3op0</guid>
      <description>&lt;p&gt;I have a confession.&lt;/p&gt;

&lt;p&gt;For a long time I was a WDIO loyalist. I had built entire frameworks on it, trained teammates on it, and defended it in every "what tool should we use?" conversation. Then a project pushed me to use Playwright seriously — and I had to completely reassess my opinions.&lt;/p&gt;

&lt;p&gt;This is not a "Playwright wins, delete WDIO" article. Both tools are genuinely excellent. But they are excellent in different ways, for different teams, in different situations.&lt;/p&gt;

&lt;p&gt;After using both in real production environments, here is my honest take.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Quick Introduction for Context
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WebdriverIO (WDIO)&lt;/strong&gt; has been around since 2012. It is built on the WebDriver protocol, supports an enormous plugin ecosystem, and is battle-tested across thousands of enterprise projects. It feels like a mature, opinionated framework that has seen everything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Playwright&lt;/strong&gt; was released by Microsoft in 2020. It uses its own browser automation protocol, ships with everything built-in, and was clearly designed by people who were frustrated with the limitations of older tools. It feels like a fresh start.&lt;/p&gt;

&lt;p&gt;Both are JavaScript-first. Both support TypeScript. Both are actively maintained. That is where the easy comparisons end.&lt;/p&gt;




&lt;h2&gt;
  
  
  Round 1 🚀 — Getting Started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  WDIO
&lt;/h3&gt;

&lt;p&gt;Setting up WDIO is guided but verbose. The CLI wizard walks you through configuration choices — framework, reporter, services — and generates a config file that can easily reach 100+ lines before you write a single test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// wdio.conf.js — just the basics&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;runner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;local&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;specs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./test/specs/**/*.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
    &lt;span class="na"&gt;browserName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;}],&lt;/span&gt;
  &lt;span class="na"&gt;framework&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mocha&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;reporters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spec&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;mochaOpts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60000&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;There is power in that config — but for a new team, it is a lot of decisions upfront.&lt;/p&gt;

&lt;h3&gt;
  
  
  Playwright
&lt;/h3&gt;

&lt;p&gt;Playwright's setup is remarkably fast.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm init playwright@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One command. It installs browsers, generates a config, and creates a sample test. You are running your first test in under 5 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Playwright&lt;/strong&gt; — lower barrier to entry, faster onboarding for new teams.&lt;/p&gt;




&lt;h2&gt;
  
  
  Round 2 ⚡ — Speed &amp;amp; Execution
&lt;/h2&gt;

&lt;p&gt;This is where Playwright has a significant architectural advantage.&lt;/p&gt;

&lt;p&gt;Playwright runs tests in parallel by default — across files AND within files if you configure it. It uses its own browser communication protocol which is fundamentally faster than WebDriver.&lt;/p&gt;

&lt;p&gt;WDIO supports parallelism too, but it requires more configuration and the WebDriver protocol adds overhead per command. On large suites the difference becomes very noticeable.&lt;/p&gt;

&lt;p&gt;In my experience running the same 300-test suite:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;WDIO:&lt;/strong&gt; ~18 minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Playwright:&lt;/strong&gt; ~7 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Same tests, same machine, same assertions. The speed difference is real and it matters when you are running tests on every pull request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Playwright&lt;/strong&gt; — faster out of the box, especially at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  Round 3 🔧 — Ecosystem &amp;amp; Flexibility
&lt;/h2&gt;

&lt;p&gt;This is where WDIO fights back hard.&lt;/p&gt;

&lt;p&gt;WDIO's plugin ecosystem is enormous. Need to test a mobile app? Add Appium service. Need visual regression? Add a plugin. Need to connect to Sauce Labs, BrowserStack, or LambdaTest? There is a service for everything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WDIO — adding Appium for mobile in the same framework&lt;/span&gt;
&lt;span class="nx"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appium&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="nx"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
  &lt;span class="na"&gt;platformName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Android&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appium:deviceName&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Pixel 4&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;appium:app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./app/myapp.apk&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;WDIO is genuinely the better choice when your team needs to test web, mobile, and API from a single unified framework. That flexibility is hard to match.&lt;/p&gt;

&lt;p&gt;Playwright supports mobile viewport emulation but does not support real device or native app testing natively. For pure web automation it is complete. For cross-platform teams it has gaps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: WDIO&lt;/strong&gt; — unmatched flexibility across platforms and integrations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Round 4 🐛 — Debugging Experience
&lt;/h2&gt;

&lt;p&gt;Playwright's debugging tools are genuinely a joy to use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run in headed mode with inspector&lt;/span&gt;
npx playwright &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nt"&gt;--debug&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Playwright Inspector lets you step through tests, inspect selectors, and see exactly what the browser is doing at each step. The trace viewer shows a full visual timeline of every action, screenshot, and network request — all in a beautiful UI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Playwright — built in tracing&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;screenshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;snapshots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// ... run your test&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;trace.zip&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;WDIO has decent debugging support but it relies more heavily on external tools and browser DevTools. The experience is functional but it does not match the polish of Playwright's built-in tooling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Playwright&lt;/strong&gt; — trace viewer alone makes debugging a completely different experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  Round 5 📱 — Mobile &amp;amp; Cross-Platform Testing
&lt;/h2&gt;

&lt;p&gt;No contest here.&lt;/p&gt;

&lt;p&gt;WDIO with Appium is the industry standard for mobile automation. If your team tests iOS and Android alongside web, WDIO is the clear choice. The same framework, the same test patterns, the same reporting — just different capabilities.&lt;/p&gt;

&lt;p&gt;Playwright announced experimental Android support but it is nowhere near production-ready for most teams. For mobile, WDIO is the mature, stable, battle-tested choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: WDIO&lt;/strong&gt; — mobile testing is its home turf.&lt;/p&gt;




&lt;h2&gt;
  
  
  Round 6 🧩 — Writing Tests Day to Day
&lt;/h2&gt;

&lt;p&gt;Both tools write tests that feel natural. But there are meaningful differences in how they handle common scenarios.&lt;/p&gt;

&lt;h3&gt;
  
  
  Waiting for elements
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// WDIO&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;waitForDisplayed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Playwright — auto-waiting built in, explicit when needed&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit-btn&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;state&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright's auto-waiting is one of its most underrated features. It automatically waits for elements to be ready before acting on them. In WDIO you are more often managing waits manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Network interception
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Playwright — clean and built-in&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/api/users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;route&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;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fulfill&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;users&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;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// WDIO — requires additional setup and mock service&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For API mocking and network interception, Playwright's built-in support is significantly cleaner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Winner: Playwright&lt;/strong&gt; — day-to-day ergonomics are smoother, especially for web-focused teams.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Honest Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;WDIO&lt;/th&gt;
&lt;th&gt;Playwright&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup speed&lt;/td&gt;
&lt;td&gt;Moderate&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Execution speed&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mobile testing&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debugging tools&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ecosystem&lt;/td&gt;
&lt;td&gt;Enormous&lt;/td&gt;
&lt;td&gt;Growing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-waiting&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cross-platform&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Web only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Steeper&lt;/td&gt;
&lt;td&gt;Gentler&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  So Which Should You Choose?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Choose Playwright if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your team is web-only&lt;/li&gt;
&lt;li&gt;You want fast onboarding and minimal config&lt;/li&gt;
&lt;li&gt;Test execution speed is a priority&lt;/li&gt;
&lt;li&gt;You value built-in debugging and tracing tools&lt;/li&gt;
&lt;li&gt;You are starting a new project from scratch&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Choose WDIO if:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need web and mobile testing in one framework&lt;/li&gt;
&lt;li&gt;Your project requires specific integrations (Appium, Sauce Labs, custom services)&lt;/li&gt;
&lt;li&gt;You are working in an enterprise environment with existing WDIO infrastructure&lt;/li&gt;
&lt;li&gt;Your team values configuration flexibility over convention&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  My Personal Take
&lt;/h2&gt;

&lt;p&gt;If I am starting a new web automation project today, I reach for Playwright. The speed, the debugging tools, and the developer experience make it the right default for most web teams in 2026.&lt;/p&gt;

&lt;p&gt;But if a client comes to me needing mobile and web automation unified under one framework, I am recommending WDIO without hesitation. It has earned that trust over a decade of production use.&lt;/p&gt;

&lt;p&gt;The best tool is always the one that fits your team's actual needs — not the one winning Twitter debates this month.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by **Abdulfaizal Shaikh&lt;/em&gt;* — Senior Automation Engineer with 6+ years of hands-on experience building frameworks in both WebdriverIO and Playwright across fintech and product companies.*&lt;/p&gt;

</description>
      <category>testautomation</category>
      <category>playwright</category>
      <category>javascript</category>
      <category>qa</category>
    </item>
    <item>
      <title>How AI Is Changing QA — And What It Means for Automation Engineers</title>
      <dc:creator>Faizal</dc:creator>
      <pubDate>Sat, 06 Jun 2026 07:29:25 +0000</pubDate>
      <link>https://dev.to/sshhfaiz/how-ai-is-changing-qa-and-what-it-means-for-automation-engineers-3aik</link>
      <guid>https://dev.to/sshhfaiz/how-ai-is-changing-qa-and-what-it-means-for-automation-engineers-3aik</guid>
      <description>&lt;p&gt;Let me be honest with you.&lt;/p&gt;

&lt;p&gt;When AI coding tools started getting good, I had a quiet moment of panic. I had spent years mastering test frameworks, building automation pipelines, writing page object models from scratch. And suddenly, a chatbot could generate a working Playwright test in 10 seconds.&lt;/p&gt;

&lt;p&gt;So I did what most engineers do — I ignored the discomfort and kept working. But over the next few months, something shifted. Not in a scary way. In a way that actually made my job more interesting.&lt;/p&gt;

&lt;p&gt;Here is what I have seen, lived, and learned about how AI is genuinely changing QA.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old World of QA Automation
&lt;/h2&gt;

&lt;p&gt;Not long ago, a typical automation engineer's week looked like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Writing boilerplate selectors and page objects for hours&lt;/li&gt;
&lt;li&gt;Manually analyzing test failure logs one by one&lt;/li&gt;
&lt;li&gt;Spending half a sprint on test data setup&lt;/li&gt;
&lt;li&gt;Writing the same API assertion patterns over and over&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It was valuable work. But a lot of it was mechanical. The kind of work where experience helped you go faster, but the ceiling was always your own typing speed and mental bandwidth.&lt;/p&gt;

&lt;p&gt;That world is changing fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  What AI Is Actually Doing to QA Right Now
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. 🤖 Generating Test Boilerplate Instantly
&lt;/h3&gt;

&lt;p&gt;The most immediate change is speed. Tools like GitHub Copilot, Cursor, and even ChatGPT can generate a full page object, a set of API tests, or a data-driven test suite in seconds.&lt;/p&gt;

&lt;p&gt;Does it always get it right? No. Does it save 40% of the time on setup tasks? Absolutely yes.&lt;/p&gt;

&lt;p&gt;The engineers who resist this are like developers who refused to use Stack Overflow in 2012. The tool is not the threat — falling behind on how to use it is.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. 🔍 Smarter Test Failure Analysis
&lt;/h3&gt;

&lt;p&gt;This one is underrated. AI-powered test observability tools can now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cluster similar failures automatically&lt;/li&gt;
&lt;li&gt;Identify root causes across hundreds of test runs&lt;/li&gt;
&lt;li&gt;Distinguish flaky failures from real regressions without human review&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What used to take a senior engineer 2 hours of log diving now takes minutes. This is not replacing QA thinking — it is removing the most tedious part of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. 🧠 Exploratory Testing Augmentation
&lt;/h3&gt;

&lt;p&gt;AI is starting to assist with exploratory testing in ways nobody expected. Tools can now crawl your app, learn user flows, and suggest edge cases that human testers miss.&lt;/p&gt;

&lt;p&gt;I recently saw a tool flag a race condition in a checkout flow that had existed for two years undetected. No human had thought to test that specific timing scenario. The AI found it in a crawl session.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. 📝 Test Case Generation From Requirements
&lt;/h3&gt;

&lt;p&gt;Feed a user story into a modern AI tool and it will output a set of test cases — happy path, negative path, edge cases — in seconds. Not perfect. But a genuinely useful first draft that a QA engineer can review and refine in minutes instead of hours.&lt;/p&gt;

&lt;p&gt;This is shifting the QA role from &lt;em&gt;writer&lt;/em&gt; to &lt;em&gt;reviewer and strategist&lt;/em&gt;. That is a promotion, not a demotion.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. 🔧 Self-Healing Locators
&lt;/h3&gt;

&lt;p&gt;One of the biggest time sinks in UI automation has always been broken selectors. A developer renames a class, moves a button, and suddenly 50 tests are failing for the wrong reason.&lt;/p&gt;

&lt;p&gt;AI-powered frameworks now offer self-healing locators that detect when a selector breaks and automatically find the closest matching element. Tools like Healenium and built-in features in modern platforms are making flaky UI tests a smaller problem than they used to be.&lt;/p&gt;




&lt;h2&gt;
  
  
  What AI Cannot Do (Yet)
&lt;/h2&gt;

&lt;p&gt;Let us be clear-eyed about the limits.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI cannot understand business context.&lt;/strong&gt; It does not know that your checkout flow is used by 2 million users daily and a 200ms regression matters more than a broken tooltip. You do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI cannot own quality.&lt;/strong&gt; It can flag, suggest, generate, and analyze. But the decision of what is good enough to ship — that judgment still belongs to a human who understands the product, the users, and the risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI cannot build a quality culture.&lt;/strong&gt; Getting developers to care about testing, running effective bug triage meetings, pushing back on impossible deadlines — none of that is automatable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI hallucinates.&lt;/strong&gt; Generated tests can look correct and be completely wrong. An AI will confidently write an assertion that tests nothing meaningful. You still need the expertise to catch that.&lt;/p&gt;




&lt;h2&gt;
  
  
  What This Means for Your Career as a QA Engineer
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable truth: the QA engineers who will struggle are the ones whose entire value is writing test scripts manually. Because that specific skill is getting commoditized.&lt;/p&gt;

&lt;p&gt;But the QA engineers who will thrive are those who use AI to go 10x faster on the mechanical work — and spend the freed-up time on the things that actually require a human:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test strategy&lt;/strong&gt; — deciding what to test and why&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk analysis&lt;/strong&gt; — understanding which failures actually matter&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality advocacy&lt;/strong&gt; — making the whole team care about quality&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Framework architecture&lt;/strong&gt; — designing systems that scale&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exploratory thinking&lt;/strong&gt; — finding the bugs nobody thought to look for&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AI is compressing the distance between a junior and mid-level engineer on scripting tasks. But it is widening the gap between engineers who think about quality deeply and those who do not.&lt;/p&gt;




&lt;h2&gt;
  
  
  How I Am Using AI in My QA Work Today
&lt;/h2&gt;

&lt;p&gt;To make this concrete, here is how AI has changed my actual workflow:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I use Copilot to generate the first draft of any new page object or API test class. I review, refactor, and own the final result.&lt;/li&gt;
&lt;li&gt;I use AI chat tools to analyze stack traces and suggest root causes when I am debugging a complex failure chain.&lt;/li&gt;
&lt;li&gt;I use AI to generate edge case test ideas from acceptance criteria before I start writing any automation.&lt;/li&gt;
&lt;li&gt;I still write critical test logic by hand. The parts that touch business-critical flows, I want to fully understand and own.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern is consistent: AI handles the repetitive, I handle the strategic.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mindset Shift That Matters Most
&lt;/h2&gt;

&lt;p&gt;Stop asking &lt;em&gt;"will AI replace QA engineers?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Start asking &lt;em&gt;"what kind of QA engineer will be irreplaceable when AI handles the repetitive parts?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The answer is clear. Engineers who think critically about quality, who communicate risk to stakeholders, who design systems rather than just scripts, who understand the product as deeply as any developer — those engineers are not going anywhere.&lt;/p&gt;

&lt;p&gt;If anything, AI is creating more room for QA engineers to do the work that actually matters. The work that requires judgment, creativity, and accountability.&lt;/p&gt;

&lt;p&gt;The engineers who embrace that shift will not just survive the AI wave — they will ride it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Written by **Abdulfaizal Shaikh&lt;/em&gt;* — Senior Automation Engineer with 7+ years of experience in UI, API, and mobile test automation. Currently exploring how AI tooling integrates with modern QA practices.*&lt;/p&gt;

</description>
      <category>testautomation</category>
      <category>qa</category>
      <category>ai</category>
      <category>career</category>
    </item>
    <item>
      <title>Common Mistakes Senior QA Engineers Make (And How to Fix Them)</title>
      <dc:creator>Faizal</dc:creator>
      <pubDate>Sat, 06 Jun 2026 07:04:17 +0000</pubDate>
      <link>https://dev.to/sshhfaiz/common-mistakes-senior-qa-engineers-make-and-how-to-fix-them-4bm6</link>
      <guid>https://dev.to/sshhfaiz/common-mistakes-senior-qa-engineers-make-and-how-to-fix-them-4bm6</guid>
      <description>&lt;p&gt;After &lt;strong&gt;6+ years in QA automation&lt;/strong&gt; — working across fintech, product startups, and enterprise software — I've seen the same patterns play out again and again. Not in junior engineers. In &lt;strong&gt;senior ones&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;These are the mistakes that slow teams down, kill confidence in test suites, and make automation feel like a burden instead of a superpower. I've made most of them myself. Here's what I wish someone had told me earlier.&lt;/p&gt;




&lt;h2&gt;
  
  
  Mistake 1 🏗️ — Writing Tests Without a Strategy
&lt;/h2&gt;

&lt;p&gt;Most engineers jump straight into writing test scripts without thinking about &lt;em&gt;what&lt;/em&gt; to automate, &lt;em&gt;why&lt;/em&gt;, and &lt;em&gt;at which layer&lt;/em&gt;. The result? Thousands of flaky E2E tests covering things already validated by unit tests — and zero coverage on business-critical paths that actually matter.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Classic symptom:&lt;/strong&gt; Your test suite takes 45 minutes to run and the team has stopped trusting it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Apply the &lt;strong&gt;Test Pyramid&lt;/strong&gt; — more unit tests at the base, fewer E2E tests at the top. Map every test to a user story or business risk. If you can't answer &lt;em&gt;"what breaks if this test fails?"&lt;/em&gt;, don't automate it yet.&lt;/p&gt;


&lt;h2&gt;
  
  
  Mistake 2 😴 — Ignoring Test Maintenance Until It's Too Late
&lt;/h2&gt;

&lt;p&gt;Automation is not "write once, forget forever." I've seen teams where 30% of tests are permanently skipped because &lt;em&gt;"we'll fix them later."&lt;/em&gt; Later never comes. The suite rots, trust collapses, and someone suggests scrapping everything and starting over.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Fix:&lt;/strong&gt;&lt;br&gt;
Treat tests like production code. Add a &lt;strong&gt;flaky test threshold policy&lt;/strong&gt; — if a test fails 3 times in a row with no code change, it gets quarantined and triaged immediately. Schedule a monthly "test debt" cleanup session.&lt;/p&gt;


&lt;h2&gt;
  
  
  Mistake 3 🔗 — Hardcoding Data, URLs &amp;amp; Credentials
&lt;/h2&gt;

&lt;p&gt;You'd be surprised how often I see test files with hardcoded environment URLs, test user credentials, or even database connection strings baked directly into the code. This makes tests impossible to run across environments and is a serious security risk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Bad — hardcoded everywhere&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://staging.myapp.com&lt;/span&gt;&lt;span class="dl"&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test@1234&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Good — from environment config&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ The Fix
&lt;/h3&gt;

&lt;p&gt;Use &lt;code&gt;.env&lt;/code&gt; files and a config layer. Never commit credentials to git. Use secret managers (GitHub Secrets, AWS Secrets Manager) in your CI/CD pipelines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Bad — hardcoded everywhere&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://staging.myapp.com&lt;/span&gt;&lt;span class="dl"&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Test@1234&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Good — pulled from environment&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;BASE_URL&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;password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;TEST_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mistake 4 ⏰ — Using Thread.sleep() Instead of Smart Waits
&lt;/h2&gt;

&lt;p&gt;Static sleeps are the silent killer of automation suites. They make tests slow on fast machines and flaky on slow ones. Even experienced engineers fall into this trap when debugging locally and forgetting to clean up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;🚨 Real impact: A suite with 200 tests each having a 2-second sleep adds 6+ minutes of pure wasted time per run.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  ✅ The Fix
&lt;/h3&gt;

&lt;p&gt;Always use explicit waits — wait for a specific element state, network idle, or API response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//Bad approach:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;//Good approach in Playwright:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit-btn&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;state&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="c1"&gt;//Good approach in WDIO:&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit-btn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;waitForDisplayed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Mistake 5 📊 — Not Measuring Test Coverage or Quality
&lt;/h2&gt;

&lt;p&gt;Having 500 automated tests means nothing if they all test the same login flow 500 different ways. Senior engineers often assume "more tests = better coverage." Without metrics, you're flying blind.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ The Fix
&lt;/h3&gt;

&lt;p&gt;Track these as KPIs every sprint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Code coverage % — use Istanbul for JS, JaCoCo for Java&lt;/li&gt;
&lt;li&gt;Feature coverage — maintain a traceability matrix mapping tests to requirements&lt;/li&gt;
&lt;li&gt;Flakiness rate — aim to keep it below 2%&lt;/li&gt;
&lt;li&gt;Average test run time — flag any single test taking over 30 seconds&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Mistake 6 🤐 — Treating QA as a Gatekeeper, Not a Partner
&lt;/h2&gt;

&lt;p&gt;The biggest non-technical mistake on this list. Senior QA engineers who position themselves as the "final blocker before release" create friction, resentment, and an us-vs-them culture. Quality is everyone's responsibility, not a single department's job.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ The Fix
&lt;/h3&gt;

&lt;p&gt;Shift to a quality coaching mindset. Get involved early — in sprint planning, design discussions, and code reviews. Help developers write testable code from the beginning. Quality built-in beats quality bolted-on every single time.&lt;/p&gt;

</description>
      <category>testautomation</category>
      <category>qa</category>
      <category>career</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
