fix(trace-viewer): redirect, time and missing snapshot bugfixes (#10055)

This commit is contained in:
Pavel Feldman 2021-11-08 18:03:10 -08:00 committed by GitHub
parent 5c9dcffd67
commit 806b5706a5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 17 deletions

View File

@ -80,9 +80,10 @@ export class SnapshotRenderer {
if (!html)
return { html: '', pageId: snapshot.pageId, frameId: snapshot.frameId, index: this._index };
if (snapshot.doctype)
html = `<!DOCTYPE ${snapshot.doctype}>` + html;
html += `
// Hide the document in order to prevent flickering. We will unhide once script has processed shadow.
const hideAllStyle = '<style>*,*::before,*::after { visibility: hidden }</style>';
const prefix = snapshot.doctype ? `<!DOCTYPE ${snapshot.doctype}>` + hideAllStyle : hideAllStyle;
html = prefix + html + `
<style>*[__playwright_target__="${this.snapshotName}"] { background-color: #6fa8dc7f; }</style>
<script>${snapshotScript()}</script>
`;
@ -240,6 +241,7 @@ function snapshotScript() {
pointElement.style.top = pointY + 'px';
document.documentElement.appendChild(pointElement);
}
document.styleSheets[0].disabled = true;
};
window.addEventListener('load', onLoad);
}

View File

@ -14,7 +14,6 @@
* limitations under the License.
*/
import type { ResourceSnapshot } from '../../server/trace/common/snapshotTypes';
import { SnapshotStorage } from './snapshotStorage';
import type { Point } from '../../common/types';
import { URLSearchParams } from 'url';
@ -44,7 +43,9 @@ export class SnapshotServer {
return this._respondWithJson(snapshot ? {
viewport: snapshot.viewport(),
url: snapshot.snapshot().frameUrl
} : {});
} : {
error: 'No snapshot found'
});
}
private _snapshot(pathname: string, params: URLSearchParams) {
@ -70,15 +71,7 @@ export class SnapshotServer {
return new Response(null, { status: 404 });
const sha1 = resource.response.content._sha1;
if (!sha1)
return new Response(null, { status: 404 });
return this._innerServeResource(sha1, resource);
}
private async _innerServeResource(sha1: string, resource: ResourceSnapshot): Promise<Response> {
const content = await this._snapshotStorage.resourceContent(sha1);
if (!content)
return new Response(null, { status: 404 });
const content = sha1 ? await this._snapshotStorage.resourceContent(sha1) || new Blob([]) : new Blob([]);
let contentType = resource.response.content.mimeType;
const isTextEncoding = /^text\/|^application\/(javascript|json)/.test(contentType);
@ -95,7 +88,11 @@ export class SnapshotServer {
headers.delete('Content-Length');
headers.set('Content-Length', String(content.size));
headers.set('Cache-Control', 'public, max-age=31536000');
return new Response(content, { headers });
return new Response(content, {
headers,
status: resource.response.status,
statusText: resource.response.statusText,
});
}
}

View File

@ -42,7 +42,7 @@ export const CallTab: React.FunctionComponent<{
<div className='call-line'>{action.metadata.apiName}</div>
{<>
<div className='call-section'>Time</div>
<div className='call-line'>wall time: <span className='datetime' title={wallTime}>{wallTime}</span></div>
{action.metadata.wallTime && <div className='call-line'>wall time: <span className='datetime' title={wallTime}>{wallTime}</span></div>}
<div className='call-line'>duration: <span className='datetime' title={duration}>{duration}</span></div>
</>}
{ !!paramKeys.length && <div className='call-section'>Parameters</div> }

View File

@ -63,7 +63,8 @@ export const SnapshotTab: React.FunctionComponent<{
if (snapshotInfoUrl) {
const response = await fetch(snapshotInfoUrl);
const info = await response.json();
setSnapshotInfo(info);
if (!info.error)
setSnapshotInfo(info);
}
if (!iframeRef.current)
return;

View File

@ -216,6 +216,7 @@ function distillSnapshot(snapshot, distillTarget = true) {
if (distillTarget)
html = html.replace(/\s__playwright_target__="[^"]+"/g, '');
return html
.replace(/<style>\*,\*::before,\*::after { visibility: hidden }<\/style>/, '')
.replace(/<script>[.\s\S]+<\/script>/, '')
.replace(/<style>.*__playwright_target__.*<\/style>/, '')
.replace(/<BASE href="about:blank">/, '')

View File

@ -14,6 +14,7 @@
* limitations under the License.
*/
import fs from 'fs';
import path from 'path';
import type { Browser, Frame, Locator, Page } from 'playwright-core';
import { showTraceViewer } from '../../packages/playwright-core/lib/server/trace/viewer/traceViewer';
@ -552,3 +553,29 @@ test('should show action source', async ({ showTraceViewer }) => {
await expect(page.locator('.source-line-running')).toContainText('page.click');
await expect(page.locator('.stack-trace-frame.selected')).toHaveText(/doClick.*trace-viewer\.spec\.ts:[\d]+/);
});
test('should follow redirects', async ({ page, runAndTrace, server, asset }) => {
server.setRoute('/empty.html', (req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`<div><img id=img src="image.png"></img></div>`);
});
server.setRoute('/image.png', (req, res) => {
res.writeHead(301, { location: '/image-301.png' });
res.end();
});
server.setRoute('/image-301.png', (req, res) => {
res.writeHead(302, { location: '/image-302.png' });
res.end();
});
server.setRoute('/image-302.png', (req, res) => {
res.writeHead(200, { 'content-type': 'image/png' });
res.end(fs.readFileSync(asset('digits/0.png')));
});
const traceViewer = await runAndTrace(async () => {
await page.goto(server.EMPTY_PAGE);
expect(await page.evaluate(() => (window as any).img.naturalWidth)).toBe(10);
});
const snapshotFrame = await traceViewer.snapshotFrame('page.evaluate');
await expect(snapshotFrame.locator('img')).toHaveJSProperty('naturalWidth', 10);
});