Merge pull request #988 from seleniumbase/recorder-mode-improvements

Recorder Mode improvements (and more)
This commit is contained in:
Michael Mintz 2021-09-26 15:16:49 -04:00 committed by GitHub
commit dc533cb9e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 611 additions and 163 deletions

View File

@ -23,8 +23,8 @@
<p align="center">
<a href="#python_installation">🚀 Start</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/ReadMe.md">👨‍🏫 Examples</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/ReadMe.md">🧙‍♂️ Scripts</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/features_list.md">🏰 Features</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/seleniumbase/console_scripts/ReadMe.md">🧙‍♂️ Scripts</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md">🖥️ CLI</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/mobile_testing.md">📱 Mobile</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/visual_testing/ReadMe.md">🖼️ VisualTest</a>
@ -38,8 +38,8 @@
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/js_package_manager.md">🕹️ JSPkgMgr</a>
<br />
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/integrations/github/workflows/ReadMe.md">🤖 CI</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/ReadMe.md">🎞️ Present</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/tree/master/examples/boilerplates">♻️ Boilerplate</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/presenter/ReadMe.md">🎞️ Slides</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/tree/master/examples/boilerplates">♻️ Boilerplates</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/translations.md">🌏 Translate</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/tour_examples/ReadMe.md">🗺️ Tour</a> |
<a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/chart_maker/ReadMe.md">📶 Charts</a> |

View File

@ -1,4 +1,4 @@
regex>=2021.8.27
regex>=2021.9.24
tqdm>=4.62.3
livereload==2.6.3;python_version>="3.6"
joblib==1.0.1;python_version>="3.6"
@ -20,7 +20,7 @@ lunr==0.6.0;python_version>="3.6"
nltk==3.6.3;python_version>="3.6"
watchdog==2.1.5;python_version>="3.6"
mkdocs==1.2.2;python_version>="3.6"
mkdocs-material==7.2.8;python_version>="3.6"
mkdocs-material==7.3.0;python_version>="3.6"
mkdocs-exclude-search==0.5.2;python_version>="3.6"
mkdocs-simple-hooks==0.1.3
mkdocs-material-extensions==1.0.3;python_version>="3.6"

View File

@ -1,4 +1,4 @@
<img src="https://seleniumbase.io/cdn/img/super_logo_sb.png" title="SeleniumBase" width="320" />
[<img src="https://seleniumbase.io/cdn/img/sb_logo_10t.png" title="SeleniumBase" width="220">](https://github.com/seleniumbase/SeleniumBase/)
<h2><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Running Example Tests:</h2>
@ -169,7 +169,7 @@ python gui_test_runner.py
--------
<img src="https://seleniumbase.io/cdn/img/super_logo_sb4.png" title="SeleniumBase" width="320" />
<img src="https://seleniumbase.io/cdn/img/super_logo_sb.png" title="SeleniumBase" width="320" />
<a href="https://github.com/seleniumbase/SeleniumBase">
<img src="https://img.shields.io/badge/tested%20with-SeleniumBase-04C38E.svg" alt="Tested with SeleniumBase" /></a>

View File

@ -37,7 +37,7 @@ class DialogBoxTests(BaseCase):
buttons = [(btn_text_1, "green"), (btn_text_2, "purple")]
choice_2 = self.get_jqc_button_input(message, buttons)
if choice_2 == btn_text_2:
self.open("https://xkcd.com/1287/")
self.open_if_not_url("https://xkcd.com/1287/")
message = "Brain sports count as sports!<br /><br />"
message += "Are you ready for more?"
self.get_jqc_button_input(message, ["Let's Go!"])
@ -77,14 +77,12 @@ class DialogBoxTests(BaseCase):
self.set_jqc_theme("bootstrap", color="red", width="32%")
if self.is_text_visible("No matching documents", ".md-search-result"):
self.get_jqc_button_input("Your search had no results!", ["OK"])
elif self.is_element_visible("a.md-search-result__link"):
self.click("a.md-search-result__link")
self.set_jqc_theme("bootstrap", color="green", width="32%")
self.get_jqc_button_input("You found search results!", ["OK"])
elif self.is_text_visible("Type to start searching", "div.md-search"):
self.get_jqc_button_input("You did not do a search!", ["OK"])
else:
self.get_jqc_button_input("We're not sure what happened.", ["OK"])
self.click_if_visible("a.md-search-result__link")
self.set_jqc_theme("bootstrap", color="green", width="32%")
self.get_jqc_button_input("You found search results!", ["OK"])
self.open("https://seleniumbase.io/help_docs/ReadMe/")
self.highlight("h1")
@ -99,19 +97,20 @@ class DialogBoxTests(BaseCase):
message = "Now let's combine form inputs with multiple button options!"
message += "<br /><br />"
message += "Pick something to search. Then pick the site to search on."
buttons = ["XKCD.com", "Wikipedia.org"]
buttons = ["XKCD.com Store", "Wikipedia.org"]
text, choice = self.get_jqc_form_inputs(message, buttons)
if choice == "XKCD.com":
self.open("https://relevant-xkcd.github.io/")
if choice == "XKCD.com Store":
self.open("https://store.xkcd.com/search")
else:
self.open("https://en.wikipedia.org/wiki/Main_Page")
self.highlight_update_text('input[name="search"]', text + "\n")
self.open("https://en.wikipedia.org/wiki/Special:Search")
self.highlight_update_text('input[id*="search"]', text + "\n")
self.wait_for_ready_state_complete()
self.sleep(1)
self.highlight("body")
self.reset_jqc_theme()
self.get_jqc_button_input("<b>Here are your results.</b>", ["OK"])
message = "<h3>You've reached the end of this tutorial!</h3><br />"
message += "Thanks for learning about SeleniumBase Dialog Boxes!<br />"
message += "<br />Check out the SeleniumBase page on GitHub for more!"
message += "Now you know about SeleniumBase Dialog Boxes!<br />"
message += "<br />Check out SeleniumBase on GitHub for more!"
self.set_jqc_theme("modern", color="purple", width="56%")
self.get_jqc_button_input(message, ["Goodbye!"])

View File

@ -7,7 +7,7 @@ class GoogleTests(BaseCase):
[
["PyPI", "pypi.org", 'img[alt="PyPI"]'],
["Wikipedia", "www.wikipedia.org", "img.central-featured-logo"],
["SeleniumBase GitHub", "SeleniumBase - GitH", 'img[title*="Se"]'],
["SeleniumBase GitHub", "Selenium, Python", 'img[title*="Selen"]'],
]
)
def test_parameterized_google_search(self, search_key, expected_text, img):

View File

