Skip to content

Commit 5a37678

Browse files
authored
Selenium 4 support (#166)
Make tor-browser-selenium compatible with Selenium 4. The following TorBrowserDriver init parameters are changed: - `timeout` (removed): not supported by webdriver - `options` (selenium.webdriver.firefox.options.Options, added) - `use_custom_profile` (Bool, added) - Add tests to make sure the builtin addons (NoScript and HTTPE) come installed. - Add tests to check whether we can install custom extensions. - Remove the profile directory only if it's temporary (use_custom_profile=False). - Remove the missing browser test - Fix the platform name (linux2 -> linux) * Use the correct profile path * Prevent exceptions due to undefined last_err. * Pick a random Marionette port when custom profile is not used. Hardcoding the Marionette port (2828) makes it impossible to use tbselenium in parallel. After the following bug is fixed we can stop passing the hardcoded port all together: https://bugzilla.mozilla.org/show_bug.cgi?id=1421766 * Use geckodriver 30 in the CI tests
1 parent 21d5f16 commit 5a37678

16 files changed

Lines changed: 198 additions & 142 deletions

tbselenium/common.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
DEFAULT_TOR_DATA_PATH = join(DEFAULT_TBB_DATA_DIR, 'Tor')
1515
TB_CHANGE_LOG_PATH = join(DEFAULT_TBB_TORBROWSER_DIR,
1616
'Docs', 'ChangeLog.txt')
17+
# noscript .xpi path as found in the TBB distributions
18+
DEFAULT_TBB_NO_SCRIPT_XPI_PATH = join(
19+
DEFAULT_TBB_PROFILE_PATH, 'extensions',
20+
'{73a6fe31-595d-460b-a920-fcc0f8843232}.xpi')
1721

1822
# Directories for bundled fonts - Linux only
1923
DEFAULT_FONTCONFIG_PATH = join(DEFAULT_TBB_DATA_DIR, 'fontconfig')
@@ -37,7 +41,6 @@
3741
PORT_BAN_PREFS = ["extensions.torbutton.banned_ports",
3842
"network.security.ports.banned"]
3943

40-
TB_INIT_TIMEOUT = 60
4144

4245
# Test constants
4346
CHECK_TPO_URL = "http://check.torproject.org"

tbselenium/tbdriver.py

Lines changed: 86 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,20 @@
22
from os import environ, chdir
33
from os.path import isdir, isfile, join, abspath
44
from time import sleep
5-
from selenium import webdriver
5+
from http.client import CannotSendRequest
66
from selenium.webdriver.support.ui import WebDriverWait
77
from selenium.webdriver.support import expected_conditions as EC
88
from selenium.webdriver.common.by import By
9-
from selenium.common.exceptions import WebDriverException
9+
from selenium.webdriver.firefox.service import Service
1010
from selenium.webdriver.firefox.webdriver import WebDriver as FirefoxDriver
1111
from selenium.webdriver.firefox.options import Options
12+
from selenium.common.exceptions import WebDriverException
1213
import tbselenium.common as cm
1314
from tbselenium.utils import prepend_to_env_var, is_busy
1415
from tbselenium.tbbinary import TBBinary
15-
from tbselenium.exceptions import (TBDriverConfigError, TBDriverPortError,
16-
TBDriverPathError)
16+
from tbselenium.exceptions import (
17+
TBDriverConfigError, TBDriverPortError, TBDriverPathError)
1718

18-
try:
19-
from httplib import CannotSendRequest
20-
except ImportError:
21-
from http.client import CannotSendRequest
2219

2320
DEFAULT_BANNED_PORTS = "9050,9051,9150,9151"
2421

@@ -38,42 +35,83 @@ def __init__(self,
3835
pref_dict={},
3936
socks_port=None,
4037
control_port=None,
41-
extensions=None,
38+
extensions=[],
4239
default_bridge_type="",
43-
capabilities=None,
44-
headless=False):
45-
40+
headless=False,
41+
options=None,
42+
use_custom_profile=False
43+
):
44+
45+
# use_custom_profile: whether to launch from and *write to* the given
46+
# profile
47+
# False: copy the profile to a tempdir; remove the temp folder on quit
48+
# True: use the given profile without copying. This can be used to keep
49+
# a stateful profile across different launches of the Tor Browser.
50+
# It uses firefox's `-profile`` command line parameter under the hood
51+
52+
self.use_custom_profile = use_custom_profile
4653
self.tor_cfg = tor_cfg
54+
4755
self.setup_tbb_paths(tbb_path, tbb_fx_binary_path,
4856
tbb_profile_path, tor_data_dir)
49-
self.profile = webdriver.FirefoxProfile(self.tbb_profile_path)
50-
self.install_extensions(extensions)
57+
self.options = Options() if options is None else options
58+
install_noscript = False
59+
60+
USE_DEPRECATED_PROFILE_METHOD = True
61+
if self.use_custom_profile:
62+
# launch from and write to this custom profile
63+
self.options.add_argument("-profile")
64+
self.options.add_argument(self.tbb_profile_path)
65+
elif USE_DEPRECATED_PROFILE_METHOD:
66+
# launch from this custom profile
67+
self.options.profile = self.tbb_profile_path
68+
else:
69+
# Launch with no profile at all. This should be used with caution.
70+
# NoScript does not come installed on browsers launched by this
71+
# method, so we install it ourselves
72+
install_noscript = True
73+
5174
self.init_ports(tor_cfg, socks_port, control_port)
5275
self.init_prefs(pref_dict, default_bridge_type)
53-
self.setup_capabilities(capabilities)
5476
self.export_env_vars()
55-
self.binary = self.get_tb_binary(logfile=tbb_logfile_path)
56-
self.binary.add_command_line_options('--class', '"Tor Browser"')
57-
options = Options()
77+
# TODO:
78+
# self.binary = self.get_tb_binary(logfile=tbb_logfile_path)
79+
if use_custom_profile:
80+
tbb_service = Service(
81+
executable_path=executable_path,
82+
log_path=tbb_logfile_path,
83+
service_args=["--marionette-port", "2828"]
84+
)
85+
else:
86+
tbb_service = Service(
87+
executable_path=executable_path,
88+
log_path=tbb_logfile_path
89+
)
90+
self.options.binary = self.tbb_fx_binary_path
91+
self.options.add_argument('--class')
92+
self.options.add_argument('"Tor Browser"')
5893
if headless:
59-
options.set_headless()
94+
self.options.headless = True
95+
6096
super(TorBrowserDriver, self).__init__(
61-
firefox_profile=self.profile,
62-
firefox_binary=self.binary,
63-
capabilities=self.capabilities,
64-
timeout=cm.TB_INIT_TIMEOUT,
65-
service_log_path=tbb_logfile_path,
97+
service=tbb_service,
6698
executable_path=executable_path,
67-
options=options)
99+
options=self.options,
100+
)
68101
self.is_running = True
102+
self.install_extensions(extensions, install_noscript)
103+
self.temp_profile_dir = self.capabilities["moz:profile"]
69104
sleep(1)
70105

