New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

playwright-core

Package Overview
Dependencies
Maintainers
2
Versions
4782
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-post-next.1586393147597

lib/download.js

2

lib/api.js

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

exports.Dialog = dialog_1.Dialog;
var download_1 = require("./download");
exports.Download = download_1.Download;
var dom_1 = require("./dom");

@@ -26,0 +28,0 @@ exports.ElementHandle = dom_1.ElementHandle;

@@ -18,9 +18,44 @@ "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 transport_1 = require("./transport");
const events_2 = require("./events");
class BrowserBase extends events_1.EventEmitter {
constructor() {
super(...arguments);
this._downloadsPath = '';
this._downloads = new Map();
this._debugProtocol = transport_1.debugProtocol;
this._ownedServer = null;
}
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));
}
}
exports.createPageInNewContext = createPageInNewContext;
exports.BrowserBase = BrowserBase;
//# sourceMappingURL=browser.js.map

27

lib/browserContext.js

@@ -21,6 +21,6 @@ "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 {
const extendedEventEmitter_1 = require("./extendedEventEmitter");
class BrowserContextBase extends extendedEventEmitter_1.ExtendedEventEmitter {
constructor(options) {

@@ -33,14 +33,24 @@ super();

this._permissions = new Map();
this._downloads = new Set();
this._options = options;
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 +79,2 @@ 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);
}
}

@@ -80,0 +81,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,3 +32,3 @@ const crConnection_1 = require("./crConnection");

const crExecutionContext_1 = require("./crExecutionContext");
class CRBrowser extends platform.EventEmitter {
class CRBrowser extends browser_1.BrowserBase {
constructor(connection) {

@@ -61,21 +60,29 @@ super();

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;

@@ -94,5 +101,2 @@ }

}
async newPage(options) {
return browser_1.createPageInNewContext(this, options);
}
_onAttachedToTarget({ targetInfo, sessionId, waitingForDebugger }) {

@@ -167,7 +171,4 @@ const session = this._connection.session(sessionId);

}
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;
}

@@ -179,3 +180,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 = [

@@ -199,10 +200,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;
}

@@ -217,5 +217,2 @@ isConnected() {

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

@@ -243,8 +240,16 @@ exports.CRBrowser = CRBrowser;

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);
}

@@ -282,2 +287,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 });

@@ -323,3 +335,3 @@ }

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

@@ -334,3 +346,3 @@ async setExtraHTTPHeaders(headers) {

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

@@ -340,3 +352,3 @@ async setHTTPCredentials(httpCredentials) {

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

@@ -377,3 +389,3 @@ async addInitScript(script, arg) {

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

@@ -380,0 +392,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,3 +29,3 @@ Disconnected: Symbol('ConnectionEvents.Disconnected')

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

@@ -40,4 +41,2 @@ super();

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

@@ -50,33 +49,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 (transport_1.debugProtocol.enabled)
transport_1.debugProtocol('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 (transport_1.debugProtocol.enabled)
transport_1.debugProtocol('◀ 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);
}

@@ -109,3 +108,3 @@ _onClose() {

};
class CRSession extends platform.EventEmitter {
class CRSession extends events_1.EventEmitter {
constructor(connection, rootSessionId, targetType, sessionId) {

@@ -127,3 +126,3 @@ super();

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) => {

@@ -138,3 +137,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

@@ -165,6 +164,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);

@@ -181,3 +180,4 @@ }

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

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

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

@@ -174,3 +173,3 @@ constructor(client, page) {

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');
};

@@ -250,3 +249,3 @@ return new network.Response(request.request, responsePayload.status, responsePayload.statusText, headersObject(responsePayload.headers), getResponseBody);

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 = {};

@@ -260,3 +259,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', {

@@ -263,0 +262,0 @@ requestId: this._interceptionId,

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

const console_1 = require("../console");
const platform = require("../platform");
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;

@@ -52,37 +48,255 @@ this._opener = opener;

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);
helper_1.assert(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(helper_1.debugError)));
}
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.evaluate(dom.setFileInputFunction, 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._firstNonInitialNavigationCommittedCallback = () => { };
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 => this._firstNonInitialNavigationCommittedCallback = f);
}
_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.

@@ -94,6 +308,6 @@ this._client.send('Page.createIsolatedWorld', {

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

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

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

@@ -129,4 +343,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)

@@ -140,14 +356,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'));

@@ -157,8 +371,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 });

