<?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-04T11:29:15+00:00</updated>
     <id>https://www.bigbinary.com/</id>
     <entry>
       <title><![CDATA[Fixing the Cypress out of memory error in Chromium]]></title>
       <author><name>S Varun</name></author>
      <link href="https://www.bigbinary.com/blog/how-we-fixed-the-cypress-out-of-memory-error-in-chromium-browsers"/>
      <updated>2024-05-21T12:00:00+00:00</updated>
      <id>https://www.bigbinary.com/blog/how-we-fixed-the-cypress-out-of-memory-error-in-chromium-browsers</id>
      <content type="html"><![CDATA[<p>At BigBinary, we use <a href="https://www.cypress.io/">Cypress</a> as our primaryend-to-end testing framework because of its simplicity and compatibility. Wehave 400+ tests across multiple products, most of which are long-running testshandling complex workflows. We use the most commonly used version of<a href="https://www.chromium.org/Home/">Chromium</a> as the test browser to make sure thetests capture how the majority uses our products. While the development hasalways been smooth sailing, the same cannot be said about the test runs. As thenumber of tests and the duration for each of them increased, our tests wouldrandomly crash with the following error:</p><pre><code>We detected that the Chromium Renderer process just crashed.This is the equivalent of seeing the 'sad face' when Chrome dies.This can happen for a number of different reasons:- You wrote an endless loop and you must fix your own code- You are running Docker (there is an easy fix for this: see link below)- You are running lots of tests on a memory intense application.    - Try enabling experimentalMemoryManagement in your config file.    - Try lowering numTestsKeptInMemory in your config file.- You are running in a memory starved VM environment.    - Try enabling experimentalMemoryManagement in your config file.    - Try lowering numTestsKeptInMemory in your config file.- There are problems with your GPU / GPU drivers- There are browser bugs in ChromiumYou can learn more, including how to fix Docker here:https://on.cypress.io/renderer-process-crashed</code></pre><p>The occurrence of the crashes was rare initially. But as our test suitesexpanded the crash frequency increased as well. The crashes were so frequent ata point in time that none of our tests would run to completion. Neither thesolutions mentioned in the official documentation nor the suggestions in thecommunity discussions (like enabling experimentalMemoryManagement) wereeffective. This led us to investigate this problem.</p><h2>About our CI setup</h2><p>We used to run Cypress on<a href="https://circleci.com/docs/configuration-reference/#docker-execution-environment">CircleCI</a>on a medium Docker resource class. This resource class allocates 4GB of memoryto the process. Later on, we moved to our home-grown CI solution,<a href="https://www.neeto.com/neetoci/">NeetoCI</a> for running our Cypress tests whichgave us much more control over the test environment.</p><h2>The investigation setup</h2><p>Since the errors were caused because Cypress ran out of memory, we started bylooking into the resource utilization on the VM environment. We noticed thatnone of the crashed runs used more than 50% of the allotted memory. The memorystarvation while using only a portion of the allocated resources, meant thatCypress was not utilizing the full memory.</p><p>We couldn't reproduce this issue reliably, so we attempted to simulate the errorby creating a high memory usage scenario. For the simulation, we created a dummytest that takes the following steps.</p><ol><li>Visit a page.</li><li>Get an element.</li><li>Save the element in the memory as a new alias.</li><li>Repeat steps 1-3 infinitely until the browser crashes.</li><li>During each iteration, log the iteration number to know how many iterationswere completed successfully before the crash.</li></ol><p>For logging the iteration number, we used the<a href="https://docs.cypress.io/api/commands/task">Cypress task - log</a> is illustratedin the official documentation. The iteration number provided us with anadditional metric to compare the performance of the solutions we tried. The codefor implementing the investigation setup can be seen below.</p><pre><code class="language-javascript">const saveButtonAsAlias = iteration =&gt; {  cy.get(&quot;.button&quot;).as(`button-${iteration}`);  saveButtonAsAlias(iteration + 1);  cy.task(&quot;log&quot;, iteration);};it(&quot;dummy test&quot;, () =&gt; {  cy.visit(&quot;/&quot;);  saveButtonAsAlias(1);});</code></pre><p>The above code will save the same button component as different aliases in thememory thus simulating a high memory usage test environment. On executing thistest we saw that the memory usage peaked at about 1GB - 1.5GB in a 4GB dockerenvironment before the browser crashed.</p><h2>Solutions</h2><h3>1. Using an alternate browser</h3><p>Even though <a href="https://www.google.com/chrome/">Google Chrome</a> is the most popularbrowser in the market, it's far from being the most memory-efficient. So wetested out with other chromium-based browsers available for Cypress andconcluded that <a href="https://www.microsoft.com/en-us/edge">Microsoft Edge</a> ran thetests in a much more memory-efficient manner. While running the dummy test, weobserved the memory usage by each of the browsers and compared the results.</p><p>Google Chrome ran the tests faster and crashed first when memory was starved.Microsoft Edge ran the tests at a similar pace initially, but when the memorywas almost used up completely, the tests slowed dow,n and the browser startedrigorous garbage collection. The memory usage was increasing at a gradual rateand more iterations were completed successfully, as compared to Chrome, beforethe browser crashed. The table below shows the runtime comparison between GoogleChrome and Microsoft Edge (higher runtime is better).</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Attempt &lt;/td&gt;&lt;td&gt; Google Chrome runtime before crash &lt;/td&gt;&lt;td&gt; Microsoft Edge runtime before crash &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0:45&lt;/td&gt;&lt;td&gt;0:59&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0:46&lt;/td&gt;&lt;td&gt;1:00&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0:45&lt;/td&gt;&lt;td&gt;1:01&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><p>While switching the browser improved the completion rate of the runs, it stilldidn't solve the issue completely. This led us to look for further enhancements.</p><h3>2. Increasing the max-old-space-size</h3><p>The most unusual behaviour we noticed in resource utilization was that Cypressdid not use the entire allocated memory before crashing. To understand whyCypress behaves like this we need to have a basic understanding of itsarchitecture which can be seen below.</p><p><img src="/blog/images/images_used_in_blog/2024/how-we-fixed-the-cypress-out-of-memory-error-in-chromium-browsers/cypress-architecture.png" alt="Cypress architecture"></p><p>Cypress works as two different processes. The NodeJS application and the browseron which the tests run. When executing <code>cypress run</code> and <code>cypress open</code>commands, we start the NodeJS application. This NodeJS application goes throughour tests and configuration and loads them into our preferred browser where theyare executed.</p><p>The split architecture of Cypress means that the memory allocation for theNodeJS process and the Chromium browser are different. This is why the totalmemory usage by the NodeJS process doesn't give us proper insights into why theChromium process crashed and was starved of memory. To analyze the browsermemory usage we used the browser<a href="https://developer.mozilla.org/en-US/docs/Web/API/Performance/memory">Performance APIs</a>.</p><p>We found that the Cypress tests were allocated only about 500MB of memorydespite the test environment having 4GB of memory. So the solution was toincrease the heap memory allocated to the chromium renderer. The<a href="https://nodejs.org/api/cli.html#--max-old-space-sizesize-in-megabytes:~:text=are%20documented%20here%3A-,%2D%2Dmax%2Dold%2Dspace%2Dsize%3DSIZE,-(in%20megabytes)">max-old-space-size</a>command-line flag is used to set the V8 engine's maximum old memory limit. Whenthe memory usage approaches this limit, garbage collection begins in an effortto free up memory. So by manually increasing the <code>max-old-space-size</code> for thechromium renderer, we can increase the heap memory allocated to it.</p><p>If it were a node application, the process of increasing the<code>max-old-space-size</code> would be as simple as executing the Cypress commandlike-wise:</p><p><code>NODE_OPTIONS=--max-old-space-size=3500 yarn cypress run</code></p><p>But because of the split architecture, executing the above command onlyincreases the <code>max-old-space-size</code> for the NodeJS application and not the actualCypress tests running in the Chromium browser. To increase the<code>max-old-space-size</code> for the Chromium renderer we need to make use of the<a href="https://docs.cypress.io/api/plugins/browser-launch-api">Browser launch APIs</a>provided by Cypress.</p><pre><code class="language-javascript">// cypress.config.jsconst { defineConfig } = require(&quot;cypress&quot;);module.exports = defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      on(&quot;before:browser:launch&quot;, (browser = {}, launchOptions) =&gt; {        launchOptions.args.push(&quot;--js-flags=--max-old-space-size=3500&quot;);        return launchOptions;      });    },  },});</code></pre><p>In the configuration above, we can see that we have passed in the<code>--max-old-space-size</code> command line flag within the <code>--js-flags</code> Chromium flag.This is because Chromium expects NodeJS options using the <code>--js-flags</code> commandline switch. The above configuration increases the maximum usable heap size ofthe Cypress tests to 3500MB.</p><p>Depending on the available memory on the test environment, we can increase ordecrease the <code>max-old-space-size</code> value. The benchmarking results we receivedafter making this configuration change showed a significant improvement in theperformance. The table below documents the runtime comparison between thedefault <code>max-old-space-size</code> and <code>max-old-space-size</code> set to 3500 MB (higherruntime is better).</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Attempt &lt;/td&gt;&lt;td&gt;Runtime before crash with default &lt;code&gt;max-old-space-size&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Runtime before crash with &lt;code&gt;max-old-space-size=3500&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0:44&lt;/td&gt;&lt;td&gt;2:22&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0:45&lt;/td&gt;&lt;td&gt;2:20&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;&lt;td&gt;0:45&lt;/td&gt;&lt;td&gt;2:21&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><p>The benchmark above shows the improvement in performance after increasing the<code>max-old-space-size</code> in the Google Chrome browser. By switching the browser toMicrosoft Edge we got even better results. The table below shows the runtimecomparison between the default <code>max-old-space-size</code> and <code>max-old-space-size</code> setto 3500 MB in each of these browsers (higher runtime is better).</p><p>&lt;table&gt;&lt;tr&gt;&lt;td&gt; Attempt &lt;/td&gt;&lt;td colspan=&quot;2&quot;&gt;Runtime before crash with default &lt;code&gt;max-old-space-size&lt;/code&gt;&lt;/td&gt;&lt;td colspan=&quot;2&quot;&gt;Runtime before crash with &lt;code&gt;max-old-space-size=3500&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Google Chrome&lt;/td&gt;&lt;td&gt;Microsoft Edge&lt;/td&gt;&lt;td&gt;Google Chrome&lt;/td&gt;&lt;td&gt;Microsoft Edge&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0:44&lt;/td&gt;&lt;td&gt;0:59&lt;/td&gt;&lt;td&gt;2:22&lt;/td&gt;&lt;td&gt;2:40&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;&lt;td&gt;0:45&lt;/td&gt;&lt;td&gt;1:00&lt;/td&gt;&lt;td&gt;2:20&lt;/td&gt;&lt;td&gt;2:38&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;&lt;td&gt;0:45&lt;/td&gt;&lt;td&gt;1:01&lt;/td&gt;&lt;td&gt;2:21&lt;/td&gt;&lt;td&gt;2:37&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</p><h2>Additional tips to reduce memory usage in Cypress</h2><ol><li><p>Chromium browsers sandbox the pages which increases the memory usage. Sincewe're running the Cypress tests on trusted sites, we can enable the<code>--no-sandbox</code> flag to reduce memory consumption.</p></li><li><p>When running Cypress tests in headless mode, we can disable the WebGLgraphics on the rendered pages to avoid additional memory usage by passingthe <code>--disable-gl-drawing-for-tests</code> flag.</p></li><li><p>When running tests on low-resource machines, using hardware acceleration canimpact performance. To avoid this we can pass the <code>--disable-gpu</code> flag.</p></li></ol><pre><code class="language-javascript">// cypress.config.jsconst { defineConfig } = require(&quot;cypress&quot;);module.exports = defineConfig({  // setupNodeEvents can be defined in either  // the e2e or component configuration  e2e: {    setupNodeEvents(on, config) {      on(&quot;before:browser:launch&quot;, (browser, launchOptions) =&gt; {        if ([&quot;chrome&quot;, &quot;edge&quot;].includes(browser.name)) {          if (browser.isHeadless) {            launchOptions.args.push(&quot;--no-sandbox&quot;);            launchOptions.args.push(&quot;--disable-gl-drawing-for-tests&quot;);            launchOptions.args.push(&quot;--disable-gpu&quot;);          }          launchOptions.args.push(&quot;--js-flags=--max-old-space-size=3500&quot;);        }        return launchOptions;      });    },  },});</code></pre><h2>Conclusion</h2><p>Since Cypress tests are executed inside the browser all the constraints of abrowser environment apply to them, including the memory constraints. The defaultconfigurations in the browsers are targeted to run on the most number ofsystems. When facing memory starvation issues during complex and long-runningtests, we should configure Cypress according to the resources available in theenvironment in which our tests are running to achieve peak performance.Increasing the available memory for the browser by manually setting anappropriate <code>max-old-space-size</code> value and choosing a memory-efficient browserwill make sure that Cypress will be able to run smoothly in most of thescenarios.</p><h2>References</h2><ul><li><a href="https://github.com/cypress-io/cypress/issues/24719">Chromium renderer crash GitHub issue</a></li><li><a href="https://www.memorymanagement.org/glossary/s.html">Memory management glossary</a></li><li><a href="https://support.circleci.com/hc/en-us/articles/360009208393-How-Can-I-Increase-the-Max-Memory-for-Node">Increasing node memory size</a></li><li><a href="https://github.com/cypress-io/cypress-docker-images/blob/master/included/10.0.0/Dockerfile">Cypress docker images</a></li><li><a href="https://peter.sh/experiments/chromium-command-line-switches">Chromium command-line switches</a></li><li><a href="https://docs.docker.com/config/containers/resource_constraints/">Docker resource constraints</a></li></ul>]]></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>