Skip to content

Commit aa51386

Browse files
authored
Merge pull request #474 from seleniumbase/improve-logging
Improve the Logging System
2 parents 24d2f3b + da72e73 commit aa51386

File tree

10 files changed

+89
-52
lines changed

10 files changed

+89
-52
lines changed

README.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[<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)
22

3-
[<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/latest?definitionId=1&branchName=master) [<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)
3+
[<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

55
Python Framework for End-to-End UI Testing with [Selenium WebDriver](https://selenium.dev) and [pytest](https://pytest.org).
66

@@ -228,7 +228,6 @@ SeleniumBase provides additional Pytest command-line options for tests:
228228
--headless # (The option to run tests headlessly. The default on Linux OS.)
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.)
231-
--log-path=LOG_PATH # (The directory where log files get saved to.)
232231
--archive-logs # (Archive old log files instead of deleting them.)
233232
--slow # (The option to slow down the automation.)
234233
--demo # (The option to visually see test actions as they occur.)

examples/example_logs/basic_test_info.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
Last_Page: https://xkcd.com/731/
2-
Browser: firefox
1+
Last Page: https://xkcd.com/731/
2+
Browser: firefox
3+
Timestamp: 1540898481
34
Traceback: File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/case.py", line 615, in run
45
testMethod()
56
File "/Users/michael/github/SeleniumBase/examples/test_fail.py", line 12, in test_find_army_of_robots_on_xkcd_desert_island

requirements.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pip>=19.3.1
2-
setuptools>=44.0.0
2+
setuptools>=44.0.0;python_version<"3"
3+
setuptools>=45.0.0;python_version>="3"
34
setuptools-scm>=3.3.3
45
wheel>=0.33.6
56
six==1.13.0
@@ -34,7 +35,7 @@ pyopenssl==19.1.0
3435
packaging>=20.0
3536
pygments==2.5.2
3637
colorama==0.4.3
37-
coverage>=5.0.2
38+
coverage>=5.0.3
3839
pymysql==0.9.3
3940
pyotp==2.3.0
4041
boto==2.49.0

seleniumbase/core/browser_launcher.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def _add_chrome_proxy_extension(
9595
""" Implementation of https://stackoverflow.com/a/35293284 for
9696
https://stackoverflow.com/questions/12848327/
9797
(Run Selenium on a proxy server that requires authentication.) """
98-
if not "".join(sys.argv) == "-c":
98+
if not ("-n" in sys.argv or "".join(sys.argv) == "-c"):
9999
# Single-threaded
100100
proxy_helper.create_proxy_zip(proxy_string, proxy_user, proxy_pass)
101101
else:
@@ -529,7 +529,8 @@ def get_local_driver(
529529
logging.debug("\nWarning: Could not make geckodriver"
530530
" executable: %s" % e)
531531
elif not is_geckodriver_on_path():
532-
if not "".join(sys.argv) == "-c": # Skip if multithreaded
532+
if not ("-n" in sys.argv or "".join(sys.argv) == "-c"):
533+
# (Not multithreaded)
533534
from seleniumbase.console_scripts import sb_install
534535
sys_args = sys.argv # Save a copy of current sys args
535536
print("\nWarning: geckodriver not found!"
@@ -600,7 +601,8 @@ def get_local_driver(
600601
else:
601602
return webdriver.Edge()
602603
elif browser_name == constants.Browser.SAFARI:
603-
if "".join(sys.argv) == "-c": # Skip if multithreaded
604+
if ("-n" in sys.argv or "".join(sys.argv) == "-c"):
605+
# Skip if multithreaded
604606
raise Exception("Can't run Safari tests in multi-threaded mode!")
605607
return webdriver.Safari()
606608
elif browser_name == constants.Browser.OPERA:
@@ -631,7 +633,8 @@ def get_local_driver(
631633
logging.debug("\nWarning: Could not make chromedriver"
632634
" executable: %s" % e)
633635
elif not is_chromedriver_on_path():
634-
if not "".join(sys.argv) == "-c": # Skip if multithreaded
636+
if not ("-n" in sys.argv or "".join(sys.argv) == "-c"):
637+
# (Not multithreaded)
635638
from seleniumbase.console_scripts import sb_install
636639
sys_args = sys.argv # Save a copy of current sys args
637640
print("\nWarning: chromedriver not found. Installing now:")

seleniumbase/core/download_helper.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import os
22
import shutil
3-
import sys
43
import time
54
from seleniumbase.config import settings
65
from seleniumbase.fixtures import constants
@@ -26,11 +25,7 @@ def reset_downloads_folder():
2625
if os.path.exists(downloads_path) and not os.listdir(downloads_path) == []:
2726
archived_downloads_folder = os.path.join(downloads_path, '..',
2827
ARCHIVE_DIR)
29-
if not "".join(sys.argv) == "-c":
30-
# Only move files if the test run is not multi-threaded.
31-
# (Running tests with "-n NUM" will create threads that only
32-
# have "-c" in the sys.argv list. Easy to catch.)
33-
reset_downloads_folder_assistant(archived_downloads_folder)
28+
reset_downloads_folder_assistant(archived_downloads_folder)
3429

3530

3631
def reset_downloads_folder_assistant(archived_downloads_folder):
@@ -43,8 +38,11 @@ def reset_downloads_folder_assistant(archived_downloads_folder):
4338
archived_downloads_folder, int(time.time()))
4439
if os.path.exists(downloads_path):
4540
if not os.listdir(downloads_path) == []:
46-
shutil.move(downloads_path, new_archived_downloads_sub_folder)
47-
os.makedirs(downloads_path)
41+
try:
42+
shutil.move(downloads_path, new_archived_downloads_sub_folder)
43+
os.makedirs(downloads_path)
44+
except Exception:
45+
pass
4846
if not settings.ARCHIVE_EXISTING_DOWNLOADS:
4947
try:
5048
shutil.rmtree(new_archived_downloads_sub_folder)

seleniumbase/core/log_helper.py

Lines changed: 54 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ def log_test_failure_data(test, test_logpath, driver, browser):
3131
log_file = codecs.open(basic_file_path, "w+", "utf-8")
3232
last_page = get_last_page(driver)
3333
data_to_save = []
34-
data_to_save.append("Last_Page: %s" % last_page)
35-
data_to_save.append("Browser: %s " % browser)
34+
data_to_save.append("Last Page: %s" % last_page)
35+
data_to_save.append(" Browser: %s" % browser)
36+
data_to_save.append("Timestamp: %s" % int(time.time()))
3637
if sys.version_info[0] >= 3 and hasattr(test, '_outcome'):
3738
if test._outcome.errors:
3839
try:
@@ -104,6 +105,47 @@ def get_html_source_with_base_href(driver, page_source):
104105
return ''
105106

106107

108+
def copytree(src, dst, symlinks=False, ignore=None):
109+
if not os.path.exists(dst):
110+
os.makedirs(dst)
111+
for item in os.listdir(src):
112+
s = os.path.join(src, item)
113+
d = os.path.join(dst, item)
114+
if os.path.isdir(s):
115+
copytree(s, d, symlinks, ignore)
116+
else:
117+
if not os.path.exists(d) or (
118+
os.stat(s).st_mtime - os.stat(d).st_mtime > 1):
119+
shutil.copy2(s, d)
120+
121+
122+
def archive_logs_if_set(log_path, archive_logs=False):
123+
""" Handle Logging """
124+
if "-n" in sys.argv or "".join(sys.argv) == "-c":
125+
return # Skip if multithreaded
126+
if log_path.endswith("/"):
127+
log_path = log_path[:-1]
128+
if not os.path.exists(log_path):
129+
try:
130+
os.makedirs(log_path)
131+
except Exception:
132+
pass # Only reachable during multi-threaded runs
133+
else:
134+
if settings.ARCHIVE_EXISTING_LOGS or archive_logs:
135+
if len(os.listdir(log_path)) > 0:
136+
archived_folder = "%s/../archived_logs/" % log_path
137+
archived_folder = os.path.realpath(archived_folder) + '/'
138+
log_path = os.path.realpath(log_path) + '/'
139+
if not os.path.exists(archived_folder):
140+
try:
141+
os.makedirs(archived_folder)
142+
except Exception:
143+
pass # Only reachable during multi-threaded runs
144+
time_id = str(int(time.time()))
145+
archived_logs = "%slogs_%s" % (archived_folder, time_id)
146+
copytree(log_path, archived_logs)
147+
148+
107149
def log_folder_setup(log_path, archive_logs=False):
108150
""" Handle Logging """
109151
if log_path.endswith("/"):
@@ -115,30 +157,23 @@ def log_folder_setup(log_path, archive_logs=False):
115157
pass # Should only be reachable during multi-threaded runs
116158
else:
117159
archived_folder = "%s/../archived_logs/" % log_path
160+
archived_folder = os.path.realpath(archived_folder) + '/'
118161
if not os.path.exists(archived_folder):
119162
try:
120163
os.makedirs(archived_folder)
121164
except Exception:
122165
pass # Should only be reachable during multi-threaded runs
123-
if not "".join(sys.argv) == "-c":
124-
# Only move log files if the test run is not multi-threaded.
125-
# (Running tests with "-n NUM" will create threads that only
126-
# have "-c" in the sys.argv list. Easy to catch.)
127-
archived_logs = "%slogs_%s" % (
128-
archived_folder, int(time.time()))
129-
if "_logs" not in log_path:
130-
# Don't move files in a custom-named log folder (in case
131-
# the user specifed a folder with important files in it)
132-
# unless the folder name contains "_logs".
133-
# The default name for the log folder is "latest_logs".
134-
return
166+
archived_logs = "%slogs_%s" % (
167+
archived_folder, int(time.time()))
168+
169+
if len(os.listdir(log_path)) > 0:
135170
shutil.move(log_path, archived_logs)
136171
os.makedirs(log_path)
137172
if not settings.ARCHIVE_EXISTING_LOGS and not archive_logs:
138173
shutil.rmtree(archived_logs)
139-
elif len(os.listdir(archived_logs)) == 0:
140-
# Don't archive an empty directory
141-
shutil.rmtree(archived_logs)
142174
else:
143-
# Logs are saved/archived
144-
pass
175+
if ("-n" in sys.argv or "".join(sys.argv) == "-c"):
176+
# Logs are saved/archived now if tests are multithreaded
177+
pass
178+
else:
179+
shutil.rmtree(archived_logs) # (Archive test run later)

seleniumbase/plugins/base_plugin.py

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
# -*- coding: utf-8 -*-
22
""" This is the Nose plugin for setting a test environment and saving logs. """
33

4-
import os
54
import sys
65
import time
76
from nose.plugins import Plugin
@@ -17,7 +16,6 @@ class Base(Plugin):
1716
--env=ENV (Set a test environment. Use "self.env" to use this in tests.)
1817
--data=DATA (Extra data to pass to tests. Use "self.data" in tests.)
1918
--settings-file=FILE (Overrides SeleniumBase settings.py values.)
20-
--log-path=LOG_PATH (The directory where log files get saved to.)
2119
--archive-logs (Archive old log files instead of deleting them.)
2220
--report (The option to create a fancy report after tests complete.)
2321
--show-report If self.report is turned on, then the report will
@@ -59,7 +57,7 @@ def options(self, parser, env):
5957
'--log_path', '--log-path',
6058
dest='log_path',
6159
default='latest_logs/',
62-
help='Where the log files are saved.')
60+
help='Where the log files are saved. (No longer editable!)')
6361
parser.add_option(
6462
'--archive_logs', '--archive-logs',
6563
action="store_true",
@@ -102,16 +100,13 @@ def configure(self, options, conf):
102100
self.page_results_list = []
103101
self.test_count = 0
104102
self.import_error = False
105-
log_path = options.log_path
103+
log_path = 'latest_logs/'
106104
archive_logs = options.archive_logs
107105
log_helper.log_folder_setup(log_path, archive_logs)
108106
if self.report_on:
109107
report_helper.clear_out_old_report_logs(archive_past_runs=False)
110108

111109
def beforeTest(self, test):
112-
test_logpath = self.options.log_path + "/" + test.id()
113-
if not os.path.exists(test_logpath):
114-
os.makedirs(test_logpath)
115110
test.test.environment = self.options.environment
116111
test.test.env = self.options.environment # Add a shortened version
117112
test.test.data = self.options.data
@@ -123,6 +118,8 @@ def beforeTest(self, test):
123118
self.start_time = float(time.time())
124119

125120
def finalize(self, result):
121+
log_helper.archive_logs_if_set(
122+
self.options.log_path, self.options.archive_logs)
126123
if self.report_on:
127124
if not self.import_error:
128125
report_helper.add_bad_page_log_file(self.page_results_list)

seleniumbase/plugins/basic_test_info.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
import os
1313
import codecs
14+
import time
1415
import traceback
1516
from nose.plugins import Plugin
1617
from seleniumbase.config import settings
@@ -55,8 +56,9 @@ def addFailure(self, test, err, capt=None, tbinfo=None):
5556

5657
def __log_test_error_data(self, log_file, test, err, type):
5758
data_to_save = []
58-
data_to_save.append("Last_Page: %s" % test.driver.current_url)
59-
data_to_save.append("Browser: %s " % self.options.browser)
59+
data_to_save.append("Last Page: %s" % test.driver.current_url)
60+
data_to_save.append(" Browser: %s" % self.options.browser)
61+
data_to_save.append("Timestamp: %s" % int(time.time()))
6062
data_to_save.append("Server: %s " % self.options.servername)
6163
data_to_save.append("%s: %s" % (type, err[0]))
6264
data_to_save.append("Traceback: " + ''.join(

seleniumbase/plugins/pytest_plugin.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ def pytest_addoption(parser):
2929
--headless (The option to run tests headlessly. The default on Linux OS.)
3030
--headed (The option to run tests with a GUI on Linux OS.)
3131
--start-page=URL (The starting URL for the web browser when tests begin.)
32-
--log-path=LOG_PATH (The directory where log files get saved to.)
3332
--archive-logs (Archive old log files instead of deleting them.)
3433
--slow (The option to slow down the automation.)
3534
--demo (The option to visually see test actions as they occur.)
@@ -113,7 +112,7 @@ def pytest_addoption(parser):
113112
parser.addoption('--log_path', '--log-path',
114113
dest='log_path',
115114
default='latest_logs/',
116-
help='Where the log files are saved.')
115+
help='Where log files are saved. (No longer editable!)')
117116
parser.addoption('--archive_logs', '--archive-logs',
118117
action="store_true",
119118
dest='archive_logs',
@@ -380,7 +379,7 @@ def pytest_configure(config):
380379
sb_config.settings_file = config.getoption('settings_file')
381380
sb_config.user_data_dir = config.getoption('user_data_dir')
382381
sb_config.database_env = config.getoption('database_env')
383-
sb_config.log_path = config.getoption('log_path')
382+
sb_config.log_path = 'latest_logs/' # (No longer editable!)
384383
sb_config.archive_logs = config.getoption('archive_logs')
385384
sb_config.slow_mode = config.getoption('slow_mode')
386385
sb_config.demo_mode = config.getoption('demo_mode')
@@ -402,7 +401,8 @@ def pytest_configure(config):
402401
sb_config.pytest_html_report = config.getoption('htmlpath') # --html=FILE
403402

404403
if sb_config.reuse_session:
405-
if "".join(sys.argv) == "-c": # Can't "reuse_session" if multithreaded
404+
if "-n" in sys.argv or "".join(sys.argv) == "-c":
405+
# Can't "reuse_session" if multithreaded
406406
sb_config.reuse_session = False
407407

408408
if "linux" in sys.platform and (
@@ -432,6 +432,7 @@ def pytest_unconfigure():
432432
except Exception:
433433
pass
434434
sb_config.shared_driver = None
435+
log_helper.archive_logs_if_set(sb_config.log_path, sb_config.archive_logs)
435436

436437

437438
def pytest_runtest_setup():

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545

4646
setup(
4747
name='seleniumbase',
48-
version='1.34.17',
48+
version='1.34.18',
4949
description='Fast, Easy, and Reliable Browser Automation & Testing.',
5050
long_description=long_description,
5151
long_description_content_type='text/markdown',
@@ -117,7 +117,7 @@
117117
'packaging>=20.0',
118118
'pygments>=2.5.2',
119119
'colorama==0.4.3',
120-
'coverage>=5.0.2',
120+
'coverage>=5.0.3',
121121
'pymysql==0.9.3',
122122
'pyotp==2.3.0',
123123
'boto==2.49.0',

0 commit comments

Comments
 (0)