diff options
Diffstat (limited to 'test/conftest.py')
-rw-r--r-- | test/conftest.py | 174 |
1 files changed, 174 insertions, 0 deletions
diff --git a/test/conftest.py b/test/conftest.py new file mode 100644 index 0000000..4eea714 --- /dev/null +++ b/test/conftest.py @@ -0,0 +1,174 @@ +# SPDX-License-Identifier: GPL-3.0-or-later + +""" +Common fixtures for Haketilo unit tests +""" + +# This file is part of Haketilo. +# +# Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. +# +# I, Wojtek Kosior, thereby promise not to sue for violation of this file's +# license. Although I request that you do not make use of this code in a +# proprietary program, I am not going to enforce this in court. + +import pytest +from pathlib import Path +from tempfile import TemporaryDirectory +from selenium.webdriver.common.by import By +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC + +from .profiles import firefox_safe_mode +from .server import do_an_internet +from .extension_crafting import make_extension +from .world_wide_library import start_serving_script, dump_scripts +from .misc_constants import here + +@pytest.fixture(scope="session") +def proxy(): + httpd = do_an_internet() + yield httpd + httpd.shutdown() + +@pytest.fixture(scope="session") +def _driver(proxy): + with firefox_safe_mode() as driver: + yield driver + driver.quit() + +def close_all_but_one_window(driver): + while len(driver.window_handles) > 1: + driver.switch_to.window(driver.window_handles[-1]) + driver.close() + driver.switch_to.window(driver.window_handles[0]) + +@pytest.fixture() +def driver(_driver, request): + nav_target = request.node.get_closest_marker('get_page') + close_all_but_one_window(_driver) + _driver.get(nav_target.args[0] if nav_target else 'about:blank') + _driver.implicitly_wait(0) + yield _driver + +@pytest.fixture() +def webextension(driver, request): + ext_data = request.node.get_closest_marker('ext_data') + if ext_data is None: + raise Exception('"webextension" fixture requires "ext_data" marker to be set') + ext_data = ext_data.args[0].copy() + + navigate_to = ext_data.get('navigate_to') + if navigate_to is not None: + del ext_data['navigate_to'] + + driver.get('https://gotmyowndoma.in/') + ext_path = make_extension(Path(driver.firefox_profile.path), **ext_data) + addon_id = driver.install_addon(str(ext_path), temporary=True) + WebDriverWait(driver, 10).until( + EC.url_matches('^moz-extension://.*') + ) + + if navigate_to is not None: + testpage_url = driver.execute_script('return window.location.href;') + driver.get(testpage_url.replace('testpage.html', navigate_to)) + + yield + + close_all_but_one_window(driver) + driver.get('https://gotmyowndoma.in/') + driver.uninstall_addon(addon_id) + ext_path.unlink() + +@pytest.fixture() +def haketilo(driver): + addon_id = driver.install_addon(str(here.parent / 'mozilla-build.zip'), + temporary=True) + + yield + + driver.uninstall_addon(addon_id) + +script_injector_script = '''\ +/* + * Selenium by default executes scripts in some weird one-time context. We want + * separately-loaded scripts to be able to access global variables defined + * before, including those declared with `const` or `let`. To achieve that, we + * run our scripts by injecting them into the page with a <script> tag that runs + * javascript served by our proxy. We use custom properties of the `window` + * object to communicate with injected code. + */ +const inject = async () => { + delete window.haketilo_selenium_return_value; + delete window.haketilo_selenium_exception; + window.returnval = val => window.haketilo_selenium_return_value = val; + + const injectee = document.createElement('script'); + injectee.src = arguments[0]; + injectee.type = "application/javascript"; + injectee.async = true; + const prom = new Promise(cb => injectee.onload = cb); + + window.arguments = arguments[1]; + document.body.append(injectee); + + await prom; + + /* + * To ease debugging, we want this script to signal all exceptions from the + * injectee. + */ + if (window.haketilo_selenium_exception !== false) + throw ['haketilo_selenium_error', + 'Error in injected script! Check your geckodriver.log and ./injected_scripts/!']; + + return window.haketilo_selenium_return_value; +} +return inject(); +''' + +def _execute_in_page_context(driver, script, args): + script = script + '\n;\nwindow.haketilo_selenium_exception = false;' + script_url = start_serving_script(script) + + try: + result = driver.execute_script(script_injector_script, script_url, args) + if type(result) is list and len(result) == 2 and \ + result[0] == 'haketilo_selenium_error': + raise Exception(result[1]) + return result + except Exception as e: + dump_scripts() + raise e from None + +# Some fixtures here just define functions that operate on driver. We should +# consider making them into webdriver wrapper class methods. + +@pytest.fixture() +def execute_in_page(driver): + def do_execute(script, *args): + return _execute_in_page_context(driver, script, args) + + yield do_execute + +@pytest.fixture() +def wait_elem_text(driver): + def do_wait(id, text): + WebDriverWait(driver, 10).until( + EC.text_to_be_present_in_element((By.ID, id), text) + ) + + yield do_wait |