@@ -187,2 +400,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);

@@ -202,2 +421,7 @@ }

_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);

@@ -230,2 +454,6 @@ }

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') {

@@ -255,5 +483,6 @@ // Ideally, detaching should resume any target, but there is a bug in the backend.

// 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);

@@ -282,6 +511,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) {

@@ -318,8 +543,17 @@ await Promise.all([

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

@@ -329,8 +563,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 };

@@ -352,79 +595,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', {

@@ -437,3 +620,3 @@ objectId: toRemoteObject(handle).objectId

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

@@ -459,6 +642,3 @@ 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', {

@@ -476,3 +656,3 @@ objectId: toRemoteObject(handle).objectId

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

@@ -487,3 +667,3 @@ objectId: toRemoteObject(handle).objectId,

}
async getContentQuads(handle) {
async _getContentQuads(handle) {
const result = await this._client.send('DOM.getContentQuads', {

@@ -501,16 +681,9 @@ objectId: toRemoteObject(handle).objectId

}
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', {

@@ -524,27 +697,3 @@ backendNodeId,

}
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) {

@@ -551,0 +700,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);
}

@@ -90,0 +85,0 @@ exports.readProtocolStream = readProtocolStream;

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

Object.defineProperty(exports, "__esModule", { value: true });
const debug = require("debug");
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 debugInput = debug('pw:input');
class FrameExecutionContext extends js.ExecutionContext {
constructor(delegate, frame) {
super(delegate);
this._injectedGeneration = -1;
this.frame = frame;

@@ -38,3 +41,3 @@ }

return this._delegate.evaluate(this, returnByValue, pageFunction, ...args);
}, waitForNavigations ? undefined : { waitUntil: 'nowait' });
}, Number.MAX_SAFE_INTEGER, waitForNavigations ? undefined : { waitUntil: 'nowait' });
}

@@ -47,45 +50,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;
}
}

@@ -124,3 +91,5 @@ exports.FrameExecutionContext = FrameExecutionContext;

async _scrollRectIntoViewIfNeeded(rect) {
debugInput('scrolling into veiw if needed...');
await this._page._delegate.scrollRectIntoViewIfNeeded(this, rect);
debugInput('...done');
}

@@ -182,11 +151,14 @@ async scrollIntoViewIfNeeded() {

}
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;
if (!force)
await this._waitForHitTargetAt(point, options);
await this._waitForHitTargetAt(point, deadline);
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {

@@ -196,6 +168,8 @@ let restoreModifiers;

restoreModifiers = await this._page.keyboard._ensureModifiers(options.modifiers);
debugInput('performing input action...');
await action(point);
debugInput('...done');
if (restoreModifiers)
await this._page.keyboard._ensureModifiers(restoreModifiers);
}, options, true);
}, deadline, options, true);
}

@@ -212,2 +186,3 @@ hover(options) {

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

@@ -231,17 +206,21 @@ if (!Array.isArray(values))

return this._evaluateInUtility(({ injected, node }, selectOptions) => injected.selectOptions(node, selectOptions), selectOptions);
}, options);
}, 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) + '"');
const deadline = this._page._timeoutSettings.computeDeadline(options);
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 errorOrNeedsInput = await this._evaluateInUtility(({ injected, node }, value) => injected.fill(node, value), value);
if (typeof errorOrNeedsInput === 'string')
throw new Error(errorOrNeedsInput);
if (errorOrNeedsInput) {
if (value)
await this._page.keyboard.insertText(value);
else
await this._page.keyboard.press('Delete');
}
}, deadline, options, true);
}
async setInputFiles(files) {
async setInputFiles(files, options) {
const deadline = this._page._timeoutSettings.computeDeadline(options);
const multiple = await this._evaluateInUtility(({ node }) => {

@@ -263,5 +242,5 @@ if (node.nodeType !== Node.ELEMENT_NODE || node.tagName !== 'INPUT')

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

@@ -276,3 +255,3 @@ filePayloads.push(file);

await this._page._delegate.setInputFiles(this, filePayloads);
});
}, deadline, options);
}

