diff --git a/README.md b/README.md index d6d09112c13..c05d6780735 100755 --- a/README.md +++ b/README.md @@ -199,11 +199,25 @@ nosetests my_first_test.py --browser=firefox --with-selenium -s ``` After the test completes, in the console output you'll see a dot on a new line, representing a passing test. (On test failures you'll see an F instead, and on test errors you'll see an E). It looks more like a moving progress bar when you're running a ton of unit tests side by side. This is part of nosetests. After all tests complete (in this case there is only one), you'll see the "Ran 1 test in ..." line, followed by an "OK" if all nosetests passed. -If the example is moving too fast for your eyes to see what's going on, there are 2 things you can do. Add either of the following: + +If the example is moving too fast for your eyes to see what's going on, there are a few things you can do. +You can add ``--demo_mode`` on the command line, which pauses the browser for about a second (by default) after each action: + +```bash +nosetests my_first_test.py --with-selenium -s --demo_mode +``` + +You can override the default wait time by either updating [settings.py](https://github.com/mdmintz/SeleniumBase/blob/master/seleniumbase/config/settings.py) or by using ``--demo_sleep={NUM}`` when using Demo Mode. (NOTE: If you use ``--demo_sleep={NUM}`` without using ``--demo_mode``, nothing will happen.) + +```bash +nosetests my_first_test.py --with-selenium -s --demo_mode --demo_sleep=1.2 +``` + +You can also add either of the following to your scripts to slow down the tests: ```python -import time; time.sleep(5) # sleep for 5 seconds (add this after the line you want to pause on) -import ipdb; ipdb.set_trace() # waits for your command. n = next line of current method, c = continue, s = step / next executed line (will jump) +import time; time.sleep(5) # sleep for 5 seconds (add this after the line you want to pause on) +import ipdb; ipdb.set_trace() # waits for your command. n = next line of current method, c = continue, s = step / next executed line (will jump) ``` (NOTE: If you're using pytest instead of nosetests and you want to use ipdb in your script for debugging purposes, you'll either need to add "--capture=no" on the command line, or use "import pytest; pytest.set_trace()" instead of using ipdb. More info on that [here](http://stackoverflow.com/questions/2678792/can-i-debug-with-python-debugger-when-using-py-test-somehow).) diff --git a/conftest.py b/conftest.py index 75007b42244..50829f86204 100755 --- a/conftest.py +++ b/conftest.py @@ -34,6 +34,15 @@ def pytest_addoption(parser): parser.addoption('--log_path', dest='log_path', default='logs/', help='Where the log files are saved.') + parser.addoption('--demo_mode', action="store_true", + dest='demo_mode', + default=False, + help="""Using this slows down the automation so that + you can see what it's actually doing.""") + parser.addoption('--demo_sleep', action='store', dest='demo_sleep', + default=None, + help="""Setting this overrides the Demo Mode sleep + time that happens after browser actions.""") def pytest_configure(config): @@ -41,7 +50,11 @@ def pytest_configure(config): with_testing_base = config.getoption('with_testing_base') browser = config.getoption('browser') log_path = config.getoption('log_path') + demo_mode = config.getoption('demo_mode') + demo_sleep = '' data = '' + if config.getoption('demo_sleep') is not None: + demo_sleep = config.getoption('demo_sleep') if config.getoption('data') is not None: data = config.getoption('data') # Create a temporary config file while tests are running @@ -52,6 +65,8 @@ def pytest_configure(config): config_file.write("data:::%s\n" % data) config_file.write("with_testing_base:::%s\n" % with_testing_base) config_file.write("log_path:::%s\n" % log_path) + config_file.write("demo_mode:::%s\n" % demo_mode) + config_file.write("demo_sleep:::%s\n" % demo_sleep) config_file.close() log_folder_setup(config) diff --git a/docker/docker_setup.py b/docker/docker_setup.py index 89e17d791c4..baacb1bf155 100755 --- a/docker/docker_setup.py +++ b/docker/docker_setup.py @@ -8,7 +8,7 @@ setup( name='seleniumbase', - version='1.1.15', + version='1.1.16', author='Michael Mintz', author_email='@mintzworld', maintainer='Michael Mintz', diff --git a/seleniumbase/config/settings.py b/seleniumbase/config/settings.py index 6ae63fc1784..c1a9d64ce71 100755 --- a/seleniumbase/config/settings.py +++ b/seleniumbase/config/settings.py @@ -10,6 +10,12 @@ LARGE_TIMEOUT = 10 EXTREME_TIMEOUT = 30 +# Default time to wait after each browser action performed during Demo Mode +# Use Demo Mode when you want others to see what your automation is doing +# Usage: --demo_mode when run from the command line when using --with-selenium +# This value can be overwritten on the command line by using --demo_sleep=FLOAT +DEFAULT_DEMO_MODE_TIMEOUT = 1.2 + # If True, existing logs from past test runs will be saved and take up space. # If False, only the logs from the most recent test run will be saved locally. # This has no effect on Jenkins/S3/MySQL, which may still be saving test logs. diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 84bac6e87bb..f9dccbb9915 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -38,6 +38,7 @@ def open(self, url): self.driver.get(url) if settings.WAIT_FOR_RSC_ON_PAGE_LOADS: self.wait_for_ready_state_complete() + self._demo_mode_pause_if_active() def open_url(self, url): """ In case people are mixing up self.open() with open(), @@ -48,9 +49,11 @@ def click(self, selector, by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT): element = page_actions.wait_for_element_visible( self.driver, selector, by, timeout=timeout) + self._demo_mode_scroll_if_active(selector, by) element.click() if settings.WAIT_FOR_RSC_ON_CLICKS: self.wait_for_ready_state_complete() + self._demo_mode_pause_if_active() def click_chain(self, selectors_list, by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT, spacing=0): @@ -66,12 +69,14 @@ def click_link_text(self, link_text, timeout=settings.SMALL_TIMEOUT): element.click() if settings.WAIT_FOR_RSC_ON_CLICKS: self.wait_for_ready_state_complete() + self._demo_mode_pause_if_active() def add_text(self, selector, new_value, timeout=settings.SMALL_TIMEOUT): """ The more-reliable version of driver.send_keys() Similar to update_text(), but won't clear the text field first. """ element = self.wait_for_element_visible(selector, timeout=timeout) element.send_keys(new_value) + self._demo_mode_pause_if_active() def send_keys(self, selector, new_value, timeout=settings.SMALL_TIMEOUT): """ Same as add_text() -> more reliable, but less name confusion. """ @@ -88,12 +93,14 @@ def update_text_value(self, selector, new_value, """ element = self.wait_for_element_visible(selector, timeout=timeout) element.clear() + self._demo_mode_pause_if_active(tiny=True) element.send_keys(new_value) if (retry and element.get_attribute('value') != new_value and ( not new_value.endswith('\n'))): logging.debug('update_text_value is falling back to jQuery!') selector = self.jq_format(selector) self.set_value(selector, new_value) + self._demo_mode_pause_if_active() def update_text(self, selector, new_value, timeout=settings.SMALL_TIMEOUT, retry=False): @@ -124,57 +131,97 @@ def execute_script(self, script): def set_window_size(self, width, height): return self.driver.set_window_size(width, height) + self._demo_mode_pause_if_active() def maximize_window(self): return self.driver.maximize_window() + self._demo_mode_pause_if_active() def activate_jquery(self): - """ (It's not on by default on all website pages.) """ + """ If "jQuery is not defined", use this method to activate it for use. + This happens because jQuery is not always defined on web sites. """ + try: + # Let's first find out if jQuery is already defined. + self.driver.execute_script("jQuery('html')") + # Since that command worked, jQuery is defined. Let's return. + return + except Exception: + # jQuery is not currently defined. Let's proceed by defining it. + pass self.driver.execute_script( '''var script = document.createElement("script"); ''' '''script.src = "https://ajax.googleapis.com/ajax/libs/jquery/1/''' '''jquery.min.js"; document.getElementsByTagName("head")[0]''' '''.appendChild(script);''') + for x in xrange(30): + # jQuery needs a small amount of time to activate. (At most 3s) + try: + self.driver.execute_script("jQuery('html')") + return + except Exception: + time.sleep(0.1) + # Since jQuery still isn't activating, give up and raise an exception + raise Exception("Exception: WebDriver could not activate jQuery!") def scroll_to(self, selector): self.wait_for_element_visible(selector, timeout=settings.SMALL_TIMEOUT) - self.driver.execute_script( - "jQuery('%s')[0].scrollIntoView()" % selector) + scroll_script = "jQuery('%s')[0].scrollIntoView()" % selector + try: + self.driver.execute_script(scroll_script) + except Exception: + # The likely reason this fails is because: "jQuery is not defined" + self.activate_jquery() # It's a good thing we can define it here + self.driver.execute_script(scroll_script) + self._demo_mode_pause_if_active(tiny=True) def scroll_click(self, selector): self.scroll_to(selector) self.click(selector) def jquery_click(self, selector): + self.scroll_to(selector) self.driver.execute_script("jQuery('%s').click()" % selector) + self._demo_mode_pause_if_active() def jq_format(self, code): return page_utils.jq_format(code) def set_value(self, selector, value): + self.scroll_to(selector) val = json.dumps(value) self.driver.execute_script("jQuery('%s').val(%s)" % (selector, val)) + self._demo_mode_pause_if_active() def jquery_update_text_value(self, selector, new_value, timeout=settings.SMALL_TIMEOUT): element = self.wait_for_element_visible(selector, timeout=timeout) + self.scroll_to(selector) self.driver.execute_script("""jQuery('%s').val('%s')""" % (selector, self.jq_format(new_value))) if new_value.endswith('\n'): element.send_keys('\n') + self._demo_mode_pause_if_active() def jquery_update_text(self, selector, new_value, timeout=settings.SMALL_TIMEOUT): self.jquery_update_text_value(selector, new_value, timeout=timeout) def hover_on_element(self, selector): + self.wait_for_element_visible(selector, timeout=settings.SMALL_TIMEOUT) + self.scroll_to(selector) + time.sleep(0.05) # Settle down from scrolling before hovering return page_actions.hover_on_element(self.driver, selector) def hover_and_click(self, hover_selector, click_selector, click_by=By.CSS_SELECTOR, timeout=settings.SMALL_TIMEOUT): - return page_actions.hover_and_click(self.driver, hover_selector, - click_selector, click_by, timeout) + self.wait_for_element_visible(hover_selector, timeout=timeout) + self.scroll_to(hover_selector) + # Settle down from the scrolling before hovering + element = page_actions.hover_and_click( + self.driver, hover_selector, click_selector, click_by, timeout) + self._demo_mode_pause_if_active() + return element def wait_for_element_present(self, selector, by=By.CSS_SELECTOR, timeout=settings.LARGE_TIMEOUT): @@ -221,6 +268,23 @@ def wait_for_and_switch_to_alert(self, timeout=settings.LARGE_TIMEOUT): def save_screenshot(self, name, folder=None): return page_actions.save_screenshot(self.driver, name, folder) + def _demo_mode_pause_if_active(self, tiny=False): + if self.demo_mode: + if self.demo_sleep: + wait_time = float(self.demo_sleep) + else: + wait_time = settings.DEFAULT_DEMO_MODE_TIMEOUT + if not tiny: + time.sleep(wait_time) + else: + time.sleep(wait_time/3.0) + + def _demo_mode_scroll_if_active(self, selector, by): + if self.demo_mode: + if by == By.CSS_SELECTOR: + self.scroll_to(selector) + + # PyTest-Specific Code # def setUp(self): @@ -243,6 +307,8 @@ def setUp(self): self.log_path = pytest.config.option.log_path self.browser = pytest.config.option.browser self.data = pytest.config.option.data + self.demo_mode = pytest.config.option.demo_mode + self.demo_sleep = pytest.config.option.demo_sleep if self.with_selenium: self.driver = browser_launcher.get_driver(self.browser) diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py index 9917e30c1db..df506f8d4fb 100755 --- a/seleniumbase/plugins/selenium_plugin.py +++ b/seleniumbase/plugins/selenium_plugin.py @@ -46,6 +46,15 @@ def options(self, parser, env): default='4444', help="""Designates the port used by the test. Default: 4444.""") + parser.add_option('--demo_mode', action="store_true", + dest='demo_mode', + default=False, + help="""Using this slows down the automation so that + you can see what it's actually doing.""") + parser.add_option('--demo_sleep', action='store', dest='demo_sleep', + default=None, + help="""Setting this overrides the Demo Mode sleep + time that happens after browser actions.""") def configure(self, options, conf): super(SeleniumBrowser, self).configure(options, conf) @@ -95,6 +104,8 @@ def beforeTest(self, test): else: version = "" test.test.browser = "%s%s" % (self.options.browser, version) + test.test.demo_mode = self.options.demo_mode + test.test.demo_sleep = self.options.demo_sleep except Exception as err: print "Error starting/connecting to Selenium:" print err diff --git a/setup.py b/setup.py index a67ae6b3a15..a1cc0e35c39 100755 --- a/setup.py +++ b/setup.py @@ -6,7 +6,7 @@ setup( name='seleniumbase', - version='1.1.15', + version='1.1.16', url='https://github.com/mdmintz/SeleniumBase', author='Michael Mintz', author_email='@mintzworld',