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
```
-
+
(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..a4ae0429ce8 100755
--- a/examples/tour_examples/ReadMe.md
+++ b/examples/tour_examples/ReadMe.md
@@ -1,15 +1,33 @@
## Creating 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
+```
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()
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')
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/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/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
diff --git a/setup.py b/setup.py
index d1d91d30369..bd48921a0e9 100755
--- a/setup.py
+++ b/setup.py
@@ -7,24 +7,42 @@
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',
'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',