@@ -290,12 +269,14 @@ async focus() {

async type(text, options) {
const deadline = this._page._timeoutSettings.computeDeadline(options);
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
await this.focus();
await this._page.keyboard.type(text, options);
}, options, true);
}, deadline, options, true);
}
async press(key, options) {
const deadline = this._page._timeoutSettings.computeDeadline(options);
await this._page._frameManager.waitForNavigationsCreatedBy(async () => {
await this.focus();
await this._page.keyboard.press(key, options);
}, options, true);
}, deadline, options, true);
}

@@ -322,17 +303,21 @@ async check(options) {

$(selector) {
return this._context._$(selector, this);
// TODO: this should be ownerFrame() instead.
return selectors_1.selectors._query(this._context.frame, selector, this);
}
$$(selector) {
return this._context._$$(selector, this);
// TODO: this should be ownerFrame() instead.
return selectors_1.selectors._queryAll(this._context.frame, selector, this);
}
async $eval(selector, pageFunction, arg) {
const elementHandle = await this._context._$(selector, this);
if (!elementHandle)
// TODO: this should be ownerFrame() instead.
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);
// TODO: this should be ownerFrame() instead.
const arrayHandle = await selectors_1.selectors._queryArray(this._context.frame, selector, this);
const result = await arrayHandle.evaluate(pageFunction, arg);

@@ -342,9 +327,12 @@ arrayHandle.dispose();

}
async _waitForDisplayedAtStablePosition(options = {}) {
async _waitForDisplayedAtStablePosition(deadline) {
debugInput('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));
await helper_1.helper.waitWithDeadline(stablePromise, 'element to be displayed and not moving', deadline);
debugInput('...done');
}
async _waitForHitTargetAt(point, options = {}) {
async _waitForHitTargetAt(point, deadline) {
debugInput(`waiting for element to receive pointer events at (${point.x},${point.y}) ...`);
const frame = await this.ownerFrame();

@@ -361,4 +349,5 @@ 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 });
await helper_1.helper.waitWithDeadline(hitTargetPromise, 'element to receive pointer events', deadline);
debugInput('...done');
}

@@ -365,0 +354,0 @@ }

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

Dialog: 'dialog',
Download: 'download',
FileChooser: 'filechooser',

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

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

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

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

const ffPage_1 = require("./ffPage");
class FFBrowser extends platform.EventEmitter {
class FFBrowser extends browser_1.BrowserBase {
constructor(connection) {

@@ -48,2 +47,4 @@ super();

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)),
];

@@ -86,10 +87,13 @@ this._firstPagePromise = new Promise(f => this._firstPageCallback = f);

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);

@@ -103,5 +107,2 @@ await context._initialize();

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

@@ -131,12 +132,17 @@ const ffPage = this._ffPages.get(payload.targetId);

}
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;
}
}

@@ -162,2 +168,4 @@ exports.FFBrowser = FFBrowser;

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

@@ -180,2 +188,6 @@ _ffPages() {

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;
});

