commit f3616395722e5d80a794ba9a74738acfe40c19f1
parent af4d54f136dd57d3deecc0b44beeee7ece97b0c2
Author: Beatriz Rizental <brizental@torproject.org>
Date: Tue, 27 May 2025 14:37:29 +0200
TB 43817: Add tests for Tor Browser
This is a catch all commits for adding any tests or
testing infrastructure that doesn't obviously fit
any other commit.
Diffstat:
6 files changed, 191 insertions(+), 0 deletions(-)
diff --git a/python/mozbuild/mozbuild/action/test_archive.py b/python/mozbuild/mozbuild/action/test_archive.py
@@ -229,6 +229,12 @@ ARCHIVE_FILES = {
"pattern": "**",
"dest": "certs",
},
+ {
+ "source": buildconfig.topsrcdir,
+ "base": "",
+ "pattern": "testing/tor",
+ "dest": "tor",
+ },
],
"cppunittest": [
{"source": STAGE, "base": "", "pattern": "cppunittest/**"},
diff --git a/testing/marionette/harness/marionette_harness/tests/integration-tests.toml b/testing/marionette/harness/marionette_harness/tests/integration-tests.toml
@@ -56,6 +56,10 @@
["include:../../../../../netwerk/test/marionette/manifest.toml"]
+# tor tests
+
+["include:../../../../../testing/tor/manifest.toml"]
+
# toolkit tests
["include:../../../../../toolkit/components/antitracking/bouncetrackingprotection/test/marionette/manifest.toml"]
diff --git a/testing/moz.build b/testing/moz.build
@@ -23,3 +23,5 @@ DIRS += ["manifest", "mozbase", "mozharness"]
PERFTESTS_MANIFESTS += [
"performance/perftest.toml",
]
+
+MARIONETTE_MANIFESTS += ["tor/manifest.toml"]
diff --git a/testing/tor/manifest.toml b/testing/tor/manifest.toml
@@ -0,0 +1,6 @@
+[DEFAULT]
+tags = "tor"
+
+["test_circuit_isolation.py"]
+
+["test_network_check.py"]
diff --git a/testing/tor/test_circuit_isolation.py b/testing/tor/test_circuit_isolation.py
@@ -0,0 +1,101 @@
+from ipaddress import ip_address
+
+from marionette_driver import By
+from marionette_driver.errors import NoSuchElementException
+from marionette_harness import MarionetteTestCase
+
+TOR_BOOTSTRAP_TIMEOUT = 30000 # 30s
+
+
+class TestCircuitIsolation(MarionetteTestCase):
+ def tearDown(self):
+ self.marionette.restart(in_app=False, clean=True)
+ super(TestCircuitIsolation, self).tearDown()
+
+ def bootstrap(self):
+ with self.marionette.using_context("chrome"):
+ self.marionette.execute_async_script(
+ """
+ const { TorConnect, TorConnectTopics } = ChromeUtils.importESModule(
+ "resource://gre/modules/TorConnect.sys.mjs"
+ );
+ const [resolve] = arguments;
+
+ function waitForBootstrap() {
+ const topic = TorConnectTopics.BootstrapComplete;
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, topic);
+ resolve();
+ }, topic);
+ TorConnect.beginBootstrapping();
+ }
+
+ const stageTopic = TorConnectTopics.StageChange;
+ function stageObserver() {
+ if (TorConnect.canBeginNormalBootstrap) {
+ Services.obs.removeObserver(stageObserver, stageTopic);
+ waitForBootstrap();
+ }
+ }
+ Services.obs.addObserver(stageObserver, stageTopic);
+ stageObserver();
+ """,
+ script_timeout=TOR_BOOTSTRAP_TIMEOUT,
+ )
+
+ def extract_from_check_tpo(self):
+ # Fetch the IP from check.torproject.org.
+ # In addition to that, since we are loading this page, we
+ # perform some additional sanity checks.
+ self.marionette.navigate("https://check.torproject.org/")
+ # When check.tpo's check succeed (i.e., it thinks we're
+ # connecting through tor), we should be able to find a h1.on,
+ # with some message...
+ on = self.marionette.find_element(By.CLASS_NAME, "on")
+ self.assertIsNotNone(
+ on,
+ "h1.on not found, you might not be connected through tor",
+ )
+ # ... but if it fails, the message is inside a h1.off. We want
+ # to make sure we do not find that either (even though there is
+ # no reason for both of the h1 to be outputted at the moment).
+ self.assertRaises(
+ NoSuchElementException,
+ self.marionette.find_element,
+ By.CLASS_NAME,
+ "off",
+ )
+ ip = self.marionette.find_element(By.TAG_NAME, "strong")
+ return ip_address(ip.text.strip())
+
+ def extract_generic(self, url):
+ # Fetch the IP address from any generic page that only contains
+ # the address.
+ self.marionette.navigate(url)
+ return ip_address(
+ self.marionette.execute_script(
+ "return document.documentElement.textContent"
+ ).strip()
+ )
+
+ def test_circuit_isolation(self):
+ self.bootstrap()
+ ips = [
+ self.extract_from_check_tpo(),
+ self.extract_generic("https://am.i.mullvad.net/ip"),
+ self.extract_generic("https://test1.ifconfig.me/ip"),
+ ]
+ self.logger.info(f"Found the following IP addresses: {ips}")
+ unique_ips = set(ips)
+ self.logger.info(f"Found the following unique IP addresses: {unique_ips}")
+ self.assertEqual(
+ len(ips),
+ len(unique_ips),
+ "Some of the IP addresses we got are not unique.",
+ )
+ duplicate = self.extract_generic("https://test2.ifconfig.me/ip")
+ self.assertEqual(
+ ips[-1],
+ duplicate,
+ "Two IPs that were expected to be equal are different, we might be over isolating!",
+ )
diff --git a/testing/tor/test_network_check.py b/testing/tor/test_network_check.py
@@ -0,0 +1,72 @@
+from marionette_driver import By, Wait, errors
+from marionette_driver.localization import L10n
+from marionette_harness import MarionetteTestCase
+
+NETWORK_CHECK_URL = "https://check.torproject.org/"
+TOR_BOOTSTRAP_TIMEOUT = 30 # 30s
+
+STRINGS_LOCATION = "chrome://torbutton/locale/torConnect.properties"
+
+
+class TestNetworkCheck(MarionetteTestCase):
+ def setUp(self):
+ MarionetteTestCase.setUp(self)
+
+ self.l10n = L10n(self.marionette)
+
+ def tearDown(self):
+ self.marionette.restart(in_app=False, clean=True)
+ super(TestNetworkCheck, self).tearDown()
+
+ def attemptConnection(self, tries=1):
+ if tries > 3:
+ self.assertTrue(False, "Failed to connect to Tor after 3 attempts")
+
+ connectBtn = self.marionette.find_element(By.ID, "connectButton")
+ Wait(self.marionette, timeout=10).until(
+ lambda _: connectBtn.is_displayed(),
+ message="Timed out waiting for tor connect button to show up.",
+ )
+ connectBtn.click()
+
+ try:
+
+ def check(m):
+ if not m.get_url().startswith("about:torconnect"):
+ # We have finished connecting and have been redirected.
+ return True
+
+ try:
+ heading = self.marionette.find_element(By.ID, "tor-connect-heading")
+ except errors.NoSuchElementException:
+ # Page is probably redirecting.
+ return False
+
+ if heading.text not in [
+ self.l10n.localize_property(
+ [STRINGS_LOCATION], "torConnect.torConnecting"
+ ),
+ self.l10n.localize_property(
+ [STRINGS_LOCATION], "torConnect.torConnected"
+ ),
+ ]:
+ raise ValueError("Tor connect page is not connecting or connected")
+
+ return False
+
+ Wait(self.marionette, timeout=TOR_BOOTSTRAP_TIMEOUT).until(check)
+ except (errors.TimeoutException, ValueError):
+ cancelBtn = self.marionette.find_element(By.ID, "cancelButton")
+ if cancelBtn.is_displayed():
+ cancelBtn.click()
+
+ self.attemptConnection(tries + 1)
+
+ def test_network_check(self):
+ self.attemptConnection()
+ self.marionette.navigate(NETWORK_CHECK_URL)
+ self.assertRegex(
+ self.marionette.title,
+ r"^Congratulations\.",
+ f"{NETWORK_CHECK_URL} should have the expected title.",
+ )