Skip to content

Commit eeff476

Browse files
committed
Improve multi-threading
1 parent a1a3b65 commit eeff476

File tree

5 files changed

+96
-47
lines changed

5 files changed

+96
-47
lines changed

seleniumbase/core/browser_launcher.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1769,17 +1769,27 @@ def _add_chrome_proxy_extension(
17691769
):
17701770
# Single-threaded
17711771
if zip_it:
1772-
proxy_helper.create_proxy_ext(
1773-
proxy_string, proxy_user, proxy_pass, bypass_list
1774-
)
1775-
proxy_zip = proxy_helper.PROXY_ZIP_PATH
1776-
chrome_options.add_extension(proxy_zip)
1772+
proxy_zip_lock = fasteners.InterProcessLock(PROXY_ZIP_LOCK)
1773+
with proxy_zip_lock:
1774+
proxy_helper.create_proxy_ext(
1775+
proxy_string, proxy_user, proxy_pass, bypass_list
1776+
)
1777+
proxy_zip = proxy_helper.PROXY_ZIP_PATH
1778+
chrome_options.add_extension(proxy_zip)
17771779
else:
1778-
proxy_helper.create_proxy_ext(
1779-
proxy_string, proxy_user, proxy_pass, bypass_list, zip_it=False
1780-
)
1781-
proxy_dir_path = proxy_helper.PROXY_DIR_PATH
1782-
chrome_options = add_chrome_ext_dir(chrome_options, proxy_dir_path)
1780+
proxy_dir_lock = fasteners.InterProcessLock(PROXY_DIR_LOCK)
1781+
with proxy_dir_lock:
1782+
proxy_helper.create_proxy_ext(
1783+
proxy_string,
1784+
proxy_user,
1785+
proxy_pass,
1786+
bypass_list,
1787+
zip_it=False,
1788+
)
1789+
proxy_dir_path = proxy_helper.PROXY_DIR_PATH
1790+
chrome_options = add_chrome_ext_dir(
1791+
chrome_options, proxy_dir_path
1792+
)
17831793
else:
17841794
# Multi-threaded
17851795
if zip_it:
@@ -1808,7 +1818,7 @@ def _add_chrome_proxy_extension(
18081818
proxy_user,
18091819
proxy_pass,
18101820
bypass_list,
1811-
False,
1821+
zip_it=False,
18121822
)
18131823
chrome_options = add_chrome_ext_dir(
18141824
chrome_options, proxy_helper.PROXY_DIR_PATH

seleniumbase/core/log_helper.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from seleniumbase import config as sb_config
88
from seleniumbase.config import settings
99
from seleniumbase.fixtures import constants
10+
from seleniumbase.fixtures import shared_utils
1011

1112
python3_11_or_newer = False
1213
if sys.version_info >= (3, 11):
@@ -33,6 +34,8 @@ def log_screenshot(test_logpath, driver, screenshot=None, get=False):
3334
if screenshot != screenshot_warning:
3435
with open(screenshot_path, "wb") as file:
3536
file.write(screenshot)
37+
with suppress(Exception):
38+
shared_utils.make_writable(screenshot_path)
3639
else:
3740
print("WARNING: %s" % screenshot_warning)
3841
if get:
@@ -282,13 +285,14 @@ def log_test_failure_data(test, test_logpath, driver, browser, url=None):
282285
sb_config._report_time = the_time
283286
sb_config._report_traceback = traceback_message
284287
sb_config._report_exception = exc_message
285-
with suppress(Exception):
286-
if not os.path.exists(test_logpath):
288+
if not os.path.exists(test_logpath):
289+
with suppress(Exception):
287290
os.makedirs(test_logpath)
288291
with suppress(Exception):
289292
log_file = codecs.open(basic_file_path, "w+", encoding="utf-8")
290293
log_file.writelines("\r\n".join(data_to_save))
291294
log_file.close()
295+
shared_utils.make_writable(basic_file_path)
292296

293297

294298
def log_skipped_test_data(test, test_logpath, driver, browser, reason):
@@ -343,6 +347,7 @@ def log_skipped_test_data(test, test_logpath, driver, browser, reason):
343347
log_file = codecs.open(file_path, "w+", encoding="utf-8")
344348
log_file.writelines("\r\n".join(data_to_save))
345349
log_file.close()
350+
shared_utils.make_writable(file_path)
346351

347352

348353
def log_page_source(test_logpath, driver, source=None):
@@ -365,14 +370,15 @@ def log_page_source(test_logpath, driver, source=None):
365370
"unresponsive, or closed prematurely!</h4>"
366371
)
367372
)
368-
with suppress(Exception):
369-
if not os.path.exists(test_logpath):
373+
if not os.path.exists(test_logpath):
374+
with suppress(Exception):
370375
os.makedirs(test_logpath)
371376
html_file_path = os.path.join(test_logpath, html_file_name)
372377
with suppress(Exception):
373378
html_file = codecs.open(html_file_path, "w+", encoding="utf-8")
374379
html_file.write(page_source)
375380
html_file.close()
381+
shared_utils.make_writable(html_file_path)
376382

