<p dir="auto">Currently <code class="notranslate">page.route(url, handler, options)</code>/<code class="notranslate">browserContext.route(url, handler, options)</code> intercept routes based on url alone. It would be great if we have the ability to intercept based on any request properties like <code class="notranslate">method</code>, <code class="notranslate">header</code>, etc.</p>
<h2 dir="auto">Use case</h2>
<p dir="auto">A use case would be the common REST scenario where we want to intercept both <code class="notranslate">GET /items</code> and <code class="notranslate">POST /items</code>.</p>
<p dir="auto">Very simplified example:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="test('items', async ({ page }) =&gt; {
  // Intercept GET /api/items
  await page.route('/api/items', route =&gt; route.fulfill({ body: JSON.stringify([{ id: 1, name: 'Item 1' }]) }))
  
  // Intercept POST /api/items
  await page.route('/api/items', route =&gt; route.fulfill({ body: JSON.stringify({ id: 2, name: 'Item 2' }) }))
  
  await page.goto('/items')
  //  expect &quot;Item 1&quot; to be in the list
  
  await page.click('role=button[name=&quot;New item&quot;]')
  // expect &quot;Item 2&quot; to be created in the list
})"><pre class="notranslate"><span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s">'items'</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">{</span> page <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-c">// Intercept GET /api/items</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">route</span><span class="pl-kos">(</span><span class="pl-s">'/api/items'</span><span class="pl-kos">,</span> <span class="pl-s1">route</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-en">fulfill</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">body</span>: <span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">stringify</span><span class="pl-kos">(</span><span class="pl-kos">[</span><span class="pl-kos">{</span> <span class="pl-c1">id</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-c1">name</span>: <span class="pl-s">'Item 1'</span> <span class="pl-kos">}</span><span class="pl-kos">]</span><span class="pl-kos">)</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
  
  <span class="pl-c">// Intercept POST /api/items</span>
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">route</span><span class="pl-kos">(</span><span class="pl-s">'/api/items'</span><span class="pl-kos">,</span> <span class="pl-s1">route</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-en">fulfill</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">body</span>: <span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">stringify</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">id</span>: <span class="pl-c1">2</span><span class="pl-kos">,</span> <span class="pl-c1">name</span>: <span class="pl-s">'Item 2'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span> <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span>
  
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">goto</span><span class="pl-kos">(</span><span class="pl-s">'/items'</span><span class="pl-kos">)</span>
  <span class="pl-c">//  expect "Item 1" to be in the list</span>
  
  <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">click</span><span class="pl-kos">(</span><span class="pl-s">'role=button[name="New item"]'</span><span class="pl-kos">)</span>
  <span class="pl-c">// expect "Item 2" to be created in the list</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">The "matcher"(<code class="notranslate">url&lt;string|RegExp|function(URL):boolean&gt;</code>) doesn't allow for any matching on the request itself - only the request url.<br>
