326 lines
56 KiB
JavaScript
326 lines
56 KiB
JavaScript
|
"use strict";
|
||
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||
|
};
|
||
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||
|
const debug_1 = __importDefault(require("debug"));
|
||
|
const testcafe_browser_tools_1 = __importDefault(require("testcafe-browser-tools"));
|
||
|
const os_family_1 = __importDefault(require("os-family"));
|
||
|
const path_1 = require("path");
|
||
|
const make_dir_1 = __importDefault(require("make-dir"));
|
||
|
const connection_1 = __importDefault(require("../connection"));
|
||
|
const delay_1 = __importDefault(require("../../utils/delay"));
|
||
|
const client_functions_1 = require("./utils/client-functions");
|
||
|
const warning_message_1 = __importDefault(require("../../notifications/warning-message"));
|
||
|
const get_os_info_1 = __importDefault(require("get-os-info"));
|
||
|
const DEBUG_LOGGER = (0, debug_1.default)('testcafe:browser:provider');
|
||
|
const BROWSER_OPENING_DELAY = 2000;
|
||
|
const RESIZE_DIFF_SIZE = {
|
||
|
width: 100,
|
||
|
height: 100,
|
||
|
};
|
||
|
function sumSizes(sizeA, sizeB) {
|
||
|
return {
|
||
|
width: sizeA.width + sizeB.width,
|
||
|
height: sizeA.height + sizeB.height,
|
||
|
};
|
||
|
}
|
||
|
function subtractSizes(sizeA, sizeB) {
|
||
|
return {
|
||
|
width: sizeA.width - sizeB.width,
|
||
|
height: sizeA.height - sizeB.height,
|
||
|
};
|
||
|
}
|
||
|
class BrowserProvider {
|
||
|
constructor(plugin) {
|
||
|
this.plugin = plugin;
|
||
|
this.initPromise = Promise.resolve(false);
|
||
|
this.isMultiBrowser = this.plugin.isMultiBrowser;
|
||
|
// HACK: The browser window has different border sizes in normal and maximized modes. So, we need to be sure that the window is
|
||
|
// not maximized before resizing it in order to keep the mechanism of correcting the client area size working. When browser is started,
|
||
|
// we are resizing it for the first time to switch the window to normal mode, and for the second time - to restore the client area size.
|
||
|
this.localBrowsersInfo = {};
|
||
|
}
|
||
|
_ensureLocalBrowserInfo(browserId) {
|
||
|
if (this.localBrowsersInfo[browserId])
|
||
|
return;
|
||
|
this.localBrowsersInfo[browserId] = {
|
||
|
windowDescriptor: null,
|
||
|
maxScreenSize: null,
|
||
|
resizeCorrections: null,
|
||
|
};
|
||
|
}
|
||
|
async _findWindow(browserId) {
|
||
|
const pageTitle = this._getPageTitle(browserId);
|
||
|
return testcafe_browser_tools_1.default.findWindow(pageTitle);
|
||
|
}
|
||
|
_getPageTitle(browserId) {
|
||
|
if (this.plugin.getPageTitle)
|
||
|
return this.plugin.getPageTitle(browserId);
|
||
|
return browserId;
|
||
|
}
|
||
|
_getWindowDescriptor(browserId) {
|
||
|
if (this.plugin.getWindowDescriptor)
|
||
|
return this.plugin.getWindowDescriptor(browserId);
|
||
|
return this.localBrowsersInfo[browserId] && this.localBrowsersInfo[browserId].windowDescriptor;
|
||
|
}
|
||
|
_setWindowDescriptor(browserId, windowDescriptor) {
|
||
|
if (this.plugin.setWindowDescriptor) {
|
||
|
this.plugin.setWindowDescriptor(browserId, windowDescriptor);
|
||
|
return;
|
||
|
}
|
||
|
this.localBrowsersInfo[browserId].windowDescriptor = windowDescriptor;
|
||
|
}
|
||
|
_getMaxScreenSize(browserId) {
|
||
|
return this.localBrowsersInfo[browserId] && this.localBrowsersInfo[browserId].maxScreenSize;
|
||
|
}
|
||
|
_getResizeCorrections(browserId) {
|
||
|
return this.localBrowsersInfo[browserId] && this.localBrowsersInfo[browserId].resizeCorrections;
|
||
|
}
|
||
|
_isBrowserIdle(browserId) {
|
||
|
const connection = connection_1.default.getById(browserId);
|
||
|
return connection.idle;
|
||
|
}
|
||
|
async _calculateResizeCorrections(browserId) {
|
||
|
if (!this._isBrowserIdle(browserId))
|
||
|
return;
|
||
|
const title = await this.plugin.runInitScript(browserId, client_functions_1.GET_TITLE_SCRIPT);
|
||
|
if (!await testcafe_browser_tools_1.default.isMaximized(title))
|
||
|
return;
|
||
|
const currentSize = await this.plugin.runInitScript(browserId, client_functions_1.GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
|
||
|
const etalonSize = subtractSizes(currentSize, RESIZE_DIFF_SIZE);
|
||
|
await testcafe_browser_tools_1.default.resize(title, currentSize.width, currentSize.height, etalonSize.width, etalonSize.height);
|
||
|
let resizedSize = await this.plugin.runInitScript(browserId, client_functions_1.GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
|
||
|
let correctionSize = subtractSizes(resizedSize, etalonSize);
|
||
|
await testcafe_browser_tools_1.default.resize(title, resizedSize.width, resizedSize.height, etalonSize.width, etalonSize.height);
|
||
|
resizedSize = await this.plugin.runInitScript(browserId, client_functions_1.GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
|
||
|
correctionSize = sumSizes(correctionSize, subtractSizes(resizedSize, etalonSize));
|
||
|
if (this.localBrowsersInfo[browserId])
|
||
|
this.localBrowsersInfo[browserId].resizeCorrections = correctionSize;
|
||
|
await testcafe_browser_tools_1.default.maximize(title);
|
||
|
}
|
||
|
async _calculateMacSizeLimits(browserId) {
|
||
|
if (!this._isBrowserIdle(browserId))
|
||
|
return;
|
||
|
const sizeInfo = await this.plugin.runInitScript(browserId, client_functions_1.GET_WINDOW_DIMENSIONS_INFO_SCRIPT);
|
||
|
if (this.localBrowsersInfo[browserId]) {
|
||
|
this.localBrowsersInfo[browserId].maxScreenSize = {
|
||
|
width: sizeInfo.availableWidth - (sizeInfo.outerWidth - sizeInfo.width),
|
||
|
height: sizeInfo.availableHeight - (sizeInfo.outerHeight - sizeInfo.height),
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
async _ensureBrowserWindowDescriptor(browserId) {
|
||
|
if (this._getWindowDescriptor(browserId))
|
||
|
return;
|
||
|
await this._ensureLocalBrowserInfo(browserId);
|
||
|
// NOTE: delay to ensure the window finished the opening
|
||
|
await this.plugin.waitForConnectionReady(browserId);
|
||
|
await (0, delay_1.default)(BROWSER_OPENING_DELAY);
|
||
|
if (this.localBrowsersInfo[browserId]) {
|
||
|
const connection = connection_1.default.getById(browserId);
|
||
|
let windowDescriptor = null;
|
||
|
try {
|
||
|
windowDescriptor = await this._findWindow(browserId);
|
||
|
}
|
||
|
catch (err) {
|
||
|
// NOTE: We can suppress the error here since we can just disable window manipulation functions
|
||
|
// when we cannot find a local window descriptor
|
||
|
DEBUG_LOGGER(err);
|
||
|
connection.addWarning(warning_message_1.default.cannotFindWindowDescriptorError, connection.browserInfo.alias, err.message);
|
||
|
}
|
||
|
this._setWindowDescriptor(browserId, windowDescriptor);
|
||
|
}
|
||
|
}
|
||
|
async _ensureBrowserWindowParameters(browserId) {
|
||
|
await this._ensureBrowserWindowDescriptor(browserId);
|
||
|
if (os_family_1.default.win && !this._getResizeCorrections(browserId))
|
||
|
await this._calculateResizeCorrections(browserId);
|
||
|
else if (os_family_1.default.mac && !this._getMaxScreenSize(browserId))
|
||
|
await this._calculateMacSizeLimits(browserId);
|
||
|
}
|
||
|
async _closeLocalBrowser(browserId) {
|
||
|
if (this.plugin.needCleanUpBrowserInfo)
|
||
|
this.plugin.cleanUpBrowserInfo(browserId);
|
||
|
const windowDescriptor = this._getWindowDescriptor(browserId);
|
||
|
await testcafe_browser_tools_1.default.close(windowDescriptor);
|
||
|
}
|
||
|
async _resizeLocalBrowserWindow(browserId, width, height, currentWidth, currentHeight) {
|
||
|
await this._ensureBrowserWindowDescriptor(browserId);
|
||
|
const resizeCorrections = this._getResizeCorrections(browserId);
|
||
|
if (resizeCorrections && await testcafe_browser_tools_1.default.isMaximized(this._getWindowDescriptor(browserId))) {
|
||
|
width -= resizeCorrections.width;
|
||
|
height -= resizeCorrections.height;
|
||
|
}
|
||
|
await testcafe_browser_tools_1.default.resize(this._getWindowDescriptor(browserId), currentWidth, currentHeight, width, height);
|
||
|
}
|
||
|
async _takeLocalBrowserScreenshot(browserId, screenshotPath) {
|
||
|
await testcafe_browser_tools_1.default.screenshot(this._getWindowDescriptor(browserId), screenshotPath);
|
||
|
}
|
||
|
async _canResizeLocalBrowserWindowToDimensions(browserId, width, height) {
|
||
|
if (!os_family_1.default.mac)
|
||
|
return true;
|
||
|
const maxScreenSize = this._getMaxScreenSize(browserId);
|
||
|
return width <= maxScreenSize.width && height <= maxScreenSize.height;
|
||
|
}
|
||
|
async _maximizeLocalBrowserWindow(browserId) {
|
||
|
await this._ensureBrowserWindowDescriptor(browserId);
|
||
|
await testcafe_browser_tools_1.default.maximize(this._getWindowDescriptor(browserId));
|
||
|
}
|
||
|
async _ensureRetryTestPagesWarning(browserId) {
|
||
|
const connection = connection_1.default.getById(browserId);
|
||
|
if (connection === null || connection === void 0 ? void 0 : connection.retryTestPages) {
|
||
|
const isServiceWorkerEnabled = await this.plugin.runInitScript(browserId, client_functions_1.GET_IS_SERVICE_WORKER_ENABLED);
|
||
|
if (!isServiceWorkerEnabled)
|
||
|
connection.addWarning(warning_message_1.default.retryTestPagesIsNotSupported, connection.browserInfo.alias, connection.browserInfo.alias);
|
||
|
}
|
||
|
}
|
||
|
async canUseDefaultWindowActions(browserId) {
|
||
|
const isLocalBrowser = await this.plugin.isLocalBrowser(browserId);
|
||
|
const isHeadlessBrowser = await this.plugin.isHeadlessBrowser(browserId);
|
||
|
return isLocalBrowser && !isHeadlessBrowser;
|
||
|
}
|
||
|
async init() {
|
||
|
const initialized = await this.initPromise;
|
||
|
if (initialized)
|
||
|
return;
|
||
|
this.initPromise = this.plugin
|
||
|
.init()
|
||
|
.then(() => true);
|
||
|
try {
|
||
|
await this.initPromise;
|
||
|
}
|
||
|
catch (error) {
|
||
|
this.initPromise = Promise.resolve(false);
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
async dispose() {
|
||
|
const initialized = await this.initPromise;
|
||
|
if (!initialized)
|
||
|
return;
|
||
|
this.initPromise = this.plugin
|
||
|
.dispose()
|
||
|
.then(() => false);
|
||
|
try {
|
||
|
await this.initPromise;
|
||
|
}
|
||
|
catch (error) {
|
||
|
this.initPromise = Promise.resolve(false);
|
||
|
throw error;
|
||
|
}
|
||
|
}
|
||
|
async isLocalBrowser(browserId, browserName) {
|
||
|
return await this.plugin.isLocalBrowser(browserId, browserName);
|
||
|
}
|
||
|
isHeadlessBrowser(browserId, browserName) {
|
||
|
return this.plugin.isHeadlessBrowser(browserId, browserName);
|
||
|
}
|
||
|
async getOSInfo(browserId) {
|
||
|
if (await this.isLocalBrowser(browserId))
|
||
|
return await (0, get_os_info_1.default)();
|
||
|
return await this.plugin.getOSInfo(browserId);
|
||
|
}
|
||
|
async openBrowser(browserId, pageUrl, browserOption, additionalOptions = { disableMultipleWindows: false }) {
|
||
|
await this.plugin.openBrowser(browserId, pageUrl, browserOption, additionalOptions);
|
||
|
await this._ensureRetryTestPagesWarning(browserId);
|
||
|
if (await this.canUseDefaultWindowActions(browserId))
|
||
|
await this._ensureBrowserWindowParameters(browserId);
|
||
|
}
|
||
|
async closeBrowser(browserId, data) {
|
||
|
const canUseDefaultWindowActions = await this.canUseDefaultWindowActions(browserId);
|
||
|
const customActionsInfo = await this.hasCustomActionForBrowser(browserId);
|
||
|
const hasCustomCloseBrowser = customActionsInfo.hasCloseBrowser;
|
||
|
const usePluginsCloseBrowser = hasCustomCloseBrowser || !canUseDefaultWindowActions;
|
||
|
if (usePluginsCloseBrowser)
|
||
|
await this.plugin.closeBrowser(browserId, data);
|
||
|
else
|
||
|
await this._closeLocalBrowser(browserId);
|
||
|
if (canUseDefaultWindowActions)
|
||
|
delete this.localBrowsersInfo[browserId];
|
||
|
}
|
||
|
async getBrowserList() {
|
||
|
return await this.plugin.getBrowserList();
|
||
|
}
|
||
|
async isValidBrowserName(browserName) {
|
||
|
return await this.plugin.isValidBrowserName(browserName);
|
||
|
}
|
||
|
async resizeWindow(browserId, width, height, currentWidth, currentHeight) {
|
||
|
const canUseDefaultWindowActions = await this.canUseDefaultWindowActions(browserId);
|
||
|
const customActionsInfo = await this.hasCustomActionForBrowser(browserId);
|
||
|
const hasCustomResizeWindow = customActionsInfo.hasResizeWindow;
|
||
|
if (canUseDefaultWindowActions && !hasCustomResizeWindow) {
|
||
|
await this._resizeLocalBrowserWindow(browserId, width, height, currentWidth, currentHeight);
|
||
|
return;
|
||
|
}
|
||
|
await this.plugin.resizeWindow(browserId, width, height, currentWidth, currentHeight);
|
||
|
}
|
||
|
async canResizeWindowToDimensions(browserId, width, height) {
|
||
|
const canUseDefaultWindowActions = await this.canUseDefaultWindowActions(browserId);
|
||
|
const customActionsInfo = await this.hasCustomActionForBrowser(browserId);
|
||
|
const hasCustomCanResizeToDimensions = customActionsInfo.hasCanResizeWindowToDimensions;
|
||
|
if (canUseDefaultWindowActions && !hasCustomCanResizeToDimensions)
|
||
|
return await this._canResizeLocalBrowserWindowToDimensions(browserId, width, height);
|
||
|
return await this.plugin.canResizeWindowToDimensions(browserId, width, height);
|
||
|
}
|
||
|
async maximizeWindow(browserId) {
|
||
|
const canUseDefaultWindowActions = await this.canUseDefaultWindowActions(browserId);
|
||
|
const customActionsInfo = await this.hasCustomActionForBrowser(browserId);
|
||
|
const hasCustomMaximizeWindow = customActionsInfo.hasMaximizeWindow;
|
||
|
if (canUseDefaultWindowActions && !hasCustomMaximizeWindow)
|
||
|
return await this._maximizeLocalBrowserWindow(browserId);
|
||
|
return await this.plugin.maximizeWindow(browserId);
|
||
|
}
|
||
|
async takeScreenshot(browserId, screenshotPath, pageWidth, pageHeight, fullPage) {
|
||
|
const canUseDefaultWindowActions = await this.canUseDefaultWindowActions(browserId);
|
||
|
const customActionsInfo = await this.hasCustomActionForBrowser(browserId);
|
||
|
const hasCustomTakeScreenshot = customActionsInfo.hasTakeScreenshot;
|
||
|
const connection = connection_1.default.getById(browserId);
|
||
|
const takeLocalBrowsersScreenshot = canUseDefaultWindowActions && !hasCustomTakeScreenshot;
|
||
|
const isLocalFullPageMode = takeLocalBrowsersScreenshot && fullPage;
|
||
|
if (isLocalFullPageMode) {
|
||
|
connection.addWarning(warning_message_1.default.screenshotsFullPageNotSupported, connection.browserInfo.alias);
|
||
|
return;
|
||
|
}
|
||
|
await (0, make_dir_1.default)((0, path_1.dirname)(screenshotPath));
|
||
|
if (takeLocalBrowsersScreenshot)
|
||
|
await this._takeLocalBrowserScreenshot(browserId, screenshotPath);
|
||
|
else
|
||
|
await this.plugin.takeScreenshot(browserId, screenshotPath, pageWidth, pageHeight, fullPage);
|
||
|
}
|
||
|
async getVideoFrameData(browserId) {
|
||
|
return this.plugin.getVideoFrameData(browserId);
|
||
|
}
|
||
|
async startCapturingVideo(browserId) {
|
||
|
await this.plugin.startCapturingVideo(browserId);
|
||
|
}
|
||
|
async stopCapturingVideo(browserId) {
|
||
|
await this.plugin.stopCapturingVideo(browserId);
|
||
|
}
|
||
|
async hasCustomActionForBrowser(browserId) {
|
||
|
return this.plugin.hasCustomActionForBrowser(browserId);
|
||
|
}
|
||
|
async reportJobResult(browserId, status, data) {
|
||
|
await this.plugin.reportJobResult(browserId, status, data);
|
||
|
}
|
||
|
getActiveWindowId(browserId) {
|
||
|
if (!this.plugin.supportMultipleWindows)
|
||
|
return null;
|
||
|
return this.plugin.getActiveWindowId(browserId);
|
||
|
}
|
||
|
setActiveWindowId(browserId, val) {
|
||
|
this.plugin.setActiveWindowId(browserId, val);
|
||
|
}
|
||
|
async openFileProtocol(browserId, url) {
|
||
|
await this.plugin.openFileProtocol(browserId, url);
|
||
|
}
|
||
|
async closeBrowserChildWindow(browserId) {
|
||
|
await this.plugin.closeBrowserChildWindow(browserId);
|
||
|
}
|
||
|
async dispatchProxylessEvent(browserId, type, options) {
|
||
|
await this.plugin.dispatchProxylessEvent(browserId, type, options);
|
||
|
}
|
||
|
}
|
||
|
exports.default = BrowserProvider;
|
||
|
module.exports = exports.default;
|
||
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYnJvd3Nlci9wcm92aWRlci9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLGtEQUEwQjtBQUMxQixvRkFBa0Q7QUFDbEQsMERBQTJCO0FBQzNCLCtCQUErQjtBQUMvQix3REFBK0I7QUFDL0IsK0RBQXNFO0FBQ3RFLDhEQUFzQztBQUN0QywrREFJa0M7QUFDbEMsMEZBQWtFO0FBR2xFLDhEQUFxRDtBQUtyRCxNQUFNLFlBQVksR0FBRyxJQUFBLGVBQUssRUFBQywyQkFBMkIsQ0FBQyxDQUFDO0FBRXhELE1BQU0scUJBQXFCLEdBQUcsSUFBSSxDQUFDO0FBRW5DLE1BQU0sZ0JBQWdCLEdBQUc7SUFDckIsS0FBSyxFQUFHLEdBQUc7SUFDWCxNQUFNLEVBQUUsR0FBRztDQUNkLENBQUM7QUFhRixTQUFTLFFBQVEsQ0FBRSxLQUFXLEVBQUUsS0FBVztJQUN2QyxPQUFPO1FBQ0gsS0FBSyxFQUFHLEtBQUssQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUs7UUFDakMsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLE1BQU07S0FDdEMsQ0FBQztBQUNOLENBQUM7QUFFRCxTQUFTLGFBQWEsQ0FBRSxLQUFXLEVBQUUsS0FBVztJQUM1QyxPQUFPO1FBQ0gsS0FBSyxFQUFHLEtBQUssQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDLEtBQUs7UUFDakMsTUFBTSxFQUFFLEtBQUssQ0FBQyxNQUFNLEdBQUcsS0FBSyxDQUFDLE1BQU07S0FDdEMsQ0FBQztBQUNOLENBQUM7QUFFRCxNQUFxQixlQUFlO0lBTWhDLFlBQW9CLE1BQVc7UUFDM0IsSUFBSSxDQUFDLE1BQU0sR0FBVyxNQUFNLENBQUM7UUFDN0IsSUFBSSxDQUFDLFdBQVcsR0FBTSxPQUFPLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzdDLElBQUksQ0FBQyxj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
|