// Licensed to the Software Freedom Conservancy (SFC) under one // or more contributor license agreements. See the NOTICE file // distributed with this work for additional information // regarding copyright ownership. The SFC licenses this file // to you 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 Atoms-based implementation of the webelement interface. */ goog.provide('webdriver.atoms.element'); goog.require('bot.Keyboard.Keys'); goog.require('bot.action'); goog.require('bot.dom'); goog.require('goog.array'); goog.require('goog.math.Coordinate'); goog.require('goog.style'); goog.require('webdriver.Key'); goog.require('webdriver.atoms.element.attribute'); /** * @param {!Element} element The element to use. * @return {boolean} Whether the element is checked or selected. */ webdriver.atoms.element.isSelected = function(element) { // Although this method looks unloved, its compiled form is used by // Chrome. if (!bot.dom.isSelectable(element)) { return false; } return bot.dom.isSelected(element); }; /** * @const * @deprecated Use webdriver.atoms.element.attribute.get() instead. */ webdriver.atoms.element.getAttribute = webdriver.atoms.element.attribute.get; /** * Get the location of the element in page space, if it's displayed. * * @param {!Element} element The element to get the location for. * @return {?goog.math.Rect} The bounding rectangle of the element. */ webdriver.atoms.element.getLocation = function(element) { if (!bot.dom.isShown(element)) { return null; } return goog.style.getBounds(element); }; /** * Scrolls the element into the client's view and returns its position * relative to the client viewport. If the element or region is too * large to fit in the view, it will be aligned to the top-left of the * container. * * The element should be attached to the current document. * * @param {!Element} elem The element to use. * @param {!goog.math.Rect=} opt_elemRegion The region relative to the element * to be scrolled into view. * @return {!goog.math.Coordinate} The coordinate of the element in client * space. */ webdriver.atoms.element.getLocationInView = function(elem, opt_elemRegion) { bot.action.scrollIntoView(elem, opt_elemRegion); var region = bot.dom.getClientRegion(elem, opt_elemRegion); return new goog.math.Coordinate(region.left, region.top); }; /** * @param {?Node} element The element to use. * @return {boolean} Whether the element is in the HEAD tag. * @private * @suppress {reportUnknownTypes} */ webdriver.atoms.element.isInHead_ = function(element) { while (element) { if (element.tagName && element.tagName.toLowerCase() == 'head') { return true; } try { element = element.parentNode; } catch (e) { // Fine. the DOM has dispeared from underneath us return false; } } return false; }; /** * @param {!Element} element The element to get the text from. * @return {string} The visible text or an empty string. */ webdriver.atoms.element.getText = function(element) { return bot.dom.getVisibleText(element); }; /** * Types keys on the given `element` with a virtual keyboard. Converts * special characters from the WebDriver JSON wire protocol to the appropriate * {@link bot.Keyboard.Key} value. * * @param {!Element} element The element to type upon. * @param {!Array.} keys The keys to type on the element. * @param {?bot.Keyboard=} opt_keyboard Keyboard to use; if not provided, * constructs one. * @param {boolean=} opt_persistModifiers Whether modifier keys should remain * pressed when this function ends. * @see bot.action.type * @see https://github.com/SeleniumHQ/selenium/wiki/JsonWireProtocol * @suppress {reportUnknownTypes} */ webdriver.atoms.element.type = function( element, keys, opt_keyboard, opt_persistModifiers) { var persistModifierKeys = !!opt_persistModifiers; function createSequenceRecord() { return {persist: persistModifierKeys, keys: []}; } /** * @type {!Array.<{persist: boolean, * keys: !Array.<(string|!bot.Keyboard.Key)>}>} */ var convertedSequences = []; /** * @type {{persist: boolean, * keys: !Array.<(string|!bot.Keyboard.Key)>}} */ var current = createSequenceRecord(); convertedSequences.push(current); goog.array.forEach(keys, function(sequence) { goog.array.forEach(sequence.split(''), function(key) { if (isWebDriverKey(key)) { var webdriverKey = webdriver.atoms.element.type.JSON_TO_KEY_MAP_[key]; // goog.isNull uses ==, which accepts undefined. if (webdriverKey === null) { // bot.action.type does not support a "null" key, so we have to // terminate the entire sequence to release modifier keys. If // we currently allow modifier key state to persist across key // sequences, we need to inject a dummy sequence that does not // persist state so every modifier key gets released. convertedSequences.push(current = createSequenceRecord()); if (persistModifierKeys) { current.persist = false; convertedSequences.push(current = createSequenceRecord()); } } else if (webdriverKey !== undefined) { current.keys.push(webdriverKey); } else { throw Error('Unsupported WebDriver key: \\u' + key.charCodeAt(0).toString(16)); } } else { // Handle common aliases. switch (key) { case '\n': current.keys.push(bot.Keyboard.Keys.ENTER); break; case '\t': current.keys.push(bot.Keyboard.Keys.TAB); break; case '\b': current.keys.push(bot.Keyboard.Keys.BACKSPACE); break; default: current.keys.push(key); break; } } }); }); goog.array.forEach(convertedSequences, function(sequence) { bot.action.type(element, sequence.keys, opt_keyboard, sequence.persist); }); /** * @param {!string|!bot.Keyboard.Key} c * @returns {!boolean} */ function isWebDriverKey(c) { return '\uE000' <= c && c <= '\uE03D'; } }; /** * Maps JSON wire protocol values to their {@link bot.Keyboard.Key} counterpart. * @private {!Object.} * @const */ webdriver.atoms.element.type.JSON_TO_KEY_MAP_ = {}; goog.scope(function() { var map = webdriver.atoms.element.type.JSON_TO_KEY_MAP_; var key = webdriver.Key; var botKey = bot.Keyboard.Keys; map[key.NULL] = null; map[key.BACK_SPACE] = botKey.BACKSPACE; map[key.TAB] = botKey.TAB; map[key.RETURN] = botKey.ENTER; // This not correct, but most browsers will do the right thing. map[key.ENTER] = botKey.ENTER; map[key.SHIFT] = botKey.SHIFT; map[key.CONTROL] = botKey.CONTROL; map[key.ALT] = botKey.ALT; map[key.PAUSE] = botKey.PAUSE; map[key.ESCAPE] = botKey.ESC; map[key.SPACE] = botKey.SPACE; map[key.PAGE_UP] = botKey.PAGE_UP; map[key.PAGE_DOWN] = botKey.PAGE_DOWN; map[key.END] = botKey.END; map[key.HOME] = botKey.HOME; map[key.LEFT] = botKey.LEFT; map[key.UP] = botKey.UP; map[key.RIGHT] = botKey.RIGHT; map[key.DOWN] = botKey.DOWN; map[key.INSERT] = botKey.INSERT; map[key.DELETE] = botKey.DELETE; map[key.SEMICOLON] = botKey.SEMICOLON; map[key.EQUALS] = botKey.EQUALS; map[key.NUMPAD0] = botKey.NUM_ZERO; map[key.NUMPAD1] = botKey.NUM_ONE; map[key.NUMPAD2] = botKey.NUM_TWO; map[key.NUMPAD3] = botKey.NUM_THREE; map[key.NUMPAD4] = botKey.NUM_FOUR; map[key.NUMPAD5] = botKey.NUM_FIVE; map[key.NUMPAD6] = botKey.NUM_SIX; map[key.NUMPAD7] = botKey.NUM_SEVEN; map[key.NUMPAD8] = botKey.NUM_EIGHT; map[key.NUMPAD9] = botKey.NUM_NINE; map[key.MULTIPLY] = botKey.NUM_MULTIPLY; map[key.ADD] = botKey.NUM_PLUS; map[key.SUBTRACT] = botKey.NUM_MINUS; map[key.DECIMAL] = botKey.NUM_PERIOD; map[key.DIVIDE] = botKey.NUM_DIVISION; map[key.SEPARATOR] = botKey.SEPARATOR; map[key.F1] = botKey.F1; map[key.F2] = botKey.F2; map[key.F3] = botKey.F3; map[key.F4] = botKey.F4; map[key.F5] = botKey.F5; map[key.F6] = botKey.F6; map[key.F7] = botKey.F7; map[key.F8] = botKey.F8; map[key.F9] = botKey.F9; map[key.F10] = botKey.F10; map[key.F11] = botKey.F11; map[key.F12] = botKey.F12; map[key.META] = botKey.META; }); // goog.scope