@ -1,8 +1,8 @@
<h3 align="left"><img src="https://seleniumbase.io/cdn/img/sb_logo_b.png" alt="SeleniumBase" width="360" /></h3>
[<img src="https://seleniumbase.io/cdn/img/sb_logo_10t.png" title="SeleniumBase" width="220">](https://github.com/seleniumbase/SeleniumBase/)
<h1> 📰 Presenter 📑 </h1>
<h1> 📑 Presenter / Slides 🎞️ </h1>
<p>SeleniumBase Presenter lets you use Python to generate HTML presentations from Reveal JS.</p>
<p>SeleniumBase Presenter / Slides lets you use Python to generate HTML presentations and slide shows from Reveal-JS.</p>
<b>Here's a sample presentation:</b>
@ -244,3 +244,7 @@ Presentations automatically get saved when calling:
```python
self.begin_presentation(show_notes=True)
```
--------
<h3 align="left"><img src="https://seleniumbase.io/cdn/img/sb_logo_b.png" alt="SeleniumBase" width="240" /></h3>

View File

@ -3,20 +3,13 @@ from seleniumbase import BaseCase
class CheckboxTests(BaseCase):
def test_checkboxes_and_radio_buttons(self):
self.open(
"https://www.w3schools.com/tags/tryit.asp"
"?filename=tryhtml5_input_type_checkbox"
)
self.ad_block()
self.open("https://seleniumbase.io/w3schools/checkboxes")
self.switch_to_frame("iframeResult")
checkbox = "input#vehicle2"
self.assert_false(self.is_selected(checkbox))
self.click(checkbox)
self.assert_true(self.is_selected(checkbox))
self.open(
"https://www.w3schools.com/tags/tryit.asp"
"?filename=tryhtml5_input_type_radio"
)
self.open("https://seleniumbase.io/w3schools/radio_buttons")
self.switch_to_frame("iframeResult")
option_button = "input#css"
self.assert_false(self.is_selected(option_button))

View File

@ -3,21 +3,13 @@ from seleniumbase import BaseCase
class DoubleClickTests(BaseCase):
def test_switch_to_frame_and_double_click(self):
self.open(
"https://www.w3schools.com/jsref"
"/tryit.asp?filename=tryjsref_ondblclick"
)
self.ad_block()
self.open("https://seleniumbase.io/w3schools/double_click")
self.switch_to_frame("iframe#iframeResult")
self.double_click('[ondblclick="myFunction()"]')
self.assert_text("Hello World", "#demo")
def test_switch_to_frame_of_element_and_double_click(self):
self.open(
"https://www.w3schools.com/jsref"
"/tryit.asp?filename=tryjsref_ondblclick"
)
self.ad_block()
self.open("https://seleniumbase.io/w3schools/double_click")
self.switch_to_frame_of_element('[ondblclick="myFunction()"]')
self.double_click('[ondblclick="myFunction()"]')
self.assert_text("Hello World", "#demo")

View File

@ -13,8 +13,7 @@ class DragAndDropTests(BaseCase):
self.sleep(0.8)
def test_w3schools_drag_and_drop(self):
url = "://w3schools.com/html/tryit.asp?filename=tryhtml5_draganddrop"
self.open(url)
self.open("https://seleniumbase.io/w3schools/drag_drop")
self.remove_elements("#tryitLeaderboard")
self.switch_to_frame("iframeResult")
self.assert_element_not_visible("#div1 img#drag1")

View File

@ -3,23 +3,25 @@ from seleniumbase import BaseCase
class FrameTests(BaseCase):
def test_iframe_basics(self):
self.open(
"https://www.w3schools.com/html/tryit.asp"
"?filename=tryhtml_iframe_height_width_css"
)
self.ad_block() # Reduce noise during automation
self.switch_to_frame("iframeResult") # Enter the iFrame
self.open("https://seleniumbase.io/w3schools/iframes.html")
self.switch_to_frame("iframeResult") # Enter the iframe
self.assert_text("HTML Iframes", "h2")
self.switch_to_frame('[title*="Iframe"]') # Enter iFrame inside iFrame
self.switch_to_frame('[title*="Iframe"]') # Enter iframe inside iframe
self.assert_text("This page is displayed in an iframe", "h1")
self.switch_to_default_content() # Exit all iFrames
self.switch_to_frame("iframeResult") # Go back inside 1st iFrame
self.click("button#runbtn")
self.switch_to_frame("iframeResult") # Go back inside 1st iframe
self.highlight('iframe[title="Iframe Example"]')
def test_set_content_to_frame(self):
self.open(
"https://www.w3schools.com/html/tryit.asp"
"?filename=tryhtml_iframe_height_width_css"
)
self.open("https://seleniumbase.io/w3schools/iframes.html")
self.set_content_to_frame("iframeResult")
self.highlight('iframe[title="Iframe Example"]', loops=8)
self.highlight('iframe[title="Iframe Example"]')
self.set_content_to_frame("iframe")
self.assert_element_not_visible('iframe')
self.highlight("body")
self.set_content_to_default(nested=False)
self.highlight('iframe[title="Iframe Example"]')
self.set_content_to_default()
self.click("button#runbtn")
self.highlight("#iframeResult")

View File

@ -6,10 +6,7 @@ from seleniumbase import BaseCase
class FileUploadButtonTests(BaseCase):
def test_file_upload_button(self):
self.open(
"https://www.w3schools.com/jsref/tryit.asp"
"?filename=tryjsref_fileupload_get"
)
self.open("https://seleniumbase.io/w3schools/file_upload")
self.set_content_to_frame("iframeResult")
zoom_in = 'input[type="file"]{zoom: 1.6;-moz-transform: scale(1.6);}'
self.add_css_style(zoom_in)

View File

@ -1,4 +1,4 @@
[<img src="https://seleniumbase.io/cdn/img/super_logo_sb.png" title="SeleniumBase" width="320">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)
[<img src="https://seleniumbase.io/cdn/img/sb_logo_10t.png" title="SeleniumBase" width="220">](https://github.com/seleniumbase/SeleniumBase/)
# pytest CLI Options

View File

@ -199,6 +199,8 @@ self.switch_to_default_content()
self.set_content_to_frame(frame, timeout=None)
self.set_content_to_default(nested=True)
self.open_new_window(switch_to=True)
self.switch_to_window(window, timeout=None)

View File

@ -1,4 +1,4 @@
[<img src="https://seleniumbase.io/cdn/img/sb_logo_10t.png" title="SeleniumBase" width="260">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)
[<img src="https://seleniumbase.io/cdn/img/sb_logo_10t.png" title="SeleniumBase" width="220">](https://github.com/seleniumbase/SeleniumBase/)
<h2><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Recorder Mode</h2>
@ -21,7 +21,14 @@ import ipdb; ipdb.set_trace()
🔴 Once you've reached the breakpoint, you can take control of the browser and add in any actions that you want recorded. When you are finished recording, type "``c``" on the command-line and press ``[Enter]`` to let the test continue from the breakpoint. After the test completes, a file called ``TEST_NAME_rec.py`` will be automatically created in the ``./recordings`` folder, which will include the actions performed by the test, and the manual actions that you added in. Below is a command-line notification:
```bash
>>> RECORDING saved to: recordings/my_first_test_rec.py
>>> RECORDING SAVED as: recordings/my_first_test_rec.py
*******************************************************
```
If a Python file contains more tests, they'll be added:
```bash
>>> RECORDING ADDED to: recordings/my_first_test_rec.py
*******************************************************
```
@ -49,6 +56,8 @@ class RecorderTest(BaseCase):
<p>🔴 SeleniumBase <code>1.66.1</code> adds the ability to record changes to <i>"Choose File"</i> <code>input</code> fields. Sometimes the <i>"Choose File"</i> input field is hidden on websites, so <code>self.show_file_choosers()</code> was added to get around this edge case. Version <code>1.66.1</code> also adds <code>self.set_content_to_frame(frame)</code>, which lets you record actions inside of iframes.</p>
<p>🔴 SeleniumBase <code>1.66.2</code> adds the ability to save selectors using the <code>":contains(TEXT)"</code> selector. If a Python file being recorded has multiple tests being run, then all those tests will get saved to the generated "*_rec.py" file. In order to escape iframes when using <code>self.set_content_to_frame(frame)</code>, a new method was added: <code>self.set_content_to_default()</code>. The <code>self.set_content_to_*()</code> methods will be automatically used in place of <code>self.switch_to_*()</code> methods in Recorder Mode, unless a test explicitly calls <code>self._rec_overrides_switch = False</code> before the <code>self.switch_to_*()</code> methods are called. Additionally, if an iframe contains the <code>src</code> attribute, that page will get loaded in a new tab when switching to it in Recorder Mode.</p>
--------
<div>To learn more about SeleniumBase, check out the Docs Site:</div>

View File

@ -39,7 +39,7 @@ theme:
static_templates:
- 404.html
features:
- search.highlight
# - search.highlight
- toc.integrate
- navigation.indexes
# - navigation.sections

View File

@ -5,7 +5,7 @@ packaging>=21.0;python_version>="3.6"
typing-extensions>=3.10.0.2
setuptools>=44.1.1;python_version<"3.5"
setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"
setuptools>=58.0.4;python_version>="3.6"
setuptools>=58.1.0;python_version>="3.6"
setuptools-scm==5.0.2;python_version<"3.6"
setuptools-scm>=6.3.2;python_version>="3.6"
tomli>=1.2.1;python_version>="3.6"
@ -27,7 +27,7 @@ idna==3.2;python_version>="3.6"
chardet==3.0.4;python_version<"3.5"
chardet==4.0.0;python_version>="3.5"
charset-normalizer==2.0.6;python_version>="3.5"
urllib3==1.26.6
urllib3==1.26.7
requests==2.26.0;python_version<"3.5"
requests==2.25.1;python_version>="3.5" and python_version<"3.6"
requests==2.26.0;python_version>="3.6"
@ -83,15 +83,15 @@ decorator==5.1.0;python_version>="3.5"
ipython==5.10.0;python_version<"3.5"
ipython==7.9.0;python_version>="3.5" and python_version<"3.6"
ipython==7.16.1;python_version>="3.6" and python_version<"3.7"
ipython==7.27.0;python_version>="3.7"
ipython==7.28.0;python_version>="3.7"
matplotlib-inline==0.1.3;python_version>="3.7"
colorama==0.4.4
platformdirs==2.0.2;python_version<"3.6"
platformdirs==2.3.0;python_version>="3.6"
platformdirs==2.4.0;python_version>="3.6"
pathlib2==2.3.5;python_version<"3.5"
importlib-metadata==2.0.0;python_version<"3.5"
importlib-metadata==2.1.1;python_version>="3.5" and python_version<"3.6"
virtualenv>=20.8.0
virtualenv>=20.8.1
pymysql==0.10.1;python_version<"3.6"
pymysql==1.0.2;python_version>="3.6"
pyotp==2.6.0
@ -101,7 +101,7 @@ toml==0.10.2
Pillow==6.2.2;python_version<"3.5"
Pillow==7.2.0;python_version>="3.5" and python_version<"3.6"
Pillow==8.3.2;python_version>="3.6"
rich==10.10.0;python_version>="3.6" and python_version<"4.0"
rich==10.11.0;python_version>="3.6" and python_version<"4.0"
tornado==5.1.1;python_version<"3.5"
tornado==6.1;python_version>="3.5"
pdfminer.six==20191110;python_version<"3.5"

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "1.66.1"
__version__ = "1.66.2"

View File

@ -635,9 +635,10 @@ def show_options():
op += " | return / r: Run until method returns. j: Jump to line. |\n"
op += " | where / w: Show stack spot. u: Up stack. d: Down stack. |\n"
op += " | longlist / ll: See code. dir(): List namespace objects. |\n"
op += "--recorder (Record browser actions to generate test scripts.)\n"
op += "--save-screenshot (Save a screenshot at the end of each test.)\n"
op += "-x (Stop running the tests after the first failure is reached.)\n"
op += "--archive-logs (Archive old log files instead of deleting them.)\n"
op += "--save-screenshot (Save a screenshot at the end of each test.)\n"
op += "--check-js (Check for JavaScript errors after page loads.)\n"
op += "--start-page=URL (The browser start page when tests begin.)\n"
op += "--agent=STRING (Modify the web browser's User-Agent string.)\n"

View File

@ -28,6 +28,7 @@ if not os.environ["PATH"].startswith(DRIVER_DIR):
os.environ["PATH"] = DRIVER_DIR + os.pathsep + os.environ["PATH"]
EXTENSIONS_DIR = os.path.dirname(os.path.realpath(extensions.__file__))
DISABLE_CSP_ZIP_PATH = "%s/%s" % (EXTENSIONS_DIR, "disable_csp.zip")
AD_BLOCK_ZIP_PATH = "%s/%s" % (EXTENSIONS_DIR, "ad_block.zip")
RECORDER_ZIP_PATH = "%s/%s" % (EXTENSIONS_DIR, "recorder.zip")
PROXY_ZIP_PATH = proxy_helper.PROXY_ZIP_PATH
PROXY_ZIP_PATH_2 = proxy_helper.PROXY_ZIP_PATH_2
@ -199,8 +200,17 @@ def _add_chrome_disable_csp_extension(chrome_options):
return chrome_options
def _add_chrome_ad_block_extension(chrome_options):
"""Block Ads on Chromium Browsers with a browser extension.
See https://github.com/slingamn/simpleblock for details."""
ad_block_zip = AD_BLOCK_ZIP_PATH
chrome_options.add_extension(ad_block_zip)
return chrome_options
def _add_chrome_recorder_extension(chrome_options):
"""The SeleniumBase Recorder Chromium extension."""
"""The SeleniumBase Recorder Chrome/Edge extension.
https://seleniumbase.io/help_docs/recorder_mode/"""
recorder_zip = RECORDER_ZIP_PATH
chrome_options.add_extension(recorder_zip)
return chrome_options
@ -228,6 +238,7 @@ def _set_chrome_options(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
user_data_dir,
@ -322,10 +333,10 @@ def _set_chrome_options(
chrome_options.add_experimental_option(
"mobileEmulation", emulator_settings
)
chrome_options.add_argument("--enable-sync")
if (
not proxy_auth
and not disable_csp
and not ad_block_on
and not recorder_ext
and (not extension_zip and not extension_dir)
):
@ -391,7 +402,8 @@ def _set_chrome_options(
# Headless Chrome doesn't support extensions, which are required
# for disabling the Content Security Policy on Chrome
chrome_options = _add_chrome_disable_csp_extension(chrome_options)
chrome_options.add_argument("--enable-sync")
if ad_block_on and not headless:
chrome_options = _add_chrome_ad_block_extension(chrome_options)
if recorder_ext and not headless:
chrome_options = _add_chrome_recorder_extension(chrome_options)
if proxy_string:
@ -687,6 +699,7 @@ def get_driver(
devtools=None,
remote_debug=None,
swiftshader=None,
ad_block_on=None,
block_images=None,
chromium_arg=None,
firefox_arg=None,
@ -760,6 +773,7 @@ def get_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
firefox_arg,
@ -796,6 +810,7 @@ def get_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
firefox_arg,
@ -836,6 +851,7 @@ def get_remote_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
firefox_arg,
@ -900,6 +916,7 @@ def get_remote_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
user_data_dir,
@ -1053,6 +1070,7 @@ def get_local_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
firefox_arg,
@ -1220,6 +1238,7 @@ def get_local_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
user_data_dir,
@ -1307,7 +1326,6 @@ def get_local_driver(
edge_options.add_experimental_option(
"mobileEmulation", emulator_settings
)
edge_options.add_argument("--enable-sync")
if user_data_dir:
abs_path = os.path.abspath(user_data_dir)
edge_options.add_argument("user-data-dir=%s" % abs_path)
@ -1342,10 +1360,10 @@ def get_local_driver(
# Headless Edge doesn't support extensions, which are required
# for disabling the Content Security Policy on Edge
edge_options = _add_chrome_disable_csp_extension(edge_options)
edge_options.add_argument("--enable-sync")
if ad_block_on and not headless:
edge_options = _add_chrome_ad_block_extension(edge_options)
if recorder_ext and not headless:
edge_options = _add_chrome_recorder_extension(edge_options)
edge_options.add_argument("--enable-sync")
if proxy_string:
if proxy_auth:
edge_options = _add_chrome_proxy_extension(
@ -1431,6 +1449,7 @@ def get_local_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
user_data_dir,
@ -1475,6 +1494,7 @@ def get_local_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
user_data_dir,
@ -1556,6 +1576,7 @@ def get_local_driver(
devtools,
remote_debug,
swiftshader,
ad_block_on,
block_images,
chromium_arg,
user_data_dir,

View File

@ -3,5 +3,6 @@
<h2><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="30" /> SeleniumBase browser extension storage</h2>
**The List:**
* ad_block.zip => This extension blocks ad content from appearing on any website.
* disable_csp.zip => This extension disables a website's Content-Security-Policy.
* recorder.zip => Save browser actions to sessionStorage with good CSS selectors.

Binary file not shown.

Binary file not shown.

View File

@ -78,6 +78,10 @@ class BaseCase(unittest.TestCase):
self.driver = None
self.environment = None
self.env = None # Add a shortened version of self.environment
self.__page_sources = []
self.__extra_actions = []
self.__js_start_time = 0
self.__set_c_from_switch = False
self.__called_setup = False
self.__called_teardown = False
self.__start_time_ms = None
@ -108,6 +112,7 @@ class BaseCase(unittest.TestCase):
self._language = "English"
self._presentation_slides = {}
self._presentation_transition = {}
self._rec_overrides_switch = True # Recorder-Mode uses set_c vs switch
self._sb_test_identifier = None
self._html_report_extra = [] # (Used by pytest_plugin.py)
self._default_driver = None
@ -200,6 +205,7 @@ class BaseCase(unittest.TestCase):
if scroll and not self.demo_mode and not self.slow_mode:
self.__scroll_to_element(element, selector, by)
pre_action_url = self.driver.current_url
pre_window_count = len(self.driver.window_handles)
try:
if self.browser == "ie" and by == By.LINK_TEXT:
# An issue with clicking Link Text on IE means using jquery
@ -310,6 +316,10 @@ class BaseCase(unittest.TestCase):
self.driver, selector, by, timeout=timeout
)
element.click()
if self.recorder_mode:
latest_window_count = len(self.driver.window_handles)
if latest_window_count > pre_window_count:
self.switch_to_newest_window()
if settings.WAIT_FOR_RSC_ON_CLICKS:
self.wait_for_ready_state_complete()
if self.demo_mode:
@ -2238,6 +2248,23 @@ class BaseCase(unittest.TestCase):
self.scroll_to(frame, timeout=1)
except Exception:
pass
if self.recorder_mode and self._rec_overrides_switch:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
r_a = self.get_session_storage_item("recorder_activated")
if r_a == "yes":
time_stamp = self.execute_script("return Date.now();")
action = ["sk_op", "", "", time_stamp]
self.__extra_actions.append(action)
self.__set_c_from_switch = True
self.set_content_to_frame(frame, timeout=timeout)
self.__set_c_from_switch = False
time_stamp = self.execute_script("return Date.now();")
origin = self.get_origin()
action = ["sw_fr", frame, origin, time_stamp]
self.__extra_actions.append(action)
return
page_actions.switch_to_frame(self.driver, frame, timeout)
def switch_to_default_content(self):
@ -2246,14 +2273,162 @@ class BaseCase(unittest.TestCase):
will be set to one level above the current frame. If the driver
control is not currently in an iframe, nothing will happen.)"""
self.__check_scope()
if self.recorder_mode and self._rec_overrides_switch:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
r_a = self.get_session_storage_item("recorder_activated")
if r_a == "yes":
self.__set_c_from_switch = True
self.set_content_to_default()
self.__set_c_from_switch = False
time_stamp = self.execute_script("return Date.now();")
origin = self.get_origin()
action = ["sw_dc", "", origin, time_stamp]
self.__extra_actions.append(action)
return
self.driver.switch_to.default_content()
def set_content_to_frame(self, frame, timeout=None):
"""Replaces the page html with an iframe's html from that page."""
self.switch_to_frame(frame, timeout=timeout)
"""Replaces the page html with an iframe's html from that page.
If the iFrame contains an "src" field that includes a valid URL,
then instead of replacing the current html, this method will then
open up the "src" URL of the iFrame in a new browser tab.
To return to default content, use: self.set_content_to_default().
This method also sets the state of the browser window so that the
self.set_content_to_default() method can bring the user back to
the original content displayed, which is similar to how the methods
self.switch_to_frame(frame) and self.switch_to_default_content()
work together to get the user into frames and out of all of them.
"""
self.__check_scope()
if not timeout:
timeout = settings.SMALL_TIMEOUT
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
current_url = self.get_current_url()
c_tab = self.driver.current_window_handle
current_page_source = self.get_page_source()
self.execute_script("document.cframe_swap = 0;")
page_actions.switch_to_frame(self.driver, frame, timeout)
iframe_html = self.get_page_source()
self.switch_to_default_content()
self.set_content(iframe_html)
self.driver.switch_to.default_content()
self.wait_for_ready_state_complete()
frame_found = False
o_frame = frame
if self.is_element_present(frame):
frame_found = True
elif " " not in frame:
frame = 'iframe[name="%s"]' % frame
if self.is_element_present(frame):
frame_found = True
url = None
if frame_found:
url = self.execute_script(
"""return document.querySelector('%s').src;""" % frame
)
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
pass
else:
url = None
cframe_tab = False
if url:
cframe_tab = True
self.__page_sources.append([current_url, current_page_source, c_tab])
if self.recorder_mode and not self.__set_c_from_switch:
time_stamp = self.execute_script("return Date.now();")
action = ["sk_op", "", "", time_stamp]
self.__extra_actions.append(action)
if cframe_tab:
self.execute_script("document.cframe_tab = 1;")
self.open_new_window(switch_to=True)
self.open(url)
self.execute_script("document.cframe_tab = 1;")
else:
self.set_content(iframe_html)
if not self.execute_script("return document.cframe_swap;"):
self.execute_script("document.cframe_swap = 1;")
else:
self.execute_script("document.cframe_swap += 1;")
if self.recorder_mode and not self.__set_c_from_switch:
time_stamp = self.execute_script("return Date.now();")
action = ["s_c_f", o_frame, "", time_stamp]
self.__extra_actions.append(action)
def set_content_to_default(self, nested=True):
"""After using self.set_content_to_frame(), this reverts the page back.
If self.set_content_to_frame() hasn't been called here, only refreshes.
If "nested" is set to False when the content was set to nested iFrames,
then the control will only move above the last iFrame that was entered.
"""
self.__check_scope()
swap_cnt = self.execute_script("return document.cframe_swap;")
tab_sta = self.execute_script("return document.cframe_tab;")
if self.recorder_mode and not self.__set_c_from_switch:
time_stamp = self.execute_script("return Date.now();")
action = ["sk_op", "", "", time_stamp]
self.__extra_actions.append(action)
if nested:
if (
len(self.__page_sources) > 0
and (
(swap_cnt and int(swap_cnt) > 0)
or (tab_sta and int(tab_sta) > 0)
)
):
past_content = self.__page_sources[0]
past_url = past_content[0]
past_source = past_content[1]
past_tab = past_content[2]
current_tab = self.driver.current_window_handle
if not current_tab == past_tab:
if past_tab in self.driver.window_handles:
self.switch_to_window(past_tab)
url_of_past_tab = self.get_current_url()
if url_of_past_tab == past_url:
self.set_content(past_source)
else:
self.refresh_page()
else:
self.refresh_page()
self.execute_script("document.cframe_swap = 0;")
self.__page_sources = []
else:
just_refresh = False
if swap_cnt and int(swap_cnt) > 0 and len(self.__page_sources) > 0:
self.execute_script("document.cframe_swap -= 1;")
current_url = self.get_current_url()
past_content = self.__page_sources.pop()
past_url = past_content[0]
past_source = past_content[1]
if current_url == past_url:
self.set_content(past_source)
else:
just_refresh = True
elif tab_sta and int(tab_sta) > 0 and len(self.__page_sources) > 0:
past_content = self.__page_sources.pop()
past_tab = past_content[2]
if past_tab in self.driver.window_handles:
self.switch_to_window(past_tab)
else:
just_refresh = True
else:
just_refresh = True
if just_refresh:
self.refresh_page()
self.execute_script("document.cframe_swap = 0;")
self.__page_sources = []
if self.recorder_mode and not self.__set_c_from_switch:
time_stamp = self.execute_script("return Date.now();")
action = ["s_c_d", nested, "", time_stamp]
self.__extra_actions.append(action)
def open_new_window(self, switch_to=True):
""" Opens a new browser tab/window and switches to it by default. """
@ -2305,6 +2480,7 @@ class BaseCase(unittest.TestCase):
devtools=None,
remote_debug=None,
swiftshader=None,
ad_block_on=None,
block_images=None,
chromium_arg=None,
firefox_arg=None,
@ -2344,6 +2520,7 @@ class BaseCase(unittest.TestCase):
devtools - the option to open Chrome's DevTools on start (Chrome)
remote_debug - the option to enable Chrome's Remote Debugger
swiftshader - the option to use Chrome's swiftshader (Chrome-only)
ad_block_on - the option to block ads from loading (Chromium-only)
block_images - the option to block images from loading (Chrome)
chromium_arg - the option to add a Chromium arg to Chrome/Edge
firefox_arg - the option to add a Firefox arg to Firefox runs
@ -2429,6 +2606,8 @@ class BaseCase(unittest.TestCase):
remote_debug = self.remote_debug
if swiftshader is None:
swiftshader = self.swiftshader
if ad_block_on is None:
ad_block_on = self.ad_block_on
if block_images is None:
block_images = self.block_images
if chromium_arg is None:
@ -2489,6 +2668,7 @@ class BaseCase(unittest.TestCase):
devtools=devtools,
remote_debug=remote_debug,
swiftshader=swiftshader,
ad_block_on=ad_block_on,
block_images=block_images,
chromium_arg=chromium_arg,
firefox_arg=firefox_arg,
@ -2579,6 +2759,7 @@ class BaseCase(unittest.TestCase):
def switch_to_driver(self, driver):
"""Sets self.driver to the specified driver.
You may need this if using self.get_new_driver() in your code."""
self.__check_scope()
self.driver = driver
if self.driver in self.__driver_browser_map:
self.browser = self.__driver_browser_map[self.driver]
@ -2715,13 +2896,11 @@ class BaseCase(unittest.TestCase):
self.wait_for_angularjs(timeout=settings.MINI_TIMEOUT)
if self.js_checking_on:
self.assert_no_js_errors()
if self.ad_block_on:
# If the ad_block feature is enabled, then block ads for new URLs
if self.ad_block_on and (self.headless or not self.is_chromium()):
# For Chromium browsers in headed mode, the extension is used
current_url = self.get_current_url()
if not current_url == self.__last_page_load_url:
time.sleep(0.02)
self.ad_block()
time.sleep(0.02)
if self.is_element_present("iframe"):
time.sleep(0.1) # iframe ads take slightly longer to load
self.ad_block() # Do ad_block on slower-loading iframes
@ -2842,13 +3021,27 @@ class BaseCase(unittest.TestCase):
if action not in used_actions:
used_actions.append(action)
raw_actions.append(action)
for action in self.__extra_actions:
if action not in used_actions:
used_actions.append(action)
raw_actions.append(action)
for action in raw_actions:
# Use key because multiple actions can happen at the same timestamp
if self._reuse_session:
if int(action[3]) < int(self.__js_start_time):
continue
# Use key for sorting and preventing duplicates
key = str(action[3]) + "-" + str(action[0])
action_dict[key] = action
for key in sorted(action_dict):
# print(action_dict[key]) # For debugging purposes
srt_actions.append(action_dict[key])
for n in range(len(srt_actions)):
if (
(srt_actions[n][0] == "begin" or srt_actions[n][0] == "_url_")
and n > 0
and srt_actions[n-1][0] == "sk_op"
):
srt_actions[n][0] = "_skip"
for n in range(len(srt_actions)):
if (
(srt_actions[n][0] == "begin" or srt_actions[n][0] == "_url_")
@ -2880,6 +3073,25 @@ class BaseCase(unittest.TestCase):
url2 = url2[:-1]
if url1 == url2:
srt_actions[n-1][0] = "_skip"
for n in range(len(srt_actions)):
if (
(srt_actions[n][0] == "begin" or srt_actions[n][0] == "_url_")
and n > 0
and (
srt_actions[n-1][0] == "click"
or srt_actions[n-1][0] == "input"
)
and (int(srt_actions[n][3]) - int(srt_actions[n-1][3]) < 6500)
):
if srt_actions[n-1][0] == "click":
if (
srt_actions[n-1][1].startswith("input")
or srt_actions[n-1][1].startswith("button")
):
srt_actions[n][0] = "f_url"
elif srt_actions[n-1][0] == "input":
if srt_actions[n-1][2].endswith("\n"):
srt_actions[n][0] = "f_url"
for n in range(len(srt_actions)):
cleaned_actions.append(srt_actions[n])
for action in srt_actions:
@ -2888,94 +3100,172 @@ class BaseCase(unittest.TestCase):
elif action[0] == "f_url":
sb_actions.append('self.open_if_not_url("%s")' % action[2])
elif action[0] == "click":
method = "click"
if '"' not in action[1]:
sb_actions.append('self.click("%s")' % action[1])
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.click('%s')" % action[1])
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "input":
method = "type"
text = action[2].replace("\n", "\\n")
if '"' not in action[1] and '"' not in text:
sb_actions.append('self.type("%s", "%s")' % (
action[1], text))
sb_actions.append('self.%s("%s", "%s")' % (
method, action[1], text))
elif '"' not in action[1] and '"' in text:
sb_actions.append('self.type("%s", \'%s\')' % (
action[1], text))
sb_actions.append('self.%s("%s", \'%s\')' % (
method, action[1], text))
elif '"' in action[1] and '"' not in text:
sb_actions.append('self.type(\'%s\', "%s")' % (
action[1], text))
sb_actions.append('self.%s(\'%s\', "%s")' % (
method, action[1], text))
elif '"' in action[1] and '"' in text:
sb_actions.append("self.type('%s', '%s')" % (
action[1], text))
sb_actions.append("self.%s('%s', '%s')" % (
method, action[1], text))
elif action[0] == "h_clk":
method = "hover_and_click"
if '"' not in action[1] and '"' not in action[2]:
sb_actions.append('self.hover_and_click("%s", "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", "%s")' % (
method, action[1], action[2]))
elif '"' not in action[1] and '"' in action[2]:
sb_actions.append('self.hover_and_click("%s", \'%s\')' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", \'%s\')' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' not in action[2]:
sb_actions.append('self.hover_and_click(\'%s\', "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s(\'%s\', "%s")' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' in action[2]:
sb_actions.append("self.hover_and_click('%s', '%s')" % (
action[1], action[2]))
sb_actions.append("self.%s('%s', '%s')" % (
method, action[1], action[2]))
elif action[0] == "ddrop":
method = "drag_and_drop"
if '"' not in action[1] and '"' not in action[2]:
sb_actions.append('self.drag_and_drop("%s", "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", "%s")' % (
method, action[1], action[2]))
elif '"' not in action[1] and '"' in action[2]:
sb_actions.append('self.drag_and_drop("%s", \'%s\')' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", \'%s\')' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' not in action[2]:
sb_actions.append('self.drag_and_drop(\'%s\', "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s(\'%s\', "%s")' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' in action[2]:
sb_actions.append("self.drag_and_drop('%s', '%s')" % (
action[1], action[2]))
sb_actions.append("self.%s('%s', '%s')" % (
method, action[1], action[2]))
elif action[0] == "s_opt":
method = "select_option_by_text"
if '"' not in action[1] and '"' not in action[2]:
sb_actions.append(
'self.select_option_by_text("%s", "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", "%s")' % (
method, action[1], action[2]))
elif '"' not in action[1] and '"' in action[2]:
sb_actions.append(
'self.select_option_by_text("%s", \'%s\')' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", \'%s\')' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' not in action[2]:
sb_actions.append(
'self.select_option_by_text(\'%s\', "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s(\'%s\', "%s")' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' in action[2]:
sb_actions.append(
"self.select_option_by_text('%s', '%s')" % (
action[1], action[2]))
sb_actions.append("self.%s('%s', '%s')" % (
method, action[1], action[2]))
elif action[0] == "set_v":
method = "set_value"
if '"' not in action[1] and '"' not in action[2]:
sb_actions.append('self.set_value("%s", "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", "%s")' % (
method, action[1], action[2]))
elif '"' not in action[1] and '"' in action[2]:
sb_actions.append('self.set_value("%s", \'%s\')' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", \'%s\')' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' not in action[2]:
sb_actions.append('self.set_value(\'%s\', "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s(\'%s\', "%s")' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' in action[2]:
sb_actions.append("self.set_value('%s', '%s')" % (
action[1], action[2]))
sb_actions.append("self.%s('%s', '%s')" % (
method, action[1], action[2]))
elif action[0] == "cho_f":
method = "choose_file"
action[2] = action[2].replace("\\", "\\\\")
if '"' not in action[1] and '"' not in action[2]:
sb_actions.append('self.choose_file("%s", "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", "%s")' % (
method, action[1], action[2]))
elif '"' not in action[1] and '"' in action[2]:
sb_actions.append('self.choose_file("%s", \'%s\')' % (
action[1], action[2]))
sb_actions.append('self.%s("%s", \'%s\')' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' not in action[2]:
sb_actions.append('self.choose_file(\'%s\', "%s")' % (
action[1], action[2]))
sb_actions.append('self.%s(\'%s\', "%s")' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' in action[2]:
sb_actions.append("self.choose_file('%s', '%s')" % (
action[1], action[2]))
sb_actions.append("self.%s('%s', '%s')" % (
method, action[1], action[2]))
elif action[0] == "sw_fr":
method = "switch_to_frame"
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "sw_dc":
sb_actions.append("self.switch_to_default_content()")
elif action[0] == "s_c_f":
method = "set_content_to_frame"
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "s_c_d":
method = "set_content_to_default"
nested = action[1]
if nested:
sb_actions.append("self.%s()" % method)
else:
sb_actions.append("self.%s(nested=False)" % method)
elif action[0] == "as_el":
method = "assert_element"
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "as_ep":
method = "assert_element_present"
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "asenv":
method = "assert_element_not_visible"
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "as_lt":
method = "assert_link_text"
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "as_ti":
method = "assert_title"
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (method, action[1]))
elif action[0] == "as_te" or action[0] == "as_et":
method = "assert_text"
if action[0] == "as_et":
method = "assert_exact_text"
if action[2] != "html":
if '"' not in action[1] and '"' not in action[2]:
sb_actions.append('self.%s("%s", "%s")' % (
method, action[1], action[2]))
elif '"' not in action[1] and '"' in action[2]:
sb_actions.append('self.%s("%s", \'%s\')' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' not in action[2]:
sb_actions.append('self.%s(\'%s\', "%s")' % (
method, action[1], action[2]))
elif '"' in action[1] and '"' in action[2]:
sb_actions.append("self.%s('%s', '%s')" % (
method, action[1], action[2]))
else:
if '"' not in action[1]:
sb_actions.append('self.%s("%s")' % (
method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (
method, action[1]))
elif action[0] == "c_box":
cb_method = "check_if_unchecked"
if action[2] == "no":
@ -2984,11 +3274,19 @@ class BaseCase(unittest.TestCase):
sb_actions.append('self.%s("%s")' % (cb_method, action[1]))
else:
sb_actions.append("self.%s('%s')" % (cb_method, action[1]))
filename = self.__get_filename()
new_file = False
data = []
data.append("from seleniumbase import BaseCase")
data.append("")
data.append("")
data.append("class %s(BaseCase):" % self.__class__.__name__)
if filename not in sb_config._recorded_actions:
new_file = True
sb_config._recorded_actions[filename] = []
data.append("from seleniumbase import BaseCase")
data.append("")
data.append("")
data.append("class %s(BaseCase):" % self.__class__.__name__)
else:
data = sb_config._recorded_actions[filename]
data.append(" def %s(self):" % self._testMethodName)
if len(sb_actions) > 0:
for action in sb_actions:
@ -2996,6 +3294,7 @@ class BaseCase(unittest.TestCase):
else:
data.append(" pass")
data.append("")
sb_config._recorded_actions[filename] = data
recordings_folder = constants.Recordings.SAVED_FOLDER
if recordings_folder.endswith("/"):
@ -3011,7 +3310,9 @@ class BaseCase(unittest.TestCase):
out_file = codecs.open(file_path, "w+", "utf-8")
out_file.writelines("\r\n".join(data))
out_file.close()
rec_message = ">>> RECORDING saved to: "
rec_message = ">>> RECORDING SAVED as: "
if not new_file:
rec_message = ">>> RECORDING ADDED to: "
star_len = len(rec_message) + len(file_path)
try:
terminal_size = os.get_terminal_size().columns
@ -4241,6 +4542,15 @@ class BaseCase(unittest.TestCase):
a_t = SD.translate_assert_title(self._language)
messenger_post = "%s: {%s}" % (a_t, title)
self.__highlight_with_assert_success(messenger_post, "html")
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
action = ["as_ti", title, "", time_stamp]
self.__extra_actions.append(action)
return True
def assert_no_js_errors(self):
"""Asserts that there are no JavaScript "SEVERE"-level page errors.
@ -7746,6 +8056,14 @@ class BaseCase(unittest.TestCase):
self.__assert_shadow_element_present(selector)
return True
self.wait_for_element_present(selector, by=by, timeout=timeout)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
action = ["as_ep", selector, "", time_stamp]
self.__extra_actions.append(action)
return True
def assert_elements_present(self, *args, **kwargs):
@ -7773,9 +8091,10 @@ class BaseCase(unittest.TestCase):
if type(selector) is str:
selectors.append(selector)
elif type(selector) is list:
for a_selector in selector:
if type(a_selector) is str:
selectors.append(a_selector)
selectors_list = selector
for selector in selectors_list:
if type(selector) is str:
selectors.append(selector)
else:
raise Exception('Unknown kwarg: "%s"!' % kwarg)
if not timeout:
@ -7833,6 +8152,14 @@ class BaseCase(unittest.TestCase):
a_t = SD.translate_assert(self._language)
messenger_post = "%s %s: %s" % (a_t, by.upper(), selector)
self.__highlight_with_assert_success(messenger_post, selector, by)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
action = ["as_el", selector, "", time_stamp]
self.__extra_actions.append(action)
return True
def assert_element_visible(
@ -7871,9 +8198,10 @@ class BaseCase(unittest.TestCase):
if type(selector) is str:
selectors.append(selector)
elif type(selector) is list:
for a_selector in selector:
if type(a_selector) is str:
selectors.append(a_selector)
selectors_list = selector
for selector in selectors_list:
if type(selector) is str:
selectors.append(selector)
else:
raise Exception('Unknown kwarg: "%s"!' % kwarg)
if not timeout:
@ -8012,6 +8340,14 @@ class BaseCase(unittest.TestCase):
selector,
)
self.__highlight_with_assert_success(messenger_post, selector, by)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
action = ["as_te", text, selector, time_stamp]
self.__extra_actions.append(action)
return True
def assert_exact_text(
@ -8050,6 +8386,14 @@ class BaseCase(unittest.TestCase):
selector,
)
self.__highlight_with_assert_success(messenger_post, selector, by)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
action = ["as_et", text, selector, time_stamp]
self.__extra_actions.append(action)
return True
############
@ -8152,6 +8496,14 @@ class BaseCase(unittest.TestCase):
self.__highlight_with_assert_success(
messenger_post, link_text, by=By.LINK_TEXT
)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
action = ["as_lt", link_text, "", time_stamp]
self.__extra_actions.append(action)
return True
def wait_for_partial_link_text(self, partial_link_text, timeout=None):
@ -8265,6 +8617,14 @@ class BaseCase(unittest.TestCase):
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
timeout = self.__get_new_timeout(timeout)
self.wait_for_element_not_visible(selector, by=by, timeout=timeout)
if self.recorder_mode:
url = self.get_current_url()
if url and len(url) > 0:
if ("http:") in url or ("https:") in url or ("file:") in url:
if self.get_session_storage_item("pause_recorder") == "no":
time_stamp = self.execute_script("return Date.now();")
action = ["asenv", selector, "", time_stamp]
self.__extra_actions.append(action)
return True
############
@ -9468,6 +9828,10 @@ class BaseCase(unittest.TestCase):
settings.SMALL_TIMEOUT = sb_config._SMALL_TIMEOUT
settings.LARGE_TIMEOUT = sb_config._LARGE_TIMEOUT
if not hasattr(sb_config, "_recorded_actions"):
# Only filled when Recorder Mode is enabled
sb_config._recorded_actions = {}
# Parse the settings file
if self.settings_file:
from seleniumbase.core import settings_parser
@ -9583,6 +9947,7 @@ class BaseCase(unittest.TestCase):
devtools=self.devtools,
remote_debug=self.remote_debug,
swiftshader=self.swiftshader,
ad_block_on=self.ad_block_on,
block_images=self.block_images,
chromium_arg=self.chromium_arg,
firefox_arg=self.firefox_arg,
@ -9615,6 +9980,11 @@ class BaseCase(unittest.TestCase):
# Call this once in case of multiple setUp() calls in the same test
self.__start_time_ms = sb_config.start_time_ms
# Set the JS start time for Recorder Mode if reusing the session.
# Use this to skip saving recorded actions from previous tests.
if self.recorder_mode and self._reuse_session:
self.__js_start_time = self.execute_script("return Date.now();")
def __set_last_page_screenshot(self):
"""self.__last_page_screenshot is only for pytest html report logs.
self.__last_page_screenshot_png is for all screenshot log files."""
@ -9853,6 +10223,16 @@ class BaseCase(unittest.TestCase):
test_id = full
return test_id
def __get_filename(self):
""" The filename of the current SeleniumBase test. (NOT Path) """
filename = None
if "PYTEST_CURRENT_TEST" in os.environ:
test_id = os.environ["PYTEST_CURRENT_TEST"].split(" ")[0]
filename = test_id.split("::")[0].split("/")[-1]
else:
filename = self.__class__.__module__.split(".")[-1] + ".py"
return filename
def __create_log_path_as_needed(self, test_logpath):
if not os.path.exists(test_logpath):
try:

View File

@ -107,6 +107,7 @@ var getBestSelector = function(el) {
non_id_attributes.push('aria-label');
non_id_attributes.push('ng-href');
non_id_attributes.push('href');
non_id_attributes.push('label');
non_id_attributes.push('value');
non_id_attributes.push('ng-model');
non_id_attributes.push('ng-if');
@ -125,18 +126,52 @@ var getBestSelector = function(el) {
}
child_count_by_attr[i] = ssOccurrences(selector_by_attr[i], child_sep);
}
tag_name = el.tagName.toLowerCase();
basic_tags = [];
basic_tags.push('h1');
basic_tags.push('input');
basic_tags.push('button');
basic_tags.push('textarea');
for (var i = 0; i < basic_tags.length; i++) {
tag_name = el.tagName.toLowerCase();
if (tag_name == basic_tags[i] &&
el == document.querySelector(basic_tags[i]))
{
return basic_tags[i];
}
}
contains_tags = [];
contains_tags.push('a');
contains_tags.push('b');
contains_tags.push('i');
contains_tags.push('td');
contains_tags.push('h1');
contains_tags.push('h2');
contains_tags.push('h3');
contains_tags.push('h4');
contains_tags.push('code');
contains_tags.push('button');
all_by_tag = [];
for (var i = 0; i < contains_tags.length; i++) {
if (tag_name == contains_tags[i] &&
el.innerText.trim().length > 1 &&
el.innerText.trim().length <= 64)
{
t_count = 0;
inner_text = el.innerText.trim();
all_by_tag[i] = document.querySelectorAll(contains_tags[i]);
for (var j = 0; j < all_by_tag[i].length; j++) {
if (all_by_tag[i][j].innerText.includes(inner_text))
{
t_count += 1;
}
}
if (t_count === 1 && !inner_text.includes('\n'))
{
inner_text = inner_text.replace("'", "\\'");
inner_text = inner_text.replace('"', '\\"');
return tag_name += ':contains("'+inner_text+'")';
}
}
}
best_selector = selector_by_id;
lowest_child_count = child_count_by_id;
for (var i = 0; i < non_id_attributes.length; i++) {
@ -211,7 +246,7 @@ reset_recorder_state();
document.body.addEventListener('click', function (event) {
// Do nothing here.
});
document.body.addEventListener('submit', function (event) {
document.body.addEventListener('formdata', function (event) {
if (typeof document.recorded_actions === 'undefined')
reset_recorder_state();
if (sessionStorage.getItem('pause_recorder') === 'yes')

View File

@ -886,6 +886,19 @@ def pytest_addoption(parser):
'\n (DO NOT combine "--forked" with "--rs"/"--reuse-session"!)\n'
)
# Recorder Mode does not support multi-threaded / multi-process runs.
if (
"--recorder" in sys_argv
or "--record" in sys_argv
or "--rec" in sys_argv
):
arg_join = " ".join(sys.argv)
if ("-n" in sys_argv) or (" -n=" in arg_join) or ("-c" in sys_argv):
raise Exception(
"\n\n Recorder Mode does NOT support multi-process mode (-n)!"
'\n (DO NOT combine "--recorder" with "-n NUM_PROCESSES"!)\n'
)
# As a shortcut, you can use "--edge" instead of "--browser=edge", etc,
# but you can only specify one default browser for tests. (Default: chrome)
browser_changes = 0

View File

@ -121,7 +121,7 @@ setup(
"typing-extensions>=3.10.0.2",
'setuptools>=44.1.1;python_version<"3.5"',
'setuptools>=50.3.2;python_version>="3.5" and python_version<"3.6"',
'setuptools>=58.0.4;python_version>="3.6"',
'setuptools>=58.1.0;python_version>="3.6"',
'setuptools-scm==5.0.2;python_version<"3.6"',
'setuptools-scm>=6.3.2;python_version>="3.6"',
'tomli>=1.2.1;python_version>="3.6"',
@ -143,7 +143,7 @@ setup(
'chardet==3.0.4;python_version<"3.5"', # Stay in sync with "requests"
'chardet==4.0.0;python_version>="3.5"', # Stay in sync with "requests"
'charset-normalizer==2.0.6;python_version>="3.5"', # Sync "requests"
"urllib3==1.26.6", # Must stay in sync with "requests"
"urllib3==1.26.7", # Must stay in sync with "requests"
'requests==2.26.0;python_version<"3.5"',
'requests==2.25.1;python_version>="3.5" and python_version<"3.6"',
'requests==2.26.0;python_version>="3.6"',
@ -199,15 +199,15 @@ setup(
'ipython==5.10.0;python_version<"3.5"',
'ipython==7.9.0;python_version>="3.5" and python_version<"3.6"',
'ipython==7.16.1;python_version>="3.6" and python_version<"3.7"',
'ipython==7.27.0;python_version>="3.7"', # Requires matplotlib-inline
'ipython==7.28.0;python_version>="3.7"', # Requires matplotlib-inline
'matplotlib-inline==0.1.3;python_version>="3.7"', # ipython needs this
"colorama==0.4.4",
'platformdirs==2.0.2;python_version<"3.6"',
'platformdirs==2.3.0;python_version>="3.6"',
'platformdirs==2.4.0;python_version>="3.6"',
'pathlib2==2.3.5;python_version<"3.5"', # Sync with "virtualenv"
'importlib-metadata==2.0.0;python_version<"3.5"',
'importlib-metadata==2.1.1;python_version>="3.5" and python_version<"3.6"', # noqa: E501
"virtualenv>=20.8.0", # Sync with importlib-metadata and pathlib2
"virtualenv>=20.8.1", # Sync with importlib-metadata and pathlib2
'pymysql==0.10.1;python_version<"3.6"',
'pymysql==1.0.2;python_version>="3.6"',
"pyotp==2.6.0",
@ -217,7 +217,7 @@ setup(
'Pillow==6.2.2;python_version<"3.5"',
'Pillow==7.2.0;python_version>="3.5" and python_version<"3.6"',
'Pillow==8.3.2;python_version>="3.6"',
'rich==10.10.0;python_version>="3.6" and python_version<"4.0"',
'rich==10.11.0;python_version>="3.6" and python_version<"4.0"',
'tornado==5.1.1;python_version<"3.5"',
'tornado==6.1;python_version>="3.5"',
'pdfminer.six==20191110;python_version<"3.5"',