Merge pull request #992 from seleniumbase/recorder-mode-updates-and-more
Recorder Mode updates and more
This commit is contained in:
commit
4e973b7098
|
@ -47,10 +47,12 @@
|
|||
</p>
|
||||
|
||||
<p align="left">
|
||||
✅ Smart-waiting code prevents flaky tests.<br />
|
||||
✅ Can run on <a href="https://seleniumbase.io/integrations/github/workflows/ReadMe/">GHA</a>, <a href="https://seleniumbase.io/integrations/google_cloud/ReadMe/">GCP</a>, <a href="https://seleniumbase.io/integrations/azure/jenkins/ReadMe/">Azure</a>, & <a href="https://seleniumbase.io/integrations/docker/ReadMe/">Docker</a>.<br />
|
||||
✅ Get set up in minutes. Deploy anywhere.<br />
|
||||
✅ Build reliable tests with a <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/method_summary.md">complete API</a>.<br />
|
||||
✅ Learn from 100s of real-world <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/ReadMe.md">examples</a>.<br />
|
||||
✅ Includes <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md">dashboards and reporting tools</a>.<br />
|
||||
✅ Supports Chromium, Firefox, IE, & Safari.<br />
|
||||
✅ Includes <a href="https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md">dashboards and reporting tools</a>.
|
||||
✅ Can run on <a href="https://seleniumbase.io/integrations/github/workflows/ReadMe/">GHA</a>, <a href="https://seleniumbase.io/integrations/google_cloud/ReadMe/">GCP</a>, <a href="https://seleniumbase.io/integrations/azure/jenkins/ReadMe/">Azure</a>, & <a href="https://seleniumbase.io/integrations/docker/ReadMe/">Docker</a>.<br />
|
||||
</p>
|
||||
|
||||
--------
|
||||
|
|
|
@ -11,7 +11,7 @@ keyring==23.2.1;python_version>="3.6"
|
|||
pkginfo==1.7.1;python_version>="3.6"
|
||||
Jinja2==3.0.1;python_version>="3.6"
|
||||
click==8.0.1;python_version>="3.6"
|
||||
zipp==3.5.0;python_version>="3.6"
|
||||
zipp==3.5.1;python_version>="3.6"
|
||||
readme-renderer==29.0
|
||||
pymdown-extensions==8.2;python_version>="3.6"
|
||||
importlib-metadata==4.8.1;python_version>="3.6"
|
||||
|
|
|
@ -7,9 +7,10 @@
|
|||
|
||||
* <b>SeleniumBase</b> tests are run with <b>pytest</b>.
|
||||
* Chrome is the default browser if not specified.
|
||||
* Example tests are located in: <b>[SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples)</b>.
|
||||
* During test failures, logs and screenshots from the latest test run are saved to the ``latest_logs/`` folder.
|
||||
* Tests can be structured using [17 unique syntax formats](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md).
|
||||
* Tests are structured using [17 unique syntax formats](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/syntax_formats.md).
|
||||
* Logs from test failures are saved to ``./latest_logs/``.
|
||||
* Tests can be run with [multiple command-line options](https://github.com/seleniumbase/SeleniumBase/blob/master/help_docs/customizing_test_runs.md).
|
||||
* Example tests are found in: **[SeleniumBase/examples/](https://github.com/seleniumbase/SeleniumBase/tree/master/examples)**.
|
||||
|
||||
(NOTE: Some example tests fail on purpose to demonstrate [logging features](https://github.com/seleniumbase/SeleniumBase/blob/master/examples/example_logs/ReadMe.md).)
|
||||
|
||||
|
|
|
@ -12,18 +12,16 @@ class HackingTests(BaseCase):
|
|||
self.assert_element('input[title="Search"]')
|
||||
self.set_attribute('[action="/search"]', "action", "//bing.com/search")
|
||||
self.set_attributes('[value="Google Search"]', "value", "Bing Search")
|
||||
self.type('input[title="Search"]', "SeleniumBase GitHub")
|
||||
self.type('input[title="Search"]', "SeleniumBase GitHub Docs Install")
|
||||
self.sleep(0.5)
|
||||
self.js_click('[value="Bing Search"]')
|
||||
self.highlight("h1.b_logo")
|
||||
help_docs_install_link = 'a[href*="seleniumbase.io/help_docs/install"]'
|
||||
if self.is_element_visible(help_docs_install_link):
|
||||
self.highlight_click(help_docs_install_link)
|
||||
self.switch_to_newest_window()
|
||||
self.assert_text("Install SeleniumBase", "h1")
|
||||
self.click_link_text("GitHub branch")
|
||||
self.highlight_click('a[href*="github.com/seleniumbase/SeleniumBase"]')
|
||||
self.switch_to_newest_window()
|
||||
self.assert_element('[href="/seleniumbase/SeleniumBase"]')
|
||||
self.assert_true("seleniumbase/SeleniumBase" in self.get_current_url())
|
||||
self.click('a[title="examples"]')
|
||||
|
|
|
@ -18,7 +18,8 @@ python_files = test_*.py *_test.py *_tests.py *_suite.py *_test_*.py
|
|||
python_classes = Test* *Test* *Test *Tests *Suite
|
||||
python_functions = test_*
|
||||
|
||||
# Here are the pytest markers used in the example tests:
|
||||
# Here are some common pytest markers:
|
||||
# (Some are used in the example tests.)
|
||||
# (pytest v4.5.0 and newer requires marker registration to prevent warnings.)
|
||||
# (Future versions of pytest may turn those marker warnings into errors.)
|
||||
markers =
|
||||
|
@ -32,7 +33,11 @@ markers =
|
|||
offline: custom marker
|
||||
develop: custom marker
|
||||
qa: custom marker
|
||||
ci: custom marker
|
||||
e2e: custom marker
|
||||
ready: custom marker
|
||||
smoke: custom marker
|
||||
deploy: custom marker
|
||||
active: custom marker
|
||||
master: custom marker
|
||||
release: custom marker
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[<img src="https://seleniumbase.io/cdn/img/super_logo_sb.png" title="SeleniumBase" width="290">](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" /> JS Package Manager</h2>
|
||||
|
||||
|
@ -90,6 +90,8 @@ def add_css_link(driver, css_link):
|
|||
|
||||
--------
|
||||
|
||||
[<img src="https://seleniumbase.io/cdn/img/super_logo_sb.png" title="SeleniumBase" width="220">](https://github.com/seleniumbase/SeleniumBase/)
|
||||
|
||||
<div>To learn more about SeleniumBase, check out the Docs Site:</div>
|
||||
<a href="https://seleniumbase.io">
|
||||
<img src="https://img.shields.io/badge/docs-%20%20SeleniumBase.io-11BBDD.svg" alt="SeleniumBase.io Docs" /></a>
|
||||
|
|
|
@ -224,9 +224,9 @@ self.switch_to_driver(driver)
|
|||
|
||||
self.switch_to_default_driver()
|
||||
|
||||
self.save_screenshot(name, folder=None)
|
||||
self.save_screenshot(name, folder=None, selector=None, by=By.CSS_SELECTOR)
|
||||
|
||||
self.save_screenshot_to_logs(name=None)
|
||||
self.save_screenshot_to_logs(name=None, selector=None, by=By.CSS_SELECTOR)
|
||||
|
||||
self.save_page_source(name, folder=None)
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
<h2><img src="https://seleniumbase.io/img/logo6.png" title="SeleniumBase" width="32" /> Recorder Mode</h2>
|
||||
|
||||
🔴 SeleniumBase <b>Recorder Mode</b> gives you the power to create automation scripts from manual browser actions.<br>(<i>Only Chromium browsers such as Chrome and Edge are supported.</i>)
|
||||
🔴 <b>SeleniumBase Recorder Mode</b> lets you record & export browser actions into automation scripts.<br>
|
||||
|
||||
<img src="https://seleniumbase.io/cdn/img/sb_recorder_notification.png" title="SeleniumBase" width="380">
|
||||
|
||||
🔴 To activate Recorder Mode, add ``--recorder`` to your ``pytest`` run command when running an existing test: (Also add ``-s`` to allow breakpoints, unless you already have a ``pytest.ini`` file present with ``--capture=no`` in it.)
|
||||
🔴 To activate Recorder Mode, add ``--recorder`` to your ``pytest`` run command when running an existing test on Chrome or Edge. (Also add ``-s`` to allow breakpoints, unless you already have a ``pytest.ini`` file present with ``addopts = --capture=no`` in it.)
|
||||
|
||||
```bash
|
||||
pytest TEST_NAME.py --recorder -s
|
||||
|
@ -60,6 +60,8 @@ class RecorderTest(BaseCase):
|
|||
|
||||
<p>🔴 SeleniumBase <code>1.66.3</code> improves the algorithm for generating efficient selectors. Additionally, single and double quotes in selectors will now be properly escaped with backslashes as needed.</p>
|
||||
|
||||
<p>🔴 SeleniumBase <code>1.66.4</code> adds better error-handling when using The Recorder, as well as a few other improvements.</p>
|
||||
|
||||
--------
|
||||
|
||||
<div>To learn more about SeleniumBase, check out the Docs Site:</div>
|
||||
|
|
|
@ -36,7 +36,7 @@ msedge-selenium-tools==3.141.3
|
|||
more-itertools==5.0.0;python_version<"3.5"
|
||||
more-itertools==8.10.0;python_version>="3.5"
|
||||
cssselect==1.1.0
|
||||
filelock==3.0.12
|
||||
filelock==3.1.0
|
||||
fasteners==0.16;python_version<"3.5"
|
||||
fasteners==0.16.3;python_version>="3.5"
|
||||
execnet==1.9.0
|
||||
|
@ -69,8 +69,6 @@ beautifulsoup4==4.10.0;python_version>="3.5"
|
|||
cryptography==2.9.2;python_version<"3.5"
|
||||
cryptography==3.2.1;python_version>="3.5" and python_version<"3.6"
|
||||
cryptography==3.4.8;python_version>="3.6"
|
||||
pyopenssl==19.1.0;python_version<"3.5"
|
||||
pyopenssl==20.0.1;python_version>="3.5"
|
||||
pygments==2.5.2;python_version<"3.5"
|
||||
pygments==2.10.0;python_version>="3.5"
|
||||
traitlets==4.3.3;python_version<"3.7"
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
# seleniumbase package
|
||||
__version__ = "1.66.3"
|
||||
__version__ = "1.66.4"
|
||||
|
|
|
@ -69,7 +69,7 @@ AD_BLOCK_LIST = [
|
|||
"[data-google-av-adk]",
|
||||
"[data-google-query-id]",
|
||||
'[data-ylk*="sponsored_cluster"]',
|
||||
'[data-google-av-cxn*="pagead"]',
|
||||
"[data-google-av-cxn]",
|
||||
"[data-ad-client]",
|
||||
"[data-ad-slot]",
|
||||
'[href*="doubleclick"]',
|
||||
|
@ -108,4 +108,5 @@ AD_BLOCK_LIST = [
|
|||
'link[href*="/adservice."]',
|
||||
"section.dianomi-ad",
|
||||
"ytd-promoted-video-renderer",
|
||||
"ytd-video-masthead-ad-v3-renderer",
|
||||
]
|
||||
|
|
|
@ -38,6 +38,15 @@ REPORT_ARCHIVE_DIR = "archived_reports"
|
|||
HTML_REPORT = "report.html"
|
||||
RESULTS_TABLE = "results_table.csv"
|
||||
|
||||
"""
|
||||
If True, switch to new tabs/windows automatically if a click opens a new one.
|
||||
(This switch only happens if the initial tab is still on same URL as before,
|
||||
which prevents a situation where a click opens up a new URL in the same tab,
|
||||
where a pop-up might open up a new tab on its own, leading to a double open.
|
||||
If False, the browser will stay on the current tab where the click happened.
|
||||
"""
|
||||
SWITCH_TO_NEW_TABS_ON_CLICK = True
|
||||
|
||||
"""
|
||||
This adds wait_for_ready_state_complete() after various browser actions.
|
||||
Setting this to True may improve reliability at the cost of speed.
|
||||
|
|
|
@ -44,8 +44,8 @@ urllib3.disable_warnings()
|
|||
DRIVER_DIR = os.path.dirname(os.path.realpath(drivers.__file__))
|
||||
LOCAL_PATH = "/usr/local/bin/" # On Mac and Linux systems
|
||||
DEFAULT_CHROMEDRIVER_VERSION = "2.44" # (Specify "latest" to get the latest)
|
||||
DEFAULT_GECKODRIVER_VERSION = "v0.29.1"
|
||||
DEFAULT_EDGEDRIVER_VERSION = "91.0.864.71" # (Looks for LATEST_STABLE first)
|
||||
DEFAULT_GECKODRIVER_VERSION = "v0.30.0"
|
||||
DEFAULT_EDGEDRIVER_VERSION = "93.0.961.52" # (Looks for LATEST_STABLE first)
|
||||
DEFAULT_OPERADRIVER_VERSION = "v.88.0.4324.104"
|
||||
|
||||
|
||||
|
|
|
@ -399,10 +399,11 @@ def _set_chrome_options(
|
|||
)
|
||||
chrome_options.add_experimental_option("useAutomationExtension", False)
|
||||
if (settings.DISABLE_CSP_ON_CHROME or disable_csp) and not headless:
|
||||
# Headless Chrome doesn't support extensions, which are required
|
||||
# for disabling the Content Security Policy on Chrome
|
||||
# Headless Chrome does not support extensions, which are required
|
||||
# for disabling the Content Security Policy on Chrome.
|
||||
chrome_options = _add_chrome_disable_csp_extension(chrome_options)
|
||||
if ad_block_on and not headless:
|
||||
# Headless Chrome does not support extensions.
|
||||
chrome_options = _add_chrome_ad_block_extension(chrome_options)
|
||||
if recorder_ext and not headless:
|
||||
chrome_options = _add_chrome_recorder_extension(chrome_options)
|
||||
|
@ -494,18 +495,9 @@ def _set_firefox_options(
|
|||
options.set_preference("extensions.update.autoUpdateDefault", False)
|
||||
options.set_preference("extensions.update.enabled", False)
|
||||
options.set_preference("extensions.update.silent", True)
|
||||
options.set_preference(
|
||||
"datareporting.healthreport.logging.consoleEnabled", False
|
||||
)
|
||||
options.set_preference("datareporting.healthreport.service.enabled", False)
|
||||
options.set_preference(
|
||||
"datareporting.healthreport.service.firstRun", False
|
||||
)
|
||||
options.set_preference("datareporting.healthreport.uploadEnabled", False)
|
||||
options.set_preference("datareporting.policy.dataSubmissionEnabled", False)
|
||||
options.set_preference(
|
||||
"datareporting.policy.dataSubmissionPolicyAccepted", False
|
||||
)
|
||||
options.set_preference("toolkit.telemetry.unified", False)
|
||||
if proxy_string:
|
||||
socks_proxy = False
|
||||
|
|
|
@ -316,10 +316,18 @@ 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()
|
||||
latest_window_count = len(self.driver.window_handles)
|
||||
if (
|
||||
latest_window_count > pre_window_count
|
||||
and (
|
||||
self.recorder_mode
|
||||
or (
|
||||
settings.SWITCH_TO_NEW_TABS_ON_CLICK
|
||||
and self.driver.current_url == pre_action_url
|
||||
)
|
||||
)
|
||||
):
|
||||
self.switch_to_newest_window()
|
||||
if settings.WAIT_FOR_RSC_ON_CLICKS:
|
||||
self.wait_for_ready_state_complete()
|
||||
if self.demo_mode:
|
||||
|
@ -2771,18 +2779,35 @@ class BaseCase(unittest.TestCase):
|
|||
if self.driver in self.__driver_browser_map:
|
||||
self.browser = self.__driver_browser_map[self.driver]
|
||||
|
||||
def save_screenshot(self, name, folder=None):
|
||||
"""Saves a screenshot of the current page.
|
||||
def save_screenshot(
|
||||
self, name, folder=None, selector=None, by=By.CSS_SELECTOR
|
||||
):
|
||||
"""
|
||||
Saves a screenshot of the current page.
|
||||
If no folder is specified, uses the folder where pytest was called.
|
||||
The screenshot will be in PNG format."""
|
||||
The screenshot will include the entire page unless a selector is given.
|
||||
If a provided selector is not found, then takes a full-page screenshot.
|
||||
If the folder provided doesn't exist, it will get created.
|
||||
The screenshot will be in PNG format: (*.png)
|
||||
"""
|
||||
self.wait_for_ready_state_complete()
|
||||
if selector and by:
|
||||
selector, by = self.__recalculate_selector(selector, by)
|
||||
if page_actions.is_element_present(self.driver, selector, by):
|
||||
return page_actions.save_screenshot(
|
||||
self.driver, name, folder, selector, by
|
||||
)
|
||||
return page_actions.save_screenshot(self.driver, name, folder)
|
||||
|
||||
def save_screenshot_to_logs(self, name=None):
|
||||
def save_screenshot_to_logs(
|
||||
self, name=None, selector=None, by=By.CSS_SELECTOR
|
||||
):
|
||||
"""Saves a screenshot of the current page to the "latest_logs" folder.
|
||||
Naming is automatic:
|
||||
If NO NAME provided: "_1_screenshot.png", "_2_screenshot.png", etc.
|
||||
If NAME IS provided, it becomes: "_1_name.png", "_2_name.png", etc.
|
||||
The screenshot will include the entire page unless a selector is given.
|
||||
If a provided selector is not found, then takes a full-page screenshot.
|
||||
(The last_page / failure screenshot is always "screenshot.png")
|
||||
The screenshot will be in PNG format."""
|
||||
self.wait_for_ready_state_complete()
|
||||
|
@ -2801,6 +2826,12 @@ class BaseCase(unittest.TestCase):
|
|||
if len(name) == 0:
|
||||
name = "screenshot"
|
||||
name = "%s%s.png" % (pre_name, name)
|
||||
if selector and by:
|
||||
selector, by = self.__recalculate_selector(selector, by)
|
||||
if page_actions.is_element_present(self.driver, selector, by):
|
||||
return page_actions.save_screenshot(
|
||||
self.driver, name, test_logpath, selector, by
|
||||
)
|
||||
return page_actions.save_screenshot(self.driver, name, test_logpath)
|
||||
|
||||
def save_page_source(self, name, folder=None):
|
||||
|
@ -2900,10 +2931,10 @@ class BaseCase(unittest.TestCase):
|
|||
# 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:
|
||||
self.ad_block()
|
||||
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
|
||||
if page_actions.is_element_present(
|
||||
self.driver, "iframe", By.CSS_SELECTOR
|
||||
):
|
||||
self.ad_block()
|
||||
self.__last_page_load_url = current_url
|
||||
return is_ready
|
||||
|
||||
|
@ -3046,7 +3077,10 @@ class BaseCase(unittest.TestCase):
|
|||
if (
|
||||
(srt_actions[n][0] == "begin" or srt_actions[n][0] == "_url_")
|
||||
and n > 0
|
||||
and srt_actions[n-1][0] == "click"
|
||||
and (
|
||||
srt_actions[n-1][0] == "click"
|
||||
or srt_actions[n-1][0] == "js_cl"
|
||||
)
|
||||
):
|
||||
url1 = srt_actions[n-1][2]
|
||||
if url1.endswith("/"):
|
||||
|
@ -3079,11 +3113,15 @@ class BaseCase(unittest.TestCase):
|
|||
and n > 0
|
||||
and (
|
||||
srt_actions[n-1][0] == "click"
|
||||
or srt_actions[n-1][0] == "js_cl"
|
||||
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][0] == "click"
|
||||
or srt_actions[n-1][0] == "js_cl"
|
||||
):
|
||||
if (
|
||||
srt_actions[n-1][1].startswith("input")
|
||||
or srt_actions[n-1][1].startswith("button")
|
||||
|
@ -3105,6 +3143,12 @@ class BaseCase(unittest.TestCase):
|
|||
sb_actions.append('self.%s("%s")' % (method, action[1]))
|
||||
else:
|
||||
sb_actions.append("self.%s('%s')" % (method, action[1]))
|
||||
elif action[0] == "js_cl":
|
||||
method = "js_click"
|
||||
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] == "input":
|
||||
method = "type"
|
||||
text = action[2].replace("\n", "\\n")
|
||||
|
@ -3679,6 +3723,23 @@ class BaseCase(unittest.TestCase):
|
|||
css_selector = self.convert_to_css_selector(selector, by=by)
|
||||
css_selector = re.escape(css_selector) # Add "\\" to special chars
|
||||
css_selector = self.__escape_quotes_if_needed(css_selector)
|
||||
action = None
|
||||
pre_action_url = self.driver.current_url
|
||||
pre_window_count = len(self.driver.window_handles)
|
||||
if self.recorder_mode:
|
||||
time_stamp = self.execute_script("return Date.now();")
|
||||
tag_name = None
|
||||
href = ""
|
||||
if ":contains\\(" not in css_selector:
|
||||
tag_name = self.execute_script(
|
||||
"return document.querySelector('%s').tagName.toLowerCase()"
|
||||
% css_selector
|
||||
)
|
||||
if tag_name == "a":
|
||||
href = self.execute_script(
|
||||
"return document.querySelector('%s').href" % css_selector
|
||||
)
|
||||
action = ["js_cl", selector, href, time_stamp]
|
||||
if not all_matches:
|
||||
if ":contains\\(" not in css_selector:
|
||||
self.__js_click(selector, by=by)
|
||||
|
@ -3691,6 +3752,20 @@ class BaseCase(unittest.TestCase):
|
|||
else:
|
||||
click_script = """jQuery('%s').click();""" % css_selector
|
||||
self.safe_execute_script(click_script)
|
||||
if self.recorder_mode and action:
|
||||
self.__extra_actions.append(action)
|
||||
latest_window_count = len(self.driver.window_handles)
|
||||
if (
|
||||
latest_window_count > pre_window_count
|
||||
and (
|
||||
self.recorder_mode
|
||||
or (
|
||||
settings.SWITCH_TO_NEW_TABS_ON_CLICK
|
||||
and self.driver.current_url == pre_action_url
|
||||
)
|
||||
)
|
||||
):
|
||||
self.switch_to_newest_window()
|
||||
self.wait_for_ready_state_complete()
|
||||
self.__demo_mode_pause_if_active()
|
||||
|
||||
|
@ -3783,7 +3858,7 @@ class BaseCase(unittest.TestCase):
|
|||
""" Block ads that appear on the current web page. """
|
||||
from seleniumbase.config import ad_block_list
|
||||
|
||||
self.__check_scope()
|
||||
self.__check_scope() # Using wait_for_RSC would cause an infinite loop
|
||||
for css_selector in ad_block_list.AD_BLOCK_LIST:
|
||||
css_selector = re.escape(css_selector) # Add "\\" to special chars
|
||||
css_selector = self.__escape_quotes_if_needed(css_selector)
|
||||
|
@ -9832,6 +9907,10 @@ class BaseCase(unittest.TestCase):
|
|||
# Only filled when Recorder Mode is enabled
|
||||
sb_config._recorded_actions = {}
|
||||
|
||||
if not hasattr(settings, "SWITCH_TO_NEW_TABS_ON_CLICK"):
|
||||
# If using an older settings file, set the new definitions manually
|
||||
settings.SWITCH_TO_NEW_TABS_ON_CLICK = True
|
||||
|
||||
# Parse the settings file
|
||||
if self.settings_file:
|
||||
from seleniumbase.core import settings_parser
|
||||
|
@ -9964,10 +10043,14 @@ class BaseCase(unittest.TestCase):
|
|||
if self._reuse_session:
|
||||
sb_config.shared_driver = self.driver
|
||||
|
||||
if self.browser in ["firefox", "ie", "safari"]:
|
||||
# Only Chromium-based browsers have the mobile emulator.
|
||||
if self.browser in ["firefox", "ie", "safari", "opera"]:
|
||||
# Only Chrome and Edge browsers have the mobile emulator.
|
||||
# Some actions such as hover-clicking are different on mobile.
|
||||
self.mobile_emulator = False
|
||||
# The Recorder Mode browser extension is only for Chrome/Edge.
|
||||
if self.recorder_mode:
|
||||
print('\n* The Recorder extension is for Chrome & Edge only!')
|
||||
self.recorder_mode = False
|
||||
|
||||
# Configure the test time limit (if used).
|
||||
self.set_time_limit(self.time_limit)
|
||||
|
@ -10569,7 +10652,6 @@ class BaseCase(unittest.TestCase):
|
|||
out_file.close()
|
||||
sb_config._dash_html = the_html
|
||||
if self._multithreaded:
|
||||
sb_config._dash_html_for_multithreading = the_html
|
||||
d_stats = (num_passed, num_failed, num_skipped, num_untested)
|
||||
_results = sb_config._results
|
||||
_display_id = sb_config._display_id
|
||||
|
|
|
@ -773,11 +773,16 @@ def find_visible_elements(driver, selector, by=By.CSS_SELECTOR):
|
|||
return v_elems
|
||||
|
||||
|
||||
def save_screenshot(driver, name, folder=None):
|
||||
def save_screenshot(
|
||||
driver, name, folder=None, selector=None, by=By.CSS_SELECTOR
|
||||
):
|
||||
"""
|
||||
Saves a screenshot to the current directory (or to a subfolder if provided)
|
||||
Saves a screenshot of the current page.
|
||||
If no folder is specified, uses the folder where pytest was called.
|
||||
The screenshot will include the entire page unless a selector is given.
|
||||
If a provided selector is not found, then takes a full-page screenshot.
|
||||
If the folder provided doesn't exist, it will get created.
|
||||
The screenshot will be in PNG format.
|
||||
The screenshot will be in PNG format: (*.png)
|
||||
"""
|
||||
if not name.endswith(".png"):
|
||||
name = name + ".png"
|
||||
|
@ -789,12 +794,18 @@ def save_screenshot(driver, name, folder=None):
|
|||
screenshot_path = "%s/%s" % (file_path, name)
|
||||
else:
|
||||
screenshot_path = name
|
||||
try:
|
||||
element = driver.find_element(by=By.TAG_NAME, value="body")
|
||||
element_png = element.screenshot_as_png
|
||||
with open(screenshot_path, "wb") as file:
|
||||
file.write(element_png)
|
||||
except Exception:
|
||||
if selector:
|
||||
try:
|
||||
element = driver.find_element(by=by, value=selector)
|
||||
element_png = element.screenshot_as_png
|
||||
with open(screenshot_path, "wb") as file:
|
||||
file.write(element_png)
|
||||
except Exception:
|
||||
if driver:
|
||||
driver.get_screenshot_as_file(screenshot_path)
|
||||
else:
|
||||
pass
|
||||
else:
|
||||
if driver:
|
||||
driver.get_screenshot_as_file(screenshot_path)
|
||||
else:
|
||||
|
|
|
@ -627,6 +627,7 @@ def pytest_addoption(parser):
|
|||
every page load.""",
|
||||
)
|
||||
parser.addoption(
|
||||
"--adblock",
|
||||
"--ad_block",
|
||||
"--ad-block",
|
||||
"--block_ads",
|
||||
|
@ -899,6 +900,19 @@ def pytest_addoption(parser):
|
|||
'\n (DO NOT combine "--recorder" with "-n NUM_PROCESSES"!)\n'
|
||||
)
|
||||
|
||||
# Recorder Mode does not support headless browser runs.
|
||||
# (Chromium does not allow extensions in Headless Mode)
|
||||
if (
|
||||
"--recorder" in sys_argv
|
||||
or "--record" in sys_argv
|
||||
or "--rec" in sys_argv
|
||||
):
|
||||
if ("--headless" in sys_argv):
|
||||
raise Exception(
|
||||
"\n\n Recorder Mode does NOT support Headless Mode!"
|
||||
'\n (DO NOT combine "--recorder" with "--headless"!)\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
|
||||
|
@ -1072,7 +1086,8 @@ def pytest_configure(config):
|
|||
sb_config._duration = {} # SBase Dashboard test duration
|
||||
sb_config._display_id = {} # SBase Dashboard display ID
|
||||
sb_config._d_t_log_path = {} # SBase Dashboard test log path
|
||||
sb_config._test_id = None # The SBase Dashboard test id
|
||||
sb_config._dash_html = None # SBase Dashboard HTML copy
|
||||
sb_config._test_id = None # SBase Dashboard test id
|
||||
sb_config._latest_display_id = None # The latest SBase display id
|
||||
sb_config._dashboard_initialized = False # Becomes True after init
|
||||
sb_config._has_exception = False # This becomes True if any test fails
|
||||
|
@ -1310,16 +1325,7 @@ def pytest_terminal_summary(terminalreporter):
|
|||
terminalreporter.write_sep("-", "LogPath: %s" % latest_logs_dir)
|
||||
|
||||
|
||||
def pytest_unconfigure():
|
||||
""" This runs after all tests have completed with pytest. """
|
||||
if (
|
||||
hasattr(sb_config, "_dash_html_for_multithreading")
|
||||
and sb_config._multithreaded
|
||||
):
|
||||
abs_path = os.path.abspath(".")
|
||||
dashboard_path = os.path.join(abs_path, "dashboard.html")
|
||||
with open(dashboard_path, "w", encoding="utf-8") as f:
|
||||
f.write(sb_config._dash_html)
|
||||
def _perform_pytest_unconfigure_():
|
||||
proxy_helper.remove_proxy_zip_if_present()
|
||||
if hasattr(sb_config, "reuse_session") and sb_config.reuse_session:
|
||||
# Close the shared browser session
|
||||
|
@ -1336,142 +1342,157 @@ def pytest_unconfigure():
|
|||
sb_config.log_path, sb_config.archive_logs
|
||||
)
|
||||
# Dashboard post-processing: Disable time-based refresh and stamp complete
|
||||
if sb_config._multithreaded and sb_config.dashboard:
|
||||
abs_path = os.path.abspath(".")
|
||||
dash_lock = constants.Dashboard.LOCKFILE
|
||||
dash_lock_path = os.path.join(abs_path, dash_lock)
|
||||
if os.path.exists(dash_lock_path):
|
||||
sb_config._only_unittest = False
|
||||
if hasattr(sb_config, "dashboard") and (
|
||||
sb_config.dashboard and not sb_config._only_unittest
|
||||
):
|
||||
if sb_config._multithreaded:
|
||||
import fasteners
|
||||
|
||||
dash_lock = fasteners.InterProcessLock(
|
||||
constants.Dashboard.LOCKFILE
|
||||
)
|
||||
stamp = ""
|
||||
if sb_config._dash_is_html_report:
|
||||
# (If the Dashboard URL is the same as the HTML Report URL:)
|
||||
# Have the html report refresh back to a dashboard on update
|
||||
stamp += (
|
||||
'\n<script type="text/javascript" src="%s">'
|
||||
"</script>" % constants.Dashboard.LIVE_JS
|
||||
)
|
||||
stamp += "\n<!--Test Run Complete-->"
|
||||
find_it = constants.Dashboard.META_REFRESH_HTML
|
||||
swap_with = "" # Stop refreshing the page after the run is done
|
||||
find_it_2 = "Awaiting results... (Refresh the page for updates)"
|
||||
swap_with_2 = (
|
||||
"Test Run ENDED: Some results UNREPORTED due to skipped tearDown()"
|
||||
if not hasattr(sb_config, "dashboard") or not sb_config.dashboard:
|
||||
# Done with "pytest_unconfigure" unless using the Dashboard
|
||||
return
|
||||
stamp = ""
|
||||
if sb_config._dash_is_html_report:
|
||||
# (If the Dashboard URL is the same as the HTML Report URL:)
|
||||
# Have the html report refresh back to a dashboard on update
|
||||
stamp += (
|
||||
'\n<script type="text/javascript" src="%s">'
|
||||
"</script>" % constants.Dashboard.LIVE_JS
|
||||
)
|
||||
find_it_3 = '<td class="col-result">Untested</td>'
|
||||
swap_with_3 = '<td class="col-result">Unreported</td>'
|
||||
find_it_4 = 'href="%s"' % constants.Dashboard.DASH_PIE_PNG_1
|
||||
swap_with_4 = 'href="%s"' % constants.Dashboard.DASH_PIE_PNG_2
|
||||
try:
|
||||
stamp += "\n<!--Test Run Complete-->"
|
||||
find_it = constants.Dashboard.META_REFRESH_HTML
|
||||
swap_with = "" # Stop refreshing the page after the run is done
|
||||
find_it_2 = "Awaiting results... (Refresh the page for updates)"
|
||||
swap_with_2 = (
|
||||
"Test Run ENDED: Some results UNREPORTED due to skipped tearDown()"
|
||||
)
|
||||
find_it_3 = '<td class="col-result">Untested</td>'
|
||||
swap_with_3 = '<td class="col-result">Unreported</td>'
|
||||
find_it_4 = 'href="%s"' % constants.Dashboard.DASH_PIE_PNG_1
|
||||
swap_with_4 = 'href="%s"' % constants.Dashboard.DASH_PIE_PNG_2
|
||||
try:
|
||||
abs_path = os.path.abspath(".")
|
||||
dashboard_path = os.path.join(abs_path, "dashboard.html")
|
||||
# Part 1: Finalizing the dashboard / integrating html report
|
||||
if os.path.exists(dashboard_path):
|
||||
the_html_d = None
|
||||
with open(dashboard_path, "r", encoding="utf-8") as f:
|
||||
the_html_d = f.read()
|
||||
if sb_config._multithreaded and "-c" in sys.argv:
|
||||
# Threads have "-c" in sys.argv, except for the last
|
||||
raise Exception('Break out of "try" block.')
|
||||
if sb_config._multithreaded:
|
||||
dash_lock.acquire()
|
||||
abs_path = os.path.abspath(".")
|
||||
dashboard_path = os.path.join(abs_path, "dashboard.html")
|
||||
# Part 1: Finalizing the dashboard / integrating html report
|
||||
if os.path.exists(dashboard_path):
|
||||
the_html_d = None
|
||||
with open(dashboard_path, "r", encoding="utf-8") as f:
|
||||
the_html_d = f.read()
|
||||
if sb_config._multithreaded and "-c" in sys.argv:
|
||||
# Threads have "-c" in sys.argv, except for the last
|
||||
raise Exception('Break out of "try" block.')
|
||||
if sb_config._multithreaded:
|
||||
dash_pie_loc = constants.Dashboard.DASH_PIE
|
||||
pie_path = os.path.join(abs_path, dash_pie_loc)
|
||||
if os.path.exists(pie_path):
|
||||
import json
|
||||
dash_pie_loc = constants.Dashboard.DASH_PIE
|
||||
pie_path = os.path.join(abs_path, dash_pie_loc)
|
||||
if os.path.exists(pie_path):
|
||||
import json
|
||||
|
||||
with open(pie_path, "r") as f:
|
||||
dash_pie = f.read().strip()
|
||||
sb_config._saved_dashboard_pie = json.loads(dash_pie)
|
||||
# If the test run doesn't complete by itself, stop refresh
|
||||
the_html_d = the_html_d.replace(find_it, swap_with)
|
||||
the_html_d = the_html_d.replace(find_it_2, swap_with_2)
|
||||
the_html_d = the_html_d.replace(find_it_3, swap_with_3)
|
||||
the_html_d = the_html_d.replace(find_it_4, swap_with_4)
|
||||
the_html_d += stamp
|
||||
if sb_config._dash_is_html_report and (
|
||||
sb_config._saved_dashboard_pie
|
||||
):
|
||||
the_html_d = the_html_d.replace(
|
||||
"<h1>dashboard.html</h1>",
|
||||
sb_config._saved_dashboard_pie,
|
||||
)
|
||||
the_html_d = the_html_d.replace(
|
||||
"</head>",
|
||||
'</head><link rel="shortcut icon" '
|
||||
'href="%s">' % constants.Dashboard.DASH_PIE_PNG_3,
|
||||
)
|
||||
the_html_d = the_html_d.replace(
|
||||
"<html>", '<html lang="en">'
|
||||
)
|
||||
the_html_d = the_html_d.replace(
|
||||
"<head>",
|
||||
'<head><meta http-equiv="Content-Type" '
|
||||
'content="text/html, charset=utf-8;">'
|
||||
'<meta name="viewport" content="shrink-to-fit=no">',
|
||||
)
|
||||
if sb_config._dash_final_summary:
|
||||
the_html_d += sb_config._dash_final_summary
|
||||
time.sleep(0.1) # Add time for "livejs" to detect changes
|
||||
with open(dashboard_path, "w", encoding="utf-8") as f:
|
||||
f.write(the_html_d) # Finalize the dashboard
|
||||
time.sleep(0.1) # Add time for "livejs" to detect changes
|
||||
the_html_d = the_html_d.replace(
|
||||
"</head>", "</head><!-- Dashboard Report Done -->"
|
||||
)
|
||||
with open(pie_path, "r") as f:
|
||||
dash_pie = f.read().strip()
|
||||
sb_config._saved_dashboard_pie = json.loads(dash_pie)
|
||||
# If the test run doesn't complete by itself, stop refresh
|
||||
the_html_d = the_html_d.replace(find_it, swap_with)
|
||||
the_html_d = the_html_d.replace(find_it_2, swap_with_2)
|
||||
the_html_d = the_html_d.replace(find_it_3, swap_with_3)
|
||||
the_html_d = the_html_d.replace(find_it_4, swap_with_4)
|
||||
the_html_d += stamp
|
||||
if sb_config._dash_is_html_report and (
|
||||
sb_config._saved_dashboard_pie
|
||||
):
|
||||
the_html_d = the_html_d.replace(
|
||||
"<h1>dashboard.html</h1>",
|
||||
sb_config._saved_dashboard_pie,
|
||||
)
|
||||
the_html_d = the_html_d.replace(
|
||||
"</head>",
|
||||
'</head><link rel="shortcut icon" '
|
||||
'href="%s">' % constants.Dashboard.DASH_PIE_PNG_3,
|
||||
)
|
||||
the_html_d = the_html_d.replace(
|
||||
"<html>", '<html lang="en">'
|
||||
)
|
||||
the_html_d = the_html_d.replace(
|
||||
"<head>",
|
||||
'<head><meta http-equiv="Content-Type" '
|
||||
'content="text/html, charset=utf-8;">'
|
||||
'<meta name="viewport" content="shrink-to-fit=no">',
|
||||
)
|
||||
if sb_config._dash_final_summary:
|
||||
the_html_d += sb_config._dash_final_summary
|
||||
time.sleep(0.1) # Add time for "livejs" to detect changes
|
||||
with open(dashboard_path, "w", encoding="utf-8") as f:
|
||||
f.write(the_html_d) # Finalize the dashboard
|
||||
# Part 2: Appending a pytest html report with dashboard data
|
||||
html_report_path = None
|
||||
if sb_config._html_report_name:
|
||||
html_report_path = os.path.join(
|
||||
abs_path, sb_config._html_report_name
|
||||
time.sleep(0.1) # Add time for "livejs" to detect changes
|
||||
the_html_d = the_html_d.replace(
|
||||
"</head>", "</head><!-- Dashboard Report Done -->"
|
||||
)
|
||||
with open(dashboard_path, "w", encoding="utf-8") as f:
|
||||
f.write(the_html_d) # Finalize the dashboard
|
||||
# Part 2: Appending a pytest html report with dashboard data
|
||||
html_report_path = None
|
||||
if sb_config._html_report_name:
|
||||
html_report_path = os.path.join(
|
||||
abs_path, sb_config._html_report_name
|
||||
)
|
||||
if (
|
||||
sb_config._using_html_report
|
||||
and html_report_path
|
||||
and os.path.exists(html_report_path)
|
||||
and not sb_config._dash_is_html_report
|
||||
):
|
||||
# Add the dashboard pie to the pytest html report
|
||||
the_html_r = None
|
||||
with open(html_report_path, "r", encoding="utf-8") as f:
|
||||
the_html_r = f.read()
|
||||
if sb_config._saved_dashboard_pie:
|
||||
h_r_name = sb_config._html_report_name
|
||||
if "/" in h_r_name and h_r_name.endswith(".html"):
|
||||
h_r_name = h_r_name.split("/")[-1]
|
||||
elif "\\" in h_r_name and h_r_name.endswith(".html"):
|
||||
h_r_name = h_r_name.split("\\")[-1]
|
||||
the_html_r = the_html_r.replace(
|
||||
"<h1>%s</h1>" % h_r_name,
|
||||
sb_config._saved_dashboard_pie,
|
||||
)
|
||||
if (
|
||||
sb_config._using_html_report
|
||||
and html_report_path
|
||||
and os.path.exists(html_report_path)
|
||||
and not sb_config._dash_is_html_report
|
||||
):
|
||||
# Add the dashboard pie to the pytest html report
|
||||
the_html_r = None
|
||||
with open(html_report_path, "r", encoding="utf-8") as f:
|
||||
the_html_r = f.read()
|
||||
if sb_config._saved_dashboard_pie:
|
||||
h_r_name = sb_config._html_report_name
|
||||
if "/" in h_r_name and h_r_name.endswith(".html"):
|
||||
h_r_name = h_r_name.split("/")[-1]
|
||||
elif "\\" in h_r_name and h_r_name.endswith(".html"):
|
||||
h_r_name = h_r_name.split("\\")[-1]
|
||||
the_html_r = the_html_r.replace(
|
||||
"<h1>%s</h1>" % h_r_name,
|
||||
sb_config._saved_dashboard_pie,
|
||||
)
|
||||
the_html_r = the_html_r.replace(
|
||||
"</head>",
|
||||
'</head><link rel="shortcut icon" href='
|
||||
'"%s">' % constants.Dashboard.DASH_PIE_PNG_3,
|
||||
)
|
||||
if sb_config._dash_final_summary:
|
||||
the_html_r += sb_config._dash_final_summary
|
||||
with open(html_report_path, "w", encoding="utf-8") as f:
|
||||
f.write(the_html_r) # Finalize the HTML report
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
if sb_config._multithreaded:
|
||||
dash_lock.release()
|
||||
the_html_r = the_html_r.replace(
|
||||
"</head>",
|
||||
'</head><link rel="shortcut icon" href='
|
||||
'"%s">' % constants.Dashboard.DASH_PIE_PNG_3,
|
||||
)
|
||||
if sb_config._dash_final_summary:
|
||||
the_html_r += sb_config._dash_final_summary
|
||||
with open(html_report_path, "w", encoding="utf-8") as f:
|
||||
f.write(the_html_r) # Finalize the HTML report
|
||||
except KeyboardInterrupt:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def pytest_unconfigure():
|
||||
""" This runs after all tests have completed with pytest. """
|
||||
if sb_config._multithreaded:
|
||||
import fasteners
|
||||
|
||||
dash_lock = fasteners.InterProcessLock(constants.Dashboard.LOCKFILE)
|
||||
if (
|
||||
hasattr(sb_config, "dashboard")
|
||||
and sb_config.dashboard
|
||||
and sb_config._dash_html
|
||||
):
|
||||
# Multi-threaded tests with the Dashboard
|
||||
abs_path = os.path.abspath(".")
|
||||
dash_lock_file = constants.Dashboard.LOCKFILE
|
||||
dash_lock_path = os.path.join(abs_path, dash_lock_file)
|
||||
if os.path.exists(dash_lock_path):
|
||||
sb_config._only_unittest = False
|
||||
dashboard_path = os.path.join(abs_path, "dashboard.html")
|
||||
with dash_lock:
|
||||
with open(dashboard_path, "w", encoding="utf-8") as f:
|
||||
f.write(sb_config._dash_html)
|
||||
_perform_pytest_unconfigure_()
|
||||
return
|
||||
with dash_lock:
|
||||
# Multi-threaded tests
|
||||
_perform_pytest_unconfigure_()
|
||||
return
|
||||
else:
|
||||
# Single-threaded tests
|
||||
_perform_pytest_unconfigure_()
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
|
|
|
@ -388,6 +388,7 @@ class SeleniumBrowser(Plugin):
|
|||
every page load.""",
|
||||
)
|
||||
parser.add_option(
|
||||
"--adblock",
|
||||
"--ad_block",
|
||||
"--ad-block",
|
||||
"--block_ads",
|
||||
|
|
4
setup.py
4
setup.py
|
@ -152,7 +152,7 @@ setup(
|
|||
'more-itertools==5.0.0;python_version<"3.5"',
|
||||
'more-itertools==8.10.0;python_version>="3.5"',
|
||||
"cssselect==1.1.0",
|
||||
"filelock==3.0.12", # Newer ones had issues
|
||||
"filelock==3.1.0",
|
||||
'fasteners==0.16;python_version<"3.5"',
|
||||
'fasteners==0.16.3;python_version>="3.5"',
|
||||
"execnet==1.9.0",
|
||||
|
@ -185,8 +185,6 @@ setup(
|
|||
'cryptography==2.9.2;python_version<"3.5"',
|
||||
'cryptography==3.2.1;python_version>="3.5" and python_version<"3.6"',
|
||||
'cryptography==3.4.8;python_version>="3.6"',
|
||||
'pyopenssl==19.1.0;python_version<"3.5"',
|
||||
'pyopenssl==20.0.1;python_version>="3.5"',
|
||||
'pygments==2.5.2;python_version<"3.5"',
|
||||
'pygments==2.10.0;python_version>="3.5"',
|
||||
'traitlets==4.3.3;python_version<"3.7"',
|
||||
|
|
Loading…
Reference in New Issue