<?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: Shell QA</title>
    <description>The latest articles on DEV Community by Shell QA (@shell_qa).</description>
    <link>https://dev.to/shell_qa</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4079421%2Fd58c59f2-4f10-4f9e-b125-7dc79f2166db.png</url>
      <title>DEV Community: Shell QA</title>
      <link>https://dev.to/shell_qa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shell_qa"/>
    <language>en</language>
    <item>
      <title>How to Handle O365 Shared Mailboxes in CI/CD Test Automation</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 19:14:06 +0000</pubDate>
      <link>https://dev.to/shell_qa/how-to-handle-o365-shared-mailboxes-in-cicd-test-automation-pfa</link>
      <guid>https://dev.to/shell_qa/how-to-handle-o365-shared-mailboxes-in-cicd-test-automation-pfa</guid>
      <description>&lt;p&gt;Testing email workflows—like OTP verification, password resets, or automated notifications—is a core requirement for robust end-to-end (E2E) automation. &lt;/p&gt;

&lt;p&gt;However, automating Office 365 (O365) Shared Mailboxes via UI logins or legacy IMAP basic authentication is a nightmare. UI logins trigger MFA, IMAP basic auth is deprecated across Microsoft 365, and browser automation for Outlook web is notoriously flaky.&lt;/p&gt;

&lt;p&gt;The enterprise-grade solution? Use &lt;strong&gt;Microsoft Graph API with Client Credentials (App-Only) Flow&lt;/strong&gt; directly inside your automation framework.&lt;/p&gt;

&lt;p&gt;Here is a step-by-step guide on how to set this up for your test automation pipelines.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step 1: Azure AD (Entra ID) Configuration
&lt;/h3&gt;

&lt;p&gt;To access a shared mailbox programmatically without interactive user logins or MFA:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Register an Application in the &lt;strong&gt;Azure Portal (Entra ID)&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Under &lt;strong&gt;API Permissions&lt;/strong&gt;, add Microsoft Graph permissions:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Mail.ReadWrite (Application Permission)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Mail.Send (Application Permission)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Grant &lt;strong&gt;Admin Consent&lt;/strong&gt; for the permissions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate a &lt;strong&gt;Client Secret&lt;/strong&gt; (or upload a Certificate) under &lt;strong&gt;Certificates &amp;amp; secrets&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save your &lt;strong&gt;Tenant ID, Client ID, and Client Secret&lt;/strong&gt; securely in your environment variables.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Security Best Practice:&lt;/strong&gt; Restrict the app registration's access so it can only target the specific shared mailbox rather than all tenant mailboxes using an ApplicationAccessPolicy in Exchange Online PowerShell.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  Step 2: Fetching OAuth 2.0 Tokens Programmatically
&lt;/h3&gt;

&lt;p&gt;Before querying the mailbox, request a bearer token using the Azure AD token endpoint:&lt;/p&gt;

&lt;p&gt;POST &lt;a href="https://login.microsoftonline.com/%7BTENANT_ID%7D/oauth2/v2.0/token" rel="noopener noreferrer"&gt;https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Request Body (application/x-www-form-urlencoded):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;client_id: {CLIENT_ID}&lt;/li&gt;
&lt;li&gt;scope: &lt;a href="https://graph.microsoft.com/.default" rel="noopener noreferrer"&gt;https://graph.microsoft.com/.default&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;client_secret: {CLIENT_SECRET}&lt;/li&gt;
&lt;li&gt;grant_type: client_credentials&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Step 3: Querying the Shared Mailbox
&lt;/h3&gt;

