diff --git a/README.md b/README.md
index 3364795f3fd..56f2a8a5abb 100755
--- a/README.md
+++ b/README.md
@@ -671,6 +671,7 @@ pytest test_coffee_cart.py --trace
--block-images # (Block images from loading during tests.)
--do-not-track # (Indicate to websites that you don't want to be tracked.)
--verify-delay=SECONDS # (The delay before MasterQA verification checks.)
+--ee | --esc-end # (Lets the user end the current test via the ESC key.)
--recorder # (Enables the Recorder for turning browser actions into code.)
--rec-behave # (Same as Recorder Mode, but also generates behave-gherkin.)
--rec-sleep # (If the Recorder is enabled, also records self.sleep calls.)
diff --git a/examples/raw_parameter_script.py b/examples/raw_parameter_script.py
index 89a5fcd3394..64fcec9a68c 100644
--- a/examples/raw_parameter_script.py
+++ b/examples/raw_parameter_script.py
@@ -76,6 +76,7 @@
sb._reuse_session = False
sb._crumbs = False
sb._final_debug = False
+ sb.esc_end = False
sb.use_wire = False
sb.enable_3d_apis = False
sb.window_size = None
diff --git a/help_docs/customizing_test_runs.md b/help_docs/customizing_test_runs.md
index 6f4af19cc72..c6ca5d9c269 100644
--- a/help_docs/customizing_test_runs.md
+++ b/help_docs/customizing_test_runs.md
@@ -164,6 +164,7 @@ pytest my_first_test.py --settings-file=custom_settings.py
--block-images # (Block images from loading during tests.)
--do-not-track # (Indicate to websites that you don't want to be tracked.)
--verify-delay=SECONDS # (The delay before MasterQA verification checks.)
+--ee | --esc-end # (Lets the user end the current test via the ESC key.)
--recorder # (Enables the Recorder for turning browser actions into code.)
--rec-behave # (Same as Recorder Mode, but also generates behave-gherkin.)
--rec-sleep # (If the Recorder is enabled, also records self.sleep calls.)
diff --git a/help_docs/method_summary.md b/help_docs/method_summary.md
index 2b746b4a84b..a577e33d0c9 100644
--- a/help_docs/method_summary.md
+++ b/help_docs/method_summary.md
@@ -1046,6 +1046,8 @@ driver.uc_open_with_tab(url) # (New tab with default reconnect_time)
driver.uc_open_with_reconnect(url, reconnect_time=None) # (New tab)
+driver.uc_open_with_disconnect(url) # Open in new tab + disconnect()
+
driver.reconnect(timeout) # disconnect() + sleep(timeout) + connect()
driver.disconnect() # Stops the webdriver service to prevent detection
diff --git a/help_docs/recorder_mode.md b/help_docs/recorder_mode.md
index 9b88c4b143f..e859097a151 100644
--- a/help_docs/recorder_mode.md
+++ b/help_docs/recorder_mode.md
@@ -121,6 +121,8 @@ pytest TEST_NAME.py --trace --rec -s
⏺️ Inside recorded tests, you might find the self.open_if_not_url(URL)
method, which opens the URL given if the browser is not currently on that page. SeleniumBase uses this method in recorded scripts when the Recorder detects that a browser action changed the current URL. This method prevents an unnecessary page load and shows what page the test visited after a browser action.
+⏺️ By launching the Recorder App with sbase recorder --ee
, you can end the recording by pressing {SHIFT
+ESC
} instead of the usual way of ending the recording by typing c
from a breakpoint()
and pressing Enter
. Those buttons don't need to be pressed at the same time, but SHIFT
must be pressed directly before ESC
.
+
--------
To learn more about SeleniumBase, check out the Docs Site:
diff --git a/help_docs/uc_mode.md b/help_docs/uc_mode.md
index 8c7f13b1634..1980d9fb56a 100644
--- a/help_docs/uc_mode.md
+++ b/help_docs/uc_mode.md
@@ -159,6 +159,8 @@ driver.uc_open_with_tab(url)
driver.uc_open_with_reconnect(url, reconnect_time=None)
+driver.uc_open_with_disconnect(url)
+
driver.reconnect(timeout)
driver.disconnect()
diff --git a/mkdocs_build/requirements.txt b/mkdocs_build/requirements.txt
index 6e3fe0f4227..97f1bf02efa 100644
--- a/mkdocs_build/requirements.txt
+++ b/mkdocs_build/requirements.txt
@@ -1,26 +1,26 @@
# mkdocs dependencies for generating the seleniumbase.io website
# Minimum Python version: 3.8 (for generating docs only)
-regex>=2024.4.28
+regex>=2024.5.10
pymdown-extensions>=10.8.1
-pipdeptree>=2.19.1
+pipdeptree>=2.20.0
python-dateutil>=2.8.2
Markdown==3.6
markdown2==2.4.13
MarkupSafe==2.1.5
-Jinja2==3.1.3
+Jinja2==3.1.4
click==8.1.7
ghp-import==2.1.0
watchdog==4.0.0
cairocffi==1.7.0
pathspec==0.12.1
-Babel==2.14.0
+Babel==2.15.0
paginate==0.5.6
lxml==5.2.1
pyquery==2.0.0
readtime==3.0.0
mkdocs==1.6.0
-mkdocs-material==9.5.21
+mkdocs-material==9.5.22
mkdocs-exclude-search==0.6.6
mkdocs-simple-hooks==0.1.5
mkdocs-material-extensions==1.3.1
diff --git a/requirements.txt b/requirements.txt
index 3f965d49744..85f5acba230 100755
--- a/requirements.txt
+++ b/requirements.txt
@@ -57,7 +57,8 @@ behave==1.2.6
soupsieve==2.4.1;python_version<"3.8"
soupsieve==2.5;python_version>="3.8"
beautifulsoup4==4.12.3
-pygments==2.17.2
+pygments==2.17.2;python_version<"3.8"
+pygments==2.18.0;python_version>="3.8"
pyreadline3==3.4.1;platform_system=="Windows"
tabcompleter==1.3.0
pdbp==1.5.0
@@ -72,7 +73,7 @@ rich==13.7.1
# ("pip install -r requirements.txt" also installs this, but "pip install -e ." won't.)
coverage==7.2.7;python_version<"3.8"
-coverage>=7.5.0;python_version>="3.8"
+coverage>=7.5.1;python_version>="3.8"
pytest-cov==4.1.0;python_version<"3.8"
pytest-cov>=5.0.0;python_version>="3.8"
flake8==5.0.4;python_version<"3.9"
diff --git a/seleniumbase/__version__.py b/seleniumbase/__version__.py
index 8d93f2cb5e8..9cbf6d3308a 100755
--- a/seleniumbase/__version__.py
+++ b/seleniumbase/__version__.py
@@ -1,2 +1,2 @@
# seleniumbase package
-__version__ = "4.26.3"
+__version__ = "4.26.4"
diff --git a/seleniumbase/console_scripts/ReadMe.md b/seleniumbase/console_scripts/ReadMe.md
index 38438fb185c..b12476380c3 100644
--- a/seleniumbase/console_scripts/ReadMe.md
+++ b/seleniumbase/console_scripts/ReadMe.md
@@ -406,6 +406,7 @@ sbase codegen new_test.py --url=wikipedia.org
``--edge`` (Use Edge browser instead of Chrome.)
``--gui`` / ``--headed`` (Use headed mode on Linux.)
``--uc`` / ``--undetected`` (Use undetectable mode.)
+``--ee`` (Use SHIFT + ESC to end the recording.)
``--overwrite`` (Overwrite file when it exists.)
``--behave`` (Also output Behave/Gherkin files.)
diff --git a/seleniumbase/console_scripts/run.py b/seleniumbase/console_scripts/run.py
index 561d23877b5..c0edf695f33 100644
--- a/seleniumbase/console_scripts/run.py
+++ b/seleniumbase/console_scripts/run.py
@@ -310,6 +310,7 @@ def show_mkrec_usage():
print(" --edge (Use Edge browser instead of Chrome.)")
print(" --gui / --headed (Use headed mode on Linux.)")
print(" --uc / --undetected (Use undetectable mode.)")
+ print(" --ee (Use SHIFT + ESC to end the recording.)")
print(" --overwrite (Overwrite file when it exists.)")
print(" --behave (Also output Behave/Gherkin files.)")
print(" Output:")
@@ -336,6 +337,7 @@ def show_codegen_usage():
print(" --edge (Use Edge browser instead of Chrome.)")
print(" --gui / --headed (Use headed mode on Linux.)")
print(" --uc / --undetected (Use undetectable mode.)")
+ print(" --ee (Use SHIFT + ESC to end the recording.)")
print(" --overwrite (Overwrite file when it exists.)")
print(" --behave (Also output Behave/Gherkin files.)")
print(" Output:")
diff --git a/seleniumbase/console_scripts/sb_mkrec.py b/seleniumbase/console_scripts/sb_mkrec.py
index 3ee61b34eb8..6becb6c48da 100644
--- a/seleniumbase/console_scripts/sb_mkrec.py
+++ b/seleniumbase/console_scripts/sb_mkrec.py
@@ -93,6 +93,7 @@ def main():
invalid_cmd = None
use_edge = False
use_uc = False
+ esc_end = False
start_page = None
next_is_url = False
use_colors = True
@@ -145,6 +146,8 @@ def main():
help_me = True
elif option.lower() == "--edge":
use_edge = True
+ elif option.lower() == "--ee":
+ esc_end = True
elif option.lower() in ("--gui", "--headed"):
if "linux" in sys.platform:
force_gui = True
@@ -183,6 +186,42 @@ def main():
data.append(' # type "c", and press [Enter].')
data.append(" import pdb; pdb.set_trace()")
data.append("")
+
+ if esc_end:
+ msg = ">>> Use [SHIFT + ESC] in the browser to end recording!"
+ d2 = []
+ d2.append("from seleniumbase import BaseCase")
+ d2.append("")
+ d2.append("")
+ d2.append("class RecorderTest(BaseCase):")
+ d2.append(" def test_recording(self):")
+ d2.append(" if self.recorder_ext:")
+ d2.append(" print(")
+ d2.append(' "\\n\\n%s\\n"' % msg)
+ d2.append(" )")
+ d2.append(' script = self._get_rec_shift_esc_script()')
+ d2.append(' esc = "return document.sb_esc_end;"')
+ d2.append(" start_time = self.time()")
+ d2.append(" last_handles_num = self._get_num_handles()")
+ d2.append(" for i in range(1200):")
+ d2.append(" try:")
+ d2.append(" self.execute_script(script)")
+ d2.append(" handles_num = self._get_num_handles()")
+ d2.append(" if handles_num < 1:")
+ d2.append(" return")
+ d2.append(" elif handles_num != last_handles_num:")
+ d2.append(" self.switch_to_window(-1)")
+ d2.append(" last_handles_num = handles_num")
+ d2.append(' if self.execute_script(esc) == "yes":')
+ d2.append(" return")
+ d2.append(" elif self.time() - start_time > 600:")
+ d2.append(" return")
+ d2.append(" self.sleep(0.5)")
+ d2.append(" except Exception:")
+ d2.append(" return")
+ d2.append("")
+ data = d2
+
file = codecs.open(file_path, "w+", "utf-8")
file.writelines("\r\n".join(data))
file.close()
diff --git a/seleniumbase/console_scripts/sb_recorder.py b/seleniumbase/console_scripts/sb_recorder.py
index 26cddab1dfa..056cb2b61f1 100644
--- a/seleniumbase/console_scripts/sb_recorder.py
+++ b/seleniumbase/console_scripts/sb_recorder.py
@@ -162,6 +162,8 @@ def do_recording(file_name, url, overwrite_enabled, use_chrome, window):
or "--undetectable" in command_args
):
command += " --uc"
+ if "--ee" in command_args:
+ command += " --ee"
command += add_on
poll = None
if sb_config.rec_subprocess_used:
diff --git a/seleniumbase/core/browser_launcher.py b/seleniumbase/core/browser_launcher.py
index c93d811a6b7..74ffcce97f4 100644
--- a/seleniumbase/core/browser_launcher.py
+++ b/seleniumbase/core/browser_launcher.py
@@ -443,8 +443,32 @@ def uc_open_with_reconnect(driver, url, reconnect_time=None):
js_utils.call_me_later(driver, script, 3)
time.sleep(0.007)
driver.close()
- driver.reconnect(reconnect_time)
- driver.switch_to.window(driver.window_handles[-1])
+ if reconnect_time == "disconnect":
+ driver.disconnect()
+ time.sleep(0.007)
+ else:
+ driver.reconnect(reconnect_time)
+ driver.switch_to.window(driver.window_handles[-1])
+ else:
+ driver.default_get(url) # The original one
+ return None
+
+
+def uc_open_with_disconnect(driver, url):
+ """Open a url and disconnect chromedriver.
+ Note: You can't perform Selenium actions again
+ until after you've called driver.connect()."""
+ if url.startswith("//"):
+ url = "https:" + url
+ elif ":" not in url:
+ url = "https://" + url
+ if (url.startswith("http:") or url.startswith("https:")):
+ script = 'window.open("%s","_blank");' % url
+ js_utils.call_me_later(driver, script, 3)
+ time.sleep(0.007)
+ driver.close()
+ driver.disconnect()
+ time.sleep(0.007)
else:
driver.default_get(url) # The original one
return None
@@ -3754,6 +3778,11 @@ def get_local_driver(
driver, *args, **kwargs
)
)
+ driver.uc_open_with_disconnect = (
+ lambda *args, **kwargs: uc_open_with_disconnect(
+ driver, *args, **kwargs
+ )
+ )
driver.uc_click = lambda *args, **kwargs: uc_click(
driver, *args, **kwargs
)
diff --git a/seleniumbase/fixtures/base_case.py b/seleniumbase/fixtures/base_case.py
index 1851ad9d43b..9ae0e7f02df 100644
--- a/seleniumbase/fixtures/base_case.py
+++ b/seleniumbase/fixtures/base_case.py
@@ -114,6 +114,7 @@ def __initialize_variables(self):
]
self.version_tuple = tuple(self.version_list)
self.version_info = self.version_tuple
+ self.time = time.time
self.__page_sources = []
self.__extra_actions = []
self.__js_start_time = 0
@@ -381,6 +382,7 @@ def click(
self, selector, by="css selector", timeout=None, delay=0, scroll=True
):
self.__check_scope()
+ self.__skip_if_esc()
if not timeout:
timeout = settings.SMALL_TIMEOUT
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
@@ -671,6 +673,7 @@ def click(
self.__demo_mode_pause_if_active(tiny=True)
elif self.slow_mode:
self.__slow_mode_pause_if_active()
+ self.__set_esc_skip()
def slow_click(self, selector, by="css selector", timeout=None):
"""Similar to click(), but pauses for a brief moment before clicking.
@@ -1586,6 +1589,7 @@ def get_partial_link_text_attribute(
def click_link_text(self, link_text, timeout=None):
"""This method clicks link text on a page."""
self.__check_scope()
+ self.__skip_if_esc()
if not timeout:
timeout = settings.SMALL_TIMEOUT
if self.timeout_multiplier and timeout == settings.SMALL_TIMEOUT:
@@ -4142,6 +4146,10 @@ def get_new_driver(
self.uc_open_with_tab = new_driver.uc_open_with_tab
if hasattr(new_driver, "uc_open_with_reconnect"):
self.uc_open_with_reconnect = new_driver.uc_open_with_reconnect
+ if hasattr(new_driver, "uc_open_with_disconnect"):
+ self.uc_open_with_disconnect = (
+ new_driver.uc_open_with_disconnect
+ )
if hasattr(new_driver, "reconnect"):
self.reconnect = new_driver.reconnect
if hasattr(new_driver, "disconnect"):
@@ -4404,11 +4412,35 @@ def add_cookies(self, cookies):
for cookie_dict in cookies:
self.driver.add_cookie(cookie_dict)
+ def __set_esc_skip(self):
+ if hasattr(self, "esc_end") and self.esc_end:
+ script = (
+ """document.onkeydown = function(evt) {
+ evt = evt || window.event;
+ var isEscape = false;
+ if ("key" in evt) {
+ isEscape = (evt.key === "Escape" || evt.key === "Esc");
+ } else {
+ isEscape = (evt.keyCode === 27);
+ }
+ if (isEscape) {
+ document.sb_esc_end = 'yes';
+ }
+ };"""
+ )
+ self.execute_script(script)
+
+ def __skip_if_esc(self):
+ if hasattr(self, "esc_end") and self.esc_end:
+ if self.execute_script("return document.sb_esc_end;") == "yes":
+ self.skip()
+
def wait_for_ready_state_complete(self, timeout=None):
"""Waits for the "readyState" of the page to be "complete".
Returns True when the method completes."""
self.__check_scope()
self._check_browser()
+ self.__skip_if_esc()
if not timeout:
timeout = settings.EXTREME_TIMEOUT
if self.timeout_multiplier and timeout == settings.EXTREME_TIMEOUT:
@@ -4427,6 +4459,7 @@ def wait_for_ready_state_complete(self, timeout=None):
time.sleep(0.01)
if self.undetectable:
time.sleep(0.035)
+ self.__set_esc_skip()
return True
def wait_for_angularjs(self, timeout=None, **kwargs):
@@ -5775,6 +5808,7 @@ def highlight(
scroll - the option to scroll to the element first (Default: True)
timeout - the time to wait for the element to appear """
self.__check_scope()
+ self.__skip_if_esc()
if isinstance(selector, WebElement):
self.__highlight_element(selector, loops=loops, scroll=scroll)
return
@@ -6760,10 +6794,7 @@ def get_pdf_text(
constants.PipInstall.FINDLOCK
)
with pip_find_lock:
- if (
- sys.version_info >= (3, 7)
- and sys.version_info < (3, 9)
- ):
+ if sys.version_info < (3, 9):
# Fix bug in newer cryptography for Python 3.7 and 3.8:
# "pyo3_runtime.PanicException: Python API call failed"
try:
@@ -8709,6 +8740,7 @@ def wait_for_element_visible(
):
"""Same as self.wait_for_element()"""
self.__check_scope()
+ self.__skip_if_esc()
if not timeout:
timeout = settings.LARGE_TIMEOUT
if self.timeout_multiplier and timeout == settings.LARGE_TIMEOUT:
@@ -13478,6 +13510,7 @@ def __demo_mode_scroll_if_active(self, selector, by):
self.slow_scroll_to(selector, by=by)
def __demo_mode_highlight_if_active(self, selector, by):
+ self.__skip_if_esc()
if self.demo_mode:
# Includes self.slow_scroll_to(selector, by=by) by default
self.__highlight(selector, by=by)
@@ -14379,6 +14412,7 @@ def setUp(self, masterqa_mode=False):
self.firefox_arg = sb_config.firefox_arg
self.firefox_pref = sb_config.firefox_pref
self.verify_delay = sb_config.verify_delay
+ self.esc_end = sb_config.esc_end
self.recorder_mode = sb_config.recorder_mode
self.recorder_ext = sb_config.recorder_mode
self.rec_print = sb_config.rec_print
@@ -15698,6 +15732,31 @@ def _get_driver_name_and_version(self):
else:
return None
+ def _get_num_handles(self):
+ return len(self.driver.window_handles)
+
+ def _get_rec_shift_esc_script(self):
+ return (
+ """document.onkeydown = function(evt) {
+ evt = evt || window.event;
+ var isEscape = false;
+ if ("key" in evt) {
+ isEscape = (evt.key === "Escape" || evt.key === "Esc");
+ last_key = evt.key;
+ } else {
+ isEscape = (evt.keyCode === 27);
+ last_key = evt.keyCode;
+ if (last_key === 16) {
+ last_key = "Shift";
+ }
+ }
+ if (isEscape && document.sb_last_key === "Shift") {
+ document.sb_esc_end = "yes";
+ }
+ document.sb_last_key = last_key;
+ };"""
+ )
+
def _addSkip(self, result, test_case, reason):
"""This method should NOT be called directly from tests."""
addSkip = getattr(result, 'addSkip', None)
@@ -15828,6 +15887,11 @@ def tearDown(self):
)
raise Exception(message)
# *** Start tearDown() officially ***
+ if self.undetectable:
+ try:
+ self.driver.window_handles
+ except urllib3.exceptions.MaxRetryError:
+ self.driver.connect()
self.__slow_mode_pause_if_active()
has_exception = self.__has_exception()
sb_config._has_exception = has_exception
diff --git a/seleniumbase/plugins/pytest_plugin.py b/seleniumbase/plugins/pytest_plugin.py
index 7aabe65e9fb..a6004c6f55b 100644
--- a/seleniumbase/plugins/pytest_plugin.py
+++ b/seleniumbase/plugins/pytest_plugin.py
@@ -81,6 +81,7 @@ def pytest_addoption(parser):
--block-images (Block images from loading during tests.)
--do-not-track (Indicate to websites that you don't want to be tracked.)
--verify-delay=SECONDS (The delay before MasterQA verification checks.)
+ --ee / --esc-end (Lets the user end the current test via the ESC key.)
--recorder (Enables the Recorder for turning browser actions into code.)
--rec-behave (Same as Recorder Mode, but also generates behave-gherkin.)
--rec-sleep (If the Recorder is enabled, also records self.sleep calls.)
@@ -895,6 +896,16 @@ def pytest_addoption(parser):
help="""Setting this overrides the default wait time
before each MasterQA verification pop-up.""",
)
+ parser.addoption(
+ "--esc-end",
+ "--esc_end",
+ "--ee",
+ action="store_true",
+ dest="esc_end",
+ default=False,
+ help="""End the current test early via the ESC key.
+ The test will be marked as skipped.""",
+ )
parser.addoption(
"--recorder",
"--record",
@@ -1549,6 +1560,7 @@ def pytest_configure(config):
sb_config.block_images = config.getoption("block_images")
sb_config.do_not_track = config.getoption("do_not_track")
sb_config.verify_delay = config.getoption("verify_delay")
+ sb_config.esc_end = config.getoption("esc_end")
sb_config.recorder_mode = config.getoption("recorder_mode")
sb_config.recorder_ext = config.getoption("recorder_mode") # Again
sb_config.rec_behave = config.getoption("rec_behave")
diff --git a/seleniumbase/plugins/selenium_plugin.py b/seleniumbase/plugins/selenium_plugin.py
index 4adc70f2a1b..c3cf01f5fc9 100644
--- a/seleniumbase/plugins/selenium_plugin.py
+++ b/seleniumbase/plugins/selenium_plugin.py
@@ -60,6 +60,7 @@ class SeleniumBrowser(Plugin):
--block-images (Block images from loading during tests.)
--do-not-track (Indicate to websites that you don't want to be tracked.)
--verify-delay=SECONDS (The delay before MasterQA verification checks.)
+ --ee / --esc-end (Lets the user end the current test via the ESC key.)
--recorder (Enables the Recorder for turning browser actions into code.)
--rec-behave (Same as Recorder Mode, but also generates behave-gherkin.)
--rec-sleep (If the Recorder is enabled, also records self.sleep calls.)
@@ -613,6 +614,16 @@ def options(self, parser, env):
help="""Setting this overrides the default wait time
before each MasterQA verification pop-up.""",
)
+ parser.addoption(
+ "--esc-end",
+ "--esc_end",
+ "--ee",
+ action="store_true",
+ dest="esc_end",
+ default=False,
+ help="""End the current test early via the ESC key.
+ The test will be marked as skipped.""",
+ )
parser.addoption(
"--recorder",
"--record",
@@ -1126,6 +1137,7 @@ def beforeTest(self, test):
test.test.block_images = self.options.block_images
test.test.do_not_track = self.options.do_not_track
test.test.verify_delay = self.options.verify_delay # MasterQA
+ test.test.esc_end = self.options.esc_end
test.test.recorder_mode = self.options.recorder_mode
test.test.recorder_ext = self.options.recorder_mode # Again
test.test.rec_behave = self.options.rec_behave
diff --git a/seleniumbase/undetected/webelement.py b/seleniumbase/undetected/webelement.py
index 1db44f30494..ee252e56bc6 100644
--- a/seleniumbase/undetected/webelement.py
+++ b/seleniumbase/undetected/webelement.py
@@ -14,7 +14,7 @@ def uc_click(
):
if driver and selector and by:
delayed_click = False
- if tag_name in ["span", "button", "div", "a"]:
+ if tag_name in ["span", "button", "div", "a", "b", "input"]:
delayed_click = True
if delayed_click and ":contains" not in selector:
selector = js_utils.convert_to_css_selector(selector, by)
diff --git a/setup.py b/setup.py
index 0749905259a..c2b4db9ec39 100755
--- a/setup.py
+++ b/setup.py
@@ -205,7 +205,8 @@
'soupsieve==2.4.1;python_version<"3.8"',
'soupsieve==2.5;python_version>="3.8"',
"beautifulsoup4==4.12.3",
- 'pygments==2.17.2',
+ 'pygments==2.17.2;python_version<"3.8"',
+ 'pygments==2.18.0;python_version>="3.8"',
'pyreadline3==3.4.1;platform_system=="Windows"',
"tabcompleter==1.3.0",
"pdbp==1.5.0",
@@ -229,7 +230,7 @@
# Usage: coverage run -m pytest; coverage html; coverage report
"coverage": [
'coverage==7.2.7;python_version<"3.8"',
- 'coverage>=7.5.0;python_version>="3.8"',
+ 'coverage>=7.5.1;python_version>="3.8"',
'pytest-cov==4.1.0;python_version<"3.8"',
'pytest-cov>=5.0.0;python_version>="3.8"',
],
@@ -256,7 +257,7 @@
'pdfminer.six==20221105;python_version<"3.8"',
'pdfminer.six==20231228;python_version>="3.8"',
'cryptography==39.0.2;python_version<"3.9"',
- 'cryptography==42.0.5;python_version>="3.9"',
+ 'cryptography==42.0.7;python_version>="3.9"',
'cffi==1.15.1;python_version<"3.8"',
'cffi==1.16.0;python_version>="3.8"',
"pycparser==2.22",
@@ -277,7 +278,7 @@
# Usage: proxy
# (That starts a proxy server on "127.0.0.1:8899".)
"proxy": [
- "proxy.py==2.4.3",
+ "proxy.py==2.4.4",
],
# pip install -e .[psutil]
"psutil": [