playwright-core
Version:
A high-level API to automate web browsers
366 lines (365 loc) • 13.4 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var traceModel_exports = {};
__export(traceModel_exports, {
TraceModel: () => TraceModel,
buildActionTree: () => buildActionTree,
context: () => context,
eventsForAction: () => eventsForAction,
nextActionByStartTime: () => nextActionByStartTime,
previousActionByEndTime: () => previousActionByEndTime,
stats: () => stats
});
module.exports = __toCommonJS(traceModel_exports);
var import_protocolFormatter = require("@isomorphic/protocolFormatter");
const contextSymbol = Symbol("context");
const nextInContextSymbol = Symbol("nextInContext");
const prevByEndTimeSymbol = Symbol("prevByEndTime");
const nextByStartTimeSymbol = Symbol("nextByStartTime");
const eventsSymbol = Symbol("events");
class TraceModel {
constructor(traceUri, contexts) {
contexts.forEach((contextEntry) => indexModel(contextEntry));
const libraryContext = contexts.find((context2) => context2.origin === "library");
this.traceUri = traceUri;
this.browserName = libraryContext?.browserName || "";
this.sdkLanguage = libraryContext?.sdkLanguage;
this.channel = libraryContext?.channel;
this.testIdAttributeName = libraryContext?.testIdAttributeName;
this.platform = libraryContext?.platform || "";
this.playwrightVersion = contexts.find((c) => c.playwrightVersion)?.playwrightVersion;
this.title = libraryContext?.title || "";
this.options = libraryContext?.options || {};
this.actions = mergeActionsAndUpdateTiming(contexts);
this.pages = [].concat(...contexts.map((c) => c.pages));
this.wallTime = contexts.map((c) => c.wallTime).reduce((prev, cur) => Math.min(prev || Number.MAX_VALUE, cur), Number.MAX_VALUE);
this.startTime = contexts.map((c) => c.startTime).reduce((prev, cur) => Math.min(prev, cur), Number.MAX_VALUE);
this.endTime = contexts.map((c) => c.endTime).reduce((prev, cur) => Math.max(prev, cur), Number.MIN_VALUE);
this.events = [].concat(...contexts.map((c) => c.events));
this.stdio = [].concat(...contexts.map((c) => c.stdio));
this.errors = [].concat(...contexts.map((c) => c.errors));
this.hasSource = contexts.some((c) => c.hasSource);
this.hasStepData = contexts.some((context2) => context2.origin === "testRunner");
this.resources = [...contexts.map((c) => c.resources)].flat();
this.attachments = this.actions.flatMap((action) => action.attachments?.map((attachment) => ({ ...attachment, callId: action.callId, traceUri })) ?? []);
this.visibleAttachments = this.attachments.filter((attachment) => !attachment.name.startsWith("_"));
this.events.sort((a1, a2) => a1.time - a2.time);
this.resources.sort((a1, a2) => a1._monotonicTime - a2._monotonicTime);
this.errorDescriptors = this.hasStepData ? this._errorDescriptorsFromTestRunner() : this._errorDescriptorsFromActions();
this.sources = collectSources(this.actions, this.errorDescriptors);
this.actionCounters = /* @__PURE__ */ new Map();
for (const action of this.actions) {
action.group = action.group ?? (0, import_protocolFormatter.getActionGroup)({ type: action.class, method: action.method });
if (action.group)
this.actionCounters.set(action.group, 1 + (this.actionCounters.get(action.group) || 0));
}
}
createRelativeUrl(path) {
const url = new URL("http://localhost/" + path);
url.searchParams.set("trace", this.traceUri);
return url.toString().substring("http://localhost/".length);
}
failedAction() {
return this.actions.findLast((a) => a.error);
}
filteredActions(actionsFilter) {
const filter = new Set(actionsFilter);
return this.actions.filter((action) => !action.group || filter.has(action.group));
}
renderActionTree(filter) {
const actions = this.filteredActions(filter ?? []);
const { rootItem } = buildActionTree(actions);
const actionTree = [];
const visit = (actionItem, indent) => {
const title = (0, import_protocolFormatter.renderTitleForCall)({ ...actionItem.action, type: actionItem.action.class });
actionTree.push(`${indent}${title || actionItem.id}`);
for (const child of actionItem.children)
visit(child, indent + " ");
};
rootItem.children.forEach((a) => visit(a, ""));
return actionTree;
}
_errorDescriptorsFromActions() {
const errors = [];
for (const action of this.actions || []) {
if (!action.error?.message)
continue;
errors.push({
action,
stack: action.stack,
message: action.error.message
});
}
return errors;
}
_errorDescriptorsFromTestRunner() {
return this.errors.filter((e) => !!e.message).map((error, i) => ({
stack: error.stack,
message: error.message
}));
}
}
function indexModel(context2) {
for (const page of context2.pages)
page[contextSymbol] = context2;
for (let i = 0; i < context2.actions.length; ++i) {
const action = context2.actions[i];
action[contextSymbol] = context2;
}
let lastNonRouteAction = void 0;
for (let i = context2.actions.length - 1; i >= 0; i--) {
const action = context2.actions[i];
action[nextInContextSymbol] = lastNonRouteAction;
if (action.class !== "Route")
lastNonRouteAction = action;
}
for (const event of context2.events)
event[contextSymbol] = context2;
for (const resource of context2.resources)
resource[contextSymbol] = context2;
}
function mergeActionsAndUpdateTiming(contexts) {
const result = [];
const actions = mergeActionsAndUpdateTimingSameTrace(contexts);
result.push(...actions);
result.sort((a1, a2) => {
if (a2.parentId === a1.callId)
return 1;
if (a1.parentId === a2.callId)
return -1;
return a1.endTime - a2.endTime;
});
for (let i = 1; i < result.length; ++i)
result[i][prevByEndTimeSymbol] = result[i - 1];
result.sort((a1, a2) => {
if (a2.parentId === a1.callId)
return -1;
if (a1.parentId === a2.callId)
return 1;
return a1.startTime - a2.startTime;
});
for (let i = 0; i + 1 < result.length; ++i)
result[i][nextByStartTimeSymbol] = result[i + 1];
return result;
}
let lastTmpStepId = 0;
function mergeActionsAndUpdateTimingSameTrace(contexts) {
const map = /* @__PURE__ */ new Map();
const libraryContexts = contexts.filter((context2) => context2.origin === "library");
const testRunnerContexts = contexts.filter((context2) => context2.origin === "testRunner");
if (!testRunnerContexts.length || !libraryContexts.length) {
return contexts.map((context2) => {
return context2.actions.map((action) => ({ ...action, context: context2 }));
}).flat();
}
for (const context2 of libraryContexts) {
for (const action of context2.actions) {
map.set(action.stepId || `tmp-step@${++lastTmpStepId}`, { ...action, context: context2 });
}
}
const delta = monotonicTimeDeltaBetweenLibraryAndRunner(testRunnerContexts, map);
if (delta)
adjustMonotonicTime(libraryContexts, delta);
const nonPrimaryIdToPrimaryId = /* @__PURE__ */ new Map();
for (const context2 of testRunnerContexts) {
for (const action of context2.actions) {
const existing = action.stepId && map.get(action.stepId);
if (existing) {
nonPrimaryIdToPrimaryId.set(action.callId, existing.callId);
if (action.error)
existing.error = action.error;
if (action.attachments)
existing.attachments = action.attachments;
if (action.annotations)
existing.annotations = action.annotations;
if (action.parentId)
existing.parentId = nonPrimaryIdToPrimaryId.get(action.parentId) ?? action.parentId;
if (action.group)
existing.group = action.group;
existing.startTime = action.startTime;
existing.endTime = action.endTime;
continue;
}
if (action.parentId)
action.parentId = nonPrimaryIdToPrimaryId.get(action.parentId) ?? action.parentId;
map.set(action.stepId || `tmp-step@${++lastTmpStepId}`, { ...action, context: context2 });
}
}
return [...map.values()];
}
function adjustMonotonicTime(contexts, monotonicTimeDelta) {
for (const context2 of contexts) {
context2.startTime += monotonicTimeDelta;
context2.endTime += monotonicTimeDelta;
for (const action of context2.actions) {
if (action.startTime)
action.startTime += monotonicTimeDelta;
if (action.endTime)
action.endTime += monotonicTimeDelta;
}
for (const event of context2.events)
event.time += monotonicTimeDelta;
for (const event of context2.stdio)
event.timestamp += monotonicTimeDelta;
for (const page of context2.pages) {
for (const frame of page.screencastFrames)
frame.timestamp += monotonicTimeDelta;
}
for (const resource of context2.resources) {
if (resource._monotonicTime)
resource._monotonicTime += monotonicTimeDelta;
}
}
}
function monotonicTimeDeltaBetweenLibraryAndRunner(nonPrimaryContexts, libraryActions) {
for (const context2 of nonPrimaryContexts) {
for (const action of context2.actions) {
if (!action.startTime)
continue;
const libraryAction = action.stepId ? libraryActions.get(action.stepId) : void 0;
if (libraryAction)
return action.startTime - libraryAction.startTime;
}
}
return 0;
}
function buildActionTree(actions) {
const itemMap = /* @__PURE__ */ new Map();
for (const action of actions) {
itemMap.set(action.callId, {
id: action.callId,
parent: void 0,
children: [],
action
});
}
const rootItem = { action: { ...kFakeRootAction }, id: "", parent: void 0, children: [] };
for (const item of itemMap.values()) {
rootItem.action.startTime = Math.min(rootItem.action.startTime, item.action.startTime);
rootItem.action.endTime = Math.max(rootItem.action.endTime, item.action.endTime);
const parent = item.action.parentId ? itemMap.get(item.action.parentId) || rootItem : rootItem;
parent.children.push(item);
item.parent = parent;
}
const inheritStack = (item) => {
for (const child of item.children) {
child.action.stack = child.action.stack ?? item.action.stack;
inheritStack(child);
}
};
inheritStack(rootItem);
return { rootItem, itemMap };
}
function context(action) {
return action[contextSymbol];
}
function nextInContext(action) {
return action[nextInContextSymbol];
}
function previousActionByEndTime(action) {
return action[prevByEndTimeSymbol];
}
function nextActionByStartTime(action) {
return action[nextByStartTimeSymbol];
}
function stats(action) {
let errors = 0;
let warnings = 0;
for (const event of eventsForAction(action)) {
if (event.type === "console") {
const type = event.messageType;
if (type === "warning")
++warnings;
else if (type === "error")
++errors;
}
if (event.type === "event" && event.method === "pageError")
++errors;
}
return { errors, warnings };
}
function eventsForAction(action) {
let result = action[eventsSymbol];
if (result)
return result;
const nextAction = nextInContext(action);
result = context(action).events.filter((event) => {
return event.time >= action.startTime && (!nextAction || event.time < nextAction.startTime);
});
action[eventsSymbol] = result;
return result;
}
function collectSources(actions, errorDescriptors) {
const result = /* @__PURE__ */ new Map();
for (const action of actions) {
for (const frame of action.stack || []) {
let source = result.get(frame.file);
if (!source) {
source = { errors: [], content: void 0 };
result.set(frame.file, source);
}
}
}
for (const error of errorDescriptors) {
const { action, stack, message } = error;
if (!action || !stack)
continue;
result.get(stack[0].file)?.errors.push({
line: stack[0].line || 0,
message
});
}
return result;
}
const kFakeRootAction = {
type: "action",
callId: "",
startTime: 0,
endTime: 0,
class: "",
method: "",
params: {},
log: [],
context: {
origin: "library",
startTime: 0,
endTime: 0,
browserName: "",
wallTime: 0,
options: {},
pages: [],
resources: [],
actions: [],
events: [],
stdio: [],
errors: [],
hasSource: false,
contextId: ""
}
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
TraceModel,
buildActionTree,
context,
eventsForAction,
nextActionByStartTime,
previousActionByEndTime,
stats
});