&lt;p&gt;Once you have the Bearer Token, make direct REST API requests targeting the shared mailbox email address:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. Fetch Latest Unread Email (e.g., for OTP or Link Extraction)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET [https://graph.microsoft.com/v1.0/users/shared-mailbox@yourdomain.com/messages?$filter=isRead](https://graph.microsoft.com/v1.0/users/shared-mailbox@yourdomain.com/messages?$filter=isRead) eq false&amp;amp;$top=1&amp;amp;$select=subject,body,receivedDateTime
Authorization: Bearer {YOUR_ACCESS_TOKEN}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Extract Links or Passcodes
&lt;/h4&gt;

&lt;p&gt;Parse the returned JSON payload using Regex or HTML parsers to extract verification links, tokens, or body text directly in code—bypassing UI interaction completely.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Send Email via Shared Mailbox
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST [https://graph.microsoft.com/v1.0/users/shared-mailbox@yourdomain.com/sendMail](https://graph.microsoft.com/v1.0/users/shared-mailbox@yourdomain.com/sendMail)
Authorization: Bearer {YOUR_ACCESS_TOKEN}
Content-Type: application/json

{
  "message": {
    "subject": "Automated Test Notification",
    "body": {
      "contentType": "Text",
      "content": "Test run completed successfully."
    },
    "toRecipients": [
      {
        "emailAddress": {
          "address": "recipient@yourdomain.com"
        }
      }
    ]
  }
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Strategy Wins for CI/CD Pipelines
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fast &amp;amp; Reliable:&lt;/strong&gt; Direct HTTP API execution takes milliseconds compared to seconds of UI navigation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;No MFA Blockers:&lt;/strong&gt; App Credentials bypass multi-factor authentication seamlessly in headless Jenkins/GitHub Actions runs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flake-Free Execution:&lt;/strong&gt; Zero UI flakiness from changing Outlook web interfaces or loading delays.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;How are you currently handling email validations in your automation test suites? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;




</description>
      <category>testing</category>
      <category>automation</category>
      <category>playwright</category>
      <category>devops</category>
    </item>
    <item>
      <title>A Beginner's Guide to Performance Testing with Apache JMeter</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 18:52:42 +0000</pubDate>
      <link>https://dev.to/shell_qa/a-beginners-guide-to-performance-testing-with-apache-jmeter-3on9</link>
      <guid>https://dev.to/shell_qa/a-beginners-guide-to-performance-testing-with-apache-jmeter-3on9</guid>
      <description>&lt;p&gt;Performance testing is essential for ensuring your applications can handle expected user loads without bottlenecks or failures. Apache JMeter remains one of the most popular open-source tools for load, stress, and performance testing.&lt;/p&gt;

&lt;p&gt;Here is a quick guide to getting your JMeter environment set up and executing your first load test.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prerequisites
&lt;/h3&gt;

&lt;p&gt;JMeter requires Java to execute. Ensure you have JDK 11 or higher installed on your system.&lt;/p&gt;

&lt;p&gt;Verify your Java installation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;java &lt;span class="nt"&gt;-version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Download and Installation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download the latest binary zip/tgz file from the Official Apache JMeter Site.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Extract the archive into your preferred local directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Launch JMeter from the bin directory:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Windows: Double-click jmeter.bat&lt;/li&gt;
&lt;li&gt;macOS/Linux: Open terminal and run ./jmeter.sh&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Install the Plugins Manager
&lt;/h3&gt;

&lt;p&gt;The Plugins Manager simplifies adding listeners, graph generators, and custom samplers.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Download jmeter-plugins-manager.jar from JMeter Plugins.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Move the file into your JMeter lib/ext directory.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Restart JMeter. Access the Plugins Manager under Options &amp;gt; Plugins Manager.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Building Your First Test Plan
&lt;/h3&gt;

&lt;p&gt;Set up a basic HTTP test using the GUI interface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Thread Group:&lt;/strong&gt; Right-click Test Plan &amp;gt; Add &amp;gt; Threads (Users) &amp;gt; Thread Group. Configure your target virtual users, ramp-up time, and loop count.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP Request Defaults:&lt;/strong&gt; Right-click Thread Group &amp;gt; Add &amp;gt; Config Element &amp;gt; HTTP Request Defaults. Set your target server domain/IP and port.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP Sampler:&lt;/strong&gt; Right-click Thread Group &amp;gt; Add &amp;gt; Sampler &amp;gt; HTTP Request. Define the API path and request method.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Listeners:&lt;/strong&gt; Right-click Thread Group &amp;gt; Add &amp;gt; Listener &amp;gt; View Results Tree or Summary Report (use these GUI listeners primarily for test script validation).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Running Tests in Non-GUI Mode
&lt;/h3&gt;

&lt;p&gt;Never run actual heavy load tests through the JMeter GUI as it consumes significant local system resources. Use CLI mode for accuracy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jmeter &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="nt"&gt;-t&lt;/span&gt; /path/to/testplan.jmx &lt;span class="nt"&gt;-l&lt;/span&gt; /path/to/results.jtl &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /path/to/html-report-folder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;-n: Non-GUI execution&lt;/li&gt;
&lt;li&gt;-t: Path to your .jmx test script&lt;/li&gt;
&lt;li&gt;-l: File path to output raw results (.jtl)&lt;/li&gt;
&lt;li&gt;-e -o: Automatically generates a full interactive HTML report dashboard after execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Happy load testing!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>performance</category>
      <category>testing</category>
      <category>jmeter</category>
      <category>devops</category>
    </item>
    <item>
      <title>Setting Up Playwright &amp; Cucumber UI Tests in Azure DevOps with LambdaTest</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 18:47:10 +0000</pubDate>
      <link>https://dev.to/shell_qa/setting-up-playwright-cucumber-ui-tests-in-azure-devops-with-lambdatest-2cld</link>
      <guid>https://dev.to/shell_qa/setting-up-playwright-cucumber-ui-tests-in-azure-devops-with-lambdatest-2cld</guid>
      <description>&lt;p&gt;Here is a step-by-step guide to configuring your Playwright/Cucumber test suite to run on LambdaTest Cloud via Azure DevOps pipelines, returning test results directly to Azure.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A GitHub repository containing your Playwright, Cucumber, and JavaScript automation code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An active Azure DevOps account with a project created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A LambdaTest account (you will need your username and access key).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Connect GitHub to Azure DevOps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;In Azure DevOps, navigate to Pipelines &amp;gt; New Pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select GitHub as the source and authenticate your account.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose your repository and target branch (e.g., main).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Create LambdaTest Credentials Variable Group
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Go to Pipelines &amp;gt; Library in Azure DevOps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click + Variable group and name it LambdaTest-Credentials.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following key-value pairs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LAMBDATEST_USERNAME = your_lambdatest_username&lt;/li&gt;
&lt;li&gt;LAMBDATEST_ACCESS_KEY = your_lambdatest_access_key (toggle "Keep this value secret")&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Save the group.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Add/Update Your azure-pipelines.yml
&lt;/h3&gt;

&lt;p&gt;Place this configuration file in your repository root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;pool&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;vmImage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;windows-latest'&lt;/span&gt;

&lt;span class="na"&gt;variables&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LambdaTest-Credentials&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;BASE_URL&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://your-app-url.com'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LT_BROWSER&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;chrome'&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ENABLE_LAMBDATEST&lt;/span&gt;
  &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;

&lt;span class="na"&gt;stages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;stage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;
  &lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;UITestsLambdaTest&lt;/span&gt;
    &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tests&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(LambdaTest&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Cloud)'&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodeTool@0&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;versionSpec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;20.x'&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Node.js&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;20.x'&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm ci&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Install&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Dependencies'&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;script&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;npm run test:ui:smoke&lt;/span&gt;
      &lt;span class="na"&gt;displayName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Run&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;UI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Smoke&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tests&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;on&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;LambdaTest'&lt;/span&gt;
      &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;ENABLE_LAMBDATEST&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;true'&lt;/span&gt;
        &lt;span class="na"&gt;LT_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(LAMBDATEST_USERNAME)&lt;/span&gt;
        &lt;span class="na"&gt;LT_ACCESS_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(LAMBDATEST_ACCESS_KEY)&lt;/span&gt;
        &lt;span class="na"&gt;LT_BROWSER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(LT_BROWSER)&lt;/span&gt;
        &lt;span class="na"&gt;BASE_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$(BASE_URL)&lt;/span&gt;

    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;task&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PublishTestResults@2&lt;/span&gt;
      &lt;span class="na"&gt;condition&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
      &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;testResultsFormat&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;JUnit'&lt;/span&gt;
        &lt;span class="na"&gt;testResultsFiles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reports/junit-report.xml'&lt;/span&gt;
        &lt;span class="na"&gt;testRunTitle&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;UI&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Tests&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;LambdaTest&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Cloud'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Update Your Test Code
&lt;/h3&gt;

&lt;p&gt;Ensure your test runner reads LT_USERNAME, LT_ACCESS_KEY, and ENABLE_LAMBDATEST from environment variables. Use these parameters to connect your runner to LambdaTest's WebSocket endpoint for remote browser execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Execution and Verification
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Commit and push your changes to GitHub.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The push triggers your Azure DevOps pipeline automatically.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;View execution logs and published JUnit reports in Azure DevOps under the pipeline run, or check live and historical video logs on your LambdaTest dashboard&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>testing</category>
      <category>javascript</category>
      <category>playwright</category>
    </item>
    <item>
      <title>The Ultimate Code Review Checklist for Data Validation Frameworks</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 18:24:04 +0000</pubDate>
      <link>https://dev.to/shell_qa/the-ultimate-code-review-checklist-for-data-validation-frameworks-4ola</link>
      <guid>https://dev.to/shell_qa/the-ultimate-code-review-checklist-for-data-validation-frameworks-4ola</guid>
      <description>&lt;p&gt;A comprehensive, production-ready checklist for reviewing data validation, ETL testing, and automated reconciliation codebases.&lt;/p&gt;

&lt;p&gt;Code reviews for data engineering tools need more rigor than standard web apps. A subtle bug in a data validation framework can cause silent pipeline failures, false positive test passes, or accidental execution of unbounded SQL queries on production warehouses.&lt;/p&gt;

&lt;p&gt;Whether you are building a custom data framework or maintaining automated ETL tests, use this generalized checklist during code reviews to keep your test suites secure, performant, and reliable.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Test Case Configuration (YAML / JSON)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TC ID Matching:&lt;/strong&gt; Ensure the tc_id value matches the configuration filename exactly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schema Validity:&lt;/strong&gt; Verify that type (e.g., count, data, recon, file) and source/target drivers are valid and supported.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Enablers:&lt;/strong&gt; Confirm the enabled field is explicitly set (true or false) rather than omitted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Relative File Paths:&lt;/strong&gt; For file-based validation, ensure paths are relative to defined source/target data directories.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-Empty Queries:&lt;/strong&gt; Confirm SQL sources and targets include non-empty query strings or valid template paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique Case IDs:&lt;/strong&gt; Ensure test case identifiers are unique across the test suite directory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documented Rationale:&lt;/strong&gt; If a test case has enabled: false or uses numeric tolerance thresholds (validation_tolerance), ensure a comment explains the business reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency Order:&lt;/strong&gt; Verify that basic structural checks (COUNT) run prior to deep comparisons (DATA / RECON).&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. SQL &amp;amp; Query Logic
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Projections:&lt;/strong&gt; No SELECT *. All columns must be explicitly listed to avoid schema drift breaks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alignment:&lt;/strong&gt; Source and target queries must return compatible data types and matching column ordering.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment Isolation:&lt;/strong&gt; Check that query strings contain zero hardcoded hostnames, schema names, or environment paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secret Hygiene:&lt;/strong&gt; Ensure queries contain no hardcoded credentials or connection strings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warehouse Pushdown:&lt;/strong&gt; Confirm filtering and heavy aggregation occur at the database level (WHERE / GROUP BY) rather than pulling full tables into application memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Determinism:&lt;/strong&gt; Queries must return deterministic ordering (e.g., explicit ORDER BY on primary keys) so differential comparisons yield consistent results.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Validator Core Logic
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standardized Return Schema:&lt;/strong&gt; Validator routines must always return a consistent payload schema (e.g., status, summary, src_row_count, tgt_row_count, matched_rows).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status Consistency:&lt;/strong&gt; Status values should use normalized uppercase strings ("PASS" / "FAIL").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Null/NaN Handling:&lt;/strong&gt; Verify that NaN and NULL comparisons explicitly account for missing data parity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Guards:&lt;/strong&gt; Row capping (e.g., .head(1000)) or chunking should be enforced to prevent OOM errors on large datasets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe Numbers:&lt;/strong&gt; Ensure tolerance thresholds enforce non-negative values (e.g., max(0.0, float(tolerance))).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Preservation:&lt;/strong&gt; No silent exception swallowing — all except blocks must either re-raise or log through the logger framework.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Execution &amp;amp; Pipeline Runners
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type Dispatcher Safety:&lt;/strong&gt; Ensure the execution runner raises an explicit ValueError when encountering an unsupported validation type.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Coercion Guards:&lt;/strong&gt; Data loaders (e.g., CSV readers) should default string types (dtype=str) where appropriate to prevent silent type conversions (e.g., dropping leading zeroes in zip codes).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Isolation:&lt;/strong&gt; Ensure individual test execution is wrapped in try/except blocks so a single failing test case doesn't collapse the entire run.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output Capture:&lt;/strong&gt; Redirect standard output streams properly so module logs are correctly captured in final HTML or JSON reports.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  5. Security &amp;amp; Data Safety
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Hardcoded Credentials:&lt;/strong&gt; Check that API keys, passwords, or connection strings are strictly retrieved from environment variables or secret managers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Raw PII:&lt;/strong&gt; Confirm that local test fixtures (data/src, data/tgt) contain synthetic data instead of production PII or financial records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Injection Prevention:&lt;/strong&gt; SQL execution calls must use parameterized inputs instead of raw string formatting (f-strings).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Path Traversal Protection:&lt;/strong&gt; Relative file operations must be validated to prevent directory traversal breakouts.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Code Quality &amp;amp; Maintenance
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Logging:&lt;/strong&gt; Ensure all operational feedback uses structured logging frameworks instead of raw print() statements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type Annotations:&lt;/strong&gt; Function signatures must include type hints and clear docstrings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignore Management:&lt;/strong&gt; Verify that dynamic run artifacts (.pyc, &lt;strong&gt;pycache&lt;/strong&gt;, local HTML reports, local .log files) are properly tracked in .gitignore.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Sign-Off Checklist Template
&lt;/h3&gt;

&lt;p&gt;When reviewing a PR, drop this checklist template into your code review comment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;PR Code Review Sign-Off
&lt;span class="p"&gt;-&lt;/span&gt; Config &amp;amp; SQL Review Passed
&lt;span class="p"&gt;-&lt;/span&gt; Validator &amp;amp; Runner Logic Passed
&lt;span class="p"&gt;-&lt;/span&gt; Security &amp;amp; Secret Scanning Passed
&lt;span class="p"&gt;-&lt;/span&gt; Test Coverage Verified
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;How do you handle data testing in your pipelines? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>devops</category>
      <category>testing</category>
      <category>python</category>
    </item>
    <item>
      <title>Building an Enterprise Data Validation Framework: From Architecture to Version Control</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 18:06:41 +0000</pubDate>
      <link>https://dev.to/shell_qa/building-an-enterprise-data-validation-framework-from-architecture-to-version-control-3c57</link>
      <guid>https://dev.to/shell_qa/building-an-enterprise-data-validation-framework-from-architecture-to-version-control-3c57</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;When managing large-scale data migrations, ETL pipelines, or multi-database reconciliations, manual verification quickly falls short. Without structured automation, data discrepancies—ranging from missing rows to subtle rounding errors—easily slip into production.&lt;/p&gt;

&lt;p&gt;To tackle this, we built a &lt;strong&gt;layered, configuration-driven Data Validation Framework in Python&lt;/strong&gt;. This framework enables engineering and QA teams to define test cases using clean YAML files, run multi-level checks, and auto-generate executive HTML reports.&lt;/p&gt;

&lt;p&gt;Here is the complete blueprint and best practices guide covering all 10 core architectural modules.&lt;/p&gt;




&lt;h3&gt;
  
  
  1. Framework Architecture Overview
&lt;/h3&gt;

&lt;p&gt;The framework follows a &lt;strong&gt;layered, configuration-driven architecture&lt;/strong&gt;. Understanding the execution flow prevents common misconfigurations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;run_regression.py
├── config/execution.yaml (Group-level ON/OFF switches)
├── testcases/TC_XXX_TYPE.yaml (Individual test case definitions)
├── execution/validation_runner.py (Routes to correct validator)
│   ├── execution/df_loader.py (Loads DataFrames from source/target)
│   │   ├── execution/csv_loader.py (CSV path)
│   │   └── execution/sql_executor.py (SQL path)
│   └── validators/
│       ├── count_validator.py
│       ├── data_validator.py
│       ├── recon_validator.py
│       └── file_validator.py
├── utils/html_reporter.py (Generates summary HTML)
└── utils/logger.py (Rotating file logger -&amp;gt; logs/framework.log)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Design Principle:&lt;/strong&gt; Control operates at two levels—group-level (execution.yaml) and test-case-level (enabled flag in YAML). Both must be set to true for a test case to execute.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Test Case Authoring
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Naming Convention &amp;amp; Required Fields&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Follow a strict pattern: TC_{NNN}_{TYPE}.yaml (e.g., TC_001_COUNT.yaml).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;tc_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TC_001_COUNT&lt;/span&gt;  &lt;span class="c1"&gt;# Must match the filename exactly&lt;/span&gt;
&lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;        &lt;span class="c1"&gt;# Set false to skip without deleting&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;count&lt;/span&gt;          &lt;span class="c1"&gt;# count | data | recon | file&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;"&lt;/span&gt;
  &lt;span class="s"&gt;Validate row count between Source DB and Target Staging table&lt;/span&gt;

&lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sql&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sql/src/TC_001_COUNT.sql&lt;/span&gt;

&lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sql&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sql/tgt/TC_001_COUNT.sql&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ID Alignment:&lt;/strong&gt; Always ensure tc_id matches the filename.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Human-Readable Descriptions:&lt;/strong&gt; Populate clear descriptions that explain what business metric is being validated—this text directly feeds executive HTML summary reports.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Disabling vs. Deleting:&lt;/strong&gt; Use enabled: false to skip tests temporarily. Never delete YAML files, as keeping them preserves history and audit trails.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Validation Type Selection
&lt;/h3&gt;

&lt;p&gt;Choose the right strategy based on performance requirements:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;When to Use&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;COUNT&lt;/td&gt;
&lt;td&gt;Row Count Check&lt;/td&gt;
&lt;td&gt;Quick sanity check. Always run first in any validation sequence.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DATA&lt;/td&gt;
&lt;td&gt;Cell Comparison&lt;/td&gt;
&lt;td&gt;Full cell-by-cell row matching for live database queries returning identical schemas.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RECON&lt;/td&gt;
&lt;td&gt;Numeric Reconciliation&lt;/td&gt;
&lt;td&gt;Column sum reconciliation with custom tolerances (e.g., max $0.01 rounding threshold).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FILE&lt;/td&gt;
&lt;td&gt;Flat File Comparison&lt;/td&gt;
&lt;td&gt;In-memory comparison tailored specifically for CSV or flat file extracts.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Always Lead with COUNT:&lt;/strong&gt; A count failure signals pipeline or load failures immediately before you waste compute resources on heavy cell-level checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tolerance Rules:&lt;/strong&gt; Set tolerance explicitly in YAML. Never inflate tolerance to hide true data discrepancies without documented business justification.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. SQL Query Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;External .sql Files:&lt;/strong&gt; Store queries in sql/src/ and sql/tgt/ rather than inline YAML strings to maintain clean version control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Determinism &amp;amp; Schema Drift:&lt;/strong&gt; Include explicit ORDER BY clauses to ensure deterministic row ordering during DataFrame comparison. Avoid SELECT * in production tests; explicitly declare column names to prevent silent failures caused by schema drift.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Query Symmetry:&lt;/strong&gt; Source and target queries must return matching column names and data types to prevent misleading shape mismatches.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Data Source &amp;amp; Connector Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Modular Connectors:&lt;/strong&gt; Encapsulate database connections inside dedicated modules under connectors/ using connection pooling. Never hardcode credentials—read them from environment files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CSV File Hygiene:&lt;/strong&gt; Store flat files under data/src/ and data/tgt/. Never commit real production data to version control—use anonymized or masked datasets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explicit Column Mapping:&lt;/strong&gt; Use an explicit column_map block in YAML when column names differ between source and target rather than relying on position-based auto-alignment.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Execution Configuration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Group Switches (execution.yaml):&lt;/strong&gt; Enable or disable entire validation suites (e.g., toggle all RECON tests off during initial staging loads).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution Scope:&lt;/strong&gt; Tests run alphabetically by filename. Use clean numbering ranges or isolated directories for distinct projects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fail-Safe Processing:&lt;/strong&gt; Wrap execution calls in try/except blocks so a single broken query or test case does not crash the entire regression suite.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Reporting &amp;amp; Logging
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTML Summary Reports:&lt;/strong&gt; Automatically compiled into report/TC_000_SUMMARY.html after every execution cycle, embedding branding assets directly as base64 images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Capping:&lt;/strong&gt; To optimize report rendering, mismatched row displays are capped (e.g., top 1,000 mismatches), with a "Download Full CSV" option embedded for deep root-cause analysis.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Log Rotation:&lt;/strong&gt; Maintain a rotating file handler (e.g., 5 MB per log, 3 backups) at logs/framework.log set to INFO level to maintain complete audit trails.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. File &amp;amp; Data Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NaN / Null Logic:&lt;/strong&gt; Comparison modules enforce NaN-aware equality checks (NaN == NaN), treating matching empty/null positions across datasets as valid matches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Numeric Coercion:&lt;/strong&gt; Coerce string columns to numeric during RECON runs only when the operation is lossless, preventing silent data loss when reading flat files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sort Stability:&lt;/strong&gt; Apply stable sorting algorithms (kind='stable') across all DataFrame columns prior to comparison for deterministic results.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9. Environment Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Config Isolation:&lt;/strong&gt; Isolate connection parameters by environment (config/env/qa.yaml, config/env/uat.yaml, config/env/prod.yaml).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Security:&lt;/strong&gt; Inject sensitive credentials using environment variables or dedicated secret managers (Azure Key Vault, AWS Secrets Manager). Never commit raw passwords to repository control.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. Framework Extension &amp;amp; Maintenance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Adding New Validators:&lt;/strong&gt; Build custom modules under validators/ (e.g., schema_validator.py) adhering strictly to the framework's output contract (status, summary, src_to_tgt, tgt_to_src, matched_rows).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Connector Resiliency:&lt;/strong&gt; Implement exponential backoff and retry logic in connectors to handle transient network blips gracefully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Control Hygiene:&lt;/strong&gt; Maintain a robust .gitignore excluding generated reports (report/.html), execution logs (logs/.log), temporary flat files (data/*/.csv), and &lt;em&gt;pycache&lt;/em&gt;/.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;A configuration-driven data validation framework provides the predictability and speed needed for modern data engineering pipelines. By decoupling test configuration from execution logic, teams can scale coverage effortlessly while maintaining high reliability.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;How do you handle automated data validation in your ETL and pipeline workflows? Let's discuss in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>dataengineering</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Building a Secure Enterprise AI Assistant: A Complete Architecture &amp; Usage Guide</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 17:13:53 +0000</pubDate>
      <link>https://dev.to/shell_qa/building-a-secure-enterprise-ai-assistant-a-complete-architecture-usage-guide-1gd3</link>
      <guid>https://dev.to/shell_qa/building-a-secure-enterprise-ai-assistant-a-complete-architecture-usage-guide-1gd3</guid>
      <description>&lt;p&gt;Integrating generative AI into internal workflows requires a strict balance between user accessibility and enterprise data privacy. Below is a blueprint for designing, deploying, and governing an internal AI assistant (Secure GPT) using managed LLMs.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Architecture &amp;amp; System Overview
&lt;/h2&gt;

&lt;p&gt;An enterprise AI assistant acts as a secure bridge between internal teams and Large Language Models (LLMs).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model Orchestration:&lt;/strong&gt; Powered by managed endpoints (e.g., Azure OpenAI running models like GPT-4.1 Nano) to ensure consistent performance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Boundary:&lt;/strong&gt; Processing occurs entirely within isolated enterprise boundaries. Inputs are never retained, logged for third-party training, or exposed externally.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Context-Bound Execution:&lt;/strong&gt; The assistant operates without live web access, relying strictly on curated training data cutoff points and user-provided session context to eliminate unauthorized external data leakage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Multi-Modal Processing:&lt;/strong&gt; Supports native parsing of structured documents and image inputs for real-time extraction.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Integration via API
&lt;/h2&gt;

&lt;p&gt;For teams integrating the AI assistant into automated pipelines or internal tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;JSON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;payload&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;structure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"model"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gpt-4.1-nano"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"messages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"system"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are an internal assistant. Follow data privacy guidelines."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Summarize the key compliance points from the attached document."&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"temperature"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Authentication:&lt;/strong&gt; Access is managed through enterprise API gateways using scoped API keys.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validation:&lt;/strong&gt; Always validate outputs programmatically before passing generated responses to critical downstream business logic.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  3. Prompt Engineering Best Practices
&lt;/h2&gt;

&lt;p&gt;To help non-technical and technical users extract high-quality outputs, encourage these prompting patterns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Specify Constraints:&lt;/strong&gt; Replace broad requests with bounded requirements.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bad: "Tell me about our travel policies."&lt;/li&gt;
&lt;li&gt;Good: "Summarize the top 5 expense limits in our 2026 travel policy."&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provide Explicit Context:&lt;/strong&gt; Frame the prompt with domain background (e.g., "Under GDPR compliance standards, how should we structure this data retention notice?").&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Chain-of-Thought Decomposition:&lt;/strong&gt; Break multi-step logic into distinct tasks within the prompt (e.g., "Step 1: Extract the core features. Step 2: Compare them against the baseline.").&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Security &amp;amp; Governance Rules
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Data Minimization:&lt;/strong&gt; Avoid sending PII or sensitive system credentials unless explicitly isolated within secure pipeline boundaries.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document Parsing:&lt;/strong&gt; Encourage users to upload files directly into the context window rather than pasting raw text into chat inputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Human-in-the-Loop:&lt;/strong&gt; Implement mandatory review policies for high-stakes operational outputs generated by the LLM.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>architecture</category>
      <category>security</category>
      <category>devops</category>
    </item>
    <item>
      <title>Supercharging Test Automation with Custom AI Agents and Secure GPT</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 16:37:56 +0000</pubDate>
      <link>https://dev.to/shell_qa/supercharging-test-automation-with-custom-ai-agents-and-sgpt-30jb</link>
      <guid>https://dev.to/shell_qa/supercharging-test-automation-with-custom-ai-agents-and-sgpt-30jb</guid>
      <description>&lt;h3&gt;
  
  
  Introduction
&lt;/h3&gt;

&lt;p&gt;As software applications grow in complexity, traditional test design and automation engineering often become bottlenecks. Between incomplete test cases, inconsistent documentation, and missing context, teams waste substantial cycles simply preparing test assets.&lt;/p&gt;

&lt;p&gt;To tackle these challenges, we built an &lt;strong&gt;AI-driven test automation pipeline&lt;/strong&gt; combining &lt;strong&gt;Custom AI Agents&lt;/strong&gt; for script generation and &lt;strong&gt;Secure GPT&lt;/strong&gt; for high-speed test design—all while keeping a Human-in-the-Loop for validation.&lt;/p&gt;




&lt;h3&gt;
  
  
  Phase 1: AI Custom Agents for Script Generation
&lt;/h3&gt;

&lt;p&gt;Instead of using a single monolithic prompt, we broken down script generation into specialized, modular AI Agents that handle specific artifacts across the automation lifecycle.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Agent Pipeline Architecture
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context Agent:&lt;/strong&gt; Processes inputs like KT video recordings, application screenshots, and page sources to establish domain context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Case Agent:&lt;/strong&gt; Maps context to structured test cases.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature File Agent:&lt;/strong&gt; Converts test cases into Gherkin feature files for BDD workflows.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Page Object Agent:&lt;/strong&gt; Generates Page Object Model (POM) element locators and structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Step Definition Agent:&lt;/strong&gt; Generates the underlying code logic for execution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Telemetry Layer:&lt;/strong&gt; Tracks token usage and execution status across agents, outputting real-time data to an executive reporting dashboard.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Key Challenges Solved
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Insufficient context for AI adoption.&lt;/li&gt;
&lt;li&gt;Single-line or incomplete test cases in legacy repositories.&lt;/li&gt;
&lt;li&gt;Lack of test case prioritization and coverage gaps across application modules.&lt;/li&gt;
&lt;li&gt;Inconsistent documentation.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Phase 2: Test Design Acceleration with Secure GPT
&lt;/h3&gt;

&lt;p&gt;By leveraging a Secure GPT instance with a Human-in-the-Loop review process, we targeted initial test case generation for existing regression suites before expanding into active sprint stories.&lt;/p&gt;

&lt;h4&gt;
  
  
  Productivity Breakdown
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;AI Team Member (Secure GPT)&lt;/th&gt;
&lt;th&gt;Traditional Automation Engineer&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Average Productivity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~35 test cases / day&lt;/td&gt;
&lt;td&gt;~7.5 test cases / day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Productivity Gain&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;4.6x Higher&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Baseline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Time to Create 100 Test Cases&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~3 Days&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;~13–20 Days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In initial rollouts across enterprise applications, accuracy rates consistently ranged between 40–60% for fully automated initial drafts, allowing test leads to focus on refining edge cases rather than building test suites from scratch.&lt;/p&gt;




&lt;h3&gt;
  
  
  What's Next?
&lt;/h3&gt;

&lt;p&gt;Our next milestone expands this pipeline to &lt;em&gt;in-sprint user stories&lt;/em&gt;. By feeding detailed user stories, Business Requirement Documents (BRDs), application screenshots, and acceptance criteria directly into Secure GPT, the team can auto-generate new test scenarios as soon as a story enters the sprint.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;How is your team integrating generative AI into your testing workflows? Let’s discuss in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>Complete Guide to Building a Scalable Java-Selenium Automation Framework</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 16:24:55 +0000</pubDate>
      <link>https://dev.to/shell_qa/complete-guide-to-building-a-scalable-java-selenium-automation-framework-44fg</link>
      <guid>https://dev.to/shell_qa/complete-guide-to-building-a-scalable-java-selenium-automation-framework-44fg</guid>
      <description>&lt;p&gt;QA Automation - Best Practices Guide&lt;br&gt;
Framework: Hybrid Test Automation Framework &lt;br&gt;
Stack: Java - Selenium 4 - TestNG - Maven - ExtentReports&lt;br&gt;
Project: Enterprise QA Automation&lt;/p&gt;

&lt;p&gt;Table of Contents&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project Structure&lt;/li&gt;
&lt;li&gt;Naming Conventions&lt;/li&gt;
&lt;li&gt;Page Object Model (UI Pages)&lt;/li&gt;
&lt;li&gt;Business Components&lt;/li&gt;
&lt;li&gt;Test Scripts&lt;/li&gt;
&lt;li&gt;Data Management&lt;/li&gt;
&lt;li&gt;Waits &amp;amp; Synchronization&lt;/li&gt;
&lt;li&gt;Assertions &amp;amp; Reporting&lt;/li&gt;
&lt;li&gt;Parallel Execution&lt;/li&gt;
&lt;li&gt;Run Manager &amp;amp; Configuration&lt;/li&gt;
&lt;li&gt;ExtentReports&lt;/li&gt;
&lt;li&gt;CI/CD &amp;amp; Pipeline&lt;/li&gt;
&lt;li&gt;Error Handling&lt;/li&gt;
&lt;li&gt;Code Quality&lt;/li&gt;
&lt;li&gt;Environment Configuration

&lt;ol&gt;
&lt;li&gt;Project Structure
The framework follows a strict layered architecture. Never mix responsibilities across layers.
src/
└── test/
├── java/
│   ├── allocator/                  + Entry point &amp;amp; parallel execution engine
│   │   ├── Allocator.java
│   │   └── ParallelRunner.java
│   ├── uiPages/                    + Page Object locators ONLY (no logic)
│   ├── businessComponents/         + Reusable page actions &amp;amp; validations
│   ├── commonComponents/           + Grouped reusable component flows
│   └── testscripts/                + Test scripts (keywords/steps only)
└── resources/
├── Run Manager.xlsm            + Test execution control
└── GlobalSettings.properties&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;uiPages: Only By locators (no logic, no assertions).&lt;/li&gt;
&lt;li&gt;businessComponents: Only reusable actions; never call driver directly in test scripts.&lt;/li&gt;
&lt;li&gt;testscripts: Only orchestrate business component calls (keyword-driven).&lt;/li&gt;
&lt;li&gt;allocator: Do NOT modify unless changing threading/reporting strategy.

&lt;ol&gt;
&lt;li&gt;Naming Conventions
| Layer | Class Naming | Method Naming |
|---|---|---|
| uiPages | page_name_uipages.java | static final By FieldName |
| businessComponents | pagePageName.java | camelCase() verb-first |
| testscripts | ScriptName.java | execute() or step methods |
Examples:
// Good - uiPages locator
public static final By btn_Submit = By.xpath("//button[&lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;='btnSubmit']/span");&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Good - business component method&lt;br&gt;
public void fillFormDetails(String primaryField, String secondaryField) throws InterruptedException { }&lt;/p&gt;

&lt;p&gt;// Bad - logic inside uiPages&lt;br&gt;
public void clickSubmit() { driver.findElement(btn_Submit).click(); } // Don't do this in uiPages!&lt;/p&gt;

&lt;p&gt;Locator ID Preferences (Priority Order):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;By.id() — most stable&lt;/li&gt;
&lt;li&gt;By.name()&lt;/li&gt;
&lt;li&gt;By.cssSelector()&lt;/li&gt;
&lt;li&gt;By.xpath() — only when no better option exists; avoid absolute XPaths
Preferred:
By.id("btnSubmit")
By.cssSelector("button[data-id='submit']")&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Acceptable:&lt;br&gt;
By.xpath("//button[&lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;='btnSubmit']/span")&lt;/p&gt;

&lt;p&gt;Avoid - fragile absolute XPath:&lt;br&gt;
By.xpath("/html/body/div[2]/form/button[1]")&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Page Object Model (UI Pages)
Each UI page maps to one class in uiPages. Keep locators static and final.
// Good structure
package uiPages;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;import org.openqa.selenium.By;&lt;/p&gt;

&lt;p&gt;public class form_details_page {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Group locators with comments for readability
// === Header Section ===
public static final By txt_referenceNumber = By.xpath("//input[@id='referenceNumber']");
public static final By txt_submissionDate = By.id("submissionDate");

// === Dynamic Locators - use methods ===
public static By get_recordLink(String recordId) {
    return By.xpath("//a[text()='" + recordId + "']");
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One class per page/module.&lt;/li&gt;
&lt;li&gt;Use static final for all fixed locators.&lt;/li&gt;
&lt;li&gt;Use static methods for dynamic/parameterized locators.&lt;/li&gt;
&lt;li&gt;Group related locators with inline comments.&lt;/li&gt;
&lt;li&gt;Do not import Selenium WebDriver or WebElement in UI pages.

&lt;ol&gt;
&lt;li&gt;Business Components
Business components contain the actual Selenium interactions and extend GeneralComponents (which extends ReusableLibrary).
// Good business component method
public void fillFormDetails(String userName, String effectiveDate) {
sendKeys(form_details_page.txt_userName, userName, "User Name");
sendKeys(form_details_page.txt_effectiveDate, effectiveDate, "Effective Date");
report_updateTestLog("Fill Form Details", 
"User: " + userName + " | Date: " + effectiveDate, Status.PASS);
}&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Bad - raw driver calls inside a business component&lt;br&gt;
driver.findElement(By.xpath("//input[&lt;a class="mentioned-user" href="https://dev.to/id"&gt;@id&lt;/a&gt;='userName']")).sendKeys(userName);&lt;/p&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always use GeneralComponents helper methods (clickElement, sendKeys, selectDropDownByValue, etc.) — never call driver.findElement() directly.&lt;/li&gt;
&lt;li&gt;Every significant action should update the report via report_updateTestLog.&lt;/li&gt;
&lt;li&gt;Use wait (FluentWait and explicitWait) defined in GeneralComponents — never use Thread.sleep().&lt;/li&gt;
&lt;li&gt;Use PauseScript() only as a last resort; prefer explicit waits.&lt;/li&gt;
&lt;li&gt;Keep methods atomic — one action per method when possible.
FluentWait Usage:
// Correct - use inherited waits
wait.until(ExpectedConditions.visibilityOfElementLocated(signin_home_page.welcomeText));&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Test Scripts&lt;br&gt;
Test scripts are the entry point for each test case. They call business component methods in sequence.&lt;br&gt;
// Good test script structure&lt;br&gt;
public class TC_001_CreateNewRecord extends ScriptHelper {&lt;/p&gt;

&lt;p&gt;public void execute() {&lt;br&gt;
    // Step 1: Login&lt;br&gt;
    pdf_login_new = new pdf_login_helper();&lt;br&gt;
    login.login();&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Step 2: Navigate to New Record
pdf_home_new = new pdf_home_helper();
home.clickNewRecord();

// Step 3: Fill form details
pdf_formDetails = new pdf_formDetails_helper();
formDetails.fillFormDetails(
    dataTable.getData("TestData", "UserName"),
    dataTable.getData("TestData", "EffectiveDate")
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;}&lt;br&gt;
}&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test scripts must not contain XPaths, locators, or driver calls.&lt;/li&gt;
&lt;li&gt;Each step should be a business component call.&lt;/li&gt;
&lt;li&gt;Use dataTable.getData() for test data — never hardcode values.&lt;/li&gt;
&lt;li&gt;Keep test scripts short and readable (under 100 lines where possible).&lt;/li&gt;
&lt;li&gt;One test script = one test scenario.

&lt;ol&gt;
&lt;li&gt;Data Management
Excel Run Manager:&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Control test execution in Run Manager.xlsm — set Execute column to Yes/No.&lt;/li&gt;
&lt;li&gt;Use TestConfig_optionsID to reference browser/environment config from the TestConfig_options sheet.&lt;/li&gt;
&lt;li&gt;Set IterationMode to RUN_ALL_ITERATIONS, RUN_ONE_ITERATION_ONLY, or RUN_RANGE_OF_ITERATIONS.
Test Data:
// Read Data from Excel data sheet
String email = dataTable.getData("TestData", "Email");
String country = dataTable.getCommonData("country", "DataValue");&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Never hardcode test data&lt;br&gt;
String email = "&lt;a href="mailto:user@example.com"&gt;user@example.com&lt;/a&gt;"; // Don't do this&lt;/p&gt;

&lt;p&gt;Data Best Practices:&lt;br&gt;
| Rule | Reason |&lt;br&gt;
|---|---|&lt;br&gt;
| Use CommonData sheet for shared values (email, URL, etc.) | Single source of truth |&lt;br&gt;
| Use scenario-specific sheets for test-specific data | Separation of concerns |&lt;br&gt;
| Do not store passwords in plain text in Excel | Security |&lt;br&gt;
| Use JavaFaker for generating random test data where applicable | Data independence |&lt;br&gt;
// Using JavaFaker for random data&lt;br&gt;
Faker faker = new Faker();&lt;br&gt;
String productName = faker.company().name();&lt;br&gt;
String productRef = faker.number().digits(8);&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Waits &amp;amp; Synchronization
Wait Hierarchy (use in this order):
| Wait Type | When to Use | Config Key |
|---|---|---|
| wait (FluentWait) | Standard UI elements | wait in properties |
| longwait (FluentWait) | Slow-loading pages/modals | longwait in properties |
| ExpectedConditions.visibilityOf | Checking visibility before interaction | — |
| ExpectedConditions.elementToBeClickable | Before clicking dynamic elements | — |
| PauseScript() | Last resort only — unavoidable timing gaps | — |
Best practice wait pattern:
try {
wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
clickElement(locator, "Button Name");
} catch (TimeoutException e) {
report_updateTestLog("Step", "Element not visible after timeout", Status.FAIL);
}&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// For elements that may or may not appear&lt;br&gt;
try {&lt;br&gt;
    wait.until(ExpectedConditions.visibilityOfElementLocated(optionalElement));&lt;br&gt;
    clickElement(optionalElement, "Optional Button");&lt;br&gt;
} catch (TimeoutException | NoSuchElementException e) {&lt;br&gt;
    report_updateTestLog("Step", "Optional element not present - skipping", Status.PASS);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Never use&lt;br&gt;
Thread.sleep(5000);&lt;/p&gt;

&lt;p&gt;Configuring Wait Timeouts:&lt;br&gt;
Set in GlobalSettings.properties:&lt;br&gt;
wait=30&lt;br&gt;
longwait=60&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assertions &amp;amp; Reporting
Status Levels:
| Status | When to Use |
|---|---|
| Status.PASS | Assertion passed / action successful |
| Status.FAIL | Critical assertion failed — stops test |
| Status.WARNING | Non-critical mismatch (e.g., dynamic external values) |
| Status.DONE | Informational step (no assertion) |
Good assertion pattern:
if (driver.findElement(welcomeText).isDisplayed()) {
report_updateTestLog("Login Validation", "Homepage loaded successfully", Status.PASS);
} else {
report_updateTestLog("Login Validation", "Homepage did NOT load", Status.FAIL);
}&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// Use framework helper for value comparison&lt;br&gt;
validateActualExpectedValue(actualValue, expectedValue, "Field Name");&lt;/p&gt;

&lt;p&gt;// Use WARNING for values dependent on external systems (APIs, DBs)&lt;br&gt;
validateActualExpectedValueForUnpredictableStringValue(actualValue, expectedValue, "Calculated Field");&lt;/p&gt;

&lt;p&gt;Log Message Best Practices:&lt;br&gt;
// Good - descriptive, includes actual value&lt;br&gt;
report_updateTestLog("Field Validation", &lt;br&gt;
    "Actual: " + actualValue + " | Expected: " + expectedValue, Status.PASS);&lt;/p&gt;

&lt;p&gt;// Bad - too vague&lt;br&gt;
report_updateTestLog("Check", "OK", Status.PASS);&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parallel Execution
The framework supports three threading models via Allocator.java:
| Method | Description | When to Use |
|---|---|---|
| executeTestBatch() | Fixed ThreadPoolExecutor | Production stable runs |
| executeTestBatch_Virtual() | Fixed ThreadPool (active) | Default - best for most scenarios |
| executeTestBatch_AutoThreadPool() | WorkStealingPool | Experimental - perf testing |
Thread Safety Rules:

&lt;ul&gt;
&lt;li&gt;Never use static mutable state in business components or test scripts.&lt;/li&gt;
&lt;li&gt;referenceNumber in GeneralComponents is static — use with caution in parallel runs; prefer passing values as parameters.&lt;/li&gt;
&lt;li&gt;Each ParallelRunner instance is independent — ensure no shared file/resource access without synchronization.
// Thread-safe - instance variable
private String referenceNumber;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// Risk in parallel - static shared variable&lt;br&gt;
public static String referenceNumber = null; // Avoid in parallel scenarios&lt;/p&gt;

&lt;p&gt;Configuring Threads:&lt;br&gt;
GlobalSettings.properties:&lt;br&gt;
NumberOfThreads=3&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recommended: Set threads based on available machine CPUs (e.g., threads = CPUs * 2).&lt;/li&gt;
&lt;li&gt;Caution: Too many threads on a single machine causes driver conflicts and flaky tests.

&lt;ol&gt;
&lt;li&gt;Run Manager &amp;amp; Configuration
Run Manager Sheet Columns:
| Column | Value | Notes |
|---|---|---|
| Execute | Yes / No | Controls if test runs |
| TestScenario | Package/folder name | Maps to test script folder |
| TestCase | Class name | Exact Java class name |
| IterationMode | RUN_ALL_ITERATIONS | Controls data iteration |
| StepsToExecute/EndIteration | Number | For ranged iterations |
| TestConfigurationID | Config name | Maps to TestConfigurations sheet |
TestConfigurations Sheet:
| Column | Example Values |
|---|---|
| ExecutionMode | LOCAL, REMOTE, GRID |
| Browser | EDGE, CHROME, FIREFOX |
| Platform | WINDOWS, LINUX |
| ExecutionTimeout | 120, leave blank for auto |
GlobalSettings.properties Key Properties:
# Execution
RunConfiguration=Regression          # Sheet name in Run Manager
NumberOfThreads=2
DefaultBrowser=EDGE
DefaultExecutionMode=LOCAL
DefaultPlatform=WINDOWS
Environment=SIT                       # SIT or UAT&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  URLs
&lt;/h1&gt;

&lt;p&gt;uialuat_SIT=&lt;a href="https://sit.app.example.com" rel="noopener noreferrer"&gt;https://sit.app.example.com&lt;/a&gt;&lt;br&gt;
uialuat_UAT=&lt;a href="https://uat.app.example.com" rel="noopener noreferrer"&gt;https://uat.app.example.com&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Waits (seconds)
&lt;/h1&gt;

&lt;p&gt;wait=30&lt;br&gt;
longwait=60&lt;/p&gt;

&lt;h1&gt;
  
  
  Reporting
&lt;/h1&gt;

&lt;p&gt;ProjectName=AutomationProject&lt;br&gt;
GenerateHTMLReport=false&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ExtentReports&lt;br&gt;
Reports are auto-generated in target/Reports/Extent Result/ExtentReport.html.&lt;br&gt;
Best Practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always call extentReportFlush() at the end — already handled in Allocator.driveBatchExecution().&lt;/li&gt;
&lt;li&gt;Ensure report_updateTestLog() is used — it auto-attaches screenshots on failure.&lt;/li&gt;
&lt;li&gt;Never create a new ExtentReports instance inside test scripts.
Viewing Reports:
target/
└── Reports/
├── Extent Result/
│   └── ExtentReport.html  • Open in browser
└── HTML Reports/
└── index.html&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;CI/CD &amp;amp; Pipeline&lt;br&gt;
Maven Profiles:&lt;/p&gt;
&lt;h1&gt;
  
  
  Run via Allocator (Hybrid Framework) - DEFAULT
&lt;/h1&gt;

&lt;p&gt;mvn clean test -PRunAllocator&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h1&gt;
  
  
  Run via TestNG suite
&lt;/h1&gt;

&lt;p&gt;mvn clean test -PRunTestNGTests&lt;/p&gt;

&lt;h1&gt;
  
  
  Pass run configuration at runtime
&lt;/h1&gt;

&lt;p&gt;mvn clean test -PRunAllocator -DRunConfiguration=SIT_Smoke&lt;/p&gt;

&lt;h1&gt;
  
  
  Pass environment at runtime
&lt;/h1&gt;

&lt;p&gt;mvn clean test -PRunAllocator -DRunConfiguration=SIT_Smoke -DEnvironment=SIT&lt;/p&gt;

&lt;p&gt;Azure Pipeline (azure-pipelines.yml):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure NumberOfThreads is set to match pipeline agent specs.&lt;/li&gt;
&lt;li&gt;Use pipeline variables for environment switching instead of hardcoding.&lt;/li&gt;
&lt;li&gt;Archive target/Reports/ as a pipeline artifact for post-run analysis.
# Good pipeline variable usage

&lt;ul&gt;
&lt;li&gt;task: Maven@3
inputs:
goals: 'clean test'
options: '-PRunAllocator -DRunConfiguration=$(RunConfig) -DEnvironment=$(Env)'&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Error Handling
Exception Handling Pattern:
// Catch specific exceptions and report
try {
wait.until(ExpectedConditions.visibilityOfElementLocated(locator));
clickElement(locator, "Submit Button");
} catch (TimeoutException e) {
report_updateTestLog("Submit", "Element not visible - TimeoutException", Status.FAIL);
} catch (NoSuchElementException e) {
report_updateTestLog("Submit", "Element not found in DOM", Status.FAIL);
}&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// For optional elements (may or may not appear)&lt;br&gt;
try {&lt;br&gt;
    clickElement(optionalPopup, "Optional Popup OK");&lt;br&gt;
} catch (NoSuchElementException | TimeoutException e) {&lt;br&gt;
    // Silently skip - optional element&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Never swallow exceptions silently without a log&lt;br&gt;
try {&lt;br&gt;
    // ...&lt;br&gt;
} catch (Exception e) {&lt;br&gt;
    // Empty catch - bad practice&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Stop Execution on Critical Failure:&lt;br&gt;
The ParallelRunner checks frameworkParameters.getStopExecution() — use this mechanism when a prerequisite test fails and subsequent tests cannot proceed.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Code Quality
General Rules:
| Rule | Detail |
|---|---|
| DRY | Extract repeated actions into GeneralComponents or CommonMethods. |
| Single Responsibility | Each method does one thing. |
| Descriptive Names | fillFormDetails() not step1(). |
| No Magic Numbers | Use named constants or data table values. |
| Comment Why, Not What | Code is self-explanatory; comments explain business logic. |
| Remove Dead Code | Don't leave commented-out blocks; use version control instead. |
Preferred Patterns:
// Extract repeated logic into CommonMethods
public String getProcessedReferenceNumber(String refNumber) {
return refNumber.substring(0, refNumber.length() - 1) + "B";
}&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;// Use constants instead of magic strings&lt;br&gt;
private static final String PASS_STATUS = "PASS";&lt;br&gt;
private static final String ENVIRONMENT_SIT = "SIT";&lt;/p&gt;

&lt;p&gt;// Null/empty guard&lt;br&gt;
if (testData != null &amp;amp;&amp;amp; !testData.isEmpty()) {&lt;br&gt;
    getTestControlObject().getReport().reportEvent("TestControlData", testData, Status.PASS);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Code Review Checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No hardcoded URLs, credentials, or test data.&lt;/li&gt;
&lt;li&gt;All driver.findElement() calls go through GeneralComponents helpers.&lt;/li&gt;
&lt;li&gt;All XPaths are relative (not absolute).&lt;/li&gt;
&lt;li&gt;All exceptions are caught and logged to report.&lt;/li&gt;
&lt;li&gt;Report steps have meaningful descriptions.&lt;/li&gt;
&lt;li&gt;No Thread.sleep() in code.&lt;/li&gt;
&lt;li&gt;Run Manager updated for new test cases.&lt;/li&gt;
&lt;li&gt;UI page locators added to correct uiPages class.

&lt;ol&gt;
&lt;li&gt;Environment Configuration
Switching Environments:
Environment is controlled via GlobalSettings.properties or pipeline parameter:
Environment=SIT or UAT
Business components should always read URLs from properties:
// Good - environment driven
if (properties.getProperty("Environment").equals("SIT")) {
driver.get(properties.getProperty("uialuat_SIT"));
} else if (properties.getProperty("Environment").equals("UAT")) {
driver.get(properties.getProperty("uialuat_UAT"));
}&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Bad - hardcoded URL&lt;br&gt;
driver.get("&lt;a href="https://sit.app.example.com%22" rel="noopener noreferrer"&gt;https://sit.app.example.com"&lt;/a&gt;);&lt;/p&gt;

&lt;p&gt;Supported Environments:&lt;br&gt;
| Key | Description |&lt;br&gt;
|---|---|&lt;br&gt;
| SIT | System Integration Testing |&lt;br&gt;
| UAT | User Acceptance Testing |&lt;br&gt;
Quick Reference - Do's and Don'ts&lt;br&gt;
| DO | DON'T |&lt;br&gt;
|---|---|&lt;br&gt;
| Use GeneralComponents helper methods | Call driver.findElement() directly in test scripts |&lt;br&gt;
| Use FluentWait with ExpectedConditions | Use Thread.sleep() |&lt;br&gt;
| Read all test data from Excel/properties | Hardcode values in test scripts |&lt;br&gt;
| Report every step with report_updateTestLog() | Leave silent catch blocks |&lt;br&gt;
| Keep locators in uiPages only | Put locators in business components |&lt;br&gt;
| Use relative XPaths | Use absolute XPaths |&lt;br&gt;
| Use Status.WARNING for dynamic external values | Fail tests for known external dependencies |&lt;br&gt;
| Flush ExtentReport at end of run | Create multiple ExtentReport instances |&lt;br&gt;
| Set Execute=No to skip tests | Delete rows from Run Manager |&lt;br&gt;
| Use parameterized locator methods for dynamic elements | Concatenate XPath strings inline |&lt;br&gt;
Support&lt;br&gt;
Automation Team: QE Architect&lt;br&gt;
&lt;a href="mailto:automation.team@example.com"&gt;automation.team@example.com&lt;/a&gt;&lt;br&gt;
Generated for Hybrid Test Automation Framework — Enterprise QA Automation Project&lt;/p&gt;

</description>
      <category>java</category>
      <category>selenium</category>
      <category>testing</category>
      <category>automation</category>
    </item>
    <item>
      <title>Playwright JavaScript Framework Best Practices</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sun, 16 Aug 2026 16:13:21 +0000</pubDate>
      <link>https://dev.to/shell_qa/playwright-javascript-framework-best-practices-1b38</link>
      <guid>https://dev.to/shell_qa/playwright-javascript-framework-best-practices-1b38</guid>
      <description>&lt;p&gt;Playwright JavaScript Framework — Best Practices&lt;br&gt;
A comprehensive guide for writing reliable, maintainable, and scalable end-to-end tests using Playwright with JavaScript and Cucumber BDD.&lt;br&gt;
Table of Contents&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Project Structure &amp;amp; Organization&lt;/li&gt;
&lt;li&gt;Page Object Model (POM)&lt;/li&gt;
&lt;li&gt;Selectors &amp;amp; Locators&lt;/li&gt;
&lt;li&gt;Assertions&lt;/li&gt;
&lt;li&gt;Waiting Strategies&lt;/li&gt;
&lt;li&gt;Test Isolation &amp;amp; State Management&lt;/li&gt;
&lt;li&gt;Authentication &amp;amp; Login&lt;/li&gt;
&lt;li&gt;Test Data Management&lt;/li&gt;
&lt;li&gt;BDD / Cucumber Integration&lt;/li&gt;
&lt;li&gt;Error Handling &amp;amp; Debugging&lt;/li&gt;
&lt;li&gt;Retries &amp;amp; Flakiness&lt;/li&gt;
&lt;li&gt;Parallelism &amp;amp; Performance&lt;/li&gt;
&lt;li&gt;Configuration Management&lt;/li&gt;
&lt;li&gt;Reporting &amp;amp; Observability&lt;/li&gt;
&lt;li&gt;CI/CD Integration&lt;/li&gt;
&lt;li&gt;Security &amp;amp; Secrets&lt;/li&gt;
&lt;li&gt;Code Quality &amp;amp; Maintainability&lt;/li&gt;
&lt;li&gt;Accessibility &amp;amp; Cross-Browser Testing

&lt;ol&gt;
&lt;li&gt;Project Structure &amp;amp; Organization
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Keep a flat, predictable folder structure that mirrors the application's domain (e.g., admin/, user/, billing/, reports/).&lt;/li&gt;
&lt;li&gt;Co-locate feature files, step definitions, and page objects by module/domain so related code is easy to find.&lt;/li&gt;
&lt;li&gt;Use index.js barrel exports to avoid long relative import paths.&lt;/li&gt;
&lt;li&gt;Store all environment-specific configuration in a single config.js at the root; never hardcode URLs or credentials inside test files.
DON'T&lt;/li&gt;
&lt;li&gt;Don't scatter page objects and step definitions randomly across the project.&lt;/li&gt;
&lt;li&gt;Don't mix UI concerns with business logic in the same file.
Recommended Layout
features/
modules/
Admin_Group_Management.feature
User_Task_Management.feature&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;step-definitions/&lt;br&gt;
  modules/&lt;br&gt;
    AdminSteps.js&lt;br&gt;
    UserSteps.js&lt;/p&gt;

&lt;p&gt;page-objects/&lt;br&gt;
  modules/&lt;br&gt;
    basepage/&lt;br&gt;
    AdminPage.js&lt;br&gt;
    UserPage.js&lt;/p&gt;

&lt;p&gt;utils/&lt;br&gt;
  logger.js&lt;br&gt;
  ExcelHelper.js&lt;/p&gt;

&lt;p&gt;setup/&lt;br&gt;
  hooks.js&lt;br&gt;
  assertions.js&lt;br&gt;
  config.js&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Page Object Model (POM)&lt;br&gt;
DO&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Encapsulate all page interactions (clicks, fills, navigations) inside dedicated Page Object classes.&lt;/li&gt;
&lt;li&gt;Keep page objects thin — they should only expose methods, not assertions.&lt;/li&gt;
&lt;li&gt;Compose complex pages from smaller component objects (e.g., TableComponent, ModalComponent).&lt;/li&gt;
&lt;li&gt;Accept the Playwright page instance via the constructor; never create a new browser context inside a POM.
// Good — page-objects/modules/AdminPage.js
export class AdminPage {
constructor(page) {
this.page = page;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Pre-define locators for reuse&lt;br&gt;
this.groupNameInput = page.locator('[data-test="group-name"]');&lt;br&gt;
this.saveButton = page.locator('[data-test="save-button"]');&lt;br&gt;
this.successBanner = page.locator('[data-test="success-banner"]');&lt;br&gt;
}&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;async navigateToGroupManagement() {&lt;br&gt;
    await this.page.click('[data-test="group-management"]');&lt;br&gt;
  }&lt;/p&gt;

&lt;p&gt;async createGroup(groupData) {&lt;br&gt;
    await this.groupNameInput.fill(groupData.name);&lt;br&gt;
    await this.saveButton.click();&lt;br&gt;
  }&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't put expect() assertions inside page objects — keep them in step definitions or test files.&lt;/li&gt;
&lt;li&gt;Don't duplicate selectors across multiple files; define them once in the page object.

&lt;ol&gt;
&lt;li&gt;Selectors &amp;amp; Locators
Priority Order (most preferred → least preferred)
| Priority | Strategy | Example |
|---|---|---|
| 1 | data-test / custom test data attributes | [data-test="save-button"] |
| 2 | ARIA roles &amp;amp; labels | page.getByRole('button', { name: 'Save' }) |
| 3 | Playwright built-in locators | page.getByLabel('Username') |
| 4 | CSS class (stable, non-generated) | .modal-title |
| 5 | XPath | //div[&lt;a class="mentioned-user" href="https://dev.to/class"&gt;@class&lt;/a&gt;="header"] |
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Use data-test or data-qa attributes — they are immune to styling and structural changes.&lt;/li&gt;
&lt;li&gt;Use Playwright's semantic locators (getByRole, getByLabel, getByText, getByPlaceholder) for readability and resilience.&lt;/li&gt;
&lt;li&gt;Define locators as class properties in page objects to avoid string duplication.&lt;/li&gt;
&lt;li&gt;Use chaining to scope locators: page.locator('.modal').locator('[data-test="confirm"]').
// Semantic locator
await page.getByRole('button', { name: 'Submit' }).click();&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// custom data-test attribute&lt;br&gt;
await page.locator('[data-test="group-name"]').fill('Automation Group');&lt;/p&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't use auto-generated class names (div.sc-abc123) or positional XPaths (/div[3]/span[1]).&lt;/li&gt;
&lt;li&gt;Don't use page.$() (legacy Playwright API) — always use page.locator().

&lt;ol&gt;
&lt;li&gt;Assertions
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Always use Playwright's built-in expect — it has automatic retry, built-in timeouts, and clear error messages.&lt;/li&gt;
&lt;li&gt;Prefer web-first assertions that wait for the UI state to match:
// Web-first assertions (auto-retry)
await expect(page.locator('[data-test="success-banner"]')).toBeVisible();
await expect(page.locator('[data-test="user-count"]')).toHaveText('5');
await expect(page.locator('[data-test="save-button"]')).toBeEnabled();&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Use soft assertions when you want to collect multiple failures in one test run:&lt;br&gt;
const softExpect = expect.configure({ soft: true });&lt;br&gt;
await softExpect(heading).toHaveText('Dashboard');&lt;br&gt;
await softExpect(logo).toBeVisible();&lt;br&gt;
// All soft assertion failures are reported at the end&lt;/p&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't use page.isVisible() in if statements as a substitute for assertions.&lt;/li&gt;
&lt;li&gt;Don't hard-code waitForTimeout before an assertion — let expect do the waiting.

&lt;ol&gt;
&lt;li&gt;Waiting Strategies
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Rely on Playwright's auto-waiting — most click, fill, and expect operations auto-wait for elements to be actionable.&lt;/li&gt;
&lt;li&gt;Use waitForSelector or waitForResponse only for specific async operations not covered by auto-waiting.&lt;/li&gt;
&lt;li&gt;Wait for network responses when actions trigger API calls:
// Wait for API response after action
const [response] = await Promise.all([
page.waitForResponse(resp =&amp;gt; resp.url().includes('/api/groups') &amp;amp;&amp;amp; resp.status() === 200),
page.locator('[data-test="save-button"]').click()
]);&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Use page.waitForLoadState('networkidle') only for pages with complex background requests.&lt;/p&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Never use arbitrary page.waitForTimeout(3000) — this is a top cause of slow, flaky tests.&lt;/li&gt;
&lt;li&gt;Don't poll visibility in a loop; use expect(...).toBeVisible({ timeout: 10000 }) instead.

&lt;ol&gt;
&lt;li&gt;Test Isolation &amp;amp; State Management
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Each Cucumber scenario must be fully independent — it should not rely on state left by a previous scenario.&lt;/li&gt;
&lt;li&gt;Use Before / After hooks in setup/hooks.js to:

&lt;ul&gt;
&lt;li&gt;Create a fresh browser context per scenario.&lt;/li&gt;
&lt;li&gt;Navigate to a known starting page.&lt;/li&gt;
&lt;li&gt;Clean up created test data after each scenario.
// setup/hooks.js
Before(async function () {
this.context = await browser.newContext();
this.page = await this.context.newPage();
});&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After(async function (scenario) {&lt;br&gt;
  if (scenario.result.status === 'FAILED') {&lt;br&gt;
    await this.page.screenshot({ path: reports/screenshots/${scenario.pickle.name}.png });&lt;br&gt;
  }&lt;br&gt;
  await this.context.close();&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;// Use tagged hooks to apply setup only to relevant scenarios:&lt;br&gt;
Before({ tags: '@admin' }, async function () {&lt;br&gt;
  await loginAsAdmin(this.page);&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't share page instances or logged-in sessions across unrelated scenarios.&lt;/li&gt;
&lt;li&gt;Don't depend on execution order — scenarios should be runnable in any order.

&lt;ol&gt;
&lt;li&gt;Authentication &amp;amp; Login
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Reuse authenticated state using Playwright's storageState to avoid repeating login for every scenario:
// Save auth state once
await page.context().storageState({ path: 'setup/auth-state.json' });&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Reuse in playwright.config.js&lt;br&gt;
use: {&lt;br&gt;
  storageState: 'setup/auth-state.json'&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Store credentials only in environment variables — never in code or feature files.&lt;br&gt;
// Use a dedicated loginAsRole utility function to support multiple user roles cleanly.&lt;/p&gt;

&lt;p&gt;// utils/auth.js&lt;br&gt;
export async function loginAs(page, role) {&lt;br&gt;
  const creds = {&lt;br&gt;
    admin: { user: process.env.ADMIN_USER, pass: process.env.ADMIN_PASS },&lt;br&gt;
    user: { user: process.env.STANDARD_USER, pass: process.env.STANDARD_PASS },&lt;br&gt;
    approver: { user: process.env.APPROVER_USER, pass: process.env.APPROVER_PASS }&lt;br&gt;
  };&lt;/p&gt;

&lt;p&gt;await page.goto(process.env.APP_BASE_URL);&lt;br&gt;
  await page.fill('[data-test="username"]', creds[role].user);&lt;br&gt;
  await page.fill('[data-test="password"]', creds[role].pass);&lt;br&gt;
  await page.click('[data-test="login-button"]');&lt;br&gt;
  await expect(page.locator('[data-test="dashboard"]')).toBeVisible();&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Enterprise Single Sign-On (SSO)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintain a dedicated document (e.g., SSO_TESTING.md) for handling Identity Provider (IdP) login flows.&lt;/li&gt;
&lt;li&gt;Mock or bypass SSO in lower environments whenever possible to speed up test execution.

&lt;ol&gt;
&lt;li&gt;Test Data Management
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Keep test data separate from test logic — store in test-data/json/ or test-data/excel/.&lt;/li&gt;
&lt;li&gt;Use unique data per run (e.g., timestamps, UUIDs) to prevent collisions when tests run in parallel.
const groupName = AutoGroup_${Date.now()};&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;// Use factory functions to generate test data objects:&lt;br&gt;
// utils/dataFactory.js&lt;br&gt;
export function createGroupPayload(overrides = {}) {&lt;br&gt;
  return {&lt;br&gt;
    name: AutoGroup_${Date.now()},&lt;br&gt;
    description: 'Generated by automation',&lt;br&gt;
    type: 'standard',&lt;br&gt;
    ...overrides&lt;br&gt;
  };&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;// Clean up all data created during a test in the After hook.&lt;/p&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't hardcode test data (names, IDs, dates) inside step definitions.&lt;/li&gt;
&lt;li&gt;Don't leave orphaned test data in shared environments — it causes noise for manual testers.

&lt;ol&gt;
&lt;li&gt;BDD / Cucumber Integration
Feature File Best Practices&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Write scenarios from the user's perspective using Given / When / Then.&lt;/li&gt;
&lt;li&gt;One scenario = one behavior. Don't write "super scenarios" that test 10 things at once.&lt;/li&gt;
&lt;li&gt;Use Background for common pre-conditions, not complex setup logic.&lt;/li&gt;
&lt;li&gt;Use tags consistently to allow selective execution:
&lt;a class="mentioned-user" href="https://dev.to/smoke"&gt;@smoke&lt;/a&gt; @regression @admin @group-management
Feature: Admin Group Management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Background:&lt;br&gt;
    Given I am logged in as an admin&lt;/p&gt;

&lt;p&gt;@create-group&lt;br&gt;
  Scenario: Admin creates a new group&lt;br&gt;
    When I navigate to Group Management&lt;br&gt;
    And I create a group with name "AutoGroup"&lt;br&gt;
    Then the group "AutoGroup" should appear in the list&lt;/p&gt;

&lt;p&gt;Step Definition Best Practices&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep steps atomic and reusable across scenarios.&lt;/li&gt;
&lt;li&gt;Use World object (this) to share state between steps within a scenario — never use module-level globals.&lt;/li&gt;
&lt;li&gt;Avoid logic-heavy step definitions; delegate to page objects.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Thin step, rich page object:&lt;br&gt;
When('I create a group with name {string}', async function (name) {&lt;br&gt;
await this.adminPage.createGroup(name);&lt;br&gt;
});&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use Cucumber Data Tables and Doc Strings for structured input data.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Error Handling &amp;amp; Debugging
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable screenshots on failure in After hooks (see Section 6).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable video recording for CI runs to replay failures:&lt;br&gt;
// playwright.config.js&lt;br&gt;
use: {&lt;br&gt;
video: 'retain-on-failure',&lt;br&gt;
screenshot: 'only-on-failure',&lt;br&gt;
trace: 'retain-on-failure'&lt;br&gt;
}&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the Playwright Trace Viewer (npx playwright show-trace trace.zip) to inspect failing steps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the built-in logger (utils/logger.js) instead of console.log for structured output.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add meaningful step names so traces and reports are human-readable.&lt;br&gt;
Debugging Locally&lt;/p&gt;
&lt;h1&gt;
  
  
  Run in headed mode with Playwright Inspector
&lt;/h1&gt;

&lt;p&gt;PWDEBUG=1 npx playwright test&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Run a single scenario by tag
&lt;/h1&gt;

&lt;p&gt;npx test --tags="@create-group"&lt;/p&gt;

&lt;h1&gt;
  
  
  Slow down execution for visual debugging
&lt;/h1&gt;

&lt;p&gt;npx playwright test --headed --slowmo=500&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retries &amp;amp; Flakiness
Retry Policy

&lt;ul&gt;
&lt;li&gt;Set retries: 1 (max 2) in playwright.config.js for CI — enough to handle transient network issues, not enough to hide real bugs.&lt;/li&gt;
&lt;li&gt;Never increase retries as a fix for a broken test — find and fix the root cause.
// playwright.config.js
retries: process.env.CI ? 1 : 0,&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Flakiness Thresholds&lt;br&gt;
| Level | Threshold | Action |&lt;br&gt;
|---|---|---|&lt;br&gt;
| Acceptable | &amp;lt; 1% over 7 days | Monitor |&lt;br&gt;
| Warning | 1% – 5% | Investigate &amp;amp; RCA |&lt;br&gt;
| Critical | &amp;gt; 5% | Block release, escalate |&lt;br&gt;
Common Flakiness Causes &amp;amp; Fixes&lt;br&gt;
| Cause | Fix |&lt;br&gt;
|---|---|&lt;br&gt;
| waitForTimeout | Replace with expect or waitForResponse |&lt;br&gt;
| Fragile selectors | Switch to data-test / ARIA locators |&lt;br&gt;
| Shared state | Isolate each scenario (see Section 6) |&lt;br&gt;
| Race conditions | Use Promise.all + network wait |&lt;br&gt;
| Dynamic content | Use toBeVisible({ timeout }) instead of hard-wait |&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parallelism &amp;amp; Performance
DO

&lt;ul&gt;
&lt;li&gt;Start with workers: 2 in CI and increase only after flakiness stabilizes below 1%.&lt;/li&gt;
&lt;li&gt;Use Playwright sharding to distribute tests across CI runners:
npx playwright test --shard=1/3
npx playwright test --shard=2/3
npx playwright test --shard=3/3&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Group slow scenarios (e.g., approval workflows) with &lt;a class="mentioned-user" href="https://dev.to/slow"&gt;@slow&lt;/a&gt; tags and run them separately.&lt;/li&gt;
&lt;li&gt;Use storageState to avoid redundant logins (see Section 7).
DON'T&lt;/li&gt;
&lt;li&gt;Don't share a single browser context between parallel workers.&lt;/li&gt;
&lt;li&gt;Don't run all scenarios in parallel before verifying they are properly isolated.

&lt;ol&gt;
&lt;li&gt;Configuration Management
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Use a single config.js as the source of truth for all configuration values.&lt;/li&gt;
&lt;li&gt;Override config values via environment variables — never change the config file between environments.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use .env files for local development; inject secrets via CI environment variables in pipelines.&lt;br&gt;
// config.js&lt;br&gt;
export const config = {&lt;br&gt;
baseUrl: process.env.APP_BASE_URL || '&lt;a href="https://qa-environment.example.com" rel="noopener noreferrer"&gt;https://qa-environment.example.com&lt;/a&gt;',&lt;br&gt;
browser: process.env.BROWSER || 'chromium',&lt;br&gt;
headless: process.env.HEADLESS !== 'false',&lt;br&gt;
defaultTimeout: Number(process.env.DEFAULT_TIMEOUT) || 120000,&lt;br&gt;
actionTimeout: Number(process.env.ACTION_TIMEOUT) || 30000,&lt;br&gt;
};&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;See ENV_SETUP.md for full environment variable documentation.&lt;br&gt;
DON'T&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Don't commit .env files with real credentials to source control.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Don't hardcode environment URLs in test files — always reference config.js.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reporting &amp;amp; Observability
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use Allure as the primary report for rich test history, attachments, and trend analysis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Attach screenshots, videos, and traces to Allure on failure automatically via hooks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add Allure labels to enrich reports with metadata:&lt;br&gt;
// in step definitions&lt;br&gt;
this.allure.attachment('Response Body', JSON.stringify(responseBody, null, 2), 'application/json');&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate reports as part of every CI pipeline run:&lt;br&gt;
npm run generate:report # Generate Allure report&lt;br&gt;
npm run open:report     # Open in browser locally&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Maintain a weekly flakiness dashboard from Allure history data and share with the team.&lt;br&gt;
Report Locations&lt;br&gt;
| Type | Location |&lt;br&gt;
|---|---|&lt;br&gt;
| Allure HTML | allure-report/ |&lt;br&gt;
| Allure Results | allure-results/ |&lt;br&gt;
| JSON Report | reports/cucumber_report.json |&lt;br&gt;
| Screenshots | reports/screenshots/ |&lt;br&gt;
| Videos | test-results/ |&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CI/CD Integration
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run tests in headless mode in CI:&lt;br&gt;
npm run test:ci&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fail the pipeline on any test failure — don't silently ignore failed tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Archive Allure results and test-results as CI artifacts for post-run analysis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run linting (eslint) and type checks before executing tests in the pipeline.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use branch-specific tags to run only smoke tests on PRs and full regression on main:&lt;/p&gt;
&lt;h1&gt;
  
  
  Example CI step
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;name: Run Smoke Tests (PR)
run: npm test -- --tags "&lt;a class="mentioned-user" href="https://dev.to/smoke"&gt;@smoke&lt;/a&gt;"
if: github.event_name == 'pull_request'&lt;/li&gt;
&lt;li&gt;name: Run Full Suite (Main)
run: npm test
if: github.ref == 'refs/heads/main'&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't run tests against production environments from CI without explicit approval gates.&lt;/li&gt;
&lt;li&gt;Don't skip report generation in CI — reports are essential for debugging failures.

&lt;ol&gt;
&lt;li&gt;Security &amp;amp; Secrets
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Store all credentials in environment variables or a secrets manager (e.g., Vault, CI Secrets).&lt;/li&gt;
&lt;li&gt;Reference secrets via process.env.VAR_NAME — never hardcode them.&lt;/li&gt;
&lt;li&gt;Use .gitignore to exclude .env, auth-state.json, and any file that may contain tokens.&lt;/li&gt;
&lt;li&gt;Rotate test user passwords regularly and update secrets in the CI store.&lt;/li&gt;
&lt;li&gt;See SECRETS.md for detailed secrets management guidelines.
DON'T&lt;/li&gt;
&lt;li&gt;Don't log credentials or tokens to the console or report output.&lt;/li&gt;
&lt;li&gt;Don't commit auth-state.json (contains session tokens) to source control.&lt;/li&gt;
&lt;li&gt;Don't use personal accounts as test users — use dedicated service accounts.

&lt;ol&gt;
&lt;li&gt;Code Quality &amp;amp; Maintainability
DO&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Enable ESLint with a consistent style guide (e.g., Airbnb or StandardJS).&lt;/li&gt;
&lt;li&gt;Use ES Modules (import/export) consistently — configure "type": "module" in package.json.&lt;/li&gt;
&lt;li&gt;Use async/await everywhere — avoid mixing Promises and callbacks.&lt;/li&gt;
&lt;li&gt;Keep functions small and single-purpose (\le 20 lines is a good target).&lt;/li&gt;
&lt;li&gt;Write JSDoc comments for all page object methods and utilities.&lt;/li&gt;
&lt;li&gt;Review test code in pull requests — test code is production code.
/**&lt;/li&gt;
&lt;li&gt;Creates a new group with the given data.&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {Object} groupData&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {string} groupData.name&lt;/li&gt;
&lt;li&gt;
&lt;a class="mentioned-user" href="https://dev.to/param"&gt;@param&lt;/a&gt; {string} [groupData.description]
*/
async createGroup(groupData) {
await this.groupNameInput.fill(groupData.name);
await this.saveButton.click();
await expect(this.successBanner).toBeVisible();
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DON'T&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't leave commented-out code or console.log statements in committed code.&lt;/li&gt;
&lt;li&gt;Don't duplicate step definitions — extract shared steps into a common-steps.js.&lt;/li&gt;
&lt;li&gt;Don't use var — always use const or let.

&lt;ol&gt;
&lt;li&gt;Accessibility &amp;amp; Cross-Browser Testing
Accessibility&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Prefer ARIA roles and labels as primary selectors — this naturally validates accessibility.&lt;/li&gt;
&lt;li&gt;Run @axe-core/playwright checks on key pages as part of accessibility regression:
import { checkA11y } from 'axe-playwright';&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;await checkA11y(page, undefined, {&lt;br&gt;
  runOnly: ['wcag2a', 'wcag2aa']&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;Cross-Browser&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Configure modern browsers (chromium, firefox, webkit) in playwright.config.js:&lt;br&gt;
projects: [&lt;br&gt;
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },&lt;br&gt;
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },&lt;br&gt;
{ name: 'webkit', use: { ...devices['Desktop Safari'] } }&lt;br&gt;
]&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Run cross-browser tests on a schedule (e.g., nightly), not on every PR, to keep PR pipelines fast.&lt;br&gt;
Quick Reference Checklist&lt;br&gt;
Use this checklist before merging new tests:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Tests are isolated — no shared state between scenarios&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;data-test or ARIA locators used — no fragile XPath/CSS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No waitForTimeout calls&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assertions use Playwright's expect (web-first)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Credentials are in environment variables, not hardcoded&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test data uses unique identifiers (timestamp/UUID)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Screenshots/video capture is configured on failure&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Feature file has correct tags for selective execution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Page objects contain no assertions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Code reviewed and ESLint passes&lt;br&gt;
References&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Playwright Official Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Playwright Best Practices&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Cucumber.js Documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Allure Playwright Integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ENV_SETUP.md&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SELECTORS.md&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;FLAKINESS.md&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SECRETS.md&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SSO_TESTING.md&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;CI_HARNESS.md&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>javascript</category>
      <category>testing</category>
      <category>playwright</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Everything You Need for API Automation (A Complete Blueprint)</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sat, 15 Aug 2026 21:44:51 +0000</pubDate>
      <link>https://dev.to/shell_qa/everything-you-need-for-api-automation-a-complete-blueprint-44fn</link>
      <guid>https://dev.to/shell_qa/everything-you-need-for-api-automation-a-complete-blueprint-44fn</guid>
      <description>&lt;p&gt;Setting up an API automation framework requires aligning business goals, developer specifications, infrastructure, and core testing strategies. &lt;/p&gt;

&lt;p&gt;Here is a comprehensive requirement checklist and workflow to ensure complete coverage across every stage of your API automation setup.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Requirements from Client / Business Owner
&lt;/h2&gt;

&lt;p&gt;Before writing code, define &lt;strong&gt;what&lt;/strong&gt; needs to be tested:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Business requirements (BRD)&lt;/strong&gt; &amp;amp; user stories / use cases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expected API behavior&lt;/strong&gt; &amp;amp; acceptance criteria (success &amp;amp; failure cases)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Priority APIs&lt;/strong&gt; (critical vs optional pathing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance expectations&lt;/strong&gt; (SLA, response time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API versioning policy&lt;/strong&gt; (backward compatibility expectations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security &amp;amp; compliance requirements&lt;/strong&gt; (data privacy, PII handling)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Technical Details from Developers
&lt;/h2&gt;

&lt;p&gt;Understand &lt;strong&gt;how&lt;/strong&gt; the APIs operate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Documentation:&lt;/strong&gt; Swagger / OpenAPI specifications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoints:&lt;/strong&gt; Base URL + specific paths&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Methods:&lt;/strong&gt; GET, POST, PUT, DELETE, PATCH&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Request Details:&lt;/strong&gt; Headers, query params, request body (JSON/XML)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Details:&lt;/strong&gt; Expected status codes (200, 201, 400, 401, 403, 404, 500) and response schema structures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication:&lt;/strong&gt; OAuth, JWT, API keys, or Basic Auth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Error Handling:&lt;/strong&gt; Error codes &amp;amp; error messages&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API Contracts:&lt;/strong&gt; Consumer-driven contract definitions (e.g., using Pact)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate Limits &amp;amp; Throttling:&lt;/strong&gt; Maximum request limits and wait strategies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downstream Dependencies:&lt;/strong&gt; Dependent APIs required for mock/stub planning&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Infrastructure &amp;amp; Environment Setup
&lt;/h2&gt;

&lt;p&gt;Coordinate with the Application Owner or Infra Team for execution requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Environment URLs:&lt;/strong&gt; Dev, QA, UAT, and Prod environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access Control:&lt;/strong&gt; VPN access, API gateway setups, credentials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Data Strategy:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Valid, invalid, edge case, and boundary value datasets&lt;/li&gt;
&lt;li&gt;Data seeding scripts for pre-test setup&lt;/li&gt;
&lt;li&gt;Data teardown/cleanup scripts for post-test cleanup&lt;/li&gt;
&lt;li&gt;Data isolation per environment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Access:&lt;/strong&gt; Direct access for validating API output directly against DB records&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mocking/Stubbing:&lt;/strong&gt; Availability of tools like WireMock or MSW for dependent APIs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Management:&lt;/strong&gt; Environment variables (.env, secret managers) to avoid hardcoding sensitive tokens&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  4. Recommended Tooling Stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Category&lt;/th&gt;
&lt;th&gt;Recommended Tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Manual Testing (First Step)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Postman (validate requests/responses, export collections to Newman)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automation Tools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Playwright (JavaScript), RestAssured (Java), PyTest + Requests (Python), Karate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Performance Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;k6, JMeter, Gatling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Security Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OWASP ZAP, Burp Suite&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Contract Testing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pact&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mocking / Stubbing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;WireMock, MSW&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CI/CD Integration&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jenkins, GitHub Actions, GitLab CI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Reporting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Allure Report, HTML Reporter, Extent Reports&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Automation Framework Setup
&lt;/h2&gt;

&lt;p&gt;Ensure your test automation codebase includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Data-driven, reusable design patterns&lt;/li&gt;
&lt;li&gt;Reusable request utility and helper functions&lt;/li&gt;
&lt;li&gt;Automatic authentication handling (token refreshes, session management)&lt;/li&gt;
&lt;li&gt;Schema validation engines (JSON Schema / Ajv)&lt;/li&gt;
&lt;li&gt;Comprehensive test suites covering:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Positive test coverage&lt;/strong&gt; (happy paths)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Negative test coverage&lt;/strong&gt; (missing fields, invalid data types, boundary values, malformed payloads, unauthorized access)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Multi-layer assertion strategy (status code + body structure + response headers + schema)&lt;/li&gt;
&lt;li&gt;Pre-test data seeding and post-test data cleanup&lt;/li&gt;
&lt;li&gt;Centralized logging and error reporting&lt;/li&gt;
&lt;li&gt;Dynamic configuration management across environments&lt;/li&gt;
&lt;li&gt;Code review guidelines prior to merging automation scripts&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  6. Pro Tips for API Automation
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Manual First:&lt;/strong&gt; Always validate endpoints manually in Postman before writing automated scripts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensure API Stability:&lt;/strong&gt; Wait until the endpoints are stable before investing time in script creation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep Secrets Secret:&lt;/strong&gt; Never hardcode credentials—manage access via environment variables or secret store managers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean Up After Execution:&lt;/strong&gt; Implement automated teardown routines to clean up test-created data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assert Schemas:&lt;/strong&gt; Run schema validation on every response, not just status code checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unified Frameworks:&lt;/strong&gt; If using tools like Playwright, leverage the built-in API request context to combine API and UI tests within a single framework.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  7. The Complete API Execution Workflow
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Get API Details (Swagger/Docs)
       ↓
Manual Validation in Postman
       ↓
Schema &amp;amp; Contract Validation Setup
       ↓
Test Data Seeding
       ↓
Automate Scenarios (Positive + Negative + Edge Cases)
       ↓
Schema Assertions on Response
       ↓
Code Review &amp;amp; Approval
       ↓
CI/CD Pipeline Integration (Trigger on Build)
       ↓
Test Reporting (Allure / HTML Reports)
       ↓
Post-Test Data Cleanup (Teardown)
       ↓
Production Monitoring &amp;amp; Alerts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Must-Have Quick Verification Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Swagger / API documentation received&lt;/li&gt;
&lt;li&gt;Postman collections created &amp;amp; verified&lt;/li&gt;
&lt;li&gt;Environment credentials and VPN access ready&lt;/li&gt;
&lt;li&gt;Valid/Invalid test data seeded&lt;/li&gt;
&lt;li&gt;Mock servers running for external dependencies&lt;/li&gt;
&lt;li&gt;Test reporting integrated with CI/CD pipeline&lt;/li&gt;
&lt;li&gt;Automated teardown configured&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;What tools are you currently using in your API automation stack? Let's discuss in the comments below!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>automation</category>
      <category>api</category>
      <category>qa</category>
    </item>
    <item>
      <title>Playwright + Cucumber Code Review Checklist: A Senior QA Guide to Reliable CI Test Suites</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sat, 15 Aug 2026 20:54:36 +0000</pubDate>
      <link>https://dev.to/shell_qa/playwright-cucumber-code-review-checklist-a-senior-qa-guide-to-reliable-ci-test-suites-3113</link>
      <guid>https://dev.to/shell_qa/playwright-cucumber-code-review-checklist-a-senior-qa-guide-to-reliable-ci-test-suites-3113</guid>
      <description>&lt;p&gt;Building an enterprise test automation framework is one thing; keeping it reliable, maintainable, and fast in CI over time is another.&lt;/p&gt;

&lt;p&gt;To prevent test rot, flakiness, and architectural drift, code reviews need to evaluate more than just standard syntax. They must enforce design boundaries, state isolation, and robust synchronization strategies.&lt;/p&gt;

&lt;p&gt;Here is the complete Code Review Checklist we use for our Playwright + JavaScript + Cucumber BDD suites. Feel free to adopt or adapt it for your team's workflow!&lt;/p&gt;

&lt;h2&gt;
  
  
  Playwright JavaScript Framework — Code Review Checklist
&lt;/h2&gt;

&lt;p&gt;Use this checklist during pull request reviews to keep tests reliable, maintainable, secure, and CI-friendly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;New Playwright test scenarios&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Updated Cucumber feature files and step definitions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Changes to page objects, hooks, utilities, config, and test data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reporting, retry, CI, and environment-related automation changes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Review Checklist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Test Intent and Coverage
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Does the change clearly cover a real business workflow or acceptance criterion?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the scenario scoped to a single behavior instead of combining multiple unrelated validations?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are positive, negative, and edge-case paths considered where appropriate?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are feature names, scenario names, and tags meaningful and consistent?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is there any duplication with an existing scenario that should be reused or refactored?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Feature File Quality (Cucumber)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are Given, When, and Then steps written from the user's perspective?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are scenarios concise, readable, and free of implementation details?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are tags applied correctly for execution targeting (e.g., &lt;a class="mentioned-user" href="https://dev.to/smoke"&gt;@smoke&lt;/a&gt;, @admin, @performer, @approver)?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is Background used only for simple shared preconditions?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are hardcoded credentials, URLs, IDs, or sensitive values avoided in feature files?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Step Definition Quality
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are step definitions thin and delegated to page objects or utilities?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are duplicate or overly similar step definitions avoided?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is shared scenario state stored on the Cucumber World object (this) instead of globals?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are step definitions readable and reusable across scenarios?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do step definitions avoid embedding large selectors, raw waits, or excessive business logic?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Page Object Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are UI interactions encapsulated in the correct page object under page-objects/?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are selectors centralized in the page object instead of duplicated across steps?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are page object methods named by user intent (e.g., createGroup, approveTask, searchQuery)?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are page objects focused on actions and state access rather than test assertions?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a page object became too large, should it be split into smaller components or helper classes?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Selector Strategy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are selectors stable and intention-revealing?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are data-qa, data-test, data-testid, or semantic Playwright locators preferred?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are fragile selectors such as positional XPath, generated classes, or deep CSS chains avoided?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a stable selector was not available, is the fallback rationale documented?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are selector updates aligned with SELECTORS.md?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Assertions and Validation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are assertions using Playwright's expect with web-first behavior?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do assertions verify outcomes that matter to the user or workflow?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are assertions placed in the appropriate layer rather than hidden inside unrelated helpers?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are weak checks avoided, such as validating only that a page loaded without confirming expected data or state?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Where useful, do assertion messages or attachments help future debugging?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  7. Waiting and Synchronization
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Does the code rely on Playwright auto-waiting wherever possible?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are explicit waits tied to real signals like element state, navigation, or API responses?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are arbitrary sleeps such as waitForTimeout() avoided?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If network-dependent behavior exists, does the code wait on the relevant response or UI state change?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Could any race condition appear under CI speed or parallel execution?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Test Isolation and State Management
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Can the scenario run independently and in any order?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is test setup and teardown handled through hooks where appropriate?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are browser context, page, storage state, and created records isolated per scenario?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is cleanup handled for created users, groups, files, or seeded data when needed?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the change introduce hidden dependencies on previously executed tests?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  9. Test Data Handling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is test data externalized to test-data/, factories, or utilities where appropriate?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are unique identifiers used to avoid collisions in shared environments?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are hardcoded dates, names, or IDs avoided unless they are intentionally fixed test fixtures?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is seeded data documented and compatible with the current environment?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are data generation and cleanup steps deterministic and maintainable?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  10. Configuration and Environment Safety
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are environment-specific values sourced from config.js or environment variables?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are .env values kept out of committed code and documentation examples unless intentionally sanitized?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the change behave safely across local, QA, and CI environments?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are timeouts, retries, browser settings, and base URLs configurable rather than hardcoded?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the documentation updated if the new change adds or alters configuration requirements?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  11. Authentication and Secrets
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are credentials sourced only from environment variables or approved secret stores?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are login flows reusable and abstracted where appropriate?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are session files, tokens, or auth-state artifacts excluded from source control if sensitive?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are sensitive values omitted from logs, screenshots, attachments, and console output?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the implementation aligned with SECRETS.md and ENV_SETUP.md?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  12. Flakiness, Retries, and Reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Does the change reduce or increase flakiness risk?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are retries treated as a safety net rather than the primary fix?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a flaky area is touched, was the root cause addressed instead of masking the problem?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the scenario safe to run under current workers and retry settings?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the change consistent with FLAKINESS.md?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  13. Logging, Debugging, and Failure Diagnostics
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Will a future failure be easy to diagnose from logs, screenshots, traces, and reports?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are screenshots, traces, or attachments captured at the right level when failures occur?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the code use structured logging utilities instead of noisy console.log statements?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are debug artifacts meaningful and free of sensitive data?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the reviewer had to debug this in CI, would the report provide enough context?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  14. Code Quality and Maintainability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Is the code easy to read, with clear function and variable names?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are functions small and focused on one responsibility?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is duplication avoided through helpers, shared steps, or utility methods?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the module style consistent with the codebase conventions?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are unused imports, dead code, commented-out blocks, and temporary debugging code removed?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  15. Hooks, Utilities, and Shared Framework Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If setup/hooks.js or shared utilities changed, have downstream impacts been considered?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are hooks minimal, predictable, and safe for all tagged scenarios they affect?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Do shared utilities remain generic enough for reuse?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Could the change unintentionally affect unrelated modules like Admin, Performer, Approver?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If framework behavior changed, should related docs or runbooks be updated?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  16. Parallelism and CI Compatibility
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Will the change behave correctly when tests run in parallel or rerun on failure?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are file paths, downloads, screenshots, and artifacts unique per test where needed?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the change assume a local-only environment or headed execution?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is the scenario suitable for CI headless mode?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are run tags and suite grouping still appropriate for smoke vs broader regression?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  17. Reporting and Observability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Are Allure and other reporting outputs still generated correctly?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are important artifacts attached for failures without creating excessive noise?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the change preserve report readability and trend usefulness?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If new metadata, labels, or attachments were added, are they helpful and consistent?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are report-related updates aligned with current scripts and CI expectations?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  18. Security, Compliance, and Governance
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Does the change avoid exposing confidential business data in code, fixtures, or reports?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If AI-assisted code was introduced, does it receive human review per AI_GOVERNANCE.md?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Are compliance-sensitive workflows documented clearly enough for audit or review if needed?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Does the change follow team expectations captured in DoD.md?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Reviewer Quick Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Before hitting Approve, ask yourself:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Would this still be understandable to a new automation engineer in three months?&lt;/li&gt;
&lt;li&gt;Would this remain reliable in CI under retry and parallel execution?&lt;/li&gt;
&lt;li&gt;Would a failure provide enough evidence to debug quickly?&lt;/li&gt;
&lt;li&gt;Did this change add unnecessary complexity to the framework?&lt;/li&gt;
&lt;li&gt;Is the root cause solved, or is the PR only hiding symptoms?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;How do you handle PR reviews for your automation suites? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>playwright</category>
      <category>testing</category>
      <category>qa</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Automate Email Verification in Playwright using Microsoft Graph API &amp; TypeScript</title>
      <dc:creator>Shell QA</dc:creator>
      <pubDate>Sat, 15 Aug 2026 20:43:54 +0000</pubDate>
      <link>https://dev.to/shell_qa/automate-email-verification-in-playwright-using-microsoft-graph-api-typescriptpublished-true-43fi</link>
      <guid>https://dev.to/shell_qa/automate-email-verification-in-playwright-using-microsoft-graph-api-typescriptpublished-true-43fi</guid>
      <description>&lt;p&gt;Automating end-to-end flows like user registration, password resets, or OTP verifications often hits a wall when dealing with emails. Automating UI login for webmail providers like Gmail or Outlook is flaky and frequently blocked by 2FA or CAPTCHAs.&lt;/p&gt;

&lt;p&gt;Using &lt;strong&gt;Microsoft Graph API&lt;/strong&gt; with an Azure App Registration (Client Credentials Flow) lets you fetch target user inbox messages programmatically in your Playwright tests without any UI interaction.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Prerequisites &amp;amp; Azure Setup
&lt;/h2&gt;

&lt;p&gt;Before writing test code, configure your Azure Entra ID (formerly Azure AD):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Register an App:&lt;/strong&gt; Go to Azure Portal &amp;gt; &lt;strong&gt;App registrations&lt;/strong&gt; &amp;gt; &lt;strong&gt;New registration&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set Permissions:&lt;/strong&gt; Navigate to &lt;strong&gt;API permissions&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add a permission&lt;/strong&gt; &amp;gt; &lt;strong&gt;Microsoft Graph&lt;/strong&gt; &amp;gt; &lt;strong&gt;Application permissions&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Mail.Read&lt;/strong&gt; (or Mail.ReadWrite if you plan to delete emails post-test).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Grant Admin Consent:&lt;/strong&gt; Click &lt;strong&gt;Grant admin consent for [Your Org]&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create Credentials:&lt;/strong&gt; Go to &lt;strong&gt;Certificates &amp;amp; secrets&lt;/strong&gt; &amp;gt; &lt;strong&gt;New client secret&lt;/strong&gt;. Store the secret securely.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Collect the following environment variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AZURE_TENANT_ID&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_ID&lt;/li&gt;
&lt;li&gt;AZURE_CLIENT_SECRET&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  2. Install Required Dependencies
&lt;/h2&gt;

&lt;p&gt;Install the Azure Identity SDK and Microsoft Graph Client:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; @azure/identity @microsoft/microsoft-graph-client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Build the Graph Email Service
&lt;/h2&gt;

&lt;p&gt;Create a dedicated helper service (graphService.ts) to manage authentication, inbox polling, and token extraction.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ClientSecretCredential&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;@azure/identity&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&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;Client&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;@microsoft/microsoft-graph-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&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;TokenCredentialAuthenticationProvider&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;@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&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;GraphService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;graphClient&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tenantId&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;AZURE_TENANT_ID&lt;/span&gt;&lt;span class="o"&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;clientId&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;AZURE_CLIENT_ID&lt;/span&gt;&lt;span class="o"&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;clientSecret&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;AZURE_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&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;credential&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;ClientSecretCredential&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;clientSecret&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;authProvider&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;TokenCredentialAuthenticationProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;scopes&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;[https://graph.microsoft.com/.default](https://graph.microsoft.com/.default)&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;graphClient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;initWithMiddleware&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;authProvider&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Polls the inbox for an email matching specific subject criteria within a timeout.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getLatestEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;targetEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;subjectContains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;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;startTime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;startTime&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&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;graphClient&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/users/&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;targetEmail&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="sr"&gt;/messages&lt;/span&gt;&lt;span class="err"&gt;)
&lt;/span&gt;        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;${subjectContains}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subject,body,receivedDateTime&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;receivedDateTime desc&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;top&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="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;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;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&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="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

      &lt;span class="c1"&gt;// Wait 3 seconds before polling again&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resolve&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="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Email&lt;/span&gt; &lt;span class="kd"&gt;with&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="nx"&gt;containing&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;${subjectContains}&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="nx"&gt;was&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;received&lt;/span&gt; &lt;span class="nx"&gt;within&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;timeoutMs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;.);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Helper to extract links or OTP codes using regular expressions.
   */&lt;/span&gt;
  &lt;span class="nf"&gt;extractPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;RegExp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;htmlContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Pattern&lt;/span&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;not&lt;/span&gt; &lt;span class="nx"&gt;found&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;the&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;match&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="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;match&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;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  4. Integrate with Playwright Tests
&lt;/h2&gt;

&lt;p&gt;Use the service directly inside your Playwright test file (emailValidation.spec.ts):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&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="nx"&gt;expect&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GraphService&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;./graphService&lt;/span&gt;&lt;span class="dl"&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;Registration &amp;amp; Email Verification Flow&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graphService&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;GraphService&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;testUserEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;qa-automation@yourdomain.com&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;User registers and verifies OTP from 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;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;// 1. Trigger signup action in web app&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;[https://example.com/register](https://example.com/register)&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;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;#email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;testUserEmail&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;// 2. Fetch latest email and extract 6-digit OTP&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;graphService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getLatestEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;testUserEmail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your Verification Code&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;otpCode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;graphService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extractPattern&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;emailBody&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\b\d{6}\b&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Complete verification on page&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;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;#otp-input&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;otpCode&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;#verify-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;// 4. Assert successful navigation&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;locator&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="nf"&gt;toBeVisible&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;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No Flaky UI:&lt;/strong&gt; API-level retrieval eliminates UI dependency for email checking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Polling Strategy:&lt;/strong&gt; Always implement retry loops with timeouts to account for network latency in email delivery.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Limit application permissions in Azure using Application Access Policies if you need to restrict access to specific automated QA mailboxes.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>playwright</category>
      <category>typescript</category>
      <category>azure</category>
      <category>testing</category>
    </item>
  </channel>
</rss>