71-
def install_extensions(self, extensions):
72-
"""Install the given extension to the profile we are launching."""
73-
if extensions is None:
74-
return
106+
def install_extensions(self, extensions, install_noscript):
107+
"""Install the given extensions to the profile we are launching."""
108+
if install_noscript:
109+
no_script_xpi = join(
110+
self.tbb_path, cm.DEFAULT_TBB_NO_SCRIPT_XPI_PATH)
111+
extensions.append(no_script_xpi)
112+
75113
for extension in extensions:
76-
self.profile.add_extension(extension)
114+
self.install_addon(extension)
77115

78116
def init_ports(self, tor_cfg, socks_port, control_port):
79117
"""Check SOCKS port and Tor config inputs."""
@@ -174,8 +212,8 @@ def add_ports_to_fx_banned_ports(self, socks_port, control_port):
174212
"""
175213
if socks_port in cm.KNOWN_SOCKS_PORTS:
176214
return
177-
tb_prefs = self.profile.default_preferences
178-
set_pref = self.profile.set_preference
215+
tb_prefs = self.options.preferences
216+
set_pref = self.options.set_preference
179217

180218
for port_ban_pref in cm.PORT_BAN_PREFS:
181219
banned_ports = tb_prefs.get(port_ban_pref, DEFAULT_BANNED_PORTS)
@@ -188,7 +226,7 @@ def set_tb_prefs_for_using_system_tor(self, control_port):
188226
189227
We set these prefs for running with Tor started with Stem as well.
190228
"""
191-
set_pref = self.profile.set_preference
229+
set_pref = self.options.set_preference
192230
# Prevent Tor Browser running its own Tor process
193231
set_pref('extensions.torlauncher.start_tor', False)
194232
# TODO: investigate whether these prefs are up to date or not
@@ -206,11 +244,15 @@ def set_tb_prefs_for_using_system_tor(self, control_port):
206244
set_pref('extensions.torlauncher.loglevel', 2)
207245
set_pref('extensions.torlauncher.logmethod', 0)
208246
set_pref('extensions.torlauncher.prompt_at_startup', False)
247+
# disable XPI signature checking
248+
set_pref('xpinstall.signatures.required', False)
249+
set_pref('xpinstall.whitelist.required', False)
209250

