Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@
* limitations under the License.
*/

import { SnapshotStorage } from '../../../../../trace-viewer/src/sw/snapshotStorage';
import { SnapshotStorage } from '../../../utils/isomorphic/trace/snapshotStorage';
import { ManualPromise } from '../../../utils';
import { HarTracer } from '../../har/harTracer';
import { Snapshotter } from '../recorder/snapshotter';

import type { SnapshotRenderer } from '../../../../../trace-viewer/src/sw/snapshotRenderer';
import type { SnapshotRenderer } from '../../../utils/isomorphic/trace/snapshotRenderer';
import type { BrowserContext } from '../../browserContext';
import type { HarTracerDelegate } from '../../har/harTracer';
import type { Page } from '../../page';
Expand Down
3 changes: 3 additions & 0 deletions packages/playwright-core/src/utils/isomorphic/trace/DEPS.list
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*]
./**
../**
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* limitations under the License.
*/

import type { Language } from '../../../playwright-core/src/utils/isomorphic/locatorGenerators';
import type { Language } from '../locatorGenerators';
import type { ResourceSnapshot } from '@trace/snapshot';
import type * as trace from '@trace/trace';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@
* limitations under the License.
*/

import { escapeHTMLAttribute, escapeHTML } from '@isomorphic/stringUtils';
import { escapeHTMLAttribute, escapeHTML } from '../stringUtils';

import type { FrameSnapshot, NodeNameAttributesChildNodesSnapshot, NodeSnapshot, RenderedFrameSnapshot, ResourceSnapshot, SubtreeReferenceSnapshot } from '@trace/snapshot';
import type { PageEntry } from '../types/entries';
import type { LRUCache } from './lruCache';
import type { PageEntry } from './entries';
import type { LRUCache } from '../lruCache';

