From f33c16656446e08658659f153d5cd24c988df042 Mon Sep 17 00:00:00 2001 From: Andrey Belym Date: Mon, 31 Oct 2016 18:00:35 +0300 Subject: [PATCH] Overhaul t.resizeWindow and implement t.maximizeWindow (closes #816, closes #812) (#837) * Show error is the requested size is too big on Mac * Implement t.maximize --- src/api/test-controller.js | 7 +- src/browser/provider/built-in/base.js | 109 -------- src/browser/provider/built-in/index.js | 12 +- .../provider/built-in/locally-installed.js | 25 +- src/browser/provider/built-in/path.js | 27 +- src/browser/provider/built-in/remote.js | 75 +++--- src/browser/provider/cache-item.js | 27 -- src/browser/provider/index.js | 237 ++++++++++++++++++ src/browser/provider/plugin-host.js | 26 +- src/browser/provider/pool.js | 6 +- .../prepare-browser-manipulation.js | 1 + src/errors/test-run/index.js | 8 + src/errors/test-run/templates.js | 4 + src/errors/test-run/type.js | 3 +- src/test-run/browser-manipulation-queue.js | 12 - src/test-run/commands/browser-manipulation.js | 6 + src/test-run/commands/from-object.js | 6 +- src/test-run/commands/type.js | 1 + src/test-run/index.js | 39 ++- src/warnings/message.js | 5 +- .../es-next/maximize-window/pages/index.html | 8 + .../api/es-next/maximize-window/test.js | 10 + .../testcafe-fixtures/maximize-window-test.js | 24 ++ .../api/es-next/resize-window/test.js | 10 + .../testcafe-fixtures/resize-window-test.js | 7 + test/server/browser-connection-test.js | 26 +- .../window-dimensions-overflow-error | 21 ++ test/server/runner-test.js | 32 ++- test/server/test-run-error-formatting-test.js | 5 + 29 files changed, 525 insertions(+), 254 deletions(-) delete mode 100644 src/browser/provider/built-in/base.js delete mode 100644 src/browser/provider/cache-item.js create mode 100644 src/browser/provider/index.js create mode 100644 test/functional/fixtures/api/es-next/maximize-window/pages/index.html create mode 100644 test/functional/fixtures/api/es-next/maximize-window/test.js create mode 100644 test/functional/fixtures/api/es-next/maximize-window/testcafe-fixtures/maximize-window-test.js create mode 100644 test/server/data/expected-test-run-errors/window-dimensions-overflow-error diff --git a/src/api/test-controller.js b/src/api/test-controller.js index a4fc0536658..7520786b1c9 100644 --- a/src/api/test-controller.js +++ b/src/api/test-controller.js @@ -30,7 +30,8 @@ import { import { TakeScreenshotCommand, ResizeWindowCommand, - ResizeWindowToFitDeviceCommand + ResizeWindowToFitDeviceCommand, + MaximizeWindowCommand } from '../test-run/commands/browser-manipulation'; import { WaitCommand } from '../test-run/commands/observation'; @@ -205,6 +206,10 @@ export default class TestController { return this._enqueueAction('resizeWindowToFitDevice', ResizeWindowToFitDeviceCommand, { device, options }); } + _maximizeWindow$ () { + return this._enqueueAction('maximizeWindow', MaximizeWindowCommand); + } + _switchToIframe$ (selector) { return this._enqueueAction('switchToIframe', SwitchToIframeCommand, { selector }); } diff --git a/src/browser/provider/built-in/base.js b/src/browser/provider/built-in/base.js deleted file mode 100644 index 933397df90f..00000000000 --- a/src/browser/provider/built-in/base.js +++ /dev/null @@ -1,109 +0,0 @@ -import browserTools from 'testcafe-browser-tools'; -import OS from 'os-family'; -import WARNING_MESSAGE from '../../../warnings/message'; -import delay from '../../../utils/delay'; - - -/*eslint-disable no-undef*/ -function getTitle () { - return document.title; -} - -function getCurrentSize () { - return { - width: window.innerWidth, - height: window.innerHeight, - }; -} -/*eslint-disable no-undef*/ - -const GET_TITLE_SCRIPT = getTitle.toString(); -const GET_CURRENT_SIZE_SCRIPT = getCurrentSize.toString(); - -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 - }; -} - -export default class BrowserProviderBase { - constructor () { - // 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.resizeCorrections = {}; - } - - async calculateResizeCorrections (browserId) { - // NOTE: delay to ensure the window finished the opening - await this.waitForConnectionReady(browserId); - await delay(BROWSER_OPENING_DELAY); - - var title = await this.runInitScript(browserId, GET_TITLE_SCRIPT); - - if (!await browserTools.isMaximized(title)) - return; - - var currentSize = await this.runInitScript(browserId, GET_CURRENT_SIZE_SCRIPT); - var etalonSize = subtractSizes(currentSize, RESIZE_DIFF_SIZE); - - await browserTools.resize(title, currentSize.width, currentSize.height, etalonSize.width, etalonSize.height); - - var resizedSize = await this.runInitScript(browserId, GET_CURRENT_SIZE_SCRIPT); - var correctionSize = subtractSizes(resizedSize, etalonSize); - - await browserTools.resize(title, resizedSize.width, resizedSize.height, etalonSize.width, etalonSize.height); - - resizedSize = await this.runInitScript(browserId, GET_CURRENT_SIZE_SCRIPT); - - correctionSize = sumSizes(correctionSize, subtractSizes(resizedSize, etalonSize)); - - this.resizeCorrections[browserId] = correctionSize; - - await browserTools.maximize(title); - } - - async resizeWindow (browserId, width, height, currentWidth, currentHeight) { - // TODO: remove once https://github.com/DevExpress/testcafe-browser-tools/issues/12 implemented - if (OS.linux) { - this.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsNotSupportedOnLinux); - return; - } - - if (this.resizeCorrections[browserId]) { - width -= this.resizeCorrections[browserId].width; - height -= this.resizeCorrections[browserId].height; - - delete this.resizeCorrections[browserId]; - } - - await browserTools.resize(browserId, currentWidth, currentHeight, width, height); - } - - async takeScreenshot (browserId, screenshotPath) { - // TODO: remove once https://github.com/DevExpress/testcafe-browser-tools/issues/12 implemented - if (OS.linux) { - this.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsNotSupportedOnLinux); - return; - } - - await browserTools.screenshot(browserId, screenshotPath); - } -} diff --git a/src/browser/provider/built-in/index.js b/src/browser/provider/built-in/index.js index fcce72b3ab5..788d791c4db 100644 --- a/src/browser/provider/built-in/index.js +++ b/src/browser/provider/built-in/index.js @@ -1,10 +1,10 @@ -import PathBrowserProvider from './path'; -import LocallyInstalledBrowserProvider from './locally-installed'; -import RemoteBrowserProvider from './remote'; +import pathBrowserProvider from './path'; +import locallyInstalledBrowserProvider from './locally-installed'; +import remoteBrowserProvider from './remote'; export default { - 'locally-installed': new LocallyInstalledBrowserProvider(), - 'path': new PathBrowserProvider(), - 'remote': new RemoteBrowserProvider() + 'locally-installed': locallyInstalledBrowserProvider, + 'path': pathBrowserProvider, + 'remote': remoteBrowserProvider }; diff --git a/src/browser/provider/built-in/locally-installed.js b/src/browser/provider/built-in/locally-installed.js index 52a2fe0f5d8..92138de18e8 100644 --- a/src/browser/provider/built-in/locally-installed.js +++ b/src/browser/provider/built-in/locally-installed.js @@ -1,19 +1,28 @@ import browserTools from 'testcafe-browser-tools'; -import PathBrowserProvider from './path'; -export default class LocallyInstalledBrowserProvider extends PathBrowserProvider { - constructor () { - super(); +export default { + isMultiBrowser: true, - this.isMultiBrowser = true; - } + async openBrowser (browserId, pageUrl, browserName) { + var openParameters = await browserTools.getBrowserInfo(browserName); + + await browserTools.open(openParameters, pageUrl); + }, + + async closeBrowser (browserId) { + await browserTools.close(browserId); + }, + + async isLocalBrowser () { + return true; + }, async getBrowserList () { var installations = await browserTools.getInstallations(); return Object.keys(installations); - } + }, async isValidBrowserName (browserName) { var browserNames = await this.getBrowserList(); @@ -22,4 +31,4 @@ export default class LocallyInstalledBrowserProvider extends PathBrowserProvider return browserNames.indexOf(browserName) > -1; } -} +}; diff --git a/src/browser/provider/built-in/path.js b/src/browser/provider/built-in/path.js index 8d510cf7b69..27a5ac8a6e3 100644 --- a/src/browser/provider/built-in/path.js +++ b/src/browser/provider/built-in/path.js @@ -1,14 +1,8 @@ import browserTools from 'testcafe-browser-tools'; -import OS from 'os-family'; -import BrowserProviderBase from './base'; -export default class PathBrowserProvider extends BrowserProviderBase { - constructor () { - super(); - - this.isMultiBrowser = true; - } +export default { + isMultiBrowser: true, async _handleJSON (str) { var params = null; @@ -32,7 +26,7 @@ export default class PathBrowserProvider extends BrowserProviderBase { openParameters.cmd = params.cmd; return openParameters; - } + }, async openBrowser (browserId, pageUrl, browserName) { var openParameters = await browserTools.getBrowserInfo(browserName) || await this._handleJSON(browserName); @@ -41,18 +35,13 @@ export default class PathBrowserProvider extends BrowserProviderBase { throw new Error('The specified browser name is not valid!'); await browserTools.open(openParameters, pageUrl); - - if (OS.win) - await super.calculateResizeCorrections(browserId); - } + }, async closeBrowser (browserId) { await browserTools.close(browserId); - } + }, - async getBrowserList () { - return [ - '${PATH_TO_BROWSER_EXECUTABLE}' - ]; + async isLocalBrowser () { + return true; } -} +}; diff --git a/src/browser/provider/built-in/remote.js b/src/browser/provider/built-in/remote.js index 19d8a6cac62..dc3b5ea60e6 100644 --- a/src/browser/provider/built-in/remote.js +++ b/src/browser/provider/built-in/remote.js @@ -1,46 +1,47 @@ -import BrowserProviderBase from './base'; -import OS from 'os-family'; +import { findWindow } from 'testcafe-browser-tools'; import WARNING_MESSAGE from '../../../warnings/message'; -export default class RemoteBrowserProvider extends BrowserProviderBase { - constructor () { - super(); - - // NOTE: This can be used to disable resize correction when running unit tests. - this.disableResizeHack = false; - } +export default { + localBrowsersFlags: {}, async openBrowser (browserId) { - try { - if (OS.win && !this.disableResizeHack) - await super.calculateResizeCorrections(browserId); - } - catch (e) { - return; - } - } + await this.waitForConnectionReady(browserId); - async closeBrowser () { - return; - } + var localBrowserWindow = await findWindow(browserId); - // NOTE: we must try to do a local screenshot or resize, if browser is accessible, but emit warning - async takeScreenshot (browserId, ...args) { - try { - await super.takeScreenshot(browserId, ...args); - } - catch (e) { - this.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsOnRemoteBrowser); - } - } + this.localBrowsersFlags[browserId] = localBrowserWindow !== null; + }, + + async closeBrowser (browserId) { + delete this.localBrowsersFlags[browserId]; + }, + + async isLocalBrowser (browserId) { + return this.localBrowsersFlags[browserId]; + }, + + // NOTE: we must try to do a local screenshot or resize, if browser is accessible, and emit warning otherwise + async hasCustomActionForBrowser (browserId) { + var isLocalBrowser = this.localBrowsersFlags[browserId]; + + return { + hasResizeWindow: !isLocalBrowser, + hasMaximizeWindow: !isLocalBrowser, + hasTakeScreenshot: !isLocalBrowser, + hasCanResizeWindowToDimensions: !isLocalBrowser + }; + }, + + async takeScreenshot (browserId) { + this.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsOnRemoteBrowser); + }, + + async resizeWindow (browserId) { + this.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsOnRemoteBrowser); + }, - async resizeWindow (browserId, ...args) { - try { - await super.resizeWindow(browserId, ...args); - } - catch (e) { - this.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsOnRemoteBrowser); - } + async maximizeWindow (browserId) { + this.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsOnRemoteBrowser); } -} +}; diff --git a/src/browser/provider/cache-item.js b/src/browser/provider/cache-item.js deleted file mode 100644 index e808b8f6fec..00000000000 --- a/src/browser/provider/cache-item.js +++ /dev/null @@ -1,27 +0,0 @@ -import Promise from 'pinkie'; - - -export default class BrowserProviderCacheItem { - constructor (provider) { - this.provider = provider; - this.initPromise = Promise.resolve(false); - } - - init () { - this.initPromise = this.initPromise - .then(initialized => initialized ? Promise.resolve() : this.provider.init()) - .then(() => true) - .catch(() => false); - - return this.initPromise; - } - - dispose () { - this.initPromise = this.initPromise - .then(initialized => initialized ? this.provider.dispose() : Promise.resolve()) - .then(() => false) - .catch(() => false); - - return this.initPromise; - } -} diff --git a/src/browser/provider/index.js b/src/browser/provider/index.js new file mode 100644 index 00000000000..db844cc9de2 --- /dev/null +++ b/src/browser/provider/index.js @@ -0,0 +1,237 @@ +import Promise from 'pinkie'; +import browserTools from 'testcafe-browser-tools'; +import OS from 'os-family'; +import delay from '../../utils/delay'; +import WARNING_MESSAGE from '../../warnings/message'; + + +/*eslint-disable no-undef*/ +function getTitle () { + return document.title; +} + +function getWindowDimensionsInfo () { + return { + width: window.innerWidth, + height: window.innerHeight, + outerWidth: window.outerWidth, + outerHeight: window.outerHeight, + availableWidth: screen.availWidth, + availableHeight: screen.availHeight + }; +} +/*eslint-disable no-undef*/ + +const GET_TITLE_SCRIPT = getTitle.toString(); +const GET_WINDOW_DIMENSIONS_INFO_SCRIPT = getWindowDimensionsInfo.toString(); + +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 + }; +} + +export default 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.resizeCorrections = {}; + this.maxScreenSizes = {}; + } + + async _calculateResizeCorrections (browserId) { + // NOTE: delay to ensure the window finished the opening + await this.plugin.waitForConnectionReady(browserId); + await delay(BROWSER_OPENING_DELAY); + + var title = await this.plugin.runInitScript(browserId, GET_TITLE_SCRIPT); + + if (!await browserTools.isMaximized(title)) + return; + + var currentSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT); + var etalonSize = subtractSizes(currentSize, RESIZE_DIFF_SIZE); + + await browserTools.resize(title, currentSize.width, currentSize.height, etalonSize.width, etalonSize.height); + + var resizedSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT); + var correctionSize = subtractSizes(resizedSize, etalonSize); + + await browserTools.resize(title, resizedSize.width, resizedSize.height, etalonSize.width, etalonSize.height); + + resizedSize = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT); + + correctionSize = sumSizes(correctionSize, subtractSizes(resizedSize, etalonSize)); + + this.resizeCorrections[browserId] = correctionSize; + + await browserTools.maximize(title); + } + + async _calculateMacSizeLimits (browserId) { + await this.plugin.waitForConnectionReady(browserId); + await delay(BROWSER_OPENING_DELAY); + + var sizeInfo = await this.plugin.runInitScript(browserId, GET_WINDOW_DIMENSIONS_INFO_SCRIPT); + + this.maxScreenSizes[browserId] = { + width: sizeInfo.availableWidth - (sizeInfo.outerWidth - sizeInfo.width), + height: sizeInfo.availableHeight - (sizeInfo.outerHeight - sizeInfo.height) + }; + } + + async _onOpenBrowser (browserId) { + if (OS.win) + await this._calculateResizeCorrections(browserId); + else if (OS.mac) + await this._calculateMacSizeLimits(browserId); + } + + async _closeLocalBrowser (browserId) { + await browserTools.close(browserId); + } + + async _resizeLocalBrowserWindow (browserId, width, height, currentWidth, currentHeight) { + // TODO: remove once https://github.com/DevExpress/testcafe-browser-tools/issues/12 implemented + if (OS.linux) { + this.plugin.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsNotSupportedOnLinux); + return; + } + + if (this.resizeCorrections[browserId]) { + width -= this.resizeCorrections[browserId].width; + height -= this.resizeCorrections[browserId].height; + + delete this.resizeCorrections[browserId]; + } + + await browserTools.resize(browserId, currentWidth, currentHeight, width, height); + } + + async _takeLocalBrowserScreenshot (browserId, screenshotPath) { + // TODO: remove once https://github.com/DevExpress/testcafe-browser-tools/issues/12 implemented + if (OS.linux) { + this.plugin.reportWarning(browserId, WARNING_MESSAGE.browserManipulationsNotSupportedOnLinux); + return; + } + + await browserTools.screenshot(browserId, screenshotPath); + } + + async _canResizeLocalBrowserWindowToDimensions (browserId, width, height) { + if (!OS.mac) + return true; + + var maxScreenSize = this.maxScreenSizes[browserId]; + + return width <= maxScreenSize.width && height <= maxScreenSize.height; + } + + async _maximizeLocalBrowserWindow (browserId) { + await browserTools.maximize(browserId); + } + + init () { + this.initPromise = this.initPromise + .then(initialized => initialized ? Promise.resolve() : this.plugin.init()) + .then(() => true) + .catch(() => false); + + return this.initPromise; + } + + dispose () { + this.initPromise = this.initPromise + .then(initialized => initialized ? this.plugin.dispose() : Promise.resolve()) + .then(() => false) + .catch(() => false); + + return this.initPromise; + } + + async openBrowser (browserId, pageUrl, browserName) { + await this.plugin.openBrowser(browserId, pageUrl, browserName); + + var isLocalBrowser = await this.plugin.isLocalBrowser(browserId); + + if (isLocalBrowser) + await this._onOpenBrowser(browserId); + } + + async closeBrowser (browserId) { + await this.plugin.closeBrowser(browserId); + } + + async getBrowserList () { + return await this.plugin.getBrowserList(); + } + + async isValidBrowserName (browserName) { + return await this.plugin.isValidBrowserName(browserName); + } + + async resizeWindow (browserId, width, height, currentWidth, currentHeight) { + var isLocalBrowser = await this.plugin.isLocalBrowser(browserId); + var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId); + + if (isLocalBrowser && !supportedFeatures.hasResizeWindow) { + await this._resizeLocalBrowserWindow(browserId, width, height, currentWidth, currentHeight); + return; + } + + await this.plugin.resizeWindow(browserId, width, height, currentWidth, currentHeight); + } + + async canResizeWindowToDimensions (browserId, width, height) { + var isLocalBrowser = await this.plugin.isLocalBrowser(browserId); + var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId); + + if (isLocalBrowser && !supportedFeatures.hasCanResizeWindowToDimensions) + return await this._canResizeLocalBrowserWindowToDimensions(browserId, width, height); + + return await this.plugin.canResizeWindowToDimensions(browserId, width, height); + } + + async maximizeWindow (browserId) { + var isLocalBrowser = await this.plugin.isLocalBrowser(browserId); + var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId); + + if (isLocalBrowser && !supportedFeatures.hasCanResizeWindowToDimensions) + return await this._maximizeLocalBrowserWindow(browserId); + + return await this.plugin.maximizeWindow(browserId); + } + + async takeScreenshot (browserId, screenshotPath, pageWidth, pageHeight) { + var isLocalBrowser = await this.plugin.isLocalBrowser(browserId); + var supportedFeatures = await this.plugin.hasCustomActionForBrowser(browserId); + + if (isLocalBrowser && !supportedFeatures.hasTakeScreenshot) { + await this._takeLocalBrowserScreenshot(browserId, screenshotPath, pageWidth, pageHeight); + return; + } + + await this.plugin.takeScreenshot(browserId, screenshotPath, pageWidth, pageHeight); + } +} diff --git a/src/browser/provider/plugin-host.js b/src/browser/provider/plugin-host.js index b834b8e5b35..10325a6d4c0 100644 --- a/src/browser/provider/plugin-host.js +++ b/src/browser/provider/plugin-host.js @@ -1,6 +1,6 @@ /* global Symbol */ -import { assignIn } from 'lodash'; import Promise from 'pinkie'; +import { assignIn } from 'lodash'; import promisifyEvent from 'promisify-event'; import BrowserConnection from '../connection'; import WARNING_MESSAGE from '../../warnings/message'; @@ -45,7 +45,6 @@ export default class BrowserProviderPluginHost { } // API - // Required // Browser control async openBrowser (/* browserId, pageUrl, browserName */) { throw new Error('Not implemented!'); @@ -55,8 +54,6 @@ export default class BrowserProviderPluginHost { throw new Error('Not implemented!'); } - - // Optional // Initialization async init () { return; @@ -77,11 +74,32 @@ export default class BrowserProviderPluginHost { } // Extra functions + async isLocalBrowser (/* browserId */) { + return false; + } + + async hasCustomActionForBrowser (/* browserId */) { + return { + hasResizeWindow: this.hasOwnProperty('resizeWindow'), + hasTakeScreenshot: this.hasOwnProperty('takeScreenshot'), + hasCanResizeWindowToDimensions: this.hasOwnProperty('canResizeWindowToDimensions'), + hasMaximizeWindow: this.hasOwnProperty('maximizeWindow') + }; + } + async resizeWindow (/* browserId, width, height, currentWidth, currentHeight */) { this.reportWarning(WARNING_MESSAGE.resizeNotSupportedByBrowserProvider, this[name]); } + async canResizeWindowToDimensions (/* browserId, width, height */) { + return true; + } + async takeScreenshot (/* browserId, screenshotPath, pageWidth, pageHeight */) { this.reportWarning(WARNING_MESSAGE.screenshotNotSupportedByBrowserProvider, this[name]); } + + async maximizeWindow (/*browserId*/) { + this.reportWarning(WARNING_MESSAGE.maximizeNotSupportedByBrowserProvider, this[name]); + } } diff --git a/src/browser/provider/pool.js b/src/browser/provider/pool.js index 690b00a10d5..8f1a9dba1d4 100644 --- a/src/browser/provider/pool.js +++ b/src/browser/provider/pool.js @@ -1,7 +1,7 @@ import Promise from 'pinkie'; import BUILT_IN_PROVIDERS from './built-in'; import BrowserProviderPluginHost from './plugin-host'; -import BrowserProviderCacheItem from './cache-item'; +import BrowserProvider from './'; import BrowserConnection from '../connection'; import { GeneralError } from '../../errors/runtime'; import MESSAGE from '../../errors/runtime/message'; @@ -79,7 +79,7 @@ export default { }, _getProviderFromCache (providerName) { - return this.providersCache[providerName] && this.providersCache[providerName].provider || null; + return this.providersCache[providerName] || null; }, _getBuiltinProvider (providerName) { @@ -111,7 +111,7 @@ export default { }, addProvider (providerName, providerObject) { - this.providersCache[providerName] = new BrowserProviderCacheItem( + this.providersCache[providerName] = new BrowserProvider( new BrowserProviderPluginHost(providerObject, providerName) ); }, diff --git a/src/client/driver/command-executors/prepare-browser-manipulation.js b/src/client/driver/command-executors/prepare-browser-manipulation.js index 5cc7adf2023..071d56ff5fd 100644 --- a/src/client/driver/command-executors/prepare-browser-manipulation.js +++ b/src/client/driver/command-executors/prepare-browser-manipulation.js @@ -3,6 +3,7 @@ import testCafeCore from '../deps/testcafe-core'; import MESSAGE from '../../../test-run/client-messages'; import DriverStatus from '../status'; + var nativeMethods = hammerhead.nativeMethods; var transport = hammerhead.transport; var delay = testCafeCore.delay; diff --git a/src/errors/test-run/index.js b/src/errors/test-run/index.js index 1ae5c3c1f58..3fbd9b51963 100644 --- a/src/errors/test-run/index.js +++ b/src/errors/test-run/index.js @@ -376,3 +376,11 @@ export class SetNativeDialogHandlerCodeWrongTypeError extends TestRunErrorBase { this.actualType = actualType; } } + +export class WindowDimensionsOverflowError extends TestRunErrorBase { + constructor (callsite) { + super(TYPE.windowDimensionsOverflowError); + + this.callsite = callsite; + } +} diff --git a/src/errors/test-run/templates.js b/src/errors/test-run/templates.js index f12b682f261..ee4a05be4c3 100644 --- a/src/errors/test-run/templates.js +++ b/src/errors/test-run/templates.js @@ -211,5 +211,9 @@ export default { [TYPE.cantObtainInfoForElementSpecifiedBySelectorError]: err => markup(err, ` Cannot obtain information about the node because the specified selector does not match any node in the DOM tree. + `), + + [TYPE.windowDimensionsOverflowError]: err => markup(err, ` + Unable to resize the window because the specified size exceeds the screen size. On macOS, a window cannot be larger than the screen. `) }; diff --git a/src/errors/test-run/type.js b/src/errors/test-run/type.js index d488338be38..ac9db1e8bdc 100644 --- a/src/errors/test-run/type.js +++ b/src/errors/test-run/type.js @@ -46,5 +46,6 @@ export default { invalidSelectorResultError: 'invalidSelectorResultError', cantObtainInfoForElementSpecifiedBySelectorError: 'cantObtainInfoForElementSpecifiedBySelectorError', externalAssertionLibraryError: 'externalAssertionLibraryError', - pageLoadError: 'pageLoadError' + pageLoadError: 'pageLoadError', + windowDimensionsOverflowError: 'windowDimensionsOverflowError' }; diff --git a/src/test-run/browser-manipulation-queue.js b/src/test-run/browser-manipulation-queue.js index 5a0408b4d3d..91dd2fb8084 100644 --- a/src/test-run/browser-manipulation-queue.js +++ b/src/test-run/browser-manipulation-queue.js @@ -1,4 +1,3 @@ -import { getViewportSize } from 'testcafe-browser-tools'; import { isServiceCommand } from './commands/utils'; import COMMAND_TYPE from './commands/type'; import WARNING_MESSAGE from '../warnings/message'; @@ -22,14 +21,6 @@ export default class BrowserManipulationQueue { } } - async _resizeWindowToFitDevice (device, portrait, currentWidth, currentHeight) { - var { landscapeWidth, portraitWidth } = getViewportSize(device); - var width = portrait ? portraitWidth : landscapeWidth; - var height = portrait ? landscapeWidth : portraitWidth; - - return await this._resizeWindow(width, height, currentWidth, currentHeight); - } - async _takeScreenshot (capture) { if (!this.screenshotCapturer.enabled) { this.warningLog.addWarning(WARNING_MESSAGE.screenshotsPathNotSpecified); @@ -65,9 +56,6 @@ export default class BrowserManipulationQueue { case COMMAND_TYPE.resizeWindow: return await this._resizeWindow(command.width, command.height, driverMsg.innerWidth, driverMsg.innerHeight); - - case COMMAND_TYPE.resizeWindowToFitDevice: - return await this._resizeWindowToFitDevice(command.device, command.options.portraitOrientation, driverMsg.innerWidth, driverMsg.innerHeight); } return null; diff --git a/src/test-run/commands/browser-manipulation.js b/src/test-run/commands/browser-manipulation.js index 34f87214fd9..741963d152c 100644 --- a/src/test-run/commands/browser-manipulation.js +++ b/src/test-run/commands/browser-manipulation.js @@ -75,3 +75,9 @@ export class ResizeWindowToFitDeviceCommand extends Assignable { ]; } } + +export class MaximizeWindowCommand { + constructor () { + this.type = TYPE.maximizeWindow; + } +} diff --git a/src/test-run/commands/from-object.js b/src/test-run/commands/from-object.js index 193f64666be..197ddfa632c 100644 --- a/src/test-run/commands/from-object.js +++ b/src/test-run/commands/from-object.js @@ -23,7 +23,8 @@ import { import { TakeScreenshotCommand, ResizeWindowCommand, - ResizeWindowToFitDeviceCommand + ResizeWindowToFitDeviceCommand, + MaximizeWindowCommand } from './browser-manipulation'; import { WaitCommand } from './observation'; @@ -86,6 +87,9 @@ export default function createCommandFromObject (obj) { case TYPE.resizeWindowToFitDevice: return new ResizeWindowToFitDeviceCommand(obj); + case TYPE.maximizeWindow: + return new MaximizeWindowCommand(obj); + case TYPE.switchToIframe: return new SwitchToIframeCommand(obj); diff --git a/src/test-run/commands/type.js b/src/test-run/commands/type.js index ad4bde1146e..799cee05ef6 100644 --- a/src/test-run/commands/type.js +++ b/src/test-run/commands/type.js @@ -26,6 +26,7 @@ export default { prepareBrowserManipulation: 'prepare-browser-manipulation', resizeWindow: 'resize-window', resizeWindowToFitDevice: 'resize-window-to-fit-device', + maximizeWindow: 'maximize-window', switchToIframe: 'switch-to-iframe', switchToMainWindow: 'switch-to-main-window', setNativeDialogHandler: 'set-native-dialog-handler', diff --git a/src/test-run/index.js b/src/test-run/index.js index 124c02a639f..56fca2a9597 100644 --- a/src/test-run/index.js +++ b/src/test-run/index.js @@ -3,15 +3,16 @@ import { readSync as read } from 'read-file-relative'; import Promise from 'pinkie'; import Mustache from 'mustache'; import { Session } from 'testcafe-hammerhead'; +import { getViewportSize } from 'testcafe-browser-tools'; import TestRunDebugLog from './debug-log'; import TestRunErrorFormattableAdapter from '../errors/test-run/formattable-adapter'; -import { PageLoadError } from '../errors/test-run/'; +import { PageLoadError, WindowDimensionsOverflowError } from '../errors/test-run/'; import BrowserManipulationQueue from './browser-manipulation-queue'; import CLIENT_MESSAGES from './client-messages'; import STATE from './state'; import COMMAND_TYPE from './commands/type'; -import { TakeScreenshotOnFailCommand } from './commands/browser-manipulation'; +import { TakeScreenshotOnFailCommand, ResizeWindowCommand } from './commands/browser-manipulation'; import { TestDoneCommand, @@ -281,13 +282,45 @@ export default class TestRun extends Session { return this.currentDriverTask ? this.currentDriverTask.command : null; } + static _transformResizeWindowToFitDeviceCommand (command) { + var { landscapeWidth, portraitWidth } = getViewportSize(command.device); + var portrait = command.options.portraitOrientation; + var width = portrait ? portraitWidth : landscapeWidth; + var height = portrait ? landscapeWidth : portraitWidth; + + return new ResizeWindowCommand({ width, height }); + } + + _maximizeBrowserWindow () { + var browserId = this.browserConnection.id; + var provider = this.browserConnection.provider; + + return provider.maximizeWindow(browserId); + } + // Execute command - executeCommand (command, callsite) { + async executeCommand (command, callsite) { this.debugLog.command(command); if (this.pendingPageError && isCommandRejectableByPageError(command)) return this._rejectCommandWithPageError(callsite); + if (command.type === COMMAND_TYPE.resizeWindowToFitDevice) + command = TestRun._transformResizeWindowToFitDeviceCommand(command); + + if (command.type === COMMAND_TYPE.resizeWindow) { + var browserId = this.browserConnection.id; + var provider = this.browserConnection.provider; + + var canResizeWindow = await provider.canResizeWindowToDimensions(browserId, command.width, command.height); + + if (!canResizeWindow) + return Promise.reject(new WindowDimensionsOverflowError(callsite)); + } + + if (command.type === COMMAND_TYPE.maximizeWindow) + return this._maximizeBrowserWindow(); + if (isBrowserManipulationCommand(command)) { this.browserManipulationQueue.push(command); diff --git a/src/warnings/message.js b/src/warnings/message.js index 142edabe8d5..0e49ea4a4a5 100644 --- a/src/warnings/message.js +++ b/src/warnings/message.js @@ -1,9 +1,10 @@ export default { screenshotsPathNotSpecified: 'Was unable to take screenshots because the screenshot directory is not specified. To specify it, use the "-s" or "--screenshots" command line option or the "screenshots" method of the test runner in case you are using API.', screenshotError: 'Was unable to take a screenshot due to an error.\n\n{errMessage}', - resizeError: 'Was unable to resize the window due to an error.\n\n{errMessage}', browserManipulationsNotSupportedOnLinux: 'The screenshot and window resize functionalities are not yet supported on Linux. Subscribe to the following issue to keep track: https://github.com/DevExpress/testcafe-browser-tools/issues/12', browserManipulationsOnRemoteBrowser: 'The screenshot and window resize functionalities are not supported in a remote browser. They can function only if the browser is running on the same machine and in the same environment as the TestCafe server.', screenshotNotSupportedByBrowserProvider: 'The screenshot functionality is not supported by the "{providerName}" browser provider.', - resizeNotSupportedByBrowserProvider: 'The window resize functionality is not supported by the "{providerName}" browser provider.' + resizeNotSupportedByBrowserProvider: 'The window resize functionality is not supported by the "{providerName}" browser provider.', + maximizeNotSupportedByBrowserProvider: 'The maximize functionality is not supported by the "{providerName}" browser provider.', + resizeError: 'Was unable to resize the window due to an error.\n\n{errMessage}' }; diff --git a/test/functional/fixtures/api/es-next/maximize-window/pages/index.html b/test/functional/fixtures/api/es-next/maximize-window/pages/index.html new file mode 100644 index 00000000000..d01255bb6ec --- /dev/null +++ b/test/functional/fixtures/api/es-next/maximize-window/pages/index.html @@ -0,0 +1,8 @@ + + + + Maximize window + + + + diff --git a/test/functional/fixtures/api/es-next/maximize-window/test.js b/test/functional/fixtures/api/es-next/maximize-window/test.js new file mode 100644 index 00000000000..5b09299edad --- /dev/null +++ b/test/functional/fixtures/api/es-next/maximize-window/test.js @@ -0,0 +1,10 @@ +var config = require('../../../../config.js'); + + +describe('[API] t.maximizeWindow', function () { + if (config.useLocalBrowsers) { + it('Should maximize the window to the maximum available size', function () { + return runTests('./testcafe-fixtures/maximize-window-test.js', 'Maximize window'); + }); + } +}); diff --git a/test/functional/fixtures/api/es-next/maximize-window/testcafe-fixtures/maximize-window-test.js b/test/functional/fixtures/api/es-next/maximize-window/testcafe-fixtures/maximize-window-test.js new file mode 100644 index 00000000000..00bf86a0dbf --- /dev/null +++ b/test/functional/fixtures/api/es-next/maximize-window/testcafe-fixtures/maximize-window-test.js @@ -0,0 +1,24 @@ +import { expect } from 'chai'; +import { ClientFunction } from 'testcafe'; + +fixture `Maximize Window` + .page `http://localhost:3000/fixtures/api/es-next/maximize-window/pages/index.html`; + + +const getWindowDimensionsInfo = ClientFunction(() => { + return { + width: window.outerWidth, + height: window.outerHeight, + availableHeight: screen.availHeight, + availableWidth: screen.availWidth + }; +}); + +test('Maximize window', async t => { + await t.maximizeWindow(); + + var dimensions = await getWindowDimensionsInfo(); + + expect(dimensions.width).to.be.at.least(dimensions.availableWidth); + expect(dimensions.height).to.be.at.least(dimensions.availableHeight); +}); diff --git a/test/functional/fixtures/api/es-next/resize-window/test.js b/test/functional/fixtures/api/es-next/resize-window/test.js index bd56c072d28..971ea43fd58 100644 --- a/test/functional/fixtures/api/es-next/resize-window/test.js +++ b/test/functional/fixtures/api/es-next/resize-window/test.js @@ -61,6 +61,16 @@ describe('[API] Resize window actions', function () { }); }); }); + + if (OS.mac) { + it('Should fail when the requested size exceeds the maximum available size', function () { + return runTests('./testcafe-fixtures/resize-window-test.js', 'Too big size', { shouldFail: true }) + .catch(function (errs) { + errorInEachBrowserContains(errs, 'Unable to resize the window because the specified size exceeds the screen size. On macOS, a window cannot be larger than the screen.', 0); + errorInEachBrowserContains(errs, '> 97 | await t.resizeWindow(hugeWidth, hugeHeight);', 0); + }); + }); + } } if (OS.linux) { diff --git a/test/functional/fixtures/api/es-next/resize-window/testcafe-fixtures/resize-window-test.js b/test/functional/fixtures/api/es-next/resize-window/testcafe-fixtures/resize-window-test.js index 861d893efec..ade72f45b83 100644 --- a/test/functional/fixtures/api/es-next/resize-window/testcafe-fixtures/resize-window-test.js +++ b/test/functional/fixtures/api/es-next/resize-window/testcafe-fixtures/resize-window-test.js @@ -89,3 +89,10 @@ test('Resize the window to fit a device leads to js-error', async t => { await t.resizeWindowToFitDevice('iPhone'); }); + +test('Too big size', async t => { + var hugeWidth = 100000; + var hugeHeight = 100000; + + await t.resizeWindow(hugeWidth, hugeHeight); +}); diff --git a/test/server/browser-connection-test.js b/test/server/browser-connection-test.js index 2419af788df..d511ab8a0b0 100644 --- a/test/server/browser-connection-test.js +++ b/test/server/browser-connection-test.js @@ -10,9 +10,19 @@ var browserProviderPool = require('../../lib/browser/provider/pool'); var promisedRequest = promisify(request); describe('Browser connection', function () { - var testCafe = null; - var connection = null; + var testCafe = null; + var connection = null; + var origRemoteBrowserProvider = null; + var remoteBrowserProviderMock = { + openBrowser: function () { + return Promise.resolve(); + }, + + closeBrowser: function () { + return Promise.resolve(); + } + }; // Fixture setup/teardown before(function () { @@ -25,18 +35,16 @@ describe('Browser connection', function () { return browserProviderPool.getProvider('remote'); }) .then(function (remoteBrowserProvider) { - remoteBrowserProvider.disableResizeHack = true; + origRemoteBrowserProvider = remoteBrowserProvider; + + browserProviderPool.addProvider('remote', remoteBrowserProviderMock); }); }); after(function () { - return browserProviderPool - .getProvider('remote') - .then(function (remoteBrowserProvider) { - remoteBrowserProvider.disableResizeHack = false; + browserProviderPool.addProvider('remote', origRemoteBrowserProvider); - return testCafe.close(); - }); + return testCafe.close(); }); diff --git a/test/server/data/expected-test-run-errors/window-dimensions-overflow-error b/test/server/data/expected-test-run-errors/window-dimensions-overflow-error new file mode 100644 index 00000000000..9adb8d1cf2e --- /dev/null +++ b/test/server/data/expected-test-run-errors/window-dimensions-overflow-error @@ -0,0 +1,21 @@ +Unable to resize the window because the specified size exceeds the screen +size. On macOS, a window cannot be larger than the screen. + +Browser: Chrome 15.0.874 / Mac OS X 10.8.1 +Screenshot: /unix/path/with/ + + 18 |function func1 () { + 19 | record = createCallsiteRecord('func1'); + 20 |} + 21 | + 22 |(function func2 () { + > 23 | func1(); + 24 |})(); + 25 | + 26 |stackTrace.filter.deattach(stackFilter); + 27 | + 28 |module.exports = record; + + at func2 (testfile.js:23:5) + at Object. (testfile.js:24:3) + diff --git a/test/server/runner-test.js b/test/server/runner-test.js index 28cd768676d..0d85b5238c8 100644 --- a/test/server/runner-test.js +++ b/test/server/runner-test.js @@ -14,10 +14,20 @@ var delay = require('../../lib/utils/delay'); describe('Runner', function () { - var testCafe = null; - var runner = null; - var connection = null; - + var testCafe = null; + var runner = null; + var connection = null; + var origRemoteBrowserProvider = null; + + var remoteBrowserProviderMock = { + openBrowser: function () { + return Promise.resolve(); + }, + + closeBrowser: function () { + return Promise.resolve(); + } + }; // Fixture setup/teardown before(function () { @@ -28,7 +38,9 @@ describe('Runner', function () { return browserProviderPool.getProvider('remote'); }) .then(function (remoteBrowserProvider) { - remoteBrowserProvider.disableResizeHack = true; + origRemoteBrowserProvider = remoteBrowserProvider; + + browserProviderPool.addProvider('remote', remoteBrowserProviderMock); return testCafe.createBrowserConnection(); }) @@ -41,14 +53,10 @@ describe('Runner', function () { }); after(function () { - return browserProviderPool - .getProvider('remote') - .then(function (remoteBrowserProvider) { - remoteBrowserProvider.disableResizeHack = false; + browserProviderPool.addProvider('remote', origRemoteBrowserProvider); - connection.close(); - return testCafe.close(); - }); + connection.close(); + return testCafe.close(); }); diff --git a/test/server/test-run-error-formatting-test.js b/test/server/test-run-error-formatting-test.js index bec9a28ae1d..aa2c99e76a2 100644 --- a/test/server/test-run-error-formatting-test.js +++ b/test/server/test-run-error-formatting-test.js @@ -49,6 +49,7 @@ var NativeDialogNotHandledError = require('../../lib/error var UncaughtErrorInNativeDialogHandler = require('../../lib/errors/test-run').UncaughtErrorInNativeDialogHandler; var SetNativeDialogHandlerCodeWrongTypeError = require('../../lib/errors/test-run').SetNativeDialogHandlerCodeWrongTypeError; var CantObtainInfoForElementSpecifiedBySelectorError = require('../../lib/errors/test-run').CantObtainInfoForElementSpecifiedBySelectorError; +var WindowDimensionsOverflowError = require('../../lib/errors/test-run').WindowDimensionsOverflowError; var TEST_FILE_STACK_ENTRY_RE = new RegExp('\\s*\\n?\\(' + escapeRe(require.resolve('./data/test-callsite')), 'g'); @@ -283,6 +284,10 @@ describe('Error formatting', function () { it('Should format "cantObtainInfoForElementSpecifiedBySelectorError"', function () { assertErrorMessage('cant-obtain-info-for-element-specified-by-selector-error', new CantObtainInfoForElementSpecifiedBySelectorError(testCallsite)); }); + + it('Should format "windowDimensionsOverflowError"', function () { + assertErrorMessage('window-dimensions-overflow-error', new WindowDimensionsOverflowError()); + }); }); describe('Test coverage', function () {