377383

378384
def get_test_id(test):

seleniumbase/core/proxy_helper.py

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
import re
33
import warnings
44
import zipfile
5+
from contextlib import suppress
56
from seleniumbase.config import proxy_list
67
from seleniumbase.config import settings
78
from seleniumbase.fixtures import constants
89
from seleniumbase.fixtures import page_utils
10+
from seleniumbase.fixtures import shared_utils
911

1012
DOWNLOADS_DIR = constants.Files.DOWNLOADS_FOLDER
1113
PROXY_ZIP_PATH = os.path.join(DOWNLOADS_DIR, "proxy.zip")
@@ -109,45 +111,48 @@ def create_proxy_ext(
109111
""""minimum_chrome_version":"22.0.0"\n"""
110112
"""}"""
111113
)
112-
import threading
113-
114-
lock = threading.RLock() # Support multi-threaded tests. Eg. "pytest -n=4"
115-
with lock:
116-
abs_path = os.path.abspath(".")
117-
downloads_path = os.path.join(abs_path, DOWNLOADS_DIR)
118-
if not os.path.exists(downloads_path):
119-
os.mkdir(downloads_path)
120-
if zip_it:
121-
zf = zipfile.ZipFile(PROXY_ZIP_PATH, mode="w")
122-
zf.writestr("background.js", background_js)
123-
zf.writestr("manifest.json", manifest_json)
124-
zf.close()
125-
else:
126-
proxy_ext_dir = PROXY_DIR_PATH
127-
if not os.path.exists(proxy_ext_dir):
128-
os.mkdir(proxy_ext_dir)
129-
manifest_file = os.path.join(proxy_ext_dir, "manifest.json")
130-
with open(manifest_file, mode="w") as f:
131-
f.write(manifest_json)
132-
proxy_host = proxy_string.split(":")[0]
133-
proxy_port = proxy_string.split(":")[1]
134-
background_file = os.path.join(proxy_ext_dir, "background.js")
135-
with open(background_file, mode="w") as f:
136-
f.write(background_js)
114+
abs_path = os.path.abspath(".")
115+
downloads_path = os.path.join(abs_path, DOWNLOADS_DIR)
116+
if not os.path.exists(downloads_path):
117+
os.mkdir(downloads_path)
118+
if zip_it:
119+
zf = zipfile.ZipFile(PROXY_ZIP_PATH, mode="w")
120+
zf.writestr("background.js", background_js)
121+
zf.writestr("manifest.json", manifest_json)
122+
zf.close()
123+
with suppress(Exception):
124+
shared_utils.make_writable(PROXY_ZIP_PATH)
125+
else:
126+
proxy_ext_dir = PROXY_DIR_PATH
127+
if not os.path.exists(proxy_ext_dir):
128+
os.mkdir(proxy_ext_dir)
129+
with suppress(Exception):
130+
shared_utils.make_writable(proxy_ext_dir)
131+
manifest_file = os.path.join(proxy_ext_dir, "manifest.json")
132+
with open(manifest_file, mode="w") as f:
133+
f.write(manifest_json)
134+
with suppress(Exception):
135+
shared_utils.make_writable(manifest_json)
136+
proxy_host = proxy_string.split(":")[0]
137+
proxy_port = proxy_string.split(":")[1]
138+
background_file = os.path.join(proxy_ext_dir, "background.js")
139+
with open(background_file, mode="w") as f:
140+
f.write(background_js)
141+
with suppress(Exception):
142+
shared_utils.make_writable(background_js)
137143

138144

139145
def remove_proxy_zip_if_present():
140146
"""Remove Chromium extension zip file used for proxy server authentication.
141147
Used in the implementation of https://stackoverflow.com/a/35293284
142148
for https://stackoverflow.com/questions/12848327/
143149
"""
144-
try:
145-
if os.path.exists(PROXY_ZIP_PATH):
150+
if os.path.exists(PROXY_ZIP_PATH):
151+
with suppress(Exception):
146152
os.remove(PROXY_ZIP_PATH)
147-
if os.path.exists(PROXY_ZIP_LOCK):
153+
if os.path.exists(PROXY_ZIP_LOCK):
154+
with suppress(Exception):
148155
os.remove(PROXY_ZIP_LOCK)
149-
except Exception:
150-
pass
151156

