<?xml version="1.0" encoding="utf-8"?>
    <feed xmlns="http://www.w3.org/2005/Atom">
     <title>BigBinary Blog</title>
     <link href="https://www.bigbinary.com/feed.xml" rel="self"/>
     <link href="https://www.bigbinary.com/"/>
     <updated>2026-10-04T10:50:30+00:00</updated>
     <id>https://www.bigbinary.com/</id>
     <entry>
       <title><![CDATA[How to analyze Playwright traces]]></title>
       <author><name>Deepanshu Rajput</name></author>
      <link href="https://www.bigbinary.com/blog/how-to-analyze-playwright-traces"/>
      <updated>2026-01-15T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/how-to-analyze-playwright-traces</id>
      <content type="html"><![CDATA[<p>Playwright traces are like flight recorders for your tests - they capture everyaction, network request, DOM snapshot, and console log during test execution.When a test fails, especially in CI environments, traces become the mostpowerful debugging tool, providing a complete timeline of what happened and why.</p><p>We learned so much about Playwright traces while adding the Playwright's tracingcapabilities to <a href="https://neeto.com/playdash">NeetoPlaydash</a>. We builtNeetoPlaydash and it is the most affordable Playwright Dashboard. NeetoPlaydashcollects, monitors and debugs Playwright test reports.</p><h2>What are Playwright traces?</h2><p>Playwright traces are compressed archives (<code>.zip</code> files) that contain:</p><ul><li><strong>Complete DOM snapshots</strong> before, during, and after each action.</li><li><strong>Screenshots</strong> of the browser state at every step.</li><li><strong>Network activity</strong>, including all HTTP requests and responses.</li><li><strong>Console logs</strong> from both the browser and your test.</li><li><strong>Action timeline</strong> showing what your test did and when.</li><li><strong>Source code</strong> mapping actions back to your test files.</li><li><strong>Metadata</strong> about the test environment, browser, and configuration.</li></ul><p>Think of traces as a time machine for test execution - we can pause at anymoment and see exactly what the browser looked like, what network calls were inflight, and what our code was doing.</p><h2>What is the Trace Viewer?</h2><p>The Trace Viewer is an application used to view and analyze the informationcollected in trace files. Traces are information collected into a zip fileduring test execution. The Trace Viewer makes sense of and displays thisinformation in an interactive interface.</p><p>We can view traces in two ways:</p><ul><li><strong>Locally</strong> - Using the Trace Viewer app that ships with Playwright (via thePlaywright CLI).</li><li><strong>Online</strong> - By visiting <a href="https://trace.playwright.dev/">trace.playwright.dev</a>and uploading our trace file.</li></ul><p>Both methods provide the same powerful debugging experience, allowing us tonavigate through our test execution, inspect DOM snapshots, analyze networkrequests, and debug failures.</p><h2>Understanding the Trace Viewer interface</h2><p>One can download a<a href="/blog/images/images_used_in_blog/how_to_analyze_playwright_traces/sample-trace.zip">sample trace file</a>to follow along with this blog. We can also view it directly in the<a href="https://trace.playwright.dev/">Trace Viewer</a>.</p><p>The Trace Viewer interface is divided into several key areas that work togetherto help us debug your tests.</p><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/trace-viewer-overview.png" alt="Trace Viewer Overview"></p><h3>1. Action Timeline (Top)</h3><p>A visual filmstrip showing screenshots of our test execution over time. We can:</p><ul><li><strong>Hover</strong> to see magnified previews.</li><li><strong>Click</strong> to jump to specific moments.</li><li><strong>Double-click</strong> an action to focus on its time range.</li><li><strong>Drag</strong> to select a range of actions for filtering.</li></ul><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/action-timeline.png" alt="Action Timeline"></p><h3>2. The Actions Tab: Our test execution timeline</h3><p>The Actions tab is typically our starting point. Each action shows:</p><ul><li><strong>Duration anomalies</strong> - Actions taking unusually long suggest performanceissues or waiting problems.</li><li><strong>Locator information</strong> - Verify that we are targeting the correct elements.</li><li><strong>Action sequence</strong> - Ensure actions execute in the expected order.</li><li><strong>Red/failed actions</strong> - These are our primary debugging targets.</li></ul><p>Hover over each action to see the DOM highlight change in real-time. This helpsverify that Playwright is interacting with the correct element.</p><p><strong>Example:</strong></p><pre><code> page.goto(&quot;https://app.neetocal.com&quot;) - 1.2s page.getByTestId(&quot;login-button&quot;).click() - 0.3s page.getByTestId(&quot;email-input&quot;).fill(&quot;test@example.com&quot;) - 30s TIMEOUT</code></pre><p>Here, the fill action timed out. Select the action and use the <strong>Before</strong> and<strong>After</strong> tabs above the main snapshot viewing area to see why the elementwasn't available.</p><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/actions-tab.png" alt="Actions Tab"></p><h3>3. Metadata Tab: Environment context</h3><p>The <strong>Metadata</strong> section provides high-level contextual information about thetest execution environment and run characteristics. This information helps inunderstanding <em>when</em>, <em>how</em>, and <em>under what conditions</em> the trace was recorded.</p><ul><li><strong>Time:</strong> Displays the start time, end time, and total duration of the test.</li><li><strong>Browser:</strong> Displays the browser (e.g., Chromium, Firefox, WebKit), platform,and user agent used during the test run.</li><li><strong>Config:</strong> Shows the Playwright configuration applied for the run. Includesrelevant settings such as test options, retries, timeouts, and project-leveloverrides.</li><li><strong>Viewport:</strong> Specifies the viewport dimensions used during execution. Helpsdiagnose layout, responsiveness, and visual issues tied to screen size.</li><li><strong>Counts (Metrics):</strong> Summarizes key execution metrics captured in the trace,such as:<ul><li><strong>Pages</strong> - Pages captured during the trace.</li><li><strong>Actions</strong> - Actions performed during the test.</li><li><strong>Events</strong> - Runtime events logged during execution.</li></ul></li></ul><p>This is particularly useful when:</p><ul><li>Tests pass locally but fail in CI.</li><li>Tests behave differently across browsers.</li><li>Issues are viewport-specific (responsive design bugs).</li></ul><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/metadata-tab.png" alt="Metadata Tab"></p><h3>4. Main Content Area (Center)</h3><p>The <strong>Main Content Area</strong> is the primary visual workspace of the Trace Viewer.It displays detailed, interactive views of the application state for theselected action, enabling precise inspection and debugging.</p><h4>Pick Locator</h4><p>Allows us to interactively select elements directly from the snapshot.Automatically generates the corresponding Playwright locator, helping validateselectors and improve test reliability.</p><h4>Snapshots: Time-Travel Debugging</h4><p>Snapshots capture the complete DOM state at three critical moments:</p><ul><li><strong>Action</strong> - The exact moment of interaction (showing the precise clickcoordinates or input position with a red dot).</li><li><strong>Before</strong> - State when the action was called.</li><li><strong>After</strong> - State after the action is completed.</li></ul><p><strong>Using snapshots effectively:</strong></p><ul><li><strong>Compare Before/After</strong> to see what changed.</li><li><strong>Inspect element visibility</strong> - Was the element actually visible andinteractable?</li><li><strong>Check for overlays</strong> - Are modals, loading spinners, or other elementsblocking interaction?</li><li><strong>Verify element state</strong> - Is the button disabled? Is the input read-only?</li></ul><p><strong>Debugging technique:</strong> When a click fails, examine the highlighted clickposition in the Action snapshot. If it's not where we expect, we may have:</p><ul><li>Multiple elements matching your locator (strict mode violation).</li><li>An element that moved during Playwright's auto-wait.</li><li>An element obscured by another element (z-index issues).</li></ul><h4>Open Snapshot in a New Tab</h4><p>Opens the current DOM snapshot in a separate browser tab. Useful for deepinspection, side-by-side comparison, or analyzing complex layouts without losingtrace context.</p><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/main-content-area.png" alt="Main Content Area"></p><h3>5. Tab Bar (Bottom/Right)</h3><h4>Locator Tab</h4><p>This is useful to get the locator of any element. Click the Locator button andhover over any component in the snapshot; the locator for that element willappear in the code space below the button. The reverse is also possible - typinga locator in the code space will highlight the corresponding element in the maincontent area, making it easy to verify locators and test selectors.</p><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/locator-tab.png" alt="Locator Tab"></p><h4>Call Tab: Action details</h4><p>The Call tab provides granular information about each action:</p><ul><li><strong>Function signature</strong> - The exact Playwright method called.</li><li><strong>Parameters</strong> - Arguments passed to the function.</li><li><strong>Locator string</strong> - How Playwright found the element.</li><li><strong>Strict mode</strong> - Whether strict mode was enforced.</li><li><strong>Timeout</strong> - Maximum wait time configured.</li><li><strong>Return value</strong> - The resolved value of the Playwright function call.</li></ul><p><strong>Example:</strong></p><pre><code class="language-javascript">await expect(page.getByTestId(&quot;publish-btn&quot;)).toBeDisabled({ timeout: 10_000 });</code></pre><p>The corresponding details would show the function signature, parameters, andexecution result.</p><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/call-tab.png" alt="Call Tab"></p><h4>Log Tab: Playwright's internal actions</h4><p>The Log tab reveals what Playwright does behind the scenes. ContainsPlaywright-generated internal logs for the selected action. Provides insightinto retries, waiting behavior, timeouts, and internal decision-making.</p><pre><code> waiting for getByTestId('submit-button'). locator resolved to &lt;button&gt;Submit&lt;/button&gt;. scrolling element into view if needed. waiting for element to be visible. waiting for element to be enabled. waiting for element to be stable. waiting for element to receive pointer events. performing click action. click action completed.</code></pre><p><strong>Why this matters:</strong> Understanding Playwright's auto-wait mechanism helps us infollowing areas:</p><ul><li>Identify which wait condition failed.</li><li>Optimize your locators.</li><li>Add appropriate waits when auto-wait isn't sufficient.</li><li>Debug flaky tests caused by race conditions.</li></ul><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/log-tab.png" alt="Log Tab"></p><h4>Errors Tab: Failure analysis</h4><p>When tests fail, the Errors tab is our first stop. It shows:</p><ul><li>Error messages with stack traces.</li><li>Playwright's failure reason.</li><li>Timeout information.</li><li>Expected vs. actual states (for assertions).</li></ul><p><strong>The timeline also highlights errors</strong> with a red vertical line, making it easyto see when things went wrong. Lists errors and exceptions associated with theaction or test step. Includes failure messages, stack traces, and error types toquickly identify what went wrong.</p><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/errors-tab.png" alt="Errors Tab"></p><h4>Console Tab: Browser and test logs</h4><p>The Console tab shows all console output, including:</p><ul><li><code>console.log</code>, <code>console.error</code>, <code>console.warn</code> from our application.</li><li>Browser warnings and errors.</li><li>Test framework logs.</li><li>Playwright's internal logs.</li></ul><p><strong>Visual indicators:</strong></p><ul><li>Different icons distinguish between browser console logs and test logs.</li><li>Error messages are highlighted in red.</li><li>Warnings appear in yellow.</li></ul><p><strong>Filtering console logs:</strong> Double-click an action in the sidebar to filterconsole logs to only that action's timeframe. This is crucial when dealing withverbose applications.</p><p><strong>Common patterns to look for:</strong></p><pre><code class="language-javascript">// React errorsWarning: Can't perform a React state update on an unmounted component// Network errorsFailed to load resource: the server responded with a status of 404// Application errorsUncaught TypeError: Cannot read property 'id' of undefined// CORS issuesAccess to fetch at 'https://api.example.com' has been blocked by CORS policy</code></pre><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/console-tab.png" alt="Console Tab"></p><h4>The Network Tab: API and resource investigation</h4><p>The Network tab is invaluable for debugging issues related to:</p><ul><li>API failures.</li><li>Slow page loads.</li><li>Missing resources.</li><li>Authentication problems.</li></ul><p><strong>Key columns:</strong></p><ul><li><strong>Method</strong> (GET, POST, PUT, DELETE, etc.)</li><li><strong>URL</strong> (full request path)</li><li><strong>Status</strong> (200, 404, 500, etc.)</li><li><strong>Content Type</strong> (application/json, text/html, etc.)</li><li><strong>Duration</strong> (request time)</li><li><strong>Size</strong> (response size)</li></ul><p><strong>Filtering network requests:</strong></p><ul><li>Use the timeline to select a specific action range.</li><li>The network tab automatically filters to show only requests during thatperiod.</li></ul><p><strong>What to investigate:</strong></p><pre><code> GET /api/auth/session - 200 - 45ms - application/json POST /api/bookings/create - 500 - 2.1s - application/json GET /api/user/profile - 200 - 89ms - application/json</code></pre><p>The 500 error on the booking creation is our culprit. Click on it to see:</p><ul><li><strong>Request headers</strong> - Is authentication included (CSRF token)?</li><li><strong>Request body</strong> - Is the payload correct?</li><li><strong>Response headers</strong> - Any CORS issues?</li><li><strong>Response body</strong> - What error message did the server return?</li></ul><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/network-tab.png" alt="Network Tab"></p><h4>Source Tab: Connecting traces to code</h4><p>The Source tab displays our test code and highlights the exact linecorresponding to the selected action. This is crucial for:</p><ul><li>Understanding what your test was trying to do.</li><li>Verifying locators and test logic.</li><li>Jumping between test code and execution results.</li></ul><p><strong>Workflow:</strong></p><ol><li>Click an action in the sidebar.</li><li>Source tab automatically shows the relevant code line.</li><li>Review the locator, expected behavior, and assertions.</li><li>Cross-reference with the DOM snapshot to verify assumptions.</li></ol><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/source-tab.png" alt="Source Tab"></p><h4>Attachments Tab: Visual regression and screenshots</h4><p>For tests using visual comparisons or custom attachments, this tab shows:</p><ul><li><strong>Screenshot comparisons</strong> (expected, actual, diff)</li><li><strong>Image slider</strong> to overlay images and spot differences</li><li><strong>Custom attachments</strong> added via <code>test.attach()</code></li><li><strong>Screenshots</strong> &amp; <strong>Video recordings</strong> (if configured)</li></ul><p><strong>Visual regression workflow:</strong></p><ol><li>Navigate to the Attachments tab.</li><li>View the diff image, highlighting differences in red.</li><li>Use the slider to compare expected vs. actual.</li><li>Determine if changes are legitimate or bugs.</li></ol><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/attachments-tab.png" alt="Attachments Tab"></p><h2>Advanced debugging techniques</h2><h3>1. Time-Range filtering for complex tests</h3><p>For long-running tests with many actions:</p><ol><li>Click a starting point on the timeline.</li><li>Drag to an ending point.</li><li>All tabs (Actions, Network, Console) filter to this range.</li><li>Focus your investigation on the relevant portion.</li></ol><h3>2. Network request correlation</h3><p>When debugging API-dependent tests:</p><ol><li>Find the failing action.</li><li>Check the Network tab for API calls during that action.</li><li>Verify request/response timing.</li><li>Ensure data contracts match expectations.</li></ol><p><strong>Example investigation:</strong></p><pre><code>Test: User creates a booking.Action: page.click(&quot;button[type=submit]&quot;).Network: POST /api/bookings/create - 400 Bad Request.Response: {&quot;error&quot;: &quot;Invalid time slot&quot;}.Conclusion: Form validation passed, but the API rejected the data.Next step: Check whether the time slot selection logic has a bug.</code></pre><h3>3. Console error causation</h3><p>Browser console errors often precede test failures:</p><ol><li>Review the Console tab chronologically.</li><li>Look for errors before the failing action.</li><li>JavaScript errors may prevent event handlers from working.</li><li>Network errors may leave the UI in an invalid state.</li></ol><h3>4. Locator strategy validation</h3><p>When the element isn't found:</p><ol><li>Check the Before snapshot - is the element present?</li><li>Review the Call tab - is the locator correct?</li><li>Use browser DevTools on the snapshot to test alternative locators</li></ol><h3>5. Race condition detection</h3><p>Flaky tests often have race conditions:</p><ol><li>Compare traces from passed vs. failed runs.</li><li>Look for timing differences in the Network tab.</li><li>Check if elements appear/disappear between snapshots.</li></ol><h2>Common debugging scenarios</h2><h3>Scenario 1: &quot;Element Not Found&quot; errors</h3><p><strong>Trace analysis steps:</strong></p><ol><li>Navigate to the failing click/fill action.</li><li>Examine the Before snapshot - is the element in the DOM?</li><li>Check the Console for JavaScript errors that might prevent rendering.</li><li>Review Network tab - did the page load completely?</li><li>Verify the locator in the Call tab matches the element you expect.</li></ol><p><strong>Possible causes:</strong></p><ul><li>Element hasn't rendered yet (needs <code>waitForSelector</code>).</li><li>Wrong locator (typo, dynamic attributes).</li><li>Element is in a different frame/iframe.</li><li>Previous action failed, leaving UI in an unexpected state.</li></ul><h3>Scenario 2: &quot;Timeout Waiting for Element&quot; errors</h3><p><strong>Trace analysis steps:</strong></p><ol><li>Check element visibility in the Before snapshot.</li><li>Look at the element's CSS properties (display, opacity, visibility).</li><li>Check for z-index issues or overlapping elements.</li><li>Review the Network tab for slow API responses blocking the UI.</li><li>Check the Console for loading state indicators.</li></ol><p><strong>Possible causes:</strong></p><ul><li>CSS hides the element.</li><li>Loading spinner still active.</li><li>API call hasn't completed.</li><li>Modal or overlay blocking interaction.</li><li>Element removed and re-added (Playwright lost reference).</li></ul><h3>Scenario 3: API failures causing test failures</h3><p><strong>Trace analysis steps:</strong></p><ol><li>Filter the Network tab to the action's timeframe.</li><li>Find failed API requests (4xx, 5xx status codes).</li><li>Inspect request payload - is test data valid?</li><li>Check the response body for error details.</li><li>Verify authentication headers are present.</li></ol><p><strong>Possible causes:</strong></p><ul><li>Test data doesn't match API validation rules.</li><li>Authentication token expired (X-CSRF token).</li><li>Database state inconsistent (previous test didn't clean up).</li><li>API endpoint changed (version mismatch).</li></ul><h3>Scenario 4: Tests pass locally but fail in CI</h3><p><strong>Trace analysis steps:</strong></p><p>Run the test locally as well and open the traces for both runs (local and CI):</p><ol><li>Compare the Metadata tab - check browser, viewport, timezone differences</li><li>Look for timing differences in action durations</li><li>Check for environment-specific console errors</li><li>Compare Network tab - are API endpoints different?</li></ol><p><strong>Possible causes:</strong></p><ul><li>Timezone-dependent test data.</li><li>Slower CI environment (needs longer timeouts).</li><li>Different environment variables.</li><li>Some other tests are affecting the test (very rare case).</li></ul><h3>Scenario 5: Flaky tests (Intermittent failures)</h3><p><strong>Trace analysis steps:</strong></p><ol><li>Compare traces from multiple runs (passed and failed).</li><li>Look for timing variations in Network requests.</li><li>Check for race conditions between actions.</li><li>Review auto-wait logs for differences in element stability.</li><li>Look for animations or transitions affecting element states.</li></ol><p><strong>Possible causes:</strong></p><ul><li>Race conditions between UI updates and test actions.</li><li>Async operations without proper waits.</li><li>Animations not completing before interaction.</li><li>Network request order is non-deterministic.</li><li>Shared test state between test runs.</li></ul><h2>Best practices for trace analysis</h2><h4>1. Start with the error</h4><p>Always begin at the point of failure. The Errors tab and red timeline markersguide you directly there.</p><h4>2. Trace backwards</h4><p>After identifying the error, trace back through the actions to determine itsroot cause, which may be linked to an event that occurred several steps earlier.</p><h4>3. Compare known-good traces</h4><p>If you have a passing trace, compare it side-by-side with the failing trace tospot differences quickly.</p><h4>4. Use timeline filtering liberally</h4><p>Don't drown in information. Filter the timeline to focus on relevant actions andreduce noise.</p><h4>5. Correlate across tabs</h4><p>True debugging power comes from correlating information across tabs:</p><ul><li>Action timing + Network requests + Console logs = complete picture</li></ul><h4>6. Document your findings</h4><p>When you identify the root cause, document it:</p><ul><li>Add comments to your test code.</li><li>Update test data or fixtures.</li><li>Fix race conditions with proper waits.</li><li>Report application bugs with trace evidence.</li></ul><h4>7. Configure appropriate trace collection</h4><p>In your <code>playwright.config.ts</code>:</p><pre><code class="language-typescript">export default defineConfig({  use: {    // Capture trace only on first retry (recommended for CI)    trace: &quot;on-first-retry&quot;,    // Or retain traces only for failures    // trace: 'retain-on-failure',    // For local debugging, enable traces for all tests    // trace: 'on',  },  // Enable retries to capture traces on failures  retries: process.env.CI ? 2 : 0,});</code></pre><h2>Trace Viewer keyboard shortcuts</h2><p>Speed up your analysis with these shortcuts:</p><ul><li><strong>Arrow keys</strong> - Navigate between actions.</li><li><strong>Esc</strong> - Clear selection/filtering.</li><li><strong>Ctrl/Cmd + F</strong> - Search within trace.</li></ul><h2>Accessing traces in NeetoPlaydash</h2><p><a href="https://neeto.com/neetoplaydash">NeetoPlaydash</a> is a test management platformthat integrates seamlessly with Playwright's tracing capabilities. When a testruns on CI, Playwright automatically generates trace files. To access them:</p><ol><li><strong>Navigate to NeetoPlaydash</strong> - Open your projects dashboard.</li><li><strong>Select a Test Run</strong> - Choose the test execution you want to investigate fora particular project.</li><li><strong>Open Test Details Pane</strong> - View the details of a specific test.</li><li><strong>Click &quot;Open Trace&quot;</strong> - This button launches the Trace Viewer with yourtest's trace file.</li></ol><p>The trace opens in your browser at <code>trace.playwright.dev</code> or locally via thePlaywright CLI, providing a complete, interactive debugging experience withoutrequiring manual file downloads.</p><p><img src="/blog/images/images_used_in_blog/2026/how-to-analyze-playwright-traces/access-trace-neetoplaydash.png" alt="Access trace in NeetoPlaydash"></p><h2>Further resources</h2><ul><li><a href="https://playwright.dev/docs/trace-viewer">Playwright Trace Viewer Documentation</a></li><li><a href="https://playwright.dev/docs/best-practices">Playwright Best Practices</a></li><li><a href="https://playwright.dev/docs/debug">Debugging Playwright Tests</a></li><li><a href="https://trace.playwright.dev/">Online Trace Viewer</a></li><li><a href="https://courses.bigbinaryacademy.com/learn-qa-automation-using-playwright/">Learn QA Automation using Playwright Course</a></li></ul>]]></content>
    </entry><entry>
       <title><![CDATA[Why we switched from Cypress to Playwright]]></title>
       <author><name>S Varun</name></author>
      <link href="https://www.bigbinary.com/blog/why-we-switched-from-cypress-to-playwright"/>
      <updated>2024-09-18T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/why-we-switched-from-cypress-to-playwright</id>
      <content type="html"><![CDATA[<p>Until early 2024, <a href="https://cypress.io">Cypress</a> used to be the most downloadedend-to-end (e2e) testing framework in JavaScript. Since then, it has seen asteep decline in popularity and <a href="https://playwright.dev">Playwright</a> hasovertaken it as the most downloaded end-to-end testing framework.</p><p>We at BigBinary also switched from Cypress to Playwright in late 2023. In thisarticle, we will see some critical reasons for this change in trends and ourpersonal views on why we think Playwright is the superior JavaScript testingframework.</p><p>&lt;div style=&quot;width:100%;max-width:600px;margin:auto;display:grid;grid-template-columns:auto auto;gap:2rem;align-items:end;&quot;&gt;&lt;figure style=&quot;display:flex;flex-direction:column;align-items:center;gap:0.5rem;&quot;&gt;&lt;imgwidth=&quot;1000&quot;height=&quot;600&quot;alt=&quot;Cypress weekly downloads early 2024&quot;src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/cypress-weekly-downloads-early-2024.png&quot;/&gt;&lt;figcaption style=&quot;font-size:x-small;&quot;&gt;Cypress weekly downloads - Early 2024&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure style=&quot;display:flex;flex-direction:column;align-items:center;gap:0.5rem;&quot;&gt;&lt;imgwidth=&quot;1000&quot;height=&quot;600&quot;alt=&quot;Playwright weekly downloads early 2024&quot;src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/playwright-weekly-downloads-early-2024.png&quot;/&gt;&lt;figcaption style=&quot;font-size:x-small;&quot;&gt;Playwright weekly downloads - Early 2024&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure style=&quot;display:flex;flex-direction:column;align-items:center;gap:0.5rem;&quot;&gt;&lt;imgwidth=&quot;1000&quot;height=&quot;600&quot;alt=&quot;Cypress weekly downloads September 2024&quot;src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/cypress-weekly-downloads-september-2024.png&quot;/&gt;&lt;figcaption style=&quot;font-size:x-small;&quot;&gt;Cypress weekly downloads - September 2024&lt;/figcaption&gt;&lt;/figure&gt;&lt;figure style=&quot;display:flex;flex-direction:column;align-items:center;gap:0.5rem;&quot;&gt;&lt;imgwidth=&quot;1200&quot;height=&quot;800&quot;alt=&quot;Playwright weekly downloads September 2024&quot;src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/playwright-weekly-downloads-september-2024.png&quot;/&gt;&lt;figcaption style=&quot;font-size:x-small;&quot;&gt;Playwright weekly downloads - September 2024&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;</p><h2>Why we chose Cypress initially</h2><p>At BigBinary, we are building a number of products at<a href="https://www.neeto.com">Neeto</a>. When the number of products in our product suitegrew and the complexity of each one increased, we needed an automated end-to-endsolution to ensure our applications were stable since manual testing was nolonger viable.</p><p>When the discussion about choosing the e2e testing framework began in mid-2020,a few names emerged, including top players like Selenium and Cypress, and newplayers like Playwright. We chose Cypress owing to its popularity andsimplicity.</p><p>We were satisfied with its overall performance and easy learning curve. We choseCypress as our primary e2e testing framewor,k and wrote extensive e2e tests forthe entire application suite. While things were smooth sailing initially, wesoon encountered many issues with Cypress.</p><h2>Why we decided to switch to Playwright</h2><p>In late August 2023, Cypress released version 13, a major upgrade to theframework that brought along many new features. As Cypress users, we wereoverjoyed. But the excitement quickly turned to frustration when we realizedthat, along with the latest features, Cypress had introduced a few changes thatwere not so open-source in nature.</p><p>It's a known fact that <a href="https://www.cypress.io/cloud">Cypress Cloud</a> is a veryexpensive platform. A few third-party providers like<a href="https://currents.dev">Currents.dev</a> and <a href="https://testomat.io/">Testomat</a>provided similar services at much more affordable costs. However, Cypressversion 13 blocked all third-party reporters. The main reason offered by theCypress team was that Cypress Cloud was their primary source of income and thatthey had to block the third-party tools to survive in the market. They laterrevised this explanation with other arguments on how these third-party reportersmisused the Cypress name for personal gain due to public backlash.</p><p>We had switched to Currents.dev ourselves a few months prior to the event andwere affected by this change. At that point, we had two options: switch toCypress Cloud and incur the additional cost, or stay with Currents.dev but getlocked into an older version of Cypress permanently.</p><p>Both of these choices were unacceptable to us. This was the final nudge weneeded to switch to a new framework. We were already dissatisfied with manyissues with Cypress, so we took advantage of the opportunity to research thebest e2e testing framework available and switch to that. We compared all thepopular frameworks available and observed how they solved our pain points withCypress. That is when we fell in love with Playwright.</p><p>In our comparison, Playwright was the fastest framework in terms of rawperformance and had the highest adoption rate compared to all the otherframeworks. It is an open-source framework maintained by Microsoft. Thearchitecture would enable us to automate more scenarios that were deemedunautomatable using Cypress. We were thrilled to learn that Playwright would fixmost of the issues we faced with Cypress.</p><h3>Features locked behind a paywall and control over third-party software</h3><p>While Cypress supports parallelism and orchestration, it is blocked behind apaywall with a subscription to Cypress Cloud. This means these features, whichcan easily be implemented with the base Cypress package, are only accessiblethrough an external package.</p><p>While Cypress provides APIs for reporters and orchestration, it deliberatelyblocks popular third-party tools and services. That's not a good open sourcepractice. The combination of both makes Cypress an incomplete tool withoutsubscribing to the expensive Cypress Cloud plans, even though the tool isconsidered free and open-source.</p><p>At the same time, Playwright is an entirely open framework in which anyone cancreate and publish third-party reporters. It comes in-built with features suchas parallelization, sharding and orchestration without needing third-party toolsand services. The Playwright team goes a step further by showcasing the popularthird-party reporters on their official documentation.</p><h3>Performance</h3><p>Cypress is the slowest of the e2e testing frameworks available in JS. Here isthe list of the most popular frameworks in decreasing order of performance.</p><p>&lt;div style=&quot;width:100%;display:flex;justify-content:center;&quot;&gt;&lt;imgalt=&quot;Speed comparison of popular JS testing frameworks&quot;src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/speed-comparison-of-testing-frameworks.png&quot;/&gt;&lt;/div&gt;</p><p>&lt;br /&gt;</p><p>Let's compare the performance when the same scenario is implemented in Cypressand Playwright. The scenario is to visit the <a href="https://neeto.com">Neeto homepage</a>and verify the page title.</p><pre><code class="language-js">// Cypresscy.visit(&quot;https://neeto.com&quot;);cy.title().should(&quot;eq&quot;, &quot;Neeto: Get things done&quot;);</code></pre><pre><code class="language-ts">// Playwrightawait page.goto(&quot;https://neeto.com&quot;);expect(await page.title()).toBe(&quot;Neeto: Get things done&quot;);</code></pre><p>The results speak for themselves. While Cypress took <strong>16.09 seconds</strong> to finishthe execution, Playwright took only <strong>1.82 seconds</strong>. This is an improvement of<strong>88.68%</strong>! Here, the execution time combines the time taken for setup and thetime to complete the test. This is the actual time that matters because this isthe time an engineer has to wait until they see the final test result.</p><p>&lt;div&gt;&lt;br /&gt;&lt;figure style=&quot;display:flex;flex-direction:column;align-items:center;gap:0.5rem;&quot;&gt;&lt;imgalt=&quot;Cypress weekly downloads early 2024&quot;src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/cypress-execution-time.png&quot;/&gt;&lt;figcaption style=&quot;font-size:small;&quot;&gt;Cypress execution&lt;/figcaption&gt;&lt;/figure&gt;&lt;br /&gt;&lt;figure style=&quot;display:flex;flex-direction:column;align-items:center;gap:0.5rem;&quot;&gt;&lt;imgalt=&quot;Cypress weekly downloads early 2024&quot;src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/playwright-execution-time.png&quot;/&gt;&lt;figcaption style=&quot;font-size:small;&quot;&gt;Playwright execution&lt;/figcaption&gt;&lt;/figure&gt;&lt;br /&gt;&lt;/div&gt;</p><p>This shows how much of a performance gain switching to Playwright gave us. If welook at a more practical example, our authentication flows through Cypress, andPlaywright gives a much better idea of the time saved. The authentication flow,which consistently took around <strong>2 minutes</strong> in Cypress, is completed in under<strong>20 seconds</strong> using Playwright.</p><p>Playwright's out-of-the-box support for parallelism and sharding can havemultiplicative effect in time savings. If we provide a process-based parallelismof 4 and shard the tests in 4 machines, then 16 tests are run concurrently,which reduces the execution time dramatically.</p><p>Implementing these additional configurations reduced the total test duration forone of our products from <strong>2 hours and 27 minutes</strong> to just <strong>16 minutes</strong>. This<strong>89.12%</strong> of time saving, directly translating to CI cost savings.</p><h3>Memory issues</h3><p>Cypress follows a split architecture. This means that Cypress executes the testswith a NodeJS process, which orchestrates the tests in the browser where thetests are executed. This also means that the browser execution environmentlimits the memory available for tests. Due to this, we have faced crashes inbetween tests multiple times. At one point, the crashes became so frequent thatwe had to invest a lot of time and energy into finding a solution because notest executions were running to completion. We have written a detailed blog onthis topic, which can be found<a href="https://www.bigbinary.com/blog/how-we-fixed-the-cypress-out-of-memory-error-in-chromium-browsers">here</a>.</p><p>Playwright fixes these issues because it handles the test execution in NodeJSservice and communicates with the browsers using<a href="https://chromedevtools.github.io/devtools-protocol/">CDP sessions</a>. This meansthat the memory management can be done on the NodeJS application, while thebrowser only has to worry about handling the actual web application we'retesting.</p><h3>Architecture prone to flakiness</h3><p>Many of Cypress's features are closely tied to its architecture. For example,one of the popular features in Cypress is its<a href="https://docs.cypress.io/guides/core-concepts/retry-ability">retry mechanism</a>and<a href="https://docs.cypress.io/guides/core-concepts/introduction-to-cypress#Chains-of-Commands">chaining</a>.However, these features do not always go hand-in-hand.</p><p>Let's consider this snippet of Cypress code.</p><pre><code class="language-js">cy.get(&quot;.inactive-field&quot;).click().type(&quot;Oliver Smith&quot;);</code></pre><p>While this code looks syntactically correct, it will make the test flaky. Thisis because, in Cypress, only queries are retried, not commands. In the exampleabove, consider that the class name of the field is updated to <code>active-field</code>when we click on it. This means that the <code>cy.get</code> query locates the field andthe <code>click</code> command work fine, but the chain fails at the <code>type</code> command. Thisis because an element with the class name <code>.inactive-field</code> no longer exists inthe DOM tree.</p><p>With the Cypress retry mechanisms, one would think that the whole chain would beretried from fetching the element. However, the chain was completed successfullyuntil the <code>click</code> action. So, only the <code>type</code> action will be retried and willcause the whole chain to fail. To avoid this issue, we must rewrite the testsafter splitting the chain.</p><pre><code class="language-js">cy.get(&quot;.inactive-field&quot;).click();cy.get(&quot;.inactive-field&quot;).type(&quot;Oliver Smith&quot;);</code></pre><p>While this works without issues, the syntactical sugar that Cypress provides bychaining the commands is no longer usable. Now, let's observe the Playwrightcode for the same.</p><pre><code class="language-ts">await page.locator(&quot;.inactive-field&quot;).click();await page.locator(&quot;.inactive-field&quot;).type(&quot;Oliver Smith&quot;);</code></pre><p>It looks pretty similar. This is because Playwright is designed to reduceflakiness as much as possible. To achieve that goal, it prevents the user fromimplementing anti-patterns that can lead to flaky results.</p><h3>Misleading simplicity</h3><p>Cypress is well known for its simplicity and natural syntax, which even the mostnon-technical person can learn. The code samples in the official documentation(which is still one of the best documentation for any framework) make it seemlike a walk in the park. But you soon realize that the examples are for verystraightforward application scenarios that we seldom encounter when working onlarge projects. When you start automating complex scenarios, things soon getcomplicated.</p><p>While performing simple tasks such as clicking on a button or asserting a textis extremely simple, doing something more moderately complex, such as storingthe text contents of a button in a variable, becomes highly complex. This isbecause Cypress architecture works by enqueuing the asynchronous commands. Thismeans that there are no return values for the commands, and the only way toretrieve values from Cypress commands is through a combination of closures andaliases. Let's consider a scenario where we have to verify that the sum of therandomly generated numbers on the screen is the same as the value shown on thepage.</p><p>&lt;div&gt;&lt;br/&gt;&lt;figure style=&quot;display:flex;flex-direction:column;align-items:center;gap:0.5rem;&quot;&gt;&lt;img width=&quot;666&quot; alt=&quot;Sample scenarios of the sum application&quot; src=&quot;/blog/images/images_used_in_blog/2024/why-we-switched-from-cypress-to-playwright/sample-application-that-adds-two-numbers.png&quot;&gt;&lt;figcaption style=&quot;font-size:small;&quot;&gt;A sample application that adds two random numbers&lt;/figcaption&gt;&lt;/figure&gt;&lt;br/&gt;&lt;/div&gt;</p><p>Let's see the difference in code when automating this scenario in Cypress andPlaywright.</p><pre><code class="language-js">// Cypress// Considering all elements have proper data-cy labelscy.get('[data-cy=&quot;generate-new-numbers-button&quot;]').click();cy.get('[data-cy=&quot;first-number&quot;]').as(&quot;firstNumber&quot;);cy.get('[data-cy=&quot;second-number&quot;]').as(&quot;secondNumber&quot;);cy.get('[data-cy=&quot;sum&quot;]').as(&quot;sum&quot;);cy.get(&quot;@firstNumber&quot;).invoke(&quot;text&quot;).then(parseInt).as(&quot;num1&quot;);cy.get(&quot;@secondNumber&quot;).invoke(&quot;text&quot;).then(parseInt).as(&quot;num2&quot;);cy.get(&quot;@sum&quot;).invoke(&quot;text&quot;).then(parseInt).as(&quot;displayedSum&quot;);// Use the aliases to perform the assertioncy.get(&quot;@num1&quot;).then(num1 =&gt; {  cy.get(&quot;@num2&quot;).then(num2 =&gt; {    cy.get(&quot;@displayedSum&quot;).then(displayedSum =&gt; {      const expectedSum = num1 + num2;      expect(displayedSum).to.equal(expectedSum);    });  });});</code></pre><p>We can see how complicated the code becomes when the scenario is just slightlycomplex. Meanwhile, the Playwright code will look like this.</p><pre><code class="language-ts">// Playwright// Considering all elements have proper data-cy labels and the default test-id-attribute is data-cyawait page.getByTestId(&quot;generate-new-numbers-button&quot;).click();const firstNumber = await page.getByTestId(&quot;first-number&quot;).innerText();const secondNumber = await page.getByTestId(&quot;second-number&quot;).innerText();const sum = await page.getByTestId(&quot;sum&quot;).innerText();expect(parseInt(firstNumber) + parseInt(secondNumber)).toBe(parseInt(sum));</code></pre><p>We can see from the code above, how easily we can implement the same logic inPlaywright.</p><h3>Cost of maintenance for Cypress vs. Playwright tests</h3><p>Cypress is an easy-to-learn framework. This simplicity is due to the abstractionof the most commonly used functionalities into Cypress commands. However, thisis a double-edged sword. The abstraction of logic into commands means thatcustomization is complicated in Cypress.</p><p>One of Cypress's significant drawbacks is its reliance on HTML tags andattributes to locate an element. While this makes sense from a programmingstandpoint, the end user is concerned about the roles of the page element(button, heading, etc.) and not how they have been implemented. For the samereason, the text, appearance and functionality of the application are bound toremain consistent throughout the various iterations, while the attributesthemselves are prone to changes.</p><p>This ultimately means the developers must keep fixing/rewriting the Cypresstests for minor UI updates. Cypress is also the slowest of all the e2e testingframeworks in JavaScript, resulting in longer CI runtimes and costs. Thesecombined make the cost of maintaining Cypress tests exceptionally high.</p><p>Besides this, Cypress has many features locked behind its Cypress Cloudplatform, which is very <a href="https://www.cypress.io/pricing">expensive</a>, consideringthe fact that all it does is to collect the test results. This is an additionalcost to bear over the already expensive costs of maintaining the Cypress tests.Given these factors, the cost of preserving Cypress tests can quickly outweighthe benefits of its simplicity and ease of use.</p><p>Playwright solves all of these issues. It has many built-in reporters and anexcellent API for creating custom reporters, so many third-party reporters areavailable. We can even build our custom reporter to save even more costs.</p><h3>Browser support and support for mobile viewport</h3><p>Since Cypress tests run directly on the browsers, only a few are supported.Until recently, it did not even support <a href="https://webkit.org/">WebKit</a> browsers,even though <a href="https://www.apple.com/in/safari/">Safari</a> has a considerable marketshare. Even at the time of writing this article, Cypress's WebKit support isstill in beta. Even if it supports the required browsers, there is still theconstraint that only one browser can be used during an execution.</p><p>Playwright fixes all these issues with minimal effort from our end. It hascomplete support for WebKit browsers and conveniently provides a set of presetsfor the browsers, user agents and viewport of the most popular devices in themarket, including mobile devices. Furthermore, Playwright allows us to executethe same test in different browsers concurrently with the help of projects.These configurations give us the confidence that a passing Playwright test meansthe features will work fine for all users.</p><h3>Support for multiple tabs and browsers</h3><p>While most of the features of a web application can be tested within a singletab, there are a few cases where multiple tabs or browsers become necessary. Onesuch scenario we encountered while writing tests for<a href="https://neeto.com/neetochat">NeetoChat</a>, a real-time chat application. To testNeetoChat we need to open two screens - one for the sender and the other for thereceiver.</p><p>Cypress lacks the support for multiple tabs, so the only way to test thesescenarios was to do this long and complicated process:</p><ol><li>Login as the sender</li><li>Send a message</li><li>Logout</li><li>Login as the receiver</li><li>Verify the message</li><li>Send a reply</li><li>Logout</li><li>Login as the sender</li><li>Verify the response.</li></ol><p>We can see the tedious steps that we need to perform for a relatively simplescenario. This becomes even more tedious if we configure sessions in Cypressbecause we need to invalidate them each time we log out so that we can log in asa different user.</p><p>On the other hand, Playwright provides support for multiple tabs and multiplebrowsers. This means we can log in as the sender from one tab and the receiverfrom another, making the scenario more straightforward and effective.Additionally, we could identify whether the messages were being delivered inreal time because there is no delay in the user switching between the messageposting and verification processes. Playwright also supports browser contexts,which isolate the events between two browser instances, aiding in test isolationduring parallel test execution.</p><h3>Lack of necessary tools</h3><p>Cypress depends on plugins for many necessary tools. These are features we havecome to expect from any modern testing framework. Let's examine a few such toolsand how Playwright handles them natively.</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt;Feature&lt;/td&gt;&lt;td&gt;Cypress plugin&lt;/td&gt;&lt;td&gt;Playwright implementation&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Waiting until a particular event completes on a page&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/NoriSte/cypress-wait-until&quot;&gt;cypress-wait-until&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Playwright offers a variety of APIs which pause the tests until a triggerevent like&lt;a href=&quot;https://playwright.dev/docs/api/class-page#page-wait-for-url&quot;&gt;waitForURL&lt;/a&gt;,&lt;a href=&quot;https://playwright.dev/docs/api/class-page#page-wait-for-request&quot;&gt;waitForRequest&lt;/a&gt;,&lt;a href=&quot;https://playwright.dev/docs/api/class-locator#locator-wait-for&quot;&gt;waitFor&lt;/a&gt;etc.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Adding steps blocks in tests to logically group commands&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/filiphric/cypress-plugin-steps&quot;&gt;cypress-plugin-steps&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-test#test-step&quot;&gt;test.step&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ability to interact with iframes&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://gitlab.com/kgroat/cypress-iframe&quot;&gt;cypress-iframe&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-framelocator&quot;&gt;frameLocator&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Filtering tests based on the titles or tags&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://github.com/cypress-io/cypress/tree/develop/npm/grep&quot;&gt;@cypress/grep&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href=&quot;https://playwright.dev/docs/api/class-fullproject#full-project-grep&quot;&gt;Playwright grep&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><p>We must consider that Playwright includes all these tools out of the box whilestill being more performant than Cypress. As we add such plugins in Cypress, thepackage size also increases.</p><h3>Random errors during tests due to Cypress's iFrame Execution Model</h3><p>As discussed already, Cypress tests are executed inside a browser. They work byrunning Cypress as the main page and running the application which is beingtested as an iframe within the page. This can lead to a lot of unexpected errorsduring the test execution.</p><p>One of the most commonly encountered errors is related to security issues withcookies. When the tested application uses cookies, it might throw random errorsduring the Cypress execution depending on the configuration. This is because the<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value">SameSite</a>configuration of the cookies might block it from being shared with the parentCypress application. This can lead to them not be sent during API requestscausing authentication failures and also cause difficulties with<a href="https://docs.cypress.io/api/commands/session">session</a> configuration.</p><h2>Additional benefits of using Playwright</h2><h3>More resistance to test failures due to minor text changes</h3><p>At BigBinary, we follow a behavioral testing pattern where we verify thefeatures, and not minor details like texts and styles. We made this decision toensure that the tests don't fail due to minor changes in the application. Whilethis is our go-to testing style, there are still some cases where we cannotavoid testing the texts on the page (for example, the error message shown whiletesting negative test cases). This required our tests to be frequently updatedwhile working with Cypress whenever a minor text change was made in theapplication.</p><p>When we switched to Playwright, we got excited about how much we could customizeit according to our needs. This customization is available because, under thehood, it's still a Node.js application. We already use<a href="https://www.i18next.com/">i18next</a> to serve the texts on our application. Wefigured that the tests should use the same translation file. The translationkeys remain consistent even when the texts are updated.</p><p>This minor change brought a huge difference in our test stability. The averagenumber of tests we had to update each week decreased from <strong>22</strong> to <strong>2</strong>. Thatis a lot of time saved, which we could effectively use to expand our testcoverage instead of wasting it fixing the existing suite.</p><h3>Ability to build our own in-house reporter</h3><p>When working with Cypress, we had to switch between many reporting tools,including Cypress Cloud, Currents.dev, and many other third-party tools. Whilethey all had benefits and drawbacks, we couldn't find one that addressed all ourneeds. This is where the excellent reporter APIs offered by Playwright allowedus to write our own Playwright reporter -<a href="https://www.neeto.com/neetoplaydash">NeetoPlaydash</a>.</p><p>We currently use NeetoPlaydash for all our reporting needs and can customize itaccording to our requirements. Most importantly, we reduced the monthlyreporting tool costs by <strong>77% ($405 to $90 per month)</strong>. We also didn't have toworry about exhausting the monthly test limits of third-party reporters,allowing us to run our tests more frequently, thus improving the stability ofour applications.</p><h3>More coverage on tests relating to third-party integrations</h3><p>In Neeto products, we have support for third-party integrations. For example, in<a href="https://www.neeto.com/neetocal">NeetoCal</a> we can have integrations for<a href="https://calendar.google.com/">Google Calendar</a>, <a href="https://zoom.us">Zoom</a>,<a href="https://teams.microsoft.com/">Microsoft teams</a> and a lot more third-partyapplications. Most of these applications have bot detection algorithmsimplemented in place to ensure that their platforms are not misused by badactors. This also meant that we had to consider the integration features to beunautomatable when tested using Cypress.</p><p>Playwright does things differently. Since it's a Node.js application, itsupports all the packages available for the platform. Because of its widesupport, the community has developed a lot of tools for Playwright. We tookadvantage of these tools and plugins and were able to bypass the bot-detectionalgorithms that prevented us from testing the third-party integrations. Thisallowed us to test these integrations in our products effectively and ensurethat the application ran smoothly with the help of automation tests.</p><h2>Conclusion</h2><p>We strongly believe that migrating to Playwright is one of the best decisions wehave ever made. We did not know what we were missing out on until we decided totake the leap and migrate. We got better performance, less flakiness and morecoverage from our test suites. The cost and time saved helped us to effectivelydivert resources to things that actually matter and let the tests do testinginstead of using additional resources to maintain the tests themselves.</p>]]></content>
    </entry><entry>
       <title><![CDATA[Fixing flaky Cypress and Playwright tests with network sync]]></title>
       <author><name>Shreya Kurian</name></author>
      <link href="https://www.bigbinary.com/blog/tackling-flaky-tests-in-cypress-and-playwright"/>
      <updated>2024-01-31T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/tackling-flaky-tests-in-cypress-and-playwright</id>
      <content type="html"><![CDATA[<p>Flaky tests are a common challenge in end-to-end testing. There are many typesof flaky tests. In this blog, we will cover the flakiness that comes when UIactions take place before the API response has arrived. We'll see how<a href="https://docs.cypress.io/">Cypress</a> and <a href="https://playwright.dev/">Playwright</a>,address these challenges.</p><h2>Taming flakiness in Cypress</h2><p>In Cypress, the <code>cy.wait()</code> command is used to pause the test execution. Let'sexplore how Cypress handles flakiness with the <code>cy.intercept()</code> and <code>cy.wait()</code>commands.</p><p>Let's consider an example of an online shopping application where a new order iscreated when we click the submit button.</p><pre><code class="language-javascript">cy.intercept(&quot;/orders/*&quot;).as(&quot;fetchOrder&quot;);cy.get(&quot;[data-cy='submit']&quot;).click();cy.wait(&quot;@fetchOrder&quot;);</code></pre><p>Let's understand what the above example is trying to achieve line by line.</p><p><code>cy.intercept(&quot;/orders/\*&quot;).as(&quot;fetchOrder&quot;)</code>: Sets up a network interception.It intercepts any network request that matches the pattern <code>/orders/</code> and givesit a unique alias <code>fetchOrder</code>. This allows us to capture and control thenetwork request for further testing.</p><p><code>cy.get(&quot;[data-cy='submit']&quot;).click()</code>: Locates an HTML element with theattribute <code>data-cy</code> set to <code>submit</code> and simulates a click on it.</p><p><code>cy.wait(&quot;@fetchOrder&quot;)</code>: Instructs Cypress to wait until the interceptednetwork request with the alias <code>fetchOrder</code> is completed before proceeding withthe test.</p><p>The <code>cy.wait()</code> command involves two distinct phases of waiting.</p><p><strong>Phase 1:</strong> The command waits for a matching request to be sent from thebrowser. In the provided example, the wait command pauses execution until arequest with the URL pattern <code>/orders/</code> is initiated by the browser. Thiswaiting period continues until a matching request is found. If the command failsto identify such a request within the configured request timeout, a timeouterror message is triggered. Upon successfully detecting the matching request,the second phase kicks in.</p><p><strong>Phase 2:</strong> In this phase, the command waits until the server responds. If theanticipated response fails to arrive within the configured response timeout, atimeout error is thrown. In the above example, the wait command in this phasewill wait for the response of the request aliased as <code>fetchOrders</code>.</p><p>The dual-layered waiting mechanism, as explained above, significantly contributesto the reliability of tests. It ensures a synchronized interaction between UIactions and server responses, facilitating more robust and dependable testscenarios.</p><h3>Managing multiple responses</h3><p>Consider a situation where a user adds a product to the cart thus initiating twoconcurrent requests. The first request adds the product to the cart, while thesecond request fetches the updated list of orders. To ensure the synchronizationof these asynchronous actions, we must wait for both requests to be successfullycompleted before continuing with the test execution.</p><p>Cypress provides the <code>times</code> property in the <code>cy.intercept()</code> options, offeringcontrol over how many times a request with a particular pattern should beintercepted.</p><pre><code class="language-javascript">cy.intercept({ url: &quot;/orders/*&quot;, times: 2 }).as(&quot;fetchOrders&quot;);cy.get(&quot;[data-cy='submit']&quot;).click();cy.wait([&quot;@fetchOrders&quot;, &quot;@fetchOrders&quot;]);</code></pre><p>Let's decode the above example line by line.</p><p><code>cy.intercept({ url: &quot;/orders/\*&quot;, times: 2 }).as(&quot;fetchOrders&quot;)</code>: Specifiesthat the interception should match requests with a pattern <code>/orders/</code> and limitthe interception to exactly two occurrences.</p><p><code>cy.get(&quot;[data-cy='submit']&quot;).click()</code>: Locates an HTML element with theattribute <code>data-cy</code> set to <code>submit</code> and simulates a click on it.</p><p><code>cy.wait([&quot;@fetchOrders&quot;, &quot;@fetchOrders&quot;])</code>: Ensures that the test waits untilthe two intercepted requests with the alias <code>fetchOrders</code> are completed beforemoving on to the next steps.</p><h2>Taming Flakiness in Playwright</h2><p>Playwright offers page methods like <code>waitForRequest</code> and <code>waitForResponse</code> toaddress synchronization challenges between UI actions and API responses. Boththese methods return a promise which is resolved when an API with a matchingpattern is found and throws an error if it exceeds the configured timeout.</p><p>Let's consider the same example of an online shopping application where a neworder is created when we click the submit button.</p><pre><code class="language-javascript">await page.getByRole(&quot;button&quot;, { name: &quot;Submit&quot; }).click();await page.waitForResponse(response =&gt; response.url().includes(&quot;/orders/&quot;));</code></pre><p>In the above example, <code>page.waitForResponse</code> waits for a network response thatmatches with the URL pattern <code>/orders/</code> after clicking the submit button.</p><p>Even though the above example seems simple, there is a chance for flakinesshere. That is because the API might respond before Playwright starts waiting forit. It might happen for two reasons:</p><ol><li>API is very fast.</li><li>External factors delay the test script.</li></ol><p>Such situations could lead to timeouts and test failures.</p><p>To address the above issue, it's important to coordinate the promises so thatthe <code>waitForResponse</code> command runs at the same time as UI actions. The followingexample illustrates this approach.</p><pre><code class="language-javascript">const fetchOrder = page.waitForResponse(response =&gt;  response.url().includes(&quot;/orders/&quot;));await page.getByRole(&quot;button&quot;, { name: &quot;Submit&quot; }).click();await fetchOrder;</code></pre><p>In the above example, the page starts watching for the responses matching thespecific URL pattern, <code>/orders/</code>, before clicking the submit button. The<code>waitForResponse</code> command returns a promise, which we have saved into thevariable <code>fetchOrder</code>. After performing the click action in the following line,we wait for the promise stored in <code>fetchOrder</code> to resolve. When it resolves, itsignifies that the response has been received. This enables us to move on to thenext assertion without facing any reliability issues.</p><h3>Managing Multiple Responses</h3><p>Let's consider a scenario similar to the one explained in Cypress, where we haveto manage multiple responses, one to add a product and another to fetch theupdated list of products.</p><p>To wait for the completion of 2 requests from the same URL pattern, consider thefollowing approach.</p><pre><code class="language-javascript">const fetchOrders = Promise.all(  [...new Array(2)].map(    page.waitForResponse(response =&gt; response.url().includes(&quot;/orders/&quot;))  ));await page.getByRole(&quot;button&quot;, { name: &quot;Submit&quot; }).click();await fetchOrders;</code></pre><p>In the above example, we start waiting for two responses with the pattern<code>/orders/</code> using <code>Promise.all</code>. The flaw in the above code is that when both the<code>waitForResponse</code> methods run in parallel, and they end up tracking the exact sameAPI request. In simpler terms, it's like waiting for just one request, as bothof them wait for the completion of the same API.</p><p>To solve the above problem, it's important to improve the code by keeping trackof the resolved APIs. Let's see how to achieve the same.</p><pre><code class="language-javascript">const trackedResponses = [];const fetchOrders = Promise.all(  [...new Array(2)].map(() =&gt;    page.waitForResponse(response =&gt; {      const requestId = response.headers()?.[&quot;x-request-id&quot;];      if (        response.url().includes(&quot;/orders/&quot;) &amp;&amp;        !trackedResponses.includes(requestId)      ) {        trackedResponses.push(requestId);        return true;      }      return false;    })  ));await page.getByRole(&quot;button&quot;, { name: &quot;Submit&quot; }).click();await fetchOrders;</code></pre><p>In the above example, we have initialized a new variable <code>trackedResponses</code> withan empty array, intended to store unique identifiers (request IDs) of resolvedAPIs. It checks if the URL includes the substring <code>/orders/</code> and also whetherthe request ID has not already been tracked in <code>trackedResponses</code> array. If bothconditions are satisfied, it adds the request ID to <code>trackedResponses</code> array andreturns <code>true</code>, indicating that we should wait for the response. This approachprevents the monitoring of the same response more than once.</p><h2>Conclusion</h2><p>By understanding and implementing these synchronization techniques in Cypressand Playwright, we can significantly enhance the robustness and reliability ofend-to-end tests, ultimately contributing to a more stable and trustworthytesting suite.</p><h2>References</h2><p><a href="https://docs.cypress.io/api/commands/intercept">cy.intercept</a></p><p><a href="https://docs.cypress.io/api/commands/wait">cy.wait</a></p><p><a href="https://playwright.dev/docs/api/class-page#page-wait-for-request">page.waitForRequest</a></p><p><a href="https://playwright.dev/docs/api/class-page#page-wait-for-response">page.waitForResponse</a></p>]]></content>
    </entry>
     </feed>