playwright-chromium
Version:
A high-level API to automate Chromium
197 lines • 8.48 kB
JavaScript
;
/**
* Copyright (c) Microsoft Corporation.
*
* Licensed under the Apache License, Version 2.0 (the 'License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.BrowserServerImpl = exports.BrowserServerLauncherImpl = void 0;
const ws = __importStar(require("ws"));
const fs_1 = __importDefault(require("fs"));
const ws_1 = require("ws");
const dispatcher_1 = require("./dispatchers/dispatcher");
const browserDispatcher_1 = require("./dispatchers/browserDispatcher");
const clientHelper_1 = require("./client/clientHelper");
const utils_1 = require("./utils/utils");
const selectorsDispatcher_1 = require("./dispatchers/selectorsDispatcher");
const selectors_1 = require("./server/selectors");
const browserContext_1 = require("./server/browserContext");
const streamDispatcher_1 = require("./dispatchers/streamDispatcher");
const instrumentation_1 = require("./server/instrumentation");
class BrowserServerLauncherImpl {
constructor(browserType) {
this._browserType = browserType;
}
async launchServer(options = {}) {
const browser = await this._browserType.launch(instrumentation_1.internalCallMetadata(), {
...options,
ignoreDefaultArgs: Array.isArray(options.ignoreDefaultArgs) ? options.ignoreDefaultArgs : undefined,
ignoreAllDefaultArgs: !!options.ignoreDefaultArgs && !Array.isArray(options.ignoreDefaultArgs),
env: options.env ? clientHelper_1.envObjectToArray(options.env) : undefined,
}, toProtocolLogger(options.logger));
return BrowserServerImpl.start(browser, options.port);
}
}
exports.BrowserServerLauncherImpl = BrowserServerLauncherImpl;
class BrowserServerImpl extends ws_1.EventEmitter {
constructor(browser, port) {
super();
this._browser = browser;
this._wsEndpoint = '';
this._process = browser.options.browserProcess.process;
let readyCallback = () => { };
this._ready = new Promise(f => readyCallback = f);
const token = utils_1.createGuid();
this._server = new ws.Server({ port, path: '/' + token }, () => {
const address = this._server.address();
this._wsEndpoint = typeof address === 'string' ? `${address}/${token}` : `ws://127.0.0.1:${address.port}/${token}`;
readyCallback();
});
this._server.on('connection', (socket, req) => {
this._clientAttached(socket);
});
browser.options.browserProcess.onclose = (exitCode, signal) => {
this._server.close();
this.emit('close', exitCode, signal);
};
}
static async start(browser, port = 0) {
const server = new BrowserServerImpl(browser, port);
await server._ready;
return server;
}
process() {
return this._process;
}
wsEndpoint() {
return this._wsEndpoint;
}
async close() {
await this._browser.options.browserProcess.close();
}
async kill() {
await this._browser.options.browserProcess.kill();
}
_clientAttached(socket) {
const connection = new dispatcher_1.DispatcherConnection();
connection.onmessage = message => {
if (socket.readyState !== ws.CLOSING)
socket.send(JSON.stringify(message));
};
socket.on('message', (message) => {
connection.dispatch(JSON.parse(Buffer.from(message).toString()));
});
socket.on('error', () => { });
const selectors = new selectors_1.Selectors();
const scope = connection.rootDispatcher();
const remoteBrowser = new RemoteBrowserDispatcher(scope, this._browser, selectors);
socket.on('close', () => {
// Avoid sending any more messages over closed socket.
connection.onmessage = () => { };
// Cleanup contexts upon disconnect.
remoteBrowser.connectedBrowser.close().catch(e => { });
});
}
}
exports.BrowserServerImpl = BrowserServerImpl;
class RemoteBrowserDispatcher extends dispatcher_1.Dispatcher {
constructor(scope, browser, selectors) {
const connectedBrowser = new ConnectedBrowser(scope, browser, selectors);
super(scope, browser, 'RemoteBrowser', {
selectors: new selectorsDispatcher_1.SelectorsDispatcher(scope, selectors),
browser: connectedBrowser,
}, false, 'remoteBrowser');
this.connectedBrowser = connectedBrowser;
connectedBrowser._remoteBrowser = this;
}
}
class ConnectedBrowser extends browserDispatcher_1.BrowserDispatcher {
constructor(scope, browser, selectors) {
super(scope, browser);
this._contexts = [];
this._closed = false;
this._selectors = selectors;
}
async newContext(params, metadata) {
if (params.recordVideo) {
// TODO: we should create a separate temp directory or accept a launchServer parameter.
params.recordVideo.dir = this._object.options.downloadsPath;
}
const result = await super.newContext(params, metadata);
const dispatcher = result.context;
dispatcher._object.on(browserContext_1.BrowserContext.Events.VideoStarted, (video) => this._sendVideo(dispatcher, video));
dispatcher._object._setSelectors(this._selectors);
this._contexts.push(dispatcher);
return result;
}
async close() {
// Only close our own contexts.
await Promise.all(this._contexts.map(context => context.close({}, instrumentation_1.internalCallMetadata())));
this._didClose();
}
_didClose() {
if (!this._closed) {
// We come here multiple times:
// - from ConnectedBrowser.close();
// - from underlying Browser.on('close').
this._closed = true;
super._didClose();
}
}
_sendVideo(contextDispatcher, video) {
video._waitForCallbackOnFinish(async () => {
const readable = fs_1.default.createReadStream(video._path);
await new Promise(f => readable.on('readable', f));
const stream = new streamDispatcher_1.StreamDispatcher(this._remoteBrowser._scope, readable);
this._remoteBrowser._dispatchEvent('video', {
stream,
context: contextDispatcher,
relativePath: video._relativePath
});
await new Promise(resolve => {
readable.on('close', resolve);
readable.on('end', resolve);
readable.on('error', resolve);
});
});
}
}
function toProtocolLogger(logger) {
return logger ? (direction, message) => {
if (logger.isEnabled('protocol', 'verbose'))
logger.log('protocol', 'verbose', (direction === 'send' ? 'SEND ► ' : '◀ RECV ') + JSON.stringify(message), [], {});
} : undefined;
}
//# sourceMappingURL=browserServerImpl.js.map