Project

General

Profile

Download (5.72 KB) Statistics
| Branch: | Tag: | Revision:

haketilo / test / conftest.py @ 26e4800d

1
# SPDX-License-Identifier: GPL-3.0-or-later
2

    
3
"""
4
Common fixtures for Haketilo unit tests
5
"""
6

    
7
# This file is part of Haketilo.
8
#
9
# Copyright (C) 2021 Wojtek Kosior <koszko@koszko.org>
10
#
11
# This program is free software: you can redistribute it and/or modify
12
# it under the terms of the GNU General Public License as published by
13
# the Free Software Foundation, either version 3 of the License, or
14
# (at your option) any later version.
15
#
16
# This program is distributed in the hope that it will be useful,
17
# but WITHOUT ANY WARRANTY; without even the implied warranty of
18
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
# GNU General Public License for more details.
20
#
21
# You should have received a copy of the GNU General Public License
22
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
23
#
24
# I, Wojtek Kosior, thereby promise not to sue for violation of this file's
25
# license. Although I request that you do not make use of this code in a
26
# proprietary program, I am not going to enforce this in court.
27

    
28
import pytest
29
from pathlib import Path
30
from tempfile import TemporaryDirectory
31
from selenium.webdriver.common.by import By
32
from selenium.webdriver.support.ui import WebDriverWait
33
from selenium.webdriver.support import expected_conditions as EC
34

    
35
from .profiles           import firefox_safe_mode
36
from .server             import do_an_internet
37
from .extension_crafting import make_extension
38
from .world_wide_library import start_serving_script, dump_scripts
39
from .misc_constants     import here
40

    
41
@pytest.fixture(scope="session")
42
def proxy():
43
    httpd = do_an_internet()
44
    yield httpd
45
    httpd.shutdown()
46

    
47
@pytest.fixture(scope="session")
48
def _driver(proxy):
49
    with firefox_safe_mode() as driver:
50
        yield driver
51
        driver.quit()
52

    
53
def close_all_but_one_window(driver):
54
    while len(driver.window_handles) > 1:
55
        driver.switch_to.window(driver.window_handles[-1])
56
        driver.close()
57
    driver.switch_to.window(driver.window_handles[0])
58

    
59
@pytest.fixture()
60
def driver(_driver, request):
61
    nav_target = request.node.get_closest_marker('get_page')
62
    close_all_but_one_window(_driver)
63
    _driver.get(nav_target.args[0] if nav_target else 'about:blank')
64
    _driver.implicitly_wait(0)
65
    yield _driver
66

    
67
@pytest.fixture()
68
def webextension(driver, request):
69
    ext_data = request.node.get_closest_marker('ext_data')
70
    if ext_data is None:
71
        raise Exception('"webextension" fixture requires "ext_data" marker to be set')
72
    ext_data = ext_data.args[0].copy()
73

    
74
    navigate_to = ext_data.get('navigate_to')
75
    if navigate_to is not None:
76
        del ext_data['navigate_to']
77

    
78
    driver.get('https://gotmyowndoma.in/')
79
    ext_path = make_extension(Path(driver.firefox_profile.path), **ext_data)
80
    addon_id = driver.install_addon(str(ext_path), temporary=True)
81
    get_url = lambda d: d.execute_script('return window.ext_page_url;')
82
    ext_page_url = WebDriverWait(driver, 10).until(get_url)
83
    driver.get(ext_page_url)
84

    
85
    if navigate_to is not None:
86
        driver.get(driver.current_url.replace('testpage.html', navigate_to))
87

    
88
    yield
89

    
90
    close_all_but_one_window(driver)
91
    driver.get('https://gotmyowndoma.in/')
92
    driver.uninstall_addon(addon_id)
93
    ext_path.unlink()
94

    
95
@pytest.fixture()
96
def haketilo(driver):
97
    addon_id = driver.install_addon(str(here.parent / 'mozilla-build.zip'),
98
                                    temporary=True)
99

    
100
    yield
101

    
102
    driver.uninstall_addon(addon_id)
103

    
104
script_injector_script = '''\
105
/*
106
 * Selenium by default executes scripts in some weird one-time context. We want
107
 * separately-loaded scripts to be able to access global variables defined
108
 * before, including those declared with `const` or `let`. To achieve that, we
109
 * run our scripts by injecting them into the page with a <script> tag that runs
110
 * javascript served by our proxy. We use custom properties of the `window`
111
 * object to communicate with injected code.
112
 */
113
const inject = async () => {
114
    delete window.haketilo_selenium_return_value;
115
    delete window.haketilo_selenium_exception;
116
    window.returnval = val => window.haketilo_selenium_return_value = val;
117

    
118
    const injectee = document.createElement('script');
119
    injectee.src = arguments[0];
120
    injectee.type = "application/javascript";
121
    injectee.async = true;
122
    const prom = new Promise(cb => injectee.onload = cb);
123

    
124
    window.arguments = arguments[1];
125
    document.body.append(injectee);
126

    
127
    await prom;
128

    
129
    /*
130
     * To ease debugging, we want this script to signal all exceptions from the
131
     * injectee.
132
     */
133
    if (window.haketilo_selenium_exception !== false)
134
        throw ['haketilo_selenium_error',
135
               'Error in injected script! Check your geckodriver.log and ./injected_scripts/!'];
136

    
137
    return window.haketilo_selenium_return_value;
138
}
139
return inject();
140
'''
141

    
142
def _execute_in_page_context(driver, script, args):
143
    script = script + '\n;\nwindow.haketilo_selenium_exception = false;'
144
    script_url = start_serving_script(script)
145

    
146
    try:
147
        result = driver.execute_script(script_injector_script, script_url, args)
148
        if type(result) is list and len(result) == 2 and \
149
           result[0] == 'haketilo_selenium_error':
150
            raise Exception(result[1])
151
        return result
152
    except Exception as e:
153
        dump_scripts()
154
        raise e from None
155

    
156
# Some fixtures here just define functions that operate on driver. We should
157
# consider making them into webdriver wrapper class methods.
158

    
159
@pytest.fixture()
160
def execute_in_page(driver):
161
    def do_execute(script, *args):
162
        return _execute_in_page_context(driver, script, args)
163

    
164
    yield do_execute
165

    
166
@pytest.fixture()
167
def wait_elem_text(driver):
168
    def do_wait(id, text):
169
        WebDriverWait(driver, 10).until(
170
            EC.text_to_be_present_in_element((By.ID, id), text)
171
        )
172

    
173
    yield do_wait
(3-3/11)