diff --git a/.gitignore b/.gitignore
index 038ce756d79..aa5bc9d68a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -19,6 +19,7 @@ lib64
__pycache__
# Python3 pyvenv
+env
venv
sbase
sbase*
@@ -74,9 +75,6 @@ archived_logs
geckodriver.log
pytestdebug.log
-# Presentations
-presentations_saved
-
# Reports
latest_report
report_archives
@@ -85,6 +83,10 @@ html_report.html
report.html
report.xml
+# Presentations
+presentations_saved
+saved_presentations
+
# Tours
tours_exported
diff --git a/examples/presenter/ReadMe.md b/examples/presenter/ReadMe.md
index 8b1409e5df3..ddc9fdfcde5 100755
--- a/examples/presenter/ReadMe.md
+++ b/examples/presenter/ReadMe.md
@@ -2,12 +2,12 @@
# 📰 Presenter 📰
-SeleniumBase Presenter allows you to create an HTML presentation with only a few lines of Python.
+SeleniumBase Presenter allows you to create HTML presentations with Python.
The Reveal-JS library is used for running the presentations.
-**Here's a sample slide:**
+**Here's a sample presentation:**
-
+
Slides can include HTML, code, images, and iframes.
@@ -21,11 +21,15 @@ pytest my_presentation.py
### Creating a new presentation:
```python
-self.create_presentation(name=None, show_notes=True)
+self.create_presentation(name=None, theme="serif", 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.
+ theme - Set a theme with a unique style for the presentation.
+ Valid themes: "serif" (default), "sky", "white", "black",
+ "simple", "league", "moon", "night",
+ "beige", "blood", and "solarized".
show_notes - When set to True, the Notes feature becomes enabled,
which allows presenters to see notes next to slides.
"""
@@ -40,7 +44,7 @@ Notes are enabled by default unless you specify:
```python
self.add_slide(content=None, image=None, code=None, iframe=None,
- notes=None, name=None)
+ content2=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.
@@ -48,6 +52,7 @@ self.add_slide(content=None, image=None, code=None, iframe=None,
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.
+ content2 - HTML content to display after adding an image or code.
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,
@@ -59,11 +64,19 @@ self.add_slide(content=None, image=None, code=None, iframe=None,
### Running a presentation:
```python
-self.begin_presentation(filename="my_presentation.html", name=None)
-""" Begin a Reveal-JS Presentation in the web browser. """
+self.begin_presentation(filename="my_presentation.html", interval=0)
+""" Begin a Reveal-JS Presentation in the web browser.
+ @Params
+ name - If creating multiple presentations at the same time,
+ use this to select the one you wish to add slides to.
+ filename - The name of the HTML file that you wish to
+ save the presentation to. (filename must end in ".html")
+ interval - The delay time between autoplaying slides. (in seconds)
+ If set to 0 (default), autoplay is disabled.
+"""
```
-Before the presentation is run, the full HTML is saved to the ``presentations_saved/`` folder.
+Before the presentation is run, the full HTML is saved to the ``saved_presentations/`` folder.
All methods have the optional ``name`` argument, which is only needed if you're creating multiple presentations at once.
@@ -77,22 +90,44 @@ from seleniumbase import BaseCase
class MyPresenterClass(BaseCase):
def test_presenter(self):
- self.create_presentation()
+ self.create_presentation(theme="serif")
+ self.add_slide(
+ '
',
+ image="https://seleniumbase.io/img/sb_logo_10.png")
+ self.begin_presentation(filename="presenter.html", interval=0)
```
#### 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:
@@ -140,7 +209,7 @@ pytest my_presentation.py
If you want to save the presentation you created as an HTML file, use:
```python
-self.save_presentation(filename="my_presentation.html", name=None)
+self.save_presentation(filename="my_presentation.html")
```
Presentations automatically get saved when calling:
diff --git a/examples/presenter/my_presentation.py b/examples/presenter/my_presentation.py
index 2a646f1b908..a7ec2d643e6 100755
--- a/examples/presenter/my_presentation.py
+++ b/examples/presenter/my_presentation.py
@@ -4,22 +4,44 @@
class MyPresenterClass(BaseCase):
def test_presenter(self):
- self.create_presentation()
+ self.create_presentation(theme="serif")
self.add_slide(
- "
\n'
+ '' % (constants.Reveal.MIN_CSS, reveal_theme_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):
+ content2=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.
@@ -3192,6 +3231,7 @@ def add_slide(self, content=None, image=None, code=None, iframe=None,
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.
+ content2 - HTML content to display after adding an image or code.
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,
@@ -3205,53 +3245,79 @@ def add_slide(self, content=None, image=None, code=None, iframe=None,
self.create_presentation(name=name, show_notes=True)
if not content:
content = ""
+ if not content2:
+ content2 = ""
if not notes:
notes = ""
- html = ('%s' % content)
+ add_line = ""
+ if content.startswith("<"):
+ add_line = "\n"
+ html = ('\n%s%s' % (add_line, content))
if image:
- html += '
' % image
+ html += '\n
' % image
if code:
- html += ''
- html += '
%s
' % code
+ html += '\n'
+ html += '\n
\n%s
' % code
if iframe:
- html += (''
- '' % iframe)
- html += '' % notes
- html += ''
+ add_line = ""
+ if content2.startswith("<"):
+ add_line = "\n"
+ if content2:
+ html += '%s%s' % (add_line, content2)
+ html += '\n' % notes
+ html += '\n\n'
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. """
+ def save_presentation(self, name=None, filename=None, interval=0):
+ """ Saves a Reveal-JS Presentation to a file for later use.
+ @Params
+ name - If creating multiple presentations at the same time,
+ use this to select the one you wish to add slides to.
+ filename - The name of the HTML file that you wish to
+ save the presentation to. (filename must end in ".html")
+ interval - The delay time between autoplaying slides. (in seconds)
+ If set to 0 (default), autoplay is disabled.
+ """
if not name:
name = "default"
+ if not filename:
+ filename = "my_presentation.html"
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"!')
+ if not interval:
+ interval = 0
+ if not type(interval) is int and not type(interval) is float:
+ raise Exception('Expecting a numeric value for "interval"!')
+ if interval < 0:
+ raise Exception('The "interval" cannot be a negative number!')
+ interval_ms = float(interval) * 1000.0
the_html = ""
for slide in self._presentation_slides[name]:
the_html += slide
the_html += (
- """
-
\n'
+ '\n'
+ '\n'
+ '\n'
+ '\n'
+ '\n'
+ '' % (constants.Reveal.MIN_JS,
+ constants.PrettifyJS.RUN_PRETTIFY_JS,
+ interval_ms))
saved_presentations_folder = constants.Presentations.SAVED_FOLDER
if saved_presentations_folder.endswith("/"):
@@ -3268,32 +3334,53 @@ def save_presentation(self, filename="my_presentation.html", name=None):
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. """
-
+ def begin_presentation(self, name=None, filename=None, interval=0):
+ """ Begin a Reveal-JS Presentation in the web browser.
+ @Params
+ name - If creating multiple presentations at the same time,
+ use this to select the one you wish to add slides to.
+ filename - The name of the HTML file that you wish to
+ save the presentation to. (filename must end in ".html")
+ interval - The delay time between autoplaying slides. (in seconds)
+ If set to 0 (default), autoplay is disabled.
+ """
if self.headless:
return # Presentations should not run in headless mode.
if not name:
name = "default"
+ if not filename:
+ filename = "my_presentation.html"
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"!')
+ if not interval:
+ interval = 0
+ if not type(interval) is int and not type(interval) is float:
+ raise Exception('Expecting a numeric value for "interval"!')
+ if interval < 0:
+ raise Exception('The "interval" cannot be a negative number!')
end_slide = (
- ''
- '
')
+ '\n\n'
+ '
\n\n')
self._presentation_slides[name].append(end_slide)
- file_path = self.save_presentation(name=name, filename=filename)
+ file_path = self.save_presentation(
+ name=name, filename=filename, interval=interval)
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
+ try:
+ while (len(self.driver.window_handles) > 0 and (
+ presentation_folder in self.get_current_url())):
+ time.sleep(0.05)
+ if self.is_element_visible(
+ "section.present p.End_Presentation_Now"):
+ break
+ time.sleep(0.05)
+ except Exception:
+ pass
############
@@ -3761,7 +3848,7 @@ def play_tour(self, name=None, interval=0):
@Params
name - If creating multiple tours at the same time,
use this to select the tour you wish to add steps to.
- interval - The delay time between autoplaying tour steps.
+ interval - The delay time between autoplaying tour steps. (Seconds)
If set to 0 (default), the tour is fully manual control.
"""
if self.headless:
diff --git a/seleniumbase/fixtures/constants.py b/seleniumbase/fixtures/constants.py
index 509bc5b4afd..0b8898e41d5 100755
--- a/seleniumbase/fixtures/constants.py
+++ b/seleniumbase/fixtures/constants.py
@@ -20,7 +20,7 @@ class Files:
class Presentations:
- SAVED_FOLDER = "presentations_saved"
+ SAVED_FOLDER = "saved_presentations"
class SavedCookies:
@@ -97,14 +97,30 @@ class Reveal:
VER = "3.8.0"
MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
"reveal.js/%s/css/reveal.min.css" % VER)
+ SERIF_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/serif.min.css" % VER)
WHITE_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
"reveal.js/%s/css/theme/white.min.css" % VER)
+ BLACK_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/black.min.css" % VER)
+ SKY_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/sky.min.css" % VER)
+ MOON_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/moon.min.css" % VER)
+ NIGHT_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/night.min.css" % VER)
+ LEAGUE_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/league.min.css" % VER)
+ BEIGE_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/beige.min.css" % VER)
+ BLOOD_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/blood.min.css" % VER)
+ SIMPLE_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/simple.min.css" % VER)
+ SOLARIZED_MIN_CSS = ("https://cdnjs.cloudflare.com/ajax/libs/"
+ "reveal.js/%s/css/theme/solarized.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:
diff --git a/seleniumbase/resources/prettify/run_prettify.js b/seleniumbase/resources/prettify/run_prettify.js
new file mode 100644
index 00000000000..8fc3e48451b
--- /dev/null
+++ b/seleniumbase/resources/prettify/run_prettify.js
@@ -0,0 +1,64 @@
+!function(){/*
+
+ Copyright (C) 2013 Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+ Copyright (C) 2006 Google Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+(function(){function aa(g){function r(){try{L.doScroll("left")}catch(ba){k.setTimeout(r,50);return}x("poll")}function x(r){if("readystatechange"!=r.type||"complete"==z.readyState)("load"==r.type?k:z)[B](n+r.type,x,!1),!l&&(l=!0)&&g.call(k,r.type||r)}var X=z.addEventListener,l=!1,E=!0,v=X?"addEventListener":"attachEvent",B=X?"removeEventListener":"detachEvent",n=X?"":"on";if("complete"==z.readyState)g.call(k,"lazy");else{if(z.createEventObject&&L.doScroll){try{E=!k.frameElement}catch(ba){}E&&r()}z[v](n+
+"DOMContentLoaded",x,!1);z[v](n+"readystatechange",x,!1);k[v](n+"load",x,!1)}}function T(){U&&aa(function(){var g=M.length;ca(g?function(){for(var r=0;r=c?parseInt(e.substring(1),8):"u"===c||"x"===c?parseInt(e.substring(2),16):e.charCodeAt(1)}function f(e){if(32>e)return(16>e?"\\x0":"\\x")+e.toString(16);e=String.fromCharCode(e);
+return"\\"===e||"-"===e||"]"===e||"^"===e?"\\"+e:e}function c(e){var c=e.substring(1,e.length-1).match(RegExp("\\\\u[0-9A-Fa-f]{4}|\\\\x[0-9A-Fa-f]{2}|\\\\[0-3][0-7]{0,2}|\\\\[0-7]{1,2}|\\\\[\\s\\S]|-|[^-\\\\]","g"));e=[];var a="^"===c[0],b=["["];a&&b.push("^");for(var a=a?1:0,h=c.length;ap||122p||90p||122m[0]&&(m[1]+1>m[0]&&b.push("-"),b.push(f(m[1])));b.push("]");return b.join("")}function g(e){for(var a=e.source.match(RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)",
+"g")),b=a.length,d=[],h=0,m=0;h/,null])):d.push(["com",/^#[^\r\n]*/,null,"#"]));a.cStyleComments&&(f.push(["com",/^\/\/[^\r\n]*/,null]),f.push(["com",/^\/\*[\s\S]*?(?:\*\/|$)/,
+null]));if(c=a.regexLiterals){var g=(c=1|\\/=?|::?|<=?|>>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*("+("/(?=[^/*"+c+"])(?:[^/\\x5B\\x5C"+c+"]|\\x5C"+g+"|\\x5B(?:[^\\x5C\\x5D"+c+"]|\\x5C"+g+")*(?:\\x5D|$))+/")+")")])}(c=a.types)&&f.push(["typ",c]);c=(""+a.keywords).replace(/^ | $/g,"");c.length&&f.push(["kwd",
+new RegExp("^(?:"+c.replace(/[\s,]+/g,"|")+")\\b"),null]);d.push(["pln",/^\s+/,null," \r\n\t\u00a0"]);c="^.[^\\s\\w.$@'\"`/\\\\]*";a.regexLiterals&&(c+="(?!s*/)");f.push(["lit",/^@[a-z_$][a-z_$@0-9]*/i,null],["typ",/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],["pln",/^[a-z_$][a-z_$@0-9]*/i,null],["lit",/^(?:0x[a-f0-9]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+\-]?\d+)?)[a-z]*/i,null,"0123456789"],["pln",/^\\[\s\S]?/,null],["pun",new RegExp(c),null]);return E(d,f)}function B(a,d,f){function c(a){var b=
+a.nodeType;if(1==b&&!r.test(a.className))if("br"===a.nodeName.toLowerCase())g(a),a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)c(a);else if((3==b||4==b)&&f){var e=a.nodeValue,d=e.match(n);d&&(b=e.substring(0,d.index),a.nodeValue=b,(e=e.substring(d.index+d[0].length))&&a.parentNode.insertBefore(q.createTextNode(e),a.nextSibling),g(a),b||a.parentNode.removeChild(a))}}function g(a){function c(a,b){var e=b?a.cloneNode(!1):a,p=a.parentNode;if(p){var p=c(p,1),d=a.nextSibling;
+p.appendChild(e);for(var f=d;f;f=d)d=f.nextSibling,p.appendChild(f)}return e}for(;!a.nextSibling;)if(a=a.parentNode,!a)return;a=c(a.nextSibling,0);for(var e;(e=a.parentNode)&&1===e.nodeType;)a=e;b.push(a)}for(var r=/(?:^|\s)nocode(?:\s|$)/,n=/\r\n?|\n/,q=a.ownerDocument,k=q.createElement("li");a.firstChild;)k.appendChild(a.firstChild);for(var b=[k],t=0;t=+g[1],d=/\n/g,r=a.a,k=r.length,f=0,q=a.c,n=q.length,c=0,b=a.g,t=b.length,v=0;b[t]=k;var u,e;for(e=u=0;e=m&&(c+=2);f>=p&&(v+=2)}}finally{h&&(h.style.display=a)}}catch(y){Q.console&&console.log(y&&y.stack||y)}}var Q="undefined"!==typeof window?window:{},J=["break,continue,do,else,for,if,return,while"],K=[[J,"auto,case,char,const,default,double,enum,extern,float,goto,inline,int,long,register,restrict,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
+"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],R=[K,"alignas,alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,delegate,dynamic_cast,explicit,export,friend,generic,late_check,mutable,namespace,noexcept,noreturn,nullptr,property,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],L=[K,"abstract,assert,boolean,byte,extends,finally,final,implements,import,instanceof,interface,null,native,package,strictfp,super,synchronized,throws,transient"],
+M=[K,"abstract,add,alias,as,ascending,async,await,base,bool,by,byte,checked,decimal,delegate,descending,dynamic,event,finally,fixed,foreach,from,get,global,group,implicit,in,interface,internal,into,is,join,let,lock,null,object,out,override,orderby,params,partial,readonly,ref,remove,sbyte,sealed,select,set,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,value,var,virtual,where,yield"],K=[K,"abstract,async,await,constructor,debugger,enum,eval,export,from,function,get,import,implements,instanceof,interface,let,null,of,set,undefined,var,with,yield,Infinity,NaN"],
+N=[J,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],O=[J,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],J=[J,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],P=/^(DIR|FILE|array|vector|(de|priority_)?queue|(forward_)?list|stack|(const_)?(reverse_)?iterator|(unordered_)?(multi)?(set|map)|bitset|u?(int|float)\d*)\b/,
+S=/\S/,T=v({keywords:[R,M,L,K,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",N,O,J],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),V={};n(T,["default-code"]);n(E([],[["pln",/^[^]+/],["dec",/^]*(?:>|$)/],["com",/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",
+/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^