Skip to content

Commit a6ea87f

Browse files
authored
Merge pull request #477 from seleniumbase/time-limit-feature
Add the ``--time-limit=s`` feature to fail long-running tests
2 parents aa51386 + 5b7ecf0 commit a6ea87f

18 files changed

+150
-28
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/super_logo_sb20.png" title="SeleniumBase" height="48">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)
1+
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/super_logo_sb26.png" title="SeleniumBase" height="48">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)
22

33
[<img src="https://img.shields.io/github/release/seleniumbase/SeleniumBase.svg" alt=" " />](https://github.com/seleniumbase/SeleniumBase/releases) [<img src="https://img.shields.io/pypi/v/seleniumbase.svg" alt=" " />](https://pypi.python.org/pypi/seleniumbase) [<img src="https://badges.gitter.im/seleniumbase/SeleniumBase.svg" alt=" " />](https://gitter.im/seleniumbase/SeleniumBase) [<img src="https://img.shields.io/travis/seleniumbase/SeleniumBase/master.svg?logo=travis" alt=" " />](https://travis-ci.org/seleniumbase/SeleniumBase) [<img src="https://dev.azure.com/seleniumbase/seleniumbase/_apis/build/status/seleniumbase.SeleniumBase?branchName=master" alt=" " />](https://dev.azure.com/seleniumbase/seleniumbase/_build) [<img src="https://github.com/seleniumbase/SeleniumBase/workflows/CI%20build/badge.svg">](https://github.com/seleniumbase/SeleniumBase/actions) [<img src="https://img.shields.io/badge/license-MIT-22BBCC.svg" alt=" " />](https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE) [<img src="https://img.shields.io/github/stars/seleniumbase/seleniumbase.svg" alt=" " />](https://github.com/seleniumbase/SeleniumBase/stargazers)
44

@@ -229,6 +229,7 @@ SeleniumBase provides additional Pytest command-line options for tests:
229229
--headed # (The option to run tests with a GUI on Linux OS.)
230230
--start-page=URL # (The starting URL for the web browser when tests begin.)
231231
--archive-logs # (Archive old log files instead of deleting them.)
232+
--time-limit # (The option to set a time limit per test before failing it.)
232233
--slow # (The option to slow down the automation.)
233234
--demo # (The option to visually see test actions as they occur.)
234235
--demo-sleep=SECONDS # (The option to wait longer after Demo Mode actions.)
@@ -662,4 +663,4 @@ Additionally, you can use the ``@retry_on_exception()`` decorator to specificall
662663

663664
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/sb_media_logo_c.png" title="SeleniumBase" height="100">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md) <br /> [<img src="https://img.shields.io/badge/license-MIT-22BBCC.svg" alt=" " />](https://github.com/seleniumbase/SeleniumBase/blob/master/LICENSE)
664665

665-
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/super_logo_n.png" title="SeleniumBase" height="48">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)
666+
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/super_logo_sb22.png" title="SeleniumBase" height="48">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)

examples/ReadMe.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,4 @@ python gui_test_runner.py
109109

110110
--------
111111

112-
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/super_logo_n.png" title="SeleniumBase" height="48">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)
112+
[<img src="https://cdn2.hubspot.net/hubfs/100006/images/super_logo_sb23.png" title="SeleniumBase" height="48">](https://github.com/seleniumbase/SeleniumBase/blob/master/README.md)

examples/raw_parameter_script.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
sb.with_s3_logging = False
5757
sb.js_checking_on = False
5858
sb.is_pytest = False
59+
sb.time_limit = None
5960
sb.slow_mode = False
6061
sb.demo_mode = False
6162
sb.demo_sleep = 1

examples/timeout_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
""" This test fails on purpose to demonstrate the timeout feature
2-
for tests that run longer than the time limit specified. """
1+
""" This test fails on purpose to demonstrate the time-limit feature
2+
for tests that run longer than the time limit specified in seconds.
3+
Usage: (inside tests) -> self.set_time_limit(SECONDS) """
34

45
import pytest
5-
import time
66
from seleniumbase import BaseCase
77

88

99
class MyTestClass(BaseCase):
1010

1111
@pytest.mark.expected_failure
12-
@pytest.mark.timeout(6) # The test will fail if it runs longer than this
13-
def test_timeout_failure(self):
12+
def test_time_limit_feature(self):
13+
self.set_time_limit(6) # Test fails if run-time exceeds limit
1414
self.open("https://xkcd.com/1658/")
15-
time.sleep(7)
15+
self.sleep(7)

help_docs/method_summary.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,8 @@ self.js_update_text(selector, new_value, by=By.CSS_SELECTOR, timeout=None)
262262

263263
self.jquery_update_text(selector, new_value, by=By.CSS_SELECTOR, timeout=None)
264264

265+
self.set_time_limit(time_limit)
266+
265267
########
266268

267269
self.add_css_link(css_link)

requirements.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ pytest-html==2.0.1;python_version>="3.6"
2222
pytest-metadata>=1.8.0
2323
pytest-ordering>=0.6
2424
pytest-rerunfailures>=8.0
25-
pytest-timeout>=1.3.4
2625
pytest-xdist>=1.31.0
2726
parameterized>=0.7.1
2827
soupsieve==1.9.5

seleniumbase/core/browser_launcher.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,8 @@ def get_local_driver(
601601
else:
602602
return webdriver.Edge()
603603
elif browser_name == constants.Browser.SAFARI:
604-
if ("-n" in sys.argv or "".join(sys.argv) == "-c"):
604+
arg_join = " ".join(sys.argv)
605+
if ("-n" in sys.argv) or ("-n=" in arg_join) or (arg_join == "-c"):
605606
# Skip if multithreaded
606607
raise Exception("Can't run Safari tests in multi-threaded mode!")
607608
return webdriver.Safari()

seleniumbase/core/log_helper.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,8 @@ def copytree(src, dst, symlinks=False, ignore=None):
121121

122122
def archive_logs_if_set(log_path, archive_logs=False):
123123
""" Handle Logging """
124-
if "-n" in sys.argv or "".join(sys.argv) == "-c":
124+
arg_join = " ".join(sys.argv)
125+
if ("-n" in sys.argv) or ("-n=" in arg_join) or (arg_join == "-c"):
125126
return # Skip if multithreaded
126127
if log_path.endswith("/"):
127128
log_path = log_path[:-1]
@@ -172,7 +173,8 @@ def log_folder_setup(log_path, archive_logs=False):
172173
if not settings.ARCHIVE_EXISTING_LOGS and not archive_logs:
173174
shutil.rmtree(archived_logs)
174175
else:
175-
if ("-n" in sys.argv or "".join(sys.argv) == "-c"):
176+
a_join = " ".join(sys.argv)
177+
if ("-n" in sys.argv) or ("-n=" in a_join) or (a_join == "-c"):
176178
# Logs are saved/archived now if tests are multithreaded
177179
pass
178180
else:

seleniumbase/core/tour_helper.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@
1111
from seleniumbase.fixtures import constants
1212
from seleniumbase.fixtures import js_utils
1313
from seleniumbase.fixtures import page_actions
14-
15-
EXPORTED_TOURS_FOLDER = "tours_exported"
14+
EXPORTED_TOURS_FOLDER = constants.Tours.EXPORTED_TOURS_FOLDER
1615

1716

1817
def activate_bootstrap(driver):

seleniumbase/fixtures/base_case.py

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ def test_anything(self):
5555
from seleniumbase.fixtures import js_utils
5656
from seleniumbase.fixtures import page_actions
5757
from seleniumbase.fixtures import page_utils
58+
from seleniumbase.fixtures import shared_utils
5859
from seleniumbase.fixtures import xpath_to_css
5960
logging.getLogger("requests").setLevel(logging.ERROR)
6061
logging.getLogger("urllib3").setLevel(logging.ERROR)
@@ -976,14 +977,15 @@ def find_visible_elements(self, selector, by=By.CSS_SELECTOR, limit=0):
976977
def click_visible_elements(self, selector, by=By.CSS_SELECTOR, limit=0):
977978
""" Finds all matching page elements and clicks visible ones in order.
978979
If a click reloads or opens a new page, the clicking will stop.
980+
If no matching elements appear, an Exception will be raised.
981+
If "limit" is set and > 0, will only click that many elements.
982+
Also clicks elements that become visible from previous clicks.
979983
Works best for actions such as clicking all checkboxes on a page.
980-
Example: self.click_visible_elements('input[type="checkbox"]')
981-
If "limit" is set and > 0, will only click that many elements. """
982-
elements = []
983-
try:
984-
elements = self.find_visible_elements(selector, by=by)
985-
except Exception:
986-
elements = self.find_elements(selector, by=by)
984+
Example: self.click_visible_elements('input[type="checkbox"]') """
985+
selector, by = self.__recalculate_selector(selector, by)
986+
self.wait_for_element_present(
987+
selector, by=by, timeout=settings.SMALL_TIMEOUT)
988+
elements = self.find_elements(selector, by=by)
987989
click_count = 0
988990
for element in elements:
989991
if limit and limit > 0 and click_count >= limit:
@@ -1661,7 +1663,17 @@ def wait_for_angularjs(self, timeout=None, **kwargs):
16611663
js_utils.wait_for_angularjs(self.driver, timeout, **kwargs)
16621664

16631665
def sleep(self, seconds):
1664-
time.sleep(seconds)
1666+
if not sb_config.time_limit:
1667+
time.sleep(seconds)
1668+
else:
1669+
start_ms = time.time() * 1000.0
1670+
stop_ms = start_ms + (seconds * 1000.0)
1671+
for x in range(int(seconds * 5)):
1672+
shared_utils.check_if_time_limit_exceeded()
1673+
now_ms = time.time() * 1000.0
1674+
if now_ms >= stop_ms:
1675+
break
1676+
time.sleep(0.2)
16651677

16661678
def activate_jquery(self):
16671679
""" If "jQuery is not defined", use this method to activate it for use.
@@ -2455,6 +2467,22 @@ def jquery_update_text(self, selector, new_value, by=By.CSS_SELECTOR,
24552467
element.send_keys('\n')
24562468
self.__demo_mode_pause_if_active()
24572469

2470+
def set_time_limit(self, time_limit):
2471+
if time_limit:
2472+
try:
2473+
sb_config.time_limit = float(time_limit)
2474+
except Exception:
2475+
sb_config.time_limit = None
2476+
else:
2477+
sb_config.time_limit = None
2478+
if sb_config.time_limit and sb_config.time_limit > 0:
2479+
sb_config.time_limit_ms = int(sb_config.time_limit * 1000.0)
2480+
self.time_limit = sb_config.time_limit
2481+
else:
2482+
self.time_limit = None
2483+
sb_config.time_limit = None
2484+
sb_config.time_limit_ms = None
2485+
24582486
############
24592487

24602488
def add_css_link(self, css_link):
@@ -3237,6 +3265,7 @@ def wait_for_link_text_present(self, link_text, timeout=None):
32373265
start_ms = time.time() * 1000.0
32383266
stop_ms = start_ms + (timeout * 1000.0)
32393267
for x in range(int(timeout * 5)):
3268+
shared_utils.check_if_time_limit_exceeded()
32403269
try:
32413270
if not self.is_link_text_present(link_text):
32423271
raise Exception(
@@ -3257,6 +3286,7 @@ def wait_for_partial_link_text_present(self, link_text, timeout=None):
32573286
start_ms = time.time() * 1000.0
32583287
stop_ms = start_ms + (timeout * 1000.0)
32593288
for x in range(int(timeout * 5)):
3289+
shared_utils.check_if_time_limit_exceeded()
32603290
try:
32613291
if not self.is_partial_link_text_present(link_text):
32623292
raise Exception(
@@ -4145,6 +4175,7 @@ def setUp(self, masterqa_mode=False):
41454175
self.demo_mode = sb_config.demo_mode
41464176
self.demo_sleep = sb_config.demo_sleep
41474177
self.highlights = sb_config.highlights
4178+
self.time_limit = sb_config.time_limit
41484179
self.environment = sb_config.environment
41494180
self.env = self.environment # Add a shortened version
41504181
self.with_selenium = sb_config.with_selenium # Should be True
@@ -4242,15 +4273,27 @@ def setUp(self, masterqa_mode=False):
42424273
# pyvirtualdisplay might not be necessary anymore because
42434274
# Chrome and Firefox now have built-in headless displays
42444275
pass
4276+
else:
4277+
# (Nosetests / Not Pytest)
4278+
pass # Setup performed in plugins
42454279

42464280
# Verify that SeleniumBase is installed successfully
42474281
if not hasattr(self, "browser"):
42484282
raise Exception("""SeleniumBase plugins DID NOT load!\n\n"""
42494283
"""*** Please REINSTALL SeleniumBase using: >\n"""
42504284
""" >>> "pip install -r requirements.txt"\n"""
42514285
""" >>> "python setup.py install" """)
4286+
4287+
# Configure the test time limit (if used)
4288+
self.set_time_limit(self.time_limit)
4289+
4290+
# Set the start time for the test (in ms)
4291+
sb_config.start_time_ms = int(time.time() * 1000.0)
4292+
4293+
# Parse the settings file
42524294
if self.settings_file:
42534295
settings_parser.set_settings(self.settings_file)
4296+
42544297
# Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio
42554298
if self.device_metrics:
42564299
metrics_string = self.device_metrics

0 commit comments

Comments
 (0)