Add ability to create website tours using the IntroJS library

This commit is contained in:
Michael Mintz 2018-10-04 02:27:47 -04:00
parent ff9e518019
commit 65bcb94097
5 changed files with 301 additions and 6 deletions

View File

@ -0,0 +1,65 @@
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_introjs_tour() # OR self.create_tour(theme="introjs")
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.play_tour()
self.highlight_update_text('input[title="Search"]', "Google")
self.wait_for_element('[role="listbox"]') # Wait for autocomplete
self.create_introjs_tour()
self.add_tour_step(
"Then click here to search.", 'input[value="Google Search"]')
self.add_tour_step(
"Or press [ENTER] after typing a query here.", '[title="Search"]')
self.play_tour()
self.highlight_update_text('input[title="Search"]', "GitHub\n")
self.wait_for_element("#search")
self.create_introjs_tour()
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_introjs_tour()
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()

View File

@ -861,6 +861,50 @@ class BaseCase(unittest.TestCase):
except Exception:
return False
def __activate_introjs(self):
""" Allows you to use IntroJS Tours with SeleniumBase
https://introjs.com/
"""
intro_css = constants.IntroJS.MIN_CSS
intro_js = constants.IntroJS.MIN_JS
verify_script = ("""// Verify IntroJS activated
var intro2 = introJs();
""")
self.__activate_bootstrap()
self.wait_for_ready_state_complete()
for x in range(4):
self.activate_jquery()
self.add_css_link(intro_css)
self.add_js_link(intro_js)
time.sleep(0.1)
for x in range(int(settings.MINI_TIMEOUT * 2.0)):
# IntroJS needs a small amount of time to load & activate.
try:
self.execute_script(verify_script)
self.wait_for_ready_state_complete()
time.sleep(0.05)
return
except Exception:
time.sleep(0.15)
raise Exception(
'''Unable to load jQuery on "%s" due to a possible violation '''
'''of the website's Content Security Policy '''
'''directive. ''' % self.driver.current_url)
def __is_introjs_activated(self):
verify_script = ("""// Verify IntroJS activated
var intro2 = introJs();
""")
try:
self.execute_script(verify_script)
return True
except Exception:
return False
def __activate_shepherd(self):
""" Allows you to use Shepherd Tours with SeleniumBase
http://github.hubspot.com/shepherd/docs/welcome/
@ -976,11 +1020,34 @@ class BaseCase(unittest.TestCase):
if not name:
name = "default"
new_tour = ("""
// Bootstrap Tour
var tour = new Tour({
steps: [
""")
new_tour = (
"""
// Bootstrap Tour
var tour = new Tour({
});
tour.addSteps([
""")
self._tour_steps[name] = []
self._tour_steps[name].append(new_tour)
def create_introjs_tour(self, name=None):
""" Creates an IntroJS 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 = (
"""
// IntroJS Tour
function startIntro(){
var intro = introJs();
intro.setOptions({
steps: [
""")
self._tour_steps[name] = []
self._tour_steps[name].append(new_tour)
@ -1032,6 +1099,10 @@ class BaseCase(unittest.TestCase):
self.__add_bootstrap_tour_step(
message, selector=selector, name=name, title=title,
alignment=alignment, duration=duration)
elif "IntroJS" in self._tour_steps[name][0]:
self.__add_introjs_tour_step(
message, selector=selector, name=name, title=title,
alignment=alignment)
else:
self.__add_shepherd_tour_step(
message, selector=selector, name=name, title=title,
@ -1120,6 +1191,34 @@ class BaseCase(unittest.TestCase):
self._tour_steps[name].append(step)
def __add_introjs_tour_step(self, message, selector=None, name=None,
title=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.
alignment - Choose from "top", "bottom", "left", and "right".
("top" is the default alignment).
"""
if selector != "html":
element_row = "element: '%s'," % selector
else:
element_row = ""
if title:
message = "<center><b>" + title + "</b></center><hr>" + message
message = '<font size=\"3\" color=\"#33475B\">' + message + '</font>'
step = ("""{%s
intro: '%s',
position: '%s'},
""" % (element_row, message, alignment))
self._tour_steps[name].append(step)
def play_tour(self, name=None, interval=0):
""" Plays a tour on the current website.
@Params
@ -1138,6 +1237,8 @@ class BaseCase(unittest.TestCase):
if "Bootstrap" in self._tour_steps[name][0]:
self.__play_bootstrap_tour(name=name, interval=interval)
elif "IntroJS" in self._tour_steps[name][0]:
self.__play_introjs_tour(name=name, interval=interval)
else:
self.__play_shepherd_tour(name=name, interval=interval)
@ -1331,6 +1432,125 @@ class BaseCase(unittest.TestCase):
tour_on = False
time.sleep(0.1)
def __play_introjs_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.
"""
instructions = ""
for tour_step in self._tour_steps[name]:
instructions += tour_step
instructions += (
"""]
});
intro.setOption("disableInteraction", true);
intro.setOption("overlayOpacity", .29);
intro.setOption("scrollToElement", true);
intro.setOption("keyboardNavigation", true);
intro.setOption("exitOnEsc", false);
intro.setOption("exitOnOverlayClick", false);
intro.setOption("showStepNumbers", false);
intro.setOption("showProgress", false);
intro.start();
$intro = intro;
}
startIntro();
""")
autoplay = False
if interval and interval > 0:
autoplay = True
interval = float(interval)
if interval < 0.5:
interval = 0.5
if not self.__is_introjs_activated():
self.__activate_introjs()
if len(self._tour_steps[name]) > 1:
try:
if "element: " in self._tour_steps[name][1]:
selector = re.search(
r"[\S\s]+element: '([\S\s]+)',[\S\s]+intro: '",
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
if autoplay:
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
latest_step = 0
while tour_on:
try:
time.sleep(0.01)
if self.browser != "firefox":
result = self.execute_script(
"return $intro._currentStep")
else:
self.wait_for_element_present(
".introjs-tooltip", timeout=0.4)
result = True
except Exception:
tour_on = False
result = None
if result is not None:
tour_on = True
if autoplay:
try:
current_step = self.execute_script(
"return $intro._currentStep")
except Exception:
continue
if current_step != latest_step:
latest_step = current_step
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
now_ms = time.time() * 1000.0
if now_ms >= stop_ms:
if current_step == latest_step:
self.execute_script("return $intro.nextStep()")
try:
latest_step = self.execute_script(
"return $intro._currentStep")
start_ms = time.time() * 1000.0
stop_ms = start_ms + (interval * 1000.0)
except Exception:
pass
continue
else:
try:
time.sleep(0.01)
if self.browser != "firefox":
result = self.execute_script(
"return $intro._currentStep")
else:
self.wait_for_element_present(
".introjs-tooltip", timeout=0.4)
result = True
if result is not None:
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

View File

@ -67,6 +67,14 @@ class BootstrapTour:
"bootstrap-tour/%s/js/bootstrap-tour-standalone.min.js" % VER)
class IntroJS:
VER = "2.9.3"
MIN_CSS = ("//cdnjs.cloudflare.com/ajax/libs/"
"intro.js/%s/introjs.css" % VER)
MIN_JS = ("//cdnjs.cloudflare.com/ajax/libs/"
"intro.js/%s/intro.min.js" % VER)
class Shepherd:
VER = "1.8.1"
MIN_JS = ("//cdnjs.cloudflare.com/ajax/libs/"
@ -87,7 +95,7 @@ class Shepherd:
class Tether:
VER = "1.4.4"
MIN_JS = ("https://cdnjs.cloudflare.com/ajax/libs/"
MIN_JS = ("//cdnjs.cloudflare.com/ajax/libs/"
"tether/%s/js/tether.min.js" % VER)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long