The "response"(<code class="notranslate">handler&lt;function(Route, Request)&gt;</code>) doesn't allow us to "skip" to the next interceptor - as the comment says in the source code <code class="notranslate">// There is no chaining, first handler wins.</code>.</p>
<h2 dir="auto">Existing workarounds</h2>
<ol dir="auto">
<li>Add single interceptor with conditional handler:</li>
</ol>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="await page.route('/api/items', (route, request) =&gt; { 
  if (request.method() === 'GET') {
    return route.fulfill({ body: JSON.stringify([{ id: 1, name: 'Item 1' }]) }) 
  } 
  
  if (request.method() === 'POST') {
    return route.fulfill({ body: JSON.stringify({ id: 2, name: 'Item 2' })
  } 
  
  return route.continue()
})"><pre class="notranslate"><span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">route</span><span class="pl-kos">(</span><span class="pl-s">'/api/items'</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-s1">route</span><span class="pl-kos">,</span> <span class="pl-s1">request</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span> 
  <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">request</span><span class="pl-kos">.</span><span class="pl-en">method</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">===</span> <span class="pl-s">'GET'</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-en">fulfill</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">body</span>: <span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">stringify</span><span class="pl-kos">(</span><span class="pl-kos">[</span><span class="pl-kos">{</span> <span class="pl-c1">id</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-c1">name</span>: <span class="pl-s">'Item 1'</span> <span class="pl-kos">}</span><span class="pl-kos">]</span><span class="pl-kos">)</span> <span class="pl-kos">}</span><span class="pl-kos">)</span> 
  <span class="pl-kos">}</span> 
  
  <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">request</span><span class="pl-kos">.</span><span class="pl-en">method</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">===</span> <span class="pl-s">'POST'</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-en">fulfill</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">body</span>: <span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">stringify</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">id</span>: <span class="pl-c1">2</span><span class="pl-kos">,</span> <span class="pl-c1">name</span>: <span class="pl-s">'Item 2'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span>
  <span class="pl-kos">}</span> 
  
  <span class="pl-s1">return</span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-en">continue</span><span class="pl-kos">(</span><span class="pl-kos">)</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">While this is enough in some cases, it's not very atomic and reusable. My idea is to make these a bit more generic and use them in fixtures - making sure that i can easily target just the request i need. Also, the idea of having the matching logic in the handler instead of the matcher just seems wrong.</p>
<ol start="2" dir="auto">
<li>Use <code class="notranslate">options.times</code> and order the interceptors based on their runtime execution. This seems very fragile and really not ideal.</li>
</ol>
<h2 dir="auto">Proposal</h2>
<p dir="auto">Even though the contribution guide says "Expose as little information as needed. When in doubt, don’t expose new information.", i feel like we should have an escape hatch in the "matcher" function. Two options:</p>
<ul dir="auto">
<li>Non-breaking change: adding the request as a second parameter to the url function:<br>
<code class="notranslate">url&lt;string|RegExp|function(URL, request):boolean&gt;</code><br>
Not the best API but that's the price of non-breaking i guess.</li>
<li>Breaking change: replacing the URL param with the request param in the url function:<br>
<code class="notranslate">url&lt;string|RegExp|function(request):boolean&gt;</code><br>
This seems like a very robust and flexible way of route matching but it does break BC.</li>
</ul>
<p dir="auto">It's a bit unfortunate that we can't reuse the standard glob/regex url matching when we use the function.<br>
This is how i imagine it could look like:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="await page.route(
  (url, request) =&gt; request.method() === 'GET' &amp;&amp; minimatch('/api/items', url.toSting()), 
  route =&gt; route.fulfill({ body: JSON.stringify([{ id: 1, name: 'Item 1' }]) }) 
)

