Socket
Socket
Sign inDemoInstall

playwright-core

Package Overview
Dependencies
Maintainers
2
Versions
4552
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

playwright-core - npm Package Compare versions

Comparing version 0.12.1 to 0.13.0-next.1587452068410

lib/download.js

5

download-browser.js

@@ -65,3 +65,6 @@ /**

}
await browserFetcher.downloadBrowser({...options, progress});
await browserFetcher.downloadBrowser({...options, progress}).catch(e => {
process.exitCode = 1;
throw e;
});
logPolitely(`${options.progressBarBrowserName} downloaded to ${options.downloadPath}`);

@@ -68,0 +71,0 @@ }

4

lib/api.js

@@ -24,4 +24,8 @@ "use strict";

exports.Dialog = dialog_1.Dialog;
var download_1 = require("./download");
exports.Download = download_1.Download;
var dom_1 = require("./dom");
exports.ElementHandle = dom_1.ElementHandle;
var fileChooser_1 = require("./fileChooser");
exports.FileChooser = fileChooser_1.FileChooser;
var errors_1 = require("./errors");

@@ -28,0 +32,0 @@ exports.TimeoutError = errors_1.TimeoutError;

@@ -18,9 +18,49 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
async function createPageInNewContext(browser, options) {
const context = await browser.newContext(options);
const page = await context.newPage();
page._ownedContext = context;
return page;
const events_1 = require("events");
const download_1 = require("./download");
const events_2 = require("./events");
class BrowserBase extends events_1.EventEmitter {
constructor(logger) {
super();
this._downloadsPath = '';
this._downloads = new Map();
this._ownedServer = null;
this._logger = logger;
}
async newPage(options) {
const context = await this.newContext(options);
const page = await context.newPage();
page._ownedContext = context;
return page;
}
_downloadCreated(page, uuid, url) {
const download = new download_1.Download(page, this._downloadsPath, uuid, url);
this._downloads.set(uuid, download);
}
_downloadFinished(uuid, error) {
const download = this._downloads.get(uuid);
if (!download)
return;
download._reportFinished(error);
this._downloads.delete(uuid);
}
async close() {
if (this._ownedServer) {
await this._ownedServer.close();
}
else {
await Promise.all(this.contexts().map(context => context.close()));
this._disconnect();
}
if (this.isConnected())
await new Promise(x => this.once(events_2.Events.Browser.Disconnected, x));
}
_isLogEnabled(log) {
return this._logger._isLogEnabled(log);
}
_log(log, message, ...args) {
return this._logger._log(log, message, ...args);
}
}
exports.createPageInNewContext = createPageInNewContext;
exports.BrowserBase = BrowserBase;
//# sourceMappingURL=browser.js.map

@@ -21,7 +21,8 @@ "use strict";

const network = require("./network");
const platform = require("./platform");
const timeoutSettings_1 = require("./timeoutSettings");
const events_1 = require("./events");
class BrowserContextBase extends platform.EventEmitter {
constructor(options) {
const extendedEventEmitter_1 = require("./extendedEventEmitter");
const logger_1 = require("./logger");
class BrowserContextBase extends extendedEventEmitter_1.ExtendedEventEmitter {
constructor(browserBase, options) {
super();

@@ -33,14 +34,26 @@ this._timeoutSettings = new timeoutSettings_1.TimeoutSettings();

this._permissions = new Map();
this._downloads = new Set();
this._browserBase = browserBase;
this._options = options;
this._logger = options.logger ? new logger_1.RootLogger(options.logger) : browserBase;
this._closePromise = new Promise(fulfill => this._closePromiseFulfill = fulfill);
}
_abortPromiseForEvent(event) {
return event === events_1.Events.BrowserContext.Close ? super._abortPromiseForEvent(event) : this._closePromise;
}
_computeDeadline(options) {
return this._timeoutSettings.computeDeadline(options);
}
_browserClosed() {
for (const page of this.pages())
page._didClose();
this._didCloseInternal();
this._didCloseInternal(true);
}
_didCloseInternal() {
async _didCloseInternal(omitDeleteDownloads = false) {
this._closed = true;
this.emit(events_1.Events.BrowserContext.Close);
this._closePromiseFulfill(new Error('Context closed'));
if (!omitDeleteDownloads)
await Promise.all([...this._downloads].map(d => d.delete()));
this._downloads.clear();
}

@@ -69,11 +82,8 @@ async grantPermissions(permissions, options) {

}
async waitForEvent(event, optionsOrPredicate) {
if (!optionsOrPredicate)
optionsOrPredicate = {};
if (typeof optionsOrPredicate === 'function')
optionsOrPredicate = { predicate: optionsOrPredicate };
const { timeout = this._timeoutSettings.timeout(), predicate = () => true } = optionsOrPredicate;
const abortPromise = (event === events_1.Events.BrowserContext.Close) ? new Promise(() => { }) : this._closePromise;
return helper_1.helper.waitForEvent(this, event, (...args) => !!predicate(...args), timeout, abortPromise);
_isLogEnabled(log) {
return this._logger._isLogEnabled(log);
}
_log(log, message, ...args) {
return this._logger._log(log, message, ...args);
}
}

@@ -80,0 +90,0 @@ exports.BrowserContextBase = BrowserContextBase;

@@ -25,3 +25,2 @@ "use strict";

const page_1 = require("../page");
const platform = require("../platform");
const transport_1 = require("../transport");

@@ -33,6 +32,8 @@ const crConnection_1 = require("./crConnection");

const crExecutionContext_1 = require("./crExecutionContext");
class CRBrowser extends platform.EventEmitter {
constructor(connection) {
super();
const logger_1 = require("../logger");
class CRBrowser extends browser_1.BrowserBase {
constructor(connection, logger, isPersistent) {
super(logger);
this._clientRootSessionPromise = null;
this._defaultContext = null;
this._contexts = new Map();

@@ -47,3 +48,4 @@ this._crPages = new Map();

this._session = this._connection.rootSession;
this._defaultContext = new CRBrowserContext(this, null, browserContext_1.validateBrowserContextOptions({}));
if (isPersistent)
this._defaultContext = new CRBrowserContext(this, null, browserContext_1.validateBrowserContextOptions({}));
this._connection.on(crConnection_1.ConnectionEvents.Disconnected, () => {

@@ -58,25 +60,33 @@ for (const context of this._contexts.values())

}
static async connect(transport, isPersistent, slowMo) {
const connection = new crConnection_1.CRConnection(transport_1.SlowMoTransport.wrap(transport, slowMo));
const browser = new CRBrowser(connection);
static async connect(transport, isPersistent, logger, slowMo) {
const connection = new crConnection_1.CRConnection(transport_1.SlowMoTransport.wrap(transport, slowMo), logger);
const browser = new CRBrowser(connection, logger, isPersistent);
const session = connection.rootSession;
const promises = [
session.send('Target.setDiscoverTargets', { discover: true }),
session.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }),
session.send('Target.setDiscoverTargets', { discover: false }),
];
const existingPageAttachPromises = [];
if (isPersistent) {
// First page and background pages in the persistent context are created automatically
// and may be initialized before we enable auto-attach.
function attachToExistingPage({ targetInfo }) {
if (targetInfo.type !== 'page' && targetInfo.type !== 'background_page')
return;
existingPageAttachPromises.push(session.send('Target.attachToTarget', { targetId: targetInfo.targetId, flatten: true }));
}
session.on('Target.targetCreated', attachToExistingPage);
Promise.all(promises).then(() => session.off('Target.targetCreated', attachToExistingPage)).catch(helper_1.debugError);
if (!isPersistent) {
await session.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true });
return browser;
}
await Promise.all(promises);
await Promise.all(existingPageAttachPromises);
const existingTargetAttachPromises = [];
// First page, background pages and their service workers in the persistent context
// are created automatically and may be initialized before we enable auto-attach.
function attachToExistingPage({ targetInfo }) {
if (targetInfo.type !== 'page' && targetInfo.type !== 'background_page' && targetInfo.type !== 'service_worker')
return;
// TODO: should we handle the error during 'Target.attachToTarget'? Can the target disappear?
existingTargetAttachPromises.push(session.send('Target.attachToTarget', { targetId: targetInfo.targetId, flatten: true }));
}
session.on('Target.targetCreated', attachToExistingPage);
const startDiscover = session.send('Target.setDiscoverTargets', { discover: true });
const autoAttachAndStopDiscover = session.send('Target.setAutoAttach', { autoAttach: true, waitForDebuggerOnStart: true, flatten: true }).then(() => {
// All targets collected before setAutoAttach response will not be auto-attached, the rest will be.
// TODO: We should fix this upstream and remove this tricky logic.
session.off('Target.targetCreated', attachToExistingPage);
return session.send('Target.setDiscoverTargets', { discover: false });
});
await Promise.all([
startDiscover,
autoAttachAndStopDiscover,
]);
// Wait for initial targets to arrive.
await Promise.all(existingTargetAttachPromises);
return browser;

@@ -95,9 +105,22 @@ }

}
async newPage(options) {
return browser_1.createPageInNewContext(this, options);
}
_onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }) {
if (targetInfo.type === 'browser')
return;
const session = this._connection.session(sessionId);
const context = (targetInfo.browserContextId && this._contexts.has(targetInfo.browserContextId)) ?
this._contexts.get(targetInfo.browserContextId) : this._defaultContext;
helper_1.assert(targetInfo.browserContextId, 'targetInfo: ' + JSON.stringify(targetInfo, null, 2));
let context = this._contexts.get(targetInfo.browserContextId) || null;
if (!context) {
// TODO: auto attach only to pages from our contexts.
// assert(this._defaultContext);
context = this._defaultContext;
}
if (targetInfo.type === 'other' || !context) {
if (waitingForDebugger) {
// Ideally, detaching should resume any target, but there is a bug in the backend.
session.send('Runtime.runIfWaitingForDebugger').catch(logger_1.logError(this)).then(() => {
this._session.send('Target.detachFromTarget', { sessionId }).catch(logger_1.logError(this));
});
}
return;
}
helper_1.assert(!this._crPages.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);

@@ -118,2 +141,6 @@ helper_1.assert(!this._backgroundPages.has(targetInfo.targetId), 'Duplicate target ' + targetInfo.targetId);

this._crPages.set(targetInfo.targetId, crPage);
if (opener && opener._initializedPage) {
for (const signalBarrier of opener._initializedPage._frameManager._signalBarriers)
signalBarrier.addPopup(crPage.pageOrError());
}
crPage.pageOrError().then(() => {

@@ -137,9 +164,3 @@ this._firstPageCallback();

}
helper_1.assert(targetInfo.type === 'browser' || targetInfo.type === 'other');
if (waitingForDebugger) {
// Ideally, detaching should resume any target, but there is a bug in the backend.
session.send('Runtime.runIfWaitingForDebugger').catch(helper_1.debugError).then(() => {
this._session.send('Target.detachFromTarget', { sessionId }).catch(helper_1.debugError);
});
}
helper_1.assert(false, 'Unknown target type: ' + targetInfo.type);
}

@@ -170,7 +191,4 @@ _onDetachedFromTarget(payload) {

}
async close() {
const disconnected = new Promise(f => this._connection.once(crConnection_1.ConnectionEvents.Disconnected, f));
await Promise.all(this.contexts().map(context => context.close()));
_disconnect() {
this._connection.close();
await disconnected;
}

@@ -182,3 +200,3 @@ async newBrowserCDPSession() {

helper_1.assert(!this._tracingRecording, 'Cannot start recording trace while already recording trace.');
this._tracingClient = page ? page._delegate._client : this._session;
this._tracingClient = page ? page._delegate._mainFrameSession._client : this._session;
const defaultCategories = [

@@ -202,10 +220,9 @@ '-*', 'devtools.timeline', 'v8.execute', 'disabled-by-default-devtools.timeline',

helper_1.assert(this._tracingClient, 'Tracing was not started.');
let fulfill;
const contentPromise = new Promise(x => fulfill = x);
this._tracingClient.once('Tracing.tracingComplete', event => {
crProtocolHelper_1.readProtocolStream(this._tracingClient, event.stream, this._tracingPath).then(fulfill);
});
await this._tracingClient.send('Tracing.end');
const [event] = await Promise.all([
new Promise(f => this._tracingClient.once('Tracing.tracingComplete', f)),
this._tracingClient.send('Tracing.end')
]);
const result = await crProtocolHelper_1.readProtocolStream(this._tracingClient, event.stream, this._tracingPath);
this._tracingRecording = false;
return contentPromise;
return result;
}

@@ -220,5 +237,2 @@ isConnected() {

}
_setDebugFunction(debugFunction) {
this._connection._debugProtocol = debugFunction;
}
}

@@ -228,3 +242,3 @@ exports.CRBrowser = CRBrowser;

constructor(browserContext, session, url) {
super(url);
super(browserContext, url);
this._browserContext = browserContext;

@@ -241,3 +255,3 @@ session.once('Runtime.executionContextCreated', event => {

constructor(browser, browserContextId, options) {
super(options);
super(browser, options);
this._browser = browser;

@@ -248,8 +262,16 @@ this._browserContextId = browserContextId;

async _initialize() {
const promises = [
this._browser._session.send('Browser.setDownloadBehavior', {
behavior: this._options.acceptDownloads ? 'allowAndName' : 'deny',
browserContextId: this._browserContextId || undefined,
downloadPath: this._browser._downloadsPath
})
];
if (this._options.permissions)
await this.grantPermissions(this._options.permissions);
promises.push(this.grantPermissions(this._options.permissions));
if (this._options.offline)
await this.setOffline(this._options.offline);
promises.push(this.setOffline(this._options.offline));
if (this._options.httpCredentials)
await this.setHTTPCredentials(this._options.httpCredentials);
promises.push(this.setHTTPCredentials(this._options.httpCredentials));
await Promise.all(promises);
}

@@ -287,2 +309,9 @@ pages() {

async addCookies(cookies) {
cookies = cookies.map(c => {
const copy = { ...c };
// Working around setter issue in Chrome. Cookies are now None by default.
if (copy.sameSite === 'None')
delete copy.sameSite;
return copy;
});
await this._browser._session.send('Storage.setCookies', { cookies: network.rewriteCookies(cookies), browserContextId: this._browserContextId || undefined });

@@ -328,3 +357,3 @@ }

for (const page of this.pages())
await page._delegate._client.send('Emulation.setGeolocationOverride', geolocation || {});
await page._delegate.updateGeolocation();
}

@@ -339,3 +368,3 @@ async setExtraHTTPHeaders(headers) {

for (const page of this.pages())
await page._delegate._networkManager.setOffline(offline);
await page._delegate.updateOffline();
}

@@ -345,3 +374,3 @@ async setHTTPCredentials(httpCredentials) {

for (const page of this.pages())
await page._delegate._networkManager.authenticate(httpCredentials);
await page._delegate.updateHttpCredentials();
}

@@ -371,2 +400,7 @@ async addInitScript(script, arg) {

}
async unroute(url, handler) {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
for (const page of this.pages())
await page._delegate.updateRequestInterception();
}
async close() {

@@ -383,3 +417,3 @@ if (this._closed)

this._browser._contexts.delete(this._browserContextId);
this._didCloseInternal();
await this._didCloseInternal();
}

@@ -386,0 +420,0 @@ backgroundPages() {

@@ -20,3 +20,4 @@ "use strict";

const helper_1 = require("../helper");
const platform = require("../platform");
const transport_1 = require("../transport");
const events_1 = require("events");
exports.ConnectionEvents = {

@@ -28,4 +29,4 @@ Disconnected: Symbol('ConnectionEvents.Disconnected')

exports.kBrowserCloseMessageId = -9999;
class CRConnection extends platform.EventEmitter {
constructor(transport) {
class CRConnection extends events_1.EventEmitter {
constructor(transport, logger) {
super();

@@ -36,2 +37,3 @@ this._lastId = 0;

this._transport = transport;
this._logger = logger;
this._transport.onmessage = this._onMessage.bind(this);

@@ -41,4 +43,2 @@ this._transport.onclose = this._onClose.bind(this);

this._sessions.set('', this.rootSession);
this._debugProtocol = platform.debug('pw:protocol');
this._debugProtocol.color = '34';
}

@@ -51,33 +51,33 @@ static fromSession(session) {

}
_rawSend(sessionId, message) {
_rawSend(sessionId, method, params) {
const id = ++this._lastId;
message.id = id;
const message = { id, method, params };
if (sessionId)
message.sessionId = sessionId;
const data = JSON.stringify(message);
this._debugProtocol('SEND ► ' + (rewriteInjectedScriptEvaluationLog(message) || data));
this._transport.send(data);
if (this._logger._isLogEnabled(transport_1.protocolLog))
this._logger._log(transport_1.protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message));
this._transport.send(message);
return id;
}
async _onMessage(message) {
this._debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
if (object.id === exports.kBrowserCloseMessageId)
if (this._logger._isLogEnabled(transport_1.protocolLog))
this._logger._log(transport_1.protocolLog, '◀ RECV ' + JSON.stringify(message));
if (message.id === exports.kBrowserCloseMessageId)
return;
if (object.method === 'Target.attachedToTarget') {
const sessionId = object.params.sessionId;
const rootSessionId = object.sessionId || '';
const session = new CRSession(this, rootSessionId, object.params.targetInfo.type, sessionId);
if (message.method === 'Target.attachedToTarget') {
const sessionId = message.params.sessionId;
const rootSessionId = message.sessionId || '';
const session = new CRSession(this, rootSessionId, message.params.targetInfo.type, sessionId);
this._sessions.set(sessionId, session);
}
else if (object.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(object.params.sessionId);
else if (message.method === 'Target.detachedFromTarget') {
const session = this._sessions.get(message.params.sessionId);
if (session) {
session._onClosed();
this._sessions.delete(object.params.sessionId);
this._sessions.delete(message.params.sessionId);
}
}
const session = this._sessions.get(object.sessionId || '');
const session = this._sessions.get(message.sessionId || '');
if (session)
session._onMessage(object);
session._onMessage(message);
}

@@ -110,6 +110,7 @@ _onClose() {

};
class CRSession extends platform.EventEmitter {
class CRSession extends events_1.EventEmitter {
constructor(connection, rootSessionId, targetType, sessionId) {
super();
this._callbacks = new Map();
this._crashed = false;
this._connection = connection;

@@ -125,6 +126,11 @@ this._rootSessionId = rootSessionId;

}
_markAsCrashed() {
this._crashed = true;
}
async send(method, params) {
if (this._crashed)
throw new Error('Target crashed');
if (!this._connection)
throw new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`);
const id = this._connection._rawSend(this._sessionId, { method, params });
const id = this._connection._rawSend(this._sessionId, method, params);
return new Promise((resolve, reject) => {

@@ -139,3 +145,3 @@ this._callbacks.set(id, { resolve, reject, error: new Error(), method });

if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object));
callback.reject(createProtocolError(callback.error, callback.method, object.error));
else

@@ -166,6 +172,6 @@ callback.resolve(object.result);

exports.CRSession = CRSession;
function createProtocolError(error, method, object) {
let message = `Protocol error (${method}): ${object.error.message}`;
if ('data' in object.error)
message += ` ${object.error.data}`;
function createProtocolError(error, method, protocolError) {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError)
message += ` ${protocolError.data}`;
return rewriteError(error, message);

@@ -182,3 +188,4 @@ }

return `{"id":${message.id} [evaluate injected script]}`;
return JSON.stringify(message);
}
//# sourceMappingURL=crConnection.js.map

@@ -21,6 +21,7 @@ "use strict";

const crExecutionContext_1 = require("./crExecutionContext");
const logger_1 = require("../logger");
class CRCoverage {
constructor(client) {
this._jsCoverage = new JSCoverage(client);
this._cssCoverage = new CSSCoverage(client);
constructor(client, logger) {
this._jsCoverage = new JSCoverage(client, logger);
this._cssCoverage = new CSSCoverage(client, logger);
}

@@ -42,5 +43,6 @@ async startJSCoverage(options) {

class JSCoverage {
constructor(client) {
constructor(client, logger) {
this._reportAnonymousScripts = false;
this._client = client;
this._logger = logger;
this._enabled = false;

@@ -92,3 +94,3 @@ this._scriptIds = new Set();

// This might happen if the page has already navigated away.
helper_1.debugError(e);
logger_1.logError(this._logger)(e);
}

@@ -122,4 +124,5 @@ }

class CSSCoverage {
constructor(client) {
constructor(client, logger) {
this._client = client;
this._logger = logger;
this._enabled = false;

@@ -166,3 +169,3 @@ this._stylesheetURLs = new Map();

// This might happen if the page has already navigated away.
helper_1.debugError(e);
logger_1.logError(this._logger)(e);
}

@@ -169,0 +172,0 @@ }

@@ -21,3 +21,3 @@ "use strict";

const network = require("../network");
const platform = require("../platform");
const logger_1 = require("../logger");
class CRNetworkManager {

@@ -31,3 +31,3 @@ constructor(client, page) {

this._protocolRequestInterceptionEnabled = false;
this._requestIdToInterceptionId = new Map();
this._requestIdToRequestPausedEvent = new Map();
this._client = client;

@@ -95,6 +95,6 @@ this._page = page;

const requestId = event.requestId;
const interceptionId = this._requestIdToInterceptionId.get(requestId);
if (interceptionId) {
this._onRequest(workerFrame, event, interceptionId);
this._requestIdToInterceptionId.delete(requestId);
const requestPausedEvent = this._requestIdToRequestPausedEvent.get(requestId);
if (requestPausedEvent) {
this._onRequest(workerFrame, event, requestPausedEvent);
this._requestIdToRequestPausedEvent.delete(requestId);
}

@@ -121,3 +121,3 @@ else {

authChallengeResponse: { response, username, password },
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
}

@@ -128,3 +128,3 @@ _onRequestPaused(workerFrame, event) {

requestId: event.requestId
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
}

@@ -134,41 +134,50 @@ if (!event.networkId || event.request.url.startsWith('data:'))

const requestId = event.networkId;
const interceptionId = event.requestId;
const requestWillBeSentEvent = this._requestIdToRequestWillBeSentEvent.get(requestId);
if (requestWillBeSentEvent) {
this._onRequest(workerFrame, requestWillBeSentEvent, interceptionId);
this._onRequest(workerFrame, requestWillBeSentEvent, event);
this._requestIdToRequestWillBeSentEvent.delete(requestId);
}
else {
this._requestIdToInterceptionId.set(requestId, interceptionId);
this._requestIdToRequestPausedEvent.set(requestId, event);
}
}
_onRequest(workerFrame, event, interceptionId) {
if (event.request.url.startsWith('data:'))
_onRequest(workerFrame, requestWillBeSentEvent, requestPausedEvent) {
if (requestWillBeSentEvent.request.url.startsWith('data:'))
return;
let redirectedFrom = null;
if (event.redirectResponse) {
const request = this._requestIdToRequest.get(event.requestId);
if (requestWillBeSentEvent.redirectResponse) {
const request = this._requestIdToRequest.get(requestWillBeSentEvent.requestId);
// If we connect late to the target, we could have missed the requestWillBeSent event.
if (request) {
this._handleRequestRedirect(request, event.redirectResponse);
this._handleRequestRedirect(request, requestWillBeSentEvent.redirectResponse);
redirectedFrom = request.request;
}
}
let frame = event.frameId ? this._page._frameManager.frame(event.frameId) : workerFrame;
let frame = requestWillBeSentEvent.frameId ? this._page._frameManager.frame(requestWillBeSentEvent.frameId) : workerFrame;
// Check if it's main resource request interception (targetId === main frame id).
if (!frame && interceptionId && event.frameId === this._page._delegate._targetId) {
if (!frame && requestPausedEvent && requestWillBeSentEvent.frameId === this._page._delegate._targetId) {
// Main resource request for the page is being intercepted so the Frame is not created
// yet. Precreate it here for the purposes of request interception. It will be updated
// later as soon as the request contnues and we receive frame tree from the page.
frame = this._page._frameManager.frameAttached(event.frameId, null);
frame = this._page._frameManager.frameAttached(requestWillBeSentEvent.frameId, null);
}
if (!frame) {
if (interceptionId)
this._client.send('Fetch.continueRequest', { requestId: interceptionId }).catch(helper_1.debugError);
if (requestPausedEvent)
this._client.send('Fetch.continueRequest', { requestId: requestPausedEvent.requestId }).catch(logger_1.logError(this._page));
return;
}
const isNavigationRequest = event.requestId === event.loaderId && event.type === 'Document';
const documentId = isNavigationRequest ? event.loaderId : undefined;
const request = new InterceptableRequest(this._client, frame, interceptionId, documentId, this._userRequestInterceptionEnabled, event, redirectedFrom);
this._requestIdToRequest.set(event.requestId, request);
const isNavigationRequest = requestWillBeSentEvent.requestId === requestWillBeSentEvent.loaderId && requestWillBeSentEvent.type === 'Document';
const documentId = isNavigationRequest ? requestWillBeSentEvent.loaderId : undefined;
if (isNavigationRequest)
this._page._frameManager.frameUpdatedDocumentIdForNavigation(requestWillBeSentEvent.frameId, documentId);
const request = new InterceptableRequest({
client: this._client,
frame,
documentId,
allowInterception: this._userRequestInterceptionEnabled,
requestWillBeSentEvent,
requestPausedEvent,
redirectedFrom
});
this._requestIdToRequest.set(requestWillBeSentEvent.requestId, request);
this._page._frameManager.requestStarted(request.request);

@@ -179,3 +188,3 @@ }

const response = await this._client.send('Network.getResponseBody', { requestId: request._requestId });
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
};

@@ -235,8 +244,11 @@ return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);

class InterceptableRequest {
constructor(client, frame, interceptionId, documentId, allowInterception, event, redirectedFrom) {
constructor(options) {
const { client, frame, documentId, allowInterception, requestWillBeSentEvent, requestPausedEvent, redirectedFrom } = options;
this._client = client;
this._requestId = event.requestId;
this._interceptionId = interceptionId;
this._requestId = requestWillBeSentEvent.requestId;
this._interceptionId = requestPausedEvent && requestPausedEvent.requestId;
this._documentId = documentId;
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, event.request.url, (event.type || '').toLowerCase(), event.request.method, event.request.postData || null, headersObject(event.request.headers));
const { headers, method, url, postData = null, } = requestPausedEvent ? requestPausedEvent.request : requestWillBeSentEvent.request;
const type = (requestWillBeSentEvent.type || '').toLowerCase();
this.request = new network.Request(allowInterception ? this : null, frame, redirectedFrom, documentId, url, type, method, postData, headersObject(headers));
}

@@ -252,7 +264,7 @@ async continue(overrides = {}) {

// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
logger_1.logError(this.request._page)(error);
});
}
async fulfill(response) {
const responseBody = response.body && helper_1.helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null);
const responseBody = response.body && helper_1.helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null);
const responseHeaders = {};

@@ -266,3 +278,3 @@ if (response.headers) {

if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
await this._client.send('Fetch.fulfillRequest', {

@@ -277,3 +289,3 @@ requestId: this._interceptionId,

// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
logger_1.logError(this.request._page)(error);
});

@@ -290,3 +302,3 @@ }

// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
logger_1.logError(this.request._page)(error);
});

@@ -293,0 +305,0 @@ }

@@ -34,11 +34,9 @@ "use strict";

const console_1 = require("../console");
const platform = require("../platform");
const errors_1 = require("../errors");
const logger_1 = require("../logger");
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
class CRPage {
constructor(client, targetId, browserContext, opener) {
this._contextIdToContext = new Map();
this._eventListeners = [];
this._firstNonInitialNavigationCommittedCallback = () => { };
this._sessions = new Map();
this._initializedPage = null;
this._client = client;
this._targetId = targetId;

@@ -49,40 +47,265 @@ this._opener = opener;

this._pdf = new crPdf_1.CRPDF(client);
this._coverage = new crCoverage_1.CRCoverage(client);
this._coverage = new crCoverage_1.CRCoverage(client, browserContext);
this._browserContext = browserContext;
this._page = new page_1.Page(this, browserContext);
this._networkManager = new crNetworkManager_1.CRNetworkManager(client, this._page);
this._firstNonInitialNavigationCommittedPromise = new Promise(f => this._firstNonInitialNavigationCommittedCallback = f);
this._mainFrameSession = new FrameSession(this, client, targetId);
this._sessions.set(targetId, this._mainFrameSession);
client.once(crConnection_1.CRSessionEvents.Disconnected, () => this._page._didDisconnect());
this._pagePromise = this._initialize().then(() => this._initializedPage = this._page).catch(e => e);
this._pagePromise = this._mainFrameSession._initialize().then(() => this._initializedPage = this._page).catch(e => e);
}
async _forAllFrameSessions(cb) {
await Promise.all(Array.from(this._sessions.values()).map(frame => cb(frame)));
}
_sessionForFrame(frame) {
// Frame id equals target id.
while (!this._sessions.has(frame._id)) {
const parent = frame.parentFrame();
if (!parent)
throw new Error(`Frame has been detached.`);
frame = parent;
}
return this._sessions.get(frame._id);
}
_sessionForHandle(handle) {
const frame = handle._context.frame;
return this._sessionForFrame(frame);
}
addFrameSession(targetId, session) {
// Frame id equals target id.
const frame = this._page._frameManager.frame(targetId);
helper_1.assert(frame);
this._page._frameManager.removeChildFramesRecursively(frame);
const frameSession = new FrameSession(this, session, targetId);
this._sessions.set(targetId, frameSession);
frameSession._initialize().catch(e => e);
}
removeFrameSession(targetId) {
const frameSession = this._sessions.get(targetId);
if (!frameSession)
return;
// Frame id equals target id.
const frame = this._page._frameManager.frame(targetId);
if (frame)
this._page._frameManager.removeChildFramesRecursively(frame);
frameSession.dispose();
this._sessions.delete(targetId);
}
async pageOrError() {
return this._pagePromise;
}
didClose() {
for (const session of this._sessions.values())
session.dispose();
this._page._didClose();
}
async navigateFrame(frame, url, referrer) {
return this._sessionForFrame(frame)._navigate(frame, url, referrer);
}
async exposeBinding(binding) {
await this._forAllFrameSessions(frame => frame._initBinding(binding));
await Promise.all(this._page.frames().map(frame => frame.evaluate(binding.source).catch(logger_1.logError(this._page))));
}
async updateExtraHTTPHeaders() {
await this._forAllFrameSessions(frame => frame._updateExtraHTTPHeaders());
}
async updateGeolocation() {
await this._forAllFrameSessions(frame => frame._updateGeolocation());
}
async updateOffline() {
await this._forAllFrameSessions(frame => frame._updateOffline());
}
async updateHttpCredentials() {
await this._forAllFrameSessions(frame => frame._updateHttpCredentials());
}
async setViewportSize(viewportSize) {
helper_1.assert(this._page._state.viewportSize === viewportSize);
await this._mainFrameSession._updateViewport();
}
async updateEmulateMedia() {
await this._forAllFrameSessions(frame => frame._updateEmulateMedia());
}
async updateRequestInterception() {
await this._forAllFrameSessions(frame => frame._updateRequestInterception());
}
async setFileChooserIntercepted(enabled) {
await this._forAllFrameSessions(frame => frame._setFileChooserIntercepted(enabled));
}
async opener() {
if (!this._opener)
return null;
const openerPage = await this._opener.pageOrError();
if (openerPage instanceof page_1.Page && !openerPage.isClosed())
return openerPage;
return null;
}
async reload() {
await this._mainFrameSession._client.send('Page.reload');
}
async _go(delta) {
const history = await this._mainFrameSession._client.send('Page.getNavigationHistory');
const entry = history.entries[history.currentIndex + delta];
if (!entry)
return false;
await this._mainFrameSession._client.send('Page.navigateToHistoryEntry', { entryId: entry.id });
return true;
}
goBack() {
return this._go(-1);
}
goForward() {
return this._go(+1);
}
async evaluateOnNewDocument(source) {
await this._forAllFrameSessions(frame => frame._evaluateOnNewDocument(source));
}
async closePage(runBeforeUnload) {
if (runBeforeUnload)
await this._mainFrameSession._client.send('Page.close');
else
await this._browserContext._browser._closePage(this);
}
canScreenshotOutsideViewport() {
return false;
}
async setBackgroundColor(color) {
await this._mainFrameSession._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
}
async takeScreenshot(format, documentRect, viewportRect, quality) {
const { visualViewport } = await this._mainFrameSession._client.send('Page.getLayoutMetrics');
if (!documentRect) {
documentRect = {
x: visualViewport.pageX + viewportRect.x,
y: visualViewport.pageY + viewportRect.y,
...helper_1.helper.enclosingIntSize({
width: viewportRect.width / visualViewport.scale,
height: viewportRect.height / visualViewport.scale,
})
};
}
await this._mainFrameSession._client.send('Page.bringToFront', {});
// When taking screenshots with documentRect (based on the page content, not viewport),
// ignore current page scale.
const clip = { ...documentRect, scale: viewportRect ? visualViewport.scale : 1 };
const result = await this._mainFrameSession._client.send('Page.captureScreenshot', { format, quality, clip });
return Buffer.from(result.data, 'base64');
}
async resetViewport() {
await this._mainFrameSession._client.send('Emulation.setDeviceMetricsOverride', { mobile: false, width: 0, height: 0, deviceScaleFactor: 0 });
}
async getContentFrame(handle) {
return this._sessionForHandle(handle)._getContentFrame(handle);
}
async getOwnerFrame(handle) {
return this._sessionForHandle(handle)._getOwnerFrame(handle);
}
isElementHandle(remoteObject) {
return remoteObject.subtype === 'node';
}
async getBoundingBox(handle) {
return this._sessionForHandle(handle)._getBoundingBox(handle);
}
async scrollRectIntoViewIfNeeded(handle, rect) {
return this._sessionForHandle(handle)._scrollRectIntoViewIfNeeded(handle, rect);
}
async getContentQuads(handle) {
return this._sessionForHandle(handle)._getContentQuads(handle);
}
async layoutViewport() {
const layoutMetrics = await this._mainFrameSession._client.send('Page.getLayoutMetrics');
return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight };
}
async setInputFiles(handle, files) {
await handle._evaluateInUtility(({ injected, node }, files) => injected.setInputFiles(node, files), dom.toFileTransferPayload(files));
}
async adoptElementHandle(handle, to) {
return this._sessionForHandle(handle)._adoptElementHandle(handle, to);
}
async getAccessibilityTree(needle) {
return crAccessibility_1.getAccessibilityTree(this._mainFrameSession._client, needle);
}
async inputActionEpilogue() {
await this._mainFrameSession._client.send('Page.enable').catch(e => { });
}
async pdf(options) {
return this._pdf.generate(options);
}
coverage() {
return this._coverage;
}
async getFrameElement(frame) {
let parent = frame.parentFrame();
if (!parent)
throw new Error('Frame has been detached.');
const parentSession = this._sessionForFrame(parent);
const { backendNodeId } = await parentSession._client.send('DOM.getFrameOwner', { frameId: frame._id }).catch(e => {
if (e instanceof Error && e.message.includes('Frame with the given id was not found.'))
e.message = 'Frame has been detached.';
throw e;
});
parent = frame.parentFrame();
if (!parent)
throw new Error('Frame has been detached.');
return parentSession._adoptBackendNodeId(backendNodeId, await parent._mainContext());
}
}
exports.CRPage = CRPage;
class FrameSession {
constructor(crPage, client, targetId) {
this._contextIdToContext = new Map();
this._eventListeners = [];
this._firstNonInitialNavigationCommittedFulfill = () => { };
this._firstNonInitialNavigationCommittedReject = (e) => { };
this._client = client;
this._crPage = crPage;
this._page = crPage._page;
this._targetId = targetId;
this._networkManager = new crNetworkManager_1.CRNetworkManager(client, this._page);
this._firstNonInitialNavigationCommittedPromise = new Promise((f, r) => {
this._firstNonInitialNavigationCommittedFulfill = f;
this._firstNonInitialNavigationCommittedReject = r;
});
client.once(crConnection_1.CRSessionEvents.Disconnected, () => {
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
});
}
_isMainFrame() {
return this._targetId === this._crPage._targetId;
}
_addSessionListeners() {
this._eventListeners = [
helper_1.helper.addEventListener(this._client, 'Inspector.targetCrashed', event => this._onTargetCrashed()),
helper_1.helper.addEventListener(this._client, 'Log.entryAdded', event => this._onLogEntryAdded(event)),
helper_1.helper.addEventListener(this._client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
helper_1.helper.addEventListener(this._client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
helper_1.helper.addEventListener(this._client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
helper_1.helper.addEventListener(this._client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
helper_1.helper.addEventListener(this._client, 'Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
helper_1.helper.addEventListener(this._client, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
helper_1.helper.addEventListener(this._client, 'Page.javascriptDialogOpening', event => this._onDialog(event)),
helper_1.helper.addEventListener(this._client, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
helper_1.helper.addEventListener(this._client, 'Page.downloadWillBegin', event => this._onDownloadWillBegin(event)),
helper_1.helper.addEventListener(this._client, 'Page.downloadProgress', event => this._onDownloadProgress(event)),
helper_1.helper.addEventListener(this._client, 'Runtime.bindingCalled', event => this._onBindingCalled(event)),
helper_1.helper.addEventListener(this._client, 'Runtime.consoleAPICalled', event => this._onConsoleAPI(event)),
helper_1.helper.addEventListener(this._client, 'Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()),
helper_1.helper.addEventListener(this._client, 'Target.attachedToTarget', event => this._onAttachedToTarget(event)),
helper_1.helper.addEventListener(this._client, 'Target.detachedFromTarget', event => this._onDetachedFromTarget(event)),
];
}
async _initialize() {
let lifecycleEventsEnabled;
if (!this._isMainFrame())
this._addSessionListeners();
const promises = [
this._client.send('Page.enable'),
this._client.send('Page.getFrameTree').then(({ frameTree }) => {
this._handleFrameTree(frameTree);
this._eventListeners = [
helper_1.helper.addEventListener(this._client, 'Inspector.targetCrashed', event => this._onTargetCrashed()),
helper_1.helper.addEventListener(this._client, 'Log.entryAdded', event => this._onLogEntryAdded(event)),
helper_1.helper.addEventListener(this._client, 'Page.fileChooserOpened', event => this._onFileChooserOpened(event)),
helper_1.helper.addEventListener(this._client, 'Page.frameAttached', event => this._onFrameAttached(event.frameId, event.parentFrameId)),
helper_1.helper.addEventListener(this._client, 'Page.frameDetached', event => this._onFrameDetached(event.frameId)),
helper_1.helper.addEventListener(this._client, 'Page.frameNavigated', event => this._onFrameNavigated(event.frame, false)),
helper_1.helper.addEventListener(this._client, 'Page.frameRequestedNavigation', event => this._onFrameRequestedNavigation(event)),
helper_1.helper.addEventListener(this._client, 'Page.frameStoppedLoading', event => this._onFrameStoppedLoading(event.frameId)),
helper_1.helper.addEventListener(this._client, 'Page.javascriptDialogOpening', event => this._onDialog(event)),
helper_1.helper.addEventListener(this._client, 'Page.navigatedWithinDocument', event => this._onFrameNavigatedWithinDocument(event.frameId, event.url)),
helper_1.helper.addEventListener(this._client, 'Runtime.bindingCalled', event => this._onBindingCalled(event)),
helper_1.helper.addEventListener(this._client, 'Runtime.consoleAPICalled', event => this._onConsoleAPI(event)),
helper_1.helper.addEventListener(this._client, 'Runtime.exceptionThrown', exception => this._handleException(exception.exceptionDetails)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextDestroyed', event => this._onExecutionContextDestroyed(event.executionContextId)),
helper_1.helper.addEventListener(this._client, 'Runtime.executionContextsCleared', event => this._onExecutionContextsCleared()),
helper_1.helper.addEventListener(this._client, 'Target.attachedToTarget', event => this._onAttachedToTarget(event)),
helper_1.helper.addEventListener(this._client, 'Target.detachedFromTarget', event => this._onDetachedFromTarget(event)),
];
for (const frame of this._page.frames()) {
if (this._isMainFrame()) {
this._handleFrameTree(frameTree);
this._addSessionListeners();
}
const localFrames = this._isMainFrame() ? this._page.frames() : [this._page._frameManager.frame(this._targetId)];
for (const frame of localFrames) {
// Note: frames might be removed before we send these.

@@ -93,7 +316,7 @@ this._client.send('Page.createIsolatedWorld', {

worldName: UTILITY_WORLD_NAME,
}).catch(helper_1.debugError);
for (const binding of this._browserContext._pageBindings.values())
frame.evaluate(binding.source).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
for (const binding of this._crPage._browserContext._pageBindings.values())
frame.evaluate(binding.source).catch(logger_1.logError(this._page));
}
const isInitialEmptyPage = this._page.mainFrame().url() === ':';
const isInitialEmptyPage = this._isMainFrame() && this._page.mainFrame().url() === ':';
if (isInitialEmptyPage) {

@@ -108,3 +331,3 @@ // Ignore lifecycle events for the initial empty page. It is never the final page

else {
this._firstNonInitialNavigationCommittedCallback();
this._firstNonInitialNavigationCommittedFulfill();
this._eventListeners.push(helper_1.helper.addEventListener(this._client, 'Page.lifecycleEvent', event => this._onLifecycleEvent(event)));

@@ -124,3 +347,3 @@ }

];
const options = this._browserContext._options;
const options = this._crPage._browserContext._options;
if (options.bypassCSP)

@@ -130,4 +353,6 @@ promises.push(this._client.send('Page.setBypassCSP', { enabled: true }));

promises.push(this._client.send('Security.setIgnoreCertificateErrors', { ignore: true }));
if (options.viewport)
promises.push(this._updateViewport(true /* updateTouch */));
if (this._isMainFrame() && options.viewport)
promises.push(this._updateViewport());
if (options.hasTouch)
promises.push(this._client.send('Emulation.setTouchEmulationEnabled', { enabled: true }));
if (options.javaScriptEnabled === false)