@@ -241,2 +253,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) {

@@ -278,3 +293,3 @@ this._options.httpCredentials = httpCredentials || undefined;

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

@@ -281,0 +296,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,6 +29,5 @@ Disconnected: Symbol('Disconnected'),

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

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

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

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

_rawSend(message) {
const data = JSON.stringify(message);
this._debugProtocol('SEND ► ' + (rewriteInjectedScriptEvaluationLog(message) || data));
this._transport.send(data);
if (transport_1.debugProtocol.enabled)
transport_1.debugProtocol('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 (transport_1.debugProtocol.enabled)
transport_1.debugProtocol('◀ 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,3 +114,3 @@ }

};
class FFSession extends platform.EventEmitter {
class FFSession extends events_1.EventEmitter {
constructor(connection, targetType, sessionId, rawSend) {

@@ -145,3 +144,3 @@ super();

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

@@ -165,6 +164,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);

@@ -181,3 +180,4 @@ }

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

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

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

@@ -62,3 +61,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');
};

@@ -146,3 +145,3 @@ const headers = {};

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 +155,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', {

@@ -159,0 +158,0 @@ requestId: this._id,

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

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

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

const ffNetworkManager_1 = require("./ffNetworkManager");
const selectors_1 = require("../selectors");
const UTILITY_WORLD_NAME = '__playwright_utility_world__';

@@ -71,20 +71,9 @@ class FFPage {

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._initialized = true;
});
// 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);
}

@@ -253,6 +242,7 @@ _initializedPage() {

}
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
});

@@ -319,3 +309,3 @@ }

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

@@ -403,4 +393,3 @@ async resetViewport() {

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) => {

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

@@ -19,7 +19,9 @@ "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 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");
class FrameManager {

@@ -72,6 +74,6 @@ constructor(page) {

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

@@ -84,3 +86,3 @@ try {

// Resolve in the next task, after all waitForNavigations.
await new Promise(platform.makeWaitForNextTask());
await new Promise(helper_1.helper.makeWaitForNextTask());
return result;

@@ -109,4 +111,3 @@ }

const frame = this._frames.get(frameId);
for (const child of frame.childFrames())
this._removeFramesRecursively(child);
this.removeChildFramesRecursively(frame);
frame._url = url;

@@ -212,5 +213,8 @@ frame._name = name;

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

@@ -363,11 +367,3 @@ this._frames.delete(frame._id);

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);
}

@@ -377,7 +373,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()) {

@@ -397,13 +394,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);

@@ -414,4 +409,3 @@ arrayHandle.dispose();

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

@@ -472,3 +466,3 @@ async content() {

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, '');

@@ -513,3 +507,3 @@ return (await context.evaluateHandleInternal(addScriptContent, { content: contents, type })).asElement();

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, '') + '*/';

