From 00d1b64d1cfee735789df8eacdde1487e9efe977 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Aug 2018 11:57:14 -0400 Subject: [PATCH 1/7] Add ability to create SeleniumBase Bootstrap Tours --- help_docs/method_summary.md | 4 +- seleniumbase/fixtures/base_case.py | 224 ++++++++++++++++++++++++++--- 2 files changed, 205 insertions(+), 23 deletions(-) diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md index e9a9de3be77..ff4180ac0ac 100755 --- a/help_docs/method_summary.md +++ b/help_docs/method_summary.md @@ -91,12 +91,14 @@ self.add_css_style(css_style) self.add_js_code_from_link(js_link) -self.add_meta_tag(http_equiv=None, content=None): +self.add_meta_tag(http_equiv=None, content=None) self.activate_jquery() self.create_tour(name=None, theme=None) +self.create_bootstrap_tour(name=None) + self.add_tour_step(message, selector=None, name=None, title=None, theme=None, alignment=None) diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py index 4217e83b3fa..e9a25190901 100755 --- a/seleniumbase/fixtures/base_case.py +++ b/seleniumbase/fixtures/base_case.py @@ -813,13 +813,12 @@ def activate_jquery(self): def __activate_bootstrap(self): """ Allows you to use Bootstrap Tours with SeleniumBase http://bootstraptour.com/ - (Currently, SeleniumBase methods only use Shepherd Tours.) """ bootstrap_tour_css = constants.BootstrapTour.MIN_CSS bootstrap_tour_js = constants.BootstrapTour.MIN_JS verify_script = ("""// Instance the tour - var tour = new Tour({ + var tour2 = new Tour({ });""") for x in range(4): @@ -844,7 +843,7 @@ def __activate_bootstrap(self): def __is_bootstrap_activated(self): verify_script = ("""// Instance the tour - var tour = new Tour({ + var tour2 = new Tour({ });""") try: self.execute_script(verify_script) @@ -873,6 +872,8 @@ def __activate_shepherd(self): self.__activate_bootstrap() self.wait_for_ready_state_complete() + self.add_css_style(backdrop_style) + self.wait_for_ready_state_complete() for x in range(4): # self.activate_jquery() # Included with __activate_bootstrap() self.add_css_link(spinner_css) @@ -886,8 +887,6 @@ def __activate_shepherd(self): self.add_js_link(underscore_js) self.add_js_link(backbone_js) self.add_js_link(shepherd_js) - time.sleep(0.01) - self.add_css_style(backdrop_style) time.sleep(0.1) for x in range(int(settings.MINI_TIMEOUT * 2.0)): @@ -896,6 +895,7 @@ def __activate_shepherd(self): self.execute_script(sh_style) # Verify Shepherd has loaded self.wait_for_ready_state_complete() self.execute_script(sh_style) # Need it twice for ordering + self.wait_for_ready_state_complete() time.sleep(0.05) return except Exception: @@ -928,7 +928,10 @@ def create_tour(self, name=None, theme=None): shepherd_theme = "shepherd-theme-arrows" if theme: - if theme == "default": + if theme.lower() == "bootstrap": + self.create_bootstrap_tour(name) + return + elif theme == "default": shepherd_theme = "shepherd-theme-default" elif theme == "dark": shepherd_theme = "shepherd-theme-dark" @@ -939,18 +942,39 @@ def create_tour(self, name=None, theme=None): elif theme == "square-dark": shepherd_theme = "shepherd-theme-square-dark" - new_tour = ("""let tour = new Shepherd.Tour({ + new_tour = (""" + // Shepherd Tour + let tour = new Shepherd.Tour({ defaults: { classes: '%s', scrollTo: true } - });""" % shepherd_theme) + }); + """ % shepherd_theme) + + self._tour_steps[name] = [] + self._tour_steps[name].append(new_tour) + + def create_bootstrap_tour(self, name=None): + """ Creates a Bootstrap tour for a website. + @Params + name - If creating multiple tours, use this to select the + tour you wish to add steps to. + """ + if not name: + name = "default" + + new_tour = (""" + // Bootstrap Tour + var tour = new Tour({ + steps: [ + """) self._tour_steps[name] = [] self._tour_steps[name].append(new_tour) def add_tour_step(self, message, selector=None, name=None, - title=None, theme=None, alignment=None): + title=None, theme=None, alignment=None, duration=None): """ Allows the user to add tour steps for a website. @Params message - The message to display. @@ -958,10 +982,13 @@ def add_tour_step(self, message, selector=None, name=None, name - If creating multiple tours, use this to select the tour you wish to add steps to. title - Additional header text that appears above the message. - theme - Choose from "arrows", "dark", "default", "square", and + theme - (NON-Bootstrap Tours ONLY) The styling of the tour step. + Choose from "arrows", "dark", "default", "square", and "square-dark". ("arrows" is used if None is selected.) alignment - Choose from "top", "bottom", "left", and "right". ("top" is the default alignment). + duration - (Bootstrap Tours ONLY) The amount of time, in seconds, + before automatically advancing to the next tour step. """ if not selector: selector = "html" @@ -970,7 +997,8 @@ def add_tour_step(self, message, selector=None, name=None, if not name: name = "default" if name not in self._tour_steps: - self.create_tour(name=name) + # By default, will create a Bootstrap tour if no tours exist + self.create_tour(name=name, theme="bootstrap") if not title: title = "" @@ -981,6 +1009,34 @@ def add_tour_step(self, message, selector=None, name=None, else: message = "" + if not alignment or ( + alignment not in ["top", "bottom", "left", "right"]): + alignment = "top" + + if "Bootstrap" in self._tour_steps[name][0]: + self.__add_bootstrap_tour_step( + message, selector=selector, name=name, title=title, + alignment=alignment, duration=duration) + else: + self.__add_shepherd_tour_step( + message, selector=selector, name=name, title=title, + theme=theme, alignment=alignment) + + def __add_shepherd_tour_step(self, message, selector=None, name=None, + title=None, theme=None, alignment=None): + """ Allows the user to add tour steps for a website. + @Params + message - The message to display. + selector - The CSS Selector of the Element to attach to. + name - If creating multiple tours, use this to select the + tour you wish to add steps to. + title - Additional header text that appears above the message. + theme - (NON-Bootstrap Tours ONLY) The styling of the tour step. + Choose from "arrows", "dark", "default", "square", and + "square-dark". ("arrows" is used if None is selected.) + alignment - Choose from "top", "bottom", "left", and "right". + ("top" is the default alignment). + """ if theme == "default": shepherd_theme = "shepherd-theme-default" elif theme == "dark": @@ -997,10 +1053,6 @@ def add_tour_step(self, message, selector=None, name=None, self._tour_steps[name][0]).group(1) shepherd_theme = shepherd_base_theme - if not alignment or ( - alignment not in ["top", "bottom", "left", "right"]): - alignment = "top" - shepherd_classes = shepherd_theme if selector == "html": shepherd_classes += " shepherd-orphan" @@ -1016,6 +1068,41 @@ def add_tour_step(self, message, selector=None, name=None, self._tour_steps[name].append(step) + def __add_bootstrap_tour_step(self, message, selector=None, name=None, + title=None, alignment=None, duration=None): + """ Allows the user to add tour steps for a website. + @Params + message - The message to display. + selector - The CSS Selector of the Element to attach to. + name - If creating multiple tours, use this to select the + tour you wish to add steps to. + title - Additional header text that appears above the message. + alignment - Choose from "top", "bottom", "left", and "right". + ("top" is the default alignment). + duration - (Bootstrap Tours ONLY) The amount of time, in seconds, + before automatically advancing to the next tour step. + """ + if selector != "html": + element_row = "element: '%s'," % selector + else: + element_row = "" + if not duration: + duration = "0" + else: + duration = str(float(duration) * 1000.0) + + step = ("""{ + %s + title: '%s', + content: '%s', + orphan: true, + placement: 'auto %s', + smartPlacement: true, + duration: %s, + },""" % (element_row, title, message, alignment, duration)) + + self._tour_steps[name].append(step) + def play_tour(self, name=None, interval=0): """ Plays a tour on the current website. @Params @@ -1027,23 +1114,36 @@ def play_tour(self, name=None, interval=0): if self.headless: return # Tours should not run in headless mode. - autoplay = False - if interval and interval > 0: - autoplay = True - interval = float(interval) - if interval < 0.5: - interval = 0.5 - if not name: name = "default" if name not in self._tour_steps: raise Exception("Tour {%s} does not exist!" % name) + if "Bootstrap" in self._tour_steps[name][0]: + self.__play_bootstrap_tour(name=name, interval=interval) + else: + self.__play_shepherd_tour(name=name, interval=interval) + + def __play_shepherd_tour(self, name=None, interval=0): + """ Plays a tour on the current website. + @Params + name - If creating multiple tours, use this to select the + tour you wish to play. + interval - The delay time between autoplaying tour steps. + If set to 0 (default), the tour is fully manual control. + """ instructions = "" for tour_step in self._tour_steps[name]: instructions += tour_step instructions += "tour.start();" + autoplay = False + if interval and interval > 0: + autoplay = True + interval = float(interval) + if interval < 0.5: + interval = 0.5 + if not self.__is_shepherd_activated(): self.__activate_shepherd() @@ -1134,6 +1234,86 @@ def play_tour(self, name=None, interval=0): tour_on = False time.sleep(0.1) + def __play_bootstrap_tour(self, name=None, interval=0): + """ Plays a tour on the current website. + @Params + name - If creating multiple tours, use this to select the + tour you wish to play. + interval - The delay time between autoplaying tour steps. + If set to 0 (default), the tour is fully manual control. + """ + instructions = "" + for tour_step in self._tour_steps[name]: + instructions += tour_step + instructions += ( + """]}); + + // Initialize the tour + tour.init(); + + // Start the tour + tour.start(); + + $tour = tour; + $tour.restart();""") + + if interval and interval > 0: + if interval < 1: + interval = 1 + interval = str(float(interval) * 1000.0) + instructions = instructions.replace( + 'duration: 0,', 'duration: %s,' % interval) + + if not self.__is_bootstrap_activated(): + self.__activate_bootstrap() + + if len(self._tour_steps[name]) > 1: + try: + if "element: " in self._tour_steps[name][1]: + selector = re.search( + "[\S\s]+element: '([\S\s]+)',[\S\s]+title: '", + self._tour_steps[name][1]).group(1) + selector = selector.replace('\\', '') + self.wait_for_element_present( + selector, timeout=(settings.SMALL_TIMEOUT)) + else: + selector = "html" + except Exception: + self.__post_messenger_error_message( + "Tour Error: {'%s'} was not found!" + "" % selector, + duration=settings.SMALL_TIMEOUT) + raise Exception( + "Tour Error: {'%s'} was not found! " + "Exiting due to failure on first tour step!" + "" % selector) + + self.execute_script(instructions) + tour_on = True + while tour_on: + try: + time.sleep(0.01) + result = self.execute_script( + "return $tour.ended()") + except Exception: + tour_on = False + result = None + if result is False: + tour_on = True + else: + try: + time.sleep(0.01) + result = self.execute_script( + "return $tour.ended()") + if result is False: + time.sleep(0.1) + continue + else: + return + except Exception: + tour_on = False + time.sleep(0.1) + def __wait_for_css_query_selector( self, selector, timeout=settings.SMALL_TIMEOUT): element = None From e5ecf82e7baf9723629e52dadb4c8154156aa617 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Aug 2018 11:57:54 -0400 Subject: [PATCH 2/7] Update Google Tour example --- examples/tour_examples/google_tour.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/tour_examples/google_tour.py b/examples/tour_examples/google_tour.py index ea902b7a58a..84a7e3da2cd 100755 --- a/examples/tour_examples/google_tour.py +++ b/examples/tour_examples/google_tour.py @@ -28,10 +28,8 @@ def test_google_tour(self): self.create_tour(theme="dark") self.add_tour_step( "Search results appear here!", title="(5-second autoplay on)") - self.add_tour_step( - "Let's take another tour...", - title="Ready for more?", theme="arrows") - self.play_tour(interval=5) # tour automatically continues after 3s + self.add_tour_step("Let's take another tour:", theme="arrows") + self.play_tour(interval=5) # tour automatically continues after 5 sec self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z") self.wait_for_element('input#searchboxinput') From 2697859df141b2dc6c34df5c0ac3ec87741d6b84 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Aug 2018 11:58:18 -0400 Subject: [PATCH 3/7] Add Bootstrap Google Tour example --- .../tour_examples/bootstrap_google_tour.py | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100755 examples/tour_examples/bootstrap_google_tour.py diff --git a/examples/tour_examples/bootstrap_google_tour.py b/examples/tour_examples/bootstrap_google_tour.py new file mode 100755 index 00000000000..9838883b130 --- /dev/null +++ b/examples/tour_examples/bootstrap_google_tour.py @@ -0,0 +1,63 @@ +from seleniumbase import BaseCase + + +class MyTourClass(BaseCase): + + def test_google_tour(self): + self.open('https://google.com') + self.wait_for_element('input[title="Search"]') + + self.create_bootstrap_tour() # OR self.create_tour(theme="bootstrap") + self.add_tour_step( + "Click to begin the Google Tour!", title="SeleniumBase Tours") + self.add_tour_step( + "Type in your search query here.", 'input[title="Search"]') + self.add_tour_step( + "Then click here to search!", 'input[value="Google Search"]', + alignment="bottom") + self.add_tour_step( + "Or click here to see the top result.", + '''[value="I'm Feeling Lucky"]''', + alignment="bottom") + self.add_tour_step("Here's an example Google search:") + self.play_tour() + + self.highlight_update_text('input[title="Search"]', "GitHub") + self.highlight_click('input[value="Google Search"]') + + self.create_bootstrap_tour() # OR self.create_tour(theme="bootstrap") + self.add_tour_step( + "Search results appear here!", title="(5-second autoplay on)") + self.add_tour_step("Let's take another tour:") + self.play_tour(interval=5) # tour automatically continues after 5 sec + + self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z") + self.wait_for_element('input#searchboxinput') + + self.create_bootstrap_tour() # OR self.create_tour(theme="bootstrap") + self.add_tour_step("Welcome to Google Maps!") + self.add_tour_step( + "Type in a location here.", "#searchboxinput", title="Search Box") + self.add_tour_step( + "Then click here to show it on the map.", + "#searchbox-searchbutton", alignment="bottom") + self.add_tour_step( + "Or click here to get driving directions.", + "#searchbox-directions", alignment="bottom") + self.add_tour_step( + "Use this button to switch to Satellite view.", + "div.widget-minimap", alignment="right") + self.add_tour_step( + "Click here to zoom in.", "#widget-zoom-in", alignment="left") + self.add_tour_step( + "Or click here to zoom out.", "#widget-zoom-out", alignment="left") + self.add_tour_step( + "Use the Menu button to see more options.", + ".searchbox-hamburger-container", alignment="right") + self.add_tour_step( + "Or click here to see more Google apps.", '[title="Google apps"]', + alignment="left") + self.add_tour_step( + "Thanks for trying out SeleniumBase Tours!", + title="End of Guided Tour") + self.play_tour() From 6e9031807ac44bc842145d2ae4f1d7d82c4adb1b Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Aug 2018 13:48:51 -0400 Subject: [PATCH 4/7] Update to the latest versions of pytest and pytest-xdist --- requirements.txt | 4 ++-- setup.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements.txt b/requirements.txt index d60c4a059d5..04e519d2c52 100755 --- a/requirements.txt +++ b/requirements.txt @@ -3,9 +3,9 @@ setuptools ipython==5.6.0 selenium==3.14.0 nose==1.3.7 -pytest==3.7.2 +pytest==3.7.3 pytest-html==1.19.0 -pytest-xdist==1.22.5 +pytest-xdist==1.23.0 six==1.11.0 flake8==3.5.0 requests==2.19.1 diff --git a/setup.py b/setup.py index d1d91d30369..85271d56ab3 100755 --- a/setup.py +++ b/setup.py @@ -22,9 +22,9 @@ 'ipython==5.6.0', 'selenium==3.14.0', 'nose==1.3.7', - 'pytest==3.7.2', + 'pytest==3.7.3', 'pytest-html==1.19.0', - 'pytest-xdist==1.22.5', + 'pytest-xdist==1.23.0', 'six==1.11.0', 'flake8==3.5.0', 'requests==2.19.1', From 5e86e9eab84ffc35418b1c5ce1a7c60b30d17460 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Aug 2018 16:15:47 -0400 Subject: [PATCH 5/7] Update the SeleniumBase Tours ReadMe --- README.md | 2 +- examples/tour_examples/ReadMe.md | 34 ++++++++++++++++++++++++++++---- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f73f4fba44c..b61affcd676 100755 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ cd tour_examples pytest google_tour.py ``` -![](https://cdn2.hubspot.net/hubfs/100006/images/google_tour.gif "SeleniumBase Tours")
+
(Above: Actual demo of [google_tour.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/google_tour.py) running on [google.com](https://google.com)) For more detailed steps on getting started, see the [**Detailed Instructions**](#seleniumbase_installation) section. diff --git a/examples/tour_examples/ReadMe.md b/examples/tour_examples/ReadMe.md index ef571ab45dd..878155fbc5b 100755 --- a/examples/tour_examples/ReadMe.md +++ b/examples/tour_examples/ReadMe.md @@ -1,15 +1,33 @@ ## Creating SeleniumBase Tours -![](https://cdn2.hubspot.net/hubfs/100006/images/google_tour.gif "SeleniumBase Tours")
+SeleniumBase Tours utilize the **[Shepherd Javascript Library](https://cdnjs.com/libraries/shepherd/1.8.1)** and the **[Bootstrap Tour Library](https://cdnjs.com/libraries/bootstrap-tour)** for creating and running tours, demos, and walkthroughs on any website. -SeleniumBase Tours utilize the [Shepherd Javascript Library](https://cdnjs.com/libraries/shepherd/1.8.1) for creating and running tours, demos, and walkthroughs on any website. +Example tour utilizing the Shepherd Javascript Library: -To utilize tours, there are three methods that you need to know at the basic level (which contain optional arguments): +
-``self.create_tour(theme)`` +Example tour utilizing the Bootstrap Javascript Library: + +
+ +By default, the Shepherd Javascript Library is used when creating a tour with: + +``self.create_tour()`` + +To create a tour utilizing the Bootstrap Javascript Library, you can use either of the following: + +``self.create_bootstrap_tour()`` + +OR + +``self.create_tour(theme="bootstrap")`` + +To add a tour step, use the following: (Only ``message`` is required. The other args are optional.) ``self.add_tour_step(message, css_selector, title, alignment, theme)`` +Here's how you play a tour: + ``self.play_tour(interval)`` With the ``create_tour()`` method, you can pass a default theme to change the look & feel of the tour steps. Valid themes are ``dark``, ``default``, ``arrows``, ``square``, and ``square-dark``. @@ -18,6 +36,8 @@ With the ``self.add_tour_step()`` method, you must first pass a message to displ Finally, you can play a tour you created by calling the ``self.play_tour()`` method. If you specify an interval, the tour will automatically walk through each step after that many seconds have passed. +All methods have the optional ``name`` argument, which is only needed if you're creating multiple tours at once. Then, when you're adding a step or playing a tour, SeleniumBase knows which tour you're referring too. You can avoid using the ``name`` arg for multiple tours if you play a tour before creating a new one. + ### Here's an example of using SeleniumBase Tours: ```python @@ -44,3 +64,9 @@ class MyTourClass(BaseCase): ```bash pytest google_tour.py ``` + +#### There's also the Bootstrap Google Tour, which you can play with the following command: + +```bash +pytest bootstrap_google_tour.py +``` From e2bd8d846774b618dae43a62afe1d4bb27958d87 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Aug 2018 18:36:52 -0400 Subject: [PATCH 6/7] Version 1.15.0 --- setup.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 85271d56ab3..bd48921a0e9 100755 --- a/setup.py +++ b/setup.py @@ -7,15 +7,33 @@ setup( name='seleniumbase', - version='1.14.6', - description='Web Automation & Testing Framework - http://seleniumbase.com', - long_description='Web Automation and Testing Framework - seleniumbase.com', - platforms='Mac * Windows * Linux * Docker', - url='http://seleniumbase.com', + version='1.15.0', + description='All-In-One Test Automation Framework', + long_description='Web Automation, Testing, and User-Onboarding Framework', + url='https://github.com/seleniumbase/SeleniumBase', + platforms=["Windows", "Linux", "Unix", "Mac OS-X"], author='Michael Mintz', author_email='mdmintz@gmail.com', maintainer='Michael Mintz', - license='The MIT License', + license="MIT", + classifiers=[ + "License :: OSI Approved :: MIT License", + "Development Status :: 5 - Production/Stable", + "Topic :: Internet", + "Topic :: Scientific/Engineering", + "Topic :: Software Development", + "Operating System :: Microsoft :: Windows", + "Operating System :: Linux", + "Operating System :: Unix", + "Operating System :: MacOS", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + ], install_requires=[ 'pip', 'setuptools', From e380721785124ff666835ad14e561a8fb4c36dd8 Mon Sep 17 00:00:00 2001 From: Michael Mintz Date: Mon, 27 Aug 2018 18:50:08 -0400 Subject: [PATCH 7/7] Update GIF --- examples/tour_examples/ReadMe.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tour_examples/ReadMe.md b/examples/tour_examples/ReadMe.md index 878155fbc5b..a4ae0429ce8 100755 --- a/examples/tour_examples/ReadMe.md +++ b/examples/tour_examples/ReadMe.md @@ -8,7 +8,7 @@ Example tour utilizing the Shepherd Javascript Library: Example tour utilizing the Bootstrap Javascript Library: -
+
By default, the Shepherd Javascript Library is used when creating a tour with: