Skip to content

Commit 6340392

Browse files
authored
Merge pull request #455 from seleniumbase/optimize-virtual-display-logging-and-teardown
Optimize virtual display logging and update boilerplate
2 parents 6c59e68 + 3644d76 commit 6340392

File tree

12 files changed

+113
-103
lines changed

12 files changed

+113
-103
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pytest my_first_test.py --demo
2626

2727
## <img src="https://cdn2.hubspot.net/hubfs/100006/images/super_square_logo_3a.png" title="SeleniumBase" height="32"> Get Started:
2828

29-
You'll need **[Python](https://www.python.org/downloads/)** on your System PATH. [<img src="https://img.shields.io/pypi/pyversions/seleniumbase.svg?logo=python&logoColor=lightblue" alt="Python versions" alt="Python versions" />](https://www.python.org/downloads/)
29+
You'll need **[Python](https://www.python.org/downloads/)** on your System PATH. [<img src="https://img.shields.io/pypi/pyversions/seleniumbase.svg?logo=python&logoColor=gold" alt="Python versions" alt="Python versions" />](https://www.python.org/downloads/)
3030

3131
### <img src="https://cdn2.hubspot.net/hubfs/100006/images/super_square_logo_3a.png" title="SeleniumBase" height="32"> Install/upgrade ``pip``:
3232
```bash

azure-pipelines.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ jobs:
3030
- script: python -m pip install --upgrade pip && pip --version
3131
displayName: 'Install/upgrade pip'
3232

33-
- script: pip install seleniumbase
33+
- script: python -m pip install seleniumbase
3434
displayName: 'Verify install from PyPI'
3535

36-
- script: pip install -r requirements.txt --upgrade
36+
- script: python -m pip install -r requirements.txt --upgrade
3737
displayName: 'Install dependencies'
3838

3939
- script: python setup.py install
@@ -53,13 +53,13 @@ jobs:
5353
pytest nothing.py
5454
displayName: 'Make sure pytest is working'
5555
56-
- script: pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml
56+
- script: python -m pytest examples/boilerplates/boilerplate_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml
5757
displayName: 'Run pytest boilerplate_test.py --browser=chrome --headless'
5858

59-
- script: pytest examples/my_first_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml
59+
- script: python -m pytest examples/my_first_test.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml
6060
displayName: 'Run pytest my_first_test.py --browser=chrome --headless'
6161

62-
- script: pytest examples/test_inspect_html.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml
62+
- script: python -m pytest examples/test_inspect_html.py --browser=chrome --headless -v -s --junit-xml=junit/test-results.xml
6363
displayName: 'Run pytest test_inspect_html.py --browser=chrome --headless'
6464

6565
- task: PublishTestResults@2

examples/boilerplates/base_test_case.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@ class BaseTestCase(BaseCase):
1212

1313
def setUp(self):
1414
super(BaseTestCase, self).setUp()
15-
# Add custom setUp code for your tests AFTER the super().setUp()
15+
# <<< Add custom setUp code for tests AFTER the super().setUp() >>>
1616

1717
def tearDown(self):
18-
# Add custom tearDown code for your tests BEFORE the super().tearDown()
18+
self.save_teardown_screenshot()
19+
# <<< Add custom tearDown code BEFORE the super().tearDown() >>>
1920
super(BaseTestCase, self).tearDown()
2021

2122
def login(self):

seleniumbase/console_scripts/sb_mkdir.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,11 @@ def main():
152152
data.append("")
153153
data.append(" def setUp(self):")
154154
data.append(" super(BaseTestCase, self).setUp()")
155-
data.append(" # Add custom setUp code AFTER the super() line")
155+
data.append(" # << Add custom code AFTER the super() line >>")
156156
data.append("")
157157
data.append(" def tearDown(self):")
158-
data.append(" # Add custom code BEFORE the super() line")
158+
data.append(" self.save_teardown_screenshot()")
159+
data.append(" # << Add custom code BEFORE the super() line >>")
159160
data.append(" super(BaseTestCase, self).tearDown()")
160161
data.append("")
161162
data.append(" def login(self):")

seleniumbase/fixtures/base_case.py

Lines changed: 80 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def test_anything(self):
3939
from selenium.common import exceptions as selenium_exceptions
4040
from selenium.webdriver.common.by import By
4141
from selenium.webdriver.common.keys import Keys
42+
from selenium.webdriver.remote.remote_connection import LOGGER
4243
from selenium.webdriver.support.ui import Select
4344
from seleniumbase import config as sb_config
4445
from seleniumbase.common import decorators
@@ -58,6 +59,7 @@ def test_anything(self):
5859
logging.getLogger("requests").setLevel(logging.ERROR)
5960
logging.getLogger("urllib3").setLevel(logging.ERROR)
6061
urllib3.disable_warnings()
62+
LOGGER.setLevel(logging.WARNING)
6163
ENI_Exception = selenium_exceptions.ElementNotInteractableException
6264

6365

@@ -76,6 +78,7 @@ def __init__(self, *args, **kwargs):
7678
self.__last_page_load_url = "data:,"
7779
self.__last_page_screenshot = None
7880
self.__last_page_screenshot_png = None
81+
self.__added_pytest_html_extra = None
7982
self.__delayed_assert_count = 0
8083
self.__delayed_assert_failures = []
8184
self.__device_width = None
@@ -4108,9 +4111,7 @@ def setUp(self, masterqa_mode=False):
41084111
self.is_pytest = False
41094112
if self.is_pytest:
41104113
# pytest-specific code
4111-
test_id = "%s.%s.%s" % (self.__class__.__module__,
4112-
self.__class__.__name__,
4113-
self._testMethodName)
4114+
test_id = self.__get_test_id()
41144115
self.browser = sb_config.browser
41154116
self.data = sb_config.data
41164117
self.slow_mode = sb_config.slow_mode
@@ -4216,10 +4217,10 @@ def setUp(self, masterqa_mode=False):
42164217

42174218
# Verify that SeleniumBase is installed successfully
42184219
if not hasattr(self, "browser"):
4219-
raise Exception("""SeleniumBase plugins did not load! """
4220-
"""Please reinstall using:\n"""
4221-
""" >>> "pip install -r requirements.txt" <<<\n"""
4222-
""" >>> "python setup.py install" <<< """)
4220+
raise Exception("""SeleniumBase plugins DID NOT load!\n\n"""
4221+
"""*** Please REINSTALL SeleniumBase using: >\n"""
4222+
""" >>> "pip install -r requirements.txt"\n"""
4223+
""" >>> "python setup.py install" """)
42234224
if self.settings_file:
42244225
settings_parser.set_settings(self.settings_file)
42254226
# Mobile Emulator device metrics: CSS Width, CSS Height, & Pixel-Ratio
@@ -4346,27 +4347,29 @@ def __insert_test_result(self, state, err):
43464347
self.testcase_manager.update_testcase_data(data_payload)
43474348

43484349
def __add_pytest_html_extra(self):
4349-
try:
4350-
if self.with_selenium:
4351-
if not self.__last_page_screenshot:
4352-
self.__set_last_page_screenshot()
4353-
if self.report_on:
4354-
extra_url = {}
4355-
extra_url['name'] = 'URL'
4356-
extra_url['format'] = 'url'
4357-
extra_url['content'] = self.get_current_url()
4358-
extra_url['mime_type'] = None
4359-
extra_url['extension'] = None
4360-
extra_image = {}
4361-
extra_image['name'] = 'Screenshot'
4362-
extra_image['format'] = 'image'
4363-
extra_image['content'] = self.__last_page_screenshot
4364-
extra_image['mime_type'] = 'image/png'
4365-
extra_image['extension'] = 'png'
4366-
self._html_report_extra.append(extra_url)
4367-
self._html_report_extra.append(extra_image)
4368-
except Exception:
4369-
pass
4350+
if not self.__added_pytest_html_extra:
4351+
try:
4352+
if self.with_selenium:
4353+
if not self.__last_page_screenshot:
4354+
self.__set_last_page_screenshot()
4355+
if self.report_on:
4356+
extra_url = {}
4357+
extra_url['name'] = 'URL'
4358+
extra_url['format'] = 'url'
4359+
extra_url['content'] = self.get_current_url()
4360+
extra_url['mime_type'] = None
4361+
extra_url['extension'] = None
4362+
extra_image = {}
4363+
extra_image['name'] = 'Screenshot'
4364+
extra_image['format'] = 'image'
4365+
extra_image['content'] = self.__last_page_screenshot
4366+
extra_image['mime_type'] = 'image/png'
4367+
extra_image['extension'] = 'png'
4368+
self.__added_pytest_html_extra = True
4369+
self._html_report_extra.append(extra_url)
4370+
self._html_report_extra.append(extra_image)
4371+
except Exception:
4372+
pass
43704373

43714374
def __quit_all_drivers(self):
43724375
if self._reuse_session and sb_config.shared_driver:
@@ -4392,19 +4395,55 @@ def __quit_all_drivers(self):
43924395
self._default_driver = None
43934396
self._drivers_list = []
43944397

4398+
def __has_exception(self):
4399+
has_exception = False
4400+
if sys.version_info[0] >= 3 and hasattr(self, '_outcome'):
4401+
if hasattr(self._outcome, 'errors') and self._outcome.errors:
4402+
has_exception = True
4403+
else:
4404+
has_exception = sys.exc_info()[1] is not None
4405+
return has_exception
4406+
4407+
def __get_test_id(self):
4408+
test_id = "%s.%s.%s" % (self.__class__.__module__,
4409+
self.__class__.__name__,
4410+
self._testMethodName)
4411+
return test_id
4412+
4413+
def __create_log_path_as_needed(self, test_logpath):
4414+
if not os.path.exists(test_logpath):
4415+
try:
4416+
os.makedirs(test_logpath)
4417+
except Exception:
4418+
pass # Only reachable during multi-threaded runs
4419+
4420+
def save_teardown_screenshot(self):
4421+
""" (Should ONLY be used at the start of custom tearDown() methods.)
4422+
This method takes a screenshot of the current web page for a
4423+
failing test (or when running your tests with --save-screenshot).
4424+
That way your tearDown() method can navigate away from the last
4425+
page where the test failed, and still get the correct screenshot
4426+
before performing tearDown() steps on other pages. If this method
4427+
is not included in your custom tearDown() method, a screenshot
4428+
will still be taken after the last step of your tearDown(), where
4429+
you should be calling "super(SubClassOfBaseCase, self).tearDown()"
4430+
"""
4431+
test_id = self.__get_test_id()
4432+
test_logpath = self.log_path + "/" + test_id
4433+
self.__create_log_path_as_needed(test_logpath)
4434+
if self.__has_exception() or self.save_screenshot_after_test:
4435+
self.__set_last_page_screenshot()
4436+
if self.is_pytest:
4437+
self.__add_pytest_html_extra()
4438+
43954439
def tearDown(self):
43964440
"""
43974441
Be careful if a subclass of BaseCase overrides setUp()
43984442
You'll need to add the following line to the subclass's tearDown():
43994443
super(SubClassOfBaseCase, self).tearDown()
44004444
"""
44014445
self.__slow_mode_pause_if_active()
4402-
has_exception = False
4403-
if sys.version_info[0] >= 3 and hasattr(self, '_outcome'):
4404-
if hasattr(self._outcome, 'errors') and self._outcome.errors:
4405-
has_exception = True
4406-
else:
4407-
has_exception = sys.exc_info()[1] is not None
4446+
has_exception = self.__has_exception()
44084447
if self.__delayed_assert_failures:
44094448
print(
44104449
"\nWhen using self.delayed_assert_*() methods in your tests, "
@@ -4414,18 +4453,9 @@ def tearDown(self):
44144453
self.process_delayed_asserts()
44154454
else:
44164455
self.process_delayed_asserts(print_only=True)
4417-
self.is_pytest = None
4418-
try:
4419-
# This raises an exception if the test is not coming from pytest
4420-
self.is_pytest = sb_config.is_pytest
4421-
except Exception:
4422-
# Not using pytest (probably nosetests)
4423-
self.is_pytest = False
44244456
if self.is_pytest:
44254457
# pytest-specific code
4426-
test_id = "%s.%s.%s" % (self.__class__.__module__,
4427-
self.__class__.__name__,
4428-
self._testMethodName)
4458+
test_id = self.__get_test_id()
44294459
try:
44304460
with_selenium = self.with_selenium
44314461
except Exception:
@@ -4460,11 +4490,7 @@ def tearDown(self):
44604490
if self.with_testing_base and not has_exception and (
44614491
self.save_screenshot_after_test):
44624492
test_logpath = self.log_path + "/" + test_id
4463-
if not os.path.exists(test_logpath):
4464-
try:
4465-
os.makedirs(test_logpath)
4466-
except Exception:
4467-
pass # Only reachable during multi-threaded runs
4493+
self.__create_log_path_as_needed(test_logpath)
44684494
if not self.__last_page_screenshot_png:
44694495
self.__set_last_page_screenshot()
44704496
log_helper.log_screenshot(
@@ -4474,11 +4500,7 @@ def tearDown(self):
44744500
self.__add_pytest_html_extra()
44754501
if self.with_testing_base and has_exception:
44764502
test_logpath = self.log_path + "/" + test_id
4477-
if not os.path.exists(test_logpath):
4478-
try:
4479-
os.makedirs(test_logpath)
4480-
except Exception:
4481-
pass # Only reachable during multi-threaded runs
4503+
self.__create_log_path_as_needed(test_logpath)
44824504
if ((not self.with_screen_shots) and (
44834505
not self.with_basic_test_info) and (
44844506
not self.with_page_source)):
@@ -4553,15 +4575,9 @@ def tearDown(self):
45534575
else:
45544576
# (Nosetests)
45554577
if has_exception:
4556-
test_id = "%s.%s.%s" % (self.__class__.__module__,
4557-
self.__class__.__name__,
4558-
self._testMethodName)
4578+
test_id = self.__get_test_id()
45594579
test_logpath = self.log_path + "/" + test_id
4560-
if not os.path.exists(test_logpath):
4561-
try:
4562-
os.makedirs(test_logpath)
4563-
except Exception:
4564-
pass # Only reachable during multi-threaded runs
4580+
self.__create_log_path_as_needed(test_logpath)
45654581
log_helper.log_test_failure_data(
45664582
self, test_logpath, self.driver, self.browser)
45674583
if len(self._drivers_list) > 0:
@@ -4573,15 +4589,9 @@ def tearDown(self):
45734589
self.__last_page_screenshot_png)
45744590
log_helper.log_page_source(test_logpath, self.driver)
45754591
elif self.save_screenshot_after_test:
4576-
test_id = "%s.%s.%s" % (self.__class__.__module__,
4577-
self.__class__.__name__,
4578-
self._testMethodName)
4592+
test_id = self.__get_test_id()
45794593
test_logpath = self.log_path + "/" + test_id
4580-
if not os.path.exists(test_logpath):
4581-
try:
4582-
os.makedirs(test_logpath)
4583-
except Exception:
4584-
pass # Only reachable during multi-threaded runs
4594+
self.__create_log_path_as_needed(test_logpath)
45854595
if not self.__last_page_screenshot_png:
45864596
self.__set_last_page_screenshot()
45874597
log_helper.log_screenshot(

seleniumbase/virtual_display/abstractdisplay.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
from seleniumbase.virtual_display import xauth
88

99
mutex = Lock()
10-
RANDOMIZE_DISPLAY_NR = False
11-
if RANDOMIZE_DISPLAY_NR:
12-
import random
13-
random.seed()
1410
MIN_DISPLAY_NR = 1000
1511
USED_DISPLAY_NR_LIST = []
1612

@@ -59,8 +55,6 @@ def search_for_display(self):
5955
display = max(MIN_DISPLAY_NR, max(ls) + 3)
6056
else:
6157
display = MIN_DISPLAY_NR
62-
if RANDOMIZE_DISPLAY_NR:
63-
display += random.randint(0, 100)
6458
return display
6559

6660
def redirect_display(self, on):

seleniumbase/virtual_display/easyprocess.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import time
1414

1515
log = logging.getLogger(__name__)
16+
log.setLevel(logging.ERROR)
1617
SECTION_LINK = 'link'
1718
POLL_TIME = 0.1
1819
USE_POLL = 0

seleniumbase/virtual_display/xauth.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'''Utility functions for xauth.'''
22
import os
33
import hashlib
4-
from seleniumbase.virtual_display import easyprocess
4+
from seleniumbase.virtual_display.easyprocess import EasyProcess
55

66

77
class NotFoundError(Exception):
@@ -14,8 +14,11 @@ def is_installed():
1414
Return whether or not xauth is installed.
1515
'''
1616
try:
17-
easyprocess.EasyProcess(['xauth', '-h']).check_installed()
18-
except easyprocess.EasyProcessCheckInstalledError:
17+
p = EasyProcess(['xauth', '-V'])
18+
p.enable_stdout_log = False
19+
p.enable_stderr_log = False
20+
p.call()
21+
except Exception:
1922
return False
2023
else:
2124
return True
@@ -33,4 +36,4 @@ def call(*args):
3336
'''
3437
Call xauth with the given args.
3538
'''
36-
easyprocess.EasyProcess(['xauth'] + list(args)).call()
39+
EasyProcess(['xauth'] + list(args)).call()

seleniumbase/virtual_display/xephyr.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from seleniumbase.virtual_display.easyprocess import EasyProcess
22
from seleniumbase.virtual_display.abstractdisplay import AbstractDisplay
33

4-
PACKAGE = 'xephyr'
54
PROGRAM = 'Xephyr'
6-
URL = None
75

86

97
class XephyrDisplay(AbstractDisplay):
@@ -26,8 +24,10 @@ def __init__(self, size=(1024, 768), color_depth=24, bgcolor='black'):
2624

2725
@classmethod
2826
def check_installed(cls):
29-
EasyProcess([PROGRAM, '-help'], url=URL,
30-
ubuntu_package=PACKAGE).check_installed()
27+
p = EasyProcess([PROGRAM, '-help'])
28+
p.enable_stdout_log = False
29+
p.enable_stderr_log = False
30+
p.call()
3131

3232
@property
3333
def _cmd(self):

0 commit comments

Comments
 (0)