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

fiduswriter / fiduswriter / 27252131456

10 Jun 2026 03:57AM UTC coverage: 88.348% (-0.008%) from 88.356%
27252131456

push

github

johanneswilm
Fix heredoc delimiter clash in release workflow index page step

The outer 'EOF' heredoc was being terminated by the inner 'EOF'
inside the DEB822 example HTML content. The shell then tried to
execute the remaining HTML as bash commands, causing
'apt-get install fiduswriter-server' to fail on the GitHub runner.

Change the outer delimiter to 'INDEXHTML' which does not appear
anywhere in the content.

10880 of 12315 relevant lines covered (88.35%)

5.8 hits per line

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

82.76
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
1✔
113
        while count < retries:
1✔
114
            try:
1✔
115
                WebDriverWait(driver, self.wait_time).until(
1✔
116
                    EC.element_to_be_clickable(selector)
117
                ).click()
118
                break
1✔
119
            except ElementClickInterceptedException:
×
120
                count += 1
×
121
                time.sleep(1)
×
122

123
    def click_new_document_button(self, driver):
19✔
124
        """Click the new document button and handle the encryption choice dialog if present."""
125
        WebDriverWait(driver, self.wait_time).until(
6✔
126
            EC.element_to_be_clickable(
127
                (By.CSS_SELECTOR, ".new_document button")
128
            )
129
        ).click()
130
        try:
6✔
131
            WebDriverWait(driver, 2).until(
6✔
132
                EC.presence_of_element_located((By.CSS_SELECTOR, ".ui-dialog"))
133
            )
134
            driver.find_element(By.CSS_SELECTOR, ".ui-dialog .fw-dark").click()
×
135
        except TimeoutException:
6✔
136
            pass
6✔
137

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

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

190
    def setUp(self):
19✔
191
        # Clear ContentType cache during testing to prevent FK constraint errors
192
        # Content types get new IDs after each test but cached values don't update
193
        ContentType.objects.clear_cache()
14✔
194
        self.addCleanup(ContentType.objects.clear_cache)
14✔
195
        return super().setUp()
14✔
196

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

218
    def safe_get(self, driver, url, retries=3):
19✔
219
        """Navigate to url, retrying on transient timeout errors."""
220
        for attempt in range(retries):
1✔
221
            try:
1✔
222
                driver.get(url)
1✔
223
                return
1✔
224
            except (
×
225
                MaxRetryError,
226
                ReadTimeoutError,
227
                TimeoutException,
228
                WebDriverException,
229
            ):
230
                if attempt == retries - 1:
×
231
                    raise
×
232
                time.sleep(1)
×
233

234
    def leave_site(self, driver):
19✔
235
        try:
19✔
236
            driver.execute_script(
19✔
237
                "if (window.theApp && window.theApp.page) {"
238
                # Only call close() for non-E2EE pages. The E2EE editor's
239
                # close() schedules an async snapshot + a 300 ms timeout
240
                # before closing the WebSocket, which can race with the
241
                # navigation below and leave the browser in a bad state.
242
                "  if (!window.theApp.page.e2ee && window.theApp.page.close) {"
243
                "    window.theApp.page.close();"
244
                "  }"
245
                "  window.theApp.page = null;"
246
                "}"
247
                # Suppress any 'Leave site?' beforeunload dialog so that
248
                # driver.get() below cannot be blocked by it.
249
                "window.onbeforeunload = null;"
250
            )
251
        except (MaxRetryError, ReadTimeoutError, WebDriverException):
1✔
252
            pass
1✔
253
        try:
19✔
254
            driver.get("data:,")
19✔
255
        except (MaxRetryError, ReadTimeoutError, WebDriverException):
2✔
256
            pass
2✔
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