• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

fiduswriter / fiduswriter / 28323408770

28 Jun 2026 01:14PM UTC coverage: 88.414% (-0.06%) from 88.472%
28323408770

push

github

johanneswilm
@fiduswriter/document: 0.1.0 + fwtoolkit 0.1.3

10829 of 12248 relevant lines covered (88.41%)

5.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

83.89
fiduswriter/testing/selenium_helper.py
1
import re
19✔
2
import os
19✔
3
import time
19✔
4
from urllib3.exceptions import MaxRetryError, ReadTimeoutError
19✔
5
from selenium.common.exceptions import (
19✔
6
    ElementClickInterceptedException,
7
    TimeoutException,
8
    WebDriverException,
9
)
10
from selenium.webdriver.support.wait import WebDriverWait
19✔
11
from selenium.webdriver.support import expected_conditions as EC
19✔
12
from selenium.webdriver.common.by import By
19✔
13
from selenium import webdriver
19✔
14
from selenium.webdriver.chrome.service import Service as ChromiumService
19✔
15
from webdriver_manager.chrome import ChromeDriverManager
19✔
16
from webdriver_manager.core.os_manager import ChromeType
19✔
17

18
from allauth.account.models import EmailAddress
19✔
19
from django.contrib.auth.hashers import make_password
19✔
20
from django.contrib.auth import get_user_model
19✔
21
from django.conf import settings
19✔
22
from django.test import Client
19✔
23
from django.contrib.contenttypes.models import ContentType
19✔
24
import logging
19✔
25

26
logger = logging.getLogger(__name__)
19✔
27

28

29
class SeleniumHelper:
19✔
30
    """
31
    Methods for manipulating django and the browser for testing purposes.
32
    """
33

34
    login_page = "/"
19✔
35

36
    def find_urls(self, string):
19✔
37
        return re.findall(
2✔
38
            (
39
                "http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|"
40
                "(?:%[0-9a-fA-F][0-9a-fA-F]))+"
41
            ),
42
            string,
43
        )
44

45
    # create django data
46
    def create_user(
19✔
47
        self, username="User", email="test@example.com", passtext="p4ssw0rd"
48
    ):
49
        User = get_user_model()
18✔
50
        user = User.objects.create(
18✔
51
            username=username,
52
            email=email,
53
            password=make_password(passtext),
54
            is_active=True,
55
        )
56
        user.save()
18✔
57

58
        # avoid the unverified-email login trap
59
        EmailAddress.objects.create(
18✔
60
            user=user, email=email, verified=True, primary=True
61
        ).save()
62

63
        return user
18✔
64

65
    # drive browser
66
    def login_user(self, user, driver, client):
19✔
67
        client.force_login(user=user)
13✔
68
        cookie = client.cookies[settings.SESSION_COOKIE_NAME]
13✔
69
        # output the cookie to the console for debugging
70
        logger.debug("cookie: %s" % cookie.value)
13✔
71
        if driver.current_url == "data:,":
13✔
72
            # To set the cookie at the right domain we load the front page.
73
            driver.get(f"{self.live_server_url}{self.login_page}")
13✔
74
            WebDriverWait(driver, self.wait_time).until(
13✔
75
                EC.presence_of_element_located((By.ID, "id-login"))
76
            )
77
        driver.add_cookie(
13✔
78
            {
79
                "name": settings.SESSION_COOKIE_NAME,
80
                "value": cookie.value,
81
                "secure": False,
82
                "path": "/",
83
            }
84
        )
85

86
    def login_user_manually(self, user, driver, passtext="p4ssw0rd"):
19✔
87
        username = user.username
×
88
        driver.delete_cookie(settings.SESSION_COOKIE_NAME)
×
89
        driver.get("{}{}".format(self.live_server_url, "/"))
×
90
        driver.find_element(By.ID, "id-login").send_keys(username)
×
91
        driver.find_element(By.ID, "id-password").send_keys(passtext)
×
92
        driver.find_element(By.ID, "login-submit").click()
×
93
        # Wait until there is an element with the ID user-preferences
94
        # which is only present on the dashboard.
95
        WebDriverWait(driver, self.wait_time).until(
×
96
            EC.presence_of_element_located((By.ID, "user-preferences"))
97
        )
98

99
    def logout_user(self, driver, client):
19✔
100
        client.logout()
2✔
101
        driver.delete_cookie(settings.SESSION_COOKIE_NAME)
2✔
102

103
    def wait_until_file_exists(self, path, wait_time):
19✔
104
        count = 0
4✔
105
        while not os.path.exists(path):
4✔
106
            time.sleep(1)
4✔
107
            count += 1
4✔
108
            if count > wait_time:
4✔
109
                break
×
110

111
    def retry_click(self, driver, selector, retries=5):
19✔
112
        count = 0
2✔
113
        while count < retries:
2✔
114
            try:
2✔
115
                WebDriverWait(driver, self.wait_time).until(
2✔
116
                    EC.element_to_be_clickable(selector)
117
                ).click()
118
                break
2✔
119
            except ElementClickInterceptedException:
1✔
120
                # Alerts may be overlapping the target; wait for them to clear.
121
                try:
1✔
122
                    WebDriverWait(driver, 6).until(
1✔
123
                        EC.invisibility_of_element_located(
124
                            (By.CSS_SELECTOR, "#fw-alerts-outer-wrapper li")
125
                        )
126
                    )
127
                except TimeoutException:
×
128
                    pass
×
129
                count += 1
1✔
130
                time.sleep(1)
1✔
131

132
    def click_new_document_button(self, driver):
19✔
133
        """Click the new document button and handle the encryption choice dialog if present."""
134
        WebDriverWait(driver, self.wait_time).until(
6✔
135
            EC.element_to_be_clickable(
136
                (By.CSS_SELECTOR, ".new_document button")
137
            )
138
        ).click()