await page.route(
  (url, request) =&gt; request.method() === 'POST' &amp;&amp; minimatch('/api/items', url.toSting()), 
  route =&gt; route.fulfill({ body: JSON.stringify({ id: 2, name: 'Item 2' })
)"><pre class="notranslate"><span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">route</span><span class="pl-kos">(</span>
  <span class="pl-kos">(</span><span class="pl-s1">url</span><span class="pl-kos">,</span> <span class="pl-s1">request</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">request</span><span class="pl-kos">.</span><span class="pl-en">method</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">===</span> <span class="pl-s">'GET'</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-en">minimatch</span><span class="pl-kos">(</span><span class="pl-s">'/api/items'</span><span class="pl-kos">,</span> <span class="pl-s1">url</span><span class="pl-kos">.</span><span class="pl-en">toSting</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">,</span> 
  <span class="pl-s1">route</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-en">fulfill</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">body</span>: <span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">stringify</span><span class="pl-kos">(</span><span class="pl-kos">[</span><span class="pl-kos">{</span> <span class="pl-c1">id</span>: <span class="pl-c1">1</span><span class="pl-kos">,</span> <span class="pl-c1">name</span>: <span class="pl-s">'Item 1'</span> <span class="pl-kos">}</span><span class="pl-kos">]</span><span class="pl-kos">)</span> <span class="pl-kos">}</span><span class="pl-kos">)</span> 
<span class="pl-kos">)</span>

<span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-c1">route</span><span class="pl-kos">(</span>
  <span class="pl-kos">(</span><span class="pl-s1">url</span><span class="pl-kos">,</span> <span class="pl-s1">request</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">request</span><span class="pl-kos">.</span><span class="pl-en">method</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">===</span> <span class="pl-s">'POST'</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-en">minimatch</span><span class="pl-kos">(</span><span class="pl-s">'/api/items'</span><span class="pl-kos">,</span> <span class="pl-s1">url</span><span class="pl-kos">.</span><span class="pl-en">toSting</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">,</span> 
  <span class="pl-s1">route</span> <span class="pl-c1">=&gt;</span> <span class="pl-s1">route</span><span class="pl-kos">.</span><span class="pl-c1">fulfill</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">body</span>: <span class="pl-c1">JSON</span><span class="pl-kos">.</span><span class="pl-en">stringify</span><span class="pl-kos">(</span><span class="pl-kos">{</span> <span class="pl-c1">id</span>: <span class="pl-c1">2</span><span class="pl-kos">,</span> <span class="pl-c1">name</span>: <span class="pl-s">'Item 2'</span> <span class="pl-kos">}</span><span class="pl-kos">)</span>
<span class="pl-kos">)</span></pre></div>
<p dir="auto">I think that having a convenient way to match by method(in addition to url) would be helpful in a lot of situations.<br>
What do you guys think? Do you have other APIs in mind to achieve this or do you think the current workaround methods are good enough?</p>
<p dir="auto">I'm seeing weird, intermittent behaviors happen while running tests in the Webkit browser only, and only when it runs inside of the official Docker container... not on my Mac. It appears that some headers (like "Date" and "Expires") get broken into 2 pieces somehow, and then there's the occasional cancelling of requests. Please see the screenshots below.</p>
<p dir="auto">Something to note: The <code class="notranslate">invalid HTTP headers</code> message in red shows next to a request that looks fine... It's the one above the message that looks janky, as you can see from the headers.</p>
<p dir="auto">While I suspect a bug, I'm certainly open to the idea that I could be doing something the wrong way if these request/response "interruptions" can be clearly explained. I've definitely not found much online from a simple Google search.</p>
<h3 dir="auto">System info</h3>
<ul dir="auto">
<li>Playwright Version: v1.34.3</li>
<li>Operating Systems:
<ul dir="auto">
<li>Locally: MacOS Ventura (M1)</li>
<li>In CI or local Docker: mcr.microsoft.com/playwright:v1.34.3-focal</li>
</ul>
</li>
<li>Browser: Webkit</li>
<li>Other info:</li>
</ul>
<h3 dir="auto">Source code</h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox"> I provided exact source code that allows reproducing the issue locally.</li>
</ul>




<p dir="auto"><strong>Link to the GitHub repository with the repro</strong><br>
<em>Prioprietary</em></p>
<p dir="auto">or</p>
<p dir="auto"><strong>Config file</strong><br>
<em>Proprietary</em></p>
<p dir="auto"><strong>Test file (self-contained)</strong></p>
<p dir="auto">I can't share the test, because it's proprietary, but I've seen the issue from my screenshot in <code class="notranslate">waitForRequest</code> and <code class="notranslate">waitForUrl</code>, when the parameter is a callback function. That may not have anything to do with it, but I don't know.</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="    await test.step(`assert that a service request is made with a token`, async () =&gt; {
      const tokenedRequest = await page.waitForRequest(
        (request) =&gt; {
          const url = new URL(request.url());
          const headers = request.headers();
          return url.pathname.includes('/content/') &amp;&amp; /Bearer \S{16}/.test(headers?.authorization);
        },
        {
          timeout: 30000,
        },
      );
      expect(tokenedRequest).toBeTruthy();
    });"><pre class="notranslate">    <span class="pl-k">await</span> <span class="pl-s1">test</span><span class="pl-kos">.</span><span class="pl-en">step</span><span class="pl-kos">(</span><span class="pl-s">`assert that a service request is made with a token`</span><span class="pl-kos">,</span> <span class="pl-k">async</span> <span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
      <span class="pl-k">const</span> <span class="pl-s1">tokenedRequest</span> <span class="pl-c1">=</span> <span class="pl-k">await</span> <span class="pl-s1">page</span><span class="pl-kos">.</span><span class="pl-en">waitForRequest</span><span class="pl-kos">(</span>
        <span class="pl-kos">(</span><span class="pl-s1">request</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
          <span class="pl-k">const</span> <span class="pl-s1">url</span> <span class="pl-c1">=</span> <span class="pl-k">new</span> <span class="pl-c1">URL</span><span class="pl-kos">(</span><span class="pl-s1">request</span><span class="pl-kos">.</span><span class="pl-en">url</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
          <span class="pl-k">const</span> <span class="pl-s1">headers</span> <span class="pl-c1">=</span> <span class="pl-s1">request</span><span class="pl-kos">.</span><span class="pl-en">headers</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
          <span class="pl-k">return</span> <span class="pl-s1">url</span><span class="pl-kos">.</span><span class="pl-c1">pathname</span><span class="pl-kos">.</span><span class="pl-en">includes</span><span class="pl-kos">(</span><span class="pl-s">'/content/'</span><span class="pl-kos">)</span> <span class="pl-c1">&amp;&amp;</span> <span class="pl-pds"><span class="pl-c1">/</span>Bearer <span class="pl-cce">\S</span><span class="pl-kos">{</span>16<span class="pl-kos">}</span><span class="pl-c1">/</span></span><span class="pl-kos">.</span><span class="pl-en">test</span><span class="pl-kos">(</span><span class="pl-s1">headers</span><span class="pl-kos">?.</span><span class="pl-c1">authorization</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
        <span class="pl-kos">}</span><span class="pl-kos">,</span>
        <span class="pl-kos">{</span>
          <span class="pl-c1">timeout</span>: <span class="pl-c1">30000</span><span class="pl-kos">,</span>
        <span class="pl-kos">}</span><span class="pl-kos">,</span>
      <span class="pl-kos">)</span><span class="pl-kos">;</span>
      <span class="pl-en">expect</span><span class="pl-kos">(</span><span class="pl-s1">tokenedRequest</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">toBeTruthy</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto"><strong>Steps</strong><br>
<em>Proprietary</em></p>
<p dir="auto"><strong>Expected</strong><br>
I can't give too many details, as it's proprietary, but this is testing Azure/B2C OAuth.</p>
<p dir="auto"><strong>Actual</strong></p>
<p dir="auto">Sometimes does weird stuff to HTTP headers:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer" href="https://user-images.githubusercontent.com/77355440/244570452-c74c4aa5-8a93-4599-892b-b4032529439a.png"><img src="https://user-images.githubusercontent.com/77355440/244570452-c74c4aa5-8a93-4599-892b-b4032529439a.png" alt="image" style="max-width: 100%;"></a></p>
<p dir="auto">Sometimes cancels requests:</p>
<p dir="auto"><a target="_blank" rel="noopener noreferrer" href="https://user-images.githubusercontent.com/77355440/244570775-f9c5c3ad-f9eb-4f5a-b3c2-3baa31bbdaaf.png"><img src="https://user-images.githubusercontent.com/77355440/244570775-f9c5c3ad-f9eb-4f5a-b3c2-3baa31bbdaaf.png" alt="image" style="max-width: 100%;"></a></p>