Merge pull request #992 from seleniumbase/recorder-mode-updates-and-more

Recorder Mode updates and more
This commit is contained in:
Michael Mintz 2021-09-29 03:35:38 -04:00 committed by GitHub
commit 4e973b7098
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 328 additions and 205 deletions

View File

@ -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>
--------

View File

@ -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"

View File

@ -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).)

View File

@ -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"]')

View File

@ -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

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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"

View File

@ -1,2 +1,2 @@
# seleniumbase package
__version__ = "1.66.3"
__version__ = "1.66.4"

View File

@ -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",
]

View File

@ -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.

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -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()

View File

@ -388,6 +388,7 @@ class SeleniumBrowser(Plugin):
every page load.""",
)
parser.add_option(
"--adblock",
"--ad_block",
"--ad-block",
"--block_ads",

View File

@ -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"',