Skip to content

Commit 74ec8c2

Browse files
authored
cherry-pick(#24145): fix(snapshots): match resources by method (#24147)
Fixes #24144. Previously, we only matched by url, which confuses GET and HEAD requests where the latter is usually zero-sized. Also make sure that resources are sorted by their monotonicTime, since that's not always the case in the trace file, where they are sorted by the "response body retrieved" time.
1 parent 1e8e8b4 commit 74ec8c2

File tree

8 files changed

+43
-12
lines changed

8 files changed

+43
-12
lines changed

packages/playwright-core/src/server/har/harTracer.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,12 @@ export class HarTracer {
437437
const pageEntry = this._createPageEntryIfNeeded(page);
438438
const request = response.request();
439439

440+
// Prefer "response received" time over "request sent" time
441+
// for the purpose of matching requests that were used in a particular snapshot.
442+
// Note that both snapshot time and request time are taken here in the Node process.
443+
if (this._options.includeTraceInfo)
444+
harEntry._monotonicTime = monotonicTime();
445+
440446
harEntry.response = {
441447
status: response.status(),
442448
statusText: response.statusText(),

packages/trace-viewer/src/snapshotRenderer.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -112,17 +112,19 @@ export class SnapshotRenderer {
112112
return { html, pageId: snapshot.pageId, frameId: snapshot.frameId, index: this._index };
113113
}
114114

115-
resourceByUrl(url: string): ResourceSnapshot | undefined {
115+
resourceByUrl(url: string, method: string): ResourceSnapshot | undefined {
116116
const snapshot = this._snapshot;
117117
let result: ResourceSnapshot | undefined;
118118

119119
// First try locating exact resource belonging to this frame.
120120
for (const resource of this._resources) {
121+
// Only use resources that received response before the snapshot.
122+
// Note that both snapshot time and request time are taken in the same Node process.
121123
if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
122124
break;
123125
if (resource._frameref !== snapshot.frameId)
124126
continue;
125-
if (resource.request.url === url) {
127+
if (resource.request.url === url && resource.request.method === method) {
126128
// Pick the last resource with matching url - most likely it was used
127129
// at the time of snapshot, not the earlier aborted resource with the same url.
128130
result = resource;
@@ -132,17 +134,19 @@ export class SnapshotRenderer {
132134
if (!result) {
133135
// Then fall back to resource with this URL to account for memory cache.
134136
for (const resource of this._resources) {
137+
// Only use resources that received response before the snapshot.
138+
// Note that both snapshot time and request time are taken in the same Node process.
135139
if (typeof resource._monotonicTime === 'number' && resource._monotonicTime >= snapshot.timestamp)
136140
break;
137-
if (resource.request.url === url) {
141+
if (resource.request.url === url && resource.request.method === method) {
138142
// Pick the last resource with matching url - most likely it was used
139143
// at the time of snapshot, not the earlier aborted resource with the same url.
140144
result = resource;
141145
}
142146
}
143147
}
144148

145-
if (result) {
149+
if (result && method.toUpperCase() === 'GET') {
146150
// Patch override if necessary.
147151
for (const o of snapshot.resourceOverrides) {
148152
if (url === o.url && o.sha1) {

packages/trace-viewer/src/snapshotServer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,11 @@ export class SnapshotServer {
6565
});
6666
}
6767

68-
async serveResource(requestUrlAlternatives: string[], snapshotUrl: string): Promise<Response> {
68+
async serveResource(requestUrlAlternatives: string[], method: string, snapshotUrl: string): Promise<Response> {
6969
let resource: ResourceSnapshot | undefined;
7070
const snapshot = this._snapshotIds.get(snapshotUrl)!;
7171
for (const requestUrl of requestUrlAlternatives) {
72-
resource = snapshot?.resourceByUrl(removeHash(requestUrl));
72+
resource = snapshot?.resourceByUrl(removeHash(requestUrl), method);
7373
if (resource)
7474
break;
7575
}

packages/trace-viewer/src/snapshotStorage.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,9 @@ export class SnapshotStorage {
5656
snapshotsForTest() {
5757
return [...this._frameSnapshots.keys()];
5858
}
59+
60+
finalize() {
61+
// Resources are not necessarily sorted in the trace file, so sort them now.
62+
this._resources.sort((a, b) => (a._monotonicTime || 0) - (b._monotonicTime || 0));
63+
}
5964
}

packages/trace-viewer/src/sw.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ async function doFetch(event: FetchEvent): Promise<Response> {
142142
const lookupUrls = [request.url];
143143
if (isDeployedAsHttps && request.url.startsWith('https://'))
144144
lookupUrls.push(request.url.replace(/^https/, 'http'));
145-
return snapshotServer.serveResource(lookupUrls, snapshotUrl);
145+
return snapshotServer.serveResource(lookupUrls, request.method, snapshotUrl);
146146
}
147147

148148
async function gc() {

packages/trace-viewer/src/traceModel.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export class TraceModel {
100100

101101
this.contextEntries.push(contextEntry);
102102
}
103+
104+
this._snapshotStorage!.finalize();
103105
}
104106

105107
async hasEntry(filename: string): Promise<boolean> {

tests/library/snapshotter.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ it.describe('snapshots', () => {
5151
});
5252
await page.setContent('<link rel="stylesheet" href="style.css"><button>Hello</button>');
5353
const snapshot = await snapshotter.captureSnapshot(toImpl(page), 'call@1', 'snapshot@call@1');
54-
const resource = snapshot.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
54+
const resource = snapshot.resourceByUrl(`http://localhost:${server.PORT}/style.css`, 'GET');
5555
expect(resource).toBeTruthy();
5656
});
5757

@@ -124,7 +124,7 @@ it.describe('snapshots', () => {
124124

125125
await page.evaluate(() => { (document.styleSheets[0].cssRules[0] as any).style.color = 'blue'; });
126126
const snapshot2 = await snapshotter.captureSnapshot(toImpl(page), 'call@2', 'snapshot@call@2');
127-
const resource = snapshot2.resourceByUrl(`http://localhost:${server.PORT}/style.css`);
127+
const resource = snapshot2.resourceByUrl(`http://localhost:${server.PORT}/style.css`, 'GET');
128128
expect((await snapshotter.resourceContentForTest(resource.response.content._sha1)).toString()).toBe('button { color: blue; }');
129129
});
130130

tests/library/trace-viewer.spec.ts

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ test('should open trace-1.31', async ({ showTraceViewer }) => {
851851
await expect(snapshot.locator('[__playwright_target__]')).toHaveText(['Submit']);
852852
});
853853

854-
test('should prefer later resource request', async ({ page, server, runAndTrace }) => {
854+
test('should prefer later resource request with the same method', async ({ page, server, runAndTrace }) => {
855855
const html = `
856856
<body>
857857
<script>
@@ -862,13 +862,22 @@ test('should prefer later resource request', async ({ page, server, runAndTrace
862862
863863
if (!window.location.href.includes('reloaded'))
864864
window.location.href = window.location.href + '?reloaded';
865+
else
866+
link.onload = () => fetch('style.css', { method: 'HEAD' });
865867
</script>
868+
<div>Hello</div>
866869
</body>
867870
`;
868871

869872
let reloadStartedCallback = () => {};
870873
const reloadStartedPromise = new Promise<void>(f => reloadStartedCallback = f);
871874
server.setRoute('/style.css', async (req, res) => {
875+
if (req.method === 'HEAD') {
876+
res.statusCode = 200;
877+
res.end('');
878+
return;
879+
}
880+
872881
// Make sure reload happens before style arrives.
873882
await reloadStartedPromise;
874883
res.end('body { background-color: rgb(123, 123, 123) }');
@@ -880,8 +889,13 @@ test('should prefer later resource request', async ({ page, server, runAndTrace
880889
});
881890

882891
const traceViewer = await runAndTrace(async () => {
892+
const headRequest = page.waitForRequest(req => req.url() === server.PREFIX + '/style.css' && req.method() === 'HEAD');
883893
await page.goto(server.PREFIX + '/index.html');
894+
await headRequest;
895+
await page.locator('div').click();
884896
});
885-
const frame = await traceViewer.snapshotFrame('page.goto');
886-
await expect(frame.locator('body')).toHaveCSS('background-color', 'rgb(123, 123, 123)');
897+
const frame1 = await traceViewer.snapshotFrame('page.goto');
898+
await expect(frame1.locator('body')).toHaveCSS('background-color', 'rgb(123, 123, 123)');
899+
const frame2 = await traceViewer.snapshotFrame('locator.click');
900+
await expect(frame2.locator('body')).toHaveCSS('background-color', 'rgb(123, 123, 123)');
887901
});

0 commit comments

Comments
 (0)