function findClosest<T>(items: T[], metric: (v: T) => number, target: number) {
return items.find((item, index) => {
Expand Down Expand Up @@ -254,7 +254,9 @@ declare global {

function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefined)[]) {
function applyPlaywrightAttributes(viewport: ViewportSize, ...targetIds: (string | undefined)[]) {
const searchParams = new URLSearchParams(location.search);
// eslint-disable-next-line no-restricted-globals
const win = window;
const searchParams = new URLSearchParams(win.location.search);
const shouldPopulateCanvasFromScreenshot = searchParams.has('shouldPopulateCanvasFromScreenshot');
const isUnderTest = searchParams.has('isUnderTest');

Expand All @@ -268,7 +270,7 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
viewport,
frames: new WeakMap(),
};
window['__playwright_frame_bounding_rects__'] = frameBoundingRectsInfo;
win['__playwright_frame_bounding_rects__'] = frameBoundingRectsInfo;

const kPointerWarningTitle = 'Recorded click position in absolute coordinates did not' +
' match the center of the clicked element. This is likely due to a difference between' +
Expand All @@ -279,7 +281,7 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
const targetElements: Element[] = [];
const canvasElements: HTMLCanvasElement[] = [];

let topSnapshotWindow: Window = window;
let topSnapshotWindow: Window = win;
while (topSnapshotWindow !== topSnapshotWindow.parent && !topSnapshotWindow.location.pathname.match(/\/page@[a-z0-9]+$/))
topSnapshotWindow = topSnapshotWindow.parent;

Expand Down Expand Up @@ -342,7 +344,7 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
iframe.setAttribute('src', 'data:text/html,<body style="background: #ddd"></body>');
} else {
// Retain query parameters to inherit name=, time=, pointX=, pointY= and other values from parent.
const url = new URL(window.location.href);
const url = new URL(win.location.href);
// We can be loading iframe from within iframe, reset base to be absolute.
const index = url.pathname.lastIndexOf('/snapshot/');
if (index !== -1)
Expand All @@ -354,10 +356,10 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine

{
const body = root.querySelector(`body[__playwright_custom_elements__]`);
if (body && window.customElements) {
if (body && win.customElements) {
const customElements = (body.getAttribute('__playwright_custom_elements__') || '').split(',');
for (const elementName of customElements)
window.customElements.define(elementName, class extends HTMLElement {});
win.customElements.define(elementName, class extends HTMLElement {});
}
}

Expand Down Expand Up @@ -387,7 +389,7 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
};

const onLoad = () => {
window.removeEventListener('load', onLoad);
win.removeEventListener('load', onLoad);
for (const element of scrollTops) {
element.scrollTop = +element.getAttribute('__playwright_scroll_top_')!;
element.removeAttribute('__playwright_scroll_top_');
Expand All @@ -401,19 +403,19 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
frameBoundingRectsInfo.frames.get(element)!.scrollLeft = element.scrollTop;
}

document.styleSheets[0].disabled = true;
win.document.styleSheets[0].disabled = true;

const search = new URL(window.location.href).searchParams;
const isTopFrame = window === topSnapshotWindow;
const search = new URL(win.location.href).searchParams;
const isTopFrame = win === topSnapshotWindow;

if (search.get('pointX') && search.get('pointY')) {
const pointX = +search.get('pointX')!;
const pointY = +search.get('pointY')!;
const hasInputTarget = search.has('hasInputTarget');
const hasTargetElements = targetElements.length > 0;
const roots = document.documentElement ? [document.documentElement] : [];
const roots = win.document.documentElement ? [win.document.documentElement] : [];
for (const target of (hasTargetElements ? targetElements : roots)) {
const pointElement = document.createElement('x-pw-pointer');
const pointElement = win.document.createElement('x-pw-pointer');
pointElement.style.position = 'fixed';
pointElement.style.backgroundColor = '#f44336';
pointElement.style.width = '20px';
Expand All @@ -436,7 +438,7 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
// "Warning symbol" indicates that action point is not 100% correct.
// Note that action point is relative to the top frame, so we can only compare in the top frame.
if (isTopFrame && (Math.abs(centerX - pointX) >= 10 || Math.abs(centerY - pointY) >= 10)) {
const warningElement = document.createElement('x-pw-pointer-warning');
const warningElement = win.document.createElement('x-pw-pointer-warning');
warningElement.textContent = '⚠';
warningElement.style.fontSize = '19px';
warningElement.style.color = 'white';
Expand All @@ -445,21 +447,21 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
pointElement.appendChild(warningElement);
pointElement.setAttribute('title', kPointerWarningTitle);
}
document.documentElement.appendChild(pointElement);
win.document.documentElement.appendChild(pointElement);
} else if (isTopFrame && !hasInputTarget) {
// For actions without a target element, e.g. page.mouse.move(),
// show the point at the recorded location, which is relative to the top frame.
pointElement.style.left = pointX + 'px';
pointElement.style.top = pointY + 'px';
document.documentElement.appendChild(pointElement);
win.document.documentElement.appendChild(pointElement);
}
}
}

if (canvasElements.length > 0) {
function drawCheckerboard(context: CanvasRenderingContext2D, canvas: HTMLCanvasElement) {
function createCheckerboardPattern() {
const pattern = document.createElement('canvas');
const pattern = win.document.createElement('canvas');
pattern.width = pattern.width / Math.floor(pattern.width / 24);
pattern.height = pattern.height / Math.floor(pattern.height / 24);
const context = pattern.getContext('2d')!;
Expand Down Expand Up @@ -492,7 +494,7 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
continue;
}

let currWindow: Window = window;
let currWindow: Window = win;
while (currWindow !== topSnapshotWindow) {
const iframe = currWindow.frameElement!;
currWindow = currWindow.parent;
Expand Down Expand Up @@ -553,10 +555,10 @@ function snapshotScript(viewport: ViewportSize, ...targetIds: (string | undefine
}
};

const onDOMContentLoaded = () => visit(document);
const onDOMContentLoaded = () => visit(win.document);

window.addEventListener('load', onLoad);
window.addEventListener('DOMContentLoaded', onDOMContentLoaded);
win.addEventListener('load', onLoad);
win.addEventListener('DOMContentLoaded', onDOMContentLoaded);
}

return `\n(${applyPlaywrightAttributes.toString()})(${JSON.stringify(viewport)}${targetIds.map(id => `, "${id}"`).join('')})`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import type { SnapshotRenderer } from './snapshotRenderer';
import type { SnapshotStorage } from './snapshotStorage';
import type { ResourceSnapshot } from '@trace/snapshot';

type Point = { x: number, y: number };

export class SnapshotServer {
private _snapshotStorage: SnapshotStorage;
private _resourceLoader: (sha1: string) => Promise<Blob | undefined>;
Expand Down Expand Up @@ -117,12 +115,6 @@ export class SnapshotServer {
}
}

declare global {
interface Window {
showSnapshot: (url: string, point?: Point) => Promise<void>;
}
}

function removeHash(url: string) {
try {
const u = new URL(url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
*/

import { rewriteURLForCustomProtocol, SnapshotRenderer } from './snapshotRenderer';
import { LRUCache } from './lruCache';
import { LRUCache } from '../lruCache';

import type { FrameSnapshot, ResourceSnapshot } from '@trace/snapshot';
import type { PageEntry } from '../types/entries';
import type { PageEntry } from './entries';


export class SnapshotStorage {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import { parseClientSideCallMetadata } from '@isomorphic/traceUtils';
import { SnapshotStorage } from './snapshotStorage';
import { TraceModernizer } from './traceModernizer';

import type { ContextEntry } from '../types/entries';
import type { ContextEntry } from './entries';

export interface TraceModelBackend {
export interface TraceLoaderBackend {
entryNames(): Promise<string[]>;
hasEntry(entryName: string): Promise<boolean>;
readText(entryName: string): Promise<string | undefined>;
Expand All @@ -30,16 +30,16 @@ export interface TraceModelBackend {
traceURL(): string;
}

export class TraceModel {
export class TraceLoader {
contextEntries: ContextEntry[] = [];
private _snapshotStorage: SnapshotStorage | undefined;
private _backend!: TraceModelBackend;
private _backend!: TraceLoaderBackend;
private _resourceToContentType = new Map<string, string>();

constructor() {
}

async load(backend: TraceModelBackend, unzipProgress: (done: number, total: number) => void) {
async load(backend: TraceLoaderBackend, unzipProgress: (done: number, total: number) => void) {
this._backend = backend;

const ordinals: string[] = [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import type { Language } from '@isomorphic/locatorGenerators';
import type { ResourceSnapshot } from '@trace/snapshot';
import type * as trace from '@trace/trace';
import type { ActionTraceEvent } from '@trace/trace';
import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries';
import type { ActionEntry, ContextEntry, PageEntry } from '@isomorphic/trace/entries';
import type { StackFrame } from '@protocol/channels';
import type { ActionGroup } from '@isomorphic/protocolFormatter';

Expand Down Expand Up @@ -61,7 +61,7 @@ export type ErrorDescription = {

export type Attachment = trace.AfterActionTraceEventAttachment & { callId: string };

export class MultiTraceModel {
export class TraceModel {
readonly startTime: number;
readonly endTime: number;
readonly browserName: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type * as traceV5 from './versions/traceV5';
import type * as traceV6 from './versions/traceV6';
import type * as traceV7 from './versions/traceV7';
import type * as traceV8 from './versions/traceV8';
import type { ActionEntry, ContextEntry, PageEntry } from '../types/entries';
import type { ActionEntry, ContextEntry, PageEntry } from './entries';
import type { SnapshotStorage } from './snapshotStorage';

export class TraceVersionError extends Error {
Expand Down Expand Up @@ -299,6 +299,7 @@ export class TraceModernizer {
class: metadata.type,
method: metadata.method,
params: metadata.params,
// eslint-disable-next-line no-restricted-globals
wallTime: metadata.wallTime || Date.now(),
log: metadata.log,
beforeSnapshot: metadata.snapshots.find(s => s.title === 'before')?.snapshotName,
Expand Down
27 changes: 14 additions & 13 deletions packages/trace-viewer/src/sw/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@
* limitations under the License.
*/

import { SnapshotServer } from '@isomorphic/trace/snapshotServer';
import { TraceLoader } from '@isomorphic/trace/traceLoader';
import { TraceVersionError } from '@isomorphic/trace/traceModernizer';

import { Progress, splitProgress } from './progress';
import { SnapshotServer } from './snapshotServer';
import { TraceModel } from './traceModel';
import { FetchTraceModelBackend, traceFileURL, ZipTraceModelBackend } from './traceModelBackends';
import { TraceVersionError } from './traceModernizer';
import { FetchTraceLoaderBackend, traceFileURL, ZipTraceLoaderBackend } from './traceLoaderBackends';

type Client = {
id: string;
Expand Down Expand Up @@ -59,7 +60,7 @@ self.addEventListener('activate', function(event: any) {
});

type LoadedTrace = {
traceModel: TraceModel;
traceLoader: TraceLoader;
snapshotServer: SnapshotServer;
};

Expand Down Expand Up @@ -105,23 +106,23 @@ function loadTrace(clientId: string, url: URL, isContextRequest: boolean, progre
async function innerLoadTrace(traceUrl: string, progress: Progress): Promise<LoadedTrace> {
await gc();

const traceModel = new TraceModel();
const traceLoader = new TraceLoader();
try {
// Allow 10% to hop from sw to page.
const [fetchProgress, unzipProgress] = splitProgress(progress, [0.5, 0.4, 0.1]);
const backend = isLiveTrace(traceUrl) || traceUrl.endsWith('traces.dir') ? new FetchTraceModelBackend(traceUrl) : new ZipTraceModelBackend(traceUrl, fetchProgress);
await traceModel.load(backend, unzipProgress);
const backend = isLiveTrace(traceUrl) || traceUrl.endsWith('traces.dir') ? new FetchTraceLoaderBackend(traceUrl) : new ZipTraceLoaderBackend(traceUrl, fetchProgress);
await traceLoader.load(backend, unzipProgress);
} catch (error: any) {
// eslint-disable-next-line no-console
console.error(error);
if (error?.message?.includes('Cannot find .trace file') && await traceModel.hasEntry('index.html'))
if (error?.message?.includes('Cannot find .trace file') && await traceLoader.hasEntry('index.html'))
throw new Error('Could not load trace. Did you upload a Playwright HTML report instead? Make sure to extract the archive first and then double-click the index.html file or put it on a web server.');
if (error instanceof TraceVersionError)
throw new Error(`Could not load trace from ${traceUrl}. ${error.message}`);
throw new Error(`Could not load trace from ${traceUrl}. Make sure a valid Playwright Trace is accessible over this url.`);
}
const snapshotServer = new SnapshotServer(traceModel.storage(), sha1 => traceModel.resourceForSha1(sha1));
return { traceModel, snapshotServer };
const snapshotServer = new SnapshotServer(traceLoader.storage(), sha1 => traceLoader.resourceForSha1(sha1));
return { traceLoader, snapshotServer };
}

async function doFetch(event: FetchEvent): Promise<Response> {
Expand Down Expand Up @@ -204,7 +205,7 @@ async function doFetch(event: FetchEvent): Promise<Response> {
return errorResponse;

if (relativePath === '/contexts') {
return new Response(JSON.stringify(loadedTrace!.traceModel.contextEntries), {
return new Response(JSON.stringify(loadedTrace!.traceLoader.contextEntries), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
Expand All @@ -221,7 +222,7 @@ async function doFetch(event: FetchEvent): Promise<Response> {
}

if (relativePath.startsWith('/sha1/')) {
const blob = await loadedTrace!.traceModel.resourceForSha1(relativePath.slice('/sha1/'.length));
const blob = await loadedTrace!.traceLoader.resourceForSha1(relativePath.slice('/sha1/'.length));
if (blob)
return new Response(blob, { status: 200, headers: downloadHeaders(url.searchParams) });
return new Response(null, { status: 404 });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
import * as zipImport from '@zip.js/zip.js/lib/zip-no-worker-inflate.js';

import type * as zip from '@zip.js/zip.js';
import type { TraceModelBackend } from './traceModel';
import type { TraceLoaderBackend } from '@isomorphic/trace/traceLoader';

const zipjs = zipImport as typeof zip;

type Progress = (done: number, total: number) => undefined;

export class ZipTraceModelBackend implements TraceModelBackend {
export class ZipTraceLoaderBackend implements TraceLoaderBackend {
private _zipReader: zip.ZipReader<unknown>;
private _entriesPromise: Promise<Map<string, zip.Entry>>;
private _traceURL: string;
Expand Down Expand Up @@ -94,7 +94,7 @@ export class ZipTraceModelBackend implements TraceModelBackend {
}
}

export class FetchTraceModelBackend implements TraceModelBackend {
export class FetchTraceLoaderBackend implements TraceLoaderBackend {
private _entriesPromise: Promise<Map<string, string>>;
private _path: string;

Expand Down
Loading
Loading