152157

153158
def validate_proxy_string(proxy_string):

seleniumbase/fixtures/shared_utils.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"""Shared utility methods"""
22
import colorama
33
import os
4+
import pathlib
45
import platform
56
import sys
67
import time
@@ -128,6 +129,16 @@ def is_chrome_130_or_newer(self, binary_location=None):
128129
return False
129130

130131

132+
def make_dir_files_writable(dir_path):
133+
# Make all files in the given directory writable.
134+
for file_path in pathlib.Path(dir_path).glob("*"):
135+
if file_path.is_file():
136+
mode = os.stat(file_path).st_mode
137+
mode |= (mode & 0o444) >> 1 # copy R bits to W
138+
with suppress(Exception):
139+
os.chmod(file_path, mode)
140+
141+
131142
def make_writable(file_path):
132143
# Set permissions to: "If you can read it, you can write it."
133144
mode = os.stat(file_path).st_mode

seleniumbase/plugins/pytest_plugin.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2143,6 +2143,9 @@ def _perform_pytest_unconfigure_(config):
21432143
log_helper.archive_logs_if_set(
21442144
constants.Logs.LATEST + "/", sb_config.archive_logs
21452145
)
2146+
if os.path.exists("./assets/"): # Used by pytest-html reports
2147+
with suppress(Exception):
2148+
shared_utils.make_dir_files_writable("./assets/")
21462149
log_helper.clear_empty_logs()
21472150
# Dashboard post-processing: Disable time-based refresh and stamp complete
21482151
if not hasattr(sb_config, "dashboard") or not sb_config.dashboard:
@@ -2207,8 +2210,12 @@ def _perform_pytest_unconfigure_(config):
22072210
)
22082211
with open(html_report_path, "w", encoding="utf-8") as f:
22092212
f.write(the_html_r) # Finalize the HTML report
2213+
with suppress(Exception):
2214+
shared_utils.make_writable(html_report_path)
22102215
with open(html_report_path_copy, "w", encoding="utf-8") as f:
2211-
f.write(the_html_r) # Finalize the HTML report
2216+
f.write(the_html_r) # Finalize the HTML report copy
2217+
with suppress(Exception):
2218+
shared_utils.make_writable(html_report_path_copy)
22122219
assets_style = "./assets/style.css"
22132220
if os.path.exists(assets_style):
22142221
html_style = None
@@ -2223,6 +2230,8 @@ def _perform_pytest_unconfigure_(config):
22232230
)
22242231
with open(assets_style, "w", encoding="utf-8") as f:
22252232
f.write(html_style)
2233+
with suppress(Exception):
2234+
shared_utils.make_writable(assets_style)
22262235
# Done with "pytest_unconfigure" unless using the Dashboard
22272236
return
22282237
stamp = ""
@@ -2304,6 +2313,8 @@ def _perform_pytest_unconfigure_(config):
23042313
)
23052314
with open(dashboard_path, "w", encoding="utf-8") as f:
23062315
f.write(the_html_d) # Finalize the dashboard
2316+
with suppress(Exception):
2317+
shared_utils.make_writable(dashboard_path)
23072318
assets_style = "./assets/style.css"
23082319
if os.path.exists(assets_style):
23092320
html_style = None
@@ -2318,6 +2329,8 @@ def _perform_pytest_unconfigure_(config):
23182329
)
23192330
with open(assets_style, "w", encoding="utf-8") as f:
23202331
f.write(html_style)
2332+
with suppress(Exception):
2333+
shared_utils.make_writable(assets_style)
23212334
# Part 2: Appending a pytest html report with dashboard data
23222335
html_report_path = None
23232336
if sb_config._html_report_name:
@@ -2398,8 +2411,12 @@ def _perform_pytest_unconfigure_(config):
23982411
)
23992412
with open(html_report_path, "w", encoding="utf-8") as f:
24002413
f.write(the_html_r) # Finalize the HTML report
2414+
with suppress(Exception):
2415+
shared_utils.make_writable(html_report_path)
24012416
with open(html_report_path_copy, "w", encoding="utf-8") as f:
2402-
f.write(the_html_r) # Finalize the HTML report
2417+
f.write(the_html_r) # Finalize the HTML report copy
2418+
with suppress(Exception):
2419+
shared_utils.make_writable(html_report_path_copy)
24032420
except KeyboardInterrupt:
24042421
pass
24052422
except Exception:

0 commit comments

Comments
 (0)