tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

fixtures.py (9896B)


      1 import os
      2 
      3 import pytest
      4 import pytest_asyncio
      5 from tests.support.helpers import deep_update
      6 
      7 from .chrome_handler import using_chrome_handler
      8 from .context import using_context
      9 from .helpers import (
     10    Browser,
     11    Geckodriver,
     12    create_custom_profile,
     13    get_pref,
     14    get_profile_folder,
     15    read_user_preferences,
     16    set_pref,
     17 )
     18 
     19 
     20 def pytest_collection_modifyitems(items):
     21    """Auto-apply markers for specific API usage by fixtures"""
     22    for item in items:
     23        if "use_pref" in getattr(item, "fixturenames", ()):
     24            item.add_marker("allow_system_access")
     25 
     26 
     27 def pytest_configure(config):
     28    # register the allow_system_access marker
     29    config.addinivalue_line(
     30        "markers", "allow_system_access: Mark test to allow system access"
     31    )
     32 
     33 
     34 @pytest.fixture(scope="module")
     35 def browser(configuration, firefox_options):
     36    """Start a Firefox instance without using geckodriver.
     37 
     38    geckodriver will automatically use the --remote-allow-hosts and
     39    --remote.allow.origins command line arguments.
     40 
     41    Starting Firefox without geckodriver allows to set those command line arguments
     42    as needed. The fixture method returns the browser instance that should be used
     43    to connect to a RemoteAgent supported protocol (WebDriver BiDi).
     44    """
     45    current_browser = None
     46 
     47    def _browser(
     48        clone_profile=True,
     49        extra_args=None,
     50        extra_prefs=None,
     51        use_bidi=False,
     52        use_marionette=False,
     53    ):
     54        nonlocal current_browser
     55 
     56        webdriver_args = configuration["webdriver"]["args"]
     57        log_level = None
     58        truncate_enabled = True
     59 
     60        if "-vv" or "-vvv" in webdriver_args:
     61            log_level = "Trace"
     62 
     63            if "-vvv" in webdriver_args:
     64                truncate_enabled = False
     65        elif "-v" in webdriver_args:
     66            log_level = "Debug"
     67 
     68        # If the requested preferences and arguments match the ones for the
     69        # already started firefox, we can reuse the current firefox instance,
     70        # return the instance immediately.
     71        if current_browser:
     72            if (
     73                current_browser.extra_args == extra_args
     74                and current_browser.extra_prefs == extra_prefs
     75                and current_browser.is_running
     76                and current_browser.use_bidi == use_bidi
     77                and current_browser.use_marionette == use_marionette
     78                and current_browser.log_level == log_level
     79                and current_browser.truncate_enabled == truncate_enabled
     80            ):
     81                return current_browser
     82 
     83            # Otherwise, if firefox is already started, terminate it because we need
     84            # to create a new instance for the provided preferences.
     85            current_browser.quit()
     86 
     87        binary = configuration["browser"]["binary"]
     88        env = configuration["browser"]["env"]
     89 
     90        profile_path = get_profile_folder(firefox_options)
     91        default_prefs = read_user_preferences(profile_path)
     92        profile = create_custom_profile(
     93            profile_path, default_prefs, clone=clone_profile
     94        )
     95 
     96        current_browser = Browser(
     97            binary,
     98            profile,
     99            extra_args=extra_args,
    100            extra_prefs=extra_prefs,
    101            env=env,
    102            log_level=log_level,
    103            truncate_enabled=truncate_enabled,
    104            use_bidi=use_bidi,
    105            use_marionette=use_marionette,
    106        )
    107        current_browser.start()
    108        return current_browser
    109 
    110    yield _browser
    111 
    112    # Stop firefox at the end of the test module.
    113    if current_browser is not None:
    114        current_browser.quit()
    115        current_browser = None
    116 
    117 
    118 @pytest.fixture
    119 def default_capabilities(request):
    120    """Default capabilities to use for a new WebDriver session for Mozilla specific tests."""
    121 
    122    # Get the value from the overwritten original fixture
    123    capabilities = request.getfixturevalue("default_capabilities")
    124 
    125    allow_system_access = any(
    126        marker.name == "allow_system_access" for marker in request.node.own_markers
    127    )
    128 
    129    if allow_system_access:
    130        deep_update(
    131            capabilities,
    132            {
    133                "moz:firefoxOptions": {
    134                    "args": [
    135                        "--remote-allow-system-access",
    136                    ]
    137                }
    138            },
    139        )
    140 
    141    return capabilities
    142 
    143 
    144 @pytest.fixture
    145 def default_chrome_handler(current_session):
    146    manifest_path = os.path.join(
    147        os.path.abspath(os.path.dirname(__file__)), "chrome-assets", "chrome.manifest"
    148    )
    149    entries = [["content", "marionette-chrome", "chrome/"]]
    150 
    151    with using_chrome_handler(current_session, manifest_path, entries):
    152        yield "chrome://marionette-chrome/content/"
    153 
    154 
    155 @pytest.fixture
    156 def default_preferences(profile_folder):
    157    return read_user_preferences(profile_folder)
    158 
    159 
    160 @pytest.fixture
    161 def new_chrome_window(current_session):
    162    opened_chrome_windows = []
    163 
    164    def _new_chrome_window(url, focus=True):
    165        # Bug 1944570: Replace with BiDi once scripts can be evaluated
    166        # in the parent process.
    167        with using_context(current_session, "chrome"):
    168            new_window = current_session.execute_async_script(
    169                """
    170                  const { NavigableManager } = ChromeUtils.importESModule(
    171                    "chrome://remote/content/shared/NavigableManager.sys.mjs"
    172                  );
    173 
    174                  let [url, focus, resolve] = arguments;
    175 
    176                  function waitForEvent(target, type, args) {
    177                    return new Promise(resolve => {
    178                      let params = Object.assign({once: true}, args);
    179                      target.addEventListener(type, event => {
    180                        dump(`** Received DOM event ${event.type} for ${event.target}\n`);
    181                        resolve();
    182                      }, params);
    183                    });
    184                  }
    185 
    186                  function waitForFocus(win) {
    187                    return Promise.all([
    188                      waitForEvent(win, "activate"),
    189                      waitForEvent(win, "focus", {capture: true}),
    190                    ]);
    191                  }
    192 
    193                  const isLoaded = window =>
    194                    window?.document.readyState === "complete" &&
    195                    !window?.document.isUncommittedInitialDocument;
    196 
    197                  (async function() {
    198                    // Open a window, wait for it to receive focus
    199                    let newWindow = window.openDialog(url, null, "chrome,centerscreen");
    200                    let focused = waitForFocus(newWindow);
    201 
    202                    newWindow.focus();
    203                    await focused;
    204 
    205                    // The new window shouldn't get focused. As such set the
    206                    // focus back to the opening window.
    207                    if (!focus && Services.focus.activeWindow != window) {
    208                      let focused = waitForFocus(window);
    209                      window.focus();
    210                      await focused;
    211                    }
    212 
    213                    // Wait for the new window to be finished loading
    214                    if (isLoaded(newWindow)) {
    215                      resolve(newWindow);
    216                    } else {
    217                      const onLoad = () => {
    218                        if (isLoaded(newWindow)) {
    219                          newWindow.removeEventListener("load", onLoad);
    220                          resolve(newWindow);
    221                        } else {
    222                          dump(`** Target window not loaded yet.  Waiting for the next "load" event\n`);
    223                        }
    224                      };
    225                      newWindow.addEventListener("load", onLoad);
    226                    }
    227                  })();
    228                """,
    229                args=[url, focus],
    230            )
    231 
    232            # Append opened chrome window to automatic closing on teardown
    233            opened_chrome_windows.append(new_window)
    234            return new_window
    235 
    236    yield _new_chrome_window
    237 
    238    with using_context(current_session, "chrome"):
    239        for win in opened_chrome_windows:
    240            try:
    241                current_session.window_handle = win.id
    242                current_session.execute_script("arguments[0].close()", args=[win])
    243            except Exception:
    244                pass
    245 
    246    current_session.window_handle = current_session.handles[0]
    247 
    248 
    249 @pytest.fixture(name="create_custom_profile")
    250 def fixture_create_custom_profile(default_preferences, profile_folder):
    251    profile = None
    252 
    253    def _create_custom_profile(clone=True):
    254        profile = create_custom_profile(
    255            profile_folder, default_preferences, clone=clone
    256        )
    257 
    258        return profile
    259 
    260    yield _create_custom_profile
    261 
    262    # if profile is not None:
    263    if profile:
    264        profile.cleanup()
    265 
    266 
    267 @pytest.fixture(scope="session")
    268 def firefox_options(configuration):
    269    return configuration["capabilities"]["moz:firefoxOptions"]
    270 
    271 
    272 @pytest_asyncio.fixture
    273 async def geckodriver(configuration):
    274    """Start a geckodriver instance directly."""
    275    driver = None
    276 
    277    def _geckodriver(config=None, hostname=None, extra_args=None, extra_env=None):
    278        nonlocal driver
    279 
    280        if config is None:
    281            config = configuration
    282 
    283        driver = Geckodriver(config, hostname, extra_args, extra_env)
    284        driver.start()
    285 
    286        return driver
    287 
    288    yield _geckodriver
    289 
    290    if driver is not None:
    291        await driver.stop()
    292 
    293 
    294 @pytest.fixture
    295 def profile_folder(firefox_options):
    296    return get_profile_folder(firefox_options)
    297 
    298 
    299 @pytest.fixture
    300 def use_pref(session):
    301    """Set a specific pref value."""
    302    reset_values = {}
    303 
    304    def _use_pref(pref, value):
    305        if pref not in reset_values:
    306            reset_values[pref] = get_pref(session, pref)
    307 
    308        set_pref(session, pref, value)
    309 
    310    yield _use_pref
    311 
    312    for pref, reset_value in reset_values.items():
    313        set_pref(session, pref, reset_value)