playwright-core
Version:
A high-level API to automate web browsers
221 lines • 9.54 kB
JavaScript
"use strict";
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DispatcherConnection = exports.Dispatcher = exports.lookupNullableDispatcher = exports.existingDispatcher = exports.lookupDispatcher = exports.dispatcherSymbol = void 0;
const events_1 = require("events");
const serializers_1 = require("../protocol/serializers");
const validator_1 = require("../protocol/validator");
const utils_1 = require("../utils/utils");
const validatorPrimitives_1 = require("../protocol/validatorPrimitives");
const errors_1 = require("../utils/errors");
const instrumentation_1 = require("../server/instrumentation");
const stackTrace_1 = require("../utils/stackTrace");
exports.dispatcherSymbol = Symbol('dispatcher');
function lookupDispatcher(object) {
const result = object[exports.dispatcherSymbol];
utils_1.debugAssert(result);
return result;
}
exports.lookupDispatcher = lookupDispatcher;
function existingDispatcher(object) {
return object[exports.dispatcherSymbol];
}
exports.existingDispatcher = existingDispatcher;
function lookupNullableDispatcher(object) {
return object ? lookupDispatcher(object) : undefined;
}
exports.lookupNullableDispatcher = lookupNullableDispatcher;
class Dispatcher extends events_1.EventEmitter {
constructor(parent, object, type, initializer, isScope, guid = type + '@' + utils_1.createGuid()) {
super();
// Only "isScope" channel owners have registered dispatchers inside.
this._dispatchers = new Map();
this._disposed = false;
this._connection = parent instanceof DispatcherConnection ? parent : parent._connection;
this._isScope = !!isScope;
this._parent = parent instanceof DispatcherConnection ? undefined : parent;
this._scope = isScope ? this : this._parent;
utils_1.assert(!this._connection._dispatchers.has(guid));
this._connection._dispatchers.set(guid, this);
if (this._parent) {
utils_1.assert(!this._parent._dispatchers.has(guid));
this._parent._dispatchers.set(guid, this);
}
this._type = type;
this._guid = guid;
this._object = object;
object[exports.dispatcherSymbol] = this;
if (this._parent)
this._connection.sendMessageToClient(this._parent._guid, '__create__', { type, initializer, guid });
}
_dispatchEvent(method, params = {}) {
if (this._disposed) {
if (utils_1.isUnderTest())
throw new Error(`${this._guid} is sending "${method}" event after being disposed`);
// Just ignore this event outside of tests.
return;
}
this._connection.sendMessageToClient(this._guid, method, params);
}
_dispose() {
utils_1.assert(!this._disposed);
// Clean up from parent and connection.
if (this._parent)
this._parent._dispatchers.delete(this._guid);
this._connection._dispatchers.delete(this._guid);
// Dispose all children.
for (const dispatcher of [...this._dispatchers.values()])
dispatcher._dispose();
this._dispatchers.clear();
if (this._isScope)
this._connection.sendMessageToClient(this._guid, '__dispose__', {});
}
_debugScopeState() {
return {
_guid: this._guid,
objects: Array.from(this._dispatchers.values()).map(o => o._debugScopeState()),
};
}
async waitForEventInfo() {
// Instrumentation takes care of this.
}
}
exports.Dispatcher = Dispatcher;
class Root extends Dispatcher {
constructor(connection) {
super(connection, {}, '', {}, true, '');
}
}
class DispatcherConnection {
constructor() {
this._dispatchers = new Map();
this.onmessage = (message) => { };
this._rootDispatcher = new Root(this);
const tChannel = (name) => {
return (arg, path) => {
if (arg && typeof arg === 'object' && typeof arg.guid === 'string') {
const guid = arg.guid;
const dispatcher = this._dispatchers.get(guid);
if (!dispatcher)
throw new validator_1.ValidationError(`${path}: no object with guid ${guid}`);
if (name !== '*' && dispatcher._type !== name)
throw new validator_1.ValidationError(`${path}: object with guid ${guid} has type ${dispatcher._type}, expected ${name}`);
return dispatcher;
}
throw new validator_1.ValidationError(`${path}: expected ${name}`);
};
};
const scheme = validator_1.createScheme(tChannel);
this._validateParams = (type, method, params) => {
if (method === 'waitForEventInfo')
return validatorPrimitives_1.tOptional(scheme['WaitForEventInfo'])(params.info, '');
const name = type + method[0].toUpperCase() + method.substring(1) + 'Params';
if (!scheme[name])
throw new validator_1.ValidationError(`Unknown scheme for ${type}.${method}`);
return scheme[name](params, '');
};
this._validateMetadata = (metadata) => {
return validatorPrimitives_1.tOptional(scheme['Metadata'])(metadata, '');
};
}
sendMessageToClient(guid, method, params) {
this.onmessage({ guid, method, params: this._replaceDispatchersWithGuids(params) });
}
rootDispatcher() {
return this._rootDispatcher;
}
async dispatch(message) {
const { id, guid, method, params, metadata } = message;
const dispatcher = this._dispatchers.get(guid);
if (!dispatcher) {
this.onmessage({ id, error: serializers_1.serializeError(new Error(errors_1.kBrowserOrContextClosedError)) });
return;
}
if (method === 'debugScopeState') {
this.onmessage({ id, result: this._rootDispatcher._debugScopeState() });
return;
}
let validParams;
let validMetadata;
try {
validParams = this._validateParams(dispatcher._type, method, params);
validMetadata = this._validateMetadata(metadata);
if (typeof dispatcher[method] !== 'function')
throw new Error(`Mismatching dispatcher: "${dispatcher._type}" does not implement "${method}"`);
}
catch (e) {
this.onmessage({ id, error: serializers_1.serializeError(e) });
return;
}
const callMetadata = {
id,
...validMetadata,
startTime: utils_1.monotonicTime(),
endTime: 0,
type: dispatcher._type,
method,
params,
log: [],
};
const sdkObject = dispatcher._object instanceof instrumentation_1.SdkObject ? dispatcher._object : undefined;
try {
if (sdkObject)
await sdkObject.instrumentation.onBeforeCall(sdkObject, callMetadata);
const result = await dispatcher[method](validParams, callMetadata);
this.onmessage({ id, result: this._replaceDispatchersWithGuids(result) });
}
catch (e) {
// Dispatching error
callMetadata.error = e.message;
if (callMetadata.log.length)
stackTrace_1.rewriteErrorMessage(e, e.message + formatLogRecording(callMetadata.log) + kLoggingNote);
this.onmessage({ id, error: serializers_1.serializeError(e) });
}
finally {
callMetadata.endTime = utils_1.monotonicTime();
if (sdkObject)
await sdkObject.instrumentation.onAfterCall(sdkObject, callMetadata);
}
}
_replaceDispatchersWithGuids(payload) {
if (!payload)
return payload;
if (payload instanceof Dispatcher)
return { guid: payload._guid };
if (Array.isArray(payload))
return payload.map(p => this._replaceDispatchersWithGuids(p));
if (typeof payload === 'object') {
const result = {};
for (const key of Object.keys(payload))
result[key] = this._replaceDispatchersWithGuids(payload[key]);
return result;
}
return payload;
}
}
exports.DispatcherConnection = DispatcherConnection;
const kLoggingNote = `\nNote: use DEBUG=pw:api environment variable to capture Playwright logs.`;
function formatLogRecording(log) {
if (!log.length)
return '';
const header = ` logs `;
const headerLength = 60;
const leftLength = (headerLength - header.length) / 2;
const rightLength = headerLength - header.length - leftLength;
return `\n${'='.repeat(leftLength)}${header}${'='.repeat(rightLength)}\n${log.join('\n')}\n${'='.repeat(headerLength)}`;
}
//# sourceMappingURL=dispatcher.js.map