@@ -141,14 +366,12 @@ promises.push(this._client.send('Emulation.setScriptExecutionDisabled', { value: true }));

promises.push(emulateTimezone(this._client, options.timezoneId));
if (options.geolocation)
promises.push(this._client.send('Emulation.setGeolocationOverride', options.geolocation));
promises.push(this.updateExtraHTTPHeaders());
promises.push(this.updateRequestInterception());
if (options.offline)
promises.push(this._networkManager.setOffline(options.offline));
if (options.httpCredentials)
promises.push(this._networkManager.authenticate(options.httpCredentials));
for (const binding of this._browserContext._pageBindings.values())
promises.push(this._updateGeolocation());
promises.push(this._updateExtraHTTPHeaders());
promises.push(this._updateRequestInterception());
promises.push(this._updateOffline());
promises.push(this._updateHttpCredentials());
promises.push(this._updateEmulateMedia());
for (const binding of this._crPage._browserContext._pageBindings.values())
promises.push(this._initBinding(binding));
for (const source of this._browserContext._evaluateOnNewDocumentSources)
promises.push(this.evaluateOnNewDocument(source));
for (const source of this._crPage._browserContext._evaluateOnNewDocumentSources)
promises.push(this._evaluateOnNewDocument(source));
promises.push(this._client.send('Runtime.runIfWaitingForDebugger'));

