Skip to content

Commit a2d7ab3

Browse files
authored
Merge pull request #591 from seleniumbase/driverjs-tours
Add the ability to create DriverJS website tours
2 parents 796e6b8 + 8ea8e11 commit a2d7ab3

File tree

19 files changed

+395
-17
lines changed

19 files changed

+395
-17
lines changed

docs/prepare.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def main(*args, **kwargs):
106106
changed = True
107107
line = (
108108
'<section align="center"><div align="center">'
109-
'<h2>Reliable Browser Testing</h2>'
109+
'<h2>Reliable Browser Testing</h2>'
110110
'</div></section>')
111111
if "<!-- SeleniumBase Header2 -->" in line:
112112
changed = True

examples/tour_examples/ReadMe.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
## Website Tours
44

5-
SeleniumBase Tours utilize 4 JavaScript libraries for creating interactive walkthroughs on any website:<br>**[Shepherd](https://shepherdjs.dev/)**, **[Bootstrap Tour](http://bootstraptour.com/)**, **[IntroJS](https://introjs.com/)**, and **[Hopscotch](https://linkedinattic.github.io/hopscotch/)**.
5+
SeleniumBase Tours utilize 5 JavaScript libraries for creating interactive walkthroughs on any website:<br>**[Shepherd](https://shepherdjs.dev/)**, **[Bootstrap Tour](http://bootstraptour.com/)**, **[IntroJS](https://introjs.com/)**, **[DriverJS](https://kamranahmed.info/driver.js/)**, and **[Hopscotch](https://linkedinattic.github.io/hopscotch/)**.
66

77
**Example tour:**
88

@@ -15,7 +15,7 @@ pytest google_tour.py
1515

1616
### Creating a new tour:
1717

18-
#### To create a tour utilizing the Shepherd JS Library, use one of the following:
18+
#### To create a tour utilizing the Shepherd Library, use one of the following:
1919

2020
``self.create_shepherd_tour()``
2121

@@ -33,15 +33,23 @@ OR
3333

3434
``self.create_tour(theme="bootstrap")``
3535

36-
#### To create a tour utilizing the Intro JS Library, use one of the following:
36+
#### To create a tour utilizing the IntroJS Library, use one of the following:
3737

3838
``self.create_introjs_tour()``
3939

4040
OR
4141

4242
``self.create_tour(theme="introjs")``
4343

44-
#### To create a tour utilizing the Hopscotch JS Library, use one of the following:
44+
#### To create a tour utilizing the DriverJS Library, use one of the following:
45+
46+
``self.create_driverjs_tour()``
47+
48+
OR
49+
50+
``self.create_tour(theme="driverjs")``
51+
52+
#### To create a tour utilizing the Hopscotch Library, use one of the following:
4553

4654
``self.create_hopscotch_tour()``
4755

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from seleniumbase import BaseCase
2+
3+
4+
class MyTestClass(BaseCase):
5+
6+
def test_basic(self):
7+
self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
8+
self.wait_for_element("#searchboxinput")
9+
self.wait_for_element("#minimap")
10+
self.wait_for_element("#zoom")
11+
12+
# Create a website tour using the DriverJS library
13+
# Same as: self.create_driverjs_tour()
14+
self.create_tour(theme="driverjs")
15+
self.add_tour_step("🗺️ Welcome to Google Maps 🗺️", "html",
16+
title="✅ SeleniumBase Tours 🌎")
17+
self.add_tour_step("You can type a location into this Search box.",
18+
"#searchboxinput")
19+
self.add_tour_step("Then click here to view it on the map.",
20+
"#searchbox-searchbutton", alignment="bottom")
21+
self.add_tour_step("Or click here to get driving directions.",
22+
"#searchbox-directions", alignment="bottom")
23+
self.add_tour_step("Use this button to get a Satellite view.",
24+
"#minimap div.widget-minimap", alignment="right")
25+
self.add_tour_step("Click here to zoom in.",
26+
"#widget-zoom-in", alignment="left")
27+
self.add_tour_step("Or click here to zoom out.",
28+
"#widget-zoom-out", alignment="left")
29+
self.add_tour_step("Use the Menu button for more options.",
30+
".searchbox-hamburger-container", alignment="right")
31+
self.add_tour_step("Or click here to see more Google apps.",
32+
'[title="Google apps"]', alignment="left")
33+
self.add_tour_step("Thanks for using SeleniumBase Tours", "html",
34+
title="🚃 End of Guided Tour 🚃")
35+
self.export_tour() # The default name for exports is "my_tour.js"
36+
self.play_tour(interval=0) # If interval > 0, autoplay after N seconds

requirements.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ pip>=20.1.1
22
packaging>=20.4
33
setuptools>=44.1.0;python_version<"3.5"
44
setuptools>=47.1.1;python_version>="3.5"
5-
setuptools-scm>=4.1.1
5+
setuptools-scm>=4.1.2
66
wheel>=0.34.2
77
six==1.15.0
88
nose==1.3.7
@@ -15,7 +15,7 @@ selenium==3.141.0
1515
pluggy==0.13.1
1616
attrs>=19.3.0
1717
pytest==4.6.10;python_version<"3.5"
18-
pytest==5.4.2;python_version>="3.5"
18+
pytest==5.4.3;python_version>="3.5"
1919
pytest-cov==2.9.0
2020
pytest-forked==1.1.3
2121
pytest-html==1.22.1;python_version<"3.6"
@@ -41,7 +41,7 @@ coverage==5.1
4141
pyotp==2.3.0
4242
boto==2.49.0
4343
cffi==1.14.0
44-
rich==1.2.3;python_version>="3.6" and python_version<"4.0"
44+
rich==1.3.1;python_version>="3.6" and python_version<"4.0"
4545
flake8==3.7.9;python_version<"3.5"
4646
flake8==3.8.2;python_version>="3.5"
4747
pyflakes==2.1.1;python_version<"3.5"

seleniumbase/core/style_sheet.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@
116116
}
117117
''')
118118

119+
# DriverJS Tour Backdrop Style
120+
dt_backdrop_style = (
121+
'''
122+
.driver-fix-stacking {
123+
pointer-events: none !important;
124+
}
125+
#driver-popover-item, .popover-class {
126+
pointer-events: auto !important;
127+
}
128+
''')
129+
119130
messenger_style = (
120131
'''
121132
.messenger-message-inner {

seleniumbase/core/tour_helper.py

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,51 @@ def is_bootstrap_activated(driver):
5656
return False
5757

5858

59+
def activate_driverjs(driver):
60+
""" Allows you to use DriverJS Tours with SeleniumBase
61+
https://kamranahmed.info/driver.js/
62+
"""
63+
backdrop_style = style_sheet.dt_backdrop_style
64+
driverjs_css = constants.DriverJS.MIN_CSS
65+
driverjs_js = constants.DriverJS.MIN_JS
66+
67+
verify_script = ("""// Verify DriverJS activated
68+
var driverjs2 = Driver.name;
69+
""")
70+
71+
activate_bootstrap(driver)
72+
js_utils.wait_for_ready_state_complete(driver)
73+
js_utils.wait_for_angularjs(driver)
74+
js_utils.add_css_style(driver, backdrop_style)
75+
for x in range(4):
76+
js_utils.activate_jquery(driver)
77+
js_utils.add_css_link(driver, driverjs_css)
78+
js_utils.add_js_link(driver, driverjs_js)
79+
time.sleep(0.1)
80+
for x in range(int(settings.MINI_TIMEOUT * 2.0)):
81+
# DriverJS needs a small amount of time to load & activate.
82+
try:
83+
driver.execute_script(verify_script)
84+
js_utils.wait_for_ready_state_complete(driver)
85+
js_utils.wait_for_angularjs(driver)
86+
time.sleep(0.05)
87+
return
88+
except Exception:
89+
time.sleep(0.15)
90+
js_utils.raise_unable_to_load_jquery_exception(driver)
91+
92+
93+
def is_driverjs_activated(driver):
94+
verify_script = ("""// Verify DriverJS activated
95+
var driverjs2 = Driver.name;
96+
""")
97+
try:
98+
driver.execute_script(verify_script)
99+
return True
100+
except Exception:
101+
return False
102+
103+
59104
def activate_hopscotch(driver):
60105
""" Allows you to use Hopscotch Tours with SeleniumBase
61106
http://linkedin.github.io/hopscotch/
@@ -399,6 +444,118 @@ def play_bootstrap_tour(
399444
time.sleep(0.1)
400445

401446

447+
def play_driverjs_tour(
448+
driver, tour_steps, browser, msg_dur, name=None, interval=0):
449+
""" Plays a DriverJS tour on the current website. """
450+
instructions = ""
451+
for tour_step in tour_steps[name]:
452+
instructions += tour_step
453+
instructions += (
454+
"""]
455+
);
456+
// Start the tour!
457+
tour.start();
458+
$tour = tour;""")
459+
autoplay = False
460+
if interval and interval > 0:
461+
autoplay = True
462+
interval = float(interval)
463+
if interval < 0.5:
464+
interval = 0.5
465+
466+
if not is_driverjs_activated(driver):
467+
activate_driverjs(driver)
468+
469+
if len(tour_steps[name]) > 1:
470+
try:
471+
if "element: " in tour_steps[name][1]:
472+
selector = re.search(
473+
r"[\S\s]+element: '([\S\s]+)',[\S\s]+popover: {",
474+
tour_steps[name][1]).group(1)
475+
selector = selector.replace('\\', '').replace(':first', '')
476+
page_actions.wait_for_element_present(
477+
driver, selector, by=By.CSS_SELECTOR,
478+
timeout=settings.SMALL_TIMEOUT)
479+
else:
480+
selector = "html"
481+
except Exception:
482+
js_utils.post_messenger_error_message(
483+
driver, "Tour Error: {'%s'} was not found!" % selector,
484+
msg_dur)
485+
raise Exception(
486+
"Tour Error: {'%s'} was not found! "
487+
"Exiting due to failure on first tour step!"
488+
"" % selector)
489+
490+
driver.execute_script(instructions)
491+
driver.execute_script(
492+
'document.querySelector(".driver-next-btn").focus();')
493+
tour_on = True
494+
if autoplay:
495+
start_ms = time.time() * 1000.0
496+
stop_ms = start_ms + (interval * 1000.0)
497+
latest_step = 0
498+
while tour_on:
499+
try:
500+
time.sleep(0.01)
501+
if browser != "firefox":
502+
result = not driver.execute_script(
503+
"return $tour.isActivated")
504+
else:
505+
page_actions.wait_for_element_present(
506+
driver, "#driver-popover-item",
507+
by=By.CSS_SELECTOR, timeout=0.4)
508+
result = False
509+
except Exception:
510+
tour_on = False
511+
result = None
512+
if result is False:
513+
tour_on = True
514+
driver.execute_script(
515+
'document.querySelector(".driver-next-btn").focus();')
516+
if autoplay:
517+
try:
518+
current_step = driver.execute_script(
519+
"return $tour.currentStep")
520+
except Exception:
521+
continue
522+
if current_step != latest_step:
523+
latest_step = current_step
524+
start_ms = time.time() * 1000.0
525+
stop_ms = start_ms + (interval * 1000.0)
526+
now_ms = time.time() * 1000.0
527+
if now_ms >= stop_ms:
528+
if current_step == latest_step:
529+
driver.execute_script("$tour.moveNext()")
530+
try:
531+
latest_step = driver.execute_script(
532+
"return $tour.currentStep")
533+
start_ms = time.time() * 1000.0
534+
stop_ms = start_ms + (interval * 1000.0)
535+
except Exception:
536+
pass
537+
continue
538+
else:
539+
try:
540+
time.sleep(0.01)
541+
if browser != "firefox":
542+
result = not driver.execute_script(
543+
"return $tour.isActivated")
544+
else:
545+
page_actions.wait_for_element_present(
546+
driver, "#driver-popover-item",
547+
by=By.CSS_SELECTOR, timeout=0.4)
548+
result = False
549+
if result is False:
550+
time.sleep(0.1)
551+
continue
552+
else:
553+
return
554+
except Exception:
555+
tour_on = False
556+
time.sleep(0.1)
557+
558+
402559
def play_hopscotch_tour(
403560
driver, tour_steps, browser, msg_dur, name=None, interval=0):
404561
""" Plays a Hopscotch tour on the current website. """
@@ -645,6 +802,8 @@ def export_tour(tour_steps, name=None, filename="my_tour.js", url=None):
645802
tour_type = None
646803
if "Bootstrap" in tour_steps[name][0]:
647804
tour_type = "bootstrap"
805+
elif "DriverJS" in tour_steps[name][0]:
806+
tour_type = "driverjs"
648807
elif "Hopscotch" in tour_steps[name][0]:
649808
tour_type = "hopscotch"
650809
elif "IntroJS" in tour_steps[name][0]:
@@ -705,6 +864,16 @@ def export_tour(tour_steps, name=None, filename="my_tour.js", url=None):
705864
instructions += '} }\n'
706865
instructions += 'loadResources()'
707866

867+
elif tour_type == "driverjs":
868+
driverjs_css = constants.DriverJS.MIN_CSS
869+
driverjs_js = constants.DriverJS.MIN_JS
870+
backdrop_style = style_sheet.dt_backdrop_style
871+
backdrop_style = backdrop_style.replace('\n', '')
872+
backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
873+
instructions += 'injectCSS("%s");\n' % driverjs_css
874+
instructions += 'injectStyle("%s");\n' % backdrop_style
875+
instructions += 'injectJS("%s");' % driverjs_js
876+
708877
elif tour_type == "hopscotch":
709878
hopscotch_css = constants.Hopscotch.MIN_CSS
710879
hopscotch_js = constants.Hopscotch.MIN_JS
@@ -757,6 +926,9 @@ def export_tour(tour_steps, name=None, filename="my_tour.js", url=None):
757926
if tour_type == "bootstrap":
758927
instructions += 'function loadTour() { '
759928
instructions += 'if ( typeof Tour !== "undefined" ) {\n'
929+
elif tour_type == "driverjs":
930+
instructions += 'function loadTour() { '
931+
instructions += 'if ( typeof Driver !== "undefined" ) {\n'
760932
elif tour_type == "hopscotch":
761933
instructions += 'function loadTour() { '
762934
instructions += 'if ( typeof hopscotch !== "undefined" ) {\n'
@@ -779,6 +951,13 @@ def export_tour(tour_steps, name=None, filename="my_tour.js", url=None):
779951
tour.start();
780952
$tour = tour;
781953
$tour.restart();\n""")
954+
elif tour_type == "driverjs":
955+
instructions += (
956+
"""]
957+
);
958+
// Start the tour!
959+
tour.start();
960+
$tour = tour;\n""")
782961
elif tour_type == "hopscotch":
783962
instructions += (
784963
"""]

0 commit comments

Comments
 (0)