Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ddaf7441cc | ||
|
|
a5f64e9abc | ||
|
|
38ebf2613b | ||
|
|
394f096dab | ||
|
|
3707d9f0bf | ||
|
|
433b8db2d4 | ||
|
|
1339b9c95d | ||
|
|
f6a3e91c3f | ||
|
|
4997a316c1 | ||
|
|
a9ce4718c2 | ||
|
|
ad17ce2f4b |
+17
-33
@@ -7,6 +7,7 @@ selenium webdriver wrapper to make manipulation easier.
|
||||
Features
|
||||
--------
|
||||
|
||||
* Support Internet Explorer, Chrome, Opera, Firefox, PhantomJS
|
||||
* Support remote webdriver.
|
||||
* Easy to type aliases. (find_element_by_xpath => xpath, etc.)
|
||||
* Wrapped WebdriverWait( ... ).until( ... ) pattern.
|
||||
@@ -60,25 +61,10 @@ Each contents in SeleniumContainerWrapper also SeleniumWrapper::
|
||||
|
||||
>>> tds = [tr.xpath("//td", eager=True) for tr in br.xpath("//tr", eager=True)]
|
||||
|
||||
If current page already load jquery.js, you can find elements by JQuery selector::
|
||||
|
||||
>>> buttons = br.jquery('#hoge .fuga li:even')
|
||||
|
||||
If current page doesn't load jquery, you can load your local jquery by hand::
|
||||
|
||||
>>> br.load_js("/path/to/your/local/jquery.js")
|
||||
|
||||
or::
|
||||
|
||||
>>> br.load_js(StringIO.StringIO("/path/to/your/local/jquery.js")
|
||||
|
||||
You can execute javascript by invoking **script** method::
|
||||
|
||||
>>> br.script('alert("hoge")')
|
||||
|
||||
Basic API
|
||||
---------
|
||||
* seleniumwrapper.create(drivername)
|
||||
* seleniumwrapper.connect(drivername, executor, custom_capabilities)
|
||||
Create webdriver instance and wrap it with SeleniumWrapper.
|
||||
|
||||
SeleniumWrapper
|
||||
@@ -94,6 +80,15 @@ SeleniumWrapper
|
||||
|
||||
>>> price = br.by_tag("th", "Price").parent.by_tag("td").text
|
||||
|
||||
* performance
|
||||
Returns window.performance wrapped object::
|
||||
|
||||
>>> performance = br.performance
|
||||
>>> timing = performance.timing
|
||||
>>> navigation = performance.navigation
|
||||
>>> timing.loadEventEnd
|
||||
1358319427476
|
||||
|
||||
* to_select
|
||||
Convert wrapped WebElement to raw Select object::
|
||||
|
||||
@@ -132,21 +127,6 @@ SeleniumWrapper
|
||||
|
||||
>>> br.button("Send").click()
|
||||
|
||||
* load_js(path_or_file)
|
||||
Load javascript from /path/to/file or file like object that responds to 'read' method::
|
||||
|
||||
>>> br.load_js("/path/to/jquery.js")
|
||||
|
||||
* jquery(target)
|
||||
Find elements by jquery selector::
|
||||
|
||||
>>> br.jquery('#hoge .fuga li:even')
|
||||
|
||||
* script(javascript)
|
||||
Execute given javascript::
|
||||
|
||||
>>> br.script('alert("hoge")')
|
||||
|
||||
* scroll_to(x, y)
|
||||
equivalent to javascript's scrollTo::
|
||||
|
||||
@@ -263,10 +243,14 @@ SeleniumContainerWrapper
|
||||
|
||||
Recent Change
|
||||
-------------
|
||||
* 0.4.3
|
||||
* Add **perfomance**, **performance.timing**, **performance.navigation**, **performance.memory** properties.
|
||||
* 0.4.2
|
||||
* Add 'PhantomJS' support.
|
||||
* 0.4.1
|
||||
* Fixed some bugs.
|
||||
* 0.4.0
|
||||
* Added **scroll_to**, **scroll_by**, **scroll_into_view** methods.
|
||||
* Added **jquery** method to support jquery selecter.
|
||||
* Added **load_js**, **script** methods.
|
||||
* 0.3.5
|
||||
* Added **attr** method.
|
||||
* Fixed some typos.
|
||||
|
||||
@@ -22,14 +22,14 @@ classifiers = [
|
||||
'Topic :: Software Development :: Libraries :: Python Modules'
|
||||
]
|
||||
|
||||
requires = ['selenium>=2.25.0']
|
||||
requires = ['selenium>=2.28.0']
|
||||
|
||||
setup(
|
||||
author='Keita Oouchi',
|
||||
author_email='keita.oouchi@gmail.com',
|
||||
url = 'https://github.com/keitaoouchi/seleniumwrapper',
|
||||
name = 'seleniumwrapper',
|
||||
version = '0.4.0',
|
||||
version = '0.4.3',
|
||||
package_dir={"":"src"},
|
||||
packages = ['seleniumwrapper'],
|
||||
test_suite = "test_seleniumwrapper.suite",
|
||||
|
||||
@@ -6,7 +6,7 @@ import inspect
|
||||
import time
|
||||
import random
|
||||
import selenium
|
||||
from selenium.webdriver import Ie, Opera, Chrome, Firefox
|
||||
from selenium.webdriver import Ie, Opera, Chrome, Firefox, PhantomJS
|
||||
from selenium.webdriver.remote.webdriver import WebDriver
|
||||
from selenium.webdriver.remote.webelement import WebElement
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
@@ -23,7 +23,8 @@ def create(drivername, *args, **kwargs):
|
||||
drivers = {'ie': Ie,
|
||||
'opera': Opera,
|
||||
'chrome': Chrome,
|
||||
'firefox': Firefox}
|
||||
'firefox': Firefox,
|
||||
'phantomjs': PhantomJS}
|
||||
dname = drivername.lower()
|
||||
if dname in drivers:
|
||||
try:
|
||||
@@ -31,7 +32,8 @@ def create(drivername, *args, **kwargs):
|
||||
except Exception as e:
|
||||
raise e
|
||||
else:
|
||||
msg = "drivername should be one of [IE, Opera, Chrome, Firefox](case-insentive). given {0}".format(drivername)
|
||||
msg = "".join(("drivername should be one of [IE, Opera, Chrome, Firefox, PhantomJS]",
|
||||
"(case-insentive). given {0}".format(drivername)))
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
@@ -49,7 +51,8 @@ def connect(drivername, executor, custom_capabilities=None, **kwargs):
|
||||
'opera': DesiredCapabilities.OPERA,
|
||||
'chrome': DesiredCapabilities.CHROME,
|
||||
'firefox': DesiredCapabilities.FIREFOX,
|
||||
'android': DesiredCapabilities.ANDROID}
|
||||
'android': DesiredCapabilities.ANDROID,
|
||||
'phatomjs': DesiredCapabilities.PHANTOMJS}
|
||||
dname = drivername.lower()
|
||||
if dname in capabilities:
|
||||
capability = capabilities[dname]
|
||||
@@ -62,7 +65,8 @@ def connect(drivername, executor, custom_capabilities=None, **kwargs):
|
||||
except Exception as e:
|
||||
raise e
|
||||
else:
|
||||
msg = "drivername should be one of [IE, Opera, Chrome, Firefox](case-insentive). given {0}".format(drivername)
|
||||
msg = "".join(("drivername should be one of [IE, Opera, Chrome, Firefox, PhantomJS]",
|
||||
"(case-insentive). given {0}".format(drivername)))
|
||||
raise ValueError(msg)
|
||||
|
||||
|
||||
@@ -86,7 +90,6 @@ def _chainreact(__getattr__):
|
||||
def reaction(*realargs):
|
||||
result = methodobj(*realargs)
|
||||
# for side-effective method(append, ...)
|
||||
result = result if result is not None else self
|
||||
return wrap_or_else(result)
|
||||
|
||||
return reaction
|
||||
@@ -95,6 +98,71 @@ def _chainreact(__getattr__):
|
||||
|
||||
return containment
|
||||
|
||||
class Performance(object):
|
||||
def __init__(self, performance):
|
||||
if not isinstance(performance, dict):
|
||||
raise AttributeError('Wrapped performance object should be an instance of dict.')
|
||||
self._performance = performance
|
||||
|
||||
@property
|
||||
def memory(self):
|
||||
if 'memory' in self._performance:
|
||||
return Memory(self._performance['memory'])
|
||||
raise AttributeError('window.performance.memory is not supported in this browser.')
|
||||
|
||||
@property
|
||||
def navigation(self):
|
||||
if 'navigation' in self._performance:
|
||||
return Navigation(self._performance['navigation'])
|
||||
raise AttributeError('window.performance.navigation is not supported in this browser.')
|
||||
|
||||
@property
|
||||
def timing(self):
|
||||
if 'timing' in self._performance:
|
||||
return Timing(self._performance['timing'])
|
||||
raise AttributeError('window.performance.timing is not supported in this browser.')
|
||||
|
||||
class Memory(object):
|
||||
def __init__(self, memory):
|
||||
self._memory = memory
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._memory[name]
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
return self._memory
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._memory)
|
||||
|
||||
class Navigation(object):
|
||||
def __init__(self, navigation):
|
||||
self._navigation = navigation
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._navigation[name]
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
return self._navigation
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._navigation)
|
||||
|
||||
class Timing(object):
|
||||
def __init__(self, timing):
|
||||
self._timing = timing
|
||||
|
||||
def __getattr__(self, name):
|
||||
return self._timing[name]
|
||||
|
||||
@property
|
||||
def __dict__(self):
|
||||
return self._timing
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._timing)
|
||||
|
||||
class SeleniumWrapper(object):
|
||||
def __init__(self, driver):
|
||||
@@ -109,6 +177,24 @@ class SeleniumWrapper(object):
|
||||
def unwrap(self):
|
||||
return self._wrapped
|
||||
|
||||
@property
|
||||
def performance(self):
|
||||
if isinstance(self._wrapped, WebDriver):
|
||||
if self._wrapped.execute_script("return 'performance' in window;"):
|
||||
timeout = time.time() + 120
|
||||
script = "return window.performance.timing.loadEventEnd;"
|
||||
executor = self._wrapped.execute_script
|
||||
while time.time() < timeout and not executor(script):
|
||||
time.sleep(0.2)
|
||||
if not executor(script):
|
||||
raise Exception('Timeout!')
|
||||
performance = self._wrapped.execute_script('return window.performance;')
|
||||
return Performance(performance)
|
||||
else:
|
||||
raise AttributeError("This browser is not supporting Timing APIs.")
|
||||
else:
|
||||
raise AttributeError("'WebElement' object has not attribute 'performance'")
|
||||
|
||||
@property
|
||||
def parent(self):
|
||||
if isinstance(self._wrapped, WebElement):
|
||||
@@ -226,47 +312,14 @@ class SeleniumWrapper(object):
|
||||
try:
|
||||
if presleep:
|
||||
time.sleep(presleep)
|
||||
self._wait_until_stopping(timeout, 0.1)
|
||||
self._wait_until_displayed(timeout, 0.3)
|
||||
self._wait_until_clickable(timeout, 0.3)
|
||||
self._wait_until_stopping(timeout, 0.01)
|
||||
self._wait_until_displayed(timeout, 0.01)
|
||||
self._wait_until_clickable(timeout, 0.01)
|
||||
if postsleep:
|
||||
time.sleep(postsleep)
|
||||
except Exception as e:
|
||||
raise e
|
||||
|
||||
def load_js(self, path_or_file):
|
||||
"""Load javascript from /path/to/file or file like object that responds to 'read' method."""
|
||||
if isinstance(path_or_file, str) and os.path.isfile(path_or_file):
|
||||
with open(path_or_file, 'r') as f:
|
||||
library = f.read()
|
||||
self._wrapped.execute_script(library)
|
||||
elif hasattr(path_or_file, 'read'):
|
||||
library = path_or_file.read()
|
||||
self._wrapped.execute_script(library)
|
||||
else:
|
||||
raise AttributeError('Given argument is not both file or /path/to/file:: {0}'.format(str(path_or_file)))
|
||||
|
||||
def jquery(self, target):
|
||||
"""Returns SeleniumContainerWrapper if any elements is found."""
|
||||
script = "try{{return $('{0}');}}catch(e){{}}".format(target)
|
||||
result = self._wrapped.execute_script(script)
|
||||
if result:
|
||||
if isinstance(result, collections.Sequence):
|
||||
return SeleniumContainerWrapper(result)
|
||||
else:
|
||||
return SeleniumWrapper(result)
|
||||
else:
|
||||
raise NoSuchElementException("Target {0} not found.".format(target))
|
||||
|
||||
def script(self, javascript, *args):
|
||||
"""Synchronously execute given javascript."""
|
||||
result = self._wrapped.execute_script(javascript, *args)
|
||||
if result:
|
||||
if isinstance(result, collections.Sequence):
|
||||
return SeleniumContainerWrapper(result)
|
||||
else:
|
||||
return SeleniumWrapper(result)
|
||||
|
||||
def scroll_to(self, x, y):
|
||||
if isinstance(self._wrapped, WebDriver):
|
||||
return self._wrapped.execute_script("window.scrollTo({:d}, {:d})".format(x, y))
|
||||
|
||||
@@ -135,6 +135,13 @@ class TestSeleniumWrapper(unittest.TestCase):
|
||||
wrapper = SeleniumWrapper(mocked_element)
|
||||
self.assertIsInstance(wrapper.unwrap, WebElement)
|
||||
|
||||
def test_execute_script_should_return_None_if_null_was_returned(self):
|
||||
mocked_driver = mock.Mock(WebDriver)
|
||||
mocked_driver.execute_script.return_value = None
|
||||
driver = SeleniumWrapper(mocked_driver)
|
||||
obj = driver.execute_script('return null')
|
||||
self.assertIsNone(obj)
|
||||
|
||||
class TestSeleniumWrapperAliases(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
@@ -209,31 +216,6 @@ class TestSeleniumWrapperJavascriptSupport(unittest.TestCase):
|
||||
mocky = mock.Mock(WebDriver)
|
||||
self.mock = mocky
|
||||
|
||||
def test_load_js_raise_if_given_argument_is_not_both_file_like_obj_or_path_to_file(self):
|
||||
wrapper = SeleniumWrapper(self.mock)
|
||||
self.assertRaises(AttributeError, wrapper.load_js, "/path/not/exists")
|
||||
self.assertRaises(AttributeError, wrapper.load_js, 1)
|
||||
|
||||
def test_load_js_load_string_from_StringIO_because_it_respond_to_read_method(self):
|
||||
f = StringIO.StringIO('hoge')
|
||||
wrapper = SeleniumWrapper(self.mock)
|
||||
wrapper.load_js(f)
|
||||
self.mock.execute_script.assert_called_once_with('hoge')
|
||||
|
||||
def test_script_returns_if_something_is_returned_by_execute_script(self):
|
||||
self.mock.execute_script.return_value = [mock.Mock(WebElement)]
|
||||
wrapper = SeleniumWrapper(self.mock)
|
||||
self.assertIsInstance(wrapper.script('fuga'), SeleniumContainerWrapper)
|
||||
self.mock.execute_script.return_value = mock.Mock(WebElement)
|
||||
self.assertIsInstance(wrapper.script('fuga'), SeleniumWrapper)
|
||||
self.mock.execute_script.return_value = None
|
||||
self.assertIsNone(wrapper.script('fuga'))
|
||||
|
||||
def test_jquery_raise_if_nothing_found(self):
|
||||
self.mock.execute_script.return_value = None
|
||||
wrapper = SeleniumWrapper(self.mock)
|
||||
self.assertRaises(NoSuchElementException, wrapper.jquery, 'hoge')
|
||||
|
||||
def test_scroll_methods_raise_if_wrapped_is_not_webdriver(self):
|
||||
wrapper = SeleniumWrapper(mock.Mock(WebElement))
|
||||
self.assertRaises(AttributeError, wrapper.scroll_to, *[10, 10])
|
||||
|
||||
Reference in New Issue
Block a user