/* * Copyright 2004 ThoughtWorks, Inc * * 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. * */ storedVars = new Object(); function Selenium(browserbot) { this.browserbot = browserbot; this.optionLocatorFactory = new OptionLocatorFactory(); this.page = function() { return browserbot.getCurrentPage(); }; } Selenium.createForFrame = function(frame) { return new Selenium(BrowserBot.createForFrame(frame)); }; /* * Reset the browserbot when an error occurs.. */ Selenium.prototype.reset = function() { storedVars = new Object(); this.browserbot.selectWindow("null"); }; /* * Click on the located element, and attach a callback to notify * when the page is reloaded. */ Selenium.prototype.doModalDialogTest = function(returnValue) { this.browserbot.doModalDialogTest(returnValue); }; /* * Click on the located element, and attach a callback to notify * when the page is reloaded. */ Selenium.prototype.doClick = function(locator) { var element = this.page().findElement(locator); this.page().clickElement(element); }; /** * Overwrite the text in the located text element. * TODO fail if it can't be typed into. */ Selenium.prototype.doType = function(locator, newText) { var element = this.page().findElement(locator); this.page().replaceText(element, newText); }; Selenium.prototype.findToggleButton = function(locator) { var element = this.page().findElement(locator); if (element.checked == null) { Assert.fail("Element " + locator + " is not a toggle-button."); } return element; } /** * Check a toggle-button. */ Selenium.prototype.doCheck = function(locator) { this.findToggleButton(locator).checked = true; }; /** * Uncheck a toggle-button. */ Selenium.prototype.doUncheck = function(locator) { this.findToggleButton(locator).checked = false; }; /** * Select the option from the located select element. */ Selenium.prototype.doSelect = function(locator, optionLocator) { var element = this.page().findElement(locator); if (!("options" in element)) { throw new SeleniumError("Specified element is not a Select (has no options)"); } var locator = this.optionLocatorFactory.fromLocatorString(optionLocator); var option = locator.findOption(element); this.page().selectOption(element, option); }; /* * Open the browser to a new location. */ Selenium.prototype.doOpen = function(newLocation) { this.browserbot.openLocation(newLocation); return SELENIUM_PROCESS_WAIT; }; /* * Select the named window to be the active window. */ Selenium.prototype.doSelectWindow = function(windowName) { this.browserbot.selectWindow(windowName); }; /* * Instruct Selenium to click Cancel on the next confirm dialog it encounters */ Selenium.prototype.doChooseCancelOnNextConfirmation = function() { this.browserbot.cancelNextConfirmation(); }; /* * Instruct Selenium what to answear on the next prompt dialog it encounters */ Selenium.prototype.doAnswerOnNextPrompt = function(answer) { this.browserbot.setNextPromptResult(answer); }; /* * Simulate the browser back button */ Selenium.prototype.doGoBack = function() { this.page().goBack(); }; /* * Close the browser window or tab */ Selenium.prototype.doClose = function() { this.page().close(); }; /* * Explicitly fire an event */ Selenium.prototype.doFireEvent = function(locator, event) { var element = this.page().findElement(locator); triggerEvent(element, event, false); }; /* * Get an alert message, or fail if there were no alerts. */ Selenium.prototype.getAlert = function() { if (!this.browserbot.hasAlerts()) { Assert.fail("There were no alerts"); } return this.browserbot.getNextAlert(); }; /* * Get a confirmation message, or fail if there were no confirmations. */ Selenium.prototype.getConfirmation = function() { if (!this.browserbot.hasConfirmations()) { Assert.fail("There were no confirmations"); } return this.browserbot.getNextConfirmation(); }; /* * Get a prompt message, or fail if there were no prompts. */ Selenium.prototype.getPrompt = function() { if (! this.browserbot.hasPrompts()) { Assert.fail("There were no prompts"); } return this.browserbot.getNextPrompt(); }; /* * Get the location of the current page. */ Selenium.prototype.getAbsoluteLocation = function() { return this.page().location; }; /* * Verify the location of the current page ends with the expected location. * If a querystring is provided, this is checked as well. */ Selenium.prototype.assertLocation = function(expectedLocation) { var docLocation = this.page().location; var searchPos = expectedLocation.lastIndexOf('?'); if (searchPos == -1) { Assert.matches('*' + expectedLocation, docLocation.pathname); } else { var expectedPath = expectedLocation.substring(0, searchPos); Assert.matches('*' + expectedPath, docLocation.pathname); var expectedQueryString = expectedLocation.substring(searchPos); Assert.equals(expectedQueryString, docLocation.search); } }; /* * Get the title of the current page. */ Selenium.prototype.getTitle = function() { return this.page().title(); }; /* * Get the (trimmed) value of a form element. * This is used to generate assertValue, verifyValue, ... */ Selenium.prototype.getValue = function(locator) { var element = this.page().findElement(locator) return getInputValue(element).trim(); } /** * Get the (trimmed) text of a form element. * This is used to generate assertText, verifyText, ... */ Selenium.prototype.getText = function(locator) { var element = this.page().findElement(locator); return getText(element).trim(); }; /** * Assert that a toggle-button is checked. */ Selenium.prototype.assertChecked = function(locator) { var element = this.page().findElement(locator); if (element.checked == null) { Assert.fail("Element " + locator + " is not a toggle-button."); } if (! element.checked) { Assert.fail("Element " + locator + " is not checked."); } }; /** * Assert that a toggle-button is NOT checked. */ Selenium.prototype.assertNotChecked = function(locator) { var element = this.page().findElement(locator); if (element.checked == null) { Assert.fail("Element " + locator + " is not a toggle-button."); } if (element.checked) { Assert.fail("Element " + locator + " is checked."); } }; /* * Return the text for a single cell within an HTML table. * The table locator syntax is table.row.column. */ Selenium.prototype.getTable = function(tableLocator) { // This regular expression matches "tableName.row.column" // For example, "mytable.3.4" pattern = /(.*)\.(\d+)\.(\d+)/; if(!pattern.test(tableLocator)) { throw new SeleniumError("Invalid target format. Correct format is tableName.rowNum.columnNum"); } pieces = tableLocator.match(pattern); tableName = pieces[1]; row = pieces[2]; col = pieces[3]; var table = this.page().findElement(tableName); if (row > table.rows.length) { Assert.fail("Cannot access row " + row + " - table has " + table.rows.length + " rows"); } else if (col > table.rows[row].cells.length) { Assert.fail("Cannot access column " + col + " - table row has " + table.rows[row].cells.length + " columns"); } else { actualContent = getText(table.rows[row].cells[col]); return actualContent.trim(); } }; /** * Verify that the selected option satisfies the option locator. */ Selenium.prototype.assertSelected = function(target, optionLocator) { var element = this.page().findElement(target); var locator = this.optionLocatorFactory.fromLocatorString(optionLocator); locator.assertSelected(element); }; String.prototype.parseCSV = function() { var values = this.replace(/\\,/g, "\n").split(","); // Restore escaped commas for (var i = 0; i < values.length; i++) { values[i] = values[i].replace(/\n/g, ",").trim(); } return values; }; /** * Verify the label of all of the options in the drop=down. */ Selenium.prototype.assertSelectOptions = function(target, options) { var element = this.page().findElement(target); var expectedOptionLabels = options.parseCSV(); Assert.equals("Wrong number of options", expectedOptionLabels.length, element.options.length); for (var i = 0; i < element.options.length; i++) { Assert.matches(expectedOptionLabels[i], element.options[i].text); } }; /** * Get the value of an element attribute. The syntax for returning an element attribute * is <element-locator>@attribute-name. Used to generate assert, verify, assertNot... */ Selenium.prototype.getAttribute = function(target) { return this.page().findAttribute(target); }; /* * Asserts that the specified text is present in the page content. */ Selenium.prototype.assertTextPresent = function(expectedText) { var allText = this.page().bodyText(); if(allText == "") { Assert.fail("Page text not found"); } else if(allText.indexOf(expectedText) == -1) { Assert.fail("'" + expectedText + "' not found in page text."); } }; /* * Asserts that the specified text is NOT present in the page content. */ Selenium.prototype.assertTextNotPresent = function(unexpectedText) { var allText = this.page().bodyText(); if(allText == "") { Assert.fail("Page text not found"); } else if(allText.indexOf(unexpectedText) != -1) { Assert.fail("'" + unexpectedText + "' was found in page text."); } }; /* * Asserts that the specified element can be found. */ Selenium.prototype.assertElementPresent = function(locator) { try { this.page().findElement(locator); } catch (e) { Assert.fail("Element " + locator + " not found."); } }; /* * Asserts that the specified element cannot be found. */ Selenium.prototype.assertElementNotPresent = function(locator) { try { this.page().findElement(locator); } catch (e) { return; } Assert.fail("Element " + locator + " found."); }; /* * Asserts that the specified element is visible */ Selenium.prototype.assertVisible = function(locator) { var element; try { element = this.page().findElement(locator); } catch (e) { Assert.fail("Element " + locator + " not present."); } if (! this.isVisible(element)) { Assert.fail("Element " + locator + " not visible."); } }; /* * Asserts that the specified element is visible */ Selenium.prototype.assertNotVisible = function(locator) { var element; try { element = this.page().findElement(locator); } catch (e) { return; } if (this.isVisible(element)) { Assert.fail("Element " + locator + " is visible."); } }; Selenium.prototype.isVisible = function(element) { var visibility = this.getEffectiveStyleProperty(element, "visibility"); var isDisplayed = this.isDisplayed(element); return (visibility != "hidden" && isDisplayed); }; Selenium.prototype.getEffectiveStyleProperty = function(element, property) { var effectiveStyle = this.getEffectiveStyle(element); var propertyValue = effectiveStyle[property]; if (propertyValue == 'inherit' && element.parentNode.style) { return this.getEffectiveStyleProperty(element.parentNode, property); } return propertyValue; }; Selenium.prototype.isDisplayed = function(element) { var display = this.getEffectiveStyleProperty(element, "display"); if (display == "none") return false; if (element.parentNode.style) { return this.isDisplayed(element.parentNode); } return true; }; Selenium.prototype.getEffectiveStyle = function(element) { if (element.style == undefined) { return undefined; // not a styled element } var window = this.browserbot.getContentWindow(); if (window.getComputedStyle) { // DOM-Level-2-CSS return window.getComputedStyle(element, null); } if (element.currentStyle) { // non-standard IE alternative return element.currentStyle; // TODO: this won't really work in a general sense, as // currentStyle is not identical to getComputedStyle() // ... but it's good enough for "visibility" } throw new SeleniumError("cannot determine effective stylesheet in this browser"); }; /** * Asserts that the specified element accepts user input visible */ Selenium.prototype.assertEditable = function(locator) { var element = this.page().findElement(locator); if (element.value == undefined) { Assert.fail("Element " + locator + " is not an input."); } if (element.disabled) { Assert.fail("Element " + locator + " is disabled."); } }; /** * Asserts that the specified element does not accept user input */ Selenium.prototype.assertNotEditable = function(locator) { var element = this.page().findElement(locator); if (element.value == undefined) { return; // not an input } if (element.disabled == false) { Assert.fail("Element " + locator + " is editable."); } }; /* * Return all buttons on the screen. */ Selenium.prototype.getAllButtons = function() { return this.page().getAllButtons(); }; /* * Return all links on the screen. */ Selenium.prototype.getAllLinks = function() { return this.page().getAllLinks(); }; /* * Return all fields on the screen. */ Selenium.prototype.getAllFields = function() { return this.page().getAllFields(); }; /* * Set the context for the current Test */ Selenium.prototype.doContext = function(context) { return this.page().setContext(context); }; /* * Store the value of a form input in a variable */ Selenium.prototype.doStoreValue = function(target, varName) { if (!varName) { // Backward compatibility mode: read the ENTIRE text of the page // and stores it in a variable with the name of the target value = this.page().bodyText(); storedVars[target] = value; return; } var element = this.page().findElement(target); storedVars[varName] = getInputValue(element); }; /* * Store the text of an element in a variable */ Selenium.prototype.doStoreText = function(target, varName) { var element = this.page().findElement(target); storedVars[varName] = getText(element); }; /* * Store the value of an element attribute in a variable */ Selenium.prototype.doStoreAttribute = function(target, varName) { storedVars[varName] = this.page().findAttribute(target); }; /* * Store the result of a literal value */ Selenium.prototype.doStore = function(value, varName) { storedVars[varName] = value; }; /* * Wait for the target to have the specified value by polling. * The polling is done in TestLoop.kickoffNextCommandExecution() */ Selenium.prototype.doWaitForValue = function (target, value) { var e = this.page().findElement(target); testLoop.waitForCondition = function () { return (e.value == value); }; }; /** * Evaluate a parameter, performing javascript evaluation and variable substitution. * If the string matches the pattern "javascript{ ... }", evaluate the string between the braces. */ Selenium.prototype.preprocessParameter = function(value) { var match = value.match(/^javascript\{(.+)\}$/); if (match && match[1]) { return eval(match[1]).toString(); } return this.replaceVariables(value); }; /* * Search through str and replace all variable references ${varName} with their * value in storedVars. */ Selenium.prototype.replaceVariables = function(str) { var stringResult = str; // Find all of the matching variable references var match = stringResult.match(/\$\{\w+\}/g); if (!match) { return stringResult; } // For each match, lookup the variable value, and replace if found for (var i = 0; match && i < match.length; i++) { var variable = match[i]; // The replacement variable, with ${} var name = variable.substring(2, variable.length - 1); // The replacement variable without ${} var replacement = storedVars[name]; if (replacement != undefined) { stringResult = stringResult.replace(variable, replacement); } } return stringResult; }; /** * Factory for creating "Option Locators". * An OptionLocator is an object for dealing with Select options (e.g. for * finding a specified option, or asserting that the selected option of * Select element matches some condition. * The type of locator returned by the factory depends on the locator string: * label=<exp> (OptionLocatorByLabel) * value=<exp> (OptionLocatorByValue) * index=<exp> (OptionLocatorByIndex) * id=<exp> (OptionLocatorById) * <exp> (default is OptionLocatorByLabel). */ function OptionLocatorFactory() { } OptionLocatorFactory.prototype.fromLocatorString = function(locatorString) { var locatorType = 'label'; var locatorValue = locatorString; // If there is a locator prefix, use the specified strategy var result = locatorString.match(/^([a-zA-Z]+)=(.*)/); if (result) { locatorType = result[1]; locatorValue = result[2]; } if (this.optionLocators == undefined) { this.registerOptionLocators(); } if (this.optionLocators[locatorType]) { return new this.optionLocators[locatorType](locatorValue); } throw new SeleniumError("Unkown option locator type: " + locatorType); }; /** * To allow for easy extension, all of the option locators are found by * searching for all methods of OptionLocatorFactory.prototype that start * with "OptionLocatorBy". * TODO: Consider using the term "Option Specifier" instead of "Option Locator". */ OptionLocatorFactory.prototype.registerOptionLocators = function() { this.optionLocators={}; for (var functionName in this) { var result = /OptionLocatorBy([A-Z].+)$/.exec(functionName); if (result != null) { var locatorName = result[1].lcfirst(); this.optionLocators[locatorName] = this[functionName]; } } }; /** * OptionLocator for options identified by their labels. */ OptionLocatorFactory.prototype.OptionLocatorByLabel = function(label) { this.label = label; this.labelMatcher = new PatternMatcher(this.label); this.findOption = function(element) { for (var i = 0; i < element.options.length; i++) { if (this.labelMatcher.matches(element.options[i].text)) { return element.options[i]; } } throw new SeleniumError("Option with label '" + this.label + "' not found"); }; this.assertSelected = function(element) { var selectedLabel = element.options[element.selectedIndex].text; Assert.matches(this.label, selectedLabel); }; }; /** * OptionLocator for options identified by their values. */ OptionLocatorFactory.prototype.OptionLocatorByValue = function(value) { this.value = value; this.valueMatcher = new PatternMatcher(this.value); this.findOption = function(element) { for (var i = 0; i < element.options.length; i++) { if (this.valueMatcher.matches(element.options[i].value)) { return element.options[i]; } } throw new SeleniumError("Option with value '" + this.value + "' not found"); }; this.assertSelected = function(element) { var selectedValue = element.options[element.selectedIndex].value; Assert.matches(this.value, selectedValue); }; }; /** * OptionLocator for options identified by their index. */ OptionLocatorFactory.prototype.OptionLocatorByIndex = function(index) { this.index = Number(index); if (isNaN(this.index) || this.index < 0) { throw new SeleniumError("Illegal Index: " + index); } this.findOption = function(element) { if (element.options.length <= this.index) { throw new SeleniumError("Index out of range. Only " + element.options.length + " options available"); } return element.options[this.index]; }; this.assertSelected = function(element) { Assert.equals(this.index, element.selectedIndex); }; }; /** * OptionLocator for options identified by their id. */ OptionLocatorFactory.prototype.OptionLocatorById = function(id) { this.id = id; this.idMatcher = new PatternMatcher(this.id); this.findOption = function(element) { for (var i = 0; i < element.options.length; i++) { if (this.idMatcher.matches(element.options[i].id)) { return element.options[i]; } } throw new SeleniumError("Option with id '" + this.id + "' not found"); }; this.assertSelected = function(element) { var selectedId = element.options[element.selectedIndex].id; Assert.matches(this.id, selectedId); }; };