210251
def init_prefs(self, pref_dict, default_bridge_type):
211252
self.add_ports_to_fx_banned_ports(self.socks_port, self.control_port)
212-
set_pref = self.profile.set_preference
253+
set_pref = self.options.set_preference
213254
set_pref('browser.startup.page', "0")
255+
set_pref('torbrowser.settings.quickstart.enabled', True)
214256
set_pref('browser.startup.homepage', 'about:newtab')
215257
set_pref('extensions.torlauncher.prompt_at_startup', 0)
216258
# load strategy normal is equivalent to "onload"
@@ -233,7 +275,7 @@ def init_prefs(self, pref_dict, default_bridge_type):
233275
# pref_dict overwrites above preferences
234276
for pref_name, pref_val in pref_dict.items():
235277
set_pref(pref_name, pref_val)
236-
self.profile.update_preferences()
278+
# self.profile.update_preferences()
237279

238280
def export_env_vars(self):
239281
"""Setup LD_LIBRARY_PATH and HOME environment variables.
@@ -249,22 +291,6 @@ def export_env_vars(self):
249291
# Add "TBB_DIR/Browser" to the PATH, see issue #10.
250292
prepend_to_env_var("PATH", self.tbb_browser_dir)
251293

252-
def setup_capabilities(self, caps):
253-
"""Setup the required webdriver capabilities."""
254-
if caps is None:
255-
self.capabilities = {
256-
"marionette": True,
257-
"capabilities": {
258-
"alwaysMatch": {
259-
"moz:firefoxOptions": {
260-
"log": {"level": "info"}
261-
}
262-
}
263-
}
264-
}
265-
else:
266-
self.capabilities = caps
267-
268294
def get_tb_binary(self, logfile=None):
269295
"""Return FirefoxBinary pointing to the TBB's firefox binary."""
270296
tbb_logfile = open(logfile, 'a+') if logfile else None
@@ -280,13 +306,13 @@ def clean_up_profile_dirs(self):
280306
"""Remove temporary profile directories.
281307
Only called when WebDriver.quit() is interrupted
282308
"""
283-
tempfolder = self.profile.tempfolder
284-
profile_path = self.profile.path
309+
if self.use_custom_profile:
310+
# don't remove the profile if we are writing into it
311+
# i.e. stateful mode
312+
return
285313

286-
if tempfolder and isdir(tempfolder):
287-
shutil.rmtree(tempfolder)
288-
if isdir(profile_path):
289-
shutil.rmtree(profile_path)
314+
if self.temp_profile_dir and isdir(self.temp_profile_dir):
315+
shutil.rmtree(self.temp_profile_dir)
290316

