Add ability to create website tours using the IntroJS library
This commit is contained in:
parent
ff9e518019
commit
65bcb94097
|
@ -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()
|
|
@ -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
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue