diff --git a/apps/hello/admin.py b/apps/hello/admin.py index 8c38f3f3da..16184ce333 100644 --- a/apps/hello/admin.py +++ b/apps/hello/admin.py @@ -1,3 +1,16 @@ from django.contrib import admin +from apps.hello.models import Info + # Register your models here. + +class InfoAdm(admin.StackedInline): + model = Info + extra = 2 + +class AdminInfo(admin.ModelAdmin): + pass + +admin.site.register(Info, AdminInfo) + + diff --git a/apps/hello/fixtures/initial_data.json b/apps/hello/fixtures/initial_data.json new file mode 100644 index 0000000000..d85b1086f9 --- /dev/null +++ b/apps/hello/fixtures/initial_data.json @@ -0,0 +1 @@ +[{"pk": 1, "model": "hello.info", "fields": {"info_skype": "LarryWall", "info_email": "LarryWall@perl.com", "info_firstname": "Larry", "info_other": "Larry Wall (/w\u0254\u02d0l/; born September 27, 1954) is a computer programmer and author, most widely known as the creator of the Perl programming language.", "info_jabber": "Laryy@jabber", "info_birth": "1974-10-27", "info_bio": "Larry grew up in south Los Angeles and then Bremerton, Washington, before starting higher education at Seattle Pacific University in 1976, majoring in chemistry and music and later Pre-med with a hiatus of several years working in the university's computing center before being graduated with a self-styled bachelor's degree in Natural and Artificial Languages.[1]\r\n\r\nWhile in graduate school at University of California, Berkeley, Wall and his wife were studying linguistics with the intention afterwards of finding an unwritten language, perhaps in Africa, and creating a writing system for it. They would then use this new writing system to translate various texts into the language, among them the Bible. Due to health reasons these plans were cancelled, and they remained in the United States, where Larry instead joined the NASA Jet Propulsion Laboratory after he finished graduate school.", "info_lastname": "Wall"}}] \ No newline at end of file diff --git a/apps/hello/models.py b/apps/hello/models.py index 71a8362390..44895598c4 100644 --- a/apps/hello/models.py +++ b/apps/hello/models.py @@ -1,3 +1,21 @@ from django.db import models # Create your models here. +class Info(models.Model): + class Meta: + db_table = "info" + + info_firstname = models.CharField(max_length=20, verbose_name='First Name') + info_lastname = models.CharField(max_length=20,verbose_name='Last Name') + info_bio = models.TextField(verbose_name='Bio') + info_email = models.EmailField(verbose_name='e-mail') + info_birth = models.DateField(blank = True, null = True) + info_jabber = models.CharField(max_length=30,verbose_name='Jabber') + info_other = models.TextField(verbose_name='Other Info') + info_skype = models.CharField(max_length=15,verbose_name='Skype') + + + + +def dumps(value): + return json.dumps(value,default=lambda o:None) \ No newline at end of file diff --git a/apps/hello/tests.py b/apps/hello/tests.py index 04edde2a29..a57844dc6e 100644 --- a/apps/hello/tests.py +++ b/apps/hello/tests.py @@ -1,6 +1,54 @@ from django.test import TestCase +from apps.hello.models import Info +from apps.selenium import webdriver +from apps.hello.views import show_info +from django.http import HttpRequest +import unittest -# Create your tests here. -class SomeTests(TestCase): - def test_math(self): - assert(2+2==5) +class DataOutTest(unittest.TestCase): + + def setUp(self): + self.browser = webdriver.Firefox() + + def tearDown(self): + self.browser.quit() + + def testTitle(self): + self.browser.get('http://localhost:8000/task/info/') + self.assertIn('InfoPage', self.browser.title) + +class ViewTest(TestCase): + + def test_root_url_resolves_to_home_page_view(self): + found = resolve('/') + self.assertEqual(found.func, show_info) + + + def test_home_page_returns_correct_html(self): + request = HttpRequest() #1 + response = show_info(request) #2 + self.assertTrue(response.content.startswith(b'')) #3 + self.assertIn(b'InfoPage', response.content) #4 + self.assertTrue(response.content.endswith(b'')) #5 + + + +class DataTest(TestCase): + def setUp(self): + + Info.objects.create(info_firstname ="Buddy", + info_lastname ="Budd", + info_bio = "Hi there", + info_email ="mail@mail.com", + info_birth = "1967-05-06", + info_jabber = "dont have", + info_other = "nothing", + info_skype= "nothing") + def test_1(self): + ''' will error on assertion''' + Buddy = Info.objects.get(info_firstname="Buddy") + self.assertEqual(Buddy.info_firstname, "Budd") + + +if __name__ == '__main__': #7 + unittest.main(warnings='ignore') \ No newline at end of file diff --git a/apps/hello/urls.py b/apps/hello/urls.py new file mode 100644 index 0000000000..236c2d0c92 --- /dev/null +++ b/apps/hello/urls.py @@ -0,0 +1,19 @@ +from django.conf.urls import patterns, include, url +from apps.hello.views import show_info_all, get_messages,show_info + +urlpatterns = patterns('', + # Examples: + # url(r'^$', 'firstapp.views.home', name='home'), + # url(r'^blog/', include('blog.urls')), + + + + url(r'^messages/', get_messages), + url(r'^info_all/', show_info_all), + url(r'^info/(?P\d+)/$', show_info), + + + + + +) \ No newline at end of file diff --git a/apps/hello/views.py b/apps/hello/views.py index 91ea44a218..7c53b78e82 100644 --- a/apps/hello/views.py +++ b/apps/hello/views.py @@ -1,3 +1,21 @@ from django.shortcuts import render +from apps.hello.models import Info +from django.shortcuts import render_to_response + +from django.contrib import messages + # Create your views here. +def show_info_all(request, id=1): + allinfo = Info.objects.all() + db = Info._meta.fields + print db + return render_to_response('info.html',{'allinfo': allinfo}) + +def show_info(request, id = "1"): + return render_to_response('info.html',{'allinfo': Info.objects.filter(pk = id)}) + +def get_messages(request): + infos = Info.objects.all() + render_to_response('messages.html',{'message': infos} ) + diff --git a/apps/selenium/__init__.py b/apps/selenium/__init__.py new file mode 100644 index 0000000000..db99ab7b67 --- /dev/null +++ b/apps/selenium/__init__.py @@ -0,0 +1,18 @@ +# Copyright 2008-2013 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. + +from selenium import selenium + + +__version__ = "2.42.1" diff --git a/apps/selenium/common/__init__.py b/apps/selenium/common/__init__.py new file mode 100644 index 0000000000..5204aa42b3 --- /dev/null +++ b/apps/selenium/common/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008-2010 WebDriver committers +# Copyright 2008-2010 Google 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. + +from . import exceptions diff --git a/apps/selenium/common/exceptions.py b/apps/selenium/common/exceptions.py new file mode 100644 index 0000000000..656c02d03e --- /dev/null +++ b/apps/selenium/common/exceptions.py @@ -0,0 +1,214 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google 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. + +""" +Exceptions that may happen in all the webdriver code. +""" + +class WebDriverException(Exception): + """ + Base webdriver exception. + """ + + def __init__(self, msg=None, screen=None, stacktrace=None): + self.msg = msg + self.screen = screen + self.stacktrace = stacktrace + + def __str__(self): + exception_msg = "Message: %s " % repr(self.msg) + if self.screen is not None: + exception_msg = "%s; Screenshot: available via screen " \ + % exception_msg + if self.stacktrace is not None: + exception_msg = "%s; Stacktrace: %s " \ + % (exception_msg, str("\n" + "\n".join(self.stacktrace))) + return exception_msg + +class ErrorInResponseException(WebDriverException): + """ + Thrown when an error has occurred on the server side. + + This may happen when communicating with the firefox extension + or the remote driver server. + """ + def __init__(self, response, msg): + WebDriverException.__init__(self, msg) + self.response = response + +class InvalidSwitchToTargetException(WebDriverException): + """ + Thrown when frame or window target to be switched doesn't exist. + """ + pass + +class NoSuchFrameException(InvalidSwitchToTargetException): + """ + Thrown when frame target to be switched doesn't exist. + """ + pass + +class NoSuchWindowException(InvalidSwitchToTargetException): + """ + Thrown when window target to be switched doesn't exist. + + To find the current set of active window handles, you can get a list + of the active window handles in the following way:: + + print driver.window_handles + + """ + pass + +class NoSuchElementException(WebDriverException): + """ + Thrown when element could not be found. + + If you encounter this exception, you may want to check the following: + * Check your selector used in your find_by... + * Element may not yet be on the screen at the time of the find operation, + (webpage is still loading) see selenium.webdriver.support.wait.WebDriverWait() + for how to write a wait wrapper to wait for an element to appear. + """ + pass + +class NoSuchAttributeException(WebDriverException): + """ + Thrown when the attribute of element could not be found. + + You may want to check if the attribute exists in the particular browser you are + testing against. Some browsers may have different property names for the same + property. (IE8's .innerText vs. Firefox .textContent) + """ + pass + +class StaleElementReferenceException(WebDriverException): + """ + Thrown when a reference to an element is now "stale". + + Stale means the element no longer appears on the DOM of the page. + + + Possible causes of StaleElementReferenceException include, but not limited to: + * You are no longer on the same page, or the page may have refreshed since the element + was located. + * The element may have been removed and re-added to the screen, since it was located. + Such as an element being relocated. + This can happen typically with a javascript framework when values are updated and the + node is rebuilt. + * Element may have been inside an iframe or another context which was refreshed. + """ + pass + +class InvalidElementStateException(WebDriverException): + """ + """ + pass + +class UnexpectedAlertPresentException(WebDriverException): + """ + Thrown when an unexpected alert is appeared. + + Usually raised when when an expected modal is blocking webdriver form executing any + more commands. + """ + pass + +class NoAlertPresentException(WebDriverException): + """ + Thrown when switching to no presented alert. + + This can be caused by calling an operation on the Alert() class when an alert is + not yet on the screen. + """ + pass + +class ElementNotVisibleException(InvalidElementStateException): + """ + Thrown when an element is present on the DOM, but + it is not visible, and so is not able to be interacted with. + + Most commonly encountered when trying to click or read text + of an element that is hidden from view. + """ + pass + +class ElementNotSelectableException(InvalidElementStateException): + """ + Thrown when trying to select an unselectable element. + + For example, selecting a 'script' element. + """ + pass + +class InvalidCookieDomainException(WebDriverException): + """ + Thrown when attempting to add a cookie under a different domain + than the current URL. + """ + pass + +class UnableToSetCookieException(WebDriverException): + """ + Thrown when a driver fails to set a cookie. + """ + pass + +class RemoteDriverServerException(WebDriverException): + """ + """ + pass + +class TimeoutException(WebDriverException): + """ + Thrown when a command does not complete in enough time. + """ + pass + +class MoveTargetOutOfBoundsException(WebDriverException): + """ + Thrown when the target provided to the `ActionsChains` move() + method is invalid, i.e. out of document. + """ + pass + +class UnexpectedTagNameException(WebDriverException): + """ + Thrown when a support class did not get an expected web element. + """ + pass + +class InvalidSelectorException(NoSuchElementException): + """ + Thrown when the selector which is used to find an element does not return + a WebElement. Currently this only happens when the selector is an xpath + expression and it is either syntactically invalid (i.e. it is not a + xpath expression) or the expression does not select WebElements + (e.g. "count(//input)"). + """ + pass + +class ImeNotAvailableException(WebDriverException): + """ + Thrown when IME support is not available. This exception is thrown for every IME-related + method call if IME support is not available on the machine. + """ + pass + +class ImeActivationFailedException(WebDriverException): + """ + Thrown when activating an IME engine has failed. + """ + pass diff --git a/apps/selenium/selenium.py b/apps/selenium/selenium.py new file mode 100644 index 0000000000..7d1b928bd3 --- /dev/null +++ b/apps/selenium/selenium.py @@ -0,0 +1,2119 @@ + +""" +Copyright 2011 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. +""" +from __future__ import unicode_literals + +__docformat__ = "restructuredtext en" + +try: + import http.client as http_client +except ImportError: + import httplib as http_client + +try: + import urllib.parse as urllib_parse +except ImportError: + import urllib as urllib_parse + +class selenium(object): + """ + Defines an object that runs Selenium commands. + + **Element Locators** + + Element Locators tell Selenium which HTML element a command refers to. + The format of a locator is: + + \ *locatorType*\ **=**\ \ *argument* + + + We support the following strategies for locating elements: + + + * \ **identifier**\ =\ *id*: + Select the element with the specified @id attribute. If no match is + found, select the first element whose @name attribute is \ *id*. + (This is normally the default; see below.) + * \ **id**\ =\ *id*: + Select the element with the specified @id attribute. + * \ **name**\ =\ *name*: + Select the first element with the specified @name attribute. + + * username + * name=username + + + The name may optionally be followed by one or more \ *element-filters*, separated from the name by whitespace. If the \ *filterType* is not specified, \ **value**\ is assumed. + + * name=flavour value=chocolate + + + * \ **dom**\ =\ *javascriptExpression*: + + Find an element by evaluating the specified string. This allows you to traverse the HTML Document Object + Model using JavaScript. Note that you must not return a value in this string; simply make it the last expression in the block. + + * dom=document.forms['myForm'].myDropdown + * dom=document.images[56] + * dom=function foo() { return document.links[1]; }; foo(); + + + * \ **xpath**\ =\ *xpathExpression*: + Locate an element using an XPath expression. + + * xpath=//img[@alt='The image alt text'] + * xpath=//table[@id='table1']//tr[4]/td[2] + * xpath=//a[contains(@href,'#id1')] + * xpath=//a[contains(@href,'#id1')]/@class + * xpath=(//table[@class='stylee'])//th[text()='theHeaderText']/../td + * xpath=//input[@name='name2' and @value='yes'] + * xpath=//\*[text()="right"] + + + * \ **link**\ =\ *textPattern*: + Select the link (anchor) element which contains text matching the + specified \ *pattern*. + + * link=The link text + + + * \ **css**\ =\ *cssSelectorSyntax*: + Select the element using css selectors. Please refer to CSS2 selectors, CSS3 selectors for more information. You can also check the TestCssLocators test in the selenium test suite for an example of usage, which is included in the downloaded selenium core package. + + * css=a[href="#id3"] + * css=span#firstChild + span + + + Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). + + * \ **ui**\ =\ *uiSpecifierString*: + Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the Selenium UI-Element Reference for more details. + + * ui=loginPages::loginButton() + * ui=settingsPages::toggle(label=Hide Email) + * ui=forumPages::postBody(index=2)//a[2] + + + + + + Without an explicit locator prefix, Selenium uses the following default + strategies: + + + * \ **dom**\ , for locators starting with "document." + * \ **xpath**\ , for locators starting with "//" + * \ **identifier**\ , otherwise + + **Element Filters** + + Element filters can be used with a locator to refine a list of candidate elements. They are currently used only in the 'name' element-locator. + + Filters look much like locators, ie. + + \ *filterType*\ **=**\ \ *argument* + + Supported element-filters are: + + \ **value=**\ \ *valuePattern* + + + Matches elements based on their values. This is particularly useful for refining a list of similarly-named toggle-buttons. + + \ **index=**\ \ *index* + + + Selects a single element based on its position in the list (offset from zero). + + **String-match Patterns** + + Various Pattern syntaxes are available for matching string values: + + + * \ **glob:**\ \ *pattern*: + Match a string against a "glob" (aka "wildmat") pattern. "Glob" is a + kind of limited regular-expression syntax typically used in command-line + shells. In a glob pattern, "\*" represents any sequence of characters, and "?" + represents any single character. Glob patterns match against the entire + string. + * \ **regexp:**\ \ *regexp*: + Match a string using a regular-expression. The full power of JavaScript + regular-expressions is available. + * \ **regexpi:**\ \ *regexpi*: + Match a string using a case-insensitive regular-expression. + * \ **exact:**\ \ *string*: + + Match a string exactly, verbatim, without any of that fancy wildcard + stuff. + + + + If no pattern prefix is specified, Selenium assumes that it's a "glob" + pattern. + + + + For commands that return multiple values (such as verifySelectOptions), + the string being matched is a comma-separated list of the return values, + where both commas and backslashes in the values are backslash-escaped. + When providing a pattern, the optional matching syntax (i.e. glob, + regexp, etc.) is specified once, as usual, at the beginning of the + pattern. + + + """ + + ### This part is hard-coded in the XSL + def __init__(self, host, port, browserStartCommand, browserURL): + self.host = host + self.port = port + self.browserStartCommand = browserStartCommand + self.browserURL = browserURL + self.sessionId = None + self.extensionJs = "" + + def setExtensionJs(self, extensionJs): + self.extensionJs = extensionJs + + def start(self, browserConfigurationOptions=None, driver=None): + start_args = [self.browserStartCommand, self.browserURL, self.extensionJs] + if browserConfigurationOptions: + start_args.append(browserConfigurationOptions) + if driver: + start_args.append('webdriver.remote.sessionid=%s' % driver.session_id) + result = self.get_string("getNewBrowserSession", start_args) + try: + self.sessionId = result + except ValueError: + raise Exception(result) + + def stop(self): + self.do_command("testComplete", []) + self.sessionId = None + + def do_command(self, verb, args): + conn = http_client.HTTPConnection(self.host, self.port, timeout=30) + try: + body = 'cmd=' + urllib_parse.quote_plus(unicode(verb).encode('utf-8')) + for i in range(len(args)): + body += '&' + unicode(i+1) + '=' + \ + urllib_parse.quote_plus(unicode(args[i]).encode('utf-8')) + if (None != self.sessionId): + body += "&sessionId=" + unicode(self.sessionId) + headers = { + "Content-Type": + "application/x-www-form-urlencoded; charset=utf-8" + } + conn.request("POST", "/selenium-server/driver/", body, headers) + + response = conn.getresponse() + data = unicode(response.read(), "UTF-8") + if (not data.startswith('OK')): + raise Exception(data) + return data + finally: + conn.close() + + def get_string(self, verb, args): + result = self.do_command(verb, args) + return result[3:] + + def get_string_array(self, verb, args): + csv = self.get_string(verb, args) + if not csv: + return [] + token = "" + tokens = [] + escape = False + for i in range(len(csv)): + letter = csv[i] + if (escape): + token = token + letter + escape = False + continue + if (letter == '\\'): + escape = True + elif (letter == ','): + tokens.append(token) + token = "" + else: + token = token + letter + tokens.append(token) + return tokens + + def get_number(self, verb, args): + return int(self.get_string(verb, args)) + + def get_number_array(self, verb, args): + string_array = self.get_string_array(verb, args) + num_array = [] + for i in string_array: + num_array.append(int(i)) + + return num_array + + def get_boolean(self, verb, args): + boolstr = self.get_string(verb, args) + if ("true" == boolstr): + return True + if ("false" == boolstr): + return False + raise ValueError("result is neither 'true' nor 'false': " + boolstr) + + def get_boolean_array(self, verb, args): + boolarr = self.get_string_array(verb, args) + for i, boolstr in enumerate(boolarr): + if ("true" == boolstr): + boolarr[i] = True + continue + if ("false" == boolstr): + boolarr[i] = False + continue + raise ValueError("result is neither 'true' nor 'false': " + boolarr[i]) + return boolarr + + + + def click(self,locator): + """ + Clicks on a link, button, checkbox or radio button. If the click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + """ + self.do_command("click", [locator,]) + + + def double_click(self,locator): + """ + Double clicks on a link, button, checkbox or radio button. If the double click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + """ + self.do_command("doubleClick", [locator,]) + + + def context_menu(self,locator): + """ + Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element). + + 'locator' is an element locator + """ + self.do_command("contextMenu", [locator,]) + + + def click_at(self,locator,coordString): + """ + Clicks on a link, button, checkbox or radio button. If the click action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("clickAt", [locator,coordString,]) + + + def double_click_at(self,locator,coordString): + """ + Doubleclicks on a link, button, checkbox or radio button. If the action + causes a new page to load (like a link usually does), call + waitForPageToLoad. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("doubleClickAt", [locator,coordString,]) + + + def context_menu_at(self,locator,coordString): + """ + Simulates opening the context menu for the specified element (as might happen if the user "right-clicked" on the element). + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("contextMenuAt", [locator,coordString,]) + + + def fire_event(self,locator,eventName): + """ + Explicitly simulate an event, to trigger the corresponding "on\ *event*" + handler. + + 'locator' is an element locator + 'eventName' is the event name, e.g. "focus" or "blur" + """ + self.do_command("fireEvent", [locator,eventName,]) + + + def focus(self,locator): + """ + Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field. + + 'locator' is an element locator + """ + self.do_command("focus", [locator,]) + + + def key_press(self,locator,keySequence): + """ + Simulates a user pressing and releasing a key. + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyPress", [locator,keySequence,]) + + + def shift_key_down(self): + """ + Press the shift key and hold it down until doShiftUp() is called or a new page is loaded. + + """ + self.do_command("shiftKeyDown", []) + + + def shift_key_up(self): + """ + Release the shift key. + + """ + self.do_command("shiftKeyUp", []) + + + def meta_key_down(self): + """ + Press the meta key and hold it down until doMetaUp() is called or a new page is loaded. + + """ + self.do_command("metaKeyDown", []) + + + def meta_key_up(self): + """ + Release the meta key. + + """ + self.do_command("metaKeyUp", []) + + + def alt_key_down(self): + """ + Press the alt key and hold it down until doAltUp() is called or a new page is loaded. + + """ + self.do_command("altKeyDown", []) + + + def alt_key_up(self): + """ + Release the alt key. + + """ + self.do_command("altKeyUp", []) + + + def control_key_down(self): + """ + Press the control key and hold it down until doControlUp() is called or a new page is loaded. + + """ + self.do_command("controlKeyDown", []) + + + def control_key_up(self): + """ + Release the control key. + + """ + self.do_command("controlKeyUp", []) + + + def key_down(self,locator,keySequence): + """ + Simulates a user pressing a key (without releasing it yet). + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyDown", [locator,keySequence,]) + + + def key_up(self,locator,keySequence): + """ + Simulates a user releasing a key. + + 'locator' is an element locator + 'keySequence' is Either be a string("\" followed by the numeric keycode of the key to be pressed, normally the ASCII value of that key), or a single character. For example: "w", "\119". + """ + self.do_command("keyUp", [locator,keySequence,]) + + + def mouse_over(self,locator): + """ + Simulates a user hovering a mouse over the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseOver", [locator,]) + + + def mouse_out(self,locator): + """ + Simulates a user moving the mouse pointer away from the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseOut", [locator,]) + + + def mouse_down(self,locator): + """ + Simulates a user pressing the left mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseDown", [locator,]) + + + def mouse_down_right(self,locator): + """ + Simulates a user pressing the right mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseDownRight", [locator,]) + + + def mouse_down_at(self,locator,coordString): + """ + Simulates a user pressing the left mouse button (without releasing it yet) at + the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseDownAt", [locator,coordString,]) + + + def mouse_down_right_at(self,locator,coordString): + """ + Simulates a user pressing the right mouse button (without releasing it yet) at + the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseDownRightAt", [locator,coordString,]) + + + def mouse_up(self,locator): + """ + Simulates the event that occurs when the user releases the mouse button (i.e., stops + holding the button down) on the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseUp", [locator,]) + + + def mouse_up_right(self,locator): + """ + Simulates the event that occurs when the user releases the right mouse button (i.e., stops + holding the button down) on the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseUpRight", [locator,]) + + + def mouse_up_at(self,locator,coordString): + """ + Simulates the event that occurs when the user releases the mouse button (i.e., stops + holding the button down) at the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseUpAt", [locator,coordString,]) + + + def mouse_up_right_at(self,locator,coordString): + """ + Simulates the event that occurs when the user releases the right mouse button (i.e., stops + holding the button down) at the specified location. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseUpRightAt", [locator,coordString,]) + + + def mouse_move(self,locator): + """ + Simulates a user pressing the mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + """ + self.do_command("mouseMove", [locator,]) + + + def mouse_move_at(self,locator,coordString): + """ + Simulates a user pressing the mouse button (without releasing it yet) on + the specified element. + + 'locator' is an element locator + 'coordString' is specifies the x,y position (i.e. - 10,20) of the mouse event relative to the element returned by the locator. + """ + self.do_command("mouseMoveAt", [locator,coordString,]) + + + def type(self,locator,value): + """ + Sets the value of an input field, as though you typed it in. + + + Can also be used to set the value of combo boxes, check boxes, etc. In these cases, + value should be the value of the option selected, not the visible text. + + + 'locator' is an element locator + 'value' is the value to type + """ + self.do_command("type", [locator,value,]) + + + def type_keys(self,locator,value): + """ + Simulates keystroke events on the specified element, as though you typed the value key-by-key. + + + This is a convenience method for calling keyDown, keyUp, keyPress for every character in the specified string; + this is useful for dynamic UI widgets (like auto-completing combo boxes) that require explicit key events. + + Unlike the simple "type" command, which forces the specified value into the page directly, this command + may or may not have any visible effect, even in cases where typing keys would normally have a visible effect. + For example, if you use "typeKeys" on a form element, you may or may not see the results of what you typed in + the field. + + In some cases, you may need to use the simple "type" command to set the value of the field and then the "typeKeys" command to + send the keystroke events corresponding to what you just typed. + + + 'locator' is an element locator + 'value' is the value to type + """ + self.do_command("typeKeys", [locator,value,]) + + + def set_speed(self,value): + """ + Set execution speed (i.e., set the millisecond length of a delay which will follow each selenium operation). By default, there is no such delay, i.e., + the delay is 0 milliseconds. + + 'value' is the number of milliseconds to pause after operation + """ + self.do_command("setSpeed", [value,]) + + + def get_speed(self): + """ + Get execution speed (i.e., get the millisecond length of the delay following each selenium operation). By default, there is no such delay, i.e., + the delay is 0 milliseconds. + + See also setSpeed. + + """ + return self.get_string("getSpeed", []) + + def get_log(self): + """ + Get RC logs associated with current session. + + """ + return self.get_string("getLog", []) + + + def check(self,locator): + """ + Check a toggle-button (checkbox/radio) + + 'locator' is an element locator + """ + self.do_command("check", [locator,]) + + + def uncheck(self,locator): + """ + Uncheck a toggle-button (checkbox/radio) + + 'locator' is an element locator + """ + self.do_command("uncheck", [locator,]) + + + def select(self,selectLocator,optionLocator): + """ + Select an option from a drop-down using an option locator. + + + + Option locators provide different ways of specifying options of an HTML + Select element (e.g. for selecting a specific option, or for asserting + that the selected option satisfies a specification). There are several + forms of Select Option Locator. + + + * \ **label**\ =\ *labelPattern*: + matches options based on their labels, i.e. the visible text. (This + is the default.) + + * label=regexp:^[Oo]ther + + + * \ **value**\ =\ *valuePattern*: + matches options based on their values. + + * value=other + + + * \ **id**\ =\ *id*: + + matches options based on their ids. + + * id=option1 + + + * \ **index**\ =\ *index*: + matches an option based on its index (offset from zero). + + * index=2 + + + + + + If no option locator prefix is provided, the default behaviour is to match on \ **label**\ . + + + + 'selectLocator' is an element locator identifying a drop-down menu + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("select", [selectLocator,optionLocator,]) + + + def add_selection(self,locator,optionLocator): + """ + Add a selection to the set of selected options in a multi-select element using an option locator. + + @see #doSelect for details of option locators + + 'locator' is an element locator identifying a multi-select box + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("addSelection", [locator,optionLocator,]) + + + def remove_selection(self,locator,optionLocator): + """ + Remove a selection from the set of selected options in a multi-select element using an option locator. + + @see #doSelect for details of option locators + + 'locator' is an element locator identifying a multi-select box + 'optionLocator' is an option locator (a label by default) + """ + self.do_command("removeSelection", [locator,optionLocator,]) + + + def remove_all_selections(self,locator): + """ + Unselects all of the selected options in a multi-select element. + + 'locator' is an element locator identifying a multi-select box + """ + self.do_command("removeAllSelections", [locator,]) + + + def submit(self,formLocator): + """ + Submit the specified form. This is particularly useful for forms without + submit buttons, e.g. single-input "Search" forms. + + 'formLocator' is an element locator for the form you want to submit + """ + self.do_command("submit", [formLocator,]) + + def open(self,url,ignoreResponseCode=True): + """ + Opens an URL in the test frame. This accepts both relative and absolute + URLs. + + The "open" command waits for the page to load before proceeding, + ie. the "AndWait" suffix is implicit. + + \ *Note*: The URL must be on the same domain as the runner HTML + due to security restrictions in the browser (Same Origin Policy). If you + need to open an URL on another domain, use the Selenium Server to start a + new browser session on that domain. + + 'url' is the URL to open; may be relative or absolute + 'ignoreResponseCode' if set to true: doesnt send ajax HEAD/GET request; if set to false: sends ajax HEAD/GET request to the url and reports error code if any as response to open. + """ + self.do_command("open", [url,ignoreResponseCode]) + + + def open_window(self,url,windowID): + """ + Opens a popup window (if a window with that ID isn't already open). + After opening the window, you'll need to select it using the selectWindow + command. + + + This command can also be a useful workaround for bug SEL-339. In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + an empty (blank) url, like this: openWindow("", "myFunnyWindow"). + + + 'url' is the URL to open, which can be blank + 'windowID' is the JavaScript window ID of the window to select + """ + self.do_command("openWindow", [url,windowID,]) + + + def select_window(self,windowID): + """ + Selects a popup window using a window locator; once a popup window has been selected, all + commands go to that window. To select the main window again, use null + as the target. + + + + + Window locators provide different ways of specifying the window object: + by title, by internal JavaScript "name," or by JavaScript variable. + + + * \ **title**\ =\ *My Special Window*: + Finds the window using the text that appears in the title bar. Be careful; + two windows can share the same title. If that happens, this locator will + just pick one. + + * \ **name**\ =\ *myWindow*: + Finds the window using its internal JavaScript "name" property. This is the second + parameter "windowName" passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag) + (which Selenium intercepts). + + * \ **var**\ =\ *variableName*: + Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current + application window, e.g. "window.foo = window.open(url);". In those cases, you can open the window using + "var=foo". + + + + + If no window locator prefix is provided, we'll try to guess what you mean like this: + + 1.) if windowID is null, (or the string "null") then it is assumed the user is referring to the original window instantiated by the browser). + + 2.) if the value of the "windowID" parameter is a JavaScript variable name in the current application window, then it is assumed + that this variable contains the return value from a call to the JavaScript window.open() method. + + 3.) Otherwise, selenium looks in a hash it maintains that maps string names to window "names". + + 4.) If \ *that* fails, we'll try looping over all of the known windows to try to find the appropriate "title". + Since "title" is not necessarily unique, this may have unexpected behavior. + + If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages + which identify the names of windows created via window.open (and therefore intercepted by Selenium). You will see messages + like the following for each window as it is opened: + + ``debug: window.open call intercepted; window ID (which you can use with selectWindow()) is "myNewWindow"`` + + In some cases, Selenium will be unable to intercept a call to window.open (if the call occurs during or before the "onLoad" event, for example). + (This is bug SEL-339.) In those cases, you can force Selenium to notice the open window's name by using the Selenium openWindow command, using + an empty (blank) url, like this: openWindow("", "myFunnyWindow"). + + + 'windowID' is the JavaScript window ID of the window to select + """ + self.do_command("selectWindow", [windowID,]) + + + def select_pop_up(self,windowID): + """ + Simplifies the process of selecting a popup window (and does not offer + functionality beyond what ``selectWindow()`` already provides). + + * If ``windowID`` is either not specified, or specified as + "null", the first non-top window is selected. The top window is the one + that would be selected by ``selectWindow()`` without providing a + ``windowID`` . This should not be used when more than one popup + window is in play. + * Otherwise, the window will be looked up considering + ``windowID`` as the following in order: 1) the "name" of the + window, as specified to ``window.open()``; 2) a javascript + variable which is a reference to a window; and 3) the title of the + window. This is the same ordered lookup performed by + ``selectWindow`` . + + + + 'windowID' is an identifier for the popup window, which can take on a number of different meanings + """ + self.do_command("selectPopUp", [windowID,]) + + + def deselect_pop_up(self): + """ + Selects the main window. Functionally equivalent to using + ``selectWindow()`` and specifying no value for + ``windowID``. + + """ + self.do_command("deselectPopUp", []) + + + def select_frame(self,locator): + """ + Selects a frame within the current window. (You may invoke this command + multiple times to select nested frames.) To select the parent frame, use + "relative=parent" as a locator; to select the top frame, use "relative=top". + You can also select a frame by its 0-based index number; select the first frame with + "index=0", or the third frame with "index=2". + + + You may also use a DOM expression to identify the frame you want directly, + like this: ``dom=frames["main"].frames["subframe"]`` + + + 'locator' is an element locator identifying a frame or iframe + """ + self.do_command("selectFrame", [locator,]) + + + def get_whether_this_frame_match_frame_expression(self,currentFrameString,target): + """ + Determine whether current/locator identify the frame containing this running code. + + + This is useful in proxy injection mode, where this code runs in every + browser frame and window, and sometimes the selenium server needs to identify + the "current" frame. In this case, when the test calls selectFrame, this + routine is called for each frame to figure out which one has been selected. + The selected frame will return true, while all others will return false. + + + 'currentFrameString' is starting frame + 'target' is new frame (which might be relative to the current one) + """ + return self.get_boolean("getWhetherThisFrameMatchFrameExpression", [currentFrameString,target,]) + + + def get_whether_this_window_match_window_expression(self,currentWindowString,target): + """ + Determine whether currentWindowString plus target identify the window containing this running code. + + + This is useful in proxy injection mode, where this code runs in every + browser frame and window, and sometimes the selenium server needs to identify + the "current" window. In this case, when the test calls selectWindow, this + routine is called for each window to figure out which one has been selected. + The selected window will return true, while all others will return false. + + + 'currentWindowString' is starting window + 'target' is new window (which might be relative to the current one, e.g., "_parent") + """ + return self.get_boolean("getWhetherThisWindowMatchWindowExpression", [currentWindowString,target,]) + + + def wait_for_pop_up(self,windowID,timeout): + """ + Waits for a popup window to appear and load up. + + 'windowID' is the JavaScript window "name" of the window that will appear (not the text of the title bar) If unspecified, or specified as "null", this command will wait for the first non-top window to appear (don't rely on this if you are working with multiple popups simultaneously). + 'timeout' is a timeout in milliseconds, after which the action will return with an error. If this value is not specified, the default Selenium timeout will be used. See the setTimeout() command. + """ + self.do_command("waitForPopUp", [windowID,timeout,]) + + + def choose_cancel_on_next_confirmation(self): + """ + + + By default, Selenium's overridden window.confirm() function will + return true, as if the user had manually clicked OK; after running + this command, the next call to confirm() will return false, as if + the user had clicked Cancel. Selenium will then resume using the + default behavior for future confirmations, automatically returning + true (OK) unless/until you explicitly call this command for each + confirmation. + + + + Take note - every time a confirmation comes up, you must + consume it with a corresponding getConfirmation, or else + the next selenium operation will fail. + + + + """ + self.do_command("chooseCancelOnNextConfirmation", []) + + + def choose_ok_on_next_confirmation(self): + """ + + + Undo the effect of calling chooseCancelOnNextConfirmation. Note + that Selenium's overridden window.confirm() function will normally automatically + return true, as if the user had manually clicked OK, so you shouldn't + need to use this command unless for some reason you need to change + your mind prior to the next confirmation. After any confirmation, Selenium will resume using the + default behavior for future confirmations, automatically returning + true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each + confirmation. + + + + Take note - every time a confirmation comes up, you must + consume it with a corresponding getConfirmation, or else + the next selenium operation will fail. + + + + """ + self.do_command("chooseOkOnNextConfirmation", []) + + + def answer_on_next_prompt(self,answer): + """ + Instructs Selenium to return the specified answer string in response to + the next JavaScript prompt [window.prompt()]. + + 'answer' is the answer to give in response to the prompt pop-up + """ + self.do_command("answerOnNextPrompt", [answer,]) + + + def go_back(self): + """ + Simulates the user clicking the "back" button on their browser. + + """ + self.do_command("goBack", []) + + + def refresh(self): + """ + Simulates the user clicking the "Refresh" button on their browser. + + """ + self.do_command("refresh", []) + + + def close(self): + """ + Simulates the user clicking the "close" button in the titlebar of a popup + window or tab. + + """ + self.do_command("close", []) + + + def is_alert_present(self): + """ + Has an alert occurred? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isAlertPresent", []) + + + def is_prompt_present(self): + """ + Has a prompt occurred? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isPromptPresent", []) + + + def is_confirmation_present(self): + """ + Has confirm() been called? + + + + This function never throws an exception + + + + """ + return self.get_boolean("isConfirmationPresent", []) + + + def get_alert(self): + """ + Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts. + + + Getting an alert has the same effect as manually clicking OK. If an + alert is generated but you do not consume it with getAlert, the next Selenium action + will fail. + + Under Selenium, JavaScript alerts will NOT pop up a visible alert + dialog. + + Selenium does NOT support JavaScript alerts that are generated in a + page's onload() event handler. In this case a visible dialog WILL be + generated and Selenium will hang until someone manually clicks OK. + + + """ + return self.get_string("getAlert", []) + + + def get_confirmation(self): + """ + Retrieves the message of a JavaScript confirmation dialog generated during + the previous action. + + + + By default, the confirm function will return true, having the same effect + as manually clicking OK. This can be changed by prior execution of the + chooseCancelOnNextConfirmation command. + + + + If an confirmation is generated but you do not consume it with getConfirmation, + the next Selenium action will fail. + + + + NOTE: under Selenium, JavaScript confirmations will NOT pop up a visible + dialog. + + + + NOTE: Selenium does NOT support JavaScript confirmations that are + generated in a page's onload() event handler. In this case a visible + dialog WILL be generated and Selenium will hang until you manually click + OK. + + + + """ + return self.get_string("getConfirmation", []) + + + def get_prompt(self): + """ + Retrieves the message of a JavaScript question prompt dialog generated during + the previous action. + + + Successful handling of the prompt requires prior execution of the + answerOnNextPrompt command. If a prompt is generated but you + do not get/verify it, the next Selenium action will fail. + + NOTE: under Selenium, JavaScript prompts will NOT pop up a visible + dialog. + + NOTE: Selenium does NOT support JavaScript prompts that are generated in a + page's onload() event handler. In this case a visible dialog WILL be + generated and Selenium will hang until someone manually clicks OK. + + + """ + return self.get_string("getPrompt", []) + + + def get_location(self): + """ + Gets the absolute URL of the current page. + + """ + return self.get_string("getLocation", []) + + + def get_title(self): + """ + Gets the title of the current page. + + """ + return self.get_string("getTitle", []) + + + def get_body_text(self): + """ + Gets the entire text of the page. + + """ + return self.get_string("getBodyText", []) + + + def get_value(self,locator): + """ + Gets the (whitespace-trimmed) value of an input field (or anything else with a value parameter). + For checkbox/radio elements, the value will be "on" or "off" depending on + whether the element is checked or not. + + 'locator' is an element locator + """ + return self.get_string("getValue", [locator,]) + + + def get_text(self,locator): + """ + Gets the text of an element. This works for any element that contains + text. This command uses either the textContent (Mozilla-like browsers) or + the innerText (IE-like browsers) of the element, which is the rendered + text shown to the user. + + 'locator' is an element locator + """ + return self.get_string("getText", [locator,]) + + + def highlight(self,locator): + """ + Briefly changes the backgroundColor of the specified element yellow. Useful for debugging. + + 'locator' is an element locator + """ + self.do_command("highlight", [locator,]) + + + def get_eval(self,script): + """ + Gets the result of evaluating the specified JavaScript snippet. The snippet may + have multiple lines, but only the result of the last line will be returned. + + + Note that, by default, the snippet will run in the context of the "selenium" + object itself, so ``this`` will refer to the Selenium object. Use ``window`` to + refer to the window of your application, e.g. ``window.document.getElementById('foo')`` + + If you need to use + a locator to refer to a single element in your application page, you can + use ``this.browserbot.findElement("id=foo")`` where "id=foo" is your locator. + + + 'script' is the JavaScript snippet to run + """ + return self.get_string("getEval", [script,]) + + + def is_checked(self,locator): + """ + Gets whether a toggle-button (checkbox/radio) is checked. Fails if the specified element doesn't exist or isn't a toggle-button. + + 'locator' is an element locator pointing to a checkbox or radio button + """ + return self.get_boolean("isChecked", [locator,]) + + + def get_table(self,tableCellAddress): + """ + Gets the text from a cell of a table. The cellAddress syntax + tableLocator.row.column, where row and column start at 0. + + 'tableCellAddress' is a cell address, e.g. "foo.1.4" + """ + return self.get_string("getTable", [tableCellAddress,]) + + + def get_selected_labels(self,selectLocator): + """ + Gets all option labels (visible text) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedLabels", [selectLocator,]) + + + def get_selected_label(self,selectLocator): + """ + Gets option label (visible text) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedLabel", [selectLocator,]) + + + def get_selected_values(self,selectLocator): + """ + Gets all option values (value attributes) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedValues", [selectLocator,]) + + + def get_selected_value(self,selectLocator): + """ + Gets option value (value attribute) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedValue", [selectLocator,]) + + + def get_selected_indexes(self,selectLocator): + """ + Gets all option indexes (option number, starting at 0) for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedIndexes", [selectLocator,]) + + + def get_selected_index(self,selectLocator): + """ + Gets option index (option number, starting at 0) for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedIndex", [selectLocator,]) + + + def get_selected_ids(self,selectLocator): + """ + Gets all option element IDs for selected options in the specified select or multi-select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectedIds", [selectLocator,]) + + + def get_selected_id(self,selectLocator): + """ + Gets option element ID for selected option in the specified select element. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string("getSelectedId", [selectLocator,]) + + + def is_something_selected(self,selectLocator): + """ + Determines whether some option in a drop-down menu is selected. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_boolean("isSomethingSelected", [selectLocator,]) + + + def get_select_options(self,selectLocator): + """ + Gets all option labels in the specified select drop-down. + + 'selectLocator' is an element locator identifying a drop-down menu + """ + return self.get_string_array("getSelectOptions", [selectLocator,]) + + + def get_attribute(self,attributeLocator): + """ + Gets the value of an element attribute. The value of the attribute may + differ across browsers (this is the case for the "style" attribute, for + example). + + 'attributeLocator' is an element locator followed by an @ sign and then the name of the attribute, e.g. "foo@bar" + """ + return self.get_string("getAttribute", [attributeLocator,]) + + + def is_text_present(self,pattern): + """ + Verifies that the specified text pattern appears somewhere on the rendered page shown to the user. + + 'pattern' is a pattern to match with the text of the page + """ + return self.get_boolean("isTextPresent", [pattern,]) + + + def is_element_present(self,locator): + """ + Verifies that the specified element is somewhere on the page. + + 'locator' is an element locator + """ + return self.get_boolean("isElementPresent", [locator,]) + + + def is_visible(self,locator): + """ + Determines if the specified element is visible. An + element can be rendered invisible by setting the CSS "visibility" + property to "hidden", or the "display" property to "none", either for the + element itself or one if its ancestors. This method will fail if + the element is not present. + + 'locator' is an element locator + """ + return self.get_boolean("isVisible", [locator,]) + + + def is_editable(self,locator): + """ + Determines whether the specified input element is editable, ie hasn't been disabled. + This method will fail if the specified element isn't an input element. + + 'locator' is an element locator + """ + return self.get_boolean("isEditable", [locator,]) + + + def get_all_buttons(self): + """ + Returns the IDs of all buttons on the page. + + + If a given button has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllButtons", []) + + + def get_all_links(self): + """ + Returns the IDs of all links on the page. + + + If a given link has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllLinks", []) + + + def get_all_fields(self): + """ + Returns the IDs of all input fields on the page. + + + If a given field has no ID, it will appear as "" in this array. + + + """ + return self.get_string_array("getAllFields", []) + + + def get_attribute_from_all_windows(self,attributeName): + """ + Returns every instance of some attribute from all known windows. + + 'attributeName' is name of an attribute on the windows + """ + return self.get_string_array("getAttributeFromAllWindows", [attributeName,]) + + + def dragdrop(self,locator,movementsString): + """ + deprecated - use dragAndDrop instead + + 'locator' is an element locator + 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" + """ + self.do_command("dragdrop", [locator,movementsString,]) + + + def set_mouse_speed(self,pixels): + """ + Configure the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + + Setting this value to 0 means that we'll send a "mousemove" event to every single pixel + in between the start location and the end location; that can be very slow, and may + cause some browsers to force the JavaScript to timeout. + + If the mouse speed is greater than the distance between the two dragged objects, we'll + just send one "mousemove" at the start location and then one final one at the end location. + + + 'pixels' is the number of pixels between "mousemove" events + """ + self.do_command("setMouseSpeed", [pixels,]) + + + def get_mouse_speed(self): + """ + Returns the number of pixels between "mousemove" events during dragAndDrop commands (default=10). + + """ + return self.get_number("getMouseSpeed", []) + + + def drag_and_drop(self,locator,movementsString): + """ + Drags an element a certain distance and then drops it + + 'locator' is an element locator + 'movementsString' is offset in pixels from the current location to which the element should be moved, e.g., "+70,-300" + """ + self.do_command("dragAndDrop", [locator,movementsString,]) + + + def drag_and_drop_to_object(self,locatorOfObjectToBeDragged,locatorOfDragDestinationObject): + """ + Drags an element and drops it on another element + + 'locatorOfObjectToBeDragged' is an element to be dragged + 'locatorOfDragDestinationObject' is an element whose location (i.e., whose center-most pixel) will be the point where locatorOfObjectToBeDragged is dropped + """ + self.do_command("dragAndDropToObject", [locatorOfObjectToBeDragged,locatorOfDragDestinationObject,]) + + + def window_focus(self): + """ + Gives focus to the currently selected window + + """ + self.do_command("windowFocus", []) + + + def window_maximize(self): + """ + Resize currently selected window to take up the entire screen + + """ + self.do_command("windowMaximize", []) + + + def get_all_window_ids(self): + """ + Returns the IDs of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowIds", []) + + + def get_all_window_names(self): + """ + Returns the names of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowNames", []) + + + def get_all_window_titles(self): + """ + Returns the titles of all windows that the browser knows about. + + """ + return self.get_string_array("getAllWindowTitles", []) + + + def get_html_source(self): + """ + Returns the entire HTML source between the opening and + closing "html" tags. + + """ + return self.get_string("getHtmlSource", []) + + + def set_cursor_position(self,locator,position): + """ + Moves the text cursor to the specified position in the given input element or textarea. + This method will fail if the specified element isn't an input element or textarea. + + 'locator' is an element locator pointing to an input element or textarea + 'position' is the numerical position of the cursor in the field; position should be 0 to move the position to the beginning of the field. You can also set the cursor to -1 to move it to the end of the field. + """ + self.do_command("setCursorPosition", [locator,position,]) + + + def get_element_index(self,locator): + """ + Get the relative index of an element to its parent (starting from 0). The comment node and empty text node + will be ignored. + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementIndex", [locator,]) + + + def is_ordered(self,locator1,locator2): + """ + Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will + not be considered ordered. + + 'locator1' is an element locator pointing to the first element + 'locator2' is an element locator pointing to the second element + """ + return self.get_boolean("isOrdered", [locator1,locator2,]) + + + def get_element_position_left(self,locator): + """ + Retrieves the horizontal position of an element + + 'locator' is an element locator pointing to an element OR an element itself + """ + return self.get_number("getElementPositionLeft", [locator,]) + + + def get_element_position_top(self,locator): + """ + Retrieves the vertical position of an element + + 'locator' is an element locator pointing to an element OR an element itself + """ + return self.get_number("getElementPositionTop", [locator,]) + + + def get_element_width(self,locator): + """ + Retrieves the width of an element + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementWidth", [locator,]) + + + def get_element_height(self,locator): + """ + Retrieves the height of an element + + 'locator' is an element locator pointing to an element + """ + return self.get_number("getElementHeight", [locator,]) + + + def get_cursor_position(self,locator): + """ + Retrieves the text cursor position in the given input element or textarea; beware, this may not work perfectly on all browsers. + + + Specifically, if the cursor/selection has been cleared by JavaScript, this command will tend to + return the position of the last location of the cursor, even though the cursor is now gone from the page. This is filed as SEL-243. + + This method will fail if the specified element isn't an input element or textarea, or there is no cursor in the element. + + 'locator' is an element locator pointing to an input element or textarea + """ + return self.get_number("getCursorPosition", [locator,]) + + + def get_expression(self,expression): + """ + Returns the specified expression. + + + This is useful because of JavaScript preprocessing. + It is used to generate commands like assertExpression and waitForExpression. + + + 'expression' is the value to return + """ + return self.get_string("getExpression", [expression,]) + + + def get_xpath_count(self,xpath): + """ + Returns the number of nodes that match the specified xpath, eg. "//table" would give + the number of tables. + + 'xpath' is the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you. + """ + return self.get_number("getXpathCount", [xpath,]) + + def get_css_count(self,css): + """ + Returns the number of nodes that match the specified css selector, eg. "css=table" would give + the number of tables. + + 'css' is the css selector to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you. + """ + return self.get_number("getCssCount", [css,]) + + def assign_id(self,locator,identifier): + """ + Temporarily sets the "id" attribute of the specified element, so you can locate it in the future + using its ID rather than a slow/complicated XPath. This ID will disappear once the page is + reloaded. + + 'locator' is an element locator pointing to an element + 'identifier' is a string to be used as the ID of the specified element + """ + self.do_command("assignId", [locator,identifier,]) + + + def allow_native_xpath(self,allow): + """ + Specifies whether Selenium should use the native in-browser implementation + of XPath (if any native version is available); if you pass "false" to + this function, we will always use our pure-JavaScript xpath library. + Using the pure-JS xpath library can improve the consistency of xpath + element locators between different browser vendors, but the pure-JS + version is much slower than the native implementations. + + 'allow' is boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath + """ + self.do_command("allowNativeXpath", [allow,]) + + + def ignore_attributes_without_value(self,ignore): + """ + Specifies whether Selenium will ignore xpath attributes that have no + value, i.e. are the empty string, when using the non-native xpath + evaluation engine. You'd want to do this for performance reasons in IE. + However, this could break certain xpaths, for example an xpath that looks + for an attribute whose value is NOT the empty string. + + The hope is that such xpaths are relatively rare, but the user should + have the option of using them. Note that this only influences xpath + evaluation when using the ajaxslt engine (i.e. not "javascript-xpath"). + + 'ignore' is boolean, true means we'll ignore attributes without value at the expense of xpath "correctness"; false means we'll sacrifice speed for correctness. + """ + self.do_command("ignoreAttributesWithoutValue", [ignore,]) + + + def wait_for_condition(self,script,timeout): + """ + Runs the specified JavaScript snippet repeatedly until it evaluates to "true". + The snippet may have multiple lines, but only the result of the last line + will be considered. + + + Note that, by default, the snippet will be run in the runner's test window, not in the window + of your application. To get the window of your application, you can use + the JavaScript snippet ``selenium.browserbot.getCurrentWindow()``, and then + run your JavaScript in there + + + 'script' is the JavaScript snippet to run + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForCondition", [script,timeout,]) + + + def set_timeout(self,timeout): + """ + Specifies the amount of time that Selenium will wait for actions to complete. + + + Actions that require waiting include "open" and the "waitFor\*" actions. + + The default timeout is 30 seconds. + + 'timeout' is a timeout in milliseconds, after which the action will return with an error + """ + self.do_command("setTimeout", [timeout,]) + + + def wait_for_page_to_load(self,timeout): + """ + Waits for a new page to load. + + + You can use this command instead of the "AndWait" suffixes, "clickAndWait", "selectAndWait", "typeAndWait" etc. + (which are only available in the JS API). + + Selenium constantly keeps track of new pages loading, and sets a "newPageLoaded" + flag when it first notices a page load. Running any other Selenium command after + turns the flag to false. Hence, if you want to wait for a page to load, you must + wait immediately after a Selenium command that caused a page-load. + + + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForPageToLoad", [timeout,]) + + + def wait_for_frame_to_load(self,frameAddress,timeout): + """ + Waits for a new frame to load. + + + Selenium constantly keeps track of new pages and frames loading, + and sets a "newPageLoaded" flag when it first notices a page load. + + + See waitForPageToLoad for more information. + + 'frameAddress' is FrameAddress from the server side + 'timeout' is a timeout in milliseconds, after which this command will return with an error + """ + self.do_command("waitForFrameToLoad", [frameAddress,timeout,]) + + + def get_cookie(self): + """ + Return all cookies of the current page under test. + + """ + return self.get_string("getCookie", []) + + + def get_cookie_by_name(self,name): + """ + Returns the value of the cookie with the specified name, or throws an error if the cookie is not present. + + 'name' is the name of the cookie + """ + return self.get_string("getCookieByName", [name,]) + + + def is_cookie_present(self,name): + """ + Returns true if a cookie with the specified name is present, or false otherwise. + + 'name' is the name of the cookie + """ + return self.get_boolean("isCookiePresent", [name,]) + + + def create_cookie(self,nameValuePair,optionsString): + """ + Create a new cookie whose path and domain are same with those of current page + under test, unless you specified a path for this cookie explicitly. + + 'nameValuePair' is name and value of the cookie in a format "name=value" + 'optionsString' is options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'. the optionsString's format is "path=/path/, max_age=60, domain=.foo.com". The order of options are irrelevant, the unit of the value of 'max_age' is second. Note that specifying a domain that isn't a subset of the current domain will usually fail. + """ + self.do_command("createCookie", [nameValuePair,optionsString,]) + + + def delete_cookie(self,name,optionsString): + """ + Delete a named cookie with specified path and domain. Be careful; to delete a cookie, you + need to delete it using the exact same path and domain that were used to create the cookie. + If the path is wrong, or the domain is wrong, the cookie simply won't be deleted. Also + note that specifying a domain that isn't a subset of the current domain will usually fail. + + Since there's no way to discover at runtime the original path and domain of a given cookie, + we've added an option called 'recurse' to try all sub-domains of the current domain with + all paths that are a subset of the current path. Beware; this option can be slow. In + big-O notation, it operates in O(n\*m) time, where n is the number of dots in the domain + name and m is the number of slashes in the path. + + 'name' is the name of the cookie to be deleted + 'optionsString' is options for the cookie. Currently supported options include 'path', 'domain' and 'recurse.' The optionsString's format is "path=/path/, domain=.foo.com, recurse=true". The order of options are irrelevant. Note that specifying a domain that isn't a subset of the current domain will usually fail. + """ + self.do_command("deleteCookie", [name,optionsString,]) + + + def delete_all_visible_cookies(self): + """ + Calls deleteCookie with recurse=true on all cookies visible to the current page. + As noted on the documentation for deleteCookie, recurse=true can be much slower + than simply deleting the cookies using a known domain/path. + + """ + self.do_command("deleteAllVisibleCookies", []) + + + def set_browser_log_level(self,logLevel): + """ + Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded. + Valid logLevel strings are: "debug", "info", "warn", "error" or "off". + To see the browser logs, you need to + either show the log window in GUI mode, or enable browser-side logging in Selenium RC. + + 'logLevel' is one of the following: "debug", "info", "warn", "error" or "off" + """ + self.do_command("setBrowserLogLevel", [logLevel,]) + + + def run_script(self,script): + """ + Creates a new "script" tag in the body of the current test window, and + adds the specified text into the body of the command. Scripts run in + this way can often be debugged more easily than scripts executed using + Selenium's "getEval" command. Beware that JS exceptions thrown in these script + tags aren't managed by Selenium, so you should probably wrap your script + in try/catch blocks if there is any chance that the script will throw + an exception. + + 'script' is the JavaScript snippet to run + """ + self.do_command("runScript", [script,]) + + + def add_location_strategy(self,strategyName,functionDefinition): + """ + Defines a new function for Selenium to locate elements on the page. + For example, + if you define the strategy "foo", and someone runs click("foo=blah"), we'll + run your function, passing you the string "blah", and click on the element + that your function + returns, or throw an "Element not found" error if your function returns null. + + We'll pass three arguments to your function: + + * locator: the string the user passed in + * inWindow: the currently selected window + * inDocument: the currently selected document + + + The function must return null if the element can't be found. + + 'strategyName' is the name of the strategy to define; this should use only letters [a-zA-Z] with no spaces or other punctuation. + 'functionDefinition' is a string defining the body of a function in JavaScript. For example: ``return inDocument.getElementById(locator);`` + """ + self.do_command("addLocationStrategy", [strategyName,functionDefinition,]) + + + def capture_entire_page_screenshot(self,filename,kwargs): + """ + Saves the entire contents of the current window canvas to a PNG file. + Contrast this with the captureScreenshot command, which captures the + contents of the OS viewport (i.e. whatever is currently being displayed + on the monitor), and is implemented in the RC only. Currently this only + works in Firefox when running in chrome mode, and in IE non-HTA using + the EXPERIMENTAL "Snapsie" utility. The Firefox implementation is mostly + borrowed from the Screengrab! Firefox extension. Please see + http://www.screengrab.org and http://snapsie.sourceforge.net/ for + details. + + 'filename' is the path to the file to persist the screenshot as. No + filename extension will be appended by default. Directories will not be + created if they do not exist, and an exception will be thrown, possibly + by native code. + + 'kwargs' is a kwargs string that modifies the way the + screenshot is captured. + + Example: "background=#CCFFDD" + + Currently valid options: + + * background + + the background CSS for the HTML document. + This may be useful to set for capturing screenshots of + less-than-ideal layouts, for example where absolute positioning + causes the calculation of the canvas dimension to fail and a black + background is exposed (possibly obscuring black text). + + """ + self.do_command("captureEntirePageScreenshot", [filename,kwargs,]) + + + def rollup(self,rollupName,kwargs): + """ + Executes a command rollup, which is a series of commands with a unique + name, and optionally arguments that control the generation of the set of + commands. If any one of the rolled-up commands fails, the rollup is + considered to have failed. Rollups may also contain nested rollups. + + 'rollupName' is the name of the rollup command + 'kwargs' is keyword arguments string that influences how the rollup expands into commands + """ + self.do_command("rollup", [rollupName,kwargs,]) + + + def add_script(self,scriptContent,scriptTagId): + """ + Loads script content into a new script tag in the Selenium document. This + differs from the runScript command in that runScript adds the script tag + to the document of the AUT, not the Selenium document. The following + entities in the script content are replaced by the characters they + represent: + + < + > + & + + The corresponding remove command is removeScript. + + 'scriptContent' is the Javascript content of the script to add + 'scriptTagId' is (optional) the id of the new script tag. If specified, and an element with this id already exists, this operation will fail. + """ + self.do_command("addScript", [scriptContent,scriptTagId,]) + + + def remove_script(self,scriptTagId): + """ + Removes a script tag from the Selenium document identified by the given + id. Does nothing if the referenced tag doesn't exist. + + 'scriptTagId' is the id of the script element to remove. + """ + self.do_command("removeScript", [scriptTagId,]) + + + def use_xpath_library(self,libraryName): + """ + Allows choice of one of the available libraries. + + 'libraryName' is name of the desired library Only the following three can be chosen: + * "ajaxslt" - Google's library + * "javascript-xpath" - Cybozu Labs' faster library + * "default" - The default library. Currently the default library is "ajaxslt" . + + If libraryName isn't one of these three, then no change will be made. + """ + self.do_command("useXpathLibrary", [libraryName,]) + + + def set_context(self,context): + """ + Writes a message to the status bar and adds a note to the browser-side + log. + + 'context' is the message to be sent to the browser + """ + self.do_command("setContext", [context,]) + + + def attach_file(self,fieldLocator,fileLocator): + """ + Sets a file input (upload) field to the file listed in fileLocator + + 'fieldLocator' is an element locator + 'fileLocator' is a URL pointing to the specified file. Before the file can be set in the input field (fieldLocator), Selenium RC may need to transfer the file to the local machine before attaching the file in a web page form. This is common in selenium grid configurations where the RC server driving the browser is not the same machine that started the test. Supported Browsers: Firefox ("\*chrome") only. + """ + self.do_command("attachFile", [fieldLocator,fileLocator,]) + + + def capture_screenshot(self,filename): + """ + Captures a PNG screenshot to the specified file. + + 'filename' is the absolute path to the file to be written, e.g. "c:\blah\screenshot.png" + """ + self.do_command("captureScreenshot", [filename,]) + + + def capture_screenshot_to_string(self): + """ + Capture a PNG screenshot. It then returns the file as a base 64 encoded string. + + """ + return self.get_string("captureScreenshotToString", []) + + + def captureNetworkTraffic(self, type): + """ + Returns the network traffic seen by the browser, including headers, AJAX requests, status codes, and timings. When this function is called, the traffic log is cleared, so the returned content is only the traffic seen since the last call. + + 'type' is The type of data to return the network traffic as. Valid values are: json, xml, or plain. + """ + return self.get_string("captureNetworkTraffic", [type,]) + + def capture_network_traffic(self, type): + return self.captureNetworkTraffic(type) + + def addCustomRequestHeader(self, key, value): + """ + Tells the Selenium server to add the specificed key and value as a custom outgoing request header. This only works if the browser is configured to use the built in Selenium proxy. + + 'key' the header name. + 'value' the header value. + """ + return self.do_command("addCustomRequestHeader", [key,value,]) + + def add_custom_request_header(self, key, value): + return self.addCustomRequestHeader(key, value) + + def capture_entire_page_screenshot_to_string(self,kwargs): + """ + Downloads a screenshot of the browser current window canvas to a + based 64 encoded PNG file. The \ *entire* windows canvas is captured, + including parts rendered outside of the current view port. + + Currently this only works in Mozilla and when running in chrome mode. + + 'kwargs' is A kwargs string that modifies the way the screenshot is captured. Example: "background=#CCFFDD". This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed (possibly obscuring black text). + """ + return self.get_string("captureEntirePageScreenshotToString", [kwargs,]) + + + def shut_down_selenium_server(self): + """ + Kills the running Selenium Server and all browser sessions. After you run this command, you will no longer be able to send + commands to the server; you can't remotely start the server once it has been stopped. Normally + you should prefer to run the "stop" command, which terminates the current browser session, rather than + shutting down the entire server. + + """ + self.do_command("shutDownSeleniumServer", []) + + + def retrieve_last_remote_control_logs(self): + """ + Retrieve the last messages logged on a specific remote control. Useful for error reports, especially + when running multiple remote controls in a distributed environment. The maximum number of log messages + that can be retrieve is configured on remote control startup. + + """ + return self.get_string("retrieveLastRemoteControlLogs", []) + + + def key_down_native(self,keycode): + """ + Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyDownNative", [keycode,]) + + + def key_up_native(self,keycode): + """ + Simulates a user releasing a key by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyUpNative", [keycode,]) + + + def key_press_native(self,keycode): + """ + Simulates a user pressing and releasing a key by sending a native operating system keystroke. + This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing + a key on the keyboard. It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and + metaKeyDown commands, and does not target any particular HTML element. To send a keystroke to a particular + element, focus on the element first before running this command. + + 'keycode' is an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes! + """ + self.do_command("keyPressNative", [keycode,]) diff --git a/apps/selenium/webdriver/__init__.py b/apps/selenium/webdriver/__init__.py new file mode 100644 index 0000000000..72d0c9b14c --- /dev/null +++ b/apps/selenium/webdriver/__init__.py @@ -0,0 +1,32 @@ +#!/usr/bin/python +# +# Copyright 2008-2013 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. + +from .firefox.webdriver import WebDriver as Firefox +from .firefox.firefox_profile import FirefoxProfile +from .chrome.webdriver import WebDriver as Chrome +from .chrome.options import Options as ChromeOptions +from .ie.webdriver import WebDriver as Ie +from .opera.webdriver import WebDriver as Opera +from .safari.webdriver import WebDriver as Safari +from .phantomjs.webdriver import WebDriver as PhantomJS +from .android.webdriver import WebDriver as Android +from .remote.webdriver import WebDriver as Remote +from .common.desired_capabilities import DesiredCapabilities +from .common.action_chains import ActionChains +from .common.touch_actions import TouchActions +from .common.proxy import Proxy + +__version__ = '2.42.1' diff --git a/apps/selenium/webdriver/android/__init__.py b/apps/selenium/webdriver/android/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/selenium/webdriver/android/webdriver.py b/apps/selenium/webdriver/android/webdriver.py new file mode 100644 index 0000000000..79655ee872 --- /dev/null +++ b/apps/selenium/webdriver/android/webdriver.py @@ -0,0 +1,41 @@ +#!/usr/bin/python +# +# Copyright 2014 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. + +import base64 +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities + +class WebDriver(RemoteWebDriver): + """ + Simple RemoteWebDriver wrapper to start connect to Selendroid's WebView app + + For more info on getting started with Selendroid + http://selendroid.io/mobileWeb.html + """ + + def __init__(self, host="localhost", port=4444, desired_capabilities=DesiredCapabilities.ANDROID): + """ + Creates a new instance of Selendroid using the WebView app + + :Args: + - host - location of where selendroid is running + - port - port that selendroid is running on + - desired_capabilities: Dictionary object with capabilities + """ + RemoteWebDriver.__init__(self, + command_executor="http://%s:%d/wd/hub" % (host, port), + desired_capabilities=desired_capabilities) + diff --git a/apps/selenium/webdriver/chrome/__init__.py b/apps/selenium/webdriver/chrome/__init__.py new file mode 100644 index 0000000000..f042f9da32 --- /dev/null +++ b/apps/selenium/webdriver/chrome/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google 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. diff --git a/apps/selenium/webdriver/chrome/options.py b/apps/selenium/webdriver/chrome/options.py new file mode 100644 index 0000000000..554250e52c --- /dev/null +++ b/apps/selenium/webdriver/chrome/options.py @@ -0,0 +1,147 @@ +#!/usr/bin/python +# +# Copyright 2012 Webdriver_name committers +# Copyright 2012 Google 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. + +import os +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +import base64 + + +class Options(object): + + def __init__(self): + self._binary_location = '' + self._arguments = [] + self._extension_files = [] + self._extensions = [] + self._experimental_options = {} + + @property + def binary_location(self): + """ + Returns the location of the binary otherwise an empty string + """ + return self._binary_location + + @binary_location.setter + def binary_location(self, value): + """ + Allows you to set where the chromium binary lives + + :Args: + - value: path to the Chromium binary + """ + self._binary_location = value + + @property + def arguments(self): + """ + Returns a list of arguments needed for the browser + """ + return self._arguments + + def add_argument(self, argument): + """ + Adds an argument to the list + + :Args: + - Sets the arguments + """ + if argument: + self._arguments.append(argument) + else: + raise ValueError("argument can not be null") + + @property + def extensions(self): + """ + Returns a list of encoded extensions that will be loaded into chrome + + """ + encoded_extensions = [] + for ext in self._extension_files: + file_ = open(ext, 'rb') + # Should not use base64.encodestring() which inserts newlines every + # 76 characters (per RFC 1521). Chromedriver has to remove those + # unnecessary newlines before decoding, causing performance hit. + encoded_extensions.append(base64.b64encode(file_.read()).decode('UTF-8')) + + file_.close() + return encoded_extensions + self._extensions + + def add_extension(self, extension): + """ + Adds the path to the extension to a list that will be used to extract it + to the ChromeDriver + + :Args: + - extension: path to the *.crx file + """ + if extension: + if os.path.exists(extension): + self._extension_files.append(extension) + else: + raise IOError("Path to the extension doesn't exist") + else: + raise ValueError("argument can not be null") + + def add_encoded_extension(self, extension): + """ + Adds Base64 encoded string with extension data to a list that will be used to extract it + to the ChromeDriver + + :Args: + - extension: Base64 encoded string with extension data + """ + if extension: + self._extensions.append(extension) + else: + raise ValueError("argument can not be null") + + @property + def experimental_options(self): + """ + Returns a dictionary of experimental options for chrome. + """ + return self._experimental_options + + def add_experimental_option(self, name, value): + """ + Adds an experimental option which is passed to chrome. + + Args: + name: The experimental option name. + value: The option value. + """ + self._experimental_options[name] = value + + def to_capabilities(self): + """ + Creates a capabilities with all the options that have been set and + + returns a dictionary with everything + """ + chrome = DesiredCapabilities.CHROME.copy() + + chrome_options = self.experimental_options.copy() + chrome_options["extensions"] = self.extensions + if self.binary_location: + chrome_options["binary"] = self.binary_location + chrome_options["args"] = self.arguments + + chrome["chromeOptions"] = chrome_options + + return chrome diff --git a/apps/selenium/webdriver/chrome/service.py b/apps/selenium/webdriver/chrome/service.py new file mode 100644 index 0000000000..dcbc49d8d8 --- /dev/null +++ b/apps/selenium/webdriver/chrome/service.py @@ -0,0 +1,112 @@ +#!/usr/bin/python +# +# Copyright 2011 Webdriver_name committers +# Copyright 2011 Google 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. +import os +import subprocess +from subprocess import PIPE +import time + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of the ChromeDriver + """ + + def __init__(self, executable_path, port=0, service_args=None, + log_path=None, env=None): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to the ChromeDriver + - port : Port the service is running on + - service_args : List of args to pass to the chromedriver service + - log_path : Path for the chromedriver service to log to""" + + self.port = port + self.path = executable_path + self.service_args = service_args or [] + if log_path: + self.service_args.append('--log-path=%s' % log_path) + if self.port == 0: + self.port = utils.free_port() + self.env = env + + def start(self): + """ + Starts the ChromeDriver Service. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + env = self.env or os.environ + try: + self.process = subprocess.Popen([ + self.path, + "--port=%d" % self.port] + + self.service_args, env=env, stdout=PIPE, stderr=PIPE) + except: + raise WebDriverException( + "ChromeDriver executable needs to be available in the path. \ + Please download from http://chromedriver.storage.googleapis.com/index.html\ + and read up at http://code.google.com/p/selenium/wiki/ChromeDriver") + count = 0 + while not utils.is_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to the ChromeDriver") + + @property + def service_url(self): + """ + Gets the url of the ChromeDriver Service + """ + return "http://localhost:%d" % self.port + + def stop(self): + """ + Tells the ChromeDriver to stop and cleans up the process + """ + #If its dead dont worry + if self.process is None: + return + + #Tell the Server to die! + try: + from urllib import request as url_request + except ImportError: + import urllib2 as url_request + + url_request.urlopen("http://127.0.0.1:%d/shutdown" % self.port) + count = 0 + while utils.is_connectable(self.port): + if count == 30: + break + count += 1 + time.sleep(1) + + #Tell the Server to properly die in case + try: + if self.process: + self.process.kill() + self.process.wait() + except OSError: + # kill may not be available under windows environment + pass diff --git a/apps/selenium/webdriver/chrome/webdriver.py b/apps/selenium/webdriver/chrome/webdriver.py new file mode 100644 index 0000000000..182702ee18 --- /dev/null +++ b/apps/selenium/webdriver/chrome/webdriver.py @@ -0,0 +1,82 @@ +#!/usr/bin/python +# +# Copyright 2011-2013 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. + +import base64 +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.common.exceptions import WebDriverException +from .service import Service +from .options import Options + +class WebDriver(RemoteWebDriver): + """ + Controls the ChromeDriver and allows you to drive the browser. + + You will need to download the ChromeDriver executable from + http://chromedriver.storage.googleapis.com/index.html + """ + + def __init__(self, executable_path="chromedriver", port=0, + chrome_options=None, service_args=None, + desired_capabilities=None, service_log_path=None): + """ + Creates a new instance of the chrome driver. + + Starts the service and then creates new instance of chrome driver. + + :Args: + - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH + - port - port you would like the service to run, if left as 0, a free port will be found. + - desired_capabilities: Dictionary object with non-browser specific + capabilities only, such as "proxy" or "loggingPref". + - chrome_options: this takes an instance of ChromeOptions + """ + if chrome_options is None: + options = Options() + else: + options = chrome_options + + if desired_capabilities is not None: + desired_capabilities.update(options.to_capabilities()) + else: + desired_capabilities = options.to_capabilities() + + self.service = Service(executable_path, port=port, + service_args=service_args, log_path=service_log_path) + self.service.start() + + try: + RemoteWebDriver.__init__(self, + command_executor=self.service.service_url, + desired_capabilities=desired_capabilities, + keep_alive=True) + except: + self.quit() + raise + self._is_remote = False + + def quit(self): + """ + Closes the browser and shuts down the ChromeDriver executable + that is started when starting the ChromeDriver + """ + try: + RemoteWebDriver.quit(self) + except: + # We don't care about the message because something probably has gone wrong + pass + finally: + self.service.stop() diff --git a/apps/selenium/webdriver/common/__init__.py b/apps/selenium/webdriver/common/__init__.py new file mode 100644 index 0000000000..f042f9da32 --- /dev/null +++ b/apps/selenium/webdriver/common/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google 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. diff --git a/apps/selenium/webdriver/common/action_chains.py b/apps/selenium/webdriver/common/action_chains.py new file mode 100644 index 0000000000..46af732692 --- /dev/null +++ b/apps/selenium/webdriver/common/action_chains.py @@ -0,0 +1,293 @@ +# Copyright 2011 WebDriver committers +# Copyright 2011 Google 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. + +""" +The ActionChains implementation, +""" +from selenium.webdriver.remote.command import Command +from selenium.webdriver.common.keys import Keys + +class ActionChains(object): + """ + ActionChains are a way to automate low level interactions such as + mouse movements, mouse button actions, key press, and context menu interactions. + This is useful for doing more complex actions like hover over and drag and drop. + + Generate user actions. + When you call methods for actions on the ActionChains object, + the actions are stored in a queue in the ActionChains object. + When you call perform(), the events are fired in the order they + are queued up. + + ActionChains can be used in a chain pattern:: + + menu = driver.find_element_by_css_selector(".nav") + hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1") + + ActionChains(driver).move_to_element(menu).click(hidden_submenu).perform() + + Or actions can be queued up one by one, then performed.:: + + menu = driver.find_element_by_css_selector(".nav") + hidden_submenu = driver.find_element_by_css_selector(".nav #submenu1") + + actions = ActionChains(driver) + actions.move_to_element(menu) + actions.click(hidden_submenu) + actions.perform() + + Either way, the actions are performed in the order they are called, one after + another. + """ + + def __init__(self, driver): + """ + Creates a new ActionChains. + + :Args: + - driver: The WebDriver instance which performs user actions. + """ + self._driver = driver + self._actions = [] + + def perform(self): + """ + Performs all stored actions. + """ + for action in self._actions: + action() + + def click(self, on_element=None): + """ + Clicks an element. + + :Args: + - on_element: The element to click. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.CLICK, {'button': 0})) + return self + + def click_and_hold(self, on_element=None): + """ + Holds down the left mouse button on an element. + + :Args: + - on_element: The element to mouse down. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.MOUSE_DOWN, {})) + return self + + def context_click(self, on_element=None): + """ + Performs a context-click (right click) on an element. + + :Args: + - on_element: The element to context-click. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.CLICK, {'button': 2})) + return self + + def double_click(self, on_element=None): + """ + Double-clicks an element. + + :Args: + - on_element: The element to double-click. + If None, clicks on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.DOUBLE_CLICK, {})) + return self + + def drag_and_drop(self, source, target): + """ + Holds down the left mouse button on the source element, + then moves to the target element and releases the mouse button. + + :Args: + - source: The element to mouse down. + - target: The element to mouse up. + """ + self.click_and_hold(source) + self.release(target) + return self + + def drag_and_drop_by_offset(self, source, xoffset, yoffset): + """ + Holds down the left mouse button on the source element, + then moves to the target offset and releases the mouse button. + + :Args: + - source: The element to mouse down. + - xoffset: X offset to move to. + - yoffset: Y offset to move to. + """ + self.click_and_hold(source) + self.move_by_offset(xoffset, yoffset) + self.release() + return self + + def key_down(self, value, element=None): + """ + Sends a key press only, without releasing it. + Should only be used with modifier keys (Control, Alt and Shift). + + :Args: + - value: The modifier key to send. Values are defined in `Keys` class. + - element: The element to send keys. + If None, sends a key to current focused element. + + Example, pressing ctrl+c:: + + ActionsChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + """ + if element: self.click(element) + self._actions.append(lambda: + self._driver.execute(Command.SEND_KEYS_TO_ACTIVE_ELEMENT, { + "value": self._keys_to_typing(value) })) + return self + + def key_up(self, value, element=None): + """ + Releases a modifier key. + + :Args: + - value: The modifier key to send. Values are defined in Keys class. + - element: The element to send keys. + If None, sends a key to current focused element. + + Example, pressing ctrl+c:: + + ActionsChains(driver).key_down(Keys.CONTROL).send_keys('c').key_up(Keys.CONTROL).perform() + + """ + if element: self.click(element) + self._actions.append(lambda: + self._driver.execute(Command.SEND_KEYS_TO_ACTIVE_ELEMENT, { + "value": self._keys_to_typing(value) })) + return self + + def move_by_offset(self, xoffset, yoffset): + """ + Moving the mouse to an offset from current mouse position. + + :Args: + - xoffset: X offset to move to, as a positive or negative integer. + - yoffset: Y offset to move to, as a positive or negative integer. + """ + self._actions.append(lambda: + self._driver.execute(Command.MOVE_TO, { + 'xoffset': int(xoffset), + 'yoffset': int(yoffset)})) + return self + + def move_to_element(self, to_element): + """ + Moving the mouse to the middle of an element. + + :Args: + - to_element: The WebElement to move to. + """ + self._actions.append(lambda: + self._driver.execute(Command.MOVE_TO, {'element': to_element.id})) + return self + + def move_to_element_with_offset(self, to_element, xoffset, yoffset): + """ + Move the mouse by an offset of the specified element. + Offsets are relative to the top-left corner of the element. + + :Args: + - to_element: The WebElement to move to. + - xoffset: X offset to move to. + - yoffset: Y offset to move to. + """ + self._actions.append(lambda: + self._driver.execute(Command.MOVE_TO, { + 'element': to_element.id, + 'xoffset': int(xoffset), + 'yoffset': int(yoffset)})) + return self + + def release(self, on_element=None): + """ + Releasing a held mouse button on an element. + + :Args: + - on_element: The element to mouse up. + If None, releases on current mouse position. + """ + if on_element: self.move_to_element(on_element) + self._actions.append(lambda: + self._driver.execute(Command.MOUSE_UP, {})) + return self + + def send_keys(self, *keys_to_send): + """ + Sends keys to current focused element. + + :Args: + - keys_to_send: The keys to send. Modifier keys constants can be found in the + 'Keys' class. + """ + self._actions.append(lambda: + self._driver.execute(Command.SEND_KEYS_TO_ACTIVE_ELEMENT, + { 'value': self._keys_to_typing(keys_to_send)})) + return self + + def send_keys_to_element(self, element, *keys_to_send): + """ + Sends keys to an element. + + :Args: + - element: The element to send keys. + - keys_to_send: The keys to send. Modifier keys constants can be found in the + 'Keys' class. + """ + self._actions.append(lambda: + element.send_keys(*keys_to_send)) + return self + + def _keys_to_typing(self, value): + typing = [] + for val in value: + if isinstance(val, Keys): + typing.append(val) + elif isinstance(val, int): + val = str(val) + for i in range(len(val)): + typing.append(val[i]) + else: + for i in range(len(val)): + typing.append(val[i]) + return typing + + # Context manager so ActionChains can be used in a 'with .. as' statements. + def __enter__(self): + return self # Return created instance of self. + + def __exit__(self, _type, _value, _traceback): + pass # Do nothing, does not require additional cleanup. diff --git a/apps/selenium/webdriver/common/alert.py b/apps/selenium/webdriver/common/alert.py new file mode 100644 index 0000000000..7bbec8e222 --- /dev/null +++ b/apps/selenium/webdriver/common/alert.py @@ -0,0 +1,89 @@ +#Copyright 2007-2009 WebDriver committers +#Copyright 2007-2009 Google 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. + +""" +The Alert implementation. +""" + +from selenium.webdriver.remote.command import Command + + +class Alert(object): + """ + Allows to work with alerts. + + Use this class to interact with alert prompts. It contains methods for dismissing, + accepting, inputting, and getting text from alert prompts. + + Accepting / Dismissing alert prompts:: + + Alert(driver).accept() + Alert(driver).dismiss() + + Inputting a value into an alert prompt: + + name_prompt = Alert(driver) + name_prompt.send_keys("Willian Shakesphere") + name_prompt.accept() + + + Reading a the text of a prompt for verification: + + alert_text = Alert(driver).text + self.assertEqual("Do you wish to quit?", alert_text) + + """ + + def __init__(self, driver): + """ + Creates a new Alert. + + :Args: + - driver: The WebDriver instance which performs user actions. + """ + self.driver = driver + + @property + def text(self): + """ + Gets the text of the Alert. + """ + return self.driver.execute(Command.GET_ALERT_TEXT)["value"] + + def dismiss(self): + """ + Dismisses the alert available. + """ + self.driver.execute(Command.DISMISS_ALERT) + + def accept(self): + """ + Accepts the alert available. + + Usage:: + Alert(driver).accept() # Confirm a alert dialog. + """ + self.driver.execute(Command.ACCEPT_ALERT) + + def send_keys(self, keysToSend): + """ + Send Keys to the Alert. + + :Args: + - keysToSend: The text to be sent to Alert. + + + """ + self.driver.execute(Command.SET_ALERT_VALUE, {'text': keysToSend}) diff --git a/apps/selenium/webdriver/common/by.py b/apps/selenium/webdriver/common/by.py new file mode 100644 index 0000000000..a08723d4bc --- /dev/null +++ b/apps/selenium/webdriver/common/by.py @@ -0,0 +1,39 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google 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. + +""" +The By implementation. +""" + +class By(object): + """ + Set of supported locator strategies. + """ + + ID = "id" + XPATH = "xpath" + LINK_TEXT = "link text" + PARTIAL_LINK_TEXT = "partial link text" + NAME = "name" + TAG_NAME = "tag name" + CLASS_NAME = "class name" + CSS_SELECTOR = "css selector" + + @classmethod + def is_valid(cls, by): + for attr in dir(cls): + if by == getattr(cls, attr): + return True + return False diff --git a/apps/selenium/webdriver/common/desired_capabilities.py b/apps/selenium/webdriver/common/desired_capabilities.py new file mode 100644 index 0000000000..d6309d58db --- /dev/null +++ b/apps/selenium/webdriver/common/desired_capabilities.py @@ -0,0 +1,123 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google 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. + +""" +The Desired Capabilities implementation. +""" + +class DesiredCapabilities(object): + """ + Set of default supported desired capabilities. + + Use this as a starting point for creating a desired capabilities object for + requesting remote webdrivers for connecting to selenium server or selenium grid. + + + Usage Example: + + from selenium import webdriver + + selenium_grid_url = "http://198.0.0.1:4444/wd/hub" + + # Create a desired capabilities object as a starting point. + capabilities = DesiredCapabilities.FIREFOX.copy() + capabilities['platform'] = "WINDOWS" + capabilities['version'] = "10" + + # Instantiate an instance of Remote WebDriver with the desired capabilities. + driver = webdriver.Remote(desired_capabilities=capabilities, + command_executor=selenium_grid_url) + + Note: Always use '.copy()' on the DesiredCapabilities object to avoid the side + effects of altering the Global class instance. + + """ + + FIREFOX = { + "browserName": "firefox", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + + INTERNETEXPLORER = { + "browserName": "internet explorer", + "version": "", + "platform": "WINDOWS", + "javascriptEnabled": True, + } + + CHROME = { + "browserName": "chrome", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + + OPERA = { + "browserName": "opera", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + + SAFARI = { + "browserName": "safari", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + + HTMLUNIT = { + "browserName": "htmlunit", + "version": "", + "platform": "ANY", + } + + HTMLUNITWITHJS = { + "browserName": "htmlunit", + "version": "firefox", + "platform": "ANY", + "javascriptEnabled": True, + } + + IPHONE = { + "browserName": "iPhone", + "version": "", + "platform": "MAC", + "javascriptEnabled": True, + } + + IPAD = { + "browserName": "iPad", + "version": "", + "platform": "MAC", + "javascriptEnabled": True, + } + + ANDROID = { + "browserName": "android", + "version": "", + "platform": "ANDROID", + "javascriptEnabled": True, + } + + PHANTOMJS = { + "browserName":"phantomjs", + "version": "", + "platform": "ANY", + "javascriptEnabled": True, + } + diff --git a/apps/selenium/webdriver/common/html5/__init__.py b/apps/selenium/webdriver/common/html5/__init__.py new file mode 100644 index 0000000000..e1e59108b2 --- /dev/null +++ b/apps/selenium/webdriver/common/html5/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2008-2013 WebDriver committers +# Copyright 2008-2009 Google 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. diff --git a/apps/selenium/webdriver/common/html5/application_cache.py b/apps/selenium/webdriver/common/html5/application_cache.py new file mode 100644 index 0000000000..cda47b6d04 --- /dev/null +++ b/apps/selenium/webdriver/common/html5/application_cache.py @@ -0,0 +1,32 @@ +""" +The ApplicationCache implementaion. +""" + +from selenium.webdriver.remote.command import Command + +class ApplicationCache(object): + """ + """ + + UNCACHED = 0 + IDLE = 1 + CHECKING = 2 + DOWNLOADING = 3 + UPDATE_READY = 4 + OBSOLETE = 5 + + def __init__(self, driver): + """ + Creates a new Aplication Cache. + + :Args: + - driver: The WebDriver instance which performs user actions. + """ + self.driver = driver + + @property + def status(self): + """ + Returns a current status of application cache. + """ + return self.driver.execute(Command.GET_APP_CACHE_STATUS)['value'] diff --git a/apps/selenium/webdriver/common/keys.py b/apps/selenium/webdriver/common/keys.py new file mode 100644 index 0000000000..07ac78d968 --- /dev/null +++ b/apps/selenium/webdriver/common/keys.py @@ -0,0 +1,93 @@ +# copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google Inc. +# +# Licensed under the Apache License Version 2.0 = uthe "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. + +""" +The Keys implementation. +""" + +from __future__ import unicode_literals + +class Keys(object): + """ + Set of special keys codes. + """ + + NULL = '\ue000' + CANCEL = '\ue001' # ^break + HELP = '\ue002' + BACKSPACE = '\ue003' + BACK_SPACE = BACKSPACE + TAB = '\ue004' + CLEAR = '\ue005' + RETURN = '\ue006' + ENTER = '\ue007' + SHIFT = '\ue008' + LEFT_SHIFT = SHIFT + CONTROL = '\ue009' + LEFT_CONTROL = CONTROL + ALT = '\ue00a' + LEFT_ALT = ALT + PAUSE = '\ue00b' + ESCAPE = '\ue00c' + SPACE = '\ue00d' + PAGE_UP = '\ue00e' + PAGE_DOWN = '\ue00f' + END = '\ue010' + HOME = '\ue011' + LEFT = '\ue012' + ARROW_LEFT = LEFT + UP = '\ue013' + ARROW_UP = UP + RIGHT = '\ue014' + ARROW_RIGHT = RIGHT + DOWN = '\ue015' + ARROW_DOWN = DOWN + INSERT = '\ue016' + DELETE = '\ue017' + SEMICOLON = '\ue018' + EQUALS = '\ue019' + + NUMPAD0 = '\ue01a' # number pad keys + NUMPAD1 = '\ue01b' + NUMPAD2 = '\ue01c' + NUMPAD3 = '\ue01d' + NUMPAD4 = '\ue01e' + NUMPAD5 = '\ue01f' + NUMPAD6 = '\ue020' + NUMPAD7 = '\ue021' + NUMPAD8 = '\ue022' + NUMPAD9 = '\ue023' + MULTIPLY = '\ue024' + ADD = '\ue025' + SEPARATOR = '\ue026' + SUBTRACT = '\ue027' + DECIMAL = '\ue028' + DIVIDE = '\ue029' + + F1 = '\ue031' # function keys + F2 = '\ue032' + F3 = '\ue033' + F4 = '\ue034' + F5 = '\ue035' + F6 = '\ue036' + F7 = '\ue037' + F8 = '\ue038' + F9 = '\ue039' + F10 = '\ue03a' + F11 = '\ue03b' + F12 = '\ue03c' + + META = '\ue03d' + COMMAND = '\ue03d' diff --git a/apps/selenium/webdriver/common/proxy.py b/apps/selenium/webdriver/common/proxy.py new file mode 100644 index 0000000000..3ca4bb8054 --- /dev/null +++ b/apps/selenium/webdriver/common/proxy.py @@ -0,0 +1,314 @@ +""" +The Proxy implementation. +""" + +class ProxyTypeFactory: + """ + Factory for proxy types. + """ + + @staticmethod + def make(ff_value, string): + return {'ff_value': ff_value, 'string': string} + +class ProxyType: + """ + Set of possible types of proxy. + + Each proxy type has 2 properties: + 'ff_value' is value of Firefox profile preference, + 'string' is id of proxy type. + """ + + DIRECT = ProxyTypeFactory.make(0, 'DIRECT') # Direct connection, no proxy (default on Windows). + MANUAL = ProxyTypeFactory.make(1, 'MANUAL') # Manual proxy settings (e.g., for httpProxy). + PAC = ProxyTypeFactory.make(2, 'PAC') # Proxy autoconfiguration from URL. + RESERVED_1 = ProxyTypeFactory.make(3, 'RESERVED1') # Never used. + AUTODETECT = ProxyTypeFactory.make(4, 'AUTODETECT') # Proxy autodetection (presumably with WPAD). + SYSTEM = ProxyTypeFactory.make(5, 'SYSTEM') # Use system settings (default on Linux). + UNSPECIFIED = ProxyTypeFactory.make(6, 'UNSPECIFIED') # Not initialized (for internal use). + + @classmethod + def load(cls, value): + if isinstance(value, dict) and 'string' in value: + value = value['string'] + value = str(value).upper() + for attr in dir(cls): + attr_value = getattr(cls, attr) + if isinstance(attr_value, dict) and 'string' in attr_value and \ + attr_value['string'] is not None and attr_value['string'] == value: + return attr_value + raise Exception("No proxy type is found for %s" % (value)) + +class Proxy(object): + """ + Proxy contains information about proxy type and necessary proxy settings. + """ + + proxyType = ProxyType.UNSPECIFIED + autodetect = False + ftpProxy = '' + httpProxy = '' + noProxy = '' + proxyAutoconfigUrl = '' + sslProxy = '' + socksProxy = '' + socksUsername = '' + socksPassword = '' + + def __init__(self, raw=None): + """ + Creates a new Proxy. + + :Args: + - raw: raw proxy data. If None, default class values are used. + """ + if raw is not None: + if 'proxyType' in raw and raw['proxyType'] is not None: + self.proxy_type = ProxyType.load(raw['proxyType']) + if 'ftpProxy' in raw and raw['ftpProxy'] is not None: + self.ftp_proxy = raw['ftpProxy'] + if 'httpProxy' in raw and raw['httpProxy'] is not None: + self.http_proxy = raw['httpProxy'] + if 'noProxy' in raw and raw['noProxy'] is not None: + self.no_proxy = raw['noProxy'] + if 'proxyAutoconfigUrl' in raw and raw['proxyAutoconfigUrl'] is not None: + self.proxy_autoconfig_url = raw['proxyAutoconfigUrl'] + if 'sslProxy' in raw and raw['sslProxy'] is not None: + self.sslProxy = raw['sslProxy'] + if 'autodetect' in raw and raw['autodetect'] is not None: + self.auto_detect = raw['autodetect'] + if 'socksProxy' in raw and raw['socksProxy'] is not None: + self.socksProxy = raw['socksProxy'] + if 'socksUsername' in raw and raw['socksUsername'] is not None: + self.socksUsername = raw['socksUsername'] + if 'socksPassword' in raw and raw['socksPassword'] is not None: + self.socksPassword = raw['socksPassword'] + + @property + def proxy_type(self): + """ + Returns proxy type as `ProxyType`. + """ + return self.proxyType + + @proxy_type.setter + def proxy_type(self, value): + """ + Sets proxy type. + + :Args: + - value: The proxy type. + """ + self._verify_proxy_type_compatibility(value) + self.proxyType = value + + @property + def auto_detect(self): + """ + Returns autodetect setting. + """ + return self.autodetect + + @auto_detect.setter + def auto_detect(self, value): + """ + Sets autodetect setting. + + :Args: + - value: The autodetect value. + """ + if isinstance(value, bool): + if self.autodetect is not value: + self._verify_proxy_type_compatibility(ProxyType.AUTODETECT) + self.proxyType = ProxyType.AUTODETECT + self.autodetect = value + else: + raise ValueError("Autodetect proxy value needs to be a boolean") + + @property + def ftp_proxy(self): + """ + Returns ftp proxy setting. + """ + return self.ftpProxy + + @ftp_proxy.setter + def ftp_proxy(self, value): + """ + Sets ftp proxy setting. + + :Args: + - value: The ftp proxy value. + """ + self._verify_proxy_type_compatibility(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.ftpProxy = value + + @property + def http_proxy(self): + """ + Returns http proxy setting. + """ + return self.httpProxy + + @http_proxy.setter + def http_proxy(self, value): + """ + Sets http proxy setting. + + :Args: + - value: The http proxy value. + """ + self._verify_proxy_type_compatibility(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.httpProxy = value + + @property + def no_proxy(self): + """ + Returns noproxy setting. + """ + return self.noProxy + + @no_proxy.setter + def no_proxy(self, value): + """ + Sets noproxy setting. + + :Args: + - value: The noproxy value. + """ + self._verify_proxy_type_compatibility(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.noProxy = value + + @property + def proxy_autoconfig_url(self): + """ + Returns proxy autoconfig url setting. + """ + return self.proxyAutoconfigUrl + + @proxy_autoconfig_url.setter + def proxy_autoconfig_url(self, value): + """ + Sets proxy autoconfig url setting. + + :Args: + - value: The proxy autoconfig url value. + """ + self._verify_proxy_type_compatibility(ProxyType.PAC) + self.proxyType = ProxyType.PAC + self.proxyAutoconfigUrl = value + + @property + def ssl_proxy(self): + """ + Returns https proxy setting. + """ + return self.sslProxy + + @ssl_proxy.setter + def ssl_proxy(self, value): + """ + Sets https proxy setting. + + :Args: + - value: The https proxy value. + """ + self._verify_proxy_type_compatibility(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.sslProxy = value + + @property + def socks_proxy(self): + """ + Returns socks proxy setting. + """ + return self.socksProxy + + @socks_proxy.setter + def socks_proxy(self, value): + """ + Sets socks proxy setting. + + :Args: + - value: The socks proxy value. + """ + self._verify_proxy_type_compatibility(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.socksProxy = value + + @property + def socks_username(self): + """ + Returns socks proxy username setting. + """ + return self.socksUsername + + @socks_username.setter + def socks_username(self, value): + """ + Sets socks proxy username setting. + + :Args: + - value: The socks proxy username value. + """ + self._verify_proxy_type_compatibility(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.socksUsername = value + + @property + def socks_password(self): + """ + Returns socks proxy password setting. + """ + return self.socksPassword + + @socks_password.setter + def socks_password(self, value): + """ + Sets socks proxy password setting. + + :Args: + - value: The socks proxy password value. + """ + self._verify_proxy_type_compatibility(ProxyType.MANUAL) + self.proxyType = ProxyType.MANUAL + self.socksPassword = value + + def _verify_proxy_type_compatibility(self, compatibleProxy): + if self.proxyType != ProxyType.UNSPECIFIED and self.proxyType != compatibleProxy: + raise Exception(" Specified proxy type (%s) not compatible with current setting (%s)" % \ + (compatibleProxy, self.proxyType)) + + + def add_to_capabilities(self, capabilities): + """ + Adds proxy information as capability in specified capabilities. + + :Args: + - capabilities: The capabilities to which proxy will be added. + """ + proxy_caps = {} + proxy_caps['proxyType'] = self.proxyType['string'] + if self.autodetect: + proxy_caps['autodetect'] = self.autodetect + if self.ftpProxy: + proxy_caps['ftpProxy'] = self.ftpProxy + if self.httpProxy: + proxy_caps['httpProxy'] = self.httpProxy + if self.proxyAutoconfigUrl: + proxy_caps['proxyAutoconfigUrl'] = self.proxyAutoconfigUrl + if self.sslProxy: + proxy_caps['sslProxy'] = self.sslProxy + if self.noProxy: + proxy_caps['noProxy'] = self.noProxy + if self.socksProxy: + proxy_caps['socksProxy'] = self.socksProxy + if self.socksUsername: + proxy_caps['socksUsername'] = self.socksUsername + if self.socksPassword: + proxy_caps['socksPassword'] = self.socksPassword + capabilities['proxy'] = proxy_caps diff --git a/apps/selenium/webdriver/common/touch_actions.py b/apps/selenium/webdriver/common/touch_actions.py new file mode 100644 index 0000000000..6f5837abf8 --- /dev/null +++ b/apps/selenium/webdriver/common/touch_actions.py @@ -0,0 +1,174 @@ +""" +The Touch Actions implementation +""" + +from selenium.webdriver.remote.command import Command + +class TouchActions(object): + """ + Generate touch actions. Works like ActionChains; actions are stored in the + TouchActions object and are fired with perform(). + """ + + def __init__(self, driver): + """ + Creates a new TouchActions object. + + :Args: + - driver: The WebDriver instance which performs user actions. + It should be with touchscreen enabled. + """ + self._driver = driver + self._actions = [] + + def perform(self): + """ + Performs all stored actions. + """ + for action in self._actions: + action() + + def tap(self, on_element): + """ + Taps on a given element. + + :Args: + - on_element: The element to tap. + """ + self._actions.append(lambda: + self._driver.execute(Command.SINGLE_TAP, {'element': on_element.id})) + return self + + def double_tap(self, on_element): + """ + Double taps on a given element. + + :Args: + - on_element: The element to tap. + """ + self._actions.append(lambda: + self._driver.execute(Command.DOUBLE_TAP, {'element': on_element.id})) + return self + + def tap_and_hold(self, xcoord, ycoord): + """ + Touch down at given coordinates. + + :Args: + - xcoord: X Coordinate to touch down. + - ycoord: Y Coordinate to touch down. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_DOWN, { + 'x': int(xcoord), + 'y': int(ycoord)})) + return self + + def move(self, xcoord, ycoord): + """ + Move held tap to specified location. + + :Args: + - xcoord: X Coordinate to move. + - ycoord: Y Coordinate to move. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_MOVE, { + 'x': int(xcoord), + 'y': int(ycoord)})) + return self + + def release(self, xcoord, ycoord): + """ + Release previously issued tap 'and hold' command at specified location. + + :Args: + - xcoord: X Coordinate to release. + - ycoord: Y Coordinate to release. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_UP, { + 'x': int(xcoord), + 'y': int(ycoord)})) + return self + + def scroll(self, xoffset, yoffset): + """ + Touch and scroll, moving by xoffset and yoffset. + + :Args: + - xoffset: X offset to scroll to. + - yoffset: Y offset to scroll to. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_SCROLL, { + 'xoffset': int(xoffset), + 'yoffset': int(yoffset)})) + return self + + def scroll_from_element(self, on_element, xoffset, yoffset): + """ + Touch and scroll starting at on_element, moving by xoffset and yoffset. + + :Args: + - on_element: The element where scroll starts. + - xoffset: X offset to scroll to. + - yoffset: Y offset to scroll to. + """ + self._actions.append(lambda: + self._driver.execute(Command.TOUCH_SCROLL, { + 'element': on_element.id, + 'xoffset': int(xoffset), + 'yoffset': int(yoffset)})) + return self + + def long_press(self, on_element): + """ + Long press on an element. + + :Args: + - on_element: The element to long press. + """ + self._actions.append(lambda: + self._driver.execute(Command.LONG_PRESS, {'element': on_element.id})) + return self + + def flick(self, xspeed, yspeed): + """ + Flicks, starting anywhere on the screen. + + :Args: + - xspeed: The X speed in pixels per second. + - yspeed: The Y speed in pixels per second. + """ + self._actions.append(lambda: + self._driver.execute(Command.FLICK, { + 'xspeed': int(xspeed), + 'yspeed': int(yspeed)})) + return self + + def flick_element(self, on_element, xoffset, yoffset, speed): + """ + Flick starting at on_element, and moving by the xoffset and yoffset + with specified speed. + + :Args: + - on_element: Flick will start at center of element. + - xoffset: X offset to flick to. + - yoffset: Y offset to flick to. + - speed: Pixels per second to flick. + """ + self._actions.append(lambda: + self._driver.execute(Command.FLICK, { + 'element': on_element.id, + 'xoffset': int(xoffset), + 'yoffset': int(yoffset), + 'speed': int(speed)})) + return self + + # Context manager so TouchActions can be used in a 'with .. as' statements. + def __enter__(self): + return self # Return created instance of self. + + def __exit__(self, _type, _value, _traceback): + pass # Do nothing, does not require additional cleanup. \ No newline at end of file diff --git a/apps/selenium/webdriver/common/utils.py b/apps/selenium/webdriver/common/utils.py new file mode 100644 index 0000000000..084e3a44ae --- /dev/null +++ b/apps/selenium/webdriver/common/utils.py @@ -0,0 +1,69 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 Google 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. + +""" +The Utils methods. +""" +import socket + + +def free_port(): + """ + Determines a free port using sockets. + """ + free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + free_socket.bind(('127.0.0.1', 0)) + free_socket.listen(5) + port = free_socket.getsockname()[1] + free_socket.close() + return port + +def is_connectable(port): + """ + Tries to connect to the server at port to see if it is running. + + :Args: + - port: The port to connect. + """ + try: + socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + socket_.settimeout(1) + socket_.connect(("localhost", port)) + socket_.close() + return True + except socket.error: + return False + +def is_url_connectable(port): + """ + Tries to connect to the HTTP server at /status path + and specified port to see if it responds successfully. + + :Args: + - port: The port to connect. + """ + try: + from urllib import request as url_request + except ImportError: + import urllib2 as url_request + + try: + res = url_request.urlopen("http://localhost:%s/status" % port) + if res.getcode() == 200: + return True + else: + return False + except: + return False diff --git a/apps/selenium/webdriver/firefox/__init__.py b/apps/selenium/webdriver/firefox/__init__.py new file mode 100644 index 0000000000..a8bb36bb9b --- /dev/null +++ b/apps/selenium/webdriver/firefox/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2010 WebDriver committers +# Copyright 2010 Google 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. diff --git a/apps/selenium/webdriver/firefox/amd64/x_ignore_nofocus.so b/apps/selenium/webdriver/firefox/amd64/x_ignore_nofocus.so new file mode 100644 index 0000000000..916e530f3e Binary files /dev/null and b/apps/selenium/webdriver/firefox/amd64/x_ignore_nofocus.so differ diff --git a/apps/selenium/webdriver/firefox/extension_connection.py b/apps/selenium/webdriver/firefox/extension_connection.py new file mode 100644 index 0000000000..2e891659a6 --- /dev/null +++ b/apps/selenium/webdriver/firefox/extension_connection.py @@ -0,0 +1,80 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 Google 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. + +import logging +import time + +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.common import utils +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.remote_connection import RemoteConnection +from selenium.webdriver.firefox.firefox_binary import FirefoxBinary + + +LOGGER = logging.getLogger(__name__) +PORT = 0 # +HOST = None +_URL = "" +class ExtensionConnection(RemoteConnection): + def __init__(self, host, firefox_profile, firefox_binary=None, timeout=30): + self.profile = firefox_profile + self.binary = firefox_binary + HOST = host + if self.binary is None: + self.binary = FirefoxBinary() + + if HOST is None: + HOST = "127.0.0.1" + + PORT = utils.free_port() + self.profile.port = PORT + self.profile.update_preferences() + + self.profile.add_extension() + + self.binary.launch_browser(self.profile) + _URL = "http://%s:%d/hub" % (HOST, PORT) + RemoteConnection.__init__( + self, _URL, keep_alive=True) + + def quit(self, sessionId=None): + self.execute(Command.QUIT, {'sessionId':sessionId}) + while self.is_connectable(): + LOGGER.info("waiting to quit") + time.sleep(1) + + def connect(self): + """Connects to the extension and retrieves the session id.""" + return self.execute(Command.NEW_SESSION, + {'desiredCapabilities': DesiredCapabilities.FIREFOX}) + + @classmethod + def connect_and_quit(self): + """Connects to an running browser and quit immediately.""" + self._request('%s/extensions/firefox/quit' % _URL) + + @classmethod + def is_connectable(self): + """Trys to connect to the extension but do not retrieve context.""" + utils.is_connectable(self.port) + +class ExtensionConnectionError(Exception): + """An internal error occurred int the extension. + + Might be caused by bad input or bugs in webdriver + """ + pass + + diff --git a/apps/selenium/webdriver/firefox/firefox_binary.py b/apps/selenium/webdriver/firefox/firefox_binary.py new file mode 100644 index 0000000000..7bb5e9b313 --- /dev/null +++ b/apps/selenium/webdriver/firefox/firefox_binary.py @@ -0,0 +1,199 @@ +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 Google 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. + + +import os +import platform +from subprocess import Popen, PIPE, STDOUT +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils +import time + + +class FirefoxBinary(object): + + NO_FOCUS_LIBRARY_NAME = "x_ignore_nofocus.so" + + def __init__(self, firefox_path=None, log_file=None): + """ + Creates a new instance of Firefox binary. + + :Args: + - firefox_path - Path to the Firefox executable. By default, it will be detected from the standard locations. + - log_file - A file object to redirect the firefox process output to. It can be sys.stdout. + Please note that with parallel run the output won't be synchronous. + By default, it will be redirected to subprocess.PIPE. + """ + self._start_cmd = firefox_path + self._log_file = log_file or PIPE + self.command_line = None + if self._start_cmd is None: + self._start_cmd = self._get_firefox_start_cmd() + # Rather than modifying the environment of the calling Python process + # copy it and modify as needed. + self._firefox_env = os.environ.copy() + self._firefox_env["MOZ_CRASHREPORTER_DISABLE"] = "1" + self._firefox_env["MOZ_NO_REMOTE"] = "1" + self._firefox_env["NO_EM_RESTART"] = "1" + + def add_command_line_options(self, *args): + self.command_line = args + + def launch_browser(self, profile): + """Launches the browser for the given profile name. + It is assumed the profile already exists. + """ + self.profile = profile + + self._start_from_profile_path(self.profile.path) + self._wait_until_connectable() + + def kill(self): + """Kill the browser. + + This is useful when the browser is stuck. + """ + if self.process: + self.process.kill() + self.process.wait() + + def _start_from_profile_path(self, path): + self._firefox_env["XRE_PROFILE_PATH"] = path + + if platform.system().lower() == 'linux': + self._modify_link_library_path() + command = [self._start_cmd, "-silent"] + if self.command_line is not None: + for cli in self.command_line: + command.append(cli) + + Popen(command, stdout=self._log_file, stderr=STDOUT, + env=self._firefox_env).communicate() + command[1] = '-foreground' + self.process = Popen( + command, stdout=self._log_file, stderr=STDOUT, + env=self._firefox_env) + + def _get_firefox_output(self): + return self.process.communicate()[0] + + def _wait_until_connectable(self): + """Blocks until the extension is connectable in the firefox.""" + count = 0 + while not utils.is_connectable(self.profile.port): + if self.process.poll() is not None: + # Browser has exited + raise WebDriverException("The browser appears to have exited " + "before we could connect. The output was: %s" % + self._get_firefox_output()) + if count == 30: + self.kill() + raise WebDriverException("Can't load the profile. Profile " + "Dir: %s Firefox output: %s" % ( + self.profile.path, self._get_firefox_output())) + count += 1 + time.sleep(1) + return True + + def _find_exe_in_registry(self): + try: + from _winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE + except ImportError: + from winreg import OpenKey, QueryValue, HKEY_LOCAL_MACHINE + import shlex + keys = ( + r"SOFTWARE\Classes\FirefoxHTML\shell\open\command", + r"SOFTWARE\Classes\Applications\firefox.exe\shell\open\command" + ) + command = "" + for path in keys: + try: + key = OpenKey(HKEY_LOCAL_MACHINE, path) + command = QueryValue(key, "") + break + except OSError: + pass + else: + return "" + + if not command: + return "" + + return shlex.split(command)[0] + + def _get_firefox_start_cmd(self): + """Return the command to start firefox.""" + start_cmd = "" + if platform.system() == "Darwin": + start_cmd = ("/Applications/Firefox.app/Contents/MacOS/firefox-bin") + elif platform.system() == "Windows": + start_cmd = (self._find_exe_in_registry() or + self._default_windows_location()) + elif platform.system() == 'Java' and os._name == 'nt': + start_cmd = self._default_windows_location() + else: + for ffname in ["firefox", "iceweasel"]: + start_cmd = self.which(ffname) + if start_cmd is not None: + break + else: + # couldn't find firefox on the system path + raise RuntimeError("Could not find firefox in your system PATH." + + " Please specify the firefox binary location or install firefox") + return start_cmd + + def _default_windows_location(self): + program_files = [os.getenv("PROGRAMFILES", r"C:\Program Files"), + os.getenv("PROGRAMFILES(X86)", r"C:\Program Files (x86)")] + for path in program_files: + binary_path = os.path.join(path, r"Mozilla Firefox\firefox.exe") + if os.access(binary_path, os.X_OK): + return binary_path + return "" + + def _modify_link_library_path(self): + existing_ld_lib_path = os.environ.get('LD_LIBRARY_PATH', '') + + new_ld_lib_path = self._extract_and_check( + self.profile, self.NO_FOCUS_LIBRARY_NAME, "x86", "amd64") + + new_ld_lib_path += existing_ld_lib_path + + self._firefox_env["LD_LIBRARY_PATH"] = new_ld_lib_path + self._firefox_env['LD_PRELOAD'] = self.NO_FOCUS_LIBRARY_NAME + + def _extract_and_check(self, profile, no_focus_so_name, x86, amd64): + + paths = [x86, amd64] + built_path = "" + for path in paths: + library_path = os.path.join(profile.path, path) + os.makedirs(library_path) + import shutil + shutil.copy(os.path.join(os.path.dirname(__file__), path, + self.NO_FOCUS_LIBRARY_NAME), + library_path) + built_path += library_path + ":" + + return built_path + + def which(self, fname): + """Returns the fully qualified path by searching Path of the given + name""" + for pe in os.environ['PATH'].split(os.pathsep): + checkname = os.path.join(pe, fname) + if os.access(checkname, os.X_OK) and not os.path.isdir(checkname): + return checkname + return None diff --git a/apps/selenium/webdriver/firefox/firefox_profile.py b/apps/selenium/webdriver/firefox/firefox_profile.py new file mode 100644 index 0000000000..2e6fa8fef1 --- /dev/null +++ b/apps/selenium/webdriver/firefox/firefox_profile.py @@ -0,0 +1,371 @@ +# Copyright 2014 Software Freedom Conservancy +# Copyright 2008-2011 WebDriver committers +# Copyright 2008-2011 Google 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. +from __future__ import with_statement + +import base64 +import copy +import json +import os +import re +import shutil +import sys +import tempfile +import zipfile + +try: + from cStringIO import StringIO as BytesIO + bytes = str + str = basestring +except ImportError: + from io import BytesIO + +from xml.dom import minidom +from selenium.webdriver.common.proxy import ProxyType +from selenium.common.exceptions import WebDriverException + + +WEBDRIVER_EXT = "webdriver.xpi" +WEBDRIVER_PREFERENCES = "webdriver_prefs.json" +EXTENSION_NAME = "fxdriver@googlecode.com" + + +class AddonFormatError(Exception): + """Exception for not well-formed add-on manifest files""" + + +class FirefoxProfile(object): + ANONYMOUS_PROFILE_NAME = "WEBDRIVER_ANONYMOUS_PROFILE" + DEFAULT_PREFERENCES = None + + def __init__(self, profile_directory=None): + """ + Initialises a new instance of a Firefox Profile + + :args: + - profile_directory: Directory of profile that you want to use. + This defaults to None and will create a new + directory when object is created. + """ + if not FirefoxProfile.DEFAULT_PREFERENCES: + with open(os.path.join(os.path.dirname(__file__), + WEBDRIVER_PREFERENCES)) as default_prefs: + FirefoxProfile.DEFAULT_PREFERENCES = json.load(default_prefs) + + self.default_preferences = copy.deepcopy( + FirefoxProfile.DEFAULT_PREFERENCES['mutable']) + self.native_events_enabled = True + self.profile_dir = profile_directory + self.tempfolder = None + if self.profile_dir is None: + self.profile_dir = self._create_tempfolder() + else: + self.tempfolder = tempfile.mkdtemp() + newprof = os.path.join(self.tempfolder, "webdriver-py-profilecopy") + shutil.copytree(self.profile_dir, newprof, + ignore=shutil.ignore_patterns("parent.lock", "lock", ".parentlock")) + self.profile_dir = newprof + self._read_existing_userjs(os.path.join(self.profile_dir, "user.js")) + self.extensionsDir = os.path.join(self.profile_dir, "extensions") + self.userPrefs = os.path.join(self.profile_dir, "user.js") + + #Public Methods + def set_preference(self, key, value): + """ + sets the preference that we want in the profile. + """ + self.default_preferences[key] = value + + def add_extension(self, extension=WEBDRIVER_EXT): + self._install_extension(extension) + + def update_preferences(self): + for key, value in FirefoxProfile.DEFAULT_PREFERENCES['frozen'].items(): + self.default_preferences[key] = value + self._write_user_prefs(self.default_preferences) + + #Properties + + @property + def path(self): + """ + Gets the profile directory that is currently being used + """ + return self.profile_dir + + @property + def port(self): + """ + Gets the port that WebDriver is working on + """ + return self._port + + @port.setter + def port(self, port): + """ + Sets the port that WebDriver will be running on + """ + if not isinstance(port, int): + raise WebDriverException("Port needs to be an integer") + try: + port = int(port) + if port < 1 or port > 65535: + raise WebDriverException("Port number must be in the range 1..65535") + except (ValueError, TypeError) as e: + raise WebDriverException("Port needs to be an integer") + self._port = port + self.set_preference("webdriver_firefox_port", self._port) + + @property + def accept_untrusted_certs(self): + return self.default_preferences["webdriver_accept_untrusted_certs"] + + @accept_untrusted_certs.setter + def accept_untrusted_certs(self, value): + if value not in [True, False]: + raise WebDriverException("Please pass in a Boolean to this call") + self.set_preference("webdriver_accept_untrusted_certs", value) + + @property + def assume_untrusted_cert_issuer(self): + return self.default_preferences["webdriver_assume_untrusted_issuer"] + + @assume_untrusted_cert_issuer.setter + def assume_untrusted_cert_issuer(self, value): + if value not in [True, False]: + raise WebDriverException("Please pass in a Boolean to this call") + + self.set_preference("webdriver_assume_untrusted_issuer", value) + + @property + def native_events_enabled(self): + return self.default_preferences['webdriver_enable_native_events'] + + @native_events_enabled.setter + def native_events_enabled(self, value): + if value not in [True, False]: + raise WebDriverException("Please pass in a Boolean to this call") + self.set_preference("webdriver_enable_native_events", value) + + @property + def encoded(self): + """ + A zipped, base64 encoded string of profile directory + for use with remote WebDriver JSON wire protocol + """ + fp = BytesIO() + zipped = zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED) + path_root = len(self.path) + 1 # account for trailing slash + for base, dirs, files in os.walk(self.path): + for fyle in files: + filename = os.path.join(base, fyle) + zipped.write(filename, filename[path_root:]) + zipped.close() + return base64.encodestring(fp.getvalue()) + + def set_proxy(self, proxy): + import warnings + + warnings.warn( + "This method has been deprecated. Please pass in the proxy object to the Driver Object", + DeprecationWarning) + if proxy is None: + raise ValueError("proxy can not be None") + + if proxy.proxy_type is ProxyType.UNSPECIFIED: + return + + self.set_preference("network.proxy.type", proxy.proxy_type['ff_value']) + + if proxy.proxy_type is ProxyType.MANUAL: + self.set_preference("network.proxy.no_proxies_on", proxy.no_proxy) + self._set_manual_proxy_preference("ftp", proxy.ftp_proxy) + self._set_manual_proxy_preference("http", proxy.http_proxy) + self._set_manual_proxy_preference("ssl", proxy.ssl_proxy) + self._set_manual_proxy_preference("socks", proxy.socks_proxy) + elif proxy.proxy_type is ProxyType.PAC: + self.set_preference("network.proxy.autoconfig_url", proxy.proxy_autoconfig_url) + + def _set_manual_proxy_preference(self, key, setting): + if setting is None or setting is '': + return + + host_details = setting.split(":") + self.set_preference("network.proxy.%s" % key, host_details[0]) + if len(host_details) > 1: + self.set_preference("network.proxy.%s_port" % key, int(host_details[1])) + + def _create_tempfolder(self): + """ + Creates a temp folder to store User.js and the extension + """ + return tempfile.mkdtemp() + + def _write_user_prefs(self, user_prefs): + """ + writes the current user prefs dictionary to disk + """ + with open(self.userPrefs, "w") as f: + for key, value in user_prefs.items(): + f.write('user_pref("%s", %s);\n' % (key, json.dumps(value))) + + def _read_existing_userjs(self, userjs): + import warnings + + PREF_RE = re.compile(r'user_pref\("(.*)",\s(.*)\)') + try: + with open(userjs) as f: + for usr in f: + matches = re.search(PREF_RE, usr) + try: + self.default_preferences[matches.group(1)] = json.loads(matches.group(2)) + except: + warnings.warn("(skipping) failed to json.loads existing preference: " + + matches.group(1) + matches.group(2)) + except: + # The profile given hasn't had any changes made, i.e no users.js + pass + + def _install_extension(self, addon, unpack=True): + """ + Installs addon from a filepath, url + or directory of addons in the profile. + - path: url, path to .xpi, or directory of addons + - unpack: whether to unpack unless specified otherwise in the install.rdf + """ + if addon == WEBDRIVER_EXT: + addon = os.path.join(os.path.dirname(__file__), WEBDRIVER_EXT) + + tmpdir = None + xpifile = None + if addon.endswith('.xpi'): + tmpdir = tempfile.mkdtemp(suffix='.' + os.path.split(addon)[-1]) + compressed_file = zipfile.ZipFile(addon, 'r') + for name in compressed_file.namelist(): + if name.endswith('/'): + os.makedirs(os.path.join(tmpdir, name)) + else: + if not os.path.isdir(os.path.dirname(os.path.join(tmpdir, name))): + os.makedirs(os.path.dirname(os.path.join(tmpdir, name))) + data = compressed_file.read(name) + with open(os.path.join(tmpdir, name), 'wb') as f: + f.write(data) + xpifile = addon + addon = tmpdir + + # determine the addon id + addon_details = self._addon_details(addon) + addon_id = addon_details.get('id') + assert addon_id, 'The addon id could not be found: %s' % addon + + # copy the addon to the profile + extensions_path = os.path.join(self.profile_dir, 'extensions') + addon_path = os.path.join(extensions_path, addon_id) + if not unpack and not addon_details['unpack'] and xpifile: + if not os.path.exists(extensions_path): + os.makedirs(extensions_path) + shutil.copy(xpifile, addon_path + '.xpi') + else: + shutil.copytree(addon, addon_path, symlinks=True) + + # remove the temporary directory, if any + if tmpdir: + shutil.rmtree(tmpdir) + + def _addon_details(self, addon_path): + """ + Returns a dictionary of details about the addon. + + :param addon_path: path to the add-on directory or XPI + + Returns:: + + {'id': u'rainbow@colors.org', # id of the addon + 'version': u'1.4', # version of the addon + 'name': u'Rainbow', # name of the addon + 'unpack': False } # whether to unpack the addon + """ + + details = { + 'id': None, + 'unpack': False, + 'name': None, + 'version': None + } + + def get_namespace_id(doc, url): + attributes = doc.documentElement.attributes + namespace = "" + for i in range(attributes.length): + if attributes.item(i).value == url: + if ":" in attributes.item(i).name: + # If the namespace is not the default one remove 'xlmns:' + namespace = attributes.item(i).name.split(':')[1] + ":" + break + return namespace + + def get_text(element): + """Retrieve the text value of a given node""" + rc = [] + for node in element.childNodes: + if node.nodeType == node.TEXT_NODE: + rc.append(node.data) + return ''.join(rc).strip() + + if not os.path.exists(addon_path): + raise IOError('Add-on path does not exist: %s' % addon_path) + + try: + if zipfile.is_zipfile(addon_path): + # Bug 944361 - We cannot use 'with' together with zipFile because + # it will cause an exception thrown in Python 2.6. + try: + compressed_file = zipfile.ZipFile(addon_path, 'r') + manifest = compressed_file.read('install.rdf') + finally: + compressed_file.close() + elif os.path.isdir(addon_path): + with open(os.path.join(addon_path, 'install.rdf'), 'r') as f: + manifest = f.read() + else: + raise IOError('Add-on path is neither an XPI nor a directory: %s' % addon_path) + except (IOError, KeyError) as e: + raise AddonFormatError(str(e), sys.exc_info()[2]) + + try: + doc = minidom.parseString(manifest) + + # Get the namespaces abbreviations + em = get_namespace_id(doc, 'http://www.mozilla.org/2004/em-rdf#') + rdf = get_namespace_id(doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#') + + description = doc.getElementsByTagName(rdf + 'Description').item(0) + for node in description.childNodes: + # Remove the namespace prefix from the tag for comparison + entry = node.nodeName.replace(em, "") + if entry in details.keys(): + details.update({entry: get_text(node)}) + except Exception as e: + raise AddonFormatError(str(e), sys.exc_info()[2]) + + # turn unpack into a true/false value + if isinstance(details['unpack'], str): + details['unpack'] = details['unpack'].lower() == 'true' + + # If no ID is set, the add-on is invalid + if details.get('id') is None: + raise AddonFormatError('Add-on id could not be found.') + + return details diff --git a/apps/selenium/webdriver/firefox/webdriver.py b/apps/selenium/webdriver/firefox/webdriver.py new file mode 100644 index 0000000000..dd709cfd24 --- /dev/null +++ b/apps/selenium/webdriver/firefox/webdriver.py @@ -0,0 +1,82 @@ +# Copyright 2008-2013 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. + + +try: + import http.client as http_client +except ImportError: + import httplib as http_client + +import shutil +import socket +import sys +from .firefox_binary import FirefoxBinary +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.firefox.extension_connection import ExtensionConnection +from selenium.webdriver.firefox.firefox_profile import FirefoxProfile +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver + + +class WebDriver(RemoteWebDriver): + + # There is no native event support on Mac + NATIVE_EVENTS_ALLOWED = sys.platform != "darwin" + + def __init__(self, firefox_profile=None, firefox_binary=None, timeout=30, + capabilities=None, proxy=None): + + self.binary = firefox_binary + self.profile = firefox_profile + + if self.profile is None: + self.profile = FirefoxProfile() + + self.profile.native_events_enabled = ( + self.NATIVE_EVENTS_ALLOWED and self.profile.native_events_enabled) + + if self.binary is None: + self.binary = FirefoxBinary() + + if capabilities is None: + capabilities = DesiredCapabilities.FIREFOX + + if proxy is not None: + proxy.add_to_capabilities(capabilities) + + RemoteWebDriver.__init__(self, + command_executor=ExtensionConnection("127.0.0.1", self.profile, + self.binary, timeout), + desired_capabilities=capabilities, + keep_alive=True) + self._is_remote = False + + def quit(self): + """Quits the driver and close every associated window.""" + try: + RemoteWebDriver.quit(self) + except (http_client.BadStatusLine, socket.error): + # Happens if Firefox shutsdown before we've read the response from + # the socket. + pass + self.binary.kill() + try: + shutil.rmtree(self.profile.path) + if self.profile.tempfolder is not None: + shutil.rmtree(self.profile.tempfolder) + except Exception as e: + print(str(e)) + + @property + def firefox_profile(self): + return self.profile diff --git a/apps/selenium/webdriver/firefox/webdriver.xpi b/apps/selenium/webdriver/firefox/webdriver.xpi new file mode 100644 index 0000000000..c279897913 Binary files /dev/null and b/apps/selenium/webdriver/firefox/webdriver.xpi differ diff --git a/apps/selenium/webdriver/firefox/webdriver_prefs.json b/apps/selenium/webdriver/firefox/webdriver_prefs.json new file mode 100644 index 0000000000..f7c3a2225b --- /dev/null +++ b/apps/selenium/webdriver/firefox/webdriver_prefs.json @@ -0,0 +1,63 @@ +{ + "frozen": { + "app.update.auto": false, + "app.update.enabled": false, + "browser.download.manager.showWhenStarting": false, + "browser.EULA.override": true, + "browser.EULA.3.accepted": true, + "browser.link.open_external": 2, + "browser.link.open_newwindow": 2, + "browser.offline": false, + "browser.safebrowsing.enabled": false, + "browser.safebrowsing.malware.enabled": false, + "browser.search.update": false, + "browser.sessionstore.resume_from_crash": false, + "browser.shell.checkDefaultBrowser": false, + "browser.tabs.warnOnClose": false, + "browser.tabs.warnOnOpen": false, + "devtools.errorconsole.enabled": true, + "dom.disable_open_during_load": false, + "extensions.autoDisableScopes": 10, + "extensions.blocklist.enabled": false, + "extensions.logging.enabled": true, + "extensions.update.enabled": false, + "extensions.update.notifyUser": false, + "layout.css.devPixelsPerPx": "1.0", + "network.manage-offline-status": false, + "network.http.phishy-userpass-length": 255, + "offline-apps.allow_by_default": true, + "prompts.tab_modal.enabled": false, + "security.csp.enable": false, + "security.fileuri.origin_policy": 3, + "security.fileuri.strict_origin_policy": false, + "security.warn_entering_secure": false, + "security.warn_entering_secure.show_once": false, + "security.warn_entering_weak": false, + "security.warn_entering_weak.show_once": false, + "security.warn_leaving_secure": false, + "security.warn_leaving_secure.show_once": false, + "security.warn_submit_insecure": false, + "security.warn_viewing_mixed": false, + "security.warn_viewing_mixed.show_once": false, + "signon.rememberSignons": false, + "toolkit.networkmanager.disable": true, + "toolkit.telemetry.prompted": 2, + "toolkit.telemetry.enabled": false, + "toolkit.telemetry.rejected": true + }, + "mutable": { + "browser.dom.window.dump.enabled": true, + "browser.newtab.url": "about:blank", + "browser.newtabpage.enabled": false, + "browser.startup.page": 0, + "browser.startup.homepage": "about:blank", + "dom.max_chrome_script_run_time": 30, + "dom.max_script_run_time": 30, + "dom.report_all_js_exceptions": true, + "javascript.options.showInConsole": true, + "network.http.max-connections-per-server": 10, + "startup.homepage_welcome_url": "about:blank", + "webdriver_accept_untrusted_certs": true, + "webdriver_assume_untrusted_issuer": true + } +} diff --git a/apps/selenium/webdriver/firefox/x86/x_ignore_nofocus.so b/apps/selenium/webdriver/firefox/x86/x_ignore_nofocus.so new file mode 100644 index 0000000000..8e7db8de3f Binary files /dev/null and b/apps/selenium/webdriver/firefox/x86/x_ignore_nofocus.so differ diff --git a/apps/selenium/webdriver/ie/__init__.py b/apps/selenium/webdriver/ie/__init__.py new file mode 100644 index 0000000000..8e02e27edf --- /dev/null +++ b/apps/selenium/webdriver/ie/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# +# Copyright 2008-2010 WebDriver committers +# Copyright 2008-2010 Google 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. diff --git a/apps/selenium/webdriver/ie/service.py b/apps/selenium/webdriver/ie/service.py new file mode 100644 index 0000000000..ad4ea3296c --- /dev/null +++ b/apps/selenium/webdriver/ie/service.py @@ -0,0 +1,110 @@ +#!/usr/bin/python +# +# Copyright 2012 Webdriver_name committers +# Copyright 2012 Google 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. +import subprocess +from subprocess import PIPE +import time +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of the IEDriver + """ + + def __init__(self, executable_path, port=0, host=None, log_level=None, log_file=None): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to the IEDriver + - port : Port the service is running on + - host : IP address the service port is bound + - log_level : Level of logging of service, may be "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE". + Default is "FATAL". + - log_file : Target of logging of service, may be "stdout", "stderr" or file path. + Default is "stdout".""" + + self.port = port + self.path = executable_path + if self.port == 0: + self.port = utils.free_port() + self.host = host + self.log_level = log_level + self.log_file = log_file + + def start(self): + """ + Starts the IEDriver Service. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + cmd = [self.path, "--port=%d" % self.port] + if self.host is not None: + cmd.append("--host=%s" % self.host) + if self.log_level is not None: + cmd.append("--log-level=%s" % self.log_level) + if self.log_file is not None: + cmd.append("--log-file=%s" % self.log_file) + self.process = subprocess.Popen(cmd, + stdout=PIPE, stderr=PIPE) + except TypeError: + raise + except: + raise WebDriverException( + "IEDriver executable needs to be available in the path. \ + Please download from http://selenium-release.storage.googleapis.com/index.html\ + and read up at http://code.google.com/p/selenium/wiki/InternetExplorerDriver") + count = 0 + while not utils.is_url_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to the IEDriver") + + def stop(self): + """ + Tells the IEDriver to stop and cleans up the process + """ + #If its dead dont worry + if self.process is None: + return + + #Tell the Server to die! + try: + from urllib import request as url_request + except ImportError: + import urllib2 as url_request + + url_request.urlopen("http://127.0.0.1:%d/shutdown" % self.port) + count = 0 + while utils.is_connectable(self.port): + if count == 30: + break + count += 1 + time.sleep(1) + + #Tell the Server to properly die in case + try: + if self.process: + self.process.kill() + self.process.wait() + except WindowsError: + # kill may not be available under windows environment + pass diff --git a/apps/selenium/webdriver/ie/webdriver.py b/apps/selenium/webdriver/ie/webdriver.py new file mode 100644 index 0000000000..5408133340 --- /dev/null +++ b/apps/selenium/webdriver/ie/webdriver.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# +# Copyright 2008-2013 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. + +from selenium.webdriver.common import utils +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.command import Command +from selenium.common.exceptions import WebDriverException +import base64 +from .service import Service + +DEFAULT_TIMEOUT = 30 +DEFAULT_PORT = 0 +DEFAULT_HOST = None +DEFAULT_LOG_LEVEL = None +DEFAULT_LOG_FILE = None + +class WebDriver(RemoteWebDriver): + + def __init__(self, executable_path='IEDriverServer.exe', capabilities=None, + port=DEFAULT_PORT, timeout=DEFAULT_TIMEOUT, host=DEFAULT_HOST, + log_level=DEFAULT_LOG_LEVEL, log_file=DEFAULT_LOG_FILE): + self.port = port + if self.port == 0: + self.port = utils.free_port() + self.host = host + self.log_level = log_level + self.log_file = log_file + + self.iedriver = Service(executable_path, port=self.port, + host=self.host, log_level=self.log_level, log_file=self.log_file) + + self.iedriver.start() + + if capabilities is None: + capabilities = DesiredCapabilities.INTERNETEXPLORER + + RemoteWebDriver.__init__( + self, + command_executor='http://localhost:%d' % self.port, + desired_capabilities=capabilities) + self._is_remote = False + + def quit(self): + RemoteWebDriver.quit(self) + self.iedriver.stop() diff --git a/apps/selenium/webdriver/opera/__init__.py b/apps/selenium/webdriver/opera/__init__.py new file mode 100644 index 0000000000..a8bb36bb9b --- /dev/null +++ b/apps/selenium/webdriver/opera/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2010 WebDriver committers +# Copyright 2010 Google 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. diff --git a/apps/selenium/webdriver/opera/service.py b/apps/selenium/webdriver/opera/service.py new file mode 100644 index 0000000000..688b8d8998 --- /dev/null +++ b/apps/selenium/webdriver/opera/service.py @@ -0,0 +1,80 @@ +#!/usr/bin/python +# +# Copyright 2011 Webdriver_name committers +# Copyright 2011 Google 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. +import subprocess +from subprocess import PIPE +import time +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of the OperaDriver + """ + + def __init__(self, executable_path, port=0): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to the OperaDriver + - port : Port the service is running on """ + + self.port = port + self.path = executable_path + if self.port == 0: + self.port = utils.free_port() + + def start(self): + """ + Starts the OperaDriver Service. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + self.process = subprocess.Popen(["java", "-jar", self.path, "-port", "%s" % self.port]) + except: + raise WebDriverException( + "OperaDriver executable needs to be available in the path. \ + ") + time.sleep(10) + count = 0 + while not utils.is_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to the OperaDriver") + + @property + def service_url(self): + """ + Gets the url of the OperaDriver Service + """ + return "http://localhost:%d/wd/hub" % self.port + + def stop(self): + """ + Tells the OperaDriver to stop and cleans up the process + """ + #If its dead dont worry + if self.process is None: + return + + self.process.kill() + self.process.wait() + diff --git a/apps/selenium/webdriver/opera/webdriver.py b/apps/selenium/webdriver/opera/webdriver.py new file mode 100644 index 0000000000..6139856e77 --- /dev/null +++ b/apps/selenium/webdriver/opera/webdriver.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# +# Copyright 2011-2013 Sofware 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. + +import base64 + +try: + import http.client as http_client +except ImportError: + import httplib as http_client + +import os +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from .service import Service + +class WebDriver(RemoteWebDriver): + """ + Controls the OperaDriver and allows you to drive the browser. + + """ + + def __init__(self, executable_path=None, port=0, + desired_capabilities=DesiredCapabilities.OPERA): + """ + Creates a new instance of the Opera driver. + + Starts the service and then creates new instance of Opera Driver. + + :Args: + - executable_path - path to the executable. If the default is used it assumes the executable is in the + Environment Variable SELENIUM_SERVER_JAR + - port - port you would like the service to run, if left as 0, a free port will be found. + - desired_capabilities: Dictionary object with desired capabilities (Can be used to provide various Opera switches). + """ + if executable_path is None: + try: + executable_path = os.environ["SELENIUM_SERVER_JAR"] + except: + raise Exception("No executable path given, please add one to Environment Variable \ + 'SELENIUM_SERVER_JAR'") + self.service = Service(executable_path, port=port) + self.service.start() + + RemoteWebDriver.__init__(self, + command_executor=self.service.service_url, + desired_capabilities=desired_capabilities) + self._is_remote = False + + def quit(self): + """ + Closes the browser and shuts down the OperaDriver executable + that is started when starting the OperaDriver + """ + try: + RemoteWebDriver.quit(self) + except http_client.BadStatusLine: + pass + finally: + self.service.stop() diff --git a/apps/selenium/webdriver/phantomjs/__init__.py b/apps/selenium/webdriver/phantomjs/__init__.py new file mode 100644 index 0000000000..ea7f0b5fb3 --- /dev/null +++ b/apps/selenium/webdriver/phantomjs/__init__.py @@ -0,0 +1,13 @@ +# 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. diff --git a/apps/selenium/webdriver/phantomjs/service.py b/apps/selenium/webdriver/phantomjs/service.py new file mode 100644 index 0000000000..2233ba4ee9 --- /dev/null +++ b/apps/selenium/webdriver/phantomjs/service.py @@ -0,0 +1,107 @@ +#!/usr/bin/python +# +# 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. +import platform +import signal +import subprocess +import time + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of PhantomJS / Ghostdriver + """ + + def __init__(self, executable_path, port=0, service_args=None, log_path=None): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to PhantomJS binary + - port : Port the service is running on + - service_args : A List of other command line options to pass to PhantomJS + - log_path: Path for PhantomJS service to log to + """ + + self.port = port + self.path = executable_path + self.service_args= service_args + if self.port == 0: + self.port = utils.free_port() + if self.service_args is None: + self.service_args = [] + else: + self.service_args=service_args[:] + self.service_args.insert(0, self.path) + self.service_args.append("--webdriver=%d" % self.port) + if not log_path: + log_path = "ghostdriver.log" + self._log = open(log_path, 'w') + + def __del__(self): + # subprocess.Popen doesn't send signal on __del__; + # we have to try to stop the launched process. + self.stop() + + def start(self): + """ + Starts PhantomJS with GhostDriver. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + self.process = subprocess.Popen(self.service_args, stdin=subprocess.PIPE, + close_fds=platform.system() != 'Windows', + stdout=self._log, stderr=self._log) + + except Exception as e: + raise WebDriverException("Unable to start phantomjs with ghostdriver.", e) + count = 0 + while not utils.is_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to GhostDriver") + + @property + def service_url(self): + """ + Gets the url of the GhostDriver Service + """ + return "http://localhost:%d/wd/hub" % self.port + + def stop(self): + """ + Cleans up the process + """ + if self._log: + self._log.close() + self._log = None + #If its dead dont worry + if self.process is None: + return + + #Tell the Server to properly die in case + try: + if self.process: + self.process.send_signal(signal.SIGTERM) + self.process.wait() + except OSError: + # kill may not be available under windows environment + pass diff --git a/apps/selenium/webdriver/phantomjs/webdriver.py b/apps/selenium/webdriver/phantomjs/webdriver.py new file mode 100644 index 0000000000..8eab1ecc0d --- /dev/null +++ b/apps/selenium/webdriver/phantomjs/webdriver.py @@ -0,0 +1,73 @@ +#!/usr/bin/python +# +# Copyright 2012-2013 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. + +import base64 +from selenium.webdriver.remote.command import Command +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.common.exceptions import WebDriverException +from .service import Service + +class WebDriver(RemoteWebDriver): + """ + Wrapper to communicate with PhantomJS through Ghostdriver. + + You will need to follow all the directions here: + https://github.com/detro/ghostdriver + """ + + def __init__(self, executable_path="phantomjs", + port=0, desired_capabilities=DesiredCapabilities.PHANTOMJS, + service_args=None, service_log_path=None): + """ + Creates a new instance of the PhantomJS / Ghostdriver. + + Starts the service and then creates new instance of the driver. + + :Args: + - executable_path - path to the executable. If the default is used it assumes the executable is in the $PATH + - port - port you would like the service to run, if left as 0, a free port will be found. + - desired_capabilities: Dictionary object with non-browser specific + capabilities only, such as "proxy" or "loggingPref". + - service_args : A List of command line arguments to pass to PhantomJS + - service_log_path: Path for phantomjs service to log to. + """ + self.service = Service(executable_path, port=port, + service_args=service_args, log_path=service_log_path) + self.service.start() + + try: + RemoteWebDriver.__init__(self, + command_executor=self.service.service_url, + desired_capabilities=desired_capabilities) + except: + self.quit() + raise + + self._is_remote = False + + def quit(self): + """ + Closes the browser and shuts down the PhantomJS executable + that is started when starting the PhantomJS + """ + try: + RemoteWebDriver.quit(self) + except: + # We don't care about the message because something probably has gone wrong + pass + finally: + self.service.stop() diff --git a/apps/selenium/webdriver/remote/__init__.py b/apps/selenium/webdriver/remote/__init__.py new file mode 100644 index 0000000000..f042f9da32 --- /dev/null +++ b/apps/selenium/webdriver/remote/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google 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. diff --git a/apps/selenium/webdriver/remote/command.py b/apps/selenium/webdriver/remote/command.py new file mode 100644 index 0000000000..1375fcee21 --- /dev/null +++ b/apps/selenium/webdriver/remote/command.py @@ -0,0 +1,142 @@ +# Copyright 2010 WebDriver committers +# Copyright 2010 Google 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. + +class Command(object): + """ + Defines constants for the standard WebDriver commands. + + While these constants have no meaning in and of themselves, they are + used to marshal commands through a service that implements WebDriver's + remote wire protocol: + + http://code.google.com/p/selenium/wiki/JsonWireProtocol + """ + + # Keep in sync with org.openqa.selenium.remote.DriverCommand + + STATUS = "status" + NEW_SESSION = "newSession" + GET_ALL_SESSIONS = "getAllSessions" + DELETE_SESSION = "deleteSession" + CLOSE = "close" + QUIT = "quit" + GET = "get" + GO_BACK = "goBack" + GO_FORWARD = "goForward" + REFRESH = "refresh" + ADD_COOKIE = "addCookie" + GET_COOKIE = "getCookie" + GET_ALL_COOKIES = "getCookies" + DELETE_COOKIE = "deleteCookie" + DELETE_ALL_COOKIES = "deleteAllCookies" + FIND_ELEMENT = "findElement" + FIND_ELEMENTS = "findElements" + FIND_CHILD_ELEMENT = "findChildElement" + FIND_CHILD_ELEMENTS = "findChildElements" + CLEAR_ELEMENT = "clearElement" + CLICK_ELEMENT = "clickElement" + SEND_KEYS_TO_ELEMENT = "sendKeysToElement" + SEND_KEYS_TO_ACTIVE_ELEMENT = "sendKeysToActiveElement" + SUBMIT_ELEMENT = "submitElement" + UPLOAD_FILE = "uploadFile" + GET_CURRENT_WINDOW_HANDLE = "getCurrentWindowHandle" + GET_WINDOW_HANDLES = "getWindowHandles" + GET_WINDOW_SIZE = "getWindowSize" + GET_WINDOW_POSITION = "getWindowPosition" + SET_WINDOW_SIZE = "setWindowSize" + SET_WINDOW_POSITION = "setWindowPosition" + SWITCH_TO_WINDOW = "switchToWindow" + SWITCH_TO_FRAME = "switchToFrame" + SWITCH_TO_PARENT_FRAME = "switchToParentFrame" + GET_ACTIVE_ELEMENT = "getActiveElement" + GET_CURRENT_URL = "getCurrentUrl" + GET_PAGE_SOURCE = "getPageSource" + GET_TITLE = "getTitle" + EXECUTE_SCRIPT = "executeScript" + GET_ELEMENT_TEXT = "getElementText" + GET_ELEMENT_VALUE = "getElementValue" + GET_ELEMENT_TAG_NAME = "getElementTagName" + SET_ELEMENT_SELECTED = "setElementSelected" + IS_ELEMENT_SELECTED = "isElementSelected" + IS_ELEMENT_ENABLED = "isElementEnabled" + IS_ELEMENT_DISPLAYED = "isElementDisplayed" + GET_ELEMENT_LOCATION = "getElementLocation" + GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW = "getElementLocationOnceScrolledIntoView" + GET_ELEMENT_SIZE = "getElementSize" + GET_ELEMENT_ATTRIBUTE = "getElementAttribute" + GET_ELEMENT_VALUE_OF_CSS_PROPERTY = "getElementValueOfCssProperty" + ELEMENT_EQUALS = "elementEquals" + SCREENSHOT = "screenshot" + IMPLICIT_WAIT = "implicitlyWait" + EXECUTE_ASYNC_SCRIPT = "executeAsyncScript" + SET_SCRIPT_TIMEOUT = "setScriptTimeout" + SET_TIMEOUTS = "setTimeouts" + MAXIMIZE_WINDOW = "windowMaximize" + GET_LOG = "getLog" + GET_AVAILABLE_LOG_TYPES = "getAvailableLogTypes" + + #Alerts + DISMISS_ALERT = "dismissAlert" + ACCEPT_ALERT = "acceptAlert" + SET_ALERT_VALUE = "setAlertValue" + GET_ALERT_TEXT = "getAlertText" + + # Advanced user interactions + CLICK = "mouseClick" + DOUBLE_CLICK = "mouseDoubleClick" + MOUSE_DOWN = "mouseButtonDown" + MOUSE_UP = "mouseButtonUp" + MOVE_TO = "mouseMoveTo" + + # Screen Orientation + SET_SCREEN_ORIENTATION = "setScreenOrientation" + GET_SCREEN_ORIENTATION = "getScreenOrientation" + + # Touch Actions + SINGLE_TAP = "touchSingleTap" + TOUCH_DOWN = "touchDown" + TOUCH_UP = "touchUp" + TOUCH_MOVE = "touchMove" + TOUCH_SCROLL = "touchScroll" + DOUBLE_TAP = "touchDoubleTap" + LONG_PRESS = "touchLongPress" + FLICK = "touchFlick" + + #HTML 5 + EXECUTE_SQL = "executeSql" + + GET_LOCATION = "getLocation" + SET_LOCATION = "setLocation" + + GET_APP_CACHE = "getAppCache" + GET_APP_CACHE_STATUS = "getAppCacheStatus" + CLEAR_APP_CACHE = "clearAppCache" + + GET_NETWORK_CONNECTION = "getNetworkConnection" + SET_NETWORK_CONNECTION = "setNetworkConnection" + + GET_LOCAL_STORAGE_ITEM = "getLocalStorageItem" + REMOVE_LOCAL_STORAGE_ITEM = "removeLocalStorageItem" + GET_LOCAL_STORAGE_KEYS = "getLocalStorageKeys" + SET_LOCAL_STORAGE_ITEM = "setLocalStorageItem" + CLEAR_LOCAL_STORAGE = "clearLocalStorage" + GET_LOCAL_STORAGE_SIZE = "getLocalStorageSize" + + GET_SESSION_STORAGE_ITEM = "getSessionStorageItem" + REMOVE_SESSION_STORAGE_ITEM = "removeSessionStorageItem" + GET_SESSION_STORAGE_KEYS = "getSessionStorageKeys" + SET_SESSION_STORAGE_ITEM = "setSessionStorageItem" + CLEAR_SESSION_STORAGE = "clearSessionStorage" + GET_SESSION_STORAGE_SIZE = "getSessionStorageSize" diff --git a/apps/selenium/webdriver/remote/errorhandler.py b/apps/selenium/webdriver/remote/errorhandler.py new file mode 100644 index 0000000000..72de77cc9f --- /dev/null +++ b/apps/selenium/webdriver/remote/errorhandler.py @@ -0,0 +1,167 @@ +# Copyright 2010 WebDriver committers +# Copyright 2010 Google 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. + +from selenium.common.exceptions import ElementNotSelectableException +from selenium.common.exceptions import ElementNotVisibleException +from selenium.common.exceptions import InvalidCookieDomainException +from selenium.common.exceptions import InvalidElementStateException +from selenium.common.exceptions import InvalidSelectorException +from selenium.common.exceptions import ImeNotAvailableException +from selenium.common.exceptions import ImeActivationFailedException +from selenium.common.exceptions import NoSuchElementException +from selenium.common.exceptions import NoSuchFrameException +from selenium.common.exceptions import NoSuchWindowException +from selenium.common.exceptions import StaleElementReferenceException +from selenium.common.exceptions import UnableToSetCookieException +from selenium.common.exceptions import UnexpectedAlertPresentException +from selenium.common.exceptions import NoAlertPresentException +from selenium.common.exceptions import ErrorInResponseException +from selenium.common.exceptions import TimeoutException +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import MoveTargetOutOfBoundsException + +try: + basestring +except NameError: # Python 3.x + basestring = str + + +class ErrorCode(object): + """ + Error codes defined in the WebDriver wire protocol. + """ + # Keep in sync with org.openqa.selenium.remote.ErrorCodes and errorcodes.h + SUCCESS = 0 + NO_SUCH_ELEMENT = [7, 'no such element'] + NO_SUCH_FRAME = [8, 'no such frame'] + UNKNOWN_COMMAND = [9, 'unknown command'] + STALE_ELEMENT_REFERENCE = [10, 'stale element reference'] + ELEMENT_NOT_VISIBLE = [11, 'element not visible'] + INVALID_ELEMENT_STATE = [12, 'invalid element state'] + UNKNOWN_ERROR = [13, 'unknown error'] + ELEMENT_IS_NOT_SELECTABLE = [15, 'element not selectable'] + JAVASCRIPT_ERROR = [17, 'javascript error'] + XPATH_LOOKUP_ERROR = [19, 'invalid selector'] + TIMEOUT = [21, 'timeout'] + NO_SUCH_WINDOW = [23, 'no such window'] + INVALID_COOKIE_DOMAIN = [24, 'invalid cookie domain'] + UNABLE_TO_SET_COOKIE = [25, 'unable to set cookie'] + UNEXPECTED_ALERT_OPEN = [26, 'unexpected alert open'] + NO_ALERT_OPEN = [27, 'no such alert'] + SCRIPT_TIMEOUT = [28, 'script timeout'] + INVALID_ELEMENT_COORDINATES = [29, 'invalid element coordinates'] + IME_NOT_AVAILABLE = [30, 'ime not available'] + IME_ENGINE_ACTIVATION_FAILED = [31, 'ime engine activation failed'] + INVALID_SELECTOR = [32, 'invalid selector'] + MOVE_TARGET_OUT_OF_BOUNDS = [34, 'move target out of bounds'] + INVALID_XPATH_SELECTOR = [51, 'invalid selector'] + INVALID_XPATH_SELECTOR_RETURN_TYPER = [52, 'invalid selector'] + METHOD_NOT_ALLOWED = [405, 'unsupported operation'] + + +class ErrorHandler(object): + """ + Handles errors returned by the WebDriver server. + """ + def check_response(self, response): + """ + Checks that a JSON response from the WebDriver does not have an error. + + :Args: + - response - The JSON response from the WebDriver server as a dictionary + object. + + :Raises: If the response contains an error message. + """ + status = response['status'] + if status == ErrorCode.SUCCESS: + return + exception_class = ErrorInResponseException + if status in ErrorCode.NO_SUCH_ELEMENT: + exception_class = NoSuchElementException + elif status in ErrorCode.NO_SUCH_FRAME: + exception_class = NoSuchFrameException + elif status in ErrorCode.NO_SUCH_WINDOW: + exception_class = NoSuchWindowException + elif status in ErrorCode.STALE_ELEMENT_REFERENCE: + exception_class = StaleElementReferenceException + elif status in ErrorCode.ELEMENT_NOT_VISIBLE: + exception_class = ElementNotVisibleException + elif status in ErrorCode.INVALID_ELEMENT_STATE: + exception_class = InvalidElementStateException + elif status in ErrorCode.INVALID_SELECTOR \ + or status in ErrorCode.INVALID_XPATH_SELECTOR \ + or status in ErrorCode.INVALID_XPATH_SELECTOR_RETURN_TYPER: + exception_class = InvalidSelectorException + elif status in ErrorCode.ELEMENT_IS_NOT_SELECTABLE: + exception_class = ElementNotSelectableException + elif status in ErrorCode.INVALID_COOKIE_DOMAIN: + exception_class = WebDriverException + elif status in ErrorCode.UNABLE_TO_SET_COOKIE: + exception_class = WebDriverException + elif status in ErrorCode.TIMEOUT: + exception_class = TimeoutException + elif status in ErrorCode.SCRIPT_TIMEOUT: + exception_class = TimeoutException + elif status in ErrorCode.UNKNOWN_ERROR: + exception_class = WebDriverException + elif status in ErrorCode.UNEXPECTED_ALERT_OPEN: + exception_class = UnexpectedAlertPresentException + elif status in ErrorCode.NO_ALERT_OPEN: + exception_class = NoAlertPresentException + elif status in ErrorCode.IME_NOT_AVAILABLE: + exception_class = ImeNotAvailableException + elif status in ErrorCode.IME_ENGINE_ACTIVATION_FAILED: + exception_class = ImeActivationFailedException + elif status in ErrorCode.MOVE_TARGET_OUT_OF_BOUNDS: + exception_class = MoveTargetOutOfBoundsException + else: + exception_class = WebDriverException + value = response['value'] + if isinstance(value, basestring): + if exception_class == ErrorInResponseException: + raise exception_class(response, value) + raise exception_class(value) + message = '' + if 'message' in value: + message = value['message'] + + screen = None + if 'screen' in value: + screen = value['screen'] + + stacktrace = None + if 'stackTrace' in value and value['stackTrace']: + stacktrace = [] + try: + for frame in value['stackTrace']: + line = self._value_or_default(frame, 'lineNumber', '') + file = self._value_or_default(frame, 'fileName', '') + if line: + file = "%s:%s" % (file, line) + meth = self._value_or_default(frame, 'methodName', '') + if 'className' in frame: + meth = "%s.%s" % (frame['className'], meth) + msg = " at %s (%s)" + msg = msg % (meth, file) + stacktrace.append(msg) + except TypeError: + pass + if exception_class == ErrorInResponseException: + raise exception_class(response, message) + raise exception_class(message, screen, stacktrace) + + def _value_or_default(self, obj, key, default): + return obj[key] if key in obj else default diff --git a/apps/selenium/webdriver/remote/mobile.py b/apps/selenium/webdriver/remote/mobile.py new file mode 100644 index 0000000000..6298fc5ca9 --- /dev/null +++ b/apps/selenium/webdriver/remote/mobile.py @@ -0,0 +1,54 @@ +# Copyright 2014 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. + +from .command import Command +from selenium.common.exceptions import WebDriverException + +class Mobile(object): + + class ConnectionType(object): + def __init__(self, mask): + self.mask = mask + @property + def airplane_mode(self): + return self.mask % 2 == 1 + @property + def wifi(self): + return (self.mask / 2) % 2 == 1 + @property + def data(self): + return (self.mask / 4) > 0 + + ALL_NETWORK = ConnectionType(6) + WIFI_NETWORK = ConnectionType(2) + DATA_NETWORK = ConnectionType(4) + AIRPLANE_MODE = ConnectionType(1) + + def __init__(self, driver): + self._driver = driver + + @property + def network_connection(self): + return self.ConnectionType(self._driver.execute(Command.GET_NETWORK_CONNECTION)['value']) + + def set_network_connection(self, network): + """ + Set the network connection for the remote device. + Example of setting airplane mode: + driver.mobile.set_network_connection(driver.mobile.AIRPLANE_MODE) + """ + mode = network.mask if isinstance(network, self.ConnectionType) else network + return self.ConnectionType(self._driver.execute(Command.SET_NETWORK_CONNECTION, + {'name':'network_connection', + 'parameters':{'type': mode}})['value']) diff --git a/apps/selenium/webdriver/remote/remote_connection.py b/apps/selenium/webdriver/remote/remote_connection.py new file mode 100644 index 0000000000..d7fa2c09d4 --- /dev/null +++ b/apps/selenium/webdriver/remote/remote_connection.py @@ -0,0 +1,457 @@ +# Copyright 2008-2013 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. + +import logging +import socket +import string +import base64 + +try: + import http.client as httplib + from urllib import request as url_request + from urllib import parse +except ImportError: # above is available in py3+, below is py2.7 + import httplib as httplib + import urllib2 as url_request + import urlparse as parse + +from .command import Command +from .errorhandler import ErrorCode +from . import utils + +LOGGER = logging.getLogger(__name__) + + +class Request(url_request.Request): + """ + Extends the url_request.Request to support all HTTP request types. + """ + + def __init__(self, url, data=None, method=None): + """ + Initialise a new HTTP request. + + :Args: + - url - String for the URL to send the request to. + - data - Data to send with the request. + """ + if method is None: + method = data is not None and 'POST' or 'GET' + elif method != 'POST' and method != 'PUT': + data = None + self._method = method + url_request.Request.__init__(self, url, data=data) + + def get_method(self): + """ + Returns the HTTP method used by this request. + """ + return self._method + + +class Response(object): + """ + Represents an HTTP response. + """ + + def __init__(self, fp, code, headers, url): + """ + Initialise a new Response. + + :Args: + - fp - The response body file object. + - code - The HTTP status code returned by the server. + - headers - A dictionary of headers returned by the server. + - url - URL of the retrieved resource represented by this Response. + """ + self.fp = fp + self.read = fp.read + self.code = code + self.headers = headers + self.url = url + + def close(self): + """ + Close the response body file object. + """ + self.read = None + self.fp = None + + def info(self): + """ + Returns the response headers. + """ + return self.headers + + def geturl(self): + """ + Returns the URL for the resource returned in this response. + """ + return self.url + + +class HttpErrorHandler(url_request.HTTPDefaultErrorHandler): + """ + A custom HTTP error handler. + + Used to return Response objects instead of raising an HTTPError exception. + """ + + def http_error_default(self, req, fp, code, msg, headers): + """ + Default HTTP error handler. + + :Args: + - req - The original Request object. + - fp - The response body file object. + - code - The HTTP status code returned by the server. + - msg - The HTTP status message returned by the server. + - headers - The response headers. + + :Returns: + A new Response object. + """ + return Response(fp, code, headers, req.get_full_url()) + + +class RemoteConnection(object): + """ + A connection with the Remote WebDriver server. + + Communicates with the server using the WebDriver wire protocol: + http://code.google.com/p/selenium/wiki/JsonWireProtocol + """ + def __init__(self, remote_server_addr, keep_alive=False): + # Attempt to resolve the hostname and get an IP address. + self.keep_alive = keep_alive + parsed_url = parse.urlparse(remote_server_addr) + addr = "" + if parsed_url.hostname: + try: + netloc = socket.gethostbyname(parsed_url.hostname) + addr = netloc + if parsed_url.port: + netloc += ':%d' % parsed_url.port + if parsed_url.username: + auth = parsed_url.username + if parsed_url.password: + auth += ':%s' % parsed_url.password + netloc = '%s@%s' % (auth, netloc) + remote_server_addr = parse.urlunparse( + (parsed_url.scheme, netloc, parsed_url.path, + parsed_url.params, parsed_url.query, parsed_url.fragment)) + except socket.gaierror: + LOGGER.info('Could not get IP address for host: %s' % parsed_url.hostname) + + self._url = remote_server_addr + if keep_alive: + self._conn = httplib.HTTPConnection(str(addr), str(parsed_url.port)) + self._commands = { + Command.STATUS: ('GET', '/status'), + Command.NEW_SESSION: ('POST', '/session'), + Command.GET_ALL_SESSIONS: ('GET', '/sessions'), + Command.QUIT: ('DELETE', '/session/$sessionId'), + Command.GET_CURRENT_WINDOW_HANDLE: + ('GET', '/session/$sessionId/window_handle'), + Command.GET_WINDOW_HANDLES: + ('GET', '/session/$sessionId/window_handles'), + Command.GET: ('POST', '/session/$sessionId/url'), + Command.GO_FORWARD: ('POST', '/session/$sessionId/forward'), + Command.GO_BACK: ('POST', '/session/$sessionId/back'), + Command.REFRESH: ('POST', '/session/$sessionId/refresh'), + Command.EXECUTE_SCRIPT: ('POST', '/session/$sessionId/execute'), + Command.GET_CURRENT_URL: ('GET', '/session/$sessionId/url'), + Command.GET_TITLE: ('GET', '/session/$sessionId/title'), + Command.GET_PAGE_SOURCE: ('GET', '/session/$sessionId/source'), + Command.SCREENSHOT: ('GET', '/session/$sessionId/screenshot'), + Command.FIND_ELEMENT: ('POST', '/session/$sessionId/element'), + Command.FIND_ELEMENTS: ('POST', '/session/$sessionId/elements'), + Command.GET_ACTIVE_ELEMENT: + ('POST', '/session/$sessionId/element/active'), + Command.FIND_CHILD_ELEMENT: + ('POST', '/session/$sessionId/element/$id/element'), + Command.FIND_CHILD_ELEMENTS: + ('POST', '/session/$sessionId/element/$id/elements'), + Command.CLICK_ELEMENT: ('POST', '/session/$sessionId/element/$id/click'), + Command.CLEAR_ELEMENT: ('POST', '/session/$sessionId/element/$id/clear'), + Command.SUBMIT_ELEMENT: ('POST', '/session/$sessionId/element/$id/submit'), + Command.GET_ELEMENT_TEXT: ('GET', '/session/$sessionId/element/$id/text'), + Command.SEND_KEYS_TO_ELEMENT: + ('POST', '/session/$sessionId/element/$id/value'), + Command.SEND_KEYS_TO_ACTIVE_ELEMENT: + ('POST', '/session/$sessionId/keys'), + Command.UPLOAD_FILE: ('POST', "/session/$sessionId/file"), + Command.GET_ELEMENT_VALUE: + ('GET', '/session/$sessionId/element/$id/value'), + Command.GET_ELEMENT_TAG_NAME: + ('GET', '/session/$sessionId/element/$id/name'), + Command.IS_ELEMENT_SELECTED: + ('GET', '/session/$sessionId/element/$id/selected'), + Command.SET_ELEMENT_SELECTED: + ('POST', '/session/$sessionId/element/$id/selected'), + Command.IS_ELEMENT_ENABLED: + ('GET', '/session/$sessionId/element/$id/enabled'), + Command.IS_ELEMENT_DISPLAYED: + ('GET', '/session/$sessionId/element/$id/displayed'), + Command.GET_ELEMENT_LOCATION: + ('GET', '/session/$sessionId/element/$id/location'), + Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW: + ('GET', '/session/$sessionId/element/$id/location_in_view'), + Command.GET_ELEMENT_SIZE: + ('GET', '/session/$sessionId/element/$id/size'), + Command.GET_ELEMENT_ATTRIBUTE: + ('GET', '/session/$sessionId/element/$id/attribute/$name'), + Command.ELEMENT_EQUALS: + ('GET', '/session/$sessionId/element/$id/equals/$other'), + Command.GET_ALL_COOKIES: ('GET', '/session/$sessionId/cookie'), + Command.ADD_COOKIE: ('POST', '/session/$sessionId/cookie'), + Command.DELETE_ALL_COOKIES: + ('DELETE', '/session/$sessionId/cookie'), + Command.DELETE_COOKIE: + ('DELETE', '/session/$sessionId/cookie/$name'), + Command.SWITCH_TO_FRAME: ('POST', '/session/$sessionId/frame'), + Command.SWITCH_TO_PARENT_FRAME: ('POST', '/session/$sessionId/frame/parent'), + Command.SWITCH_TO_WINDOW: ('POST', '/session/$sessionId/window'), + Command.CLOSE: ('DELETE', '/session/$sessionId/window'), + Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY: + ('GET', '/session/$sessionId/element/$id/css/$propertyName'), + Command.IMPLICIT_WAIT: + ('POST', '/session/$sessionId/timeouts/implicit_wait'), + Command.EXECUTE_ASYNC_SCRIPT: ('POST', '/session/$sessionId/execute_async'), + Command.SET_SCRIPT_TIMEOUT: + ('POST', '/session/$sessionId/timeouts/async_script'), + Command.SET_TIMEOUTS: + ('POST', '/session/$sessionId/timeouts'), + Command.DISMISS_ALERT: + ('POST', '/session/$sessionId/dismiss_alert'), + Command.ACCEPT_ALERT: + ('POST', '/session/$sessionId/accept_alert'), + Command.SET_ALERT_VALUE: + ('POST', '/session/$sessionId/alert_text'), + Command.GET_ALERT_TEXT: + ('GET', '/session/$sessionId/alert_text'), + Command.CLICK: + ('POST', '/session/$sessionId/click'), + Command.DOUBLE_CLICK: + ('POST', '/session/$sessionId/doubleclick'), + Command.MOUSE_DOWN: + ('POST', '/session/$sessionId/buttondown'), + Command.MOUSE_UP: + ('POST', '/session/$sessionId/buttonup'), + Command.MOVE_TO: + ('POST', '/session/$sessionId/moveto'), + Command.GET_WINDOW_SIZE: + ('GET', '/session/$sessionId/window/$windowHandle/size'), + Command.SET_WINDOW_SIZE: + ('POST', '/session/$sessionId/window/$windowHandle/size'), + Command.GET_WINDOW_POSITION: + ('GET', '/session/$sessionId/window/$windowHandle/position'), + Command.SET_WINDOW_POSITION: + ('POST', '/session/$sessionId/window/$windowHandle/position'), + Command.MAXIMIZE_WINDOW: + ('POST', '/session/$sessionId/window/$windowHandle/maximize'), + Command.SET_SCREEN_ORIENTATION: + ('POST', '/session/$sessionId/orientation'), + Command.GET_SCREEN_ORIENTATION: + ('GET', '/session/$sessionId/orientation'), + Command.SINGLE_TAP: + ('POST', '/session/$sessionId/touch/click'), + Command.TOUCH_DOWN: + ('POST', '/session/$sessionId/touch/down'), + Command.TOUCH_UP: + ('POST', '/session/$sessionId/touch/up'), + Command.TOUCH_MOVE: + ('POST', '/session/$sessionId/touch/move'), + Command.TOUCH_SCROLL: + ('POST', '/session/$sessionId/touch/scroll'), + Command.DOUBLE_TAP: + ('POST', '/session/$sessionId/touch/doubleclick'), + Command.LONG_PRESS: + ('POST', '/session/$sessionId/touch/longclick'), + Command.FLICK: + ('POST', '/session/$sessionId/touch/flick'), + Command.EXECUTE_SQL: + ('POST', '/session/$sessionId/execute_sql'), + Command.GET_LOCATION: + ('GET', '/session/$sessionId/location'), + Command.SET_LOCATION: + ('POST', '/session/$sessionId/location'), + Command.GET_APP_CACHE: + ('GET', '/session/$sessionId/application_cache'), + Command.GET_APP_CACHE_STATUS: + ('GET', '/session/$sessionId/application_cache/status'), + Command.CLEAR_APP_CACHE: + ('DELETE', '/session/$sessionId/application_cache/clear'), + Command.GET_NETWORK_CONNECTION: + ('GET', '/session/$sessionId/network_connection'), + Command.SET_NETWORK_CONNECTION: + ('POST', '/session/$sessionId/network_connection'), + Command.GET_LOCAL_STORAGE_ITEM: + ('GET', '/session/$sessionId/local_storage/key/$key'), + Command.REMOVE_LOCAL_STORAGE_ITEM: + ('DELETE', '/session/$sessionId/local_storage/key/$key'), + Command.GET_LOCAL_STORAGE_KEYS: + ('GET', '/session/$sessionId/local_storage'), + Command.SET_LOCAL_STORAGE_ITEM: + ('POST', '/session/$sessionId/local_storage'), + Command.CLEAR_LOCAL_STORAGE: + ('DELETE', '/session/$sessionId/local_storage'), + Command.GET_LOCAL_STORAGE_SIZE: + ('GET', '/session/$sessionId/local_storage/size'), + Command.GET_SESSION_STORAGE_ITEM: + ('GET', '/session/$sessionId/session_storage/key/$key'), + Command.REMOVE_SESSION_STORAGE_ITEM: + ('DELETE', '/session/$sessionId/session_storage/key/$key'), + Command.GET_SESSION_STORAGE_KEYS: + ('GET', '/session/$sessionId/session_storage'), + Command.SET_SESSION_STORAGE_ITEM: + ('POST', '/session/$sessionId/session_storage'), + Command.CLEAR_SESSION_STORAGE: + ('DELETE', '/session/$sessionId/session_storage'), + Command.GET_SESSION_STORAGE_SIZE: + ('GET', '/session/$sessionId/session_storage/size'), + Command.GET_LOG: + ('POST', '/session/$sessionId/log'), + Command.GET_AVAILABLE_LOG_TYPES: + ('GET', '/session/$sessionId/log/types'), + } + + def execute(self, command, params): + """ + Send a command to the remote server. + + Any path subtitutions required for the URL mapped to the command should be + included in the command parameters. + + :Args: + - command - A string specifying the command to execute. + - params - A dictionary of named parameters to send with the command as + its JSON payload. + """ + command_info = self._commands[command] + assert command_info is not None, 'Unrecognised command %s' % command + data = utils.dump_json(params) + path = string.Template(command_info[1]).substitute(params) + url = '%s%s' % (self._url, path) + return self._request(command_info[0], url, body=data) + + def _request(self, method, url, body=None): + """ + Send an HTTP request to the remote server. + + :Args: + - method - A string for the HTTP method to send the request with. + - url - A string for the URL to send the request to. + - body - A string for request body. Ignored unless method is POST or PUT. + + :Returns: + A dictionary with the server's parsed JSON response. + """ + LOGGER.debug('%s %s %s' % (method, url, body)) + + parsed_url = parse.urlparse(url) + + if self.keep_alive: + headers = {"Connection": 'keep-alive', method: parsed_url.path, + "User-Agent": "Python http auth", + "Content-type": "application/json;charset=\"UTF-8\"", + "Accept": "application/json"} + if parsed_url.username: + auth = base64.standard_b64encode('%s:%s' % + (parsed_url.username, parsed_url.password)).replace('\n', '') + headers["Authorization"] = "Basic %s" % auth + if body and method != 'POST' and method != 'PUT': + body = None + try: + self._conn.request(method, parsed_url.path, body, headers) + resp = self._conn.getresponse() + except httplib.HTTPException: + self._conn.close() + raise + + statuscode = resp.status + else: + password_manager = None + if parsed_url.username: + netloc = parsed_url.hostname + if parsed_url.port: + netloc += ":%s" % parsed_url.port + cleaned_url = parse.urlunparse((parsed_url.scheme, + netloc, + parsed_url.path, + parsed_url.params, + parsed_url.query, + parsed_url.fragment)) + password_manager = url_request.HTTPPasswordMgrWithDefaultRealm() + password_manager.add_password(None, + "%s://%s" % (parsed_url.scheme, netloc), + parsed_url.username, + parsed_url.password) + request = Request(cleaned_url, data=body.encode('utf-8'), method=method) + else: + request = Request(url, data=body.encode('utf-8'), method=method) + + request.add_header('Accept', 'application/json') + request.add_header('Content-Type', 'application/json;charset=UTF-8') + + if password_manager: + opener = url_request.build_opener(url_request.HTTPRedirectHandler(), + HttpErrorHandler(), + url_request.HTTPBasicAuthHandler(password_manager)) + else: + opener = url_request.build_opener(url_request.HTTPRedirectHandler(), + HttpErrorHandler()) + resp = opener.open(request) + statuscode = resp.code + if not hasattr(resp, 'getheader'): + if hasattr(resp.headers, 'getheader'): + resp.getheader = lambda x: resp.headers.getheader(x) + elif hasattr(resp.headers, 'get'): + resp.getheader = lambda x: resp.headers.get(x) + + data = resp.read() + try: + if 399 < statuscode < 500: + return {'status': statuscode, 'value': data} + if 300 <= statuscode < 304: + return self._request('GET', resp.getheader('location')) + body = data.decode('utf-8').replace('\x00', '').strip() + content_type = [] + if resp.getheader('Content-Type') is not None: + content_type = resp.getheader('Content-Type').split(';') + if not any([x.startswith('image/png') for x in content_type]): + try: + data = utils.load_json(body.strip()) + except ValueError: + if 199 < statuscode < 300: + status = ErrorCode.SUCCESS + else: + status = ErrorCode.UNKNOWN_ERROR + return {'status': status, 'value': body.strip()} + + assert type(data) is dict, ( + 'Invalid server response body: %s' % body) + assert 'status' in data, ( + 'Invalid server response; no status: %s' % body) + # Some of the drivers incorrectly return a response + # with no 'value' field when they should return null. + if 'value' not in data: + data['value'] = None + return data + else: + data = {'status': 0, 'value': body.strip()} + return data + finally: + LOGGER.debug("Finished Request") + resp.close() diff --git a/apps/selenium/webdriver/remote/switch_to.py b/apps/selenium/webdriver/remote/switch_to.py new file mode 100644 index 0000000000..03627f824a --- /dev/null +++ b/apps/selenium/webdriver/remote/switch_to.py @@ -0,0 +1,86 @@ +# Copyright 2014 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. + +from .command import Command +from selenium.webdriver.common.alert import Alert + +class SwitchTo: + def __init__(self, driver): + self._driver = driver + + @property + def active_element(self): + """ + Returns the element with focus, or BODY if nothing has focus. + + :Usage: + element = driver.switch_to.active_element + """ + return self._driver.execute(Command.GET_ACTIVE_ELEMENT)['value'] + + @property + def alert(self): + """ + Switches focus to an alert on the page. + + :Usage: + alert = driver.switch_to.alert + """ + return Alert(self._driver) + + def default_content(self): + """ + Switch focus to the default frame. + + :Usage: + driver.switch_to.default_content() + """ + self._driver.execute(Command.SWITCH_TO_FRAME, {'id': None}) + + def frame(self, frame_reference): + """ + Switches focus to the specified frame, by index, name, or webelement. + + :Args: + - frame_reference: The name of the window to switch to, an integer representing the index, + or a webelement that is an (i)frame to switch to. + + :Usage: + driver.switch_to.frame('frame_name') + driver.switch_to.frame(1) + driver.switch_to.frame(driver.find_elements_by_tag_name("iframe")[0]) + """ + self._driver.execute(Command.SWITCH_TO_FRAME, {'id': frame_reference}) + + def parent_frame(self): + """ + Switches focus to the parent context. If the current context is the top + level browsing context, the context remains unchanged. + + :Usage: + driver.switch_to.parent_frame() + """ + self._driver.execute(Command.SWITCH_TO_PARENT_FRAME) + + def window(self, window_name): + """ + Switches focus to the specified window. + + :Args: + - window_name: The name or window handle of the window to switch to. + + :Usage: + driver.switch_to.window('main') + """ + self._driver.execute(Command.SWITCH_TO_WINDOW, {'name': window_name}) \ No newline at end of file diff --git a/apps/selenium/webdriver/remote/utils.py b/apps/selenium/webdriver/remote/utils.py new file mode 100644 index 0000000000..ba0a378754 --- /dev/null +++ b/apps/selenium/webdriver/remote/utils.py @@ -0,0 +1,105 @@ +# Copyright 2008-2009 WebDriver committers +# Copyright 2008-2009 Google 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. + +import json +import logging +import os +import tempfile +import zipfile + +from selenium.common.exceptions import NoSuchElementException + +LOGGER = logging.getLogger(__name__) + +def format_json(json_struct): + return json.dumps(json_struct, indent=4) + +def dump_json(json_struct): + return json.dumps(json_struct) + +def load_json(s): + return json.loads(s) + +def handle_find_element_exception(e): + if ("Unable to find" in e.response["value"]["message"] or + "Unable to locate" in e.response["value"]["message"]): + raise NoSuchElementException("Unable to locate element:") + else: + raise e + +def return_value_if_exists(resp): + if resp and "value" in resp: + return resp["value"] + +def get_root_parent(elem): + parent = elem.parent + while True: + try: + parent.parent + parent = parent.parent + except AttributeError: + return parent + +def unzip_to_temp_dir(zip_file_name): + """Unzip zipfile to a temporary directory. + + The directory of the unzipped files is returned if success, + otherwise None is returned. """ + if not zip_file_name or not os.path.exists(zip_file_name): + return None + + zf = zipfile.ZipFile(zip_file_name) + + if zf.testzip() is not None: + return None + + # Unzip the files into a temporary directory + LOGGER.info("Extracting zipped file: %s" % zip_file_name) + tempdir = tempfile.mkdtemp() + + try: + # Create directories that don't exist + for zip_name in zf.namelist(): + # We have no knowledge on the os where the zipped file was + # created, so we restrict to zip files with paths without + # charactor "\" and "/". + name = (zip_name.replace("\\", os.path.sep). + replace("/", os.path.sep)) + dest = os.path.join(tempdir, name) + if (name.endswith(os.path.sep) and not os.path.exists(dest)): + os.mkdir(dest) + LOGGER.debug("Directory %s created." % dest) + + # Copy files + for zip_name in zf.namelist(): + # We have no knowledge on the os where the zipped file was + # created, so we restrict to zip files with paths without + # charactor "\" and "/". + name = (zip_name.replace("\\", os.path.sep). + replace("/", os.path.sep)) + dest = os.path.join(tempdir, name) + if not (name.endswith(os.path.sep)): + LOGGER.debug("Copying file %s......" % dest) + outfile = open(dest, 'wb') + outfile.write(zf.read(zip_name)) + outfile.close() + LOGGER.debug("File %s copied." % dest) + + LOGGER.info("Unzipped file can be found at %s" % tempdir) + return tempdir + + except IOError as err: + LOGGER.error("Error in extracting webdriver.xpi: %s" % err) + return None diff --git a/apps/selenium/webdriver/remote/webdriver.py b/apps/selenium/webdriver/remote/webdriver.py new file mode 100644 index 0000000000..7c258c3290 --- /dev/null +++ b/apps/selenium/webdriver/remote/webdriver.py @@ -0,0 +1,830 @@ +# Copyright 2008-2014 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. +""" +The WebDriver implementation. +""" +import base64 +import warnings +from .command import Command +from .webelement import WebElement +from .remote_connection import RemoteConnection +from .errorhandler import ErrorHandler +from .switch_to import SwitchTo +from .mobile import Mobile +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import InvalidSelectorException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.html5.application_cache import ApplicationCache + +try: + str = basestring +except NameError: + pass + +class WebDriver(object): + """ + Controls a browser by sending commands to a remote server. + This server is expected to be running the WebDriver wire protocol as defined + here: http://code.google.com/p/selenium/wiki/JsonWireProtocol + + :Attributes: + - command_executor - The command.CommandExecutor object used to execute commands. + - error_handler - errorhandler.ErrorHandler object used to verify that the server did not return an error. + - session_id - The session ID to send with every command. + - capabilities - A dictionary of capabilities of the underlying browser for this instance's session. + - proxy - A selenium.webdriver.common.proxy.Proxy object, to specify a proxy for the browser to use. + """ + + def __init__(self, command_executor='http://127.0.0.1:4444/wd/hub', + desired_capabilities=None, browser_profile=None, proxy=None, keep_alive=False): + """ + Create a new driver that will issue commands using the wire protocol. + + :Args: + - command_executor - Either a command.CommandExecutor object or a string that specifies the URL of a remote server to send commands to. + - desired_capabilities - Dictionary holding predefined values for starting a browser + - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested. + """ + if desired_capabilities is None: + raise WebDriverException("Desired Capabilities can't be None") + if not isinstance(desired_capabilities, dict): + raise WebDriverException("Desired Capabilities must be a dictionary") + if proxy is not None: + proxy.add_to_capabilities(desired_capabilities) + self.command_executor = command_executor + if type(self.command_executor) is bytes or isinstance(self.command_executor, str): + self.command_executor = RemoteConnection(command_executor, keep_alive=keep_alive) + self._is_remote = True + self.session_id = None + self.capabilities = {} + self.error_handler = ErrorHandler() + self.start_client() + self.start_session(desired_capabilities, browser_profile) + self._switch_to = SwitchTo(self) + self._mobile = Mobile(self) + + @property + def mobile(self): + return self._mobile + + @property + def name(self): + """Returns the name of the underlying browser for this instance. + + :Usage: + - driver.name + """ + if 'browserName' in self.capabilities: + return self.capabilities['browserName'] + else: + raise KeyError('browserName not specified in session capabilities') + + def start_client(self): + """ + Called before starting a new session. This method may be overridden + to define custom startup behavior. + """ + pass + + def stop_client(self): + """ + Called after executing a quit command. This method may be overridden + to define custom shutdown behavior. + """ + pass + + def start_session(self, desired_capabilities, browser_profile=None): + """ + Creates a new session with the desired capabilities. + + :Args: + - browser_name - The name of the browser to request. + - version - Which browser version to request. + - platform - Which platform to request the browser on. + - javascript_enabled - Whether the new session should support JavaScript. + - browser_profile - A selenium.webdriver.firefox.firefox_profile.FirefoxProfile object. Only used if Firefox is requested. + """ + if browser_profile: + desired_capabilities['firefox_profile'] = browser_profile.encoded + response = self.execute(Command.NEW_SESSION, { + 'desiredCapabilities': desired_capabilities, + }) + self.session_id = response['sessionId'] + self.capabilities = response['value'] + + def _wrap_value(self, value): + if isinstance(value, dict): + converted = {} + for key, val in value.items(): + converted[key] = self._wrap_value(val) + return converted + elif isinstance(value, WebElement): + return {'ELEMENT': value.id} + elif isinstance(value, list): + return list(self._wrap_value(item) for item in value) + else: + return value + + def create_web_element(self, element_id): + """ + Creates a web element with the specified element_id. + """ + return WebElement(self, element_id) + + def _unwrap_value(self, value): + if isinstance(value, dict) and 'ELEMENT' in value: + return self.create_web_element(value['ELEMENT']) + elif isinstance(value, list): + return list(self._unwrap_value(item) for item in value) + else: + return value + + def execute(self, driver_command, params=None): + """ + Sends a command to be executed by a command.CommandExecutor. + + :Args: + - driver_command: The name of the command to execute as a string. + - params: A dictionary of named parameters to send with the command. + + :Returns: + The command's JSON response loaded into a dictionary object. + """ + if self.session_id is not None: + if not params: + params = {'sessionId': self.session_id} + elif 'sessionId' not in params: + params['sessionId'] = self.session_id + + params = self._wrap_value(params) + response = self.command_executor.execute(driver_command, params) + if response: + self.error_handler.check_response(response) + response['value'] = self._unwrap_value( + response.get('value', None)) + return response + # If the server doesn't send a response, assume the command was + # a success + return {'success': 0, 'value': None, 'sessionId': self.session_id} + + def get(self, url): + """ + Loads a web page in the current browser session. + """ + self.execute(Command.GET, {'url': url}) + + @property + def title(self): + """Returns the title of the current page. + + :Usage: + driver.title + """ + resp = self.execute(Command.GET_TITLE) + return resp['value'] if resp['value'] is not None else "" + + def find_element_by_id(self, id_): + """Finds an element by id. + + :Args: + - id\_ - The id of the element to be found. + + :Usage: + driver.find_element_by_id('foo') + """ + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + """ + Finds multiple elements by id. + + :Args: + - id\_ - The id of the elements to be found. + + :Usage: + driver.find_element_by_id('foo') + """ + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_xpath(self, xpath): + """ + Finds an element by xpath. + + :Args: + - xpath - The xpath locator of the element to find. + + :Usage: + driver.find_element_by_xpath('//div/td[1]') + """ + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + """ + Finds multiple elements by xpath. + + :Args: + - xpath - The xpath locator of the elements to be found. + + :Usage: + driver.find_elements_by_xpath("//div[contains(@class, 'foo')]") + """ + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_link_text(self, link_text): + """ + Finds an element by link text. + + :Args: + - link_text: The text of the element to be found. + + :Usage: + driver.find_element_by_link_text('Sign In') + """ + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, text): + """ + Finds elements by link text. + + :Args: + - link_text: The text of the elements to be found. + + :Usage: + driver.find_elements_by_link_text('Sign In') + """ + return self.find_elements(by=By.LINK_TEXT, value=text) + + def find_element_by_partial_link_text(self, link_text): + """ + Finds an element by a partial match of its link text. + + :Args: + - link_text: The text of the element to partially match on. + + :Usage: + driver.find_element_by_partial_link_text('Sign') + """ + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + """ + Finds elements by a partial match of their link text. + + :Args: + - link_text: The text of the element to partial match on. + + :Usage: + driver.find_element_by_partial_link_text('Sign') + """ + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_name(self, name): + """ + Finds an element by name. + + :Args: + - name: The name of the element to find. + + :Usage: + driver.find_element_by_name('foo') + """ + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + """ + Finds elements by name. + + :Args: + - name: The name of the elements to find. + + :Usage: + driver.find_elements_by_name('foo') + """ + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_tag_name(self, name): + """ + Finds an element by tag name. + + :Args: + - name: The tag name of the element to find. + + :Usage: + driver.find_element_by_tag_name('foo') + """ + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + """ + Finds elements by tag name. + + :Args: + - name: The tag name the use when finding elements. + + :Usage: + driver.find_elements_by_tag_name('foo') + """ + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_class_name(self, name): + """ + Finds an element by class name. + + :Args: + - name: The class name of the element to find. + + :Usage: + driver.find_element_by_class_name('foo') + """ + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + """ + Finds elements by class name. + + :Args: + - name: The class name of the elements to find. + + :Usage: + driver.find_elements_by_class_name('foo') + """ + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + """ + Finds an element by css selector. + + :Args: + - css_selector: The css selector to use when finding elements. + + :Usage: + driver.find_element_by_css_selector('#foo') + """ + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + """ + Finds elements by css selector. + + :Args: + - css_selector: The css selector to use when finding elements. + + :Usage: + driver.find_elements_by_css_selector('.foo') + """ + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def execute_script(self, script, *args): + """ + Synchronously Executes JavaScript in the current window/frame. + + :Args: + - script: The JavaScript to execute. + - \*args: Any applicable arguments for your JavaScript. + + :Usage: + driver.execute_script('document.title') + """ + converted_args = list(args) + return self.execute(Command.EXECUTE_SCRIPT, + {'script': script, 'args':converted_args})['value'] + + def execute_async_script(self, script, *args): + """ + Asynchronously Executes JavaScript in the current window/frame. + + :Args: + - script: The JavaScript to execute. + - \*args: Any applicable arguments for your JavaScript. + + :Usage: + driver.execute_async_script('document.title') + """ + converted_args = list(args) + return self.execute(Command.EXECUTE_ASYNC_SCRIPT, + {'script': script, 'args':converted_args})['value'] + + @property + def current_url(self): + """ + Gets the URL of the current page. + + :Usage: + driver.current_url + """ + return self.execute(Command.GET_CURRENT_URL)['value'] + + @property + def page_source(self): + """ + Gets the source of the current page. + + :Usage: + driver.page_source + """ + return self.execute(Command.GET_PAGE_SOURCE)['value'] + + def close(self): + """ + Closes the current window. + + :Usage: + driver.close() + """ + self.execute(Command.CLOSE) + + def quit(self): + """ + Quits the driver and closes every associated window. + + :Usage: + driver.quit() + """ + try: + self.execute(Command.QUIT) + finally: + self.stop_client() + + @property + def current_window_handle(self): + """ + Returns the handle of the current window. + + :Usage: + driver.current_window_handle + """ + return self.execute(Command.GET_CURRENT_WINDOW_HANDLE)['value'] + + @property + def window_handles(self): + """ + Returns the handles of all windows within the current session. + + :Usage: + driver.window_handles + """ + return self.execute(Command.GET_WINDOW_HANDLES)['value'] + + def maximize_window(self): + """ + Maximizes the current window that webdriver is using + """ + self.execute(Command.MAXIMIZE_WINDOW, {"windowHandle": "current"}) + + @property + def switch_to(self): + return self._switch_to + + #Target Locators + def switch_to_active_element(self): + """ Deprecated use driver.switch_to.active_element + """ + warnings.warn("use driver.switch_to.active_element instead", DeprecationWarning) + return self._switch_to.active_element + + def switch_to_window(self, window_name): + """ Deprecated use driver.switch_to.window + """ + warnings.warn("use driver.switch_to.window instead", DeprecationWarning) + self._switch_to.window(window_name) + + def switch_to_frame(self, frame_reference): + """ Deprecated use driver.switch_to.frame + """ + warnings.warn("use driver.switch_to.frame instead", DeprecationWarning) + self._switch_to.frame(frame_reference) + + def switch_to_default_content(self): + """ Deprecated use driver.switch_to.default_content + """ + warnings.warn("use driver.switch_to.default_content instead", DeprecationWarning) + self._switch_to.default_content() + + def switch_to_alert(self): + """ Deprecated use driver.switch_to.alert + """ + warnings.warn("use driver.switch_to.alert instead", DeprecationWarning) + return self._switch_to.alert + + #Navigation + def back(self): + """ + Goes one step backward in the browser history. + + :Usage: + driver.back() + """ + self.execute(Command.GO_BACK) + + def forward(self): + """ + Goes one step forward in the browser history. + + :Usage: + driver.forward() + """ + self.execute(Command.GO_FORWARD) + + def refresh(self): + """ + Refreshes the current page. + + :Usage: + driver.refresh() + """ + self.execute(Command.REFRESH) + + # Options + def get_cookies(self): + """ + Returns a set of dictionaries, corresponding to cookies visible in the current session. + + :Usage: + driver.get_cookies() + """ + return self.execute(Command.GET_ALL_COOKIES)['value'] + + def get_cookie(self, name): + """ + Get a single cookie by name. Returns the cookie if found, None if not. + + :Usage: + driver.get_cookie('my_cookie') + """ + cookies = self.get_cookies() + for cookie in cookies: + if cookie['name'] == name: + return cookie + return None + + def delete_cookie(self, name): + """ + Deletes a single cookie with the given name. + + :Usage: + driver.delete_cookie('my_cookie') + """ + self.execute(Command.DELETE_COOKIE, {'name': name}) + + def delete_all_cookies(self): + """ + Delete all cookies in the scope of the session. + + :Usage: + driver.delete_all_cookies() + """ + self.execute(Command.DELETE_ALL_COOKIES) + + def add_cookie(self, cookie_dict): + """ + Adds a cookie to your current session. + + :Args: + - cookie_dict: A dictionary object, with required keys - "name" and "value"; + optional keys - "path", "domain", "secure", "expiry" + + Usage: + driver.add_cookie({'name' : 'foo', 'value' : 'bar'}) + driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/'}) + driver.add_cookie({'name' : 'foo', 'value' : 'bar', 'path' : '/', 'secure':True}) + + """ + self.execute(Command.ADD_COOKIE, {'cookie': cookie_dict}) + + # Timeouts + def implicitly_wait(self, time_to_wait): + """ + Sets a sticky timeout to implicitly wait for an element to be found, + or a command to complete. This method only needs to be called one + time per session. To set the timeout for calls to + execute_async_script, see set_script_timeout. + + :Args: + - time_to_wait: Amount of time to wait (in seconds) + + :Usage: + driver.implicitly_wait(30) + """ + self.execute(Command.IMPLICIT_WAIT, {'ms': float(time_to_wait) * 1000}) + + def set_script_timeout(self, time_to_wait): + """ + Set the amount of time that the script should wait during an + execute_async_script call before throwing an error. + + :Args: + - time_to_wait: The amount of time to wait (in seconds) + + :Usage: + driver.set_script_timeout(30) + """ + self.execute(Command.SET_SCRIPT_TIMEOUT, + {'ms': float(time_to_wait) * 1000}) + + def set_page_load_timeout(self, time_to_wait): + """ + Set the amount of time to wait for a page load to complete + before throwing an error. + + :Args: + - time_to_wait: The amount of time to wait + + :Usage: + driver.set_page_load_timeout(30) + """ + self.execute(Command.SET_TIMEOUTS, + {'ms': float(time_to_wait) * 1000, 'type':'page load'}) + + def find_element(self, by=By.ID, value=None): + """ + 'Private' method used by the find_element_by_* methods. + + :Usage: + Use the corresponding find_element_by_* instead of this. + + :rtype: WebElement + """ + if not By.is_valid(by) or not isinstance(value, str): + raise InvalidSelectorException("Invalid locator values passed in") + + return self.execute(Command.FIND_ELEMENT, + {'using': by, 'value': value})['value'] + + def find_elements(self, by=By.ID, value=None): + """ + 'Private' method used by the find_elements_by_* methods. + + :Usage: + Use the corresponding find_elements_by_* instead of this. + + :rtype: list of WebElement + """ + if not By.is_valid(by) or not isinstance(value, str): + raise InvalidSelectorException("Invalid locator values passed in") + + return self.execute(Command.FIND_ELEMENTS, + {'using': by, 'value': value})['value'] + @property + def desired_capabilities(self): + """ + returns the drivers current desired capabilities being used + """ + return self.capabilities + + def get_screenshot_as_file(self, filename): + """ + Gets the screenshot of the current window. Returns False if there is + any IOError, else returns True. Use full paths in your filename. + + :Args: + - filename: The full path you wish to save your screenshot to. + + :Usage: + driver.get_screenshot_as_file('/Screenshots/foo.png') + """ + png = self.get_screenshot_as_png() + try: + with open(filename, 'wb') as f: + f.write(png) + except IOError: + return False + finally: + del png + return True + + save_screenshot = get_screenshot_as_file + + def get_screenshot_as_png(self): + """ + Gets the screenshot of the current window as a binary data. + + :Usage: + driver.get_screenshot_as_png() + """ + return base64.b64decode(self.get_screenshot_as_base64().encode('ascii')) + + def get_screenshot_as_base64(self): + """ + Gets the screenshot of the current window as a base64 encoded string + which is useful in embedded images in HTML. + + :Usage: + driver.get_screenshot_as_base64() + """ + return self.execute(Command.SCREENSHOT)['value'] + + def set_window_size(self, width, height, windowHandle='current'): + """ + Sets the width and height of the current window. (window.resizeTo) + + :Args: + - width: the width in pixels to set the window to + - height: the height in pixels to set the window to + + :Usage: + driver.set_window_size(800,600) + """ + self.execute(Command.SET_WINDOW_SIZE, {'width': int(width), 'height': int(height), + 'windowHandle': windowHandle}) + + def get_window_size(self, windowHandle='current'): + """ + Gets the width and height of the current window. + + :Usage: + driver.get_window_size() + """ + return self.execute(Command.GET_WINDOW_SIZE, + {'windowHandle': windowHandle})['value'] + + def set_window_position(self, x, y, windowHandle='current'): + """ + Sets the x,y position of the current window. (window.moveTo) + + :Args: + - x: the x-coordinate in pixels to set the window position + - y: the y-coordinate in pixels to set the window position + + :Usage: + driver.set_window_position(0,0) + """ + self.execute(Command.SET_WINDOW_POSITION, {'x': int(x), 'y': int(y), + 'windowHandle': windowHandle}) + + def get_window_position(self, windowHandle='current'): + """ + Gets the x,y position of the current window. + + :Usage: + driver.get_window_position() + """ + return self.execute(Command.GET_WINDOW_POSITION, + {'windowHandle': windowHandle})['value'] + + @property + def orientation(self): + """ + Gets the current orientation of the device + + :Usage: + orientation = driver.orientation + """ + return self.execute(Command.GET_SCREEN_ORIENTATION)['value'] + + @orientation.setter + def orientation(self, value): + """ + Sets the current orientation of the device + + :Args: + - value: orientation to set it to. + + :Usage: + driver.orientation = 'landscape' + """ + allowed_values = ['LANDSCAPE', 'PORTRAIT'] + if value.upper() in allowed_values: + self.execute(Command.SET_SCREEN_ORIENTATION, {'orientation': value}) + else: + raise WebDriverException("You can only set the orientation to 'LANDSCAPE' and 'PORTRAIT'") + + @property + def application_cache(self): + """ Returns a ApplicationCache Object to interact with the browser app cache""" + return ApplicationCache(self) + + @property + def log_types(self): + """ + Gets a list of the available log types + + :Usage: + driver.log_types + """ + return self.execute(Command.GET_AVAILABLE_LOG_TYPES)['value'] + + def get_log(self, log_type): + """ + Gets the log for a given log type + + :Args: + - log_type: type of log that which will be returned + + :Usage: + driver.get_log('browser') + driver.get_log('driver') + driver.get_log('client') + driver.get_log('server') + """ + return self.execute(Command.GET_LOG, {'type': log_type})['value'] diff --git a/apps/selenium/webdriver/remote/webelement.py b/apps/selenium/webdriver/remote/webelement.py new file mode 100644 index 0000000000..52e9f03220 --- /dev/null +++ b/apps/selenium/webdriver/remote/webelement.py @@ -0,0 +1,437 @@ +# Copyright 2008-2013 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. + + +"""WebElement implementation.""" +import hashlib +import os +import zipfile +try: + from StringIO import StringIO as IOStream +except ImportError: # 3+ + from io import BytesIO as IOStream +import base64 + + +from .command import Command +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import InvalidSelectorException +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys + +try: + str = basestring +except NameError: + pass + + +class WebElement(object): + """Represents an HTML element. + + Generally, all interesting operations to do with interacting with a page + will be performed through this interface.""" + def __init__(self, parent, id_): + self._parent = parent + self._id = id_ + + @property + def tag_name(self): + """Gets this element's tagName property.""" + return self._execute(Command.GET_ELEMENT_TAG_NAME)['value'] + + @property + def text(self): + """Gets the text of the element.""" + return self._execute(Command.GET_ELEMENT_TEXT)['value'] + + def click(self): + """Clicks the element.""" + self._execute(Command.CLICK_ELEMENT) + + def submit(self): + """Submits a form.""" + self._execute(Command.SUBMIT_ELEMENT) + + def clear(self): + """Clears the text if it's a text entry element.""" + self._execute(Command.CLEAR_ELEMENT) + + def get_attribute(self, name): + """Gets the attribute value. + + :Args: + - name - name of the attribute property to retieve. + + Example:: + + # Check if the 'active' css class is applied to an element. + is_active = "active" in target_element.get_attribute("class") + + """ + resp = self._execute(Command.GET_ELEMENT_ATTRIBUTE, {'name': name}) + attributeValue = '' + if resp['value'] is None: + attributeValue = None + else: + attributeValue = resp['value'] + if name != 'value' and attributeValue.lower() in ('true', 'false'): + attributeValue = attributeValue.lower() + + return attributeValue + + def is_selected(self): + """Whether the element is selected. + + Can be used to check if a checkbox or radio button is selected. + """ + return self._execute(Command.IS_ELEMENT_SELECTED)['value'] + + def is_enabled(self): + """Whether the element is enabled.""" + return self._execute(Command.IS_ELEMENT_ENABLED)['value'] + + def find_element_by_id(self, id_): + """Finds element within the child elements of this element. + + :Args: + - id_ - ID of child element to locate. + """ + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + """Finds a list of elements within the children of this element + with the matching ID. + + :Args: + - id_ - Id of child element to find. + """ + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_name(self, name): + """Find element with in this element's children by name. + :Args: + - name - name property of the element to find. + """ + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + """Finds a list of elements with in this element's children by name. + + :Args: + - name - name property to search for. + """ + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_link_text(self, link_text): + """Finds element with in this element's children by visible link text. + + :Args: + - link_text - Link text string to search for. + """ + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, link_text): + """Finds a list of elements with in this element's children by visible link text. + + :Args: + - link_text - Link text string to search for. + """ + return self.find_elements(by=By.LINK_TEXT, value=link_text) + + def find_element_by_partial_link_text(self, link_text): + """Finds element with in this element's children by parial visible link text. + + :Args: + - link_text - Link text string to search for. + """ + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + """Finds a list of elements with in this element's children by link text. + + :Args: + - link_text - Link text string to search for. + """ + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_tag_name(self, name): + """Finds element with in this element's children by tag name. + + :Args: + - name - name of html tag (eg: h1, a, span) + """ + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + """Finds a list of elements with in this element's children by tag name. + + :Args: + - name - name of html tag (eg: h1, a, span) + """ + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_xpath(self, xpath): + """Finds element by xpath. + + :Args: + xpath - xpath of element to locate. "//input[@class='myelement']" + + Note: The base path will be relative to this element's location. + + This will select the first link under this element.:: + + myelement.find_elements_by_xpath(".//a") + + However, this will select the first link on the page. + + myelement.find_elements_by_xpath("//a") + + """ + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + """Finds elements within the elements by xpath. + + :Args: + - xpath - xpath locator string. + + Note: The base path will be relative to this element's location. + + This will select all links under this element.:: + + myelement.find_elements_by_xpath(".//a") + + However, this will select all links in the page itself. + + myelement.find_elements_by_xpath("//a") + """ + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_class_name(self, name): + """Finds an element within this element's children by their class name. + + :Args: + - name - class name to search on. + """ + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + """Finds a list of elements within children of this element by their class name. + + :Args: + - name - class name to search on. + """ + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + """Find and return an element that's a child of this element by CSS selector. + + :Args: + - css_selector - CSS selctor string, ex: 'a.nav#home' + """ + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + """Find and return list of multiple elements within the children of this + element by CSS selector. + + :Args: + - css_selector - CSS selctor string, ex: 'a.nav#home' + """ + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def send_keys(self, *value): + """Simulates typing into the element. + + :Args: + - value - A string for typing, or setting form fields. For setting + file inputs, this could be a local file path. + + Use this to send simple key events or to fill out form fields:: + + form_textfield = driver.find_element_by_name('username') + form_textfield.send_keys("admin") + + This can also be used to set file inputs.:: + + file_input = driver.find_element_by_name('profilePic') + file_input.send_keys("path/to/profilepic.gif") + # Generally it's better to wrap the file path in one of the methods + # in os.path to return the actual path to support cross OS testing. + # file_input.send_keys(os.path.abspath("path/to/profilepic.gif")) + + """ + # transfer file to another machine only if remote driver is used + # the same behaviour as for java binding + if self.parent._is_remote: + local_file = LocalFileDetector.is_local_file(*value) + if local_file is not None: + value = self._upload(local_file) + + typing = [] + for val in value: + if isinstance(val, Keys): + typing.append(val) + elif isinstance(val, int): + val = val.__str__() + for i in range(len(val)): + typing.append(val[i]) + else: + for i in range(len(val)): + typing.append(val[i]) + self._execute(Command.SEND_KEYS_TO_ELEMENT, {'value': typing}) + + # RenderedWebElement Items + def is_displayed(self): + """Whether the element would be visible to a user + """ + return self._execute(Command.IS_ELEMENT_DISPLAYED)['value'] + + @property + def location_once_scrolled_into_view(self): + """CONSIDERED LIABLE TO CHANGE WITHOUT WARNING. Use this to discover where on the screen an + element is so that we can click it. This method should cause the element to be scrolled + into view. + + Returns the top lefthand corner location on the screen, or None if the element is not visible""" + return self._execute(Command.GET_ELEMENT_LOCATION_ONCE_SCROLLED_INTO_VIEW)['value'] + + @property + def size(self): + """ Returns the size of the element """ + size = self._execute(Command.GET_ELEMENT_SIZE)['value'] + new_size = {} + new_size["height"] = size["height"] + new_size["width"] = size["width"] + return new_size + + def value_of_css_property(self, property_name): + """ Returns the value of a CSS property """ + return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, + {'propertyName': property_name})['value'] + + @property + def location(self): + """ Returns the location of the element in the renderable canvas""" + old_loc = self._execute(Command.GET_ELEMENT_LOCATION)['value'] + new_loc = {"x": old_loc['x'], + "y": old_loc['y']} + return new_loc + + @property + def parent(self): + """ Returns parent element is available. """ + return self._parent + + @property + def id(self): + """ Returns internal id used by selenium. + + This is mainly for internal use. Simple use cases such as checking if 2 webelements + refer to the same element, can be done using '==':: + + if element1 == element2: + print("These 2 are equal") + + """ + return self._id + + def __eq__(self, element): + if self._id == element.id: + return True + else: + return self._execute(Command.ELEMENT_EQUALS, {'other': element.id})['value'] + + # Private Methods + def _execute(self, command, params=None): + """Executes a command against the underlying HTML element. + + Args: + command: The name of the command to _execute as a string. + params: A dictionary of named parameters to send with the command. + + Returns: + The command's JSON response loaded into a dictionary object. + """ + if not params: + params = {} + params['id'] = self._id + return self._parent.execute(command, params) + + def find_element(self, by=By.ID, value=None): + if not By.is_valid(by) or not isinstance(value, str): + raise InvalidSelectorException("Invalid locator values passed in") + + return self._execute(Command.FIND_CHILD_ELEMENT, + {"using": by, "value": value})['value'] + + def find_elements(self, by=By.ID, value=None): + if not By.is_valid(by) or not isinstance(value, str): + raise InvalidSelectorException("Invalid locator values passed in") + + return self._execute(Command.FIND_CHILD_ELEMENTS, + {"using": by, "value": value})['value'] + + def __hash__(self): + return int(hashlib.md5(self._id.encode('utf-8')).hexdigest(), 16) + + def _upload(self, filename): + fp = IOStream() + zipped = zipfile.ZipFile(fp, 'w', zipfile.ZIP_DEFLATED) + zipped.write(filename, os.path.split(filename)[1]) + zipped.close() + content = base64.encodestring(fp.getvalue()) + if not isinstance(content, str): + content = content.decode('utf-8') + try: + return self._execute(Command.UPLOAD_FILE, + {'file': content})['value'] + except WebDriverException as e: + if "Unrecognized command: POST" in e.__str__(): + return filename + elif "Command not found: POST " in e.__str__(): + return filename + elif '{"status":405,"value":["GET","HEAD","DELETE"]}' in e.__str__(): + return filename + else: + raise e + +class LocalFileDetector(object): + + @classmethod + def is_local_file(cls, *keys): + file_path = '' + typing = [] + for val in keys: + if isinstance(val, Keys): + typing.append(val) + elif isinstance(val, int): + val = val.__str__() + for i in range(len(val)): + typing.append(val[i]) + else: + for i in range(len(val)): + typing.append(val[i]) + file_path = ''.join(typing) + + if file_path is '': + return None + + try: + if os.path.isfile(file_path): + return file_path + except: + pass + return None + diff --git a/apps/selenium/webdriver/safari/__init__.py b/apps/selenium/webdriver/safari/__init__.py new file mode 100644 index 0000000000..a8bb36bb9b --- /dev/null +++ b/apps/selenium/webdriver/safari/__init__.py @@ -0,0 +1,14 @@ +# Copyright 2010 WebDriver committers +# Copyright 2010 Google 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. diff --git a/apps/selenium/webdriver/safari/service.py b/apps/selenium/webdriver/safari/service.py new file mode 100644 index 0000000000..27e4a84c15 --- /dev/null +++ b/apps/selenium/webdriver/safari/service.py @@ -0,0 +1,79 @@ +#!/usr/bin/python +# +# Copyright 2011 Webdriver_name committers +# Copyright 2011 Google 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. +import subprocess +from subprocess import PIPE +import time +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common import utils + +class Service(object): + """ + Object that manages the starting and stopping of the SafariDriver + """ + + def __init__(self, executable_path, port=0): + """ + Creates a new instance of the Service + + :Args: + - executable_path : Path to the SafariDriver + - port : Port the service is running on """ + + self.port = port + self.path = executable_path + if self.port == 0: + self.port = utils.free_port() + + def start(self): + """ + Starts the SafariDriver Service. + + :Exceptions: + - WebDriverException : Raised either when it can't start the service + or when it can't connect to the service + """ + try: + self.process = subprocess.Popen(["java", "-jar", self.path, "-port", "%s" % self.port]) + except: + raise WebDriverException( + "SafariDriver executable needs to be available in the path.") + time.sleep(10) + count = 0 + while not utils.is_connectable(self.port): + count += 1 + time.sleep(1) + if count == 30: + raise WebDriverException("Can not connect to the SafariDriver") + + @property + def service_url(self): + """ + Gets the url of the SafariDriver Service + """ + return "http://localhost:%d/wd/hub" % self.port + + def stop(self): + """ + Tells the SafariDriver to stop and cleans up the process + """ + # If it's dead don't worry + if self.process is None: + return + + self.process.kill() + self.process.wait() + diff --git a/apps/selenium/webdriver/safari/webdriver.py b/apps/selenium/webdriver/safari/webdriver.py new file mode 100644 index 0000000000..fab4225bb9 --- /dev/null +++ b/apps/selenium/webdriver/safari/webdriver.py @@ -0,0 +1,72 @@ +#!/usr/bin/python +# +# Copyright 2011-2013 Sofware 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. + +import base64 + +try: + import http.client as http_client +except ImportError: + import httplib as http_client + +import os +from selenium.webdriver.common.desired_capabilities import DesiredCapabilities +from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver +from .service import Service + +class WebDriver(RemoteWebDriver): + """ + Controls the SafariDriver and allows you to drive the browser. + + """ + + def __init__(self, executable_path=None, port=0, + desired_capabilities=DesiredCapabilities.SAFARI): + """ + Creates a new instance of the Safari driver. + + Starts the service and then creates new instance of Safari Driver. + + :Args: + - executable_path - path to the executable. If the default is used it assumes the executable is in the + Environment Variable SELENIUM_SERVER_JAR + - port - port you would like the service to run, if left as 0, a free port will be found. + - desired_capabilities: Dictionary object with desired capabilities (Can be used to provide various Safari switches). + """ + if executable_path is None: + try: + executable_path = os.environ["SELENIUM_SERVER_JAR"] + except: + raise Exception("No executable path given, please add one to Environment Variable \ + 'SELENIUM_SERVER_JAR'") + self.service = Service(executable_path, port=port) + self.service.start() + + RemoteWebDriver.__init__(self, + command_executor=self.service.service_url, + desired_capabilities=desired_capabilities) + self._is_remote = False + + def quit(self): + """ + Closes the browser and shuts down the SafariDriver executable + that is started when starting the SafariDriver + """ + try: + RemoteWebDriver.quit(self) + except http_client.BadStatusLine: + pass + finally: + self.service.stop() diff --git a/apps/selenium/webdriver/support/__init__.py b/apps/selenium/webdriver/support/__init__.py new file mode 100644 index 0000000000..79219fdf03 --- /dev/null +++ b/apps/selenium/webdriver/support/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/python +# +# Copyright 2011 WebDriver committers +# +# 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. + diff --git a/apps/selenium/webdriver/support/abstract_event_listener.py b/apps/selenium/webdriver/support/abstract_event_listener.py new file mode 100644 index 0000000000..1c2d44e00d --- /dev/null +++ b/apps/selenium/webdriver/support/abstract_event_listener.py @@ -0,0 +1,59 @@ +#!/usr/bin/python +# +# Copyright 2011 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. + + +class AbstractEventListener(object): + """ + Event listener must subclass and implement this fully or partially + """ + + def before_navigate_to(self, url, driver): pass + + def after_navigate_to(self, url, driver): pass + + def before_navigate_back(self, driver): pass + + def after_navigate_back(self, driver): pass + + def before_navigate_forward(self, driver): pass + + def after_navigate_forward(self, driver): pass + + def before_find(self, by, value, driver): pass + + def after_find(self, by, value, driver): pass + + def before_click(self, element, driver): pass + + def after_click(self, element, driver): pass + + def before_change_value_of(self, element, driver): pass + + def after_change_value_of(self, element, driver): pass + + def before_execute_script(self, script, driver): pass + + def after_execute_script(self, script, driver): pass + + def before_close(self, driver): pass + + def after_close(self, driver): pass + + def before_quit(self, driver): pass + + def after_quit(self, driver): pass + + def on_exception(self, exception, driver): pass diff --git a/apps/selenium/webdriver/support/color.py b/apps/selenium/webdriver/support/color.py new file mode 100644 index 0000000000..d21ecbe410 --- /dev/null +++ b/apps/selenium/webdriver/support/color.py @@ -0,0 +1,308 @@ +#!/usr/bin/python + +# Copyright 2011 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. + +RGB_PATTERN = r"^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$" +RGB_PCT_PATTERN = r"^\s*rgb\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*\)\s*$" +RGBA_PATTERN = r"^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(0|1|0\.\d+)\s*\)\s*$" +RGBA_PCT_PATTERN = r"^\s*rgba\(\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(\d{1,3}|\d{1,2}\.\d+)%\s*,\s*(0|1|0\.\d+)\s*\)\s*$" +HEX_PATTERN = r"#([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})([A-Fa-f0-9]{2})" +HEX3_PATTERN = r"#([A-Fa-f0-9])([A-Fa-f0-9])([A-Fa-f0-9])" +HSL_PATTERN = r"^\s*hsl\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*\)\s*$" +HSLA_PATTERN = r"^\s*hsla\(\s*(\d{1,3})\s*,\s*(\d{1,3})%\s*,\s*(\d{1,3})%\s*,\s*(0|1|0\.\d+)\s*\)\s*$" + + +class Color(object): + """ + Color conversion support class + + Example: + + .. code-block:: python + + from selenium.webdriver.support.color import Color + + print(Color.from_string('#00ff33').rgba) + print(Color.from_string('rgb(1, 255, 3)').hex) + print(Color.from_string('blue').rgba) + """ + + @staticmethod + def from_string(str_): + import re + + class Matcher(object): + def __init__(self): + self.match_obj = None + + def match(self, pattern, str_): + self.match_obj = re.match(pattern, str_) + return self.match_obj + + @property + def groups(self): + return () if self.match_obj is None else self.match_obj.groups() + + m = Matcher() + + if m.match(RGB_PATTERN, str_): + return Color(*m.groups) + elif m.match(RGB_PCT_PATTERN, str_): + rgb = tuple([float(each) / 100 * 255 for each in m.groups]) + return Color(*rgb) + elif m.match(RGBA_PATTERN, str_): + return Color(*m.groups) + elif m.match(RGBA_PCT_PATTERN, str_): + rgba = tuple([float(each) / 100 * 255 for each in m.groups[:3]] + [m.groups[3]]) + return Color(*rgba) + elif m.match(HEX_PATTERN, str_): + rgb = tuple([int(each, 16) for each in m.groups]) + return Color(*rgb) + elif m.match(HEX3_PATTERN, str_): + rgb = tuple([int(each * 2, 16) for each in m.groups]) + return Color(*rgb) + elif m.match(HSL_PATTERN, str_) or m.match(HSLA_PATTERN, str_): + return Color._from_hsl(*m.groups) + elif str_.upper() in Colors.keys(): + return Colors[str_.upper()] + else: + raise ValueError("Could not convert %s into color" % str_) + + @staticmethod + def _from_hsl(h, s, l, a=1): + h = float(h) / 360 + s = float(s) / 100 + l = float(l) / 100 + + if s == 0: + r = l + g = r + b = r + else: + luminocity2 = l * (1 + s) if l < 0.5 else l + s - l * s + luminocity1 = 2 * l - luminocity2 + + def hue_to_rgb(lum1, lum2, hue): + if hue < 0.0: + hue += 1 + if hue > 1.0: + hue -= 1 + + if hue < 1.0 / 6.0: + return (lum1 + (lum2 - lum1) * 6.0 * hue) + elif hue < 1.0 / 2.0: + return lum2 + elif hue < 2.0 / 3.0: + return lum1 + (lum2 - lum1) * ((2.0 / 3.0) - hue) * 6.0 + else: + return lum1 + + r = hue_to_rgb(luminocity1, luminocity2, h + 1.0 / 3.0) + g = hue_to_rgb(luminocity1, luminocity2, h) + b = hue_to_rgb(luminocity1, luminocity2, h - 1.0 / 3.0) + + return Color(r * 256, g * 256, b * 256, a) + + def __init__(self, red, green, blue, alpha=1): + self.red = int(red) + self.green = int(green) + self.blue = int(blue) + self.alpha = "1" if float(alpha) == 1 else str(float(alpha) or 0) + + @property + def rgb(self): + return "rgb(%d, %d, %d)" % (self.red, self.green, self.blue) + + @property + def rgba(self): + return "rgba(%d, %d, %d, %s)" % (self.red, self.green, self.blue, self.alpha) + + @property + def hex(self): + return "#%02x%02x%02x" % (self.red, self.green, self.blue) + + def __eq__(self, other): + if isinstance(other, Color): + return self.rgba == other.rgba + return NotImplemented + + def __ne__(self, other): + result = self.__eq__(other) + if result is NotImplemented: + return result + return not result + + def __hash__(self): + return hash((self.red, self.green, self.blue, self.alpha)) + + def __repr__(self): + return "Color(red=%d, green=%d, blue=%d, alpha=%s)" % (self.red, self.green, self.blue, self.alpha) + + def __str__(self): + return "Color: %s" % self.rgba + + +# Basic, extended and transparent colour keywords as defined by the W3C HTML4 spec +# See http://www.w3.org/TR/css3-color/#html4 +Colors = { + "TRANSPARENT": Color(0, 0, 0, 0), + "ALICEBLUE": Color(240, 248, 255), + "ANTIQUEWHITE": Color(250, 235, 215), + "AQUA": Color(0, 255, 255), + "AQUAMARINE": Color(127, 255, 212), + "AZURE": Color(240, 255, 255), + "BEIGE": Color(245, 245, 220), + "BISQUE": Color(255, 228, 196), + "BLACK": Color(0, 0, 0), + "BLANCHEDALMOND": Color(255, 235, 205), + "BLUE": Color(0, 0, 255), + "BLUEVIOLET": Color(138, 43, 226), + "BROWN": Color(165, 42, 42), + "BURLYWOOD": Color(222, 184, 135), + "CADETBLUE": Color(95, 158, 160), + "CHARTREUSE": Color(127, 255, 0), + "CHOCOLATE": Color(210, 105, 30), + "CORAL": Color(255, 127, 80), + "CORNFLOWERBLUE": Color(100, 149, 237), + "CORNSILK": Color(255, 248, 220), + "CRIMSON": Color(220, 20, 60), + "CYAN": Color(0, 255, 255), + "DARKBLUE": Color(0, 0, 139), + "DARKCYAN": Color(0, 139, 139), + "DARKGOLDENROD": Color(184, 134, 11), + "DARKGRAY": Color(169, 169, 169), + "DARKGREEN": Color(0, 100, 0), + "DARKGREY": Color(169, 169, 169), + "DARKKHAKI": Color(189, 183, 107), + "DARKMAGENTA": Color(139, 0, 139), + "DARKOLIVEGREEN": Color(85, 107, 47), + "DARKORANGE": Color(255, 140, 0), + "DARKORCHID": Color(153, 50, 204), + "DARKRED": Color(139, 0, 0), + "DARKSALMON": Color(233, 150, 122), + "DARKSEAGREEN": Color(143, 188, 143), + "DARKSLATEBLUE": Color(72, 61, 139), + "DARKSLATEGRAY": Color(47, 79, 79), + "DARKSLATEGREY": Color(47, 79, 79), + "DARKTURQUOISE": Color(0, 206, 209), + "DARKVIOLET": Color(148, 0, 211), + "DEEPPINK": Color(255, 20, 147), + "DEEPSKYBLUE": Color(0, 191, 255), + "DIMGRAY": Color(105, 105, 105), + "DIMGREY": Color(105, 105, 105), + "DODGERBLUE": Color(30, 144, 255), + "FIREBRICK": Color(178, 34, 34), + "FLORALWHITE": Color(255, 250, 240), + "FORESTGREEN": Color(34, 139, 34), + "FUCHSIA": Color(255, 0, 255), + "GAINSBORO": Color(220, 220, 220), + "GHOSTWHITE": Color(248, 248, 255), + "GOLD": Color(255, 215, 0), + "GOLDENROD": Color(218, 165, 32), + "GRAY": Color(128, 128, 128), + "GREY": Color(128, 128, 128), + "GREEN": Color(0, 128, 0), + "GREENYELLOW": Color(173, 255, 47), + "HONEYDEW": Color(240, 255, 240), + "HOTPINK": Color(255, 105, 180), + "INDIANRED": Color(205, 92, 92), + "INDIGO": Color(75, 0, 130), + "IVORY": Color(255, 255, 240), + "KHAKI": Color(240, 230, 140), + "LAVENDER": Color(230, 230, 250), + "LAVENDERBLUSH": Color(255, 240, 245), + "LAWNGREEN": Color(124, 252, 0), + "LEMONCHIFFON": Color(255, 250, 205), + "LIGHTBLUE": Color(173, 216, 230), + "LIGHTCORAL": Color(240, 128, 128), + "LIGHTCYAN": Color(224, 255, 255), + "LIGHTGOLDENRODYELLOW": Color(250, 250, 210), + "LIGHTGRAY": Color(211, 211, 211), + "LIGHTGREEN": Color(144, 238, 144), + "LIGHTGREY": Color(211, 211, 211), + "LIGHTPINK": Color(255, 182, 193), + "LIGHTSALMON": Color(255, 160, 122), + "LIGHTSEAGREEN": Color(32, 178, 170), + "LIGHTSKYBLUE": Color(135, 206, 250), + "LIGHTSLATEGRAY": Color(119, 136, 153), + "LIGHTSLATEGREY": Color(119, 136, 153), + "LIGHTSTEELBLUE": Color(176, 196, 222), + "LIGHTYELLOW": Color(255, 255, 224), + "LIME": Color(0, 255, 0), + "LIMEGREEN": Color(50, 205, 50), + "LINEN": Color(250, 240, 230), + "MAGENTA": Color(255, 0, 255), + "MAROON": Color(128, 0, 0), + "MEDIUMAQUAMARINE": Color(102, 205, 170), + "MEDIUMBLUE": Color(0, 0, 205), + "MEDIUMORCHID": Color(186, 85, 211), + "MEDIUMPURPLE": Color(147, 112, 219), + "MEDIUMSEAGREEN": Color(60, 179, 113), + "MEDIUMSLATEBLUE": Color(123, 104, 238), + "MEDIUMSPRINGGREEN": Color(0, 250, 154), + "MEDIUMTURQUOISE": Color(72, 209, 204), + "MEDIUMVIOLETRED": Color(199, 21, 133), + "MIDNIGHTBLUE": Color(25, 25, 112), + "MINTCREAM": Color(245, 255, 250), + "MISTYROSE": Color(255, 228, 225), + "MOCCASIN": Color(255, 228, 181), + "NAVAJOWHITE": Color(255, 222, 173), + "NAVY": Color(0, 0, 128), + "OLDLACE": Color(253, 245, 230), + "OLIVE": Color(128, 128, 0), + "OLIVEDRAB": Color(107, 142, 35), + "ORANGE": Color(255, 165, 0), + "ORANGERED": Color(255, 69, 0), + "ORCHID": Color(218, 112, 214), + "PALEGOLDENROD": Color(238, 232, 170), + "PALEGREEN": Color(152, 251, 152), + "PALETURQUOISE": Color(175, 238, 238), + "PALEVIOLETRED": Color(219, 112, 147), + "PAPAYAWHIP": Color(255, 239, 213), + "PEACHPUFF": Color(255, 218, 185), + "PERU": Color(205, 133, 63), + "PINK": Color(255, 192, 203), + "PLUM": Color(221, 160, 221), + "POWDERBLUE": Color(176, 224, 230), + "PURPLE": Color(128, 0, 128), + "RED": Color(255, 0, 0), + "ROSYBROWN": Color(188, 143, 143), + "ROYALBLUE": Color(65, 105, 225), + "SADDLEBROWN": Color(139, 69, 19), + "SALMON": Color(250, 128, 114), + "SANDYBROWN": Color(244, 164, 96), + "SEAGREEN": Color(46, 139, 87), + "SEASHELL": Color(255, 245, 238), + "SIENNA": Color(160, 82, 45), + "SILVER": Color(192, 192, 192), + "SKYBLUE": Color(135, 206, 235), + "SLATEBLUE": Color(106, 90, 205), + "SLATEGRAY": Color(112, 128, 144), + "SLATEGREY": Color(112, 128, 144), + "SNOW": Color(255, 250, 250), + "SPRINGGREEN": Color(0, 255, 127), + "STEELBLUE": Color(70, 130, 180), + "TAN": Color(210, 180, 140), + "TEAL": Color(0, 128, 128), + "THISTLE": Color(216, 191, 216), + "TOMATO": Color(255, 99, 71), + "TURQUOISE": Color(64, 224, 208), + "VIOLET": Color(238, 130, 238), + "WHEAT": Color(245, 222, 179), + "WHITE": Color(255, 255, 255), + "WHITESMOKE": Color(245, 245, 245), + "YELLOW": Color(255, 255, 0), + "YELLOWGREEN": Color(154, 205, 50) +} diff --git a/apps/selenium/webdriver/support/event_firing_webdriver.py b/apps/selenium/webdriver/support/event_firing_webdriver.py new file mode 100644 index 0000000000..cf1f4f325b --- /dev/null +++ b/apps/selenium/webdriver/support/event_firing_webdriver.py @@ -0,0 +1,326 @@ +#!/usr/bin/python +# +# Copyright 2011 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. + +from selenium.common.exceptions import WebDriverException +from selenium.webdriver.common.by import By +from selenium.webdriver.remote.webdriver import WebDriver +from selenium.webdriver.remote.webelement import WebElement +from .abstract_event_listener import AbstractEventListener + + +def _wrap_elements(result, ef_driver): + if isinstance(result, WebElement): + return EventFiringWebElement(result, ef_driver) + elif isinstance(result, list): + return [_wrap_elements(item, ef_driver) for item in result] + else: + return result + +class EventFiringWebDriver(object): + """ + A wrapper around an arbitrary WebDriver instance which supports firing events + """ + + def __init__(self, driver, event_listener): + """ + Creates a new instance of the EventFiringWebDriver + + :Args: + - driver : A WebDriver instance + - event_listener : Instance of a class that subclasses AbstractEventListener and implements it fully or partially + + Example: + + .. code-block:: python + + from selenium.webdriver import Firefox + from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener + + class MyListener(AbstractEventListener): + def before_navigate_to(self, url, driver): + print("Before navigate to %s" % url) + def after_navigate_to(self, url, driver): + print("After navigate to %s" % url) + + driver = Firefox() + ef_driver = EventFiringWebDriver(driver, MyListener()) + ef_driver.get("http://www.google.co.in/") + """ + if not isinstance(driver, WebDriver): + raise WebDriverException("A WebDriver instance must be supplied") + if not isinstance(event_listener, AbstractEventListener): + raise WebDriverException("Event listener must be a subclass of AbstractEventListener") + self._driver = driver + self._listener = event_listener + + @property + def wrapped_driver(self): + """Returns the WebDriver instance wrapped by this EventsFiringWebDriver""" + return self._driver + + def get(self, url): + self._dispatch("navigate_to", (url, self._driver), "get", (url, )) + + def back(self): + self._dispatch("navigate_back", (self._driver,), "back", ()) + + def forward(self): + self._dispatch("navigate_forward", (self._driver,), "forward", ()) + + def execute_script(self, script, *args): + unwrapped_args = (script,) + self._unwrap_element_args(args) + return self._dispatch("execute_script", (script, self._driver), "execute_script", unwrapped_args) + + def execute_async_script(self, script, *args): + unwrapped_args = (script,) + self._unwrap_element_args(args) + return self._dispatch("execute_script", (script, self._driver), "execute_async_script", unwrapped_args) + + def close(self): + self._dispatch("close", (self._driver,), "close", ()) + + def quit(self): + self._dispatch("quit", (self._driver,), "quit", ()) + + def find_element(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_element", (by, value)) + + def find_elements(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value)) + + def find_element_by_id(self, id_): + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_xpath(self, xpath): + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_link_text(self, link_text): + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, text): + return self.find_elements(by=By.LINK_TEXT, value=text) + + def find_element_by_partial_link_text(self, link_text): + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_name(self, name): + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_tag_name(self, name): + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_class_name(self, name): + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def _dispatch(self, l_call, l_args, d_call, d_args): + getattr(self._listener, "before_%s" % l_call)(*l_args) + try: + result = getattr(self._driver, d_call)(*d_args) + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + getattr(self._listener, "after_%s" % l_call)(*l_args) + return _wrap_elements(result, self) + + def _unwrap_element_args(self, args): + if isinstance(args, EventFiringWebElement): + return args.wrapped_element + elif isinstance(args, tuple): + return tuple([self._unwrap_element_args(item) for item in args]) + elif isinstance(args, list): + return [self._unwrap_element_args(item) for item in args] + else: + return args + + def __setattr__(self, item, value): + if item.startswith("_") or not hasattr(self._driver, item): + object.__setattr__(self, item, value) + else: + try: + object.__setattr__(self._driver, item, value) + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + + def __getattr__(self, name): + + def _wrap(*args): + try: + result = attrib(*args) + return _wrap_elements(result, self) + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + + if hasattr(self._driver, name): + try: + attrib = getattr(self._driver, name) + if not callable(attrib): + return attrib + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + return _wrap + + raise AttributeError(name) + + +class EventFiringWebElement(object): + """" + A wrapper around WebElement instance which supports firing events + """ + + def __init__(self, webelement, ef_driver): + """ + Creates a new instance of the EventFiringWebElement + """ + self._webelement = webelement + self._ef_driver = ef_driver + self._driver = ef_driver.wrapped_driver + self._listener = ef_driver._listener + + @property + def wrapped_element(self): + """Returns the WebElement wrapped by this EventFiringWebElement instance""" + return self._webelement + + def click(self): + self._dispatch("click", (self._webelement, self._driver), "click", ()) + + def clear(self): + self._dispatch("change_value_of", (self._webelement, self._driver), "clear", ()) + + def send_keys(self, *value): + self._dispatch("change_value_of", (self._webelement, self._driver), "send_keys", value) + + def find_element(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_element", (by, value)) + + def find_elements(self, by=By.ID, value=None): + return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value)) + + def find_element_by_id(self, id_): + return self.find_element(by=By.ID, value=id_) + + def find_elements_by_id(self, id_): + return self.find_elements(by=By.ID, value=id_) + + def find_element_by_name(self, name): + return self.find_element(by=By.NAME, value=name) + + def find_elements_by_name(self, name): + return self.find_elements(by=By.NAME, value=name) + + def find_element_by_link_text(self, link_text): + return self.find_element(by=By.LINK_TEXT, value=link_text) + + def find_elements_by_link_text(self, link_text): + return self.find_elements(by=By.LINK_TEXT, value=link_text) + + def find_element_by_partial_link_text(self, link_text): + return self.find_element(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_elements_by_partial_link_text(self, link_text): + return self.find_elements(by=By.PARTIAL_LINK_TEXT, value=link_text) + + def find_element_by_tag_name(self, name): + return self.find_element(by=By.TAG_NAME, value=name) + + def find_elements_by_tag_name(self, name): + return self.find_elements(by=By.TAG_NAME, value=name) + + def find_element_by_xpath(self, xpath): + return self.find_element(by=By.XPATH, value=xpath) + + def find_elements_by_xpath(self, xpath): + return self.find_elements(by=By.XPATH, value=xpath) + + def find_element_by_class_name(self, name): + return self.find_element(by=By.CLASS_NAME, value=name) + + def find_elements_by_class_name(self, name): + return self.find_elements(by=By.CLASS_NAME, value=name) + + def find_element_by_css_selector(self, css_selector): + return self.find_element(by=By.CSS_SELECTOR, value=css_selector) + + def find_elements_by_css_selector(self, css_selector): + return self.find_elements(by=By.CSS_SELECTOR, value=css_selector) + + def _dispatch(self, l_call, l_args, d_call, d_args): + getattr(self._listener, "before_%s" % l_call)(*l_args) + try: + result = getattr(self._webelement, d_call)(*d_args) + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + getattr(self._listener, "after_%s" % l_call)(*l_args) + return _wrap_elements(result, self._ef_driver) + + def __setattr__(self, item, value): + if item.startswith("_") or not hasattr(self._webelement, item): + object.__setattr__(self, item, value) + else: + try: + object.__setattr__(self._webelement, item, value) + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + + def __getattr__(self, name): + + def _wrap(*args): + try: + result = attrib(*args) + return _wrap_elements(result, self._ef_driver) + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + + if hasattr(self._webelement, name): + try: + attrib = getattr(self._webelement, name) + if not callable(attrib): + return attrib + except Exception as e: + self._listener.on_exception(e, self._driver) + raise e + return _wrap + + raise AttributeError(name) diff --git a/apps/selenium/webdriver/support/events.py b/apps/selenium/webdriver/support/events.py new file mode 100644 index 0000000000..8ea0a42d1a --- /dev/null +++ b/apps/selenium/webdriver/support/events.py @@ -0,0 +1,18 @@ +#!/usr/bin/python +# +# Copyright 2011 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. + +from .abstract_event_listener import AbstractEventListener +from .event_firing_webdriver import EventFiringWebDriver diff --git a/apps/selenium/webdriver/support/expected_conditions.py b/apps/selenium/webdriver/support/expected_conditions.py new file mode 100644 index 0000000000..e1883ea16c --- /dev/null +++ b/apps/selenium/webdriver/support/expected_conditions.py @@ -0,0 +1,287 @@ +# +# Copyright 2012 WebDriver 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. + +from selenium.common.exceptions import NoSuchElementException +from selenium.common.exceptions import NoSuchFrameException +from selenium.common.exceptions import StaleElementReferenceException +from selenium.common.exceptions import WebDriverException +from selenium.common.exceptions import NoAlertPresentException + +""" + * Canned "Expected Conditions" which are generally useful within webdriver + * tests. +""" +class title_is(object): + """An expectation for checking the title of a page. + title is the expected title, which must be an exact match + returns True if the title matches, false otherwise.""" + def __init__(self, title): + self.title = title + + def __call__(self, driver): + return self.title == driver.title + +class title_contains(object): + """ An expectation for checking that the title contains a case-sensitive + substring. title is the fragment of title expected + returns True when the title matches, False otherwise + """ + def __init__(self, title): + self.title = title + + def __call__(self, driver): + return self.title in driver.title + +class presence_of_element_located(object): + """ An expectation for checking that an element is present on the DOM + of a page. This does not necessarily mean that the element is visible. + locator - used to find the element + returns the WebElement once it is located + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + return _find_element(driver, self.locator) + +class visibility_of_element_located(object): + """ An expectation for checking that an element is present on the DOM of a + page and visible. Visibility means that the element is not only displayed + but also has a height and width that is greater than 0. + locator - used to find the element + returns the WebElement once it is located and visible + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + try: + return _element_if_visible(_find_element(driver, self.locator)) + except StaleElementReferenceException: + return False + +class visibility_of(object): + """ An expectation for checking that an element, known to be present on the + DOM of a page, is visible. Visibility means that the element is not only + displayed but also has a height and width that is greater than 0. + element is the WebElement + returns the (same) WebElement once it is visible + """ + def __init__(self, element): + self.element = element + + def __call__(self, ignored): + return _element_if_visible(self.element) + +def _element_if_visible(element): + return element if element.is_displayed() else False + +class presence_of_all_elements_located(object): + """ An expectation for checking that there is at least one element present + on a web page. + locator is used to find the element + returns the list of WebElements once they are located + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + return _find_elements(driver, self.locator) + +class text_to_be_present_in_element(object): + """ An expectation for checking if the given text is present in the + specified element. + locator, text + """ + def __init__(self, locator, text_): + self.locator = locator + self.text = text_ + + def __call__(self, driver): + try : + element_text = _find_element(driver, self.locator).text + return self.text in element_text + except StaleElementReferenceException: + return False + +class text_to_be_present_in_element_value(object): + """ + An expectation for checking if the given text is present in the element's + locator, text + """ + def __init__(self, locator, text_): + self.locator = locator + self.text = text_ + + def __call__(self, driver): + try: + element_text = _find_element(driver, + self.locator).get_attribute("value") + if element_text: + return self.text in element_text + else: + return False + except StaleElementReferenceException: + return False + +class frame_to_be_available_and_switch_to_it(object): + """ An expectation for checking whether the given frame is available to + switch to. If the frame is available it switches the given driver to the + specified frame. + """ + def __init__(self, locator): + self.frame_locator = locator + + def __call__(self, driver): + try: + if isinstance(self.frame_locator, tuple): + driver.switch_to.frame(_find_element(driver, + self.frame_locator)) + else: + driver.switch_to.frame(self.frame_locator) + return True + except NoSuchFrameException: + return False + +class invisibility_of_element_located(object): + """ An Expectation for checking that an element is either invisible or not + present on the DOM. + + locator used to find the element + """ + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + try: + return not _find_element(driver, self.locator).is_displayed() + except (NoSuchElementException, StaleElementReferenceException): + # In the case of NoSuchElement, returns true because the element is + # not present in DOM. The try block checks if the element is present + # but is invisible. + # In the case of StaleElementReference, returns true because stale + # element reference implies that element is no longer visible. + return True + +class element_to_be_clickable(object): + """ An Expectation for checking an element is visible and enabled such that + you can click it.""" + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + element = visibility_of_element_located(self.locator)(driver) + if element and element.is_enabled(): + return element + else: + return False + +class staleness_of(object): + """ Wait until an element is no longer attached to the DOM. + element is the element to wait for. + returns False if the element is still attached to the DOM, true otherwise. + """ + def __init__(self, element): + self.element = element + + def __call__(self, ignored): + try: + # Calling any method forces a staleness check + self.element.is_enabled() + return False + except StaleElementReferenceException as expected: + return True + +class element_to_be_selected(object): + """ An expectation for checking the selection is selected. + element is WebElement object + """ + def __init__(self, element): + self.element = element + + def __call__(self, ignored): + return self.element.is_selected() + +class element_located_to_be_selected(object): + """An expectation for the element to be located is selected. + locator is a tuple of (by, path)""" + def __init__(self, locator): + self.locator = locator + + def __call__(self, driver): + return _find_element(driver, self.locator).is_selected() + +class element_selection_state_to_be(object): + """ An expectation for checking if the given element is selected. + element is WebElement object + is_selected is a Boolean." + """ + def __init__(self, element, is_selected): + self.element = element + self.is_selected = is_selected + + def __call__(self, ignored): + return self.element.is_selected() == self.is_selected + +class element_located_selection_state_to_be(object): + """ An expectation to locate an element and check if the selection state + specified is in that state. + locator is a tuple of (by, path) + is_selected is a boolean + """ + def __init__(self, locator, is_selected): + self.locator = locator + self.is_selected = is_selected + + def __call__(self, driver): + try: + element = _find_element(driver, self.locator) + return element.is_selected() == self.is_selected + except StaleElementReferenceException: + return False + +class alert_is_present(object): + """ Expect an alert to be present.""" + def __init__(self): + pass + + def __call__(self, driver): + try: + alert = driver.switch_to.alert + alert.text + return alert + except NoAlertPresentException: + return False + +def _find_element(driver, by): + """ Looks up an element. Logs and re-raises WebDriverException if thrown. + Method exists to gather data for + http://code.google.com/p/selenium/issues/detail?id=1800 + """ + try : + return driver.find_element(*by) + except NoSuchElementException as e: + raise e + except WebDriverException as e: + raise e + + +def _find_elements(driver, by): + try : + return driver.find_elements(*by) + except WebDriverException as e: + raise e + diff --git a/apps/selenium/webdriver/support/select.py b/apps/selenium/webdriver/support/select.py new file mode 100644 index 0000000000..cc5e6d8e00 --- /dev/null +++ b/apps/selenium/webdriver/support/select.py @@ -0,0 +1,223 @@ +#!/usr/bin/python +# +# Copyright 2011 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. + +from selenium.webdriver.common.by import By +from selenium.common.exceptions import NoSuchElementException, UnexpectedTagNameException + +class Select: + + def __init__(self, webelement): + """ + Constructor. A check is made that the given element is, indeed, a SELECT tag. If it is not, + then an UnexpectedTagNameException is thrown. + + :Args: + - webelement - element SELECT element to wrap + + Example: + from selenium.webdriver.support.ui import Select \n + Select(driver.find_element_by_tag_name("select")).select_by_index(2) + """ + if webelement.tag_name.lower() != "select": + raise UnexpectedTagNameException( + "Select only works on