291317
def quit(self):
292318
"""Quit the driver. Clean up if the parent's quit fails."""
@@ -295,11 +321,10 @@ def quit(self):
295321
super(TorBrowserDriver, self).quit()
296322
except (CannotSendRequest, AttributeError, WebDriverException):
297323
try: # Clean up if webdriver.quit() throws
298-
if self.w3c:
324+
if hasattr(self, "service"):
299325
self.service.stop()
300-
if hasattr(self, "binary"):
301-
self.binary.kill()
302-
if hasattr(self, "profile"):
326+
if hasattr(self, "options") and hasattr(
327+
self.options, "profile"):
303328
self.clean_up_profile_dirs()
304329
except Exception as e:
305330
print("[tbselenium] Exception while quitting: %s" % e)

tbselenium/test/conftest.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111

1212
def launch_tor():
13+
# TODO: Consider using system tor (when available) to speed tests up
1314
tor_process = None
1415
temp_data_dir = tempfile.mkdtemp()
1516
torrc = {'ControlPort': str(cm.STEM_CONTROL_PORT),

tbselenium/test/fixtures.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,7 @@
77
from tbselenium.utils import launch_tbb_tor_with_stem, is_busy, read_file
88
from selenium.common.exceptions import TimeoutException, WebDriverException
99

10-
try:
11-
from httplib import CannotSendRequest
12-
except ImportError:
13-
from http.client import CannotSendRequest
10+
from http.client import CannotSendRequest
1411

1512

1613
MAX_FIXTURE_TRIES = 3
@@ -31,18 +28,19 @@ def __init__(self, *args, **kwargs):
3128
return super(TBDriverFixture, self).__init__(*args, **kwargs)
3229
except (TimeoutException, WebDriverException,
3330
socket_error) as last_err:
34-
print ("\nTBDriver init error. Attempt %s %s" %
35-
((tries + 1), last_err))
31+
print("\nERROR: TBDriver init error. Attempt %s %s" %
32+
((tries + 1), last_err))
3633
if FORCE_TB_LOGS_DURING_TESTS:
3734
logs = read_file(log_file)
3835
if len(logs):
3936
print("TB logs:\n%s\n(End of TB logs)" % logs)
4037
super(TBDriverFixture, self).quit() # clean up
4138
continue
4239
# Raise if we didn't return yet
43-
to_raise = last_err if last_err else\
44-
TorBrowserDriverInitError("Cannot initialize")
45-
raise to_raise
40+
try:
41+
raise last_err
42+
except Exception:
43+
raise TorBrowserDriverInitError("Cannot initialize")
4644

4745
def __del__(self):
4846
# remove the temp log file if we created
@@ -83,13 +81,14 @@ def load_url_ensure(self, *args, **kwargs):
8381
return
8482
except (TimeoutException,
8583
CannotSendRequest) as last_err:
86-
print ("\nload_url timed out. Attempt %s %s" %
87-
((tries + 1), last_err))
84+
print("\nload_url timed out. Attempt %s %s" %
85+
((tries + 1), last_err))
8886
continue
8987
# Raise if we didn't return yet
90-
to_raise = last_err if last_err else\
91-
WebDriverException("Can't load the page")
92-
raise to_raise
88+
try:
89+
raise last_err
90+
except Exception:
91+
raise WebDriverException("Can't load the page")
9392

9493

9594
def launch_tbb_tor_with_stem_fixture(*args, **kwargs):
@@ -98,12 +97,13 @@ def launch_tbb_tor_with_stem_fixture(*args, **kwargs):
9897
try:
9998
return launch_tbb_tor_with_stem(*args, **kwargs)
10099
except OSError as last_err:
101-
print ("\nlaunch_tor try %s %s" % ((tries + 1), last_err))
100+
print("\nlaunch_tor try %s %s" % ((tries + 1), last_err))
102101
if "timeout without success" in str(last_err):
103102
continue
104103
else: # we don't want to retry if this is not a timeout
105104
raise
106105
# Raise if we didn't return yet
107-
to_raise = last_err if last_err else\
108-
StemLaunchError("Cannot start Tor")
109-
raise to_raise
106+
try:
107+
raise last_err
108+
except Exception:
109+
raise StemLaunchError("Cannot start Tor")

0 commit comments

Comments
 (0)