@@ -575,19 +569,19 @@ return (await context.evaluateHandleInternal(addStyleContent, contents)).asElement();

}
async click(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.click(options);
async click(selector, options = {}) {
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.click(helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
}
async dblclick(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.dblclick(options);
async dblclick(selector, options = {}) {
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.dblclick(helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
}
async fill(selector, value, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.fill(value, options);
async fill(selector, value, options = {}) {
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.fill(value, helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
}
async focus(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
const { handle } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.focus();

@@ -597,9 +591,9 @@ handle.dispose();

async hover(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.hover(options);
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.hover(helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
}
async selectOption(selector, values, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
const result = await handle.selectOption(values, options);
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
const result = await handle.selectOption(values, helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();

@@ -609,19 +603,19 @@ return result;

async type(selector, text, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.type(text, options);
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.type(text, helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
}
async press(selector, key, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.press(key, options);
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.press(key, helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
}
async check(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.check(options);
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.check(helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();
}
async uncheck(selector, options) {
const handle = await this._waitForSelectorInUtilityContext(selector, options);
await handle.uncheck(options);
const { handle, deadline } = await this._waitForSelectorInUtilityContext(selector, options);
await handle.uncheck(helper_1.helper.optionsWithUpdatedTimeout(options, deadline));
handle.dispose();

@@ -639,9 +633,11 @@ }

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();
const { waitFor = 'attached' } = options || {};
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)}"`);
return { handle: result.asElement(), deadline };
}
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))

@@ -656,7 +652,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);
}

@@ -678,5 +672,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);

@@ -718,21 +712,4 @@ if (data.context)

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 = () => { };

@@ -750,6 +727,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));
}

@@ -822,3 +797,3 @@ terminate(error) {

class PendingNavigationBarrier {
constructor(options) {
constructor(options, deadline) {
this._frameIds = new Map();

@@ -828,2 +803,3 @@ this._protectCount = 0;

this._options = options;
this._deadline = deadline;
this._promise = new Promise(f => this._promiseCallback = f);

@@ -838,3 +814,5 @@ this.retain();

this.retain();
await frame.waitForNavigation(this._options).catch(e => { });
const timeout = helper_1.helper.timeUntilDeadline(this._deadline);
const options = { ...this._options, timeout };
await frame.waitForNavigation(options).catch(e => { });
this.release();

@@ -866,3 +844,3 @@ }

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

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

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

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

@@ -42,3 +45,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 +58,3 @@ contents += '//# sourceURL=' + fun.path.replace(/\n/g, '');

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

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

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

@@ -140,7 +142,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() {

@@ -162,8 +162,9 @@ Helper.removeEventListeners([listener]);

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 {

@@ -269,2 +270,46 @@ 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 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) };
}
}

@@ -271,0 +316,0 @@ function assert(value, message) {

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

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

@@ -111,0 +111,0 @@ const restore = Array.from(this._pressedModifiers);

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

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

@@ -108,3 +108,3 @@ constructor(delegate) {

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

@@ -111,0 +111,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 = []) {

@@ -169,4 +171,4 @@ if (!Array.isArray(urls))

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)
};

@@ -173,0 +175,0 @@ }

@@ -30,4 +30,5 @@ "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");
class Page extends extendedEventEmitter_1.ExtendedEventEmitter {
constructor(delegate, browserContext) {

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

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

@@ -195,10 +202,4 @@ helper_1.assert(!this._closed, 'Page closed twice');

}
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) => {

@@ -208,6 +209,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) => {

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

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

@@ -245,3 +246,3 @@ async goBack(options) {

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

@@ -379,3 +380,3 @@ async setViewportSize(viewportSize) {

exports.Page = Page;
class Worker extends platform.EventEmitter {
class Worker extends events_2.EventEmitter {
constructor(url) {

@@ -382,0 +383,0 @@ super();

@@ -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,18 @@ "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', 'xpath', 'text', 'id', 'data-testid', 'data-test-id', 'data-test']);
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')
throw new Error(`"${name}" is a predefined selector engine`);

@@ -40,13 +39,155 @@ 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) {
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,2 +88,3 @@ 'mac10.15': ['firefox', 'Nightly.app', 'Contents', 'MacOS', 'firefox'],

'linux': ['pw_run.sh'],
'mac10.13': undefined,
'mac10.14': ['pw_run.sh'],

@@ -114,10 +119,9 @@ 'mac10.15': ['pw_run.sh'],

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,3 @@ const pipeTransport_1 = require("./pipeTransport");

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

@@ -42,22 +43,24 @@ 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 } = await this._launchServer(options, 'local');
const browser = await crBrowser_1.CRBrowser.connect(transport, false, 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 } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await crBrowser_1.CRBrowser.connect(transport, true, 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 = [], dumpio = false, 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.');
let temporaryUserDataDir = null;

@@ -70,5 +73,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 +82,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,

@@ -89,13 +91,12 @@ args: chromeArguments,

dumpio,
pipe: launchType !== 'server',
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 +108,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]);
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, port) : null);
return { browserServer, transport, downloadsPath };
}
async connect(options) {
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
return await transport_1.WebSocketTransport.connect(options.wsEndpoint, transport => {
return crBrowser_1.CRBrowser.connect(transport, false, options.slowMo);
});
}
_defaultArgs(options = {}, launchType, userDataDir, port) {
_defaultArgs(options = {}, launchType, userDataDir) {
const { devtools = false, headless = !devtools, args = [], } = options;

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

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)

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

exports.Chromium = Chromium;
const mkdtempAsync = platform.promisify(fs.mkdtemp);
function wrapTransportWithWebSocket(transport, 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', error => helper_1.debugError(error));
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-');

@@ -162,0 +260,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,6 @@ 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 mkdtempAsync = util.promisify(fs.mkdtemp);
class Firefox {

@@ -42,31 +44,30 @@ 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 } = await this._launchServer(options, 'local');
const browser = await transport_1.WebSocketTransport.connect(browserServer.wsEndpoint(), transport => {
return ffBrowser_1.FFBrowser.connect(transport, 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 } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await transport_1.WebSocketTransport.connect(browserServer.wsEndpoint(), transport => {
return ffBrowser_1.FFBrowser.connect(transport, 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 = [], dumpio = false, 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 firefoxArguments = [];

@@ -79,5 +80,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 +89,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,

@@ -105,10 +105,7 @@ args: firefoxArguments,

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,8 +119,12 @@ 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, port))) : new browserServer_1.WebSocketWrapper(innerEndpoint, []);
browserWSEndpoint = webSocketWrapper.wsEndpoint;
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, webSocketWrapper);
return { browserServer, downloadsPath };
}
async connect(options) {
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
return await transport_1.WebSocketTransport.connect(options.wsEndpoint, transport => {
return ffBrowser_1.FFBrowser.connect(transport, false, options.slowMo);

@@ -135,3 +136,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'));

@@ -161,2 +162,117 @@ if (userDataDirArg)

exports.Firefox = Firefox;
function wrapTransportWithWebSocket(transport, 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', error => helper_1.debugError(error));
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,7 @@ "use strict";

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

@@ -42,7 +40,7 @@ helper_1.helper.addEventListener(pipeRead, 'data', buffer => this._dispatch(buffer)),

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

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

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

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

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

@@ -70,0 +68,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,13 +20,19 @@ "use strict";

const childProcess = require("child_process");
const debug = require("debug");
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);
const removeFolderAsync = util.promisify(removeFolder);
const mkdtempAsync = util.promisify(fs.mkdtemp);
const DOWNLOADS_FOLDER = path.join(os.tmpdir(), 'playwright_downloads-');
let lastLaunchedId = 0;
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}]`);
const debugBrowser = debug(`pw:browser:proc:[${id}]`);
const debugBrowserOut = debug(`pw:browser:out:[${id}]`);
const debugBrowserErr = debug(`pw:browser:err:[${id}]`);
debugBrowser.color = '33';

@@ -65,2 +71,3 @@ debugBrowserOut.color = '178';

});
const downloadsPath = await mkdtempAsync(DOWNLOADS_FOLDER);
let processClosed = false;

@@ -74,10 +81,6 @@ const waitForProcessToClose = new Promise((fulfill, reject) => {

// 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);
});

@@ -108,3 +111,3 @@ });

debugBrowser(`<gracefully close start>`);
options.attemptToGracefullyClose().catch(() => killProcess());
await options.attemptToGracefullyClose().catch(() => killProcess());
await waitForProcessToClose;

@@ -136,3 +139,3 @@ debugBrowser(`<gracefully close end>`);

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

@@ -139,0 +142,0 @@ exports.launchProcess = launchProcess;

@@ -24,6 +24,7 @@ "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");

@@ -41,22 +42,24 @@ const browserServer_1 = require("./browserServer");

}
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 } = await this._launchServer(options, 'local');
const browser = await wkBrowser_1.WKBrowser.connect(transport, 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 } = await this._launchServer(options, 'persistent', userDataDir);
const browser = await wkBrowser_1.WKBrowser.connect(transport, 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 = [], dumpio = false, 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.');
let temporaryUserDataDir = null;

@@ -69,5 +72,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 +81,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,

@@ -92,9 +93,7 @@ args: webkitArguments,

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,10 +106,11 @@ 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]);
browserServer = new browserServer_1.BrowserServer(launchedProcess, gracefullyClose, launchType === 'server' ? wrapTransportWithWebSocket(transport, port || 0) : null);
return { browserServer, transport, downloadsPath };
}
async connect(options) {
return await platform.connectToWebsocket(options.wsEndpoint, transport => {
return await transport_1.WebSocketTransport.connect(options.wsEndpoint, transport => {
return wkBrowser_1.WKBrowser.connect(transport, options.slowMo);

@@ -122,3 +122,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 +141,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) {
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 +154,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 +196,3 @@ const socket = pageProxyIds.get(pageProxyId);

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

@@ -224,3 +207,3 @@ }

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

@@ -232,3 +215,3 @@ }

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

@@ -239,6 +222,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 +246,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 +253,3 @@ pendingBrowserContextCreations.add(seqNum);

});
socket.on('error', error => helper_1.debugError(error));
socket.on('close', socket.__closeListener = () => {

@@ -269,7 +262,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 +274,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,5 @@ "use strict";

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

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

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

@@ -76,2 +79,87 @@ 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.debugProtocol = debug('pw:protocol');
exports.debugProtocol.color = '34';
//# sourceMappingURL=transport.js.map

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

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

@@ -31,3 +30,3 @@ 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 {
class WKBrowser extends browser_1.BrowserBase {
constructor(transport, attachToDefaultContext) {

@@ -48,2 +47,4 @@ super();

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)),

@@ -78,5 +79,2 @@ ];

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

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

}
_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) {

@@ -158,12 +165,6 @@ const { pageProxyInfo } = event;

}
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;
}
}

@@ -179,14 +180,23 @@ exports.WKBrowser = WKBrowser;

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);
}

@@ -293,3 +303,3 @@ _wkPages() {

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

@@ -296,0 +306,0 @@ }

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

Object.defineProperty(exports, "__esModule", { value: true });
const debug = require("debug");
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

@@ -32,3 +34,2 @@ // should ignore.

this._closed = false;
this._debugProtocol = platform.debug('pw:protocol');
this._transport = transport;

@@ -41,3 +42,2 @@ this._transport.onmessage = this._dispatchMessage.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 (transport_1.debugProtocol.enabled)
transport_1.debugProtocol('SEND ► ' + rewriteInjectedScriptEvaluationLog(message));
this._transport.send(message);
}
_dispatchMessage(message) {
this._debugProtocol('◀ RECV ' + message);
const object = JSON.parse(message);
if (object.id === exports.kBrowserCloseMessageId)
if (transport_1.debugProtocol.enabled)
transport_1.debugProtocol('◀ 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) {

@@ -102,3 +102,3 @@ super();

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

@@ -119,3 +119,3 @@ return new Promise((resolve, reject) => {

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

@@ -125,3 +125,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

@@ -140,6 +140,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);

@@ -162,3 +162,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,2 @@ "use strict";

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

@@ -70,3 +69,3 @@ 'aborted': 'Cancellation',

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', {

@@ -102,3 +101,3 @@ requestId: this._requestId,

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');
};

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

@@ -30,5 +30,7 @@ "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 UTILITY_WORLD_NAME = '__playwright_utility_world__';

@@ -55,3 +57,3 @@ const BINDING_CALL_MESSAGE = '__playwright_binding_call__';

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 = [

@@ -138,2 +140,3 @@ helper_1.helper.addEventListener(this._pageProxySession, 'Target.targetCreated', this._onTargetCreated.bind(this)),

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

@@ -435,4 +438,5 @@ if (contextOptions.offline)

}
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));
}

@@ -549,5 +553,5 @@ async setViewportSize(viewportSize) {

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;

@@ -644,4 +648,3 @@ }

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) => {

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

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

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

"playwright": {
"chromium_revision": "751710",
"firefox_revision": "1051",
"webkit_revision": "1182"
"chromium_revision": "754895",
"firefox_revision": "1075",
"webkit_revision": "1188"
},

@@ -39,3 +39,2 @@ "scripts": {

"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",

@@ -50,5 +49,6 @@ "generate-types": "node utils/generate_types/"

"debug": "^4.1.0",
"extract-zip": "^1.6.6",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^3.0.0",
"jpeg-js": "^0.3.6",
"mime": "^2.4.4",
"pngjs": "^3.4.0",

@@ -63,3 +63,4 @@ "progress": "^2.0.3",

"@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",

@@ -83,3 +84,3 @@ "@types/proxy-from-env": "^1.0.0",

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

@@ -86,0 +87,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