@@ -158,8 +381,7 @@ promises.push(this._firstNonInitialNavigationCommittedPromise);

}
didClose() {
dispose() {
helper_1.helper.removeEventListeners(this._eventListeners);
this._networkManager.dispose();
this._page._didClose();
}
async navigateFrame(frame, url, referrer) {
async _navigate(frame, url, referrer) {
const response = await this._client.send('Page.navigate', { url, referrer, frameId: frame._id });

@@ -188,2 +410,8 @@ if (response.errorText)

_onFrameAttached(frameId, parentFrameId) {
if (this._crPage._sessions.has(frameId) && frameId !== this._targetId) {
// This is a remote -> local frame transition.
const frame = this._page._frameManager.frame(frameId);
this._page._frameManager.removeChildFramesRecursively(frame);
return;
}
this._page._frameManager.frameAttached(frameId, parentFrameId);

@@ -194,6 +422,6 @@ }

if (!initial)
this._firstNonInitialNavigationCommittedCallback();
this._firstNonInitialNavigationCommittedFulfill();
}
_onFrameRequestedNavigation(payload) {
this._page._frameManager.frameRequestedNavigation(payload.frameId);
this._page._frameManager.frameRequestedNavigation(payload.frameId, '');
}

@@ -204,2 +432,7 @@ _onFrameNavigatedWithinDocument(frameId, url) {

_onFrameDetached(frameId) {
if (this._crPage._sessions.has(frameId)) {
// This is a local -> remote frame transtion.
// We already got a new target and handled frame reattach - nothing to do here.
return;
}
this._page._frameManager.frameDetached(frameId);

@@ -232,6 +465,10 @@ }

const session = crConnection_1.CRConnection.fromSession(this._client).session(event.sessionId);
if (event.targetInfo.type === 'iframe') {
this._crPage.addFrameSession(event.targetInfo.targetId, session);
return;
}
if (event.targetInfo.type !== 'worker') {
// Ideally, detaching should resume any target, but there is a bug in the backend.
session.send('Runtime.runIfWaitingForDebugger').catch(helper_1.debugError).then(() => {
this._client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(helper_1.debugError);
session.send('Runtime.runIfWaitingForDebugger').catch(logger_1.logError(this._page)).then(() => {
this._client.send('Target.detachFromTarget', { sessionId: event.sessionId }).catch(logger_1.logError(this._page));
});

@@ -241,3 +478,3 @@ return;

const url = event.targetInfo.url;
const worker = new page_1.Worker(url);
const worker = new page_1.Worker(this._page, url);
this._page._addWorker(event.sessionId, worker);

@@ -251,3 +488,3 @@ session.once('Runtime.executionContextCreated', async (event) => {

session.send('Runtime.runIfWaitingForDebugger'),
]).catch(helper_1.debugError); // This might fail if the target is closed before we initialize.
]).catch(logger_1.logError(this._page)); // This might fail if the target is closed before we initialize.
session.on('Runtime.consoleAPICalled', event => {

@@ -259,5 +496,6 @@ const args = event.args.map(o => worker._existingExecutionContext._createHandle(o));

// TODO: attribute workers to the right frame.
this._networkManager.instrumentNetworkEvents(session, this._page.mainFrame());
this._networkManager.instrumentNetworkEvents(session, this._page._frameManager.frame(this._targetId));
}
_onDetachedFromTarget(event) {
this._crPage.removeFrameSession(event.targetId);
this._page._removeWorker(event.sessionId);

@@ -286,6 +524,2 @@ }

}
async exposeBinding(binding) {
await this._initBinding(binding);
await Promise.all(this._page.frames().map(frame => frame.evaluate(binding.source).catch(helper_1.debugError)));
}
async _initBinding(binding) {

@@ -309,3 +543,4 @@ await Promise.all([

}
_onTargetCrashed() {
async _onTargetCrashed() {
this._client._markAsCrashed();
this._page._didCrash();

@@ -323,8 +558,17 @@ }

const utilityContext = await frame._utilityContext();
const handle = await this.adoptBackendNodeId(event.backendNodeId, utilityContext);
const handle = await this._adoptBackendNodeId(event.backendNodeId, utilityContext);
this._page._onFileChooserOpened(handle);
}
async updateExtraHTTPHeaders() {
_onDownloadWillBegin(payload) {
this._crPage._browserContext._browser._downloadCreated(this._page, payload.guid, payload.url);
}
_onDownloadProgress(payload) {
if (payload.state === 'completed')
this._crPage._browserContext._browser._downloadFinished(payload.guid, '');
if (payload.state === 'canceled')
this._crPage._browserContext._browser._downloadFinished(payload.guid, 'canceled');
}
async _updateExtraHTTPHeaders() {
const headers = network.mergeHeaders([
this._browserContext._options.extraHTTPHeaders,
this._crPage._browserContext._options.extraHTTPHeaders,
this._page._state.extraHTTPHeaders

@@ -334,8 +578,17 @@ ]);

}
async setViewportSize(viewportSize) {
helper_1.assert(this._page._state.viewportSize === viewportSize);
await this._updateViewport(false /* updateTouch */);
async _updateGeolocation() {
const geolocation = this._crPage._browserContext._options.geolocation;
await this._client.send('Emulation.setGeolocationOverride', geolocation || {});
}
async _updateViewport(updateTouch) {
const options = this._browserContext._options;
async _updateOffline() {
const offline = !!this._crPage._browserContext._options.offline;
await this._networkManager.setOffline(offline);
}
async _updateHttpCredentials() {
const credentials = this._crPage._browserContext._options.httpCredentials || null;
await this._networkManager.authenticate(credentials);
}
async _updateViewport() {
helper_1.assert(this._isMainFrame());
const options = this._crPage._browserContext._options;
let viewport = options.viewport || { width: 0, height: 0 };

@@ -357,79 +610,19 @@ const viewportSize = this._page._state.viewportSize;

];
if (updateTouch)
promises.push(this._client.send('Emulation.setTouchEmulationEnabled', { enabled: !!options.hasTouch }));
await Promise.all(promises);
}
async setEmulateMedia(mediaType, colorScheme) {
async _updateEmulateMedia() {
const colorScheme = this._page._state.colorScheme || this._crPage._browserContext._options.colorScheme || 'light';
const features = colorScheme ? [{ name: 'prefers-color-scheme', value: colorScheme }] : [];
await this._client.send('Emulation.setEmulatedMedia', { media: mediaType || '', features });
await this._client.send('Emulation.setEmulatedMedia', { media: this._page._state.mediaType || '', features });
}
async updateRequestInterception() {
async _updateRequestInterception() {
await this._networkManager.setRequestInterception(this._page._needsRequestInterception());
}
async setFileChooserIntercepted(enabled) {
async _setFileChooserIntercepted(enabled) {
await this._client.send('Page.setInterceptFileChooserDialog', { enabled }).catch(e => { }); // target can be closed.
}
async opener() {
if (!this._opener)
return null;
const openerPage = await this._opener.pageOrError();
if (openerPage instanceof page_1.Page && !openerPage.isClosed())
return openerPage;
return null;
}
async reload() {
await this._client.send('Page.reload');
}
async _go(delta) {
const history = await this._client.send('Page.getNavigationHistory');
const entry = history.entries[history.currentIndex + delta];
if (!entry)
return false;
await this._client.send('Page.navigateToHistoryEntry', { entryId: entry.id });
return true;
}
goBack() {
return this._go(-1);
}
goForward() {
return this._go(+1);
}
async evaluateOnNewDocument(source) {
async _evaluateOnNewDocument(source) {
await this._client.send('Page.addScriptToEvaluateOnNewDocument', { source });
}
async closePage(runBeforeUnload) {
if (runBeforeUnload)
await this._client.send('Page.close');
else
await this._browserContext._browser._closePage(this);
}
canScreenshotOutsideViewport() {
return false;
}
async setBackgroundColor(color) {
await this._client.send('Emulation.setDefaultBackgroundColorOverride', { color });
}
async takeScreenshot(format, documentRect, viewportRect, quality) {
const { visualViewport } = await this._client.send('Page.getLayoutMetrics');
if (!documentRect) {
documentRect = {
x: visualViewport.pageX + viewportRect.x,
y: visualViewport.pageY + viewportRect.y,
...helper_1.helper.enclosingIntSize({
width: viewportRect.width / visualViewport.scale,
height: viewportRect.height / visualViewport.scale,
})
};
}
await this._client.send('Page.bringToFront', {});
// When taking screenshots with documentRect (based on the page content, not viewport),
// ignore current page scale.
const clip = { ...documentRect, scale: viewportRect ? visualViewport.scale : 1 };
const result = await this._client.send('Page.captureScreenshot', { format, quality, clip });
return platform.Buffer.from(result.data, 'base64');
}
async resetViewport() {
await this._client.send('Emulation.setDeviceMetricsOverride', { mobile: false, width: 0, height: 0, deviceScaleFactor: 0 });
}
async getContentFrame(handle) {
async _getContentFrame(handle) {
const nodeInfo = await this._client.send('DOM.describeNode', {

@@ -442,3 +635,3 @@ objectId: toRemoteObject(handle).objectId

}
async getOwnerFrame(handle) {
async _getOwnerFrame(handle) {
// document.documentElement has frameId of the owner frame.

@@ -464,9 +657,6 @@ const documentElement = await handle.evaluateHandle(node => {

}
isElementHandle(remoteObject) {
return remoteObject.subtype === 'node';
}
async getBoundingBox(handle) {
async _getBoundingBox(handle) {
const result = await this._client.send('DOM.getBoxModel', {
objectId: toRemoteObject(handle).objectId
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
if (!result)

@@ -481,3 +671,3 @@ return null;

}
async scrollRectIntoViewIfNeeded(handle, rect) {
async _scrollRectIntoViewIfNeeded(handle, rect) {
await this._client.send('DOM.scrollIntoViewIfNeeded', {

@@ -487,2 +677,4 @@ objectId: toRemoteObject(handle).objectId,

}).catch(e => {
if (e instanceof Error && e.message.includes('Node is detached from document'))
throw new errors_1.NotConnectedError();
if (e instanceof Error && e.message.includes('Node does not have a layout object'))

@@ -493,6 +685,6 @@ e.message = 'Node is either not visible or not an HTMLElement';

}
async getContentQuads(handle) {
async _getContentQuads(handle) {
const result = await this._client.send('DOM.getContentQuads', {
objectId: toRemoteObject(handle).objectId
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
if (!result)

@@ -507,20 +699,13 @@ return null;

}
async layoutViewport() {
const layoutMetrics = await this._client.send('Page.getLayoutMetrics');
return { width: layoutMetrics.layoutViewport.clientWidth, height: layoutMetrics.layoutViewport.clientHeight };
}
async setInputFiles(handle, files) {
await handle.evaluate(dom.setFileInputFunction, files);
}
async adoptElementHandle(handle, to) {
async _adoptElementHandle(handle, to) {
const nodeInfo = await this._client.send('DOM.describeNode', {
objectId: toRemoteObject(handle).objectId,
});
return this.adoptBackendNodeId(nodeInfo.node.backendNodeId, to);
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId, to);
}
async adoptBackendNodeId(backendNodeId, to) {
async _adoptBackendNodeId(backendNodeId, to) {
const result = await this._client.send('DOM.resolveNode', {
backendNodeId,
executionContextId: to._delegate._contextId,
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
if (!result || result.object.subtype === 'null')

@@ -530,27 +715,3 @@ throw new Error('Unable to adopt element handle from a different document');

}
async getAccessibilityTree(needle) {
return crAccessibility_1.getAccessibilityTree(this._client, needle);
}
async inputActionEpilogue() {
await this._client.send('Page.enable').catch(e => { });
}
async pdf(options) {
return this._pdf.generate(options);
}
coverage() {
return this._coverage;
}
async getFrameElement(frame) {
const { backendNodeId } = await this._client.send('DOM.getFrameOwner', { frameId: frame._id }).catch(e => {
if (e instanceof Error && e.message.includes('Frame with the given id was not found.'))
e.message = 'Frame has been detached.';
throw e;
});
const parent = frame.parentFrame();
if (!parent)
throw new Error('Frame has been detached.');
return this.adoptBackendNodeId(backendNodeId, await parent._mainContext());
}
}
exports.CRPage = CRPage;
function toRemoteObject(handle) {

@@ -557,0 +718,0 @@ return handle._remoteObject;

@@ -20,3 +20,4 @@ "use strict";

const helper_1 = require("../helper");
const platform = require("../platform");
const fs = require("fs");
const util = require("util");
function getExceptionMessage(exceptionDetails) {

@@ -67,3 +68,3 @@ if (exceptionDetails.exception)

if (path)
fd = await platform.openFdAsync(path, 'w');
fd = await util.promisify(fs.open)(path, 'w');
const bufs = [];

@@ -73,17 +74,11 @@ while (!eof) {

eof = response.eof;
const buf = platform.Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
const buf = Buffer.from(response.data, response.base64Encoded ? 'base64' : undefined);
bufs.push(buf);
if (path)
await platform.writeFdAsync(fd, buf);
await util.promisify(fs.write)(fd, buf);
}
if (path)
await platform.closeFdAsync(fd);
await util.promisify(fs.close)(fd);
await client.send('IO.close', { handle });
let resultBuffer = null;
try {
resultBuffer = platform.Buffer.concat(bufs);
}
finally {
return resultBuffer;
}
return Buffer.concat(bufs);
}

@@ -100,6 +95,19 @@ exports.readProtocolStream = readProtocolStream;

function exceptionToError(exceptionDetails) {
const message = getExceptionMessage(exceptionDetails);
const messageWithStack = getExceptionMessage(exceptionDetails);
const lines = messageWithStack.split('\n');
const firstStackTraceLine = lines.findIndex(line => line.startsWith(' at'));
let message = '';
let stack = '';
if (firstStackTraceLine === -1) {
message = messageWithStack;
}
else {
message = lines.slice(0, firstStackTraceLine).join('\n');
stack = messageWithStack;
}
const match = message.match(/^[a-zA-Z0-0_]*Error: (.*)$/);
if (match)
message = match[1];
const err = new Error(message);
// Don't report clientside error with a node stack attached
err.stack = 'Error: ' + err.message; // Stack is supposed to contain error message as the first line.
err.stack = stack;
return err;

@@ -106,0 +114,0 @@ }

@@ -18,11 +18,18 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const mime = require("mime");
const path = require("path");
const util = require("util");
const helper_1 = require("./helper");
const js = require("./javascript");
const injectedSource = require("./generated/injectedSource");
const helper_1 = require("./helper");
const platform = require("./platform");
const selectors_1 = require("./selectors");
const errors_1 = require("./errors");
const logger_1 = require("./logger");
exports.inputLog = {
name: 'input',
color: 'cyan'
};
class FrameExecutionContext extends js.ExecutionContext {
constructor(delegate, frame) {
super(delegate);
this._injectedGeneration = -1;
super(delegate, frame._page);
this.frame = frame;

@@ -36,5 +43,5 @@ }

async _doEvaluateInternal(returnByValue, waitForNavigations, pageFunction, ...args) {
return await this.frame._page._frameManager.waitForNavigationsCreatedBy(async () => {
return await this.frame._page._frameManager.waitForSignalsCreatedBy(async () => {
return this._delegate.evaluate(this, returnByValue, pageFunction, ...args);
}, waitForNavigations ? undefined : { waitUntil: 'nowait' });
}, Number.MAX_SAFE_INTEGER, waitForNavigations ? undefined : { noWaitAfter: true });
}

@@ -47,45 +54,9 @@ _createHandle(remoteObject) {

_injected() {
const selectors = selectors_1.Selectors._instance();
if (this._injectedPromise && selectors._generation !== this._injectedGeneration) {
this._injectedPromise.then(handle => handle.dispose());
this._injectedPromise = undefined;
}
if (!this._injectedPromise) {
const custom = [];
for (const [name, source] of selectors._engines)
custom.push(`{ name: '${name}', engine: (${source}) }`);
const source = `
new (${injectedSource.source})([
${custom.join(',\n')}
])
`;
this._injectedPromise = this._doEvaluateInternal(false /* returnByValue */, false /* waitForNavigations */, source);
this._injectedGeneration = selectors._generation;
this._injectedPromise = selectors_1.selectors._prepareEvaluator(this).then(evaluator => {
return this.evaluateHandleInternal(evaluator => evaluator.injected, evaluator);
});
}
return this._injectedPromise;
}
async _$(selector, scope) {
const handle = await this.evaluateHandleInternal(({ injected, selector, scope }) => injected.querySelector(selector, scope || document), { injected: await this._injected(), selector, scope });
if (!handle.asElement())
handle.dispose();
return handle.asElement();
}
async _$array(selector, scope) {
const arrayHandle = await this.evaluateHandleInternal(({ injected, selector, scope }) => injected.querySelectorAll(selector, scope || document), { injected: await this._injected(), selector, scope });
return arrayHandle;
}
async _$$(selector, scope) {
const arrayHandle = await this._$array(selector, scope);
const properties = await arrayHandle.getProperties();
arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
else
property.dispose();
}
return result;
}
}

@@ -110,2 +81,5 @@ exports.FrameExecutionContext = FrameExecutionContext;

return null;
const frame = this._page._frameManager.frame(frameId);
if (frame)
return frame;
for (const page of this._page._browserContext.pages()) {

@@ -124,4 +98,33 @@ const frame = page._frameManager.frame(frameId);

}
async getAttribute(name) {
return this._evaluateInUtility(({ node }, name) => {
if (node.nodeType !== Node.ELEMENT_NODE)
throw new Error('Not an element');
const element = node;
return element.getAttribute(name);
}, name);
}
async textContent() {
return this._evaluateInUtility(({ node }) => node.textContent, {});
}
async innerText() {
return this._evaluateInUtility(({ node }) => {
if (node.nodeType !== Node.ELEMENT_NODE)
throw new Error('Not an element');
const element = node;
return element.innerText;
}, {});
}
async innerHTML() {
return this._evaluateInUtility(({ node }) => {
if (node.nodeType !== Node.ELEMENT_NODE)
throw new Error('Not an element');
const element = node;
return element.innerHTML;
}, {});
}
async _scrollRectIntoViewIfNeeded(rect) {
this._page._log(exports.inputLog, 'scrolling into view if needed...');
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
this._page._log(exports.inputLog, '...done');
}

@@ -169,3 +172,3 @@ async scrollIntoViewIfNeeded() {

this.boundingBox(),
this._evaluateInUtility(({ injected, node }) => injected.getElementBorderWidth(node), {}).catch(helper_1.debugError),
this._evaluateInUtility(({ injected, node }) => injected.getElementBorderWidth(node), {}).catch(logger_1.logError(this._context._logger)),
]);

@@ -184,19 +187,25 @@ const point = { x: offset.x, y: offset.y };

}
async _performPointerAction(action, options) {
async _performPointerAction(action, options = {}) {
const deadline = this._page._timeoutSettings.computeDeadline(options);
const { force = false } = (options || {});
if (!force)
await this._waitForDisplayedAtStablePosition(options);
await this._waitForDisplayedAtStablePosition(deadline);
const position = options ? options.position : undefined;
await this._scrollRectIntoViewIfNeeded(position ? { x: position.x, y: position.y, width: 0, height: 0 } : undefined);
const point = position ? await this._offsetPoint(position) : await this._clickablePoint();
point.x = (point.x * 100 | 0) / 100;
point.y = (point.y * 100 | 0) / 100;
await this._page.mouse.move(point.x, point.y); // Force any hover effects before waiting for hit target.
if (!force)
await this._waitForHitTargetAt(point, options);
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
await this._waitForHitTargetAt(point, deadline);
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
let restoreModifiers;
if (options && options.modifiers)
restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
this._page._log(exports.inputLog, 'performing input action...');
await action(point);
this._page._log(exports.inputLog, '...done');
if (restoreModifiers)
await this._page.keyboard._ensureModifiers(restoreModifiers);
}, options, true);
}, deadline, options, true);
}

@@ -213,2 +222,3 @@ hover(options) {

async selectOption(values, options) {
const deadline = this._page._timeoutSettings.computeDeadline(options);
let vals;

@@ -230,25 +240,36 @@ if (!Array.isArray(values))

}
return await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
return this._evaluateInUtility(({ injected, node }, selectOptions) => injected.selectOptions(node, selectOptions), selectOptions);
}, options);
return await this._page._frameManager.waitForSignalsCreatedBy(async () => {
const injectedResult = await this._evaluateInUtility(({ injected, node }, selectOptions) => injected.selectOptions(node, selectOptions), selectOptions);
return handleInjectedResult(injectedResult, '');
}, deadline, options);
}
async fill(value, options) {
helper_1.assert(helper_1.helper.isString(value), 'Value must be string. Found value "' + value + '" of type "' + (typeof value) + '"');
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
const error = await this._evaluateInUtility(({ injected, node }, value) => injected.fill(node, value), value);
if (error)
throw new Error(error);
if (value)
await this._page.keyboard.insertText(value);
else
await this._page.keyboard.press('Delete');
}, options, true);
const deadline = this._page._timeoutSettings.computeDeadline(options);
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
const injectedResult = await this._evaluateInUtility(({ injected, node }, value) => injected.fill(node, value), value);
const needsInput = handleInjectedResult(injectedResult, '');
if (needsInput) {
if (value)
await this._page.keyboard.insertText(value);
else
await this._page.keyboard.press('Delete');
}
}, deadline, options, true);
}
async setInputFiles(files) {
const multiple = await this._evaluateInUtility(({ node }) => {
async selectText() {
const injectedResult = await this._evaluateInUtility(({ injected, node }) => injected.selectText(node), {});
handleInjectedResult(injectedResult, '');
}
async setInputFiles(files, options) {
const deadline = this._page._timeoutSettings.computeDeadline(options);
const injectedResult = await this._evaluateInUtility(({ node }) => {
if (node.nodeType !== Node.ELEMENT_NODE || node.tagName !== 'INPUT')
throw new Error('Node is not an HTMLInputElement');
return { status: 'error', error: 'Node is not an HTMLInputElement' };
if (!node.isConnected)
return { status: 'notconnected' };
const input = node;
return input.multiple;
return { status: 'success', value: input.multiple };
}, {});
const multiple = handleInjectedResult(injectedResult, '');
let ff;

@@ -264,5 +285,5 @@ if (!Array.isArray(files))

const file = {
name: platform.basename(item),
type: platform.getMimeType(item),
data: await platform.readFileAsync(item, 'base64')
name: path.basename(item),
mimeType: mime.getType(item) || 'application/octet-stream',
buffer: await util.promisify(fs.readFile)(item)
};

@@ -275,27 +296,23 @@ filePayloads.push(file);

}
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
await this._page._delegate.setInputFiles(this, filePayloads);
});
}, deadline, options);
}
async focus() {
const errorMessage = await this._evaluateInUtility(({ node }) => {
if (!node['focus'])
return 'Node is not an HTML or SVG element.';
node.focus();
return false;
}, {});
if (errorMessage)
throw new Error(errorMessage);
const injectedResult = await this._evaluateInUtility(({ injected, node }) => injected.focusNode(node), {});
handleInjectedResult(injectedResult, '');
}
async type(text, options) {
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
const deadline = this._page._timeoutSettings.computeDeadline(options);
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
await this.focus();
await this._page.keyboard.type(text, options);
}, options, true);
}, deadline, options, true);
}
async press(key, options) {
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
const deadline = this._page._timeoutSettings.computeDeadline(options);
await this._page._frameManager.waitForSignalsCreatedBy(async () => {
await this.focus();
await this._page.keyboard.press(key, options);
}, options, true);
}, deadline, options, true);
}

@@ -321,18 +338,18 @@ async check(options) {

}
$(selector) {
return this._context._$(selector, this);
async $(selector) {
return selectors_1.selectors._query(this._context.frame, selector, this);
}
$$(selector) {
return this._context._$$(selector, this);
async $$(selector) {
return selectors_1.selectors._queryAll(this._context.frame, selector, this);
}
async $eval(selector, pageFunction, arg) {
const elementHandle = await this._context._$(selector, this);
if (!elementHandle)
const handle = await selectors_1.selectors._query(this._context.frame, selector, this);
if (!handle)
throw new Error(`Error: failed to find element matching selector "${selector}"`);
const result = await elementHandle.evaluate(pageFunction, arg);
elementHandle.dispose();
const result = await handle.evaluate(pageFunction, arg);
handle.dispose();
return result;
}
async $$eval(selector, pageFunction, arg) {
const arrayHandle = await this._context._$array(selector, this);
const arrayHandle = await selectors_1.selectors._queryArray(this._context.frame, selector, this);
const result = await arrayHandle.evaluate(pageFunction, arg);

@@ -342,9 +359,14 @@ arrayHandle.dispose();

}
async _waitForDisplayedAtStablePosition(options = {}) {
async _waitForDisplayedAtStablePosition(deadline) {
this._page._log(exports.inputLog, 'waiting for element to be displayed and not moving...');
const stablePromise = this._evaluateInUtility(({ injected, node }, timeout) => {
return injected.waitForDisplayedAtStablePosition(node, timeout);
}, options.timeout || 0);
await helper_1.helper.waitWithTimeout(stablePromise, 'element to be displayed and not moving', options.timeout || 0);
}, helper_1.helper.timeUntilDeadline(deadline));
const timeoutMessage = 'element to be displayed and not moving';
const injectedResult = await helper_1.helper.waitWithDeadline(stablePromise, timeoutMessage, deadline);
handleInjectedResult(injectedResult, timeoutMessage);
this._page._log(exports.inputLog, '...done');
}
async _waitForHitTargetAt(point, options = {}) {
async _waitForHitTargetAt(point, deadline) {
this._page._log(exports.inputLog, `waiting for element to receive pointer events at (${point.x},${point.y}) ...`);
const frame = await this.ownerFrame();

@@ -361,19 +383,27 @@ if (frame && frame.parentFrame()) {

return injected.waitForHitTargetAt(node, timeout, point);
}, { timeout: options.timeout || 0, point });
await helper_1.helper.waitWithTimeout(hitTargetPromise, 'element to receive mouse events', options.timeout || 0);
}, { timeout: helper_1.helper.timeUntilDeadline(deadline), point });
const timeoutMessage = 'element to receive pointer events';
const injectedResult = await helper_1.helper.waitWithDeadline(hitTargetPromise, timeoutMessage, deadline);
handleInjectedResult(injectedResult, timeoutMessage);
this._page._log(exports.inputLog, '...done');
}
}
exports.ElementHandle = ElementHandle;
exports.setFileInputFunction = async (element, payloads) => {
const files = await Promise.all(payloads.map(async (file) => {
const result = await fetch(`data:${file.type};base64,${file.data}`);
return new File([await result.blob()], file.name, { type: file.type });
function toFileTransferPayload(files) {
return files.map(file => ({
name: file.name,
type: file.mimeType,
data: file.buffer.toString('base64')
}));
const dt = new DataTransfer();
for (const file of files)
dt.items.add(file);
element.files = dt.files;
element.dispatchEvent(new Event('input', { 'bubbles': true }));
element.dispatchEvent(new Event('change', { 'bubbles': true }));
};
}
exports.toFileTransferPayload = toFileTransferPayload;
function handleInjectedResult(injectedResult, timeoutMessage) {
if (injectedResult.status === 'notconnected')
throw new errors_1.NotConnectedError();
if (injectedResult.status === 'timeout')
throw new errors_1.TimeoutError(`waiting for ${timeoutMessage} failed: timeout exceeded`);
if (injectedResult.status === 'error')
throw new Error(injectedResult.error);
return injectedResult.value;
}
//# sourceMappingURL=dom.js.map

@@ -26,2 +26,8 @@ "use strict";

}
class NotConnectedError extends CustomError {
constructor() {
super('Element is not attached to the DOM');
}
}
exports.NotConnectedError = NotConnectedError;
class TimeoutError extends CustomError {

@@ -28,0 +34,0 @@ }

@@ -32,4 +32,6 @@ "use strict";

Close: 'close',
Crash: 'crash',
Console: 'console',
Dialog: 'dialog',
Download: 'download',
FileChooser: 'filechooser',

@@ -36,0 +38,0 @@ DOMContentLoaded: 'domcontentloaded',

@@ -25,3 +25,2 @@ "use strict";

const page_1 = require("../page");
const platform = require("../platform");
const transport_1 = require("../transport");

@@ -31,9 +30,11 @@ const ffConnection_1 = require("./ffConnection");

const ffPage_1 = require("./ffPage");
class FFBrowser extends platform.EventEmitter {
constructor(connection) {
super();
class FFBrowser extends browser_1.BrowserBase {
constructor(connection, logger, isPersistent) {
super(logger);
this._defaultContext = null;
this._firstPageCallback = () => { };
this._connection = connection;
this._ffPages = new Map();
this._defaultContext = new FFBrowserContext(this, null, browserContext_1.validateBrowserContextOptions({}));
if (isPersistent)
this._defaultContext = new FFBrowserContext(this, null, browserContext_1.validateBrowserContextOptions({}));
this._contexts = new Map();

@@ -48,8 +49,10 @@ this._connection.on(ffConnection_1.ConnectionEvents.Disconnected, () => {

helper_1.helper.addEventListener(this._connection, 'Browser.detachedFromTarget', this._onDetachedFromTarget.bind(this)),
helper_1.helper.addEventListener(this._connection, 'Browser.downloadCreated', this._onDownloadCreated.bind(this)),
helper_1.helper.addEventListener(this._connection, 'Browser.downloadFinished', this._onDownloadFinished.bind(this)),
];
this._firstPagePromise = new Promise(f => this._firstPageCallback = f);
}
static async connect(transport, attachToDefaultContext, slowMo) {
const connection = new ffConnection_1.FFConnection(transport_1.SlowMoTransport.wrap(transport, slowMo));
const browser = new FFBrowser(connection);
static async connect(transport, logger, attachToDefaultContext, slowMo) {
const connection = new ffConnection_1.FFConnection(transport_1.SlowMoTransport.wrap(transport, slowMo), logger);
const browser = new FFBrowser(connection, logger, attachToDefaultContext);
await connection.send('Browser.enable', { attachToDefaultContext });

@@ -65,3 +68,3 @@ return browser;

if (options.viewport) {
// TODO: remove isMobile/hasTouch from the protocol?
// TODO: remove isMobile from the protocol?
if (options.isMobile)

@@ -87,10 +90,13 @@ throw new Error('options.isMobile is not supported in Firefox');

bypassCSP: options.bypassCSP,
ignoreHTTPSErrors: options.ignoreHTTPSErrors,
javaScriptDisabled: options.javaScriptEnabled === false ? true : undefined,
viewport,
locale: options.locale,
removeOnDetach: true
timezoneId: options.timezoneId,
removeOnDetach: true,
downloadOptions: {
behavior: options.acceptDownloads ? 'saveToDisk' : 'cancel',
downloadsDir: this._downloadsPath,
},
});
// TODO: move ignoreHTTPSErrors to browser context level.
if (options.ignoreHTTPSErrors)
await this._connection.send('Browser.setIgnoreHTTPSErrors', { enabled: true });
const context = new FFBrowserContext(this, browserContextId, options);

@@ -104,5 +110,2 @@ await context._initialize();

}
async newPage(options) {
return browser_1.createPageInNewContext(this, options);
}
_onDetachedFromTarget(payload) {

@@ -117,2 +120,3 @@ const ffPage = this._ffPages.get(payload.targetId);

const context = browserContextId ? this._contexts.get(browserContextId) : this._defaultContext;
helper_1.assert(context, `Unknown context id:${browserContextId}, _defaultContext: ${this._defaultContext}`);
const session = this._connection.createSession(payload.sessionId, type);

@@ -122,2 +126,6 @@ const opener = openerId ? this._ffPages.get(openerId) : null;

this._ffPages.set(targetId, ffPage);
if (opener && opener._initializedPage) {
for (const signalBarrier of opener._initializedPage._frameManager._signalBarriers)
signalBarrier.addPopup(ffPage.pageOrError());
}
ffPage.pageOrError().then(async () => {

@@ -134,12 +142,17 @@ this._firstPageCallback();

}
async close() {
await Promise.all(this.contexts().map(context => context.close()));
_onDownloadCreated(payload) {
const ffPage = this._ffPages.get(payload.pageTargetId);
helper_1.assert(ffPage);
if (!ffPage)
return;
this._downloadCreated(ffPage._page, payload.uuid, payload.url);
}
_onDownloadFinished(payload) {
const error = payload.canceled ? 'canceled' : payload.error;
this._downloadFinished(payload.uuid, error);
}
_disconnect() {
helper_1.helper.removeEventListeners(this._eventListeners);
const disconnected = new Promise(f => this.once(events_1.Events.Browser.Disconnected, f));
this._connection.close();
await disconnected;
}
_setDebugFunction(debugFunction) {
this._connection._debugProtocol = debugFunction;
}
}

@@ -149,3 +162,3 @@ exports.FFBrowser = FFBrowser;

constructor(browser, browserContextId, options) {
super(options);
super(browser, options);
this._browser = browser;

@@ -166,2 +179,4 @@ this._browserContextId = browserContextId;

await this.setOffline(this._options.offline);
if (this._options.colorScheme)
await this._setColorScheme(this._options.colorScheme);
}

@@ -178,3 +193,3 @@ _ffPages() {

pages() {
return this._ffPages().map(ffPage => ffPage._initializedPage()).filter(pageOrNull => !!pageOrNull);
return this._ffPages().map(ffPage => ffPage._initializedPage).filter(pageOrNull => !!pageOrNull);
}

@@ -185,2 +200,6 @@ async newPage() {

browserContextId: this._browserContextId || undefined
}).catch(e => {
if (e.message.includes('Failed to override timezone'))
throw new Error(`Invalid timezone ID: ${this._options.timezoneId}`);
throw e;
});

@@ -246,2 +265,5 @@ const ffPage = this._browser._ffPages.get(targetId);

}
async _setColorScheme(colorScheme) {
await this._browser._connection.send('Browser.setColorScheme', { browserContextId: this._browserContextId || undefined, colorScheme });
}
async setHTTPCredentials(httpCredentials) {

@@ -272,2 +294,7 @@ this._options.httpCredentials = httpCredentials || undefined;

}
async unroute(url, handler) {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
if (this._routes.length === 0)
await this._browser._connection.send('Browser.setRequestInterception', { browserContextId: this._browserContextId || undefined, enabled: false });
}
async close() {

@@ -284,3 +311,3 @@ if (this._closed)

this._browser._contexts.delete(this._browserContextId);
this._didCloseInternal();
await this._didCloseInternal();
}

@@ -287,0 +314,0 @@ }

@@ -19,4 +19,5 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const helper_1 = require("../helper");
const platform = require("../platform");
const transport_1 = require("../transport");
exports.ConnectionEvents = {

@@ -28,7 +29,7 @@ Disconnected: Symbol('Disconnected'),

exports.kBrowserCloseMessageId = -9999;
class FFConnection extends platform.EventEmitter {
constructor(transport) {
class FFConnection extends events_1.EventEmitter {
constructor(transport, logger) {
super();
this._debugProtocol = platform.debug('pw:protocol');
this._transport = transport;
this._logger = logger;
this._lastId = 0;

@@ -45,3 +46,2 @@ this._callbacks = new Map();

this.once = super.once;
this._debugProtocol.color = '34';
}

@@ -59,29 +59,29 @@ async send(method, params) {

_rawSend(message) {
const data = JSON.stringify(message);
this._debugProtocol('SEND ► ' + (rewriteInjectedScriptEvaluationLog(message) || data));
this._transport.send(data);
if (this._logger._isLogEnabled(transport_1.protocolLog))
this._logger._log(transport_1.protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message));
this._transport.send(message);
}
async _onMessage(message) {
this._debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
if (object.id === exports.kBrowserCloseMessageId)
if (this._logger._isLogEnabled(transport_1.protocolLog))
this._logger._log(transport_1.protocolLog, '◀ RECV ' + JSON.stringify(message));
if (message.id === exports.kBrowserCloseMessageId)
return;
if (object.sessionId) {
const session = this._sessions.get(object.sessionId);
if (message.sessionId) {
const session = this._sessions.get(message.sessionId);
if (session)
session.dispatchMessage(object);
session.dispatchMessage(message);
}
else if (object.id) {
const callback = this._callbacks.get(object.id);
else if (message.id) {
const callback = this._callbacks.get(message.id);
// Callbacks could be all rejected if someone has called `.dispose()`.
if (callback) {
this._callbacks.delete(object.id);
if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object));
this._callbacks.delete(message.id);
if (message.error)
callback.reject(createProtocolError(callback.error, callback.method, message.error));
else
callback.resolve(object.result);
callback.resolve(message.result);
}
}
else {
Promise.resolve().then(() => this.emit(object.method, object.params));
Promise.resolve().then(() => this.emit(message.method, message.params));
}

@@ -115,6 +115,7 @@ }

};
class FFSession extends platform.EventEmitter {
class FFSession extends events_1.EventEmitter {
constructor(connection, targetType, sessionId, rawSend) {
super();
this._disposed = false;
this._crashed = false;
this._callbacks = new Map();

@@ -131,3 +132,8 @@ this._connection = connection;

}
markAsCrashed() {
this._crashed = true;
}
async send(method, params) {
if (this._crashed)
throw new Error('Page crashed');
if (this._disposed)

@@ -146,3 +152,3 @@ throw new Error(`Protocol error (${method}): Session closed. Most likely the ${this._targetType} has been closed.`);

if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object));
callback.reject(createProtocolError(callback.error, callback.method, object.error));
else

@@ -166,6 +172,6 @@ callback.resolve(object.result);

exports.FFSession = FFSession;
function createProtocolError(error, method, object) {
let message = `Protocol error (${method}): ${object.error.message}`;
if ('data' in object.error)
message += ` ${object.error.data}`;
function createProtocolError(error, method, protocolError) {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError)
message += ` ${protocolError.data}`;
return rewriteError(error, message);

@@ -182,3 +188,4 @@ }

return `{"id":${message.id} [evaluate injected script]}`;
return JSON.stringify(message);
}
//# sourceMappingURL=ffConnection.js.map

@@ -126,8 +126,8 @@ "use strict";

function checkException(exceptionDetails) {
if (exceptionDetails) {
if (exceptionDetails.value)
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
else
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
}
if (!exceptionDetails)
return;
if (exceptionDetails.value)
throw new Error('Evaluation failed: ' + JSON.stringify(exceptionDetails.value));
else
throw new Error('Evaluation failed: ' + exceptionDetails.text + '\n' + exceptionDetails.stack);
}

@@ -134,0 +134,0 @@ function deserializeValue({ unserializableValue, value }) {

@@ -21,3 +21,3 @@ "use strict";

const network = require("../network");
const platform = require("../platform");
const logger_1 = require("../logger");
class FFNetworkManager {

@@ -62,3 +62,3 @@ constructor(session, page) {

throw new Error(`Response body for ${request.request.method()} ${request.request.url()} was evicted!`);
return platform.Buffer.from(response.base64body, 'base64');
return Buffer.from(response.base64body, 'base64');
};

@@ -141,8 +141,6 @@ const headers = {};

postData: postData ? Buffer.from(postData).toString('base64') : undefined
}).catch(error => {
helper_1.debugError(error);
});
}).catch(logger_1.logError(this.request._page));
}
async fulfill(response) {
const responseBody = response.body && helper_1.helper.isString(response.body) ? platform.Buffer.from(response.body) : (response.body || null);
const responseBody = response.body && helper_1.helper.isString(response.body) ? Buffer.from(response.body) : (response.body || null);
const responseHeaders = {};

@@ -156,3 +154,3 @@ if (response.headers) {

if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
await this._session.send('Network.fulfillInterceptedRequest', {

@@ -164,5 +162,3 @@ requestId: this._id,

base64body: responseBody ? responseBody.toString('base64') : undefined,
}).catch(error => {
helper_1.debugError(error);
});
}).catch(logger_1.logError(this.request._page));
}

@@ -173,5 +169,3 @@ async abort(errorCode) {

errorCode,
}).catch(error => {
helper_1.debugError(error);
});
}).catch(logger_1.logError(this.request._page));
}

@@ -178,0 +172,0 @@ }

@@ -24,3 +24,2 @@ "use strict";

const page_1 = require("../page");
const platform = require("../platform");
const screenshotter_1 = require("../screenshotter");

@@ -32,7 +31,11 @@ const ffAccessibility_1 = require("./ffAccessibility");

const ffNetworkManager_1 = require("./ffNetworkManager");
const selectors_1 = require("../selectors");
const errors_1 = require("../errors");
const logger_1 = require("../logger");
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
class FFPage {
constructor(session, browserContext, opener) {
this.cspErrorsAsynchronousForInlineScipts = true;
this._pageCallback = () => { };
this._initialized = false;
this._initializedPage = null;
this._workers = new Map();

@@ -54,3 +57,3 @@ this._session = session;

helper_1.helper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)),
helper_1.helper.addEventListener(this._session, 'Page.navigationStarted', event => this._onNavigationStarted(event.frameId)),
helper_1.helper.addEventListener(this._session, 'Page.navigationStarted', this._onNavigationStarted.bind(this)),
helper_1.helper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),

@@ -60,2 +63,3 @@ helper_1.helper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)),

helper_1.helper.addEventListener(this._session, 'Page.linkClicked', event => this._onLinkClicked(event.phase)),
helper_1.helper.addEventListener(this._session, 'Page.willOpenNewWindowAsynchronously', this._onWillOpenNewWindowAsynchronously.bind(this)),
helper_1.helper.addEventListener(this._session, 'Page.uncaughtError', this._onUncaughtError.bind(this)),

@@ -73,24 +77,10 @@ helper_1.helper.addEventListener(this._session, 'Runtime.console', this._onConsole.bind(this)),

session.once(ffConnection_1.FFSessionEvents.Disconnected, () => this._page._didDisconnect());
this._initialize();
}
async _initialize() {
try {
await Promise.all([
// TODO: we should get rid of this call to resolve before any early events arrive, e.g. dialogs.
this._session.send('Page.addScriptToEvaluateOnNewDocument', {
script: '',
worldName: UTILITY_WORLD_NAME,
}),
new Promise(f => this._session.once('Page.ready', f)),
]);
this._session.once('Page.ready', () => {
this._pageCallback(this._page);
}
catch (e) {
this._pageCallback(e);
}
this._initialized = true;
this._initializedPage = this._page;
});
// Ideally, we somehow ensure that utility world is created before Page.ready arrives, but currently it is racy.
// Therefore, we can end up with an initialized page without utility world, although very unlikely.
this._session.send('Page.addScriptToEvaluateOnNewDocument', { script: '', worldName: UTILITY_WORLD_NAME }).catch(this._pageCallback);
}
_initializedPage() {
return this._initialized ? this._page : null;
}
async pageOrError() {

@@ -132,7 +122,15 @@ return this._pagePromise;

}
_onNavigationStarted(frameId) {
this._page._frameManager.frameRequestedNavigation(frameId);
_onWillOpenNewWindowAsynchronously() {
for (const barrier of this._page._frameManager._signalBarriers)
barrier.expectPopup();
}
_onNavigationStarted(params) {
this._page._frameManager.frameRequestedNavigation(params.frameId, params.navigationId);
}
_onNavigationAborted(params) {
const frame = this._page._frameManager.frame(params.frameId);
if (params.errorText === 'Will download to file') {
for (const barrier of this._page._frameManager._signalBarriers)
barrier.expectDownload();
}
for (const task of frame._frameTasks)

@@ -165,3 +163,4 @@ task.onNewDocument(params.navigationId, new Error(params.errorText));

_onUncaughtError(params) {
const error = new Error(params.message);
const message = params.message.startsWith('Error: ') ? params.message.substring(7) : params.message;
const error = new Error(message);
error.stack = params.stack;

@@ -177,3 +176,3 @@ this._page.emit(events_1.Events.Page.PageError, error);

this._page.emit(events_1.Events.Page.Dialog, new dialog.Dialog(params.type, params.message, async (accept, promptText) => {
await this._session.send('Page.handleDialog', { dialogId: params.dialogId, accept, promptText }).catch(helper_1.debugError);
await this._session.send('Page.handleDialog', { dialogId: params.dialogId, accept, promptText }).catch(logger_1.logError(this._page));
}, params.defaultValue));

@@ -193,3 +192,3 @@ }

const workerId = event.workerId;
const worker = new page_1.Worker(event.url);
const worker = new page_1.Worker(this._page, event.url);
const workerSession = new ffConnection_1.FFSession(this._session._connection, 'worker', workerId, (message) => {

@@ -232,2 +231,3 @@ this._session.send('Page.sendMessageToWorker', {

async _onCrashed(event) {
this._session.markAsCrashed();
this._page._didCrash();

@@ -260,6 +260,7 @@ }

}
async setEmulateMedia(mediaType, colorScheme) {
async updateEmulateMedia() {
const colorScheme = this._page._state.colorScheme || this._browserContext._options.colorScheme || 'light';
await this._session.send('Page.setEmulatedMedia', {
type: mediaType === null ? undefined : mediaType,
colorScheme: colorScheme === null ? undefined : colorScheme
type: this._page._state.mediaType === null ? undefined : this._page._state.mediaType,
colorScheme
});

@@ -326,3 +327,3 @@ }

});
return platform.Buffer.from(data, 'base64');
return Buffer.from(data, 'base64');
}

@@ -374,2 +375,6 @@ async resetViewport() {

rect,
}).catch(e => {
if (e instanceof Error && e.message.includes('Node is detached from document'))
throw new errors_1.NotConnectedError();
throw e;
});

@@ -381,3 +386,3 @@ }

objectId: toRemoteObject(handle).objectId,
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
if (!result)

@@ -391,3 +396,3 @@ return null;

async setInputFiles(handle, files) {
await handle.evaluate(dom.setFileInputFunction, files);
await handle._evaluateInUtility(({ injected, node }, files) => injected.setInputFiles(node, files), dom.toFileTransferPayload(files));
}

@@ -413,4 +418,3 @@ async adoptElementHandle(handle, to) {

throw new Error('Frame has been detached.');
const context = await parent._utilityContext();
const handles = await context._$$('iframe');
const handles = await selectors_1.selectors._queryAll(parent, 'iframe', undefined, true /* allowUtilityContext */);
const items = await Promise.all(handles.map(async (handle) => {

@@ -417,0 +421,0 @@ const frame = await handle.contentFrame().catch(e => null);

@@ -19,7 +19,11 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const types = require("./types");
const helper_1 = require("./helper");
const fs = require("fs");
const util = require("util");
const dom = require("./dom");
const errors_1 = require("./errors");
const events_1 = require("./events");
const platform = require("./platform");
const helper_1 = require("./helper");
const selectors_1 = require("./selectors");
const types = require("./types");
const hints_1 = require("./hints");
class FrameManager {

@@ -29,3 +33,3 @@ constructor(page) {

this._consoleMessageTags = new Map();
this._pendingNavigationBarriers = new Set();
this._signalBarriers = new Set();
this._page = page;

@@ -73,7 +77,7 @@ this._mainFrame = undefined;

}
async waitForNavigationsCreatedBy(action, options = {}, input) {
if (options.waitUntil === 'nowait')
async waitForSignalsCreatedBy(action, deadline, options = {}, input) {
if (options.noWaitAfter)
return action();
const barrier = new PendingNavigationBarrier({ waitUntil: 'domcontentloaded', ...options });
this._pendingNavigationBarriers.add(barrier);
const barrier = new SignalBarrier(options, deadline);
this._signalBarriers.add(barrier);
try {

@@ -85,31 +89,39 @@ const result = await action();

// Resolve in the next task, after all waitForNavigations.
await new Promise(platform.makeWaitForNextTask());
await new Promise(helper_1.helper.makeWaitForNextTask());
return result;
}
finally {
this._pendingNavigationBarriers.delete(barrier);
this._signalBarriers.delete(barrier);
}
}
frameWillPotentiallyRequestNavigation() {
for (const barrier of this._pendingNavigationBarriers)
for (const barrier of this._signalBarriers)
barrier.retain();
}
frameDidPotentiallyRequestNavigation() {
for (const barrier of this._pendingNavigationBarriers)
for (const barrier of this._signalBarriers)
barrier.release();
}
frameRequestedNavigation(frameId) {
frameRequestedNavigation(frameId, documentId) {
const frame = this._frames.get(frameId);
if (!frame)
return;
for (const barrier of this._pendingNavigationBarriers)
barrier.addFrame(frame);
for (const barrier of this._signalBarriers)
barrier.addFrameNavigation(frame);
frame._pendingDocumentId = documentId;
}
frameUpdatedDocumentIdForNavigation(frameId, documentId) {
const frame = this._frames.get(frameId);
if (!frame)
return;
frame._pendingDocumentId = documentId;
}
frameCommittedNewDocumentNavigation(frameId, url, name, documentId, initial) {
const frame = this._frames.get(frameId);
for (const child of frame.childFrames())
this._removeFramesRecursively(child);
this.removeChildFramesRecursively(frame);
frame._url = url;
frame._name = name;
helper_1.assert(!frame._pendingDocumentId || frame._pendingDocumentId === documentId);
frame._lastDocumentId = documentId;
frame._pendingDocumentId = '';
for (const task of frame._frameTasks)

@@ -164,8 +176,5 @@ task.onNewDocument(documentId);

frame._inflightRequests = new Set(Array.from(frame._inflightRequests).filter(request => request._documentId === frame._lastDocumentId));
this._stopNetworkIdleTimer(frame, 'networkidle0');
frame._stopNetworkIdleTimer();
if (frame._inflightRequests.size === 0)
this._startNetworkIdleTimer(frame, 'networkidle0');
this._stopNetworkIdleTimer(frame, 'networkidle2');
if (frame._inflightRequests.size <= 2)
this._startNetworkIdleTimer(frame, 'networkidle2');
frame._startNetworkIdleTimer();
}

@@ -191,4 +200,5 @@ requestStarted(request) {

if (request._documentId) {
const isCurrentDocument = request.frame()._lastDocumentId === request._documentId;
if (!isCurrentDocument) {
const isPendingDocument = request.frame()._pendingDocumentId === request._documentId;
if (isPendingDocument) {
request.frame()._pendingDocumentId = '';
let errorText = request.failure().errorText;

@@ -214,5 +224,8 @@ if (canceled)

}
_removeFramesRecursively(frame) {
removeChildFramesRecursively(frame) {
for (const child of frame.childFrames())
this._removeFramesRecursively(child);
}
_removeFramesRecursively(frame) {
this.removeChildFramesRecursively(frame);
frame._onDetached();

@@ -230,5 +243,3 @@ this._frames.delete(frame._id);

if (frame._inflightRequests.size === 0)
this._startNetworkIdleTimer(frame, 'networkidle0');
if (frame._inflightRequests.size === 2)
this._startNetworkIdleTimer(frame, 'networkidle2');
frame._startNetworkIdleTimer();
}

@@ -241,20 +252,4 @@ _inflightRequestStarted(request) {

if (frame._inflightRequests.size === 1)
this._stopNetworkIdleTimer(frame, 'networkidle0');
if (frame._inflightRequests.size === 3)
this._stopNetworkIdleTimer(frame, 'networkidle2');
frame._stopNetworkIdleTimer();
}
_startNetworkIdleTimer(frame, event) {
helper_1.assert(!frame._networkIdleTimers.has(event));
if (frame._firedLifecycleEvents.has(event))
return;
frame._networkIdleTimers.set(event, setTimeout(() => {
this.frameLifecycleEvent(frame._id, event);
}, 500));
}
_stopNetworkIdleTimer(frame, event) {
const timeoutId = frame._networkIdleTimers.get(event);
if (timeoutId)
clearTimeout(timeoutId);
frame._networkIdleTimers.delete(event);
}
interceptConsoleMessage(message) {

@@ -276,2 +271,3 @@ if (message.type() !== 'debug')

this._lastDocumentId = '';
this._pendingDocumentId = '';
this._frameTasks = new Set();

@@ -284,3 +280,2 @@ this._url = '';

this._inflightRequests = new Set();
this._networkIdleTimers = new Map();
this._setContentCounter = 0;

@@ -330,2 +325,5 @@ this._detachedCallback = () => { };

async waitForNavigation(options = {}) {
return this._waitForNavigation(options);
}
async _waitForNavigation(options = {}) {
const frameTask = new FrameTask(this, options);

@@ -338,3 +336,4 @@ let documentId;

const request = documentId ? frameTask.request(documentId) : null;
await frameTask.waitForLifecycle(options.waitUntil === undefined ? 'load' : options.waitUntil);
if (options.waitUntil !== 'commit')
await frameTask.waitForLifecycle(options.waitUntil === undefined ? 'load' : options.waitUntil);
frameTask.done();

@@ -371,11 +370,3 @@ return request ? request._finalRequest().response() : null;

async $(selector) {
const utilityContext = await this._utilityContext();
const mainContext = await this._mainContext();
const handle = await utilityContext._$(selector);
if (handle && handle._context !== mainContext) {
const adopted = this._page._delegate.adoptElementHandle(handle, mainContext);
handle.dispose();
return adopted;
}
return handle;
return selectors_1.selectors._query(this, selector);
}

@@ -385,7 +376,8 @@ async waitForSelector(selector, options) {

throw new Error('options.visibility is not supported, did you mean options.waitFor?');
const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'attached' } = (options || {});
const { waitFor = 'attached' } = (options || {});
if (!['attached', 'detached', 'visible', 'hidden'].includes(waitFor))
throw new Error(`Unsupported waitFor option "${waitFor}"`);
const task = waitForSelectorTask(selector, waitFor, timeout);
const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, waitFor)}"`);
const deadline = this._page._timeoutSettings.computeDeadline(options);
const { world, task } = selectors_1.selectors._waitForSelectorTask(selector, waitFor, deadline);
const result = await this._scheduleRerunnableTask(task, world, deadline, `selector "${selectorToString(selector, waitFor)}"`);
if (!result.asElement()) {

@@ -405,13 +397,11 @@ result.dispose();

async $eval(selector, pageFunction, arg) {
const context = await this._mainContext();
const elementHandle = await context._$(selector);
if (!elementHandle)
const handle = await this.$(selector);
if (!handle)
throw new Error(`Error: failed to find element matching selector "${selector}"`);
const result = await elementHandle.evaluate(pageFunction, arg);
elementHandle.dispose();
const result = await handle.evaluate(pageFunction, arg);
handle.dispose();
return result;
}
async $$eval(selector, pageFunction, arg) {
const context = await this._mainContext();
const arrayHandle = await context._$array(selector);
const arrayHandle = await selectors_1.selectors._queryArray(this, selector);
const result = await arrayHandle.evaluate(pageFunction, arg);

@@ -422,4 +412,3 @@ arrayHandle.dispose();

async $$(selector) {
const context = await this._mainContext();
return context._$$(selector);
return selectors_1.selectors._queryAll(this, selector);
}

@@ -479,8 +468,16 @@ async content() {

return (await context.evaluateHandleInternal(addScriptUrl, { url, type })).asElement();
let result;
if (path !== null) {
let contents = await platform.readFileAsync(path, 'utf8');
let contents = await util.promisify(fs.readFile)(path, 'utf8');
contents += '//# sourceURL=' + path.replace(/\n/g, '');
return (await context.evaluateHandleInternal(addScriptContent, { content: contents, type })).asElement();
result = (await context.evaluateHandleInternal(addScriptContent, { content: contents, type })).asElement();
}
return (await context.evaluateHandleInternal(addScriptContent, { content: content, type })).asElement();
else {
result = (await context.evaluateHandleInternal(addScriptContent, { content: content, type })).asElement();
}
// Another round trip to the browser to ensure that we receive CSP error messages
// (if any) logged asynchronously in a separate task on the content main thread.
if (this._page._delegate.cspErrorsAsynchronousForInlineScipts)
await context.evaluateInternal(() => true);
return result;
});

@@ -494,3 +491,3 @@ async function addScriptUrl(options) {

script.onload = res;
script.onerror = rej;
script.onerror = e => rej(typeof e === 'string' ? new Error(e) : new Error(`Failed to load script at ${script.src}`));
});

@@ -522,3 +519,3 @@ document.head.appendChild(script);

if (path !== null) {
let contents = await platform.readFileAsync(path, 'utf8');
let contents = await util.promisify(fs.readFile)(path, 'utf8');
contents += '/*# sourceURL=' + path.replace(/\n/g, '') + '*/';

@@ -584,58 +581,64 @@ return (await context.evaluateHandleInternal(addStyleContent, contents)).asElement();

}
async click(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.click(options);
handle.dispose();
async _retryWithSelectorIfNotConnected(selector, options, action) {
const deadline = this._page._timeoutSettings.computeDeadline(options);
while (!helper_1.helper.isPastDeadline(deadline)) {
try {
const { world, task } = selectors_1.selectors._waitForSelectorTask(selector, 'attached', deadline);
const handle = await this._scheduleRerunnableTask(task, world, deadline, `selector "${selector}"`);
const element = handle.asElement();
try {
return await action(element, deadline);
}
finally {
element.dispose();
}
}
catch (e) {
if (!(e instanceof errors_1.NotConnectedError))
throw e;
this._page._log(dom.inputLog, 'Element was detached from the DOM, retrying');
}
}
throw new errors_1.TimeoutError(`waiting for selector "${selector}" failed: timeout exceeded`);
}
async dblclick(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.dblclick(options);
handle.dispose();
async click(selector, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.click(helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async fill(selector, value, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.fill(value, options);
handle.dispose();
async dblclick(selector, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.dblclick(helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async focus(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.focus();
handle.dispose();
async fill(selector, value, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.fill(value, helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async hover(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.hover(options);
handle.dispose();
async focus(selector, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.focus());
}
async selectOption(selector, values, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
const result = await handle.selectOption(values, options);
handle.dispose();
return result;
async hover(selector, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.hover(helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async type(selector, text, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.type(text, options);
handle.dispose();
async selectOption(selector, values, options = {}) {
return await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.selectOption(values, helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async press(selector, key, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.press(key, options);
handle.dispose();
async setInputFiles(selector, files, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.setInputFiles(files, helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async check(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.check(options);
handle.dispose();
async type(selector, text, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.type(text, helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async uncheck(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.uncheck(options);
handle.dispose();
async press(selector, key, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.press(key, helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async check(selector, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.check(helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async uncheck(selector, options = {}) {
await this._retryWithSelectorIfNotConnected(selector, options, (handle, deadline) => handle.uncheck(helper_1.helper.optionsWithUpdatedTimeout(options, deadline)));
}
async waitFor(selectorOrFunctionOrTimeout, options = {}, arg) {
if (helper_1.helper.isString(selectorOrFunctionOrTimeout))
return this.waitForSelector(selectorOrFunctionOrTimeout, options);
if (helper_1.helper.isNumber(selectorOrFunctionOrTimeout))
if (helper_1.helper.isNumber(selectorOrFunctionOrTimeout)) {
hints_1.waitForTimeWasUsed(this._page);
return new Promise(fulfill => setTimeout(fulfill, selectorOrFunctionOrTimeout));
}
if (typeof selectorOrFunctionOrTimeout === 'function')

@@ -645,10 +648,5 @@ return this.waitForFunction(selectorOrFunctionOrTimeout, arg, options);

}
async _waitForSelectorInUtilityContext(selector, options) {
const { timeout = this._page._timeoutSettings.timeout(), waitFor = 'attached' } = (options || {});
const task = waitForSelectorTask(selector, waitFor, timeout);
const result = await this._scheduleRerunnableTask(task, 'utility', timeout, `selector "${selectorToString(selector, waitFor)}"`);
return result.asElement();
}
async waitForFunction(pageFunction, arg, options = {}) {
const { polling = 'raf', timeout = this._page._timeoutSettings.timeout() } = options;
const { polling = 'raf' } = options;
const deadline = this._page._timeoutSettings.computeDeadline(options);
if (helper_1.helper.isString(polling))

@@ -663,7 +661,5 @@ helper_1.assert(polling === 'raf' || polling === 'mutation', 'Unknown polling option: ' + polling);

const innerPredicate = new Function('arg', predicateBody);
return injected.poll(polling, undefined, timeout, (element) => {
return innerPredicate(arg);
});
}, { injected: await context._injected(), predicateBody, polling, timeout, arg });
return this._scheduleRerunnableTask(task, 'main', timeout);
return injected.poll(polling, timeout, () => innerPredicate(arg));
}, { injected: await context._injected(), predicateBody, polling, timeout: helper_1.helper.timeUntilDeadline(deadline), arg });
return this._scheduleRerunnableTask(task, 'main', deadline);
}

@@ -685,5 +681,5 @@ async title() {

}
_scheduleRerunnableTask(task, contextType, timeout, title) {
_scheduleRerunnableTask(task, contextType, deadline, title) {
const data = this._contextData.get(contextType);
const rerunnableTask = new RerunnableTask(data, task, timeout, title);
const rerunnableTask = new RerunnableTask(data, task, deadline, title);
data.rerunnableTasks.add(rerunnableTask);

@@ -723,23 +719,17 @@ if (data.context)

}
_startNetworkIdleTimer() {
helper_1.assert(!this._networkIdleTimer);
if (this._firedLifecycleEvents.has('networkidle'))
return;
this._networkIdleTimer = setTimeout(() => { this._page._frameManager.frameLifecycleEvent(this._id, 'networkidle'); }, 500);
}
_stopNetworkIdleTimer() {
if (this._networkIdleTimer)
clearTimeout(this._networkIdleTimer);
this._networkIdleTimer = undefined;
}
}
exports.Frame = Frame;
function waitForSelectorTask(selector, waitFor, timeout) {
return async (context) => context.evaluateHandleInternal(({ injected, selector, waitFor, timeout }) => {
const polling = (waitFor === 'attached' || waitFor === 'detached') ? 'mutation' : 'raf';
return injected.poll(polling, selector, timeout, (element) => {
switch (waitFor) {
case 'attached':
return element || false;
case 'detached':
return !element;
case 'visible':
return element && injected.isVisible(element) ? element : false;
case 'hidden':
return !element || !injected.isVisible(element);
}
});
}, { injected: await context._injected(), selector, waitFor, timeout });
}
class RerunnableTask {
constructor(data, task, timeout, title) {
constructor(data, task, deadline, title) {
this._resolve = () => { };

@@ -757,6 +747,4 @@ this._reject = () => { };

// timeout on our end.
if (timeout) {
const timeoutError = new errors_1.TimeoutError(`waiting for ${title || 'function'} failed: timeout ${timeout}ms exceeded`);
this._timeoutTimer = setTimeout(() => this.terminate(timeoutError), timeout);
}
const timeoutError = new errors_1.TimeoutError(`waiting for ${title || 'function'} failed: timeout exceeded`);
this._timeoutTimer = setTimeout(() => this.terminate(timeoutError), helper_1.helper.timeUntilDeadline(deadline));
}

@@ -828,8 +816,11 @@ terminate(error) {

}
class PendingNavigationBarrier {
constructor(options) {
class SignalBarrier {
constructor(options, deadline) {
this._frameIds = new Map();
this._protectCount = 0;
this._expectedPopups = 0;
this._expectedDownloads = 0;
this._promiseCallback = () => { };
this._options = options;
this._deadline = deadline;
this._promise = new Promise(f => this._promiseCallback = f);

@@ -842,7 +833,30 @@ this.retain();

}
async addFrame(frame) {
async addFrameNavigation(frame) {
this.retain();
await frame.waitForNavigation(this._options).catch(e => { });
const options = helper_1.helper.optionsWithUpdatedTimeout(this._options, this._deadline);
await frame._waitForNavigation({ ...options, waitUntil: 'commit' }).catch(e => { });
this.release();
}
async expectPopup() {
++this._expectedPopups;
}
async unexpectPopup() {
--this._expectedPopups;
this._maybeResolve();
}
async addPopup(pageOrError) {
if (this._expectedPopups)
--this._expectedPopups;
this.retain();
await pageOrError;
this.release();
}
async expectDownload() {
++this._expectedDownloads;
}
async addDownload() {
if (this._expectedDownloads)
--this._expectedDownloads;
this._maybeResolve();
}
retain() {

@@ -856,6 +870,7 @@ ++this._protectCount;

async _maybeResolve() {
if (!this._protectCount && !this._frameIds.size)
if (!this._protectCount && !this._expectedPopups && !this._expectedDownloads && !this._frameIds.size)
this._promiseCallback();
}
}
exports.SignalBarrier = SignalBarrier;
class FrameTask {

@@ -873,3 +888,3 @@ constructor(frame, options, url) {

if (timeout) {
const errorMessage = 'Navigation timeout of ' + timeout + ' ms exceeded';
const errorMessage = 'Navigation timeout exceeded';
timeoutPromise = new Promise(fulfill => this._timer = setTimeout(fulfill, timeout))

@@ -876,0 +891,0 @@ .then(() => { throw new errors_1.TimeoutError(errorMessage); });

@@ -19,5 +19,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const crypto = require("crypto");
const fs = require("fs");
const util = require("util");
const errors_1 = require("./errors");
const platform = require("./platform");
exports.debugError = platform.debug(`pw:error`);
class Helper {

@@ -42,3 +43,3 @@ static evaluationString(fun, ...args) {

else if (fun.path !== undefined) {
let contents = await platform.readFileAsync(fun.path, 'utf8');
let contents = await util.promisify(fs.readFile)(fun.path, 'utf8');
if (addSourceUrl)

@@ -55,3 +56,2 @@ contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');

static installApiHooks(className, classType) {
const log = platform.debug('pw:api');
for (const methodName of Reflect.ownKeys(classType.prototype)) {

@@ -62,3 +62,3 @@ const method = Reflect.get(classType.prototype, methodName);

const isAsync = method.constructor.name === 'AsyncFunction';
if (!isAsync && !log.enabled)
if (!isAsync)
continue;

@@ -68,22 +68,2 @@ Reflect.set(classType.prototype, methodName, function (...args) {

Error.captureStackTrace(syncStack);
if (log.enabled) {
const frames = syncStack.stack.substring('Error\n'.length)
.split('\n')
.map((f) => f.replace(/\s+at\s/, '').trim());
const userCall = frames.length <= 1 || !frames[1].includes('playwright/lib');
if (userCall) {
const match = /([^/\\]+)(:\d+:\d+)[)]?$/.exec(frames[1]);
let location = '';
if (match) {
const fileName = exports.helper.trimMiddle(match[1], 20 - match[2].length);
location = `\u001b[33m[${fileName}${match[2]}]\u001b[39m `;
}
if (args.length)
log(`${location}${className}.${methodName} %o`, args);
else
log(`${location}${className}.${methodName}`);
}
}
if (!isAsync)
return method.call(this, ...args);
return method.call(this, ...args).catch((e) => {

@@ -123,4 +103,3 @@ const stack = syncStack.stack.substring(syncStack.stack.indexOf('\n') + 1);

}
static async waitForEvent(emitter, eventName, predicate, timeout, abortPromise) {
let eventTimeout;
static async waitForEvent(emitter, eventName, predicate, deadline, abortPromise) {
let resolveCallback = () => { };

@@ -142,7 +121,5 @@ let rejectCallback = () => { };

});
if (timeout) {
eventTimeout = setTimeout(() => {
rejectCallback(new errors_1.TimeoutError(`Timeout exceeded while waiting for ${String(eventName)}`));
}, timeout);
}
const eventTimeout = setTimeout(() => {
rejectCallback(new errors_1.TimeoutError(`Timeout exceeded while waiting for ${String(eventName)}`));
}, exports.helper.timeUntilDeadline(deadline));
function cleanup() {

@@ -152,3 +129,3 @@ Helper.removeEventListeners([listener]);

}
const result = await Promise.race([promise, abortPromise]).then(r => {
return await Promise.race([promise, abortPromise]).then(r => {
cleanup();

@@ -160,13 +137,11 @@ return r;

});
if (result instanceof Error)
throw result;
return result;
}
static async waitWithTimeout(promise, taskName, timeout) {
return this.waitWithDeadline(promise, taskName, exports.helper.monotonicTime() + timeout);
}
static async waitWithDeadline(promise, taskName, deadline) {
let reject;
const timeoutError = new errors_1.TimeoutError(`waiting for ${taskName} failed: timeout ${timeout}ms exceeded`);
const timeoutError = new errors_1.TimeoutError(`waiting for ${taskName} failed: timeout exceeded`);
const timeoutPromise = new Promise((resolve, x) => reject = x);
let timeoutTimer = null;
if (timeout)
timeoutTimer = setTimeout(() => reject(timeoutError), timeout);
const timeoutTimer = setTimeout(() => reject(timeoutError), exports.helper.timeUntilDeadline(deadline));
try {

@@ -272,2 +247,49 @@ return await Promise.race([promise, timeoutPromise]);

}
// See https://joel.tools/microtasks/
static makeWaitForNextTask() {
if (parseInt(process.versions.node, 10) >= 11)
return setImmediate;
// Unlike Node 11, Node 10 and less have a bug with Task and MicroTask execution order:
// - https://github.com/nodejs/node/issues/22257
//
// So we can't simply run setImmediate to dispatch code in a following task.
// However, we can run setImmediate from-inside setImmediate to make sure we're getting
// in the following task.
let spinning = false;
const callbacks = [];
const loop = () => {
const callback = callbacks.shift();
if (!callback) {
spinning = false;
return;
}
setImmediate(loop);
// Make sure to call callback() as the last thing since it's
// untrusted code that might throw.
callback();
};
return (callback) => {
callbacks.push(callback);
if (!spinning) {
spinning = true;
setImmediate(loop);
}
};
}
static guid() {
return crypto.randomBytes(16).toString('hex');
}
static monotonicTime() {
const [seconds, nanoseconds] = process.hrtime();
return seconds * 1000 + (nanoseconds / 1000000 | 0);
}
static isPastDeadline(deadline) {
return deadline !== Number.MAX_SAFE_INTEGER && this.monotonicTime() >= deadline;
}
static timeUntilDeadline(deadline) {
return Math.min(deadline - this.monotonicTime(), 2147483647); // 2^31-1 safe setTimeout in Node.
}
static optionsWithUpdatedTimeout(options, deadline) {
return { ...(options || {}), timeout: this.timeUntilDeadline(deadline) };
}
}

@@ -274,0 +296,0 @@ function assert(value, message) {

@@ -38,38 +38,9 @@ "use strict";

_keyDescriptionForString(keyString) {
let description = usKeyboardLayout.get(keyString);
helper_1.assert(description, `Unknown key: "${keyString}"`);
const shift = this._pressedModifiers.has('Shift');
const description = {
key: '',
keyCode: 0,
keyCodeWithoutLocation: 0,
code: '',
text: '',
location: 0
};
const definition = keyboardLayout.keyDefinitions[keyString];
helper_1.assert(definition, `Unknown key: "${keyString}"`);
if (definition.key)
description.key = definition.key;
if (shift && definition.shiftKey)
description.key = definition.shiftKey;
if (definition.keyCode)
description.keyCode = definition.keyCode;
if (shift && definition.shiftKeyCode)
description.keyCode = definition.shiftKeyCode;
if (definition.code)
description.code = definition.code;
if (definition.location)
description.location = definition.location;
if (description.key.length === 1)
description.text = description.key;
if (definition.text)
description.text = definition.text;
if (shift && definition.shiftText)
description.text = definition.shiftText;
description = shift && description.shifted ? description.shifted : description;
// if any modifiers besides shift are pressed, no text should be sent
if (this._pressedModifiers.size > 1 || (!this._pressedModifiers.has('Shift') && this._pressedModifiers.size === 1))
description.text = '';
if (definition.keyCodeWithoutLocation)
description.keyCodeWithoutLocation = definition.keyCodeWithoutLocation;
else
description.keyCodeWithoutLocation = description.keyCode;
return { ...description, text: '' };
return description;

@@ -90,3 +61,3 @@ }

for (const char of text) {
if (keyboardLayout.keyDefinitions[char]) {
if (usKeyboardLayout.has(char)) {
await this.press(char, { delay });

@@ -102,2 +73,21 @@ }

async press(key, options = {}) {
function split(keyString) {
const keys = [];
let building = '';
for (const char of keyString) {
if (char === '+' && building) {
keys.push(building);
building = '';
}
else {
building += char;
}
}
keys.push(building);
return keys;
}
const tokens = split(key);
key = tokens[tokens.length - 1];
for (let i = 0; i < tokens.length - 1; ++i)
await this.down(tokens[i]);
await this.down(key);

@@ -107,2 +97,4 @@ if (options.delay)

await this.up(key);
for (let i = tokens.length - 2; i >= 0; --i)
await this.up(tokens[i]);
}

@@ -112,3 +104,3 @@ async _ensureModifiers(modifiers) {

if (!kModifiers.includes(modifier))
throw new Error('Uknown modifier ' + modifier);
throw new Error('Unknown modifier ' + modifier);
}

@@ -193,2 +185,53 @@ const restore = Array.from(this._pressedModifiers);

exports.Mouse = Mouse;
const aliases = new Map([
['ShiftLeft', ['Shift']],
['ControlLeft', ['Control']],
['AltLeft', ['Alt']],
['MetaLeft', ['Meta']],
['Enter', ['\n', '\r']],
]);
const usKeyboardLayout = buildLayoutClosure(keyboardLayout.USKeyboardLayout);
function buildLayoutClosure(layout) {
const result = new Map();
for (const code in layout) {
const definition = layout[code];
const description = {
key: definition.key || '',
keyCode: definition.keyCode || 0,
keyCodeWithoutLocation: definition.keyCodeWithoutLocation || definition.keyCode || 0,
code,
text: definition.text || '',
location: definition.location || 0,
};
if (definition.key.length === 1)
description.text = description.key;
// Generate shifted definition.
let shiftedDescription;
if (definition.shiftKey) {
helper_1.assert(definition.shiftKey.length === 1);
shiftedDescription = { ...description };
shiftedDescription.key = definition.shiftKey;
shiftedDescription.text = definition.shiftKey;
if (definition.shiftKeyCode)
shiftedDescription.keyCode = definition.shiftKeyCode;
}
// Map from code: Digit3 -> { ... descrption, shifted }
result.set(code, { ...description, shifted: shiftedDescription });
// Map from aliases: Shift -> non-shiftable definition
if (aliases.has(code)) {
for (const alias of aliases.get(code))
result.set(alias, description);
}
// Do not use numpad when converting keys to codes.
if (definition.location)
continue;
// Map from key, no shifted
if (description.key.length === 1)
result.set(description.key, description);
// Map from shiftKey, no shifted
if (shiftedDescription)
result.set(shiftedDescription.key, { ...shiftedDescription, shifted: undefined });
}
return result;
}
//# sourceMappingURL=input.js.map

@@ -18,6 +18,7 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const platform = require("./platform");
const helper_1 = require("./helper");
class ExecutionContext {
constructor(delegate) {
constructor(delegate, logger) {
this._delegate = delegate;
this._logger = logger;
}

@@ -108,3 +109,3 @@ _doEvaluateInternal(returnByValue, waitForNavigations, pageFunction, ...args) {

const pushHandle = (handle) => {
const guid = platform.guid();
const guid = helper_1.helper.guid();
guids.push(guid);

@@ -111,0 +112,0 @@ handles.push(handle);

@@ -18,4 +18,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const mime = require("mime");
const util = require("util");
const helper_1 = require("./helper");
const platform = require("./platform");
function filterCookies(cookies, urls = []) {

@@ -78,2 +80,3 @@ if (!Array.isArray(urls))

this._frame = frame;
this._page = frame._page;
this._redirectedFrom = redirectedFrom;

@@ -108,3 +111,3 @@ if (redirectedFrom)

headers() {
return this._headers;
return { ...this._headers };
}

@@ -171,4 +174,4 @@ response() {

headers: response.headers,
contentType: platform.getMimeType(response.path),
body: await platform.readFileBuffer(response.path)
contentType: mime.getType(response.path) || 'application/octet-stream',
body: await util.promisify(fs.readFile)(response.path)
};

@@ -214,3 +217,3 @@ }

headers() {
return this._headers;
return { ...this._headers };
}

@@ -217,0 +220,0 @@ finished() {

@@ -30,4 +30,7 @@ "use strict";

const accessibility = require("./accessibility");
const platform = require("./platform");
class Page extends platform.EventEmitter {
const extendedEventEmitter_1 = require("./extendedEventEmitter");
const events_2 = require("events");
const fileChooser_1 = require("./fileChooser");
const logger_1 = require("./logger");
class Page extends extendedEventEmitter_1.ExtendedEventEmitter {
constructor(delegate, browserContext) {

@@ -69,2 +72,8 @@ super();

}
_abortPromiseForEvent(event) {
return this._disconnectedPromise;
}
_computeDeadline(options) {
return this._timeoutSettings.computeDeadline(options);
}
_didClose() {

@@ -77,6 +86,3 @@ helper_1.assert(!this._closed, 'Page closed twice');

_didCrash() {
const error = new Error('Page crashed!');
// Do not report node.js stack.
error.stack = 'Error: ' + error.message; // Stack is supposed to contain error message as the first line.
this.emit('error', error);
this.emit(events_1.Events.Page.Crash);
}

@@ -94,3 +100,3 @@ _didDisconnect() {

}
const fileChooser = { element: handle, multiple };
const fileChooser = new fileChooser_1.FileChooser(this, handle, multiple);
this.emit(events_1.Events.Page.FileChooser, fileChooser);

@@ -197,10 +203,4 @@ }

}
async waitForEvent(event, optionsOrPredicate = {}) {
if (typeof optionsOrPredicate === 'function')
optionsOrPredicate = { predicate: optionsOrPredicate };
const { timeout = this._timeoutSettings.timeout(), predicate = () => true } = optionsOrPredicate;
return helper_1.helper.waitForEvent(this, event, (...args) => !!predicate(...args), timeout, this._disconnectedPromise);
}
async waitForRequest(urlOrPredicate, options = {}) {
const { timeout = this._timeoutSettings.timeout() } = options;
const deadline = this._timeoutSettings.computeDeadline(options);
return helper_1.helper.waitForEvent(this, events_1.Events.Page.Request, (request) => {

@@ -210,6 +210,6 @@ if (helper_1.helper.isString(urlOrPredicate) || helper_1.helper.isRegExp(urlOrPredicate))

return urlOrPredicate(request);
}, timeout, this._disconnectedPromise);
}, deadline, this._disconnectedPromise);
}
async waitForResponse(urlOrPredicate, options = {}) {
const { timeout = this._timeoutSettings.timeout() } = options;
const deadline = this._timeoutSettings.computeDeadline(options);
return helper_1.helper.waitForEvent(this, events_1.Events.Page.Response, (response) => {

@@ -219,3 +219,3 @@ if (helper_1.helper.isString(urlOrPredicate) || helper_1.helper.isRegExp(urlOrPredicate))

return urlOrPredicate(response);
}, timeout, this._disconnectedPromise);
}, deadline, this._disconnectedPromise);
}

@@ -247,3 +247,3 @@ async goBack(options) {

this._state.colorScheme = options.colorScheme;
await this._delegate.setEmulateMedia(this._state.mediaType, this._state.colorScheme);
await this._delegate.updateEmulateMedia();
}

@@ -270,2 +270,6 @@ async setViewportSize(viewportSize) {

}
async unroute(url, handler) {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
await this._delegate.updateRequestInterception();
}
_requestStarted(request) {

@@ -328,2 +332,5 @@ this.emit(events_1.Events.Page.Request, request);

}
async setInputFiles(selector, files, options) {
return this.mainFrame().setInputFiles(selector, files, options);
}
async type(selector, text, options) {

@@ -381,8 +388,15 @@ return this.mainFrame().type(selector, text, options);

}
_isLogEnabled(log) {
return this._browserContext._isLogEnabled(log);
}
_log(log, message, ...args) {
return this._browserContext._log(log, message, ...args);
}
}
exports.Page = Page;
class Worker extends platform.EventEmitter {
constructor(url) {
class Worker extends events_2.EventEmitter {
constructor(logger, url) {
super();
this._existingExecutionContext = null;
this._logger = logger;
this._url = url;

@@ -393,3 +407,3 @@ this._executionContextCallback = () => { };

_createExecutionContext(delegate) {
this._existingExecutionContext = new js.ExecutionContext(delegate);
this._existingExecutionContext = new js.ExecutionContext(delegate, this._logger);
this._executionContextCallback(this._existingExecutionContext);

@@ -430,3 +444,3 @@ }

}
context.evaluateInternal(expression).catch(helper_1.debugError);
context.evaluateInternal(expression).catch(logger_1.logError(page));
function deliverResult(name, seq, result) {

@@ -433,0 +447,0 @@ window[name]['callbacks'].get(seq).resolve(result);

@@ -19,4 +19,6 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const mime = require("mime");
const util = require("util");
const helper_1 = require("./helper");
const platform = require("./platform");
class Screenshotter {

@@ -132,3 +134,3 @@ constructor(page) {

if (options.path)
await platform.writeFileAsync(options.path, buffer);
await util.promisify(fs.writeFile)(options.path, buffer);
return buffer;

@@ -171,3 +173,3 @@ }

else if (options.path) {
const mimeType = platform.getMimeType(options.path);
const mimeType = mime.getType(options.path);
if (mimeType === 'image/png')

@@ -174,0 +176,0 @@ format = 'png';

@@ -18,19 +18,26 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const selectorEvaluatorSource = require("./generated/selectorEvaluatorSource");
const helper_1 = require("./helper");
let selectors;
const kEvaluatorSymbol = Symbol('evaluator');
class Selectors {
constructor() {
this._generation = 0;
// Note: keep in sync with SelectorEvaluator class.
this._builtinEngines = new Set([
'css', 'css:light',
'xpath', 'xpath:light',
'text', 'text:light',
'id', 'id:light',
'data-testid', 'data-testid:light',
'data-test-id', 'data-test-id:light',
'data-test', 'data-test:light'
]);
this._engines = new Map();
}
static _instance() {
if (!selectors)
selectors = new Selectors();
return selectors;
}
async register(name, script) {
async register(name, script, options = {}) {
const { contentScript = false } = options;
if (!name.match(/^[a-zA-Z_0-9-]+$/))
throw new Error('Selector engine name may only contain [a-zA-Z0-9_] characters');
// Note: keep in sync with Injected class, and also keep 'zs' for future.
if (['css', 'xpath', 'text', 'id', 'zs', 'data-testid', 'data-test-id', 'data-test'].includes(name))
// Note: we keep 'zs' for future use.
if (this._builtinEngines.has(name) || name === 'zs' || name === 'zs:light')
throw new Error(`"${name}" is a predefined selector engine`);

@@ -40,13 +47,156 @@ const source = await helper_1.helper.evaluationScript(script, undefined, false);

throw new Error(`"${name}" selector engine has been already registered`);
this._engines.set(name, source);
this._engines.set(name, { source, contentScript });
++this._generation;
}
_needsMainContext(parsed) {
return parsed.some(({ name }) => {
const custom = this._engines.get(name);
return custom ? !custom.contentScript : false;
});
}
async _prepareEvaluator(context) {
let data = context[kEvaluatorSymbol];
if (data && data.generation !== this._generation) {
data.promise.then(handle => handle.dispose());
data = undefined;
}
if (!data) {
const custom = [];
for (const [name, { source }] of this._engines)
custom.push(`{ name: '${name}', engine: (${source}) }`);
const source = `
new (${selectorEvaluatorSource.source})([
${custom.join(',\n')}
])
`;
data = {
promise: context._doEvaluateInternal(false /* returnByValue */, false /* waitForNavigations */, source),
generation: this._generation
};
context[kEvaluatorSymbol] = data;
}
return data.promise;
}
async _query(frame, selector, scope) {
const parsed = this._parseSelector(selector);
const context = this._needsMainContext(parsed) ? await frame._mainContext() : await frame._utilityContext();
const handle = await context.evaluateHandleInternal(({ evaluator, parsed, scope }) => evaluator.querySelector(parsed, scope || document), { evaluator: await this._prepareEvaluator(context), parsed, scope });
const elementHandle = handle.asElement();
if (!elementHandle) {
handle.dispose();
return null;
}
const mainContext = await frame._mainContext();
if (elementHandle._context === mainContext)
return elementHandle;
const adopted = frame._page._delegate.adoptElementHandle(elementHandle, mainContext);
elementHandle.dispose();
return adopted;
}
async _queryArray(frame, selector, scope) {
const parsed = this._parseSelector(selector);
const context = await frame._mainContext();
const arrayHandle = await context.evaluateHandleInternal(({ evaluator, parsed, scope }) => evaluator.querySelectorAll(parsed, scope || document), { evaluator: await this._prepareEvaluator(context), parsed, scope });
return arrayHandle;
}
async _queryAll(frame, selector, scope, allowUtilityContext) {
const parsed = this._parseSelector(selector);
const context = !allowUtilityContext || this._needsMainContext(parsed) ? await frame._mainContext() : await frame._utilityContext();
const arrayHandle = await context.evaluateHandleInternal(({ evaluator, parsed, scope }) => evaluator.querySelectorAll(parsed, scope || document), { evaluator: await this._prepareEvaluator(context), parsed, scope });
const properties = await arrayHandle.getProperties();
arrayHandle.dispose();
const result = [];
for (const property of properties.values()) {
const elementHandle = property.asElement();
if (elementHandle)
result.push(elementHandle);
else
property.dispose();
}
return result;
}
_waitForSelectorTask(selector, waitFor, deadline) {
const parsed = this._parseSelector(selector);
const task = async (context) => context.evaluateHandleInternal(({ evaluator, parsed, waitFor, timeout }) => {
const polling = (waitFor === 'attached' || waitFor === 'detached') ? 'mutation' : 'raf';
return evaluator.injected.poll(polling, timeout, () => {
const element = evaluator.querySelector(parsed, document);
switch (waitFor) {
case 'attached':
return element || false;
case 'detached':
return !element;
case 'visible':
return element && evaluator.injected.isVisible(element) ? element : false;
case 'hidden':
return !element || !evaluator.injected.isVisible(element);
}
});
}, { evaluator: await this._prepareEvaluator(context), parsed, waitFor, timeout: helper_1.helper.timeUntilDeadline(deadline) });
return { world: this._needsMainContext(parsed) ? 'main' : 'utility', task };
}
async _createSelector(name, handle) {
const mainContext = await handle._page.mainFrame()._mainContext();
return mainContext.evaluateInternal(({ injected, target, name }) => {
return injected.engines.get(name).create(document.documentElement, target);
}, { injected: await mainContext._injected(), target: handle, name });
return mainContext.evaluateInternal(({ evaluator, target, name }) => {
return evaluator.engines.get(name).create(document.documentElement, target);
}, { evaluator: await this._prepareEvaluator(mainContext), target: handle, name });
}
_parseSelector(selector) {
helper_1.assert(helper_1.helper.isString(selector), `selector must be a string`);
let index = 0;
let quote;
let start = 0;
const result = [];
const append = () => {
const part = selector.substring(start, index).trim();
const eqIndex = part.indexOf('=');
let name;
let body;
if (eqIndex !== -1 && part.substring(0, eqIndex).trim().match(/^[a-zA-Z_0-9-+:]+$/)) {
name = part.substring(0, eqIndex).trim();
body = part.substring(eqIndex + 1);
}
else if (part.startsWith('"')) {
name = 'text';
body = part;
}
else if (/^\(*\/\//.test(part)) {
// If selector starts with '//' or '//' prefixed with multiple opening
// parenthesis, consider xpath. @see https://github.com/microsoft/playwright/issues/817
name = 'xpath';
body = part;
}
else {
name = 'css';
body = part;
}
name = name.toLowerCase();
if (!this._builtinEngines.has(name) && !this._engines.has(name))
throw new Error(`Unknown engine ${name} while parsing selector ${selector}`);
result.push({ name, body });
};
while (index < selector.length) {
const c = selector[index];
if (c === '\\' && index + 1 < selector.length) {
index += 2;
}
else if (c === quote) {
quote = undefined;
index++;
}
else if (!quote && c === '>' && selector[index + 1] === '>') {
append();
index += 2;
start = index;
}
else {
index++;
}
}
append();
return result;
}
}
exports.Selectors = Selectors;
exports.selectors = new Selectors();
//# sourceMappingURL=selectors.js.map

@@ -29,5 +29,4 @@ "use strict";

const helper_1 = require("../helper");
const platform = require("../platform");
const unlinkAsync = platform.promisify(fs.unlink.bind(fs));
const chmodAsync = platform.promisify(fs.chmod.bind(fs));
const unlinkAsync = util.promisify(fs.unlink.bind(fs));
const chmodAsync = util.promisify(fs.chmod.bind(fs));
const existsAsync = (path) => new Promise(resolve => fs.stat(path, err => resolve(!err)));

@@ -42,2 +41,3 @@ const DEFAULT_DOWNLOAD_HOSTS = {

'linux': '%s/chromium-browser-snapshots/Linux_x64/%d/chrome-linux.zip',
'mac10.13': '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',
'mac10.14': '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',

@@ -50,2 +50,3 @@ 'mac10.15': '%s/chromium-browser-snapshots/Mac/%d/chrome-mac.zip',

'linux': '%s/builds/firefox/%s/firefox-linux.zip',
'mac10.13': '%s/builds/firefox/%s/firefox-mac.zip',
'mac10.14': '%s/builds/firefox/%s/firefox-mac.zip',

@@ -58,2 +59,3 @@ 'mac10.15': '%s/builds/firefox/%s/firefox-mac.zip',

'linux': '%s/builds/webkit/%s/minibrowser-gtk-wpe.zip',
'mac10.13': undefined,
'mac10.14': '%s/builds/webkit/%s/minibrowser-mac-10.14.zip',

@@ -68,2 +70,3 @@ 'mac10.15': '%s/builds/webkit/%s/minibrowser-mac-10.15.zip',

'linux': ['chrome-linux', 'chrome'],
'mac10.13': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],
'mac10.14': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],

@@ -76,2 +79,3 @@ 'mac10.15': ['chrome-mac', 'Chromium.app', 'Contents', 'MacOS', 'Chromium'],

'linux': ['firefox', 'firefox'],
'mac10.13': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],
'mac10.14': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],

@@ -84,6 +88,7 @@ 'mac10.15': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],

'linux': ['pw_run.sh'],
'mac10.13': undefined,
'mac10.14': ['pw_run.sh'],
'mac10.15': ['pw_run.sh'],
'win32': ['MiniBrowser.exe'],
'win64': ['MiniBrowser.exe'],
'win32': ['Playwright.exe'],
'win64': ['Playwright.exe'],
},

@@ -114,10 +119,9 @@ };

helper_1.assert(downloadPath, '`downloadPath` must be provided');
if (await existsAsync(downloadPath))
return;
const url = revisionURL(options);
const zipPath = path.join(os.tmpdir(), `playwright-download-${browser}-${platform}-${revision}.zip`);
if (await existsAsync(downloadPath))
throw new Error('ERROR: downloadPath folder already exists!');
try {
await downloadFile(url, zipPath, progress);
// await mkdirAsync(downloadPath, {recursive: true});
await extractZip(zipPath, downloadPath);
await extract(zipPath, { dir: downloadPath });
}

@@ -133,3 +137,5 @@ finally {

const { browser, downloadPath, platform = CURRENT_HOST_PLATFORM, } = options;
return path.join(downloadPath, ...RELATIVE_EXECUTABLE_PATHS[browser][platform]);
const relativePath = RELATIVE_EXECUTABLE_PATHS[browser][platform];
helper_1.assert(relativePath, `Unsupported platform for ${browser}: ${platform}`);
return path.join(downloadPath, ...relativePath);
}

@@ -180,10 +186,2 @@ exports.executablePath = executablePath;

}
function extractZip(zipPath, folderPath) {
return new Promise((fulfill, reject) => extract(zipPath, { dir: folderPath }, err => {
if (err)
reject(err);
else
fulfill();
}));
}
function httpRequest(url, method, response) {

@@ -190,0 +188,0 @@ let options = URL.parse(url);

@@ -19,11 +19,34 @@ "use strict";

const child_process_1 = require("child_process");
const platform = require("../platform");
class BrowserServer extends platform.EventEmitter {
constructor(process, gracefullyClose, wsEndpoint) {
const events_1 = require("events");
class WebSocketWrapper {
constructor(wsEndpoint, bindings) {
this.wsEndpoint = wsEndpoint;
this._bindings = bindings;
}
async checkLeaks() {
let counter = 0;
return new Promise((fulfill, reject) => {
const check = () => {
const filtered = this._bindings.filter(entry => entry.size);
if (!filtered.length) {
fulfill();
return;
}
if (++counter >= 50) {
reject(new Error('Web socket leak ' + filtered.map(entry => [...entry.keys()].join(':')).join('|')));
return;
}
setTimeout(check, 100);
};
check();
});
}
}
exports.WebSocketWrapper = WebSocketWrapper;
class BrowserServer extends events_1.EventEmitter {
constructor(process, gracefullyClose, webSocketWrapper) {
super();
this._browserWSEndpoint = '';
this._process = process;
this._gracefullyClose = gracefullyClose;
if (wsEndpoint)
this._browserWSEndpoint = wsEndpoint;
this._webSocketWrapper = webSocketWrapper;
}

@@ -34,3 +57,3 @@ process() {

wsEndpoint() {
return this._browserWSEndpoint;
return this._webSocketWrapper ? this._webSocketWrapper.wsEndpoint : '';
}

@@ -53,4 +76,8 @@ kill() {

}
async _checkLeaks() {
if (this._webSocketWrapper)
await this._webSocketWrapper.checkLeaks();
}
}
exports.BrowserServer = BrowserServer;
//# sourceMappingURL=browserServer.js.map

@@ -22,7 +22,7 @@ "use strict";

const path = require("path");
const util = require("util");
const helper_1 = require("../helper");
const crBrowser_1 = require("../chromium/crBrowser");
const platform = require("../platform");
const errors_1 = require("../errors");
const processLauncher_1 = require("../server/processLauncher");
const ws = require("ws");
const processLauncher_1 = require("./processLauncher");
const crConnection_1 = require("../chromium/crConnection");

@@ -32,2 +32,4 @@ const pipeTransport_1 = require("./pipeTransport");

const events_1 = require("../events");
const transport_1 = require("../transport");
const logger_1 = require("../logger");
class Chromium {

@@ -42,22 +44,25 @@ executablePath() {

}
async launch(options) {
if (options && options.userDataDir)
throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
const { browserServer, transport } = await this._launchServer(options, 'local');
const browser = await crBrowser_1.CRBrowser.connect(transport, false, options && options.slowMo);
browser['__server__'] = browserServer;
async launch(options = {}) {
helper_1.assert(!options.userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
const { browserServer, transport, downloadsPath, logger } = await this._launchServer(options, 'local');
const browser = await crBrowser_1.CRBrowser.connect(transport, false, logger, options.slowMo);
browser._ownedServer = browserServer;
browser._downloadsPath = downloadsPath;
return browser;
}
async launchServer(options) {
return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer;
async launchServer(options = {}) {
return (await this._launchServer(options, 'server')).browserServer;
}
async launchPersistentContext(userDataDir, options) {
const { timeout = 30000 } = options || {};
const { transport } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await crBrowser_1.CRBrowser.connect(transport, true);
async launchPersistentContext(userDataDir, options = {}) {
const { timeout = 30000, slowMo = 0, } = options;
const { transport, browserServer, logger } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await crBrowser_1.CRBrowser.connect(transport, true, logger, slowMo);
browser._ownedServer = browserServer;
await helper_1.helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout);
return browser._defaultContext;
}
async _launchServer(options = {}, launchType, userDataDir, port) {
const { ignoreDefaultArgs = false, args = [], dumpio = false, executablePath = null, env = process.env, handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, timeout = 30000 } = options;
async _launchServer(options, launchType, userDataDir) {
const { ignoreDefaultArgs = false, args = [], executablePath = null, env = process.env, handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, port = 0, } = options;
helper_1.assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.');
const logger = new logger_1.RootLogger(options.logger);
let temporaryUserDataDir = null;

@@ -70,5 +75,5 @@ if (!userDataDir) {

if (!ignoreDefaultArgs)
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir, port || 0));
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir));
else if (Array.isArray(ignoreDefaultArgs))
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir, port || 0).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
chromeArguments.push(...this._defaultArgs(options, launchType, userDataDir).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
else

@@ -79,4 +84,3 @@ chromeArguments.push(...args);

throw new Error(`No executable path is specified. Pass "executablePath" option directly.`);
let browserServer = undefined;
const { launchedProcess, gracefullyClose } = await processLauncher_1.launchProcess({
const { launchedProcess, gracefullyClose, downloadsPath } = await processLauncher_1.launchProcess({
executablePath: chromeExecutable,

@@ -88,14 +92,13 @@ args: chromeArguments,

handleSIGHUP,
dumpio,
pipe: launchType !== 'server',
logger,
pipe: true,
tempDir: temporaryUserDataDir || undefined,
attemptToGracefullyClose: async () => {
if (!browserServer)
return Promise.reject();
helper_1.assert(browserServer);
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that it's fine to reuse the pipe transport, since
// our connection ignores kBrowserCloseMessageId.
const t = transport || await platform.connectToWebsocket(browserWSEndpoint, async (transport) => transport);
const message = { method: 'Browser.close', id: crConnection_1.kBrowserCloseMessageId };
await t.send(JSON.stringify(message));
const t = transport;
const message = { method: 'Browser.close', id: crConnection_1.kBrowserCloseMessageId, params: {} };
t.send(message);
},

@@ -107,24 +110,15 @@ onkill: (exitCode, signal) => {

});
let transport;
let browserWSEndpoint;
if (launchType === 'server') {
const timeoutError = new errors_1.TimeoutError(`Timed out after ${timeout} ms while trying to connect to Chromium!`);
const match = await processLauncher_1.waitForLine(launchedProcess, launchedProcess.stderr, /^DevTools listening on (ws:\/\/.*)$/, timeout, timeoutError);
browserWSEndpoint = match[1];
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, browserWSEndpoint);
return { browserServer };
}
else {
// For local launch scenario close will terminate the browser process.
transport = new pipeTransport_1.PipeTransport(launchedProcess.stdio[3], launchedProcess.stdio[4], () => browserServer.close());
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, null);
return { browserServer, transport };
}
let transport = undefined;
let browserServer = undefined;
const stdio = launchedProcess.stdio;
transport = new pipeTransport_1.PipeTransport(stdio[3], stdio[4], logger);
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, logger, port) : null);
return { browserServer, transport, downloadsPath, logger };
}
async connect(options) {
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
return crBrowser_1.CRBrowser.connect(transport, false, options.slowMo);
return await transport_1.WebSocketTransport.connect(options.wsEndpoint, transport => {
return crBrowser_1.CRBrowser.connect(transport, false, new logger_1.RootLogger(options.logger), options.slowMo);
});
}
_defaultArgs(options = {}, launchType, userDataDir, port) {
_defaultArgs(options = {}, launchType, userDataDir) {
const { devtools = false, headless = !devtools, args = [], } = options;

@@ -134,3 +128,3 @@ const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir'));

throw new Error('Pass userDataDir parameter instead of specifying --user-data-dir argument');
if (args.find(arg => arg.startsWith('--remote-debugging-')))
if (args.find(arg => arg.startsWith('--remote-debugging-pipe')))
throw new Error('Playwright manages remote debugging connection itself.');

@@ -141,3 +135,3 @@ if (launchType !== 'persistent' && args.find(arg => !arg.startsWith('-')))

chromeArguments.push(`--user-data-dir=${userDataDir}`);
chromeArguments.push(launchType === 'server' ? `--remote-debugging-port=${port || 0}` : '--remote-debugging-pipe');
chromeArguments.push('--remote-debugging-pipe');
if (devtools)

@@ -160,3 +154,109 @@ chromeArguments.push('--auto-open-devtools-for-tabs');

exports.Chromium = Chromium;
const mkdtempAsync = platform.promisify(fs.mkdtemp);
function wrapTransportWithWebSocket(transport, logger, port) {
const server = new ws.Server({ port });
const guid = helper_1.helper.guid();
const awaitingBrowserTarget = new Map();
const sessionToSocket = new Map();
const socketToBrowserSession = new Map();
const browserSessions = new Set();
let lastSequenceNumber = 1;
transport.onmessage = message => {
if (typeof message.id === 'number' && awaitingBrowserTarget.has(message.id)) {
const freshSocket = awaitingBrowserTarget.get(message.id);
awaitingBrowserTarget.delete(message.id);
const sessionId = message.result.sessionId;
if (freshSocket.readyState !== ws.CLOSED && freshSocket.readyState !== ws.CLOSING) {
sessionToSocket.set(sessionId, freshSocket);
const { queue } = socketToBrowserSession.get(freshSocket);
for (const item of queue) {
item.sessionId = sessionId;
transport.send(item);
}
socketToBrowserSession.set(freshSocket, { sessionId });
browserSessions.add(sessionId);
}
else {
transport.send({
id: ++lastSequenceNumber,
method: 'Target.detachFromTarget',
params: { sessionId }
});
socketToBrowserSession.delete(freshSocket);
}
return;
}
// At this point everything we care about has sessionId.
if (!message.sessionId)
return;
const socket = sessionToSocket.get(message.sessionId);
if (socket && socket.readyState !== ws.CLOSING) {
if (message.method === 'Target.attachedToTarget')
sessionToSocket.set(message.params.sessionId, socket);
if (message.method === 'Target.detachedFromTarget')
sessionToSocket.delete(message.params.sessionId);
// Strip session ids from the browser sessions.
if (browserSessions.has(message.sessionId))
delete message.sessionId;
socket.send(JSON.stringify(message));
}
};
transport.onclose = () => {
for (const socket of socketToBrowserSession.keys()) {
socket.removeListener('close', socket.__closeListener);
socket.close(undefined, 'Browser disconnected');
}
server.close();
transport.onmessage = undefined;
transport.onclose = undefined;
};
server.on('connection', (socket, req) => {
if (req.url !== '/' + guid) {
socket.close();
return;
}
socketToBrowserSession.set(socket, { queue: [] });
transport.send({
id: ++lastSequenceNumber,
method: 'Target.attachToBrowserTarget',
params: {}
});
awaitingBrowserTarget.set(lastSequenceNumber, socket);
socket.on('message', (message) => {
const parsedMessage = JSON.parse(Buffer.from(message).toString());
// If message has sessionId, pass through.
if (parsedMessage.sessionId) {
transport.send(parsedMessage);
return;
}
// If message has no sessionId, look it up.
const session = socketToBrowserSession.get(socket);
if (session.sessionId) {
// We have it, use it.
parsedMessage.sessionId = session.sessionId;
transport.send(parsedMessage);
return;
}
// Pending session id, queue the message.
session.queue.push(parsedMessage);
});
socket.on('error', logger_1.logError(logger));
socket.on('close', socket.__closeListener = () => {
const session = socketToBrowserSession.get(socket);
if (!session || !session.sessionId)
return;
sessionToSocket.delete(session.sessionId);
browserSessions.delete(session.sessionId);
socketToBrowserSession.delete(socket);
transport.send({
id: ++lastSequenceNumber,
method: 'Target.detachFromTarget',
params: { sessionId: session.sessionId }
});
});
});
const address = server.address();
const wsEndpoint = typeof address === 'string' ? `${address}/${guid}` : `ws://127.0.0.1:${address.port}/${guid}`;
return new browserServer_1.WebSocketWrapper(wsEndpoint, [awaitingBrowserTarget, sessionToSocket, socketToBrowserSession, browserSessions]);
}
const mkdtempAsync = util.promisify(fs.mkdtemp);
const CHROMIUM_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');

@@ -163,0 +263,0 @@ const DEFAULT_ARGS = [

@@ -22,2 +22,4 @@ "use strict";

const path = require("path");
const util = require("util");
const ws = require("ws");
const errors_1 = require("../errors");

@@ -28,6 +30,7 @@ const events_1 = require("../events");

const helper_1 = require("../helper");
const platform = require("../platform");
const browserServer_1 = require("./browserServer");
const processLauncher_1 = require("./processLauncher");
const mkdtempAsync = platform.promisify(fs.mkdtemp);
const transport_1 = require("../transport");
const logger_1 = require("../logger");
const mkdtempAsync = util.promisify(fs.mkdtemp);
class Firefox {

@@ -42,31 +45,31 @@ executablePath() {

}
async launch(options) {
if (options && options.userDataDir)
throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
const browserServer = await this._launchServer(options, 'local');
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint(), transport => {
return ffBrowser_1.FFBrowser.connect(transport, false, options && options.slowMo);
async launch(options = {}) {
helper_1.assert(!options.userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
const { browserServer, downloadsPath, logger } = await this._launchServer(options, 'local');
const browser = await transport_1.WebSocketTransport.connect(browserServer.wsEndpoint(), transport => {
return ffBrowser_1.FFBrowser.connect(transport, logger, false, options.slowMo);
});
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
browser.close = () => browserServer.close();
browser['__server__'] = browserServer;
browser._ownedServer = browserServer;
browser._downloadsPath = downloadsPath;
return browser;
}
async launchServer(options) {
return await this._launchServer(options, 'server', undefined, options && options.port);
async launchServer(options = {}) {
return (await this._launchServer(options, 'server')).browserServer;
}
async launchPersistentContext(userDataDir, options) {
const { timeout = 30000 } = options || {};
const browserServer = await this._launchServer(options, 'persistent', userDataDir);
const browser = await platform.connectToWebsocket(browserServer.wsEndpoint(), transport => {
return ffBrowser_1.FFBrowser.connect(transport, true);
async launchPersistentContext(userDataDir, options = {}) {
const { timeout = 30000, slowMo = 0, } = options;
const { browserServer, downloadsPath, logger } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await transport_1.WebSocketTransport.connect(browserServer.wsEndpoint(), transport => {
return ffBrowser_1.FFBrowser.connect(transport, logger, true, slowMo);
});
browser._ownedServer = browserServer;
browser._downloadsPath = downloadsPath;
await helper_1.helper.waitWithTimeout(browser._firstPagePromise, 'first page', timeout);
// Hack: for typical launch scenario, ensure that close waits for actual process termination.
const browserContext = browser._defaultContext;
browserContext.close = () => browserServer.close();
return browserContext;
}
async _launchServer(options = {}, launchType, userDataDir, port) {
const { ignoreDefaultArgs = false, args = [], dumpio = false, executablePath = null, env = process.env, handleSIGHUP = true, handleSIGINT = true, handleSIGTERM = true, timeout = 30000, } = options;
async _launchServer(options, launchType, userDataDir) {
const { ignoreDefaultArgs = false, args = [], executablePath = null, env = process.env, handleSIGHUP = true, handleSIGINT = true, handleSIGTERM = true, timeout = 30000, port = 0, } = options;
helper_1.assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.');
const logger = new logger_1.RootLogger(options.logger);
const firefoxArguments = [];

@@ -79,5 +82,5 @@ let temporaryProfileDir = null;

if (!ignoreDefaultArgs)
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, port || 0));
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, 0));
else if (Array.isArray(ignoreDefaultArgs))
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, port || 0).filter(arg => !ignoreDefaultArgs.includes(arg)));
firefoxArguments.push(...this._defaultArgs(options, launchType, userDataDir, 0).filter(arg => !ignoreDefaultArgs.includes(arg)));
else

@@ -88,4 +91,3 @@ firefoxArguments.push(...args);

throw new Error(`No executable path is specified. Pass "executablePath" option directly.`);
let browserServer = undefined;
const { launchedProcess, gracefullyClose } = await processLauncher_1.launchProcess({
const { launchedProcess, gracefullyClose, downloadsPath } = await processLauncher_1.launchProcess({
executablePath: firefoxExecutable,

@@ -101,14 +103,11 @@ args: firefoxArguments,

handleSIGHUP,
dumpio,
logger,
pipe: false,
tempDir: temporaryProfileDir || undefined,
attemptToGracefullyClose: async () => {
if (!browserServer)
return Promise.reject();
helper_1.assert(browserServer);
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that it's fine to reuse the pipe transport, since
// our connection ignores kBrowserCloseMessageId.
const transport = await platform.connectToWebsocket(browserWSEndpoint, async (transport) => transport);
const transport = await transport_1.WebSocketTransport.connect(browserWSEndpoint, async (transport) => transport);
const message = { method: 'Browser.close', params: {}, id: ffConnection_1.kBrowserCloseMessageId };
await transport.send(JSON.stringify(message));
await transport.send(message);
},

@@ -122,9 +121,14 @@ onkill: (exitCode, signal) => {

const match = await processLauncher_1.waitForLine(launchedProcess, launchedProcess.stdout, /^Juggler listening on (ws:\/\/.*)$/, timeout, timeoutError);
const browserWSEndpoint = match[1];
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, browserWSEndpoint);
return browserServer;
const innerEndpoint = match[1];
let browserServer = undefined;
let browserWSEndpoint = undefined;
const webSocketWrapper = launchType === 'server' ? (await transport_1.WebSocketTransport.connect(innerEndpoint, t => wrapTransportWithWebSocket(t, logger, port))) : new browserServer_1.WebSocketWrapper(innerEndpoint, []);
browserWSEndpoint = webSocketWrapper.wsEndpoint;
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper);
return { browserServer, downloadsPath, logger };
}
async connect(options) {
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
return ffBrowser_1.FFBrowser.connect(transport, false, options.slowMo);
const logger = new logger_1.RootLogger(options.logger);
return await transport_1.WebSocketTransport.connect(options.wsEndpoint, transport => {
return ffBrowser_1.FFBrowser.connect(transport, logger, false, options.slowMo);
});

@@ -135,3 +139,3 @@ }

if (devtools)
throw new Error('Option "devtools" is not supported by Firefox');
console.warn('devtools parameter is not supported as a launch argument in Firefox. You can launch the devtools window manually.');
const userDataDirArg = args.find(arg => arg.startsWith('-profile') || arg.startsWith('--profile'));

@@ -155,4 +159,9 @@ if (userDataDirArg)

firefoxArguments.push(...args);
if (args.every(arg => arg.startsWith('-')))
firefoxArguments.push('about:blank');
if (launchType === 'persistent') {
if (args.every(arg => arg.startsWith('-')))
firefoxArguments.push('about:blank');
}
else {
firefoxArguments.push('-silent');
}
return firefoxArguments;

@@ -162,2 +171,117 @@ }

exports.Firefox = Firefox;
function wrapTransportWithWebSocket(transport, logger, port) {
const server = new ws.Server({ port });
const guid = helper_1.helper.guid();
const idMixer = new transport_1.SequenceNumberMixer();
const pendingBrowserContextCreations = new Set();
const pendingBrowserContextDeletions = new Map();
const browserContextIds = new Map();
const sessionToSocket = new Map();
const sockets = new Set();
transport.onmessage = message => {
if (typeof message.id === 'number') {
// Process command response.
const seqNum = message.id;
const value = idMixer.take(seqNum);
if (!value)
return;
const { id, socket } = value;
if (socket.readyState === ws.CLOSING) {
if (pendingBrowserContextCreations.has(id)) {
transport.send({
id: ++transport_1.SequenceNumberMixer._lastSequenceNumber,
method: 'Browser.removeBrowserContext',
params: { browserContextId: message.result.browserContextId }
});
}
return;
}
if (pendingBrowserContextCreations.has(seqNum)) {
// Browser.createBrowserContext response -> establish context attribution.
browserContextIds.set(message.result.browserContextId, socket);
pendingBrowserContextCreations.delete(seqNum);
}
const deletedContextId = pendingBrowserContextDeletions.get(seqNum);
if (deletedContextId) {
// Browser.removeBrowserContext response -> remove context attribution.
browserContextIds.delete(deletedContextId);
pendingBrowserContextDeletions.delete(seqNum);
}
message.id = id;
socket.send(JSON.stringify(message));
return;
}
// Process notification response.
const { method, params, sessionId } = message;
if (sessionId) {
const socket = sessionToSocket.get(sessionId);
if (!socket || socket.readyState === ws.CLOSING) {
// Drop unattributed messages on the floor.
return;
}
socket.send(JSON.stringify(message));
return;
}
if (method === 'Browser.attachedToTarget') {
const socket = browserContextIds.get(params.targetInfo.browserContextId);
if (!socket || socket.readyState === ws.CLOSING) {
// Drop unattributed messages on the floor.
return;
}
sessionToSocket.set(params.sessionId, socket);
socket.send(JSON.stringify(message));
return;
}
if (method === 'Browser.detachedFromTarget') {
const socket = sessionToSocket.get(params.sessionId);
sessionToSocket.delete(params.sessionId);
if (socket && socket.readyState !== ws.CLOSING)
socket.send(JSON.stringify(message));
return;
}
};
transport.onclose = () => {
for (const socket of sockets) {
socket.removeListener('close', socket.__closeListener);
socket.close(undefined, 'Browser disconnected');
}
server.close();
transport.onmessage = undefined;
transport.onclose = undefined;
};
server.on('connection', (socket, req) => {
if (req.url !== '/' + guid) {
socket.close();
return;
}
sockets.add(socket);
socket.on('message', (message) => {
const parsedMessage = JSON.parse(Buffer.from(message).toString());
const { id, method, params } = parsedMessage;
const seqNum = idMixer.generate({ id, socket });
transport.send({ ...parsedMessage, id: seqNum });
if (method === 'Browser.createBrowserContext')
pendingBrowserContextCreations.add(seqNum);
if (method === 'Browser.removeBrowserContext')
pendingBrowserContextDeletions.set(seqNum, params.browserContextId);
});
socket.on('error', logger_1.logError(logger));
socket.on('close', socket.__closeListener = () => {
for (const [browserContextId, s] of browserContextIds) {
if (s === socket) {
transport.send({
id: ++transport_1.SequenceNumberMixer._lastSequenceNumber,
method: 'Browser.removeBrowserContext',
params: { browserContextId }
});
browserContextIds.delete(browserContextId);
}
}
sockets.delete(socket);
});
});
const address = server.address();
const wsEndpoint = typeof address === 'string' ? `${address}/${guid}` : `ws://127.0.0.1:${address.port}/${guid}`;
return new browserServer_1.WebSocketWrapper(wsEndpoint, [pendingBrowserContextCreations, pendingBrowserContextDeletions, browserContextIds, sessionToSocket, sockets]);
}
//# sourceMappingURL=firefox.js.map

@@ -20,9 +20,8 @@ "use strict";

const helper_1 = require("../helper");
const platform_1 = require("../platform");
const logger_1 = require("../logger");
class PipeTransport {
constructor(pipeWrite, pipeRead, closeCallback) {
constructor(pipeWrite, pipeRead, logger) {
this._pendingMessage = '';
this._waitForNextTask = platform_1.makeWaitForNextTask();
this._waitForNextTask = helper_1.helper.makeWaitForNextTask();
this._pipeWrite = pipeWrite;
this._closeCallback = closeCallback;
this._eventListeners = [

@@ -35,4 +34,4 @@ helper_1.helper.addEventListener(pipeRead, 'data', buffer => this._dispatch(buffer)),

}),
helper_1.helper.addEventListener(pipeRead, 'error', helper_1.debugError),
helper_1.helper.addEventListener(pipeWrite, 'error', helper_1.debugError),
helper_1.helper.addEventListener(pipeRead, 'error', logger_1.logError(logger)),
helper_1.helper.addEventListener(pipeWrite, 'error', logger_1.logError(logger)),
];

@@ -43,7 +42,7 @@ this.onmessage = undefined;

send(message) {
this._pipeWrite.write(message);
this._pipeWrite.write(JSON.stringify(message));
this._pipeWrite.write('\0');
}
close() {
this._closeCallback();
throw new Error('unimplemented');
}

@@ -59,3 +58,3 @@ _dispatch(buffer) {

if (this.onmessage)
this.onmessage.call(null, message);
this.onmessage.call(null, JSON.parse(message));
});

@@ -68,3 +67,3 @@ let start = end + 1;

if (this.onmessage)
this.onmessage.call(null, message);
this.onmessage.call(null, JSON.parse(message));
});

@@ -71,0 +70,0 @@ start = end + 1;

@@ -25,2 +25,3 @@ "use strict";

const firefox_1 = require("./firefox");
const selectors_1 = require("../selectors");
for (const className in api) {

@@ -32,3 +33,3 @@ if (typeof api[className] === 'function')

constructor(options) {
this.selectors = api.Selectors._instance();
this.selectors = selectors_1.selectors;
const { browsers, } = options;

@@ -35,0 +36,0 @@ this.devices = deviceDescriptors_1.DeviceDescriptors;

@@ -20,17 +20,26 @@ "use strict";

const childProcess = require("child_process");
const fs = require("fs");
const os = require("os");
const path = require("path");
const readline = require("readline");
const removeFolder = require("rimraf");
const util = require("util");
const helper_1 = require("../helper");
const readline = require("readline");
const platform = require("../platform");
const removeFolderAsync = platform.promisify(removeFolder);
let lastLaunchedId = 0;
const removeFolderAsync = util.promisify(removeFolder);
const mkdtempAsync = util.promisify(fs.mkdtemp);
const DOWNLOADS_FOLDER = path.join(os.tmpdir(), 'playwright_downloads-');
const browserLog = {
name: 'browser',
};
const browserStdOutLog = {
name: 'browser:out',
};
const browserStdErrLog = {
name: 'browser:err',
severity: 'warning'
};
async function launchProcess(options) {
const id = ++lastLaunchedId;
const debugBrowser = platform.debug(`pw:browser:proc:[${id}]`);
const debugBrowserOut = platform.debug(`pw:browser:out:[${id}]`);
const debugBrowserErr = platform.debug(`pw:browser:err:[${id}]`);
debugBrowser.color = '33';
debugBrowserOut.color = '178';
debugBrowserErr.color = '160';
const logger = options.logger;
const stdio = options.pipe ? ['ignore', 'pipe', 'pipe', 'pipe', 'pipe'] : ['ignore', 'pipe', 'pipe'];
logger._log(browserLog, `<launching> ${options.executablePath} ${options.args.join(' ')}`);
const spawnedProcess = childProcess.spawn(options.executablePath, options.args, {

@@ -44,3 +53,2 @@ // On non-windows platforms, `detached: true` makes child process a leader of a new

});
debugBrowser(`<launching> ${options.executablePath} ${options.args.join(' ')}`);
if (!spawnedProcess.pid) {

@@ -54,18 +62,16 @@ let reject;

}
logger._log(browserLog, `<launched> pid=${spawnedProcess.pid}`);
const stdout = readline.createInterface({ input: spawnedProcess.stdout });
stdout.on('line', (data) => {
debugBrowserOut(data);
if (options.dumpio)
console.log(`\x1b[33m[out]\x1b[0m ${data}`); // eslint-disable-line no-console
logger._log(browserStdOutLog, data);
});
const stderr = readline.createInterface({ input: spawnedProcess.stderr });
stderr.on('line', (data) => {
debugBrowserErr(data);
if (options.dumpio)
console.log(`\x1b[31m[err]\x1b[0m ${data}`); // eslint-disable-line no-console
logger._log(browserStdErrLog, data);
});
const downloadsPath = await mkdtempAsync(DOWNLOADS_FOLDER);
let processClosed = false;
const waitForProcessToClose = new Promise((fulfill, reject) => {
spawnedProcess.once('exit', (exitCode, signal) => {
debugBrowser(`<process did exit ${exitCode}, ${signal}>`);
logger._log(browserLog, `<process did exit ${exitCode}, ${signal}>`);
processClosed = true;

@@ -75,10 +81,6 @@ helper_1.helper.removeEventListeners(listeners);

// Cleanup as processes exit.
if (options.tempDir) {
removeFolderAsync(options.tempDir)
.catch((err) => console.error(err))
.then(fulfill);
}
else {
fulfill();
}
Promise.all([
removeFolderAsync(downloadsPath),
options.tempDir ? removeFolderAsync(options.tempDir) : Promise.resolve()
]).catch((err) => console.error(err)).then(fulfill);
});

@@ -103,3 +105,3 @@ });

if (gracefullyClosing) {
debugBrowser(`<forecefully close>`);
logger._log(browserLog, `<forecefully close>`);
killProcess();

@@ -109,10 +111,10 @@ return;

gracefullyClosing = true;
debugBrowser(`<gracefully close start>`);
options.attemptToGracefullyClose().catch(() => killProcess());
logger._log(browserLog, `<gracefully close start>`);
await options.attemptToGracefullyClose().catch(() => killProcess());
await waitForProcessToClose;
debugBrowser(`<gracefully close end>`);
logger._log(browserLog, `<gracefully close end>`);
}
// This method has to be sync to be used as 'exit' event handler.
function killProcess() {
debugBrowser(`<kill>`);
logger._log(browserLog, `<kill>`);
helper_1.helper.removeEventListeners(listeners);

@@ -138,3 +140,3 @@ if (spawnedProcess.pid && !spawnedProcess.killed && !processClosed) {

}
return { launchedProcess: spawnedProcess, gracefullyClose };
return { launchedProcess: spawnedProcess, gracefullyClose, downloadsPath };
}

@@ -141,0 +143,0 @@ exports.launchProcess = launchProcess;

@@ -24,9 +24,11 @@ "use strict";

const path = require("path");
const platform = require("../platform");
const os = require("os");
const util = require("util");
const helper_1 = require("../helper");
const wkConnection_1 = require("../webkit/wkConnection");
const transport_1 = require("../transport");
const ws = require("ws");
const browserServer_1 = require("./browserServer");
const events_1 = require("../events");
const logger_1 = require("../logger");
class WebKit {

@@ -41,22 +43,25 @@ executablePath() {

}
async launch(options) {
if (options && options.userDataDir)
throw new Error('userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
const { browserServer, transport } = await this._launchServer(options, 'local');
const browser = await wkBrowser_1.WKBrowser.connect(transport, options && options.slowMo);
browser['__server__'] = browserServer;
async launch(options = {}) {
helper_1.assert(!options.userDataDir, 'userDataDir option is not supported in `browserType.launch`. Use `browserType.launchPersistentContext` instead');
const { browserServer, transport, downloadsPath, logger } = await this._launchServer(options, 'local');
const browser = await wkBrowser_1.WKBrowser.connect(transport, logger, options.slowMo, false);
browser._ownedServer = browserServer;
browser._downloadsPath = downloadsPath;
return browser;
}
async launchServer(options) {
return (await this._launchServer(options, 'server', undefined, options && options.port)).browserServer;
async launchServer(options = {}) {
return (await this._launchServer(options, 'server')).browserServer;
}
async launchPersistentContext(userDataDir, options) {
const { timeout = 30000 } = options || {};
const { transport } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await wkBrowser_1.WKBrowser.connect(transport, undefined, true);
async launchPersistentContext(userDataDir, options = {}) {
const { timeout = 30000, slowMo = 0, } = options;
const { transport, browserServer, logger } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await wkBrowser_1.WKBrowser.connect(transport, logger, slowMo, true);
browser._ownedServer = browserServer;
await helper_1.helper.waitWithTimeout(browser._waitForFirstPageTarget(), 'first page', timeout);
return browser._defaultContext;
}
async _launchServer(options = {}, launchType, userDataDir, port) {
const { ignoreDefaultArgs = false, args = [], dumpio = false, executablePath = null, env = process.env, handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, } = options;
async _launchServer(options, launchType, userDataDir) {
const { ignoreDefaultArgs = false, args = [], executablePath = null, env = process.env, handleSIGINT = true, handleSIGTERM = true, handleSIGHUP = true, port = 0, } = options;
helper_1.assert(!port || launchType === 'server', 'Cannot specify a port without launching as a server.');
const logger = new logger_1.RootLogger(options.logger);
let temporaryUserDataDir = null;

@@ -69,5 +74,5 @@ if (!userDataDir) {

if (!ignoreDefaultArgs)
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port || 0));
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port));
else if (Array.isArray(ignoreDefaultArgs))
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port || 0).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
webkitArguments.push(...this._defaultArgs(options, launchType, userDataDir, port).filter(arg => ignoreDefaultArgs.indexOf(arg) === -1));
else

@@ -78,5 +83,3 @@ webkitArguments.push(...args);

throw new Error(`No executable path is specified.`);
let transport = undefined;
let browserServer = undefined;
const { launchedProcess, gracefullyClose } = await processLauncher_1.launchProcess({
const { launchedProcess, gracefullyClose, downloadsPath } = await processLauncher_1.launchProcess({
executablePath: webkitExecutable,

@@ -88,13 +91,11 @@ args: webkitArguments,

handleSIGHUP,
dumpio,
logger,
pipe: true,
tempDir: temporaryUserDataDir || undefined,
attemptToGracefullyClose: async () => {
if (!transport)
return Promise.reject();
helper_1.assert(transport);
// We try to gracefully close to prevent crash reporting and core dumps.
// Note that it's fine to reuse the pipe transport, since
// our connection ignores kBrowserCloseMessageId.
const message = JSON.stringify({ method: 'Playwright.close', params: {}, id: wkConnection_1.kBrowserCloseMessageId });
transport.send(message);
await transport.send({ method: 'Playwright.close', params: {}, id: wkConnection_1.kBrowserCloseMessageId });
},

@@ -107,11 +108,12 @@ onkill: (exitCode, signal) => {

// For local launch scenario close will terminate the browser process.
transport = new pipeTransport_1.PipeTransport(launchedProcess.stdio[3], launchedProcess.stdio[4], () => browserServer.close());
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? await wrapTransportWithWebSocket(transport, port || 0) : null);
if (launchType === 'server')
return { browserServer };
return { browserServer, transport };
let transport = undefined;
let browserServer = undefined;
const stdio = launchedProcess.stdio;
transport = new pipeTransport_1.PipeTransport(stdio[3], stdio[4], logger);
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, logger, port || 0) : null);
return { browserServer, transport, downloadsPath, logger };
}
async connect(options) {
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
return wkBrowser_1.WKBrowser.connect(transport, options.slowMo);
return await transport_1.WebSocketTransport.connect(options.wsEndpoint, transport => {
return wkBrowser_1.WKBrowser.connect(transport, new logger_1.RootLogger(options.logger), options.slowMo);
});

@@ -122,3 +124,3 @@ }

if (devtools)
throw new Error('Option "devtools" is not supported by WebKit');
console.warn('devtools parameter as a launch argument in WebKit is not supported. Also starting Web Inspector manually will terminate the execution in WebKit.');
const userDataDirArg = args.find(arg => arg.startsWith('--user-data-dir='));

@@ -141,24 +143,8 @@ if (userDataDirArg)

exports.WebKit = WebKit;
const mkdtempAsync = platform.promisify(fs.mkdtemp);
const mkdtempAsync = util.promisify(fs.mkdtemp);
const WEBKIT_PROFILE_PATH = path.join(os.tmpdir(), 'playwright_dev_profile-');
class SequenceNumberMixer {
constructor() {
this._values = new Map();
}
generate(value) {
const sequenceNumber = ++SequenceNumberMixer._lastSequenceNumber;
this._values.set(sequenceNumber, value);
return sequenceNumber;
}
take(sequenceNumber) {
const value = this._values.get(sequenceNumber);
this._values.delete(sequenceNumber);
return value;
}
}
SequenceNumberMixer._lastSequenceNumber = 1;
function wrapTransportWithWebSocket(transport, port) {
function wrapTransportWithWebSocket(transport, logger, port) {
const server = new ws.Server({ port });
const guid = platform.guid();
const idMixer = new SequenceNumberMixer();
const guid = helper_1.helper.guid();
const idMixer = new transport_1.SequenceNumberMixer();
const pendingBrowserContextCreations = new Set();

@@ -170,38 +156,37 @@ const pendingBrowserContextDeletions = new Map();

transport.onmessage = message => {
const parsedMessage = JSON.parse(message);
if ('id' in parsedMessage) {
if (parsedMessage.id === -9999)
if (typeof message.id === 'number') {
if (message.id === -9999)
return;
// Process command response.
const value = idMixer.take(parsedMessage.id);
const value = idMixer.take(message.id);
if (!value)
return;
const { id, socket } = value;
if (!socket || socket.readyState === ws.CLOSING) {
if (socket.readyState === ws.CLOSING) {
if (pendingBrowserContextCreations.has(id)) {
transport.send(JSON.stringify({
id: ++SequenceNumberMixer._lastSequenceNumber,
transport.send({
id: ++transport_1.SequenceNumberMixer._lastSequenceNumber,
method: 'Playwright.deleteContext',
params: { browserContextId: parsedMessage.result.browserContextId }
}));
params: { browserContextId: message.result.browserContextId }
});
}
return;
}
if (pendingBrowserContextCreations.has(parsedMessage.id)) {
if (pendingBrowserContextCreations.has(message.id)) {
// Browser.createContext response -> establish context attribution.
browserContextIds.set(parsedMessage.result.browserContextId, socket);
pendingBrowserContextCreations.delete(parsedMessage.id);
browserContextIds.set(message.result.browserContextId, socket);
pendingBrowserContextCreations.delete(message.id);
}
const deletedContextId = pendingBrowserContextDeletions.get(parsedMessage.id);
const deletedContextId = pendingBrowserContextDeletions.get(message.id);
if (deletedContextId) {
// Browser.deleteContext response -> remove context attribution.
browserContextIds.delete(deletedContextId);
pendingBrowserContextDeletions.delete(parsedMessage.id);
pendingBrowserContextDeletions.delete(message.id);
}
parsedMessage.id = id;
socket.send(JSON.stringify(parsedMessage));
message.id = id;
socket.send(JSON.stringify(message));
return;
}
// Process notification response.
const { method, params, pageProxyId } = parsedMessage;
const { method, params, pageProxyId } = message;
if (pageProxyId) {

@@ -213,3 +198,3 @@ const socket = pageProxyIds.get(pageProxyId);

}
socket.send(message);
socket.send(JSON.stringify(message));
return;

@@ -224,3 +209,3 @@ }

pageProxyIds.set(params.pageProxyInfo.pageProxyId, socket);
socket.send(message);
socket.send(JSON.stringify(message));
return;

@@ -232,3 +217,3 @@ }

if (socket && socket.readyState !== ws.CLOSING)
socket.send(message);
socket.send(JSON.stringify(message));
return;

@@ -239,6 +224,15 @@ }

if (socket && socket.readyState !== ws.CLOSING)
socket.send(message);
socket.send(JSON.stringify(message));
return;
}
};
transport.onclose = () => {
for (const socket of sockets) {
socket.removeListener('close', socket.__closeListener);
socket.close(undefined, 'Browser disconnected');
}
server.close();
transport.onmessage = undefined;
transport.onclose = undefined;
};
server.on('connection', (socket, req) => {

@@ -254,3 +248,3 @@ if (req.url !== '/' + guid) {

const seqNum = idMixer.generate({ id, socket });
transport.send(JSON.stringify({ ...parsedMessage, id: seqNum }));
transport.send({ ...parsedMessage, id: seqNum });
if (method === 'Playwright.createContext')

@@ -261,2 +255,3 @@ pendingBrowserContextCreations.add(seqNum);

});
socket.on('error', logger_1.logError(logger));
socket.on('close', socket.__closeListener = () => {

@@ -269,7 +264,7 @@ for (const [pageProxyId, s] of pageProxyIds) {

if (s === socket) {
transport.send(JSON.stringify({
id: ++SequenceNumberMixer._lastSequenceNumber,
transport.send({
id: ++transport_1.SequenceNumberMixer._lastSequenceNumber,
method: 'Playwright.deleteContext',
params: { browserContextId }
}));
});
browserContextIds.delete(browserContextId);

@@ -281,16 +276,6 @@ }

});
transport.onclose = () => {
for (const socket of sockets) {
socket.removeListener('close', socket.__closeListener);
socket.close(undefined, 'Browser disconnected');
}
server.close();
transport.onmessage = undefined;
transport.onclose = undefined;
};
const address = server.address();
if (typeof address === 'string')
return address + '/' + guid;
return 'ws://127.0.0.1:' + address.port + '/' + guid;
const wsEndpoint = typeof address === 'string' ? `${address}/${guid}` : `ws://127.0.0.1:${address.port}/${guid}`;
return new browserServer_1.WebSocketWrapper(wsEndpoint, [pendingBrowserContextCreations, pendingBrowserContextDeletions, browserContextIds, pageProxyIds, sockets]);
}
//# sourceMappingURL=webkit.js.map

@@ -19,2 +19,3 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const helper_1 = require("./helper");
const DEFAULT_TIMEOUT = 30000;

@@ -42,11 +43,19 @@ class TimeoutSettings {

}
timeout() {
_timeout() {
if (this._defaultTimeout !== null)
return this._defaultTimeout;
if (this._parent)
return this._parent.timeout();
return this._parent._timeout();
return DEFAULT_TIMEOUT;
}
computeDeadline(options) {
const { timeout } = options || {};
if (timeout === 0)
return Number.MAX_SAFE_INTEGER;
else if (typeof timeout === 'number')
return helper_1.helper.monotonicTime() + timeout;
return helper_1.helper.monotonicTime() + this._timeout();
}
}
exports.TimeoutSettings = TimeoutSettings;
//# sourceMappingURL=timeoutSettings.js.map

@@ -19,2 +19,4 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const WebSocket = require("ws");
const helper_1 = require("./helper");
class SlowMoTransport {

@@ -56,3 +58,3 @@ constructor(transport, delay) {

this._readPromise = new Promise(f => callback = f);
this._delegate.onmessage = s => {
this._delegate.onmessage = (s) => {
callback();

@@ -76,2 +78,90 @@ if (this.onmessage)

exports.DeferWriteTransport = DeferWriteTransport;
class WebSocketTransport {
constructor(url) {
this._ws = new WebSocket(url, [], {
perMessageDeflate: false,
maxPayload: 256 * 1024 * 1024,
});
// The 'ws' module in node sometimes sends us multiple messages in a single task.
// In Web, all IO callbacks (e.g. WebSocket callbacks)
// are dispatched into separate tasks, so there's no need
// to do anything extra.
const messageWrap = helper_1.helper.makeWaitForNextTask();
this._ws.addEventListener('message', event => {
messageWrap(() => {
if (this.onmessage)
this.onmessage.call(null, JSON.parse(event.data));
});
});
this._ws.addEventListener('close', event => {
if (this.onclose)
this.onclose.call(null);
});
// Silently ignore all errors - we don't know what to do with them.
this._ws.addEventListener('error', () => { });
}
// 'onmessage' handler must be installed synchronously when 'onopen' callback is invoked to
// avoid missing incoming messages.
static connect(url, onopen) {
const transport = new WebSocketTransport(url);
return new Promise((fulfill, reject) => {
transport._ws.addEventListener('open', async () => fulfill(await onopen(transport)));
transport._ws.addEventListener('error', event => reject(new Error('WebSocket error: ' + event.message)));
});
}
send(message) {
this._ws.send(JSON.stringify(message));
}
close() {
this._ws.close();
}
}
exports.WebSocketTransport = WebSocketTransport;
class SequenceNumberMixer {
constructor() {
this._values = new Map();
}
generate(value) {
const sequenceNumber = ++SequenceNumberMixer._lastSequenceNumber;
this._values.set(sequenceNumber, value);
return sequenceNumber;
}
take(sequenceNumber) {
const value = this._values.get(sequenceNumber);
this._values.delete(sequenceNumber);
return value;
}
}
exports.SequenceNumberMixer = SequenceNumberMixer;
SequenceNumberMixer._lastSequenceNumber = 1;
class InterceptingTransport {
constructor(transport, interceptor) {
this._delegate = transport;
this._interceptor = interceptor;
this._delegate.onmessage = this._onmessage.bind(this);
this._delegate.onclose = this._onClose.bind(this);
}
_onmessage(message) {
if (this.onmessage)
this.onmessage(message);
}
_onClose() {
if (this.onclose)
this.onclose();
this._delegate.onmessage = undefined;
this._delegate.onclose = undefined;
}
send(s) {
this._delegate.send(this._interceptor(s));
}
close() {
this._delegate.close();
}
}
exports.InterceptingTransport = InterceptingTransport;
exports.protocolLog = {
name: 'protocol',
severity: 'verbose',
color: 'green'
};
//# sourceMappingURL=transport.js.map

@@ -18,5 +18,5 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
exports.kLifecycleEvents = new Set(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']);
exports.kLifecycleEvents = new Set(['load', 'domcontentloaded', 'networkidle']);
exports.mediaTypes = new Set(['screen', 'print']);
exports.colorSchemes = new Set(['dark', 'light', 'no-preference']);
//# sourceMappingURL=types.js.map

@@ -20,374 +20,117 @@ "use strict";

exports.keypadLocation = 3;
exports.keyDefinitions = {
'0': { 'keyCode': 48, 'key': '0', 'code': 'Digit0' },
'1': { 'keyCode': 49, 'key': '1', 'code': 'Digit1' },
'2': { 'keyCode': 50, 'key': '2', 'code': 'Digit2' },
'3': { 'keyCode': 51, 'key': '3', 'code': 'Digit3' },
'4': { 'keyCode': 52, 'key': '4', 'code': 'Digit4' },
'5': { 'keyCode': 53, 'key': '5', 'code': 'Digit5' },
'6': { 'keyCode': 54, 'key': '6', 'code': 'Digit6' },
'7': { 'keyCode': 55, 'key': '7', 'code': 'Digit7' },
'8': { 'keyCode': 56, 'key': '8', 'code': 'Digit8' },
'9': { 'keyCode': 57, 'key': '9', 'code': 'Digit9' },
'Power': { 'key': 'Power', 'code': 'Power' },
'Eject': { 'key': 'Eject', 'code': 'Eject' },
'Abort': { 'keyCode': 3, 'code': 'Abort', 'key': 'Cancel' },
'Help': { 'keyCode': 6, 'code': 'Help', 'key': 'Help' },
'Backspace': { 'keyCode': 8, 'code': 'Backspace', 'key': 'Backspace' },
'Tab': { 'keyCode': 9, 'code': 'Tab', 'key': 'Tab' },
'Numpad5': { 'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'code': 'Numpad5', 'shiftKey': '5', 'location': 3 },
'NumpadEnter': { 'keyCode': 13, 'code': 'NumpadEnter', 'key': 'Enter', 'text': '\r', 'location': 3 },
'Enter': { 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' },
'\r': { 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' },
'\n': { 'keyCode': 13, 'code': 'Enter', 'key': 'Enter', 'text': '\r' },
'ShiftLeft': { 'keyCode': 160, 'keyCodeWithoutLocation': 16, 'code': 'ShiftLeft', 'key': 'Shift', 'location': 1, 'windowsVirtualKeyCode': 160 },
'ShiftRight': { 'keyCode': 161, 'keyCodeWithoutLocation': 16, 'code': 'ShiftRight', 'key': 'Shift', 'location': 2, 'windowsVirtualKeyCode': 161 },
'ControlLeft': { 'keyCode': 162, 'keyCodeWithoutLocation': 17, 'code': 'ControlLeft', 'key': 'Control', 'location': 1, 'windowsVirtualKeyCode': 162 },
'ControlRight': { 'keyCode': 163, 'keyCodeWithoutLocation': 17, 'code': 'ControlRight', 'key': 'Control', 'location': 2, 'windowsVirtualKeyCode': 163 },
'AltLeft': { 'keyCode': 164, 'keyCodeWithoutLocation': 18, 'code': 'AltLeft', 'key': 'Alt', 'location': 1, 'windowsVirtualKeyCode': 164 },
'AltRight': { 'keyCode': 165, 'keyCodeWithoutLocation': 18, 'code': 'AltRight', 'key': 'Alt', 'location': 2, 'windowsVirtualKeyCode': 165 },
'Pause': { 'keyCode': 19, 'code': 'Pause', 'key': 'Pause' },
'CapsLock': { 'keyCode': 20, 'code': 'CapsLock', 'key': 'CapsLock' },
'Escape': { 'keyCode': 27, 'code': 'Escape', 'key': 'Escape' },
'Convert': { 'keyCode': 28, 'code': 'Convert', 'key': 'Convert' },
'NonConvert': { 'keyCode': 29, 'code': 'NonConvert', 'key': 'NonConvert' },
'Space': { 'keyCode': 32, 'code': 'Space', 'key': ' ' },
'Numpad9': { 'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'code': 'Numpad9', 'shiftKey': '9', 'location': 3 },
'PageUp': { 'keyCode': 33, 'code': 'PageUp', 'key': 'PageUp' },
'Numpad3': { 'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'code': 'Numpad3', 'shiftKey': '3', 'location': 3 },
'PageDown': { 'keyCode': 34, 'code': 'PageDown', 'key': 'PageDown' },
'End': { 'keyCode': 35, 'code': 'End', 'key': 'End' },
'Numpad1': { 'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'code': 'Numpad1', 'shiftKey': '1', 'location': 3 },
'Home': { 'keyCode': 36, 'code': 'Home', 'key': 'Home' },
'Numpad7': { 'keyCode': 36, 'shiftKeyCode': 103, 'key': 'Home', 'code': 'Numpad7', 'shiftKey': '7', 'location': 3 },
'ArrowLeft': { 'keyCode': 37, 'code': 'ArrowLeft', 'key': 'ArrowLeft' },
'Numpad4': { 'keyCode': 37, 'shiftKeyCode': 100, 'key': 'ArrowLeft', 'code': 'Numpad4', 'shiftKey': '4', 'location': 3 },
'Numpad8': { 'keyCode': 38, 'shiftKeyCode': 104, 'key': 'ArrowUp', 'code': 'Numpad8', 'shiftKey': '8', 'location': 3 },
'ArrowUp': { 'keyCode': 38, 'code': 'ArrowUp', 'key': 'ArrowUp' },
'ArrowRight': { 'keyCode': 39, 'code': 'ArrowRight', 'key': 'ArrowRight' },
'Numpad6': { 'keyCode': 39, 'shiftKeyCode': 102, 'key': 'ArrowRight', 'code': 'Numpad6', 'shiftKey': '6', 'location': 3 },
'Numpad2': { 'keyCode': 40, 'shiftKeyCode': 98, 'key': 'ArrowDown', 'code': 'Numpad2', 'shiftKey': '2', 'location': 3 },
'ArrowDown': { 'keyCode': 40, 'code': 'ArrowDown', 'key': 'ArrowDown' },
'Select': { 'keyCode': 41, 'code': 'Select', 'key': 'Select' },
'Open': { 'keyCode': 43, 'code': 'Open', 'key': 'Execute' },
'PrintScreen': { 'keyCode': 44, 'code': 'PrintScreen', 'key': 'PrintScreen' },
'Insert': { 'keyCode': 45, 'code': 'Insert', 'key': 'Insert' },
'Numpad0': { 'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'code': 'Numpad0', 'shiftKey': '0', 'location': 3 },
'Delete': { 'keyCode': 46, 'code': 'Delete', 'key': 'Delete' },
'NumpadDecimal': { 'keyCode': 46, 'shiftKeyCode': 110, 'code': 'NumpadDecimal', 'key': '\u0000', 'shiftKey': '.', 'location': 3 },
'Digit0': { 'keyCode': 48, 'code': 'Digit0', 'shiftKey': ')', 'key': '0' },
'Digit1': { 'keyCode': 49, 'code': 'Digit1', 'shiftKey': '!', 'key': '1' },
'Digit2': { 'keyCode': 50, 'code': 'Digit2', 'shiftKey': '@', 'key': '2' },
'Digit3': { 'keyCode': 51, 'code': 'Digit3', 'shiftKey': '#', 'key': '3' },
'Digit4': { 'keyCode': 52, 'code': 'Digit4', 'shiftKey': '$', 'key': '4' },
'Digit5': { 'keyCode': 53, 'code': 'Digit5', 'shiftKey': '%', 'key': '5' },
'Digit6': { 'keyCode': 54, 'code': 'Digit6', 'shiftKey': '^', 'key': '6' },
'Digit7': { 'keyCode': 55, 'code': 'Digit7', 'shiftKey': '&', 'key': '7' },
'Digit8': { 'keyCode': 56, 'code': 'Digit8', 'shiftKey': '*', 'key': '8' },
'Digit9': { 'keyCode': 57, 'code': 'Digit9', 'shiftKey': '\(', 'key': '9' },
'KeyA': { 'keyCode': 65, 'code': 'KeyA', 'shiftKey': 'A', 'key': 'a' },
'KeyB': { 'keyCode': 66, 'code': 'KeyB', 'shiftKey': 'B', 'key': 'b' },
'KeyC': { 'keyCode': 67, 'code': 'KeyC', 'shiftKey': 'C', 'key': 'c' },
'KeyD': { 'keyCode': 68, 'code': 'KeyD', 'shiftKey': 'D', 'key': 'd' },
'KeyE': { 'keyCode': 69, 'code': 'KeyE', 'shiftKey': 'E', 'key': 'e' },
'KeyF': { 'keyCode': 70, 'code': 'KeyF', 'shiftKey': 'F', 'key': 'f' },
'KeyG': { 'keyCode': 71, 'code': 'KeyG', 'shiftKey': 'G', 'key': 'g' },
'KeyH': { 'keyCode': 72, 'code': 'KeyH', 'shiftKey': 'H', 'key': 'h' },
'KeyI': { 'keyCode': 73, 'code': 'KeyI', 'shiftKey': 'I', 'key': 'i' },
'KeyJ': { 'keyCode': 74, 'code': 'KeyJ', 'shiftKey': 'J', 'key': 'j' },
'KeyK': { 'keyCode': 75, 'code': 'KeyK', 'shiftKey': 'K', 'key': 'k' },
'KeyL': { 'keyCode': 76, 'code': 'KeyL', 'shiftKey': 'L', 'key': 'l' },
'KeyM': { 'keyCode': 77, 'code': 'KeyM', 'shiftKey': 'M', 'key': 'm' },
'KeyN': { 'keyCode': 78, 'code': 'KeyN', 'shiftKey': 'N', 'key': 'n' },
'KeyO': { 'keyCode': 79, 'code': 'KeyO', 'shiftKey': 'O', 'key': 'o' },
'KeyP': { 'keyCode': 80, 'code': 'KeyP', 'shiftKey': 'P', 'key': 'p' },
'KeyQ': { 'keyCode': 81, 'code': 'KeyQ', 'shiftKey': 'Q', 'key': 'q' },
'KeyR': { 'keyCode': 82, 'code': 'KeyR', 'shiftKey': 'R', 'key': 'r' },
'KeyS': { 'keyCode': 83, 'code': 'KeyS', 'shiftKey': 'S', 'key': 's' },
'KeyT': { 'keyCode': 84, 'code': 'KeyT', 'shiftKey': 'T', 'key': 't' },
'KeyU': { 'keyCode': 85, 'code': 'KeyU', 'shiftKey': 'U', 'key': 'u' },
'KeyV': { 'keyCode': 86, 'code': 'KeyV', 'shiftKey': 'V', 'key': 'v' },
'KeyW': { 'keyCode': 87, 'code': 'KeyW', 'shiftKey': 'W', 'key': 'w' },
'KeyX': { 'keyCode': 88, 'code': 'KeyX', 'shiftKey': 'X', 'key': 'x' },
'KeyY': { 'keyCode': 89, 'code': 'KeyY', 'shiftKey': 'Y', 'key': 'y' },
'KeyZ': { 'keyCode': 90, 'code': 'KeyZ', 'shiftKey': 'Z', 'key': 'z' },
'MetaLeft': { 'keyCode': 91, 'code': 'MetaLeft', 'key': 'Meta', 'location': 1 },
'MetaRight': { 'keyCode': 92, 'code': 'MetaRight', 'key': 'Meta', 'location': 2 },
'ContextMenu': { 'keyCode': 93, 'code': 'ContextMenu', 'key': 'ContextMenu' },
'NumpadMultiply': { 'keyCode': 106, 'code': 'NumpadMultiply', 'key': '*', 'location': 3 },
'NumpadAdd': { 'keyCode': 107, 'code': 'NumpadAdd', 'key': '+', 'location': 3 },
'NumpadSubtract': { 'keyCode': 109, 'code': 'NumpadSubtract', 'key': '-', 'location': 3 },
'NumpadDivide': { 'keyCode': 111, 'code': 'NumpadDivide', 'key': '/', 'location': 3 },
'F1': { 'keyCode': 112, 'code': 'F1', 'key': 'F1' },
'F2': { 'keyCode': 113, 'code': 'F2', 'key': 'F2' },
'F3': { 'keyCode': 114, 'code': 'F3', 'key': 'F3' },
'F4': { 'keyCode': 115, 'code': 'F4', 'key': 'F4' },
'F5': { 'keyCode': 116, 'code': 'F5', 'key': 'F5' },
'F6': { 'keyCode': 117, 'code': 'F6', 'key': 'F6' },
'F7': { 'keyCode': 118, 'code': 'F7', 'key': 'F7' },
'F8': { 'keyCode': 119, 'code': 'F8', 'key': 'F8' },
'F9': { 'keyCode': 120, 'code': 'F9', 'key': 'F9' },
'F10': { 'keyCode': 121, 'code': 'F10', 'key': 'F10' },
'F11': { 'keyCode': 122, 'code': 'F11', 'key': 'F11' },
'F12': { 'keyCode': 123, 'code': 'F12', 'key': 'F12' },
'F13': { 'keyCode': 124, 'code': 'F13', 'key': 'F13' },
'F14': { 'keyCode': 125, 'code': 'F14', 'key': 'F14' },
'F15': { 'keyCode': 126, 'code': 'F15', 'key': 'F15' },
'F16': { 'keyCode': 127, 'code': 'F16', 'key': 'F16' },
'F17': { 'keyCode': 128, 'code': 'F17', 'key': 'F17' },
'F18': { 'keyCode': 129, 'code': 'F18', 'key': 'F18' },
'F19': { 'keyCode': 130, 'code': 'F19', 'key': 'F19' },
'F20': { 'keyCode': 131, 'code': 'F20', 'key': 'F20' },
'F21': { 'keyCode': 132, 'code': 'F21', 'key': 'F21' },
'F22': { 'keyCode': 133, 'code': 'F22', 'key': 'F22' },
'F23': { 'keyCode': 134, 'code': 'F23', 'key': 'F23' },
'F24': { 'keyCode': 135, 'code': 'F24', 'key': 'F24' },
'NumLock': { 'keyCode': 144, 'code': 'NumLock', 'key': 'NumLock' },
'ScrollLock': { 'keyCode': 145, 'code': 'ScrollLock', 'key': 'ScrollLock' },
'AudioVolumeMute': { 'keyCode': 173, 'code': 'AudioVolumeMute', 'key': 'AudioVolumeMute' },
'AudioVolumeDown': { 'keyCode': 174, 'code': 'AudioVolumeDown', 'key': 'AudioVolumeDown' },
'AudioVolumeUp': { 'keyCode': 175, 'code': 'AudioVolumeUp', 'key': 'AudioVolumeUp' },
'MediaTrackNext': { 'keyCode': 176, 'code': 'MediaTrackNext', 'key': 'MediaTrackNext' },
'MediaTrackPrevious': { 'keyCode': 177, 'code': 'MediaTrackPrevious', 'key': 'MediaTrackPrevious' },
'MediaStop': { 'keyCode': 178, 'code': 'MediaStop', 'key': 'MediaStop' },
'MediaPlayPause': { 'keyCode': 179, 'code': 'MediaPlayPause', 'key': 'MediaPlayPause' },
'Semicolon': { 'keyCode': 186, 'code': 'Semicolon', 'shiftKey': ':', 'key': ';' },
'Equal': { 'keyCode': 187, 'code': 'Equal', 'shiftKey': '+', 'key': '=' },
'NumpadEqual': { 'keyCode': 187, 'code': 'NumpadEqual', 'key': '=', 'location': 3 },
'Comma': { 'keyCode': 188, 'code': 'Comma', 'shiftKey': '\<', 'key': ',' },
'Minus': { 'keyCode': 189, 'code': 'Minus', 'shiftKey': '_', 'key': '-' },
'Period': { 'keyCode': 190, 'code': 'Period', 'shiftKey': '>', 'key': '.' },
'Slash': { 'keyCode': 191, 'code': 'Slash', 'shiftKey': '?', 'key': '/' },
'Backquote': { 'keyCode': 192, 'code': 'Backquote', 'shiftKey': '~', 'key': '`' },
'BracketLeft': { 'keyCode': 219, 'code': 'BracketLeft', 'shiftKey': '{', 'key': '[' },
'Backslash': { 'keyCode': 220, 'code': 'Backslash', 'shiftKey': '|', 'key': '\\' },
'BracketRight': { 'keyCode': 221, 'code': 'BracketRight', 'shiftKey': '}', 'key': ']' },
'Quote': { 'keyCode': 222, 'code': 'Quote', 'shiftKey': '"', 'key': '\'' },
'AltGraph': { 'keyCode': 225, 'code': 'AltGraph', 'key': 'AltGraph' },
'Props': { 'keyCode': 247, 'code': 'Props', 'key': 'CrSel' },
'Cancel': { 'keyCode': 3, 'key': 'Cancel', 'code': 'Abort' },
'Clear': { 'keyCode': 12, 'key': 'Clear', 'code': 'Numpad5', 'location': 3 },
'Shift': { 'keyCode': 160, 'keyCodeWithoutLocation': 16, 'key': 'Shift', 'code': 'ShiftLeft', 'location': 1, 'windowsVirtualKeyCode': 160 },
'Control': { 'keyCode': 162, 'keyCodeWithoutLocation': 17, 'key': 'Control', 'code': 'ControlLeft', 'location': 1, 'windowsVirtualKeyCode': 162 },
'Alt': { 'keyCode': 164, 'keyCodeWithoutLocation': 18, 'key': 'Alt', 'code': 'AltLeft', 'location': 1, 'windowsVirtualKeyCode': 164 },
'Accept': { 'keyCode': 30, 'key': 'Accept' },
'ModeChange': { 'keyCode': 31, 'key': 'ModeChange' },
' ': { 'keyCode': 32, 'key': ' ', 'code': 'Space' },
'Print': { 'keyCode': 42, 'key': 'Print' },
'Execute': { 'keyCode': 43, 'key': 'Execute', 'code': 'Open' },
'\u0000': { 'keyCode': 46, 'key': '\u0000', 'code': 'NumpadDecimal', 'location': 3 },
'a': { 'keyCode': 65, 'key': 'a', 'code': 'KeyA' },
'b': { 'keyCode': 66, 'key': 'b', 'code': 'KeyB' },
'c': { 'keyCode': 67, 'key': 'c', 'code': 'KeyC' },
'd': { 'keyCode': 68, 'key': 'd', 'code': 'KeyD' },
'e': { 'keyCode': 69, 'key': 'e', 'code': 'KeyE' },
'f': { 'keyCode': 70, 'key': 'f', 'code': 'KeyF' },
'g': { 'keyCode': 71, 'key': 'g', 'code': 'KeyG' },
'h': { 'keyCode': 72, 'key': 'h', 'code': 'KeyH' },
'i': { 'keyCode': 73, 'key': 'i', 'code': 'KeyI' },
'j': { 'keyCode': 74, 'key': 'j', 'code': 'KeyJ' },
'k': { 'keyCode': 75, 'key': 'k', 'code': 'KeyK' },
'l': { 'keyCode': 76, 'key': 'l', 'code': 'KeyL' },
'm': { 'keyCode': 77, 'key': 'm', 'code': 'KeyM' },
'n': { 'keyCode': 78, 'key': 'n', 'code': 'KeyN' },
'o': { 'keyCode': 79, 'key': 'o', 'code': 'KeyO' },
'p': { 'keyCode': 80, 'key': 'p', 'code': 'KeyP' },
'q': { 'keyCode': 81, 'key': 'q', 'code': 'KeyQ' },
'r': { 'keyCode': 82, 'key': 'r', 'code': 'KeyR' },
's': { 'keyCode': 83, 'key': 's', 'code': 'KeyS' },
't': { 'keyCode': 84, 'key': 't', 'code': 'KeyT' },
'u': { 'keyCode': 85, 'key': 'u', 'code': 'KeyU' },
'v': { 'keyCode': 86, 'key': 'v', 'code': 'KeyV' },
'w': { 'keyCode': 87, 'key': 'w', 'code': 'KeyW' },
'x': { 'keyCode': 88, 'key': 'x', 'code': 'KeyX' },
'y': { 'keyCode': 89, 'key': 'y', 'code': 'KeyY' },
'z': { 'keyCode': 90, 'key': 'z', 'code': 'KeyZ' },
'Meta': { 'keyCode': 91, 'key': 'Meta', 'code': 'MetaLeft', 'location': 1 },
'*': { 'keyCode': 106, 'key': '*', 'code': 'NumpadMultiply', 'location': 3 },
'+': { 'keyCode': 107, 'key': '+', 'code': 'NumpadAdd', 'location': 3 },
'-': { 'keyCode': 109, 'key': '-', 'code': 'NumpadSubtract', 'location': 3 },
'/': { 'keyCode': 111, 'key': '/', 'code': 'NumpadDivide', 'location': 3 },
';': { 'keyCode': 186, 'key': ';', 'code': 'Semicolon' },
'=': { 'keyCode': 187, 'key': '=', 'code': 'Equal' },
',': { 'keyCode': 188, 'key': ',', 'code': 'Comma' },
'.': { 'keyCode': 190, 'key': '.', 'code': 'Period' },
'`': { 'keyCode': 192, 'key': '`', 'code': 'Backquote' },
'[': { 'keyCode': 219, 'key': '[', 'code': 'BracketLeft' },
'\\': { 'keyCode': 220, 'key': '\\', 'code': 'Backslash' },
']': { 'keyCode': 221, 'key': ']', 'code': 'BracketRight' },
'\'': { 'keyCode': 222, 'key': '\'', 'code': 'Quote' },
'Attn': { 'keyCode': 246, 'key': 'Attn' },
'CrSel': { 'keyCode': 247, 'key': 'CrSel', 'code': 'Props' },
'ExSel': { 'keyCode': 248, 'key': 'ExSel' },
'EraseEof': { 'keyCode': 249, 'key': 'EraseEof' },
'Play': { 'keyCode': 250, 'key': 'Play' },
'ZoomOut': { 'keyCode': 251, 'key': 'ZoomOut' },
')': { 'keyCode': 48, 'key': ')', 'code': 'Digit0' },
'!': { 'keyCode': 49, 'key': '!', 'code': 'Digit1' },
'@': { 'keyCode': 50, 'key': '@', 'code': 'Digit2' },
'#': { 'keyCode': 51, 'key': '#', 'code': 'Digit3' },
'$': { 'keyCode': 52, 'key': '$', 'code': 'Digit4' },
'%': { 'keyCode': 53, 'key': '%', 'code': 'Digit5' },
'^': { 'keyCode': 54, 'key': '^', 'code': 'Digit6' },
'&': { 'keyCode': 55, 'key': '&', 'code': 'Digit7' },
'(': { 'keyCode': 57, 'key': '\(', 'code': 'Digit9' },
'A': { 'keyCode': 65, 'key': 'A', 'code': 'KeyA' },
'B': { 'keyCode': 66, 'key': 'B', 'code': 'KeyB' },
'C': { 'keyCode': 67, 'key': 'C', 'code': 'KeyC' },
'D': { 'keyCode': 68, 'key': 'D', 'code': 'KeyD' },
'E': { 'keyCode': 69, 'key': 'E', 'code': 'KeyE' },
'F': { 'keyCode': 70, 'key': 'F', 'code': 'KeyF' },
'G': { 'keyCode': 71, 'key': 'G', 'code': 'KeyG' },
'H': { 'keyCode': 72, 'key': 'H', 'code': 'KeyH' },
'I': { 'keyCode': 73, 'key': 'I', 'code': 'KeyI' },
'J': { 'keyCode': 74, 'key': 'J', 'code': 'KeyJ' },
'K': { 'keyCode': 75, 'key': 'K', 'code': 'KeyK' },
'L': { 'keyCode': 76, 'key': 'L', 'code': 'KeyL' },
'M': { 'keyCode': 77, 'key': 'M', 'code': 'KeyM' },
'N': { 'keyCode': 78, 'key': 'N', 'code': 'KeyN' },
'O': { 'keyCode': 79, 'key': 'O', 'code': 'KeyO' },
'P': { 'keyCode': 80, 'key': 'P', 'code': 'KeyP' },
'Q': { 'keyCode': 81, 'key': 'Q', 'code': 'KeyQ' },
'R': { 'keyCode': 82, 'key': 'R', 'code': 'KeyR' },
'S': { 'keyCode': 83, 'key': 'S', 'code': 'KeyS' },
'T': { 'keyCode': 84, 'key': 'T', 'code': 'KeyT' },
'U': { 'keyCode': 85, 'key': 'U', 'code': 'KeyU' },
'V': { 'keyCode': 86, 'key': 'V', 'code': 'KeyV' },
'W': { 'keyCode': 87, 'key': 'W', 'code': 'KeyW' },
'X': { 'keyCode': 88, 'key': 'X', 'code': 'KeyX' },
'Y': { 'keyCode': 89, 'key': 'Y', 'code': 'KeyY' },
'Z': { 'keyCode': 90, 'key': 'Z', 'code': 'KeyZ' },
':': { 'keyCode': 186, 'key': ':', 'code': 'Semicolon' },
'<': { 'keyCode': 188, 'key': '\<', 'code': 'Comma' },
'_': { 'keyCode': 189, 'key': '_', 'code': 'Minus' },
'>': { 'keyCode': 190, 'key': '>', 'code': 'Period' },
'?': { 'keyCode': 191, 'key': '?', 'code': 'Slash' },
'~': { 'keyCode': 192, 'key': '~', 'code': 'Backquote' },
'{': { 'keyCode': 219, 'key': '{', 'code': 'BracketLeft' },
'|': { 'keyCode': 220, 'key': '|', 'code': 'Backslash' },
'}': { 'keyCode': 221, 'key': '}', 'code': 'BracketRight' },
'"': { 'keyCode': 222, 'key': '"', 'code': 'Quote' },
'SoftLeft': { 'key': 'SoftLeft', 'code': 'SoftLeft', 'location': 4 },
'SoftRight': { 'key': 'SoftRight', 'code': 'SoftRight', 'location': 4 },
'Camera': { 'keyCode': 44, 'key': 'Camera', 'code': 'Camera', 'location': 4 },
'Call': { 'key': 'Call', 'code': 'Call', 'location': 4 },
'EndCall': { 'keyCode': 95, 'key': 'EndCall', 'code': 'EndCall', 'location': 4 },
'VolumeDown': { 'keyCode': 182, 'key': 'VolumeDown', 'code': 'VolumeDown', 'location': 4 },
'VolumeUp': { 'keyCode': 183, 'key': 'VolumeUp', 'code': 'VolumeUp', 'location': 4 },
exports.USKeyboardLayout = {
// Functions row
'Escape': { 'keyCode': 27, 'key': 'Escape' },
'F1': { 'keyCode': 112, 'key': 'F1' },
'F2': { 'keyCode': 113, 'key': 'F2' },
'F3': { 'keyCode': 114, 'key': 'F3' },
'F4': { 'keyCode': 115, 'key': 'F4' },
'F5': { 'keyCode': 116, 'key': 'F5' },
'F6': { 'keyCode': 117, 'key': 'F6' },
'F7': { 'keyCode': 118, 'key': 'F7' },
'F8': { 'keyCode': 119, 'key': 'F8' },
'F9': { 'keyCode': 120, 'key': 'F9' },
'F10': { 'keyCode': 121, 'key': 'F10' },
'F11': { 'keyCode': 122, 'key': 'F11' },
'F12': { 'keyCode': 123, 'key': 'F12' },
// Numbers row
'Backquote': { 'keyCode': 192, 'shiftKey': '~', 'key': '`' },
'Digit1': { 'keyCode': 49, 'shiftKey': '!', 'key': '1' },
'Digit2': { 'keyCode': 50, 'shiftKey': '@', 'key': '2' },
'Digit3': { 'keyCode': 51, 'shiftKey': '#', 'key': '3' },
'Digit4': { 'keyCode': 52, 'shiftKey': '$', 'key': '4' },
'Digit5': { 'keyCode': 53, 'shiftKey': '%', 'key': '5' },
'Digit6': { 'keyCode': 54, 'shiftKey': '^', 'key': '6' },
'Digit7': { 'keyCode': 55, 'shiftKey': '&', 'key': '7' },
'Digit8': { 'keyCode': 56, 'shiftKey': '*', 'key': '8' },
'Digit9': { 'keyCode': 57, 'shiftKey': '\(', 'key': '9' },
'Digit0': { 'keyCode': 48, 'shiftKey': ')', 'key': '0' },
'Minus': { 'keyCode': 189, 'shiftKey': '_', 'key': '-' },
'Equal': { 'keyCode': 187, 'shiftKey': '+', 'key': '=' },
'Backslash': { 'keyCode': 220, 'shiftKey': '|', 'key': '\\' },
'Backspace': { 'keyCode': 8, 'key': 'Backspace' },
// First row
'Tab': { 'keyCode': 9, 'key': 'Tab' },
'KeyQ': { 'keyCode': 81, 'shiftKey': 'Q', 'key': 'q' },
'KeyW': { 'keyCode': 87, 'shiftKey': 'W', 'key': 'w' },
'KeyE': { 'keyCode': 69, 'shiftKey': 'E', 'key': 'e' },
'KeyR': { 'keyCode': 82, 'shiftKey': 'R', 'key': 'r' },
'KeyT': { 'keyCode': 84, 'shiftKey': 'T', 'key': 't' },
'KeyY': { 'keyCode': 89, 'shiftKey': 'Y', 'key': 'y' },
'KeyU': { 'keyCode': 85, 'shiftKey': 'U', 'key': 'u' },
'KeyI': { 'keyCode': 73, 'shiftKey': 'I', 'key': 'i' },
'KeyO': { 'keyCode': 79, 'shiftKey': 'O', 'key': 'o' },
'KeyP': { 'keyCode': 80, 'shiftKey': 'P', 'key': 'p' },
'BracketLeft': { 'keyCode': 219, 'shiftKey': '{', 'key': '[' },
'BracketRight': { 'keyCode': 221, 'shiftKey': '}', 'key': ']' },
// Second row
'CapsLock': { 'keyCode': 20, 'key': 'CapsLock' },
'KeyA': { 'keyCode': 65, 'shiftKey': 'A', 'key': 'a' },
'KeyS': { 'keyCode': 83, 'shiftKey': 'S', 'key': 's' },
'KeyD': { 'keyCode': 68, 'shiftKey': 'D', 'key': 'd' },
'KeyF': { 'keyCode': 70, 'shiftKey': 'F', 'key': 'f' },
'KeyG': { 'keyCode': 71, 'shiftKey': 'G', 'key': 'g' },
'KeyH': { 'keyCode': 72, 'shiftKey': 'H', 'key': 'h' },
'KeyJ': { 'keyCode': 74, 'shiftKey': 'J', 'key': 'j' },
'KeyK': { 'keyCode': 75, 'shiftKey': 'K', 'key': 'k' },
'KeyL': { 'keyCode': 76, 'shiftKey': 'L', 'key': 'l' },
'Semicolon': { 'keyCode': 186, 'shiftKey': ':', 'key': ';' },
'Quote': { 'keyCode': 222, 'shiftKey': '"', 'key': '\'' },
'Enter': { 'keyCode': 13, 'key': 'Enter', 'text': '\r' },
// Third row
'ShiftLeft': { 'keyCode': 160, 'keyCodeWithoutLocation': 16, 'key': 'Shift', 'location': 1 },
'KeyZ': { 'keyCode': 90, 'shiftKey': 'Z', 'key': 'z' },
'KeyX': { 'keyCode': 88, 'shiftKey': 'X', 'key': 'x' },
'KeyC': { 'keyCode': 67, 'shiftKey': 'C', 'key': 'c' },
'KeyV': { 'keyCode': 86, 'shiftKey': 'V', 'key': 'v' },
'KeyB': { 'keyCode': 66, 'shiftKey': 'B', 'key': 'b' },
'KeyN': { 'keyCode': 78, 'shiftKey': 'N', 'key': 'n' },
'KeyM': { 'keyCode': 77, 'shiftKey': 'M', 'key': 'm' },
'Comma': { 'keyCode': 188, 'shiftKey': '\<', 'key': ',' },
'Period': { 'keyCode': 190, 'shiftKey': '>', 'key': '.' },
'Slash': { 'keyCode': 191, 'shiftKey': '?', 'key': '/' },
'ShiftRight': { 'keyCode': 161, 'keyCodeWithoutLocation': 16, 'key': 'Shift', 'location': 2 },
// Last row
'ControlLeft': { 'keyCode': 162, 'keyCodeWithoutLocation': 17, 'key': 'Control', 'location': 1 },
'MetaLeft': { 'keyCode': 91, 'key': 'Meta', 'location': 1 },
'AltLeft': { 'keyCode': 164, 'keyCodeWithoutLocation': 18, 'key': 'Alt', 'location': 1 },
'Space': { 'keyCode': 32, 'key': ' ' },
'AltRight': { 'keyCode': 165, 'keyCodeWithoutLocation': 18, 'key': 'Alt', 'location': 2 },
'AltGraph': { 'keyCode': 225, 'key': 'AltGraph' },
'MetaRight': { 'keyCode': 92, 'key': 'Meta', 'location': 2 },
'ContextMenu': { 'keyCode': 93, 'key': 'ContextMenu' },
'ControlRight': { 'keyCode': 163, 'keyCodeWithoutLocation': 17, 'key': 'Control', 'location': 2 },
// Center block
'PrintScreen': { 'keyCode': 44, 'key': 'PrintScreen' },
'ScrollLock': { 'keyCode': 145, 'key': 'ScrollLock' },
'Pause': { 'keyCode': 19, 'key': 'Pause' },
'PageUp': { 'keyCode': 33, 'key': 'PageUp' },
'PageDown': { 'keyCode': 34, 'key': 'PageDown' },
'Insert': { 'keyCode': 45, 'key': 'Insert' },
'Delete': { 'keyCode': 46, 'key': 'Delete' },
'Home': { 'keyCode': 36, 'key': 'Home' },
'End': { 'keyCode': 35, 'key': 'End' },
'ArrowLeft': { 'keyCode': 37, 'key': 'ArrowLeft' },
'ArrowUp': { 'keyCode': 38, 'key': 'ArrowUp' },
'ArrowRight': { 'keyCode': 39, 'key': 'ArrowRight' },
'ArrowDown': { 'keyCode': 40, 'key': 'ArrowDown' },
// Numpad
'NumLock': { 'keyCode': 144, 'key': 'NumLock' },
'NumpadDivide': { 'keyCode': 111, 'key': '/', 'location': 3 },
'NumpadMultiply': { 'keyCode': 106, 'key': '*', 'location': 3 },
'NumpadSubtract': { 'keyCode': 109, 'key': '-', 'location': 3 },
'Numpad7': { 'keyCode': 36, 'shiftKeyCode': 103, 'key': 'Home', 'shiftKey': '7', 'location': 3 },
'Numpad8': { 'keyCode': 38, 'shiftKeyCode': 104, 'key': 'ArrowUp', 'shiftKey': '8', 'location': 3 },
'Numpad9': { 'keyCode': 33, 'shiftKeyCode': 105, 'key': 'PageUp', 'shiftKey': '9', 'location': 3 },
'Numpad4': { 'keyCode': 37, 'shiftKeyCode': 100, 'key': 'ArrowLeft', 'shiftKey': '4', 'location': 3 },
'Numpad5': { 'keyCode': 12, 'shiftKeyCode': 101, 'key': 'Clear', 'shiftKey': '5', 'location': 3 },
'Numpad6': { 'keyCode': 39, 'shiftKeyCode': 102, 'key': 'ArrowRight', 'shiftKey': '6', 'location': 3 },
'NumpadAdd': { 'keyCode': 107, 'key': '+', 'location': 3 },
'Numpad1': { 'keyCode': 35, 'shiftKeyCode': 97, 'key': 'End', 'shiftKey': '1', 'location': 3 },
'Numpad2': { 'keyCode': 40, 'shiftKeyCode': 98, 'key': 'ArrowDown', 'shiftKey': '2', 'location': 3 },
'Numpad3': { 'keyCode': 34, 'shiftKeyCode': 99, 'key': 'PageDown', 'shiftKey': '3', 'location': 3 },
'Numpad0': { 'keyCode': 45, 'shiftKeyCode': 96, 'key': 'Insert', 'shiftKey': '0', 'location': 3 },
'NumpadDecimal': { 'keyCode': 46, 'shiftKeyCode': 110, 'key': '\u0000', 'shiftKey': '.', 'location': 3 },
'NumpadEnter': { 'keyCode': 13, 'key': 'Enter', 'text': '\r', 'location': 3 },
};
exports.macEditingCommands = {
'Backspace': 'deleteBackward:',
'Tab': 'insertTab:',
'Enter': 'insertNewline:',
'NumpadEnter': 'insertNewline:',
'Escape': 'cancelOperation:',
'ArrowUp': 'moveUp:',
'ArrowDown': 'moveDown:',
'ArrowLeft': 'moveLeft:',
'ArrowRight': 'moveRight:',
'F5': 'complete:',
'Delete': 'deleteForward:',
'Home': 'scrollToBeginningOfDocument:',
'End': 'scrollToEndOfDocument:',
'PageUp': 'scrollPageUp:',
'PageDown': 'scrollPageDown:',
'Shift+Backspace': 'deleteBackward:',
'Shift+Enter': 'insertNewline:',
'Shift+NumpadEnter': 'insertNewline:',
'Shift+Tab': 'insertBacktab:',
'Shift+Escape': 'cancelOperation:',
'Shift+ArrowUp': 'moveUpAndModifySelection:',
'Shift+ArrowDown': 'moveDownAndModifySelection:',
'Shift+ArrowLeft': 'moveLeftAndModifySelection:',
'Shift+ArrowRight': 'moveRightAndModifySelection:',
'Shift+F5': 'complete:',
'Shift+Delete': 'deleteForward:',
'Shift+Home': 'moveToBeginningOfDocumentAndModifySelection:',
'Shift+End': 'moveToEndOfDocumentAndModifySelection:',
'Shift+PageUp': 'pageUpAndModifySelection:',
'Shift+PageDown': 'pageDownAndModifySelection:',
'Shift+Numpad5': 'delete:',
'Control+Tab': 'selectNextKeyView:',
'Control+Enter': 'insertLineBreak:',
'Control+NumpadEnter': 'insertLineBreak:',
'Control+Quote': 'insertSingleQuoteIgnoringSubstitution:',
'Control+KeyA': 'moveToBeginningOfParagraph:',
'Control+KeyB': 'moveBackward:',
'Control+KeyD': 'deleteForward:',
'Control+KeyE': 'moveToEndOfParagraph:',
'Control+KeyF': 'moveForward:',
'Control+KeyH': 'deleteBackward:',
'Control+KeyK': 'deleteToEndOfParagraph:',
'Control+KeyL': 'centerSelectionInVisibleArea:',
'Control+KeyN': 'moveDown:',
'Control+KeyO': ['insertNewlineIgnoringFieldEditor:', 'moveBackward:'],
'Control+KeyP': 'moveUp:',
'Control+KeyT': 'transpose:',
'Control+KeyV': 'pageDown:',
'Control+KeyY': 'yank:',
'Control+Backspace': 'deleteBackwardByDecomposingPreviousCharacter:',
'Control+ArrowUp': 'scrollPageUp:',
'Control+ArrowDown': 'scrollPageDown:',
'Control+ArrowLeft': 'moveToLeftEndOfLine:',
'Control+ArrowRight': 'moveToRightEndOfLine:',
'Shift+Control+Enter': 'insertLineBreak:',
'Shift+Control+NumpadEnter': 'insertLineBreak:',
'Shift+Control+Tab': 'selectPreviousKeyView:',
'Shift+Control+Quote': 'insertDoubleQuoteIgnoringSubstitution:',
'Shift+Control+KeyA': 'moveToBeginningOfParagraphAndModifySelection:',
'Shift+Control+KeyB': 'moveBackwardAndModifySelection:',
'Shift+Control+KeyE': 'moveToEndOfParagraphAndModifySelection:',
'Shift+Control+KeyF': 'moveForwardAndModifySelection:',
'Shift+Control+KeyN': 'moveDownAndModifySelection:',
'Shift+Control+KeyP': 'moveUpAndModifySelection:',
'Shift+Control+KeyV': 'pageDownAndModifySelection:',
'Shift+Control+Backspace': 'deleteBackwardByDecomposingPreviousCharacter:',
'Shift+Control+ArrowUp': 'scrollPageUp:',
'Shift+Control+ArrowDown': 'scrollPageDown:',
'Shift+Control+ArrowLeft': 'moveToLeftEndOfLineAndModifySelection:',
'Shift+Control+ArrowRight': 'moveToRightEndOfLineAndModifySelection:',
'Alt+Backspace': 'deleteWordBackward:',
'Alt+Tab': 'insertTabIgnoringFieldEditor:',
'Alt+Enter': 'insertNewlineIgnoringFieldEditor:',
'Alt+NumpadEnter': 'insertNewlineIgnoringFieldEditor:',
'Alt+Escape': 'complete:',
'Alt+ArrowUp': ['moveBackward:', 'moveToBeginningOfParagraph:'],
'Alt+ArrowDown': ['moveForward:', 'moveToEndOfParagraph:'],
'Alt+ArrowLeft': 'moveWordLeft:',
'Alt+ArrowRight': 'moveWordRight:',
'Alt+Delete': 'deleteWordForward:',
'Alt+PageUp': 'pageUp:',
'Alt+PageDown': 'pageDown:',
'Shift+Alt+Backspace': 'deleteWordBackward:',
'Shift+Alt+Tab': 'insertTabIgnoringFieldEditor:',
'Shift+Alt+Enter': 'insertNewlineIgnoringFieldEditor:',
'Shift+Alt+NumpadEnter': 'insertNewlineIgnoringFieldEditor:',
'Shift+Alt+Escape': 'complete:',
'Shift+Alt+ArrowUp': 'moveParagraphBackwardAndModifySelection:',
'Shift+Alt+ArrowDown': 'moveParagraphForwardAndModifySelection:',
'Shift+Alt+ArrowLeft': 'moveWordLeftAndModifySelection:',
'Shift+Alt+ArrowRight': 'moveWordRightAndModifySelection:',
'Shift+Alt+Delete': 'deleteWordForward:',
'Shift+Alt+PageUp': 'pageUp:',
'Shift+Alt+PageDown': 'pageDown:',
'Control+Alt+KeyB': 'moveWordBackward:',
'Control+Alt+KeyF': 'moveWordForward:',
'Control+Alt+Backspace': 'deleteWordBackward:',
'Shift+Control+Alt+KeyB': 'moveWordBackwardAndModifySelection:',
'Shift+Control+Alt+KeyF': 'moveWordForwardAndModifySelection:',
'Shift+Control+Alt+Backspace': 'deleteWordBackward:',
'Meta+NumpadSubtract': 'cancel:',
'Meta+Backspace': 'deleteToBeginningOfLine:',
'Meta+ArrowUp': 'moveToBeginningOfDocument:',
'Meta+ArrowDown': 'moveToEndOfDocument:',
'Meta+ArrowLeft': 'moveToLeftEndOfLine:',
'Meta+ArrowRight': 'moveToRightEndOfLine:',
'Shift+Meta+NumpadSubtract': 'cancel:',
'Shift+Meta+Backspace': 'deleteToBeginningOfLine:',
'Shift+Meta+ArrowUp': 'moveToBeginningOfDocumentAndModifySelection:',
'Shift+Meta+ArrowDown': 'moveToEndOfDocumentAndModifySelection:',
'Shift+Meta+ArrowLeft': 'moveToLeftEndOfLineAndModifySelection:',
'Shift+Meta+ArrowRight': 'moveToRightEndOfLineAndModifySelection:',
'Meta+KeyA': 'selectAll:',
};
//# sourceMappingURL=usKeyboardLayout.js.map

@@ -25,3 +25,2 @@ "use strict";

const page_1 = require("../page");
const platform = require("../platform");
const transport_1 = require("../transport");

@@ -31,13 +30,13 @@ const wkConnection_1 = require("./wkConnection");

const DEFAULT_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15';
class WKBrowser extends platform.EventEmitter {
constructor(transport, attachToDefaultContext) {
super();
class WKBrowser extends browser_1.BrowserBase {
constructor(transport, logger, attachToDefaultContext) {
super(logger);
this._defaultContext = null;
this._contexts = new Map();
this._wkPages = new Map();
this._popupOpeners = [];
this._firstPageCallback = () => { };
this._connection = new wkConnection_1.WKConnection(transport, this._onDisconnect.bind(this));
this._attachToDefaultContext = attachToDefaultContext;
this._connection = new wkConnection_1.WKConnection(transport, logger, this._onDisconnect.bind(this));
this._browserSession = this._connection.browserSession;
this._defaultContext = new WKBrowserContext(this, undefined, browserContext_1.validateBrowserContextOptions({}));
if (attachToDefaultContext)
this._defaultContext = new WKBrowserContext(this, undefined, browserContext_1.validateBrowserContextOptions({}));
this._eventListeners = [

@@ -47,3 +46,4 @@ helper_1.helper.addEventListener(this._browserSession, 'Playwright.pageProxyCreated', this._onPageProxyCreated.bind(this)),

helper_1.helper.addEventListener(this._browserSession, 'Playwright.provisionalLoadFailed', event => this._onProvisionalLoadFailed(event)),
helper_1.helper.addEventListener(this._browserSession, 'Playwright.windowOpen', this._onWindowOpen.bind(this)),
helper_1.helper.addEventListener(this._browserSession, 'Playwright.downloadCreated', this._onDownloadCreated.bind(this)),
helper_1.helper.addEventListener(this._browserSession, 'Playwright.downloadFinished', this._onDownloadFinished.bind(this)),
helper_1.helper.addEventListener(this._browserSession, wkConnection_1.kPageProxyMessageReceived, this._onPageProxyMessageReceived.bind(this)),

@@ -53,4 +53,4 @@ ];

}
static async connect(transport, slowMo = 0, attachToDefaultContext = false) {
const browser = new WKBrowser(transport_1.SlowMoTransport.wrap(transport, slowMo), attachToDefaultContext);
static async connect(transport, logger, slowMo = 0, attachToDefaultContext = false) {
const browser = new WKBrowser(transport_1.SlowMoTransport.wrap(transport, slowMo), logger, attachToDefaultContext);
return browser;

@@ -79,5 +79,2 @@ }

}
async newPage(options) {
return browser_1.createPageInNewContext(this, options);
}
async _waitForFirstPageTarget() {

@@ -87,5 +84,11 @@ helper_1.assert(!this._wkPages.size);

}
_onWindowOpen(payload) {
this._popupOpeners.push(payload.pageProxyId);
_onDownloadCreated(payload) {
const page = this._wkPages.get(payload.pageProxyId);
if (!page)
return;
this._downloadCreated(page._page, payload.uuid, payload.url);
}
_onDownloadFinished(payload) {
this._downloadFinished(payload.uuid, payload.error);
}
_onPageProxyCreated(event) {

@@ -102,6 +105,6 @@ const { pageProxyInfo } = event;

}
if (!context && !this._attachToDefaultContext)
return;
if (!context)
context = this._defaultContext;
if (!context)
return;
const pageProxySession = new wkConnection_1.WKSession(this._connection, pageProxyId, `The page has been closed.`, (message) => {

@@ -111,15 +114,8 @@ this._connection.rawSend({ ...message, pageProxyId });

const opener = pageProxyInfo.openerId ? this._wkPages.get(pageProxyInfo.openerId) : undefined;
let hasInitialAboutBlank = false;
if (pageProxyInfo.openerId) {
const openerIndex = this._popupOpeners.indexOf(pageProxyInfo.openerId);
if (openerIndex !== -1) {
this._popupOpeners.splice(openerIndex, 1);
// When this page is a result of window.open($url) call, we should have it's opener
// in the list of popup openers. In this case we know there is an initial
// about:blank navigation, followed by a navigation to $url.
hasInitialAboutBlank = true;
}
const wkPage = new wkPage_1.WKPage(context, pageProxySession, opener || null);
this._wkPages.set(pageProxyId, wkPage);
if (opener && opener._initializedPage) {
for (const signalBarrier of opener._initializedPage._frameManager._signalBarriers)
signalBarrier.addPopup(wkPage.pageOrError());
}
const wkPage = new wkPage_1.WKPage(context, pageProxySession, opener || null, hasInitialAboutBlank);
this._wkPages.set(pageProxyId, wkPage);
wkPage.pageOrError().then(async () => {

@@ -161,12 +157,6 @@ this._firstPageCallback();

}
async close() {
_disconnect() {
helper_1.helper.removeEventListeners(this._eventListeners);
const disconnected = new Promise(f => this.once(events_1.Events.Browser.Disconnected, f));
await Promise.all(this.contexts().map(context => context.close()));
this._connection.close();
await disconnected;
}
_setDebugFunction(debugFunction) {
this._connection._debugProtocol = debugFunction;
}
}

@@ -176,3 +166,3 @@ exports.WKBrowser = WKBrowser;

constructor(browser, browserContextId, options) {
super(options);
super(browser, options);
this._browser = browser;

@@ -183,14 +173,23 @@ this._browserContextId = browserContextId;

async _initialize() {
const browserContextId = this._browserContextId;
const promises = [
this._browser._browserSession.send('Playwright.setDownloadBehavior', {
behavior: this._options.acceptDownloads ? 'allow' : 'deny',
downloadPath: this._browser._downloadsPath,
browserContextId
})
];
if (this._options.ignoreHTTPSErrors)
await this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId: this._browserContextId, ignore: true });
promises.push(this._browser._browserSession.send('Playwright.setIgnoreCertificateErrors', { browserContextId, ignore: true }));
if (this._options.locale)
await this._browser._browserSession.send('Playwright.setLanguages', { browserContextId: this._browserContextId, languages: [this._options.locale] });
promises.push(this._browser._browserSession.send('Playwright.setLanguages', { browserContextId, languages: [this._options.locale] }));
if (this._options.permissions)
await this.grantPermissions(this._options.permissions);
promises.push(this.grantPermissions(this._options.permissions));
if (this._options.geolocation)
await this.setGeolocation(this._options.geolocation);
promises.push(this.setGeolocation(this._options.geolocation));
if (this._options.offline)
await this.setOffline(this._options.offline);
promises.push(this.setOffline(this._options.offline));
if (this._options.httpCredentials)
await this.setHTTPCredentials(this._options.httpCredentials);
promises.push(this.setHTTPCredentials(this._options.httpCredentials));
await Promise.all(promises);
}

@@ -201,3 +200,3 @@ _wkPages() {

pages() {
return this._wkPages().map(wkPage => wkPage._initializedPage()).filter(pageOrNull => !!pageOrNull);
return this._wkPages().map(wkPage => wkPage._initializedPage).filter(pageOrNull => !!pageOrNull);
}

@@ -287,2 +286,7 @@ async newPage() {

}
async unroute(url, handler) {
this._routes = this._routes.filter(route => route.url !== url || (handler && route.handler !== handler));
for (const page of this.pages())
await page._delegate.updateRequestInterception();
}
async close() {

@@ -299,3 +303,3 @@ if (this._closed)

this._browser._contexts.delete(this._browserContextId);
this._didCloseInternal();
await this._didCloseInternal();
}

@@ -302,0 +306,0 @@ }

@@ -19,4 +19,5 @@ "use strict";

Object.defineProperty(exports, "__esModule", { value: true });
const events_1 = require("events");
const helper_1 = require("../helper");
const platform = require("../platform");
const transport_1 = require("../transport");
// WKPlaywright uses this special id to issue Browser.close command which we

@@ -29,7 +30,7 @@ // should ignore.

class WKConnection {
constructor(transport, onDisconnect) {
constructor(transport, logger, onDisconnect) {
this._lastId = 0;
this._closed = false;
this._debugProtocol = platform.debug('pw:protocol');
this._transport = transport;
this._logger = logger;
this._transport.onmessage = this._dispatchMessage.bind(this);

@@ -41,3 +42,2 @@ this._transport.onclose = this._onClose.bind(this);

});
this._debugProtocol.color = '34';
}

@@ -48,17 +48,17 @@ nextMessageId() {

rawSend(message) {
const data = JSON.stringify(message);
this._debugProtocol('SEND ► ' + (rewriteInjectedScriptEvaluationLog(message) || data));
this._transport.send(data);
if (this._logger._isLogEnabled(transport_1.protocolLog))
this._logger._log(transport_1.protocolLog, 'SEND ► ' + rewriteInjectedScriptEvaluationLog(message));
this._transport.send(message);
}
_dispatchMessage(message) {
this._debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
if (object.id === exports.kBrowserCloseMessageId)
if (this._logger._isLogEnabled(transport_1.protocolLog))
this._logger._log(transport_1.protocolLog, '◀ RECV ' + JSON.stringify(message));
if (message.id === exports.kBrowserCloseMessageId)
return;
if (object.pageProxyId) {
const payload = { message: object, pageProxyId: object.pageProxyId };
if (message.pageProxyId) {
const payload = { message: message, pageProxyId: message.pageProxyId };
this.browserSession.dispatchMessage({ method: exports.kPageProxyMessageReceived, params: payload });
return;
}
this.browserSession.dispatchMessage(object);
this.browserSession.dispatchMessage(message);
}

@@ -81,3 +81,3 @@ _onClose() {

exports.WKConnection = WKConnection;
class WKSession extends platform.EventEmitter {
class WKSession extends events_1.EventEmitter {
constructor(connection, sessionId, errorText, rawSend) {

@@ -87,2 +87,3 @@ super();

this._callbacks = new Map();
this._crashed = false;
this.connection = connection;

@@ -99,2 +100,4 @@ this.sessionId = sessionId;

async send(method, params) {
if (this._crashed)
throw new Error('Target crashed');
if (this._disposed)

@@ -104,3 +107,2 @@ throw new Error(`Protocol error (${method}): ${this.errorText}`);

const messageObj = { id, method, params };
platform.debug('pw:wrapped:' + this.sessionId)('SEND ► ' + JSON.stringify(messageObj, null, 2));
this._rawSend(messageObj);

@@ -111,2 +113,5 @@ return new Promise((resolve, reject) => {

}
markAsCrashed() {
this._crashed = true;
}
isDisposed() {

@@ -122,3 +127,2 @@ return this._disposed;

dispatchMessage(object) {
platform.debug('pw:wrapped:' + this.sessionId)('◀ RECV ' + JSON.stringify(object, null, 2));
if (object.id && this._callbacks.has(object.id)) {

@@ -128,3 +132,3 @@ const callback = this._callbacks.get(object.id);

if (object.error)
callback.reject(createProtocolError(callback.error, callback.method, object));
callback.reject(createProtocolError(callback.error, callback.method, object.error));
else

@@ -143,6 +147,6 @@ callback.resolve(object.result);

exports.WKSession = WKSession;
function createProtocolError(error, method, object) {
let message = `Protocol error (${method}): ${object.error.message}`;
if ('data' in object.error)
message += ` ${JSON.stringify(object.error.data)}`;
function createProtocolError(error, method, protocolError) {
let message = `Protocol error (${method}): ${protocolError.message}`;
if ('data' in protocolError)
message += ` ${JSON.stringify(protocolError.data)}`;
return rewriteError(error, message);

@@ -165,3 +169,4 @@ }

return `{"id":${message.id},"method":"${message.method}","params":{"message":[evaluate injected script],"targetId":"${message.params.targetId}"},"pageProxyId":${message.pageProxyId}}`;
return JSON.stringify(message);
}
//# sourceMappingURL=wkConnection.js.map

@@ -21,3 +21,3 @@ "use strict";

const helper_1 = require("../helper");
const usKeyboardLayout_1 = require("../usKeyboardLayout");
const macEditingCommands_1 = require("../macEditingCommands");
function toModifiersMask(modifiers) {

@@ -51,3 +51,3 @@ // From Source/WebKit/Shared/WebEvent.h

const shortcut = parts.join('+');
let commands = usKeyboardLayout_1.macEditingCommands[shortcut];
let commands = macEditingCommands_1.macEditingCommands[shortcut];
if (helper_1.helper.isString(commands))

@@ -54,0 +54,0 @@ commands = [commands];

@@ -21,3 +21,3 @@ "use strict";

const network = require("../network");
const platform = require("../platform");
const logger_1 = require("../logger");
const errorReasons = {

@@ -55,3 +55,3 @@ 'aborted': 'Cancellation',

// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
logger_1.logError(this.request._page);
});

@@ -62,3 +62,3 @@ }

const base64Encoded = !!response.body && !helper_1.helper.isString(response.body);
const responseBody = response.body ? (base64Encoded ? response.body.toString('base64') : response.body) : undefined;
const responseBody = response.body ? (base64Encoded ? response.body.toString('base64') : response.body) : '';
const responseHeaders = {};

@@ -72,3 +72,3 @@ if (response.headers) {

if (responseBody && !('content-length' in responseHeaders))
responseHeaders['content-length'] = String(platform.Buffer.byteLength(responseBody));
responseHeaders['content-length'] = String(Buffer.byteLength(responseBody));
await this._session.send('Network.interceptWithResponse', {

@@ -85,3 +85,3 @@ requestId: this._requestId,

// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
logger_1.logError(this.request._page);
});

@@ -99,3 +99,3 @@ }

// or the page was closed. We should tolerate these errors.
helper_1.debugError(error);
logger_1.logError(this.request._page);
});

@@ -106,3 +106,3 @@ }

const response = await this._session.send('Network.getResponseBody', { requestId: this._requestId });
return platform.Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
return Buffer.from(response.body, response.base64Encoded ? 'base64' : 'utf8');
};

@@ -109,0 +109,0 @@ return new network.Response(this.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);

@@ -30,9 +30,13 @@ "use strict";

const wkInput_1 = require("./wkInput");
const platform = require("../platform");
const wkAccessibility_1 = require("./wkAccessibility");
const wkProvisionalPage_1 = require("./wkProvisionalPage");
const selectors_1 = require("../selectors");
const jpeg = require("jpeg-js");
const png = require("pngjs");
const errors_1 = require("../errors");
const logger_1 = require("../logger");
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
const BINDING_CALL_MESSAGE = '__playwright_binding_call__';
class WKPage {
constructor(browserContext, pageProxySession, opener, hasInitialAboutBlank) {
constructor(browserContext, pageProxySession, opener) {
this._provisionalPage = null;

@@ -43,7 +47,7 @@ this._pagePromiseCallback = () => { };

this._evaluateOnNewDocumentSources = [];
this._initialized = false;
this._firstNonInitialNavigationCommittedCallback = () => { };
this._initializedPage = null;
this._firstNonInitialNavigationCommittedFulfill = () => { };
this._firstNonInitialNavigationCommittedReject = (e) => { };
this._pageProxySession = pageProxySession;
this._opener = opener;
this._hasInitialAboutBlank = hasInitialAboutBlank;
this.rawKeyboard = new wkInput_1.RawKeyboardImpl(pageProxySession);

@@ -56,3 +60,3 @@ this.rawMouse = new wkInput_1.RawMouseImpl(pageProxySession);

this._browserContext = browserContext;
this._page.on(events_1.Events.Page.FrameDetached, frame => this._removeContextsForFrame(frame, false));
this._page.on(events_1.Events.Page.FrameDetached, (frame) => this._removeContextsForFrame(frame, false));
this._eventListeners = [

@@ -65,7 +69,7 @@ helper_1.helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),

this._pagePromise = new Promise(f => this._pagePromiseCallback = f);
this._firstNonInitialNavigationCommittedPromise = new Promise(f => this._firstNonInitialNavigationCommittedCallback = f);
this._firstNonInitialNavigationCommittedPromise = new Promise((f, r) => {
this._firstNonInitialNavigationCommittedFulfill = f;
this._firstNonInitialNavigationCommittedReject = r;
});
}
_initializedPage() {
return this._initialized ? this._page : null;
}
async _initializePageProxySession() {

@@ -140,2 +144,3 @@ const promises = [

}
promises.push(this.updateEmulateMedia());
promises.push(session.send('Network.setExtraHTTPHeaders', { headers: this._calculateExtraHTTPHeaders() }));

@@ -173,4 +178,6 @@ if (contextOptions.offline)

helper_1.helper.removeEventListeners(this._sessionListeners);
if (crashed)
if (crashed) {
this._session.markAsCrashed();
this._page._didCrash();
}
}

@@ -193,2 +200,3 @@ }

this._page._didDisconnect();
this._firstNonInitialNavigationCommittedReject(new Error('Page closed'));
}

@@ -199,4 +207,8 @@ dispatchMessageToSession(message) {

handleProvisionalLoadFailed(event) {
if (!this._initialized || !this._provisionalPage)
if (!this._initializedPage) {
this._firstNonInitialNavigationCommittedReject(new Error('Initial load failed'));
return;
}
if (!this._provisionalPage)
return;
let errorText = event.error;

@@ -220,3 +232,3 @@ if (errorText.includes('cancelled'))

helper_1.assert(targetInfo.type === 'page', 'Only page targets are expected in WebKit, received: ' + targetInfo.type);
if (!this._initialized) {
if (!this._initializedPage) {
helper_1.assert(!targetInfo.isProvisional);

@@ -236,6 +248,19 @@ let pageOrError;

if (targetInfo.isPaused)
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(helper_1.debugError);
if (this._hasInitialAboutBlank)
await this._firstNonInitialNavigationCommittedPromise;
this._initialized = true;
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(logger_1.logError(this._page));
if ((pageOrError instanceof page_1.Page) && this._page.mainFrame().url() === '') {
try {
// Initial empty page has an empty url. We should wait until the first real url has been loaded,
// even if that url is about:blank. This is especially important for popups, where we need the
// actual url before interacting with it.
await this._firstNonInitialNavigationCommittedPromise;
}
catch (e) {
pageOrError = e;
}
}
else {
// Avoid rejection on disconnect.
this._firstNonInitialNavigationCommittedPromise.catch(() => { });
}
this._initializedPage = pageOrError instanceof page_1.Page ? pageOrError : null;
this._pagePromiseCallback(pageOrError);

@@ -249,3 +274,3 @@ }

this._provisionalPage.initializationPromise.then(() => {
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(helper_1.debugError);
this._pageProxySession.send('Target.resume', { targetId: targetInfo.targetId }).catch(logger_1.logError(this._page));
});

@@ -274,2 +299,4 @@ }

helper_1.helper.addEventListener(this._session, 'Page.domContentEventFired', event => this._onLifecycleEvent(event.frameId, 'domcontentloaded')),
helper_1.helper.addEventListener(this._session, 'Page.willRequestOpenWindow', event => this._onWillRequestOpenWindow()),
helper_1.helper.addEventListener(this._session, 'Page.didRequestOpenWindow', event => this._onDidRequestOpenWindow(event)),
helper_1.helper.addEventListener(this._session, 'Runtime.executionContextCreated', event => this._onExecutionContextCreated(event.context)),

@@ -297,6 +324,6 @@ helper_1.helper.addEventListener(this._session, 'Console.messageAdded', event => this._onConsoleMessage(event)),

sessions.push(this._provisionalPage._session);
await Promise.all(sessions.map(session => callback(session).catch(helper_1.debugError)));
await Promise.all(sessions.map(session => callback(session).catch(logger_1.logError(this._page))));
}
_onFrameScheduledNavigation(frameId) {
this._page._frameManager.frameRequestedNavigation(frameId);
this._page._frameManager.frameRequestedNavigation(frameId, '');
}

@@ -309,2 +336,12 @@ _onFrameStoppedLoading(frameId) {

}
_onWillRequestOpenWindow() {
for (const barrier of this._page._frameManager._signalBarriers)
barrier.expectPopup();
}
_onDidRequestOpenWindow(event) {
if (!event.opened) {
for (const barrier of this._page._frameManager._signalBarriers)
barrier.unexpectPopup();
}
}
_handleFrameTree(frameTree) {

@@ -331,3 +368,3 @@ this._onFrameAttached(frameTree.frame.id, frameTree.frame.parentId || null);

if (!initial)
this._firstNonInitialNavigationCommittedCallback();
this._firstNonInitialNavigationCommittedFulfill();
}

@@ -384,4 +421,12 @@ _onFrameNavigatedWithinDocument(frameId, url) {

if (level === 'error' && source === 'javascript') {
const error = new Error(text);
error.stack = 'Error: ' + error.message; // Nullify stack. Stack is supposed to contain error message as the first line.
const message = text.startsWith('Error: ') ? text.substring(7) : text;
const error = new Error(message);
if (event.message.stackTrace) {
error.stack = event.message.stackTrace.map(callFrame => {
return `${callFrame.functionName}@${callFrame.url}:${callFrame.lineNumber}:${callFrame.columnNumber}`;
}).join('\n');
}
else {
error.stack = '';
}
this._page.emit(events_1.Events.Page.PageError, error);

@@ -448,4 +493,5 @@ return;

}
async setEmulateMedia(mediaType, colorScheme) {
await this._forAllSessions(session => WKPage._setEmulateMedia(session, mediaType, colorScheme));
async updateEmulateMedia() {
const colorScheme = this._page._state.colorScheme || this._browserContext._options.colorScheme || 'light';
await this._forAllSessions(session => WKPage._setEmulateMedia(session, this._page._state.mediaType, colorScheme));
}

@@ -474,2 +520,6 @@ async setViewportSize(viewportSize) {

];
if (options.isMobile) {
const angle = viewport.width > viewport.height ? 90 : 0;
promises.push(this._session.send('Page.setOrientationOverride', { angle }));
}
await Promise.all(promises);

@@ -522,3 +572,3 @@ }

const script = this._bindingToScript(binding);
await Promise.all(this._page.frames().map(frame => frame.evaluate(script).catch(helper_1.debugError)));
await Promise.all(this._page.frames().map(frame => frame.evaluate(script).catch(logger_1.logError(this._page))));
}

@@ -549,3 +599,3 @@ async evaluateOnNewDocument(script) {

runBeforeUnload
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
}

@@ -556,14 +606,11 @@ canScreenshotOutsideViewport() {

async setBackgroundColor(color) {
// TODO: line below crashes, sort it out.
await this._session.send('Page.setDefaultBackgroundColorOverride', { color });
}
async takeScreenshot(format, documentRect, viewportRect, quality) {
// TODO: documentRect does not include pageScale, while backend considers it does.
// This brakes mobile screenshots of elements or full page.
const rect = (documentRect || viewportRect);
const result = await this._session.send('Page.snapshotRect', { ...rect, coordinateSystem: documentRect ? 'Page' : 'Viewport' });
const prefix = 'data:image/png;base64,';
let buffer = platform.Buffer.from(result.dataURL.substr(prefix.length), 'base64');
let buffer = Buffer.from(result.dataURL.substr(prefix.length), 'base64');
if (format === 'jpeg')
buffer = platform.pngToJpeg(buffer, quality);
buffer = jpeg.encode(png.PNG.sync.read(buffer), quality).data;
return buffer;

@@ -617,2 +664,4 @@ }

}).catch(e => {
if (e instanceof Error && e.message.includes('Node is detached from document'))
throw new errors_1.NotConnectedError();
if (e instanceof Error && e.message.includes('Node does not have a layout object'))

@@ -626,3 +675,3 @@ e.message = 'Node is either not visible or not an HTMLElement';

objectId: toRemoteObject(handle).objectId
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
if (!result)

@@ -642,3 +691,3 @@ return null;

const objectId = toRemoteObject(handle).objectId;
await this._session.send('DOM.setInputFiles', { objectId, files });
await this._session.send('DOM.setInputFiles', { objectId, files: dom.toFileTransferPayload(files) });
}

@@ -649,3 +698,3 @@ async adoptElementHandle(handle, to) {

executionContextId: to._delegate._contextId
}).catch(helper_1.debugError);
}).catch(logger_1.logError(this._page));
if (!result || result.object.subtype === 'null')

@@ -664,4 +713,3 @@ throw new Error('Unable to adopt element handle from a different document');

throw new Error('Frame has been detached.');
const context = await parent._utilityContext();
const handles = await context._$$('iframe');
const handles = await selectors_1.selectors._queryAll(parent, 'iframe', undefined, true /* allowUtilityContext */);
const items = await Promise.all(handles.map(async (handle) => {

@@ -693,2 +741,4 @@ const frame = await handle.contentFrame().catch(e => null);

const documentId = isNavigationRequest ? event.loaderId : undefined;
if (isNavigationRequest)
this._page._frameManager.frameUpdatedDocumentIdForNavigation(event.frameId, documentId);
const request = new wkInterceptableRequest_1.WKInterceptableRequest(session, this._page._needsRequestInterception(), frame, event, redirectedFrom, documentId);

@@ -695,0 +745,0 @@ this._requestIdToRequest.set(event.requestId, request);

@@ -33,3 +33,3 @@ "use strict";

helper_1.helper.addEventListener(session, 'Worker.workerCreated', (event) => {
const worker = new page_1.Worker(event.url);
const worker = new page_1.Worker(this._page, event.url);
const workerSession = new wkConnection_1.WKSession(session.connection, event.workerId, 'Most likely the worker has been closed.', (message) => {

@@ -36,0 +36,0 @@ session.send('Worker.sendMessageToWorker', {

{
"name": "playwright-core",
"version": "0.12.1",
"version": "0.13.0-next.1587452068410",
"description": "A high-level API to automate web browsers",

@@ -11,19 +11,19 @@ "repository": "github:Microsoft/playwright",

"playwright": {
"chromium_revision": "751710",
"firefox_revision": "1051",
"webkit_revision": "1182"
"chromium_revision": "759546",
"firefox_revision": "1084",
"webkit_revision": "1201"
},
"scripts": {
"ctest": "cross-env BROWSER=chromium node test/test.js",
"ftest": "cross-env BROWSER=firefox node test/test.js",
"wtest": "cross-env BROWSER=webkit node test/test.js",
"test": "cross-env BROWSER=all node test/test.js",
"cunit": "cross-env BROWSER=chromium node test/test.js",
"funit": "cross-env BROWSER=firefox node test/test.js",
"wunit": "cross-env BROWSER=webkit node test/test.js",
"unit": "cross-env BROWSER=all node test/test.js",
"ccoverage": "cross-env COVERAGE=true BROWSER=chromium node test/test.js",
"fcoverage": "cross-env COVERAGE=true BROWSER=firefox node test/test.js",
"wcoverage": "cross-env COVERAGE=true BROWSER=webkit node test/test.js",
"coverage": "cross-env COVERAGE=true BROWSER=all node test/test.js",
"ctest": "cross-env BROWSER=chromium node --unhandled-rejections=strict test/test.js",
"ftest": "cross-env BROWSER=firefox node --unhandled-rejections=strict test/test.js",
"wtest": "cross-env BROWSER=webkit node --unhandled-rejections=strict test/test.js",
"test": "cross-env BROWSER=all node --unhandled-rejections=strict test/test.js",
"cunit": "cross-env BROWSER=chromium node --unhandled-rejections=strict test/test.js",
"funit": "cross-env BROWSER=firefox node --unhandled-rejections=strict test/test.js",
"wunit": "cross-env BROWSER=webkit node --unhandled-rejections=strict test/test.js",
"unit": "cross-env BROWSER=all node --unhandled-rejections=strict test/test.js",
"ccoverage": "cross-env COVERAGE=true BROWSER=chromium node --unhandled-rejections=strict test/test.js",
"fcoverage": "cross-env COVERAGE=true BROWSER=firefox node --unhandled-rejections=strict test/test.js",
"wcoverage": "cross-env COVERAGE=true BROWSER=webkit node --unhandled-rejections=strict test/test.js",
"coverage": "cross-env COVERAGE=true BROWSER=all node --unhandled-rejections=strict test/test.js",
"eslint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe --ext js,ts ./src || eslint --ext js,ts ./src",

@@ -39,3 +39,2 @@ "tsc": "tsc -p .",

"watch": "node utils/runWebpack.js --mode='development' --watch --silent | tsc -w -p .",
"version": "node utils/sync_package_versions.js && npm run doc",
"test-types": "npm run generate-types && npx -p typescript@3.7.5 tsc -p utils/generate_types/test/tsconfig.json",

@@ -49,7 +48,8 @@ "generate-types": "node utils/generate_types/"

"dependencies": {
"debug": "^4.1.0",
"extract-zip": "^1.6.6",
"debug": "^4.1.1",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^3.0.0",
"jpeg-js": "^0.3.6",
"pngjs": "^3.4.0",
"jpeg-js": "^0.3.7",
"mime": "^2.4.4",
"pngjs": "^5.0.0",
"progress": "^2.0.3",

@@ -63,3 +63,4 @@ "proxy-from-env": "^1.1.0",

"@types/extract-zip": "^1.6.2",
"@types/node": "^8.10.34",
"@types/mime": "^2.0.1",
"@types/node": "^10.17.17",
"@types/pngjs": "^3.4.0",

@@ -77,3 +78,3 @@ "@types/proxy-from-env": "^1.0.0",

"formidable": "^1.2.1",
"minimist": "^1.2.0",
"minimist": "^1.2.5",
"ncp": "^2.0.0",

@@ -84,3 +85,3 @@ "node-stream-zip": "^1.8.2",

"ts-loader": "^6.1.2",
"typescript": "^3.7.5",
"typescript": "^3.8.3",
"webpack": "^4.41.0",

@@ -87,0 +88,0 @@ "webpack-cli": "^3.3.9"

@@ -1,5 +0,5 @@

# Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg?style=flat)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge-if-release -->[![Chromium version](https://img.shields.io/badge/chromium-83.0.4090.0-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge-if-release -->[![Firefox version](https://img.shields.io/badge/firefox-74.0b10-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> [![WebKit version](https://img.shields.io/badge/webkit-13.0.4-blue.svg?logo=safari)](https://webkit.org/) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)
# 🎭 Playwright
[![npm version](https://img.shields.io/npm/v/playwright.svg?style=flat)](https://www.npmjs.com/package/playwright) <!-- GEN:chromium-version-badge-if-release -->[![Chromium version](https://img.shields.io/badge/chromium-83.0.4101.0-blue.svg?logo=google-chrome)](https://www.chromium.org/Home)<!-- GEN:stop --> <!-- GEN:firefox-version-badge-if-release -->[![Firefox version](https://img.shields.io/badge/firefox-75.0b8-blue.svg?logo=mozilla-firefox)](https://www.mozilla.org/en-US/firefox/new/)<!-- GEN:stop --> [![WebKit version](https://img.shields.io/badge/webkit-13.0.4-blue.svg?logo=safari)](https://webkit.org/) [![Join Slack](https://img.shields.io/badge/join-slack-infomational)](https://join.slack.com/t/playwright/shared_invite/enQtOTEyMTUxMzgxMjIwLThjMDUxZmIyNTRiMTJjNjIyMzdmZDA3MTQxZWUwZTFjZjQwNGYxZGM5MzRmNzZlMWI5ZWUyOTkzMjE5Njg1NDg)
###### [API](https://github.com/microsoft/playwright/blob/v0.12.1/docs/api.md) | [Changelog](https://github.com/microsoft/playwright/releases) | [FAQ](#faq) | [Contributing](#contributing)
###### [API](https://github.com/microsoft/playwright/blob/v0.13.0/docs/api.md) | [Changelog](https://github.com/microsoft/playwright/releases) | [FAQ](#faq) | [Contributing](#contributing)

@@ -11,5 +11,5 @@

| ---: | :---: | :---: | :---: | :---: |
| Chromium| <!-- GEN:chromium-version-if-release-->83.0.4090.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Chromium| <!-- GEN:chromium-version-if-release-->83.0.4101.0<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| WebKit | 13.0.4 | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox | <!-- GEN:firefox-version-if-release -->74.0b10<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
| Firefox | <!-- GEN:firefox-version-if-release -->75.0b8<!-- GEN:stop --> | :white_check_mark: | :white_check_mark: | :white_check_mark: |
- Headless is supported for all the browsers on all platforms.

@@ -65,6 +65,5 @@

const context = await browser.newContext({
viewport: iPhone11.viewport,
userAgent: iPhone11.userAgent,
...iPhone11,
geolocation: { longitude: 12.492507, latitude: 41.889938 },
permissions: { 'https://www.google.com': ['geolocation'] }
permissions: ['geolocation']
});

@@ -71,0 +70,0 @@ const page = await context.newPage();

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc