blob: 912e5fa25244d300a867257f48a72afac72ee634 [file] [log] [blame]
// Copyright 2012 Selenium committers
// Copyright 2012 Software Freedom Conservancy
//
// 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.
/**
* @fileoverview Definitions for various command handlers used by the
* {@link safaridriver.extension.Server}.
*/
goog.provide('safaridriver.extension.commands');
goog.require('bot.response');
goog.require('goog.Uri');
goog.require('goog.array');
goog.require('goog.debug.Logger');
goog.require('goog.string');
goog.require('safaridriver.alert');
goog.require('safaridriver.extension.Tab');
goog.require('safaridriver.message.Alert');
goog.require('safaridriver.message.Load');
goog.require('webdriver.promise');
/**
* @type {!goog.debug.Logger}
* @private
* @const
*/
safaridriver.extension.commands.LOG_ = goog.debug.Logger.getLogger(
'safaridriver.extension.commands');
/**
* Retrieves a session's capabilities.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!Object.<*>} The session capabilities.
*/
safaridriver.extension.commands.describeSession = function(command, session) {
return session.getCapabilities();
};
/**
* Closes the tab the given session is currently focused on.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
*/
safaridriver.extension.commands.closeTab = function(command, session) {
session.getCommandTab().getBrowserTab().close();
};
/**
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {string} The handle for the tab the session is currently focused on.
*/
safaridriver.extension.commands.getWindowHandle = function(command, session) {
return session.getCommandTab().getId();
};
/**
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!Array.<string>} A list of IDs for the open tabs.
*/
safaridriver.extension.commands.getWindowHandles = function(command, session) {
return session.getTabIds();
};
/**
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!webdriver.promise.Promise} A promise that will resolve to a
* screenshot of the focused tab as a base64 encoded PNG.
*/
safaridriver.extension.commands.takeScreenshot = function(command, session) {
var response = new webdriver.promise.Deferred();
session.getCommandTab().visibleContentsAsDataURL(function(dataUrl) {
response.resolve(dataUrl.substring('data:image/png;base64,'.length));
});
return response.promise;
};
/**
* Loads a new page in the provided session.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!webdriver.promise.Promise} A promise that will be resolved when
* the operation has completed.
*/
safaridriver.extension.commands.loadUrl = function(command, session) {
var url = command.getParameter('url');
if (!url) {
throw Error('Invalid command: missing "url" parameter');
}
// Extensions do not work with files loaded from file://, so fail fast if
// we're asked to load such a URL.
var uri = new goog.Uri(url);
if (uri.getScheme() === 'file') {
throw Error('Unsupported URL protocol: ' + url +
'; for more information, see ' +
'http://code.google.com/p/selenium/issues/detail?id=3773');
}
var response = new webdriver.promise.Deferred();
var tab = session.getCommandTab();
tab.whenReady(function() {
var expectLoad = tab.loadsNewPage(uri);
safaridriver.extension.commands.sendNavigationCommand_(command, session,
expectLoad).then(response.resolve, response.reject);
});
return response.promise;
};
/**
* Reloads the session's current page.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!webdriver.promise.Promise} A promise that will be resolved when
* the operation has completed.
*/
safaridriver.extension.commands.refresh = function(command, session) {
var response = new webdriver.promise.Deferred();
session.getCommandTab().whenReady(function() {
safaridriver.extension.commands.sendNavigationCommand_(command, session,
true).then(response.resolve, response.reject);
});
return response.promise;
};
/**
* Sends a navigation related command to the tab for execution.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @param {boolean} waitForLoad Whether to wait for a load message from the
* tab before considering the command completed.
* @return {!webdriver.promise.Promise} A promise that will be resolved when
* the operation has completed.
* @private
*/
safaridriver.extension.commands.sendNavigationCommand_ = function(
command, session, waitForLoad) {
var response = new webdriver.promise.Deferred();
var tab = session.getCommandTab();
if (waitForLoad) {
tab.once(safaridriver.message.Load.TYPE, onLoad);
}
safaridriver.extension.commands.sendCommand(command, session).
then(onSuccess, onFailure);
return response.promise;
/** Load message handler that completes the command response. */
function onLoad() {
tab.removeListener(safaridriver.message.Alert.TYPE, onAlert);
if (response.isPending()) {
safaridriver.extension.commands.LOG_.info(
'Page load finished; returning');
tab.removeListener(safaridriver.message.Alert.TYPE, onAlert);
response.resolve();
}
}
/**
* Alert message handler that will fail the command if a UI blocking alert
* message is received.
* @param {!safaridriver.message.Alert} message The parsed message.
* @param {!SafariExtensionMessageEvent} e The message event.
*/
function onAlert(message, e) {
if (message.blocksUiThread() && response.isPending()) {
tab.removeListener(safaridriver.message.Alert.TYPE, onAlert);
tab.removeListener(safaridriver.message.Load.TYPE, onLoad);
// Stop propagation so the extension's global alert message handler
// does not fire.
e.stopPropagation();
response.resolve(
safaridriver.alert.createResponse(message.getMessage()));
}
}
/**
* Handler for when the tab responds to navigation command. The receipt of
* this response does not indicate that the navigation has completed, so
* the command will be left pending.
*/
function onSuccess() {
if (!waitForLoad && response.isPending()) {
safaridriver.extension.commands.LOG_.info(
'Not expecting a new page load; returning');
response.resolve();
}
tab.on(safaridriver.message.Alert.TYPE, onAlert);
}
/**
* Handles command failures from the tab.
* @param {Error} e The failure reason.
*/
function onFailure(e) {
if (response.isPending()) {
safaridriver.extension.commands.LOG_.severe(
'Error while loading page; failing', e);
tab.removeListener(safaridriver.message.Load.TYPE, onLoad);
response.reject(e);
}
}
};
/**
* Updates the implicit wait setting for the given session.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
*/
safaridriver.extension.commands.implicitlyWait = function(command, session) {
session.setImplicitWait(
/** @type {number} */ (command.getParameter('ms')) || 0);
};
/**
* Updates the async script timeout setting for the given session.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
*/
safaridriver.extension.commands.setScriptTimeout = function(command, session) {
session.setScriptTimeout(
/** @type {number} */ (command.getParameter('ms')) || 0);
};
/**
* Sends a command to locate an element on the current page. This operation is
* subject to the implicit wait setting on the given session. When searching
* for a single element, the driver should poll the page until the element has
* been found, or this timeout expires before returning a NoSuchElement error.
* When searching for multiple elements, the driver should poll the page until
* at least one element has been found or this timeout has expired.
*
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!webdriver.promise.Promise} A promise that will be resolved when the
* operation has completed.
*/
safaridriver.extension.commands.findElement = function(command, session) {
var started;
var result = new webdriver.promise.Deferred();
session.getCommandTab().whenReady(findElement);
return result.promise;
function findElement() {
if (!goog.isDef(started)) {
started = goog.now();
}
return safaridriver.extension.commands.sendCommand(command, session).
then(checkResponse);
}
function checkResponse(response) {
var status = response['status'];
if (status !== bot.ErrorCode.SUCCESS) {
// The command failed from an irrecoverable error.
result.resolve(response);
return;
}
var value = response['value'];
var foundElement = goog.isDefAndNotNull(value) &&
(!goog.isArray(value) || !!value.length);
if (!foundElement &&
session.getImplicitWait() > 0 &&
goog.now() - started < session.getImplicitWait()) {
setTimeout(findElement, 100);
} else if (!value) {
var error = new bot.Error(bot.ErrorCode.NO_SUCH_ELEMENT,
'Could not find element: ' + JSON.stringify(command.getParameters()));
result.reject(error);
} else {
result.resolve(response);
}
}
};
/**
* Default amount of time, in milliseconds, to wait for a response to any
* commands sent to the injected script. This is set arbitarily high as we
* should never hit. It is used soley as a means of preventing hanging the
* client when something breaks inside the driver.
* @type {number}
* @const
* @private
*/
safaridriver.extension.commands.DEFAULT_COMMAND_TIMEOUT_ = 30000;
/**
* Sends a command to the provided session's current tab.
* @param {!safaridriver.Command} command The command object.
* @param {!(safaridriver.extension.Session|safaridriver.extension.Tab)}
* sessionOrTab Either the session or tab to send the command to. If given a
* session, the command will be sent to the tab the session is currently
* focused on.
* @param {number=} opt_additionalTimeout An optional amount of time, in
* milliseconds, to wait for a command response. This timeout is added to
* the default timeout applied to all commands.
* @return {!webdriver.promise.Promise} A promise that will be resolved with
* the command response.
*/
safaridriver.extension.commands.sendCommand = function(
command, sessionOrTab, opt_additionalTimeout) {
var timeout = (opt_additionalTimeout || 0) +
safaridriver.extension.commands.DEFAULT_COMMAND_TIMEOUT_;
var tab = sessionOrTab instanceof safaridriver.extension.Tab ?
/** @type {!safaridriver.extension.Tab} */ (sessionOrTab) :
/** @type {!safaridriver.extension.Session} */ (sessionOrTab).
getCommandTab();
return tab.send(command, timeout);
};
/**
* Changes focus to another window.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!webdriver.promise.Promise} A promise that will be resolved when the
* operation has completed.
*/
safaridriver.extension.commands.switchToWindow = function(command, session) {
var result = new webdriver.promise.Deferred();
var name = /** @type {string} */ (command.getParameter('name'));
var tab = session.getTab(name);
if (tab) {
switchToTab(tab);
return result.promise;
}
var tabIds = session.getTabIds();
safaridriver.extension.commands.LOG_.info(
'Window handle not found; collecting open window names');
var windowNames = goog.array.map(tabIds, function(tabId) {
var tab = session.getTab(tabId);
if (!tab) {
// The window was closed in the time it took us to ask it for its name.
// Hopefully, this will never happen.
return null;
}
return safaridriver.extension.commands.sendCommand(command, tab).
then(bot.response.checkResponse).
then(function(responseObj) {
return responseObj['value'];
});
});
webdriver.promise.fullyResolved(windowNames).then(function(windowNames) {
safaridriver.extension.commands.LOG_.info(
'Open window names: ' + JSON.stringify(windowNames));
var index = goog.array.findIndex(windowNames, function(windowName) {
return windowName === name;
});
if (index < 0) {
result.reject(new bot.Error(bot.ErrorCode.NO_SUCH_WINDOW,
'No such window: ' + name));
return;
}
var tab = session.getTab(tabIds[index]);
switchToTab(tab);
}, result.reject);
return result.promise;
function switchToTab(tab) {
// Switch back to the default content for the current tab before switching
// to the new tab.
try {
var currentTab = session.getCommandTab();
currentTab.whenReady(function() {
var switchToNullContent = new safaridriver.Command(
goog.string.getRandomString(),
webdriver.CommandName.SWITCH_TO_FRAME, {
'id': null
});
currentTab.send(switchToNullContent);
session.setCommandTab(/** @type {!safaridriver.extension.Tab} */ (tab));
result.resolve();
});
} catch (ex) {
// If we attempt to retrieve the current tab after it's been closed,
// we'll receive a NoSuchWindowError. When this happens, just ignore it
// and move along. Any other error should be reported to the user.
if (ex.code !== bot.ErrorCode.NO_SUCH_WINDOW) {
throw ex;
}
session.setCommandTab(/** @type {!safaridriver.extension.Tab} */ (tab));
result.resolve();
}
}
};
/**
* Sends a command that should target the currently selected window.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!webdriver.promise.Promise} A promise that will be resolved with
* the command response.
*/
safaridriver.extension.commands.sendWindowCommand = function(command, session) {
var handle = /** @type {string} */ (command.getParameter('windowHandle'));
var tab;
if (handle === 'current') {
tab = session.getCommandTab();
} else if (!(tab = session.getTab(handle))) {
throw new bot.Error(bot.ErrorCode.NO_SUCH_WINDOW,
'No such window: ' + handle);
}
return safaridriver.extension.commands.sendCommand(command, tab);
};
/**
* Sends a script-based command to the currently selected window.
* @param {!safaridriver.Command} command The command object.
* @param {!safaridriver.extension.Session} session The session object.
* @return {!webdriver.promise.Promise} A promise that will be resolved with
* the command response.
*/
safaridriver.extension.commands.executeAsyncScript = function(
command, session) {
// The async timeout is saved on the session, so embed it in the command to
// be sent to the injected script.
var timeout = session.getScriptTimeout();
command.setParameter('timeout', timeout);
return safaridriver.extension.commands.sendCommand(command, session, timeout);
};
/**
* Alert handling is not supported yet. To prevent tests from hanging, alerts
* are always immediately dimissed. This command handler, used for all of the
* alert commands, provides users with a friendly error for the unsupported
* feature.
* TODO: Fully support alerts.
* @see http://code.google.com/p/selenium/issues/detail?id=3862
*/
safaridriver.extension.commands.handleNoAlertsPresent = function() {
throw new bot.Error(bot.ErrorCode.NO_MODAL_DIALOG_OPEN,
'The SafariDriver does not support alert handling. To prevent tests ' +
'from handing when an alert is opened, they are always immediately ' +
'dismissed. For more information, see ' +
'http://code.google.com/p/selenium/issues/detail?id=3862');
};