139
        try:
6✔
140
            WebDriverWait(driver, 2).until(
6✔
141
                EC.presence_of_element_located((By.CSS_SELECTOR, ".fw-dialog"))
142
            )
143
            driver.find_element(By.CSS_SELECTOR, ".fw-dialog .fw-dark").click()
×
144
        except TimeoutException:
6✔
145
            pass
6✔
146

147
    @classmethod
19✔
148
    def get_drivers(cls, number, download_dir=False, user_agent=False):
19✔
149
        # django native clients, to be used for faster login.
150
        clients = []
19✔
151
        for i in range(number):
19✔
152
            clients.append(Client())
19✔
153
        drivers = []
19✔
154
        wait_time = 0
19✔
155
        options = webdriver.ChromeOptions()
19✔
156
        options.add_argument("--kiosk-printing")
19✔
157
        options.add_argument("--safebrowsing-disable-download-protection")
19✔
158
        options.add_argument("--safebrowsing-disable-extension-blacklist")
19✔
159
        options.add_argument("--window-size=1920,1080")
19✔
160
        prefs = {
19✔
161
            "profile.password_manager_leak_detection": False,
162
        }
163
        if download_dir:
19✔
164
            prefs["download.default_directory"] = download_dir
5✔
165
            prefs["download.prompt_for_download"] = False
5✔
166
            prefs["download.directory_upgrade"] = True
5✔
167
        options.add_experimental_option("prefs", prefs)
19✔
168
        if user_agent:
19✔
169
            options.add_argument(f"user-agent={user_agent}")
1✔
170
        if os.getenv("CI"):
19✔
171
            options.binary_location = "/usr/bin/google-chrome-stable"
19✔
172
            if os.getenv("DEBUG_MODE") != "1":
19✔
173
                options.add_argument("--headless=new")
19✔
174
            options.add_argument("--disable-gpu")
19✔
175
            wait_time = 20
19✔
176
        else:
177

178
            wait_time = 10
×
179
        for i in range(number):
19✔
180
            driver_env = os.environ.copy()
19✔
181
            if os.getenv("CI") and os.getenv("DEBUG_MODE") == "1" and i < 2:
19✔
182
                driver_env["DISPLAY"] = f":{99 - i}"
×
183
            driver = webdriver.Chrome(
19✔
184
                service=ChromiumService(
185
                    ChromeDriverManager(
186
                        chrome_type=ChromeType.GOOGLE
187
                    ).install(),
188
                    env=driver_env,
189
                ),
190
                options=options,
191
            )
192
            # Set sizes of browsers so that all buttons are visible.
193
            driver.set_window_position(0, 0)
19✔
194
            driver.set_window_size(1920, 1080)
19✔
195
            drivers.append(driver)
19✔
196
        cls.drivers = drivers
19✔
197
        return {"clients": clients, "drivers": drivers, "wait_time": wait_time}
19✔
198

199
    def setUp(self):
19✔
200
        # Clear ContentType cache during testing to prevent FK constraint errors
201
        # Content types get new IDs after each test but cached values don't update
202
        ContentType.objects.clear_cache()
14✔
203
        self.addCleanup(ContentType.objects.clear_cache)
14✔
204
        return super().setUp()
14✔
205

206
    def tearDown(self):
19✔
207
        # Source: https://stackoverflow.com/a/39606065
208
        result = self._outcome.result
19✔
209
        ok = all(
19✔
210
            test != self for test, text in result.errors + result.failures
211
        )
212
        if ok:
19✔
213
            for driver in self.drivers:
19✔
214
                self.leave_site(driver)
19✔
215
        else:
216
            if not os.path.exists("screenshots"):
×
217
                os.makedirs("screenshots")
×
218
            for id, driver in enumerate(self.drivers, start=1):
×
219
                screenshotfile = (
×
220
                    f"screenshots/driver{id}-{self._testMethodName}.png"
221
                )
222
                logger.info(f"Saving {screenshotfile}")
×
223
                driver.save_screenshot(screenshotfile)
×
224
                self.leave_site(driver)
×
225
        return super().tearDown()
19✔
226

227
    def safe_get(self, driver, url, retries=3):
19✔
228
        """Navigate to url, retrying on transient timeout errors."""
229
        for attempt in range(retries):
1✔
230
            try:
1✔
231
                driver.get(url)
1✔
232
                return
1✔
233
            except (
×
234
                MaxRetryError,
235
                ReadTimeoutError,
236
                TimeoutException,
237
                WebDriverException,
238
            ):
239
                if attempt == retries - 1:
×
240
                    raise
×
241
                time.sleep(1)
×
242

243
    def leave_site(self, driver):
19✔
244
        try:
19✔
245
            driver.execute_script(
19✔
246
                "if (window.theApp && window.theApp.page) {"
247
                # Only call close() for non-E2EE pages. The E2EE editor's
248
                # close() schedules an async snapshot + a 300 ms timeout
249
                # before closing the WebSocket, which can race with the
250
                # navigation below and leave the browser in a bad state.
251
                "  if (!window.theApp.page.e2ee && window.theApp.page.close) {"
252
                "    window.theApp.page.close();"
253
                "  }"
254
                "  window.theApp.page = null;"
255
                "}"
256
                # Suppress any 'Leave site?' beforeunload dialog so that
257
                # driver.get() below cannot be blocked by it.
258
                "window.onbeforeunload = null;"
259
            )
260
        except (MaxRetryError, ReadTimeoutError, WebDriverException):
1✔
261
            pass
1✔
262
        try:
19✔
263
            driver.get("data:,")
19✔
264
        except (MaxRetryError, ReadTimeoutError, WebDriverException):
1✔
265
            pass
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc