+
+# 📰 Presenter 📰
+
+SeleniumBase Presenter allows you to create an HTML presentation with only a few lines of Python.
+The Reveal-JS library is used for running the presentations.
+
+**Here's a sample slide:**
+
+
+
+Slides can include HTML, code, images, and iframes.
+
+Here's how to run the example presentation:
+```
+cd examples/presenter
+pytest my_presentation.py
+```
+
+
+### Creating a new presentation:
+
+```python
+self.create_presentation(name=None, show_notes=True)
+ """ Creates a Reveal-JS presentation that you can add slides to.
+ @Params
+ name - If creating multiple presentations at the same time,
+ use this to specify the name of the current presentation.
+ show_notes - When set to True, the Notes feature becomes enabled,
+ which allows presenters to see notes next to slides.
+ """
+```
+
+If creating multiple presentations at the same time, you can pass the ``name`` parameter to distinguish between different presentations.
+Notes are enabled by default unless you specify:
+``show_notes=False`` when calling.
+
+
+### Adding a slide to a presentation:
+
+```python
+self.add_slide(content=None, image=None, code=None, iframe=None,
+ notes=None, name=None)
+ """ Allows the user to add slides to a presentation.
+ @Params
+ content - The HTML content to display on the presentation slide.
+ image - Attach an image (from a URL link) to the slide.
+ code - Attach code of any programming language to the slide.
+ Language-detection will be used to add syntax formatting.
+ iframe - Attach an iFrame (from a URL link) to the slide.
+ notes - Additional notes to include with the slide.
+ ONLY SEEN if show_notes is set for the presentation.
+ name - If creating multiple presentations at the same time,
+ use this to select the presentation to add slides to.
+ """
+```
+
+
+### Running a presentation:
+
+```python
+self.begin_presentation(filename="my_presentation.html", name=None)
+ """ Begin a Reveal-JS Presentation in the web browser. """
+```
+
+Before the presentation is run, the full HTML is saved to the ``presentations_saved/`` folder.
+
+
+All methods have the optional ``name`` argument, which is only needed if you're creating multiple presentations at once.
+
+### Here's an example of using SeleniumBase Presenter:
+
+```python
+from seleniumbase import BaseCase
+
+
+class MyPresenterClass(BaseCase):
+
+ def test_presenter(self):
+ self.create_presentation()
+ self.add_slide(
+ "
")
+ self.begin_presentation()
+```
+
+#### This example is from [my_presentation.py](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/my_presentation.py), which you can run from the ``examples/presenter`` folder with the following command:
+
+```bash
+pytest my_presentation.py
+```
+
+### Saving a presentation:
+
+If you want to save the presentation you created as an HTML file, use:
+
+```python
+self.save_presentation(filename="my_presentation.html", name=None)
+```
+
+Presentations automatically get saved when calling:
+```python
+self.begin_presentation()
+```
diff --git a/examples/presenter/my_presentation.py b/examples/presenter/my_presentation.py
new file mode 100755
index 00000000000..2a646f1b908
--- /dev/null
+++ b/examples/presenter/my_presentation.py
@@ -0,0 +1,56 @@
+from seleniumbase import BaseCase
+
+
+class MyPresenterClass(BaseCase):
+
+ def test_presenter(self):
+ self.create_presentation()
+ self.add_slide(
+ "
")
+ self.begin_presentation()
diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md
index b965d3a5b52..33730b71b2f 100755
--- a/help_docs/method_summary.md
+++ b/help_docs/method_summary.md
@@ -356,6 +356,16 @@ self.add_meta_tag(http_equiv=None, content=None)
############
+self.create_presentation(name=None, show_notes=True)
+
+self.add_slide(content=None, image=None, code=None, iframe=None, notes=None, name=None)
+
+self.save_presentation(filename="my_presentation.html", name=None)
+
+self.begin_presentation(filename="my_presentation.html", name=None)
+
+############
+
self.create_tour(name=None, theme=None)
self.create_shepherd_tour(name=None, theme=None)
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index 17d2fa7bf1e..ccbdab00da6 100755
--- a/seleniumbase/fixtures/base_case.py
+++ b/seleniumbase/fixtures/base_case.py
@@ -83,6 +83,7 @@ def __init__(self, *args, **kwargs):
self.__device_pixel_ratio = None
# Requires self._* instead of self.__* for external class use
self._language = "English"
+ self._presentation_slides = {}
self._html_report_extra = [] # (Used by pytest_plugin.py)
self._default_driver = None
self._drivers_list = []
@@ -3150,6 +3151,152 @@ def add_meta_tag(self, http_equiv=None, content=None):
############
+ def create_presentation(self, name=None, show_notes=True):
+ """ Creates a Reveal-JS presentation that you can add slides to.
+ @Params
+ name - If creating multiple presentations at the same time,
+ use this to specify the name of the current presentation.
+ show_notes - When set to True, the Notes feature becomes enabled,
+ which allows presenters to see notes next to slides.
+ """
+ if not name:
+ name = "default"
+
+ new_presentation = (
+ """
+
+
+
+
+
+
+
+
+
+ """ % (constants.Reveal.MIN_CSS, constants.Reveal.WHITE_MIN_CSS))
+
+ self._presentation_slides[name] = []
+ self._presentation_slides[name].append(new_presentation)
+
+ def add_slide(self, content=None, image=None, code=None, iframe=None,
+ notes=None, name=None):
+ """ Allows the user to add slides to a presentation.
+ @Params
+ content - The HTML content to display on the presentation slide.
+ image - Attach an image (from a URL link) to the slide.
+ code - Attach code of any programming language to the slide.
+ Language-detection will be used to add syntax formatting.
+ iframe - Attach an iFrame (from a URL link) to the slide.
+ notes - Additional notes to include with the slide.
+ ONLY SEEN if show_notes is set for the presentation.
+ name - If creating multiple presentations at the same time,
+ use this to select the presentation to add slides to.
+ """
+
+ if not name:
+ name = "default"
+ if name not in self._presentation_slides:
+ # Create a presentation if it doesn't already exist
+ self.create_presentation(name=name, show_notes=True)
+ if not content:
+ content = ""
+ if not notes:
+ notes = ""
+
+ html = ('%s' % content)
+ if image:
+ html += '
' % image
+ if code:
+ html += ''
+ html += '
%s
' % code
+ if iframe:
+ html += (''
+ '' % iframe)
+ html += '' % notes
+ html += ''
+
+ self._presentation_slides[name].append(html)
+
+ def save_presentation(self, filename="my_presentation.html", name=None):
+ """ Saves a Reveal-JS Presentation to a folder for later use. """
+
+ if not name:
+ name = "default"
+ if name not in self._presentation_slides:
+ raise Exception("Presentation {%s} does not exist!" % name)
+ if not filename.endswith('.html'):
+ raise Exception('Presentation file must end in ".html"!')
+
+ the_html = ""
+ for slide in self._presentation_slides[name]:
+ the_html += slide
+
+ the_html += (
+ """
+
+
+
+
+
+
+
+
+ """ % (constants.Reveal.MIN_JS,
+ constants.Reveal.MARKED_JS,
+ constants.PrettifyJS.RUN_PRETTIFY_JS))
+
+ saved_presentations_folder = constants.Presentations.SAVED_FOLDER
+ if saved_presentations_folder.endswith("/"):
+ saved_presentations_folder = saved_presentations_folder[:-1]
+ if not os.path.exists(saved_presentations_folder):
+ try:
+ os.makedirs(saved_presentations_folder)
+ except Exception:
+ pass
+ file_path = saved_presentations_folder + "/" + filename
+ out_file = codecs.open(file_path, "w+")
+ out_file.writelines(the_html)
+ out_file.close()
+ print('\n>>> [%s] was saved!\n' % file_path)
+ return file_path
+
+ def begin_presentation(self, filename="my_presentation.html", name=None):
+ """ Begin a Reveal-JS Presentation in the web browser. """
+
+ if self.headless:
+ return # Presentations should not run in headless mode.
+ if not name:
+ name = "default"
+ if name not in self._presentation_slides:
+ raise Exception("Presentation {%s} does not exist!" % name)
+ if not filename.endswith('.html'):
+ raise Exception('Presentation file must end in ".html"!')
+
+ end_slide = (
+ ''
+ '
')
+ self._presentation_slides[name].append(end_slide)
+ file_path = self.save_presentation(name=name, filename=filename)
+ self._presentation_slides[name].pop()
+
+ self.open_html_file(file_path)
+ presentation_folder = constants.Presentations.SAVED_FOLDER
+ while (len(self.driver.window_handles) > 0 and (
+ presentation_folder in self.get_current_url())):
+ time.sleep(0.1)
+ if self.is_element_visible("p.End_Presentation_Now"):
+ break
+
+ ############
+
def create_tour(self, name=None, theme=None):
""" Creates a tour for a website. By default, the Shepherd JavaScript
Library is used with the Shepherd "Light" / "Arrows" theme.
diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py
index 59bc6ad11ca..509bc5b4afd 100755
--- a/seleniumbase/fixtures/constants.py
+++ b/seleniumbase/fixtures/constants.py
@@ -19,6 +19,10 @@ class Files:
ARCHIVED_DOWNLOADS_FOLDER = "archived_files"
+class Presentations:
+ SAVED_FOLDER = "presentations_saved"
+
+
class SavedCookies:
STORAGE_FOLDER = "saved_cookies"
@@ -84,6 +88,25 @@ class HtmlInspector:
"html-inspector/%s/html-inspector.min.js" % VER)
+class PrettifyJS:
+ RUN_PRETTIFY_JS = ("https://cdn.jsdelivr.net/gh/google/"
+ "code-prettify@master/loader/run_prettify.js")
+
+
+class Reveal:
+ VER = "3.8.0"
+ MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/reveal.min.css" % VER)
+ WHITE_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/white.min.css" % VER)
+ MIN_JS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/js/reveal.min.js" % VER)
+ MARKED_JS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/plugin/markdown/marked.js" % VER)
+ MARKDOWN_MIN_JS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/plugin/markdown/markdown.min.js" % VER)
+
+
class BootstrapTour:
VER = "0.11.0"
MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
From a02e450cd32f667f59215a0108e35ccde71e17bd Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 25 Jun 2020 18:09:56 -0400
Subject: [PATCH 2/7] Update console scripts
---
seleniumbase/console_scripts/sb_print.py | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/seleniumbase/console_scripts/sb_print.py b/seleniumbase/console_scripts/sb_print.py
index 80476ffbbde..6fd1eede863 100755
--- a/seleniumbase/console_scripts/sb_print.py
+++ b/seleniumbase/console_scripts/sb_print.py
@@ -342,6 +342,13 @@ def main():
if console_width and (code_width + extra_r_spaces < console_width):
used_width = code_width + extra_r_spaces
+ try:
+ if "🗺️" in the_code:
+ # Fix width of an emoji
+ the_code = the_code.replace("🗺️", "🗺️ ")
+ except Exception:
+ pass
+
magic_syntax = Syntax(
the_code, code_lang, theme="monokai",
line_numbers=line_numbers, code_width=used_width,
From e7b6aef4db85c777463d2335107145b60b678ae2 Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 25 Jun 2020 18:10:23 -0400
Subject: [PATCH 3/7] Create a new tour example
---
examples/tour_examples/maps_introjs_tour.py | 34 +++++++++++++++++++++
1 file changed, 34 insertions(+)
create mode 100755 examples/tour_examples/maps_introjs_tour.py
diff --git a/examples/tour_examples/maps_introjs_tour.py b/examples/tour_examples/maps_introjs_tour.py
new file mode 100755
index 00000000000..fe1c9293326
--- /dev/null
+++ b/examples/tour_examples/maps_introjs_tour.py
@@ -0,0 +1,34 @@
+from seleniumbase import BaseCase
+
+
+class MyTourClass(BaseCase):
+
+ def test_google_maps_tour(self):
+ self.open("https://www.google.com/maps/@42.3598616,-71.0912631,15z")
+ self.wait_for_element("#searchboxinput")
+ self.wait_for_element("#minimap")
+ self.wait_for_element("#zoom")
+
+ self.create_tour(theme="introjs")
+ self.add_tour_step("Welcome to Google Maps!",
+ title="✅ SeleniumBase Tours 🌎")
+ 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.",
+ "#minimap 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 using SeleniumBase Tours!",
+ title="🚃 End of Guided Tour 🚃")
+ self.export_tour(filename="google_maps_introjs_tour.js")
+ self.play_tour()
From b63349568324c17093aa1655284d891168098937 Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 25 Jun 2020 18:12:09 -0400
Subject: [PATCH 4/7] Update Python dependencies
---
docs/requirements.txt | 2 +-
requirements.txt | 13 ++++++-------
setup.py | 11 +++++------
3 files changed, 12 insertions(+), 14 deletions(-)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 83f280b6b45..9b2f93ab1d3 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1,6 +1,6 @@
livereload==2.6.2;python_version>="3.6"
mkdocs==1.1.2
-mkdocs-material==5.3.0
+mkdocs-material==5.3.3
mkdocs-simple-hooks==0.1.1
mkdocs-material-extensions==1.0
mkdocs-minify-plugin==0.3.0
diff --git a/requirements.txt b/requirements.txt
index 0981f942acf..4a663531853 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,7 +6,7 @@ setuptools-scm>=4.1.2
wheel>=0.34.2
six==1.15.0
nose==1.3.7
-ipdb==0.13.2
+ipdb==0.13.3
idna==2.9
chardet==3.0.4
urllib3==1.25.9
@@ -14,16 +14,15 @@ requests==2.24.0
selenium==3.141.0
pluggy==0.13.1
attrs>=19.3.0
-py==1.8.1;sys_platform=="win32"
-py==1.8.2;sys_platform!="win32"
+py==1.8.1
pytest==4.6.11;python_version<"3.5"
pytest==5.4.3;python_version>="3.5"
pytest-cov==2.10.0
-pytest-forked==1.1.3
+pytest-forked==1.2.0
pytest-html==1.22.1;python_version<"3.6"
pytest-html==2.0.1;python_version>="3.6"
pytest-metadata==1.8.0;python_version<"3.6"
-pytest-metadata==1.9.0;python_version>="3.6"
+pytest-metadata==1.10.0;python_version>="3.6"
pytest-ordering==0.6
pytest-rerunfailures==8.0;python_version<"3.6"
pytest-rerunfailures==9.0;python_version>="3.6"
@@ -43,11 +42,11 @@ coverage==5.1
pyotp==2.3.0
boto==2.49.0
cffi==1.14.0
-rich==2.2.3;python_version>="3.6" and python_version<"4.0"
+rich==2.2.6;python_version>="3.6" and python_version<"4.0"
flake8==3.7.9;python_version<"3.5"
flake8==3.8.3;python_version>="3.5"
pyflakes==2.1.1;python_version<"3.5"
pyflakes==2.2.0;python_version>="3.5"
-certifi>=2020.4.5.2
+certifi>=2020.6.20
pdfminer.six==20191110;python_version<"3.5"
pdfminer.six==20200517;python_version>="3.5"
diff --git a/setup.py b/setup.py
index aacd5c89dd1..b5d08a8fd51 100755
--- a/setup.py
+++ b/setup.py
@@ -106,16 +106,15 @@
'selenium==3.141.0',
'pluggy==0.13.1',
'attrs>=19.3.0',
- 'py==1.8.1;sys_platform=="win32"',
- 'py==1.8.2;sys_platform!="win32"',
+ 'py==1.8.1',
'pytest==4.6.11;python_version<"3.5"',
'pytest==5.4.3;python_version>="3.5"',
'pytest-cov==2.10.0',
- 'pytest-forked==1.1.3',
+ 'pytest-forked==1.2.0',
'pytest-html==1.22.1;python_version<"3.6"',
'pytest-html==2.0.1;python_version>="3.6"',
'pytest-metadata==1.8.0;python_version<"3.6"',
- 'pytest-metadata==1.9.0;python_version>="3.6"',
+ 'pytest-metadata==1.10.0;python_version>="3.6"',
'pytest-ordering==0.6',
'pytest-rerunfailures==8.0;python_version<"3.6"',
'pytest-rerunfailures==9.0;python_version>="3.6"',
@@ -135,12 +134,12 @@
'pyotp==2.3.0',
'boto==2.49.0',
'cffi==1.14.0',
- 'rich==2.2.3;python_version>="3.6" and python_version<"4.0"',
+ 'rich==2.2.6;python_version>="3.6" and python_version<"4.0"',
'flake8==3.7.9;python_version<"3.5"',
'flake8==3.8.3;python_version>="3.5"',
'pyflakes==2.1.1;python_version<"3.5"',
'pyflakes==2.2.0;python_version>="3.5"',
- 'certifi>=2020.4.5.2',
+ 'certifi>=2020.6.20',
'pdfminer.six==20191110;python_version<"3.5"',
'pdfminer.six==20200517;python_version>="3.5"',
],
From 799c56b47214a0c3cf977d90ee51e75db7196984 Mon Sep 17 00:00:00 2001
From: Michael Mintz
Date: Thu, 25 Jun 2020 18:12:26 -0400
Subject: [PATCH 5/7] Update the ReadMe
---
README.md | 25 +++++++++++++------------
1 file changed, 13 insertions(+), 12 deletions(-)
diff --git a/README.md b/README.md
index 348ca80b6d4..88fb87710f9 100755
--- a/README.md
+++ b/README.md
@@ -27,20 +27,21 @@ Tests are run with "pytest". Browsers are controlled by WebDriver.