Version
1.59.1
Steps to reproduce
1)Сreate test:
// repro.spec.ts
import http from 'node:http';
import { expect, test } from '@playwright/test';
test.use({ trace: 'on' }); // or 'retain-on-failure' — it records during every test
test('tracing duplicates the pixel request', async ({ page }) => {
const hits: string[] = [];
const server = http.createServer((req, res) => {
if (req.url!.includes('pixel.gif')) {
hits.push(req.headers['user-agent'] ?? '');
res.setHeader('content-type', 'image/gif');
res.end(Buffer.from('R0lGODlhAQABAAAAACw=', 'base64')); // 1x1 gif
} else {
res.setHeader('content-type', 'text/html');
res.end('<html><body><img src="/pixel.gif"></body></html>');
}
});
await new Promise<void>(r => server.listen(4321, '127.0.0.1', r));
await page.goto('http://127.0.0.1:4321/');
await page.waitForTimeout(3000);
server.close();
console.log(hits); // 2 entries; the 2nd one has the raw HeadlessChrome UA
expect(hits).toHaveLength(1); // FAILS: received 2
});
2)Run: npx playwright test repro.spec.ts --project=chromium
Expected behavior
The server receives exactly one request to pixel.gif. Playwright should not silently repeat requests: a GET is not guaranteed to be side-effect free (tracking pixels, signed one-time URLs, rate-limited APIs). If the body cannot be recovered, an empty body in the trace is the safer outcome — or at least the re-download should be opt-in/opt-out.
Actual behavior
The server receives two requests with Chromium and trace ON
With trace: 'off' — one request.
Firefox and WebKit — always one request, with any settings.
Additional context
Description
When tracing is enabled on Chromium, Playwright may issue a real second HTTP request for a resource the page has already loaded. If the resource is a tracking/analytics pixel, the server counts the event twice.
Chain of events:
The page loads an image (e.g. an analytics pixel
).
The tracing HAR recorder calls response.body() for every response (content: "attach" is hard-coded in tracing.js).
In Chromium, Network.getResponseBody often returns an empty body for images (the renderer does not retain the bytes).
crNetworkManager.js falls back to Network.loadNetworkResource, which downloads the URL again through the browser network stack.
The extra request:
- is a real network request that reaches the server
-is sent with the raw HeadlessChrome User-Agent, even when the context has a UA override (e.g. devices['Desktop Chrome']) — emulation does not apply to it;
- has sec-fetch-dest: empty and no referer;
- bypasses page.route() / context.route();
- is invisible in page.on('request') and in the trace itself, which makes this very hard to debug.
Environment
System:
OS: macOS 14.6.1
CPU: (11) arm64 Apple M3 Pro
Binaries:
Node: 18.20.3 - ~/.nvm/versions/node/v18.20.3/bin/node
Yarn: 1.22.22 - /opt/homebrew/bin/yarn
npm: 10.8.1 - ~/.nvm/versions/node/v18.20.3/bin/npm
npmPackages:
@playwright/test: ^1.59.1 => 1.59.1
Version
1.59.1
Steps to reproduce
1)Сreate test:
2)Run: npx playwright test repro.spec.ts --project=chromium
Expected behavior
The server receives exactly one request to pixel.gif. Playwright should not silently repeat requests: a GET is not guaranteed to be side-effect free (tracking pixels, signed one-time URLs, rate-limited APIs). If the body cannot be recovered, an empty body in the trace is the safer outcome — or at least the re-download should be opt-in/opt-out.
Actual behavior
The server receives two requests with Chromium and trace ON
With trace: 'off' — one request.
Firefox and WebKit — always one request, with any settings.
Additional context
Description
When tracing is enabled on Chromium, Playwright may issue a real second HTTP request for a resource the page has already loaded. If the resource is a tracking/analytics pixel, the server counts the event twice.
Chain of events:
The page loads an image (e.g. an analytics pixel
).
The tracing HAR recorder calls response.body() for every response (content: "attach" is hard-coded in tracing.js).
In Chromium, Network.getResponseBody often returns an empty body for images (the renderer does not retain the bytes).
crNetworkManager.js falls back to Network.loadNetworkResource, which downloads the URL again through the browser network stack.
The extra request:
-is sent with the raw HeadlessChrome User-Agent, even when the context has a UA override (e.g. devices['Desktop Chrome']) — emulation does not apply to it;
Environment
System: OS: macOS 14.6.1 CPU: (11) arm64 Apple M3 Pro Binaries: Node: 18.20.3 - ~/.nvm/versions/node/v18.20.3/bin/node Yarn: 1.22.22 - /opt/homebrew/bin/yarn npm: 10.8.1 - ~/.nvm/versions/node/v18.20.3/bin/npm npmPackages: @playwright/test: ^1.59.1 => 1.59.1