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

fiduswriter / fiduswriter / 25264717828

02 May 2026 11:36PM UTC coverage: 86.618% (+0.1%) from 86.52%
25264717828

push

github

web-flow
Merge pull request #1383 from fiduswriter/feature/e2ee

Add E2E Encryption mode

8764 of 10118 relevant lines covered (86.62%)

5.27 hits per line

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

97.86
fiduswriter/document/tests/test_editor.py
1
import os
1✔
2
import time
1✔
3
import sys
1✔
4

5
from testing.channels_patch import ChannelsLiveServerTestCase
1✔
6
from testing.selenium_helper import SeleniumHelper
1✔
7
from testing.mail import get_outbox, empty_outbox, delete_outbox
1✔
8
from selenium.webdriver.common.by import By
1✔
9
from selenium.webdriver.common.keys import Keys
1✔
10
from selenium.webdriver.support.wait import WebDriverWait
1✔
11
from selenium.webdriver.support import expected_conditions as EC
1✔
12
from selenium.webdriver.common.action_chains import ActionChains
1✔
13
from selenium.common.exceptions import TimeoutException
1✔
14

15
from django.conf import settings
1✔
16
from django.test import override_settings
1✔
17

18
from allauth.account.models import EmailConfirmationHMAC, EmailAddress
1✔
19

20
MAIL_STORAGE_NAME = "editor"
1✔
21

22

23
@override_settings(MAIL_STORAGE_NAME=MAIL_STORAGE_NAME)
1✔
24
@override_settings(EMAIL_BACKEND="testing.mail.EmailBackend")
1✔
25
class EditorTest(SeleniumHelper, ChannelsLiveServerTestCase):
1✔
26
    fixtures = [
1✔
27
        "initial_documenttemplates.json",
28
        "initial_styles.json",
29
    ]
30

31
    @classmethod
1✔
32
    def setUpClass(cls):
1✔
33
        super().setUpClass()
1✔
34
        driver_data = cls.get_drivers(1)
1✔
35
        cls.driver = driver_data["drivers"][0]
1✔
36
        cls.client = driver_data["clients"][0]
1✔
37
        cls.driver.implicitly_wait(driver_data["wait_time"])
1✔
38
        cls.wait_time = driver_data["wait_time"]
1✔
39

40
    @classmethod
1✔
41
    def tearDownClass(cls):
1✔
42
        cls.driver.quit()
1✔
43
        delete_outbox(MAIL_STORAGE_NAME)
1✔
44
        super().tearDownClass()
1✔
45

46
    def setUp(self):
1✔
47
        self.base_url = self.live_server_url
1✔
48
        self.verificationErrors = []
1✔
49
        self.accept_next_alert = True
1✔
50
        self.user1 = self.create_user(
1✔
51
            username="Yeti", email="yeti@snowman.com", passtext="otter"
52
        )
53
        return super().setUp()
1✔
54

55
    def tearDown(self):
1✔
56
        self.driver.execute_script("window.localStorage.clear()")
1✔
57
        self.driver.execute_script("window.sessionStorage.clear()")
1✔
58
        super().tearDown()
1✔
59
        empty_outbox(MAIL_STORAGE_NAME)
1✔
60
        if "coverage" in sys.modules.keys():
1✔
61
            # Cool down
62
            time.sleep(self.wait_time / 3)
1✔
63

64
    def check_document_count(self, expected_count, timeout=10):
1✔
65
        """
66
        Check if the number of documents matches the expected count.
67

68
        Args:
69
            expected_count (int): The expected number of documents
70
            timeout (int): Maximum time to wait in seconds
71

72
        Returns:
73
            bool: True if assertion passes, False if it fails
74
        """
75

76
        def document_count_matches(driver):
1✔
77
            documents = driver.find_elements(
1✔
78
                By.CSS_SELECTOR,
79
                ".fw-contents tbody tr a.fw-data-table-title",
80
            )
81
            return len(documents) == expected_count
1✔
82

83
        try:
1✔
84
            WebDriverWait(self.driver, timeout).until(document_count_matches)
1✔
85
            return True
1✔
86
        except TimeoutException:
×
87
            documents = self.driver.find_elements(
×
88
                By.CSS_SELECTOR, ".fw-contents tbody tr a.fw-data-table-title"
89
            )
90
            actual_count = len(documents)
×
91
            raise AssertionError(
×
92
                f"Expected {expected_count} documents, but found {actual_count}"
93
            )
94

95
    def assert_with_retry(self, func, *args, max_attempts=3, wait_between=1):
1✔
96
        """
97
        Retry an assertion multiple times before failing.
98

99
        Args:
100
            func: The function to retry
101
            *args: Arguments to pass to the function
102
            max_attempts (int): Number of attempts before failing
103
            wait_between (int): Seconds to wait between attempts
104
        """
105
        for attempt in range(max_attempts):
1✔
106
            try:
1✔
107
                func(*args)
1✔
108
                return
1✔
109
            except AssertionError as e:
×
110
                if attempt == max_attempts - 1:
×
111
                    raise AssertionError(
×
112
                        f"Failed after {max_attempts} attempts. Last error: {str(e)}"
113
                    )
114
                time.sleep(wait_between)
×
115

116
    def test_crossrefs_and_internal_links(self):
1✔
117
        self.driver.get(self.base_url)
1✔
118
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti")
1✔
119
        self.driver.find_element(By.ID, "id-password").send_keys("otter")
1✔
120
        self.driver.find_element(By.ID, "login-submit").click()
1✔
121
        self.click_new_document_button(self.driver)
1✔
122
        WebDriverWait(self.driver, self.wait_time).until(
1✔
123
            EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar"))
124
        )
125
        self.driver.find_element(By.CSS_SELECTOR, ".doc-title").click()
1✔
126
        self.driver.find_element(By.CSS_SELECTOR, ".doc-title").send_keys(
1✔
127
            "Test"
128
        )
129
        # We enable the abstract
130
        self.driver.find_element(
1✔
131
            By.CSS_SELECTOR, "#header-navigation > div:nth-child(3) > span"
132
        ).click()
133
        self.driver.find_element(
1✔
134
            By.CSS_SELECTOR,
135
            (
136
                "#header-navigation > div:nth-child(3) > div "
137
                "> ul > li:nth-child(1) > span"
138
            ),
139
        ).click()
140
        self.driver.find_element(
1✔
141
            By.CSS_SELECTOR,
142
            (
143
                "#header-navigation > div:nth-child(3) > div "
144
                "> ul > li:nth-child(1) > div > ul > li:nth-child(3) > span"
145
            ),
146
        ).click()
147
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
148
        ActionChains(self.driver).send_keys(Keys.LEFT).send_keys(
1✔
149
            "An abstract title"
150
        ).perform()
151
        time.sleep(1)
1✔
152
        self.driver.find_element(
1✔
153
            By.CSS_SELECTOR, "#toolbar > div > div > div:nth-child(3) > div"
154
        ).click()
155
        self.driver.find_element(
1✔
156
            By.CSS_SELECTOR,
157
            (
158
                "#toolbar > div > div > div:nth-child(3) > div > div > "
159
                "ul > li:nth-child(4) > span > label"
160
            ),
161
        ).click()
162
        # We type in the body
163
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
164
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
165
            "Body"
166
        )
167
        # We add a figure
168
        button = self.driver.find_element(By.XPATH, '//*[@title="Figure"]')
1✔
169
        button.click()
1✔
170

171
        WebDriverWait(self.driver, self.wait_time).until(
1✔
172
            EC.presence_of_element_located(
173
                (By.CSS_SELECTOR, "span.math-field")
174
            )
175
        )
176
        self.driver.find_element(
1✔
177
            By.CSS_SELECTOR, "div.figure-category"
178
        ).click()
179
        WebDriverWait(self.driver, self.wait_time).until(
1✔
180
            EC.element_to_be_clickable(
181
                (By.XPATH, '//*[normalize-space()="Photo"]')
182
            )
183
        ).click()
184
        # click on 'Insert image' button
185
        self.driver.find_element(By.ID, "insert-figure-image").click()
1✔
186

187
        upload_button = WebDriverWait(self.driver, self.wait_time).until(
1✔
188
            EC.presence_of_element_located(
189
                (By.XPATH, '//*[normalize-space()="Add new image"]')
190
            )
191
        )
192

193
        upload_button.click()
1✔
194

195
        # image path
196
        image_path = os.path.join(
1✔
197
            settings.PROJECT_PATH, "document/tests/uploads/image.png"
198
        )
199

200
        # in order to select the image we send the image path in the
201
        # LOCAL MACHINE to the input tag
202
        upload_image_url = WebDriverWait(self.driver, self.wait_time).until(
1✔
203
            EC.presence_of_element_located(
204
                (By.XPATH, '//*[@id="editimage"]/div[1]/input[2]')
205
            )
206
        )
207
        upload_image_url.send_keys(image_path)
1✔
208

209
        # click on 'Upload' button
210
        self.driver.find_element(
1✔
211
            By.XPATH,
212
            '//*[contains(@class, "ui-button") and normalize-space()="Upload"]',
213
        ).click()
214

215
        # click on 'Use image' button
216
        WebDriverWait(self.driver, self.wait_time).until(
1✔
217
            EC.element_to_be_clickable(
218
                (By.CSS_SELECTOR, ".fw-data-table i.fa-check")
219
            )
220
        )
221

222
        self.driver.find_element(
1✔
223
            By.XPATH, '//*[normalize-space()="Use image"]'
224
        ).click()
225
        self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click()
1✔
226

227
        caption = WebDriverWait(self.driver, self.wait_time).until(
1✔
228
            EC.presence_of_element_located(
229
                (By.CSS_SELECTOR, "div.doc-body figure figcaption")
230
            )
231
        )
232

233
        caption.click()
1✔
234

235
        caption.send_keys("Caption")
1✔
236

237
        ActionChains(self.driver).send_keys(Keys.RIGHT).perform()
1✔
238
        # We add a cross reference for the heading
239
        self.driver.find_element(
1✔
240
            By.CSS_SELECTOR,
241
            "#toolbar > div > div > div:nth-child(9) > button > span > i",
242
        ).click()
243
        self.driver.find_element(
1✔
244
            By.CSS_SELECTOR, "#edit-link > div:nth-child(2) > select"
245
        ).click()
246
        self.driver.find_element(
1✔
247
            By.CSS_SELECTOR,
248
            "#edit-link > div:nth-child(2) > select > option:nth-child(2)",
249
        ).click()
250
        self.driver.find_element(
1✔
251
            By.CSS_SELECTOR,
252
            (
253
                "body > div.ui-dialog.ui-corner-all.ui-widget."
254
                "ui-widget-content.ui-front.ui-dialog-buttons > "
255
                "div.ui-dialog-buttonpane.ui-widget-content."
256
                "ui-helper-clearfix > div > button.fw-dark."
257
                "fw-button.ui-button.ui-corner-all.ui-widget"
258
            ),
259
        ).click()
260
        cross_reference = self.driver.find_element(
1✔
261
            By.CSS_SELECTOR, ".doc-body .cross-reference"
262
        )
263
        self.assertEqual(cross_reference.text, "An abstract title")
1✔
264
        # We add a second cross reference to the figure
265
        self.driver.find_element(
1✔
266
            By.CSS_SELECTOR,
267
            "#toolbar > div > div > div:nth-child(9) > button > span > i",
268
        ).click()
269
        self.driver.find_element(
1✔
270
            By.CSS_SELECTOR, "#edit-link > div:nth-child(2) > select"
271
        ).click()
272
        self.driver.find_element(
1✔
273
            By.CSS_SELECTOR,
274
            "#edit-link > div:nth-child(2) > select > option:nth-child(3)",
275
        ).click()
276
        self.driver.find_element(
1✔
277
            By.CSS_SELECTOR,
278
            (
279
                "body > div.ui-dialog.ui-corner-all.ui-widget."
280
                "ui-widget-content.ui-front.ui-dialog-buttons > "
281
                "div.ui-dialog-buttonpane.ui-widget-content."
282
                "ui-helper-clearfix > div > button.fw-dark."
283
                "fw-button.ui-button.ui-corner-all.ui-widget"
284
            ),
285
        ).click()
286
        figure_cross_reference = self.driver.find_elements(
1✔
287
            By.CSS_SELECTOR, ".doc-body .cross-reference"
288
        )[1]
289
        assert figure_cross_reference.text == "Photo 1"
1✔
290
        # We add an internal link
291
        self.driver.find_element(
1✔
292
            By.CSS_SELECTOR,
293
            "#toolbar > div > div > div:nth-child(9) > button > span > i",
294
        ).click()
295
        self.driver.find_element(
1✔
296
            By.CSS_SELECTOR, "#edit-link > div:nth-child(5) > select"
297
        ).click()
298
        self.driver.find_element(
1✔
299
            By.CSS_SELECTOR,
300
            "#edit-link > div:nth-child(5) > select > option:nth-child(2)",
301
        ).click()
302
        self.driver.find_element(
1✔
303
            By.CSS_SELECTOR,
304
            (
305
                "body > div.ui-dialog.ui-corner-all.ui-widget."
306
                "ui-widget-content.ui-front.ui-dialog-buttons > "
307
                "div.ui-dialog-buttonpane.ui-widget-content."
308
                "ui-helper-clearfix > div > button.fw-dark."
309
                "fw-button.ui-button.ui-corner-all.ui-widget"
310
            ),
311
        ).click()
312
        internal_link = self.driver.find_element(
1✔
313
            By.CSS_SELECTOR, ".doc-body a"
314
        )
315
        assert internal_link.text == "An abstract title"
1✔
316
        # We change the link text.
317
        self.driver.find_element(By.CSS_SELECTOR, ".doc-abstract h3").click()
1✔
318
        ActionChains(self.driver).send_keys(Keys.BACKSPACE).send_keys(
1✔
319
            Keys.BACKSPACE
320
        ).send_keys(Keys.BACKSPACE).send_keys(Keys.BACKSPACE).send_keys(
321
            Keys.BACKSPACE
322
        ).send_keys(
323
            Keys.BACKSPACE
324
        ).perform()
325
        internal_link = self.driver.find_element(
1✔
326
            By.CSS_SELECTOR, ".doc-body a"
327
        )
328
        assert internal_link.text == "An abstract title"
1✔
329
        assert internal_link.get_attribute("title") == "An abstract"
1✔
330
        cross_reference = self.driver.find_element(
1✔
331
            By.CSS_SELECTOR, ".doc-body .cross-reference"
332
        )
333
        assert cross_reference.text == "An abstract"
1✔
334
        # We add a second photo figure to increase the count
335
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body figure").click()
1✔
336
        ActionChains(self.driver).send_keys(Keys.LEFT).perform()
1✔
337
        button = self.driver.find_element(By.XPATH, '//*[@title="Figure"]')
1✔
338
        button.click()
1✔
339
        WebDriverWait(self.driver, self.wait_time).until(
1✔
340
            EC.presence_of_element_located(
341
                (By.CSS_SELECTOR, "span.math-field")
342
            )
343
        )
344
        self.driver.find_element(
1✔
345
            By.CSS_SELECTOR, "div.figure-category"
346
        ).click()
347
        self.driver.find_element(
1✔
348
            By.XPATH, '//*[normalize-space()="Photo"]'
349
        ).click()
350

351
        # click on 'Insert image' button
352
        self.driver.find_element(By.ID, "insert-figure-image").click()
1✔
353
        # click on 'Use image' button
354
        WebDriverWait(self.driver, self.wait_time).until(
1✔
355
            EC.element_to_be_clickable(
356
                (By.CSS_SELECTOR, ".datatable-container img")
357
            )
358
        ).click()
359

360
        self.driver.find_element(
1✔
361
            By.XPATH, '//*[normalize-space()="Use image"]'
362
        ).click()
363
        self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click()
1✔
364
        time.sleep(1)
1✔
365
        figure_cross_reference = self.driver.find_elements(
1✔
366
            By.CSS_SELECTOR, ".doc-body .cross-reference"
367
        )[1]
368
        assert figure_cross_reference.text == "Photo 2"
1✔
369

370
        # We delete the contents from the heading
371
        self.driver.find_element(By.CSS_SELECTOR, ".doc-abstract h3").click()
1✔
372
        ActionChains(self.driver).send_keys(Keys.BACKSPACE).send_keys(
1✔
373
            Keys.BACKSPACE
374
        ).send_keys(Keys.BACKSPACE).send_keys(Keys.BACKSPACE).send_keys(
375
            Keys.BACKSPACE
376
        ).send_keys(
377
            Keys.BACKSPACE
378
        ).send_keys(
379
            Keys.BACKSPACE
380
        ).send_keys(
381
            Keys.BACKSPACE
382
        ).send_keys(
383
            Keys.BACKSPACE
384
        ).send_keys(
385
            Keys.BACKSPACE
386
        ).send_keys(
387
            Keys.BACKSPACE
388
        ).perform()
389
        cross_reference = self.driver.find_element(
1✔
390
            By.CSS_SELECTOR, ".doc-body .cross-reference.missing-target"
391
        )
392
        assert cross_reference.text == "MISSING TARGET"
1✔
393
        internal_link = self.driver.find_element(
1✔
394
            By.CSS_SELECTOR, ".doc-body a.missing-target"
395
        )
396
        assert internal_link.get_attribute("title") == "Missing target"
1✔
397
        self.assertEqual(
1✔
398
            len(
399
                self.driver.find_elements(
400
                    By.CSS_SELECTOR, ".margin-box.warning"
401
                )
402
            ),
403
            2,
404
        )
405
        # We add text to the heading again
406
        ActionChains(self.driver).send_keys("Title").perform()
1✔
407
        cross_reference = self.driver.find_element(
1✔
408
            By.CSS_SELECTOR, ".doc-body .cross-reference"
409
        )
410
        assert cross_reference.text == "Title"
1✔
411
        internal_link = self.driver.find_element(
1✔
412
            By.CSS_SELECTOR, ".doc-body a"
413
        )
414
        assert internal_link.get_attribute("title") == "Title"
1✔
415
        self.assertEqual(
1✔
416
            len(
417
                self.driver.find_elements(
418
                    By.CSS_SELECTOR, ".margin-box.warning"
419
                )
420
            ),
421
            0,
422
        )
423
        # We remove the second figure
424
        self.driver.find_elements(By.CSS_SELECTOR, ".doc-body figure")[
1✔
425
            1
426
        ].click()
427
        button = self.driver.find_element(By.XPATH, '//*[@title="Figure"]')
1✔
428
        button.click()
1✔
429
        self.driver.find_element(
1✔
430
            By.XPATH,
431
            '//*[contains(@class, "ui-button") and normalize-space()="Remove"]',
432
        ).click()
433
        time.sleep(1)
1✔
434
        figure_cross_reference = self.driver.find_elements(
1✔
435
            By.CSS_SELECTOR, ".doc-body .cross-reference"
436
        )[1]
437
        assert figure_cross_reference.text == "MISSING TARGET"
1✔
438

439
    def check_body(self, driver, body_text, seconds=False):
1✔
440
        if seconds is False:
1✔
441
            seconds = self.wait_time
1✔
442
        # Contents is child 5.
443
        current_body_text = driver.execute_script(
1✔
444
            "return window.theApp.page.view.state.doc.child(5).textContent;"
445
        )
446
        if seconds < 0:
1✔
447
            assert False, f"Body text incorrect: {current_body_text}"
×
448
        elif current_body_text == body_text:
1✔
449
            return True
1✔
450
        else:
451
            time.sleep(0.1)
×
452
            return self.check_body(driver, body_text, seconds - 0.1)
×
453

454
    def test_track_changes(self):
1✔
455
        self.driver.get(self.base_url)
1✔
456
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti")
1✔
457
        self.driver.find_element(By.ID, "id-password").send_keys("otter")
1✔
458
        self.driver.find_element(By.ID, "login-submit").click()
1✔
459
        self.click_new_document_button(self.driver)
1✔
460
        WebDriverWait(self.driver, self.wait_time).until(
1✔
461
            EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar"))
462
        )
463
        self.driver.find_element(By.CSS_SELECTOR, ".doc-title").click()
1✔
464
        self.driver.find_element(By.CSS_SELECTOR, ".doc-title").send_keys(
1✔
465
            "A test article with tracked changes"
466
        )
467
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
468
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
469
            "First I type "
470
        )
471
        self.driver.find_element(
1✔
472
            By.CSS_SELECTOR, "button[title=Strong]"
473
        ).click()
474
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
475
            "some"
476
        )
477
        self.driver.find_element(
1✔
478
            By.CSS_SELECTOR, "button[title=Strong]"
479
        ).click()
480
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
481
            " standard "
482
        )
483
        self.driver.find_element(
1✔
484
            By.CSS_SELECTOR, "button[title=Emphasis]"
485
        ).click()
486
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
487
            "text"
488
        )
489
        self.driver.find_element(
1✔
490
            By.CSS_SELECTOR, "button[title=Emphasis]"
491
        ).click()
492
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
493
            " here.\nI'll even write a second paragraph."
494
        )
495
        # Turn on tracked changes
496
        self.driver.find_element(
1✔
497
            By.CSS_SELECTOR, ".header-menu:nth-child(5) > .header-nav-item"
498
        ).click()
499
        self.driver.find_element(
1✔
500
            By.CSS_SELECTOR, "li:nth-child(1) > .fw-pulldown-item"
501
        ).click()
502
        # Make changes
503
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
504
        ActionChains(self.driver).double_click(
1✔
505
            self.driver.find_element(By.CSS_SELECTOR, ".doc-body strong")
506
        ).perform()
507
        self.driver.find_element(
1✔
508
            By.CSS_SELECTOR, "button[title=Strong]"
509
        ).click()
510
        ActionChains(self.driver).double_click(
1✔
511
            self.driver.find_element(By.CSS_SELECTOR, ".doc-body em")
512
        ).perform()
513
        self.driver.find_element(
1✔
514
            By.CSS_SELECTOR, "button[title=Strong]"
515
        ).click()
516
        time.sleep(1)
1✔
517
        ActionChains(self.driver).send_keys(Keys.RIGHT).send_keys(
1✔
518
            Keys.RIGHT
519
        ).send_keys(Keys.RIGHT).send_keys(Keys.RIGHT).send_keys(
520
            Keys.RIGHT
521
        ).send_keys(
522
            Keys.BACKSPACE
523
        ).send_keys(
524
            Keys.RIGHT
525
        ).send_keys(
526
            Keys.RIGHT
527
        ).send_keys(
528
            Keys.RIGHT
529
        ).send_keys(
530
            "insertion"
531
        ).send_keys(
532
            Keys.RIGHT
533
        ).send_keys(
534
            Keys.RIGHT
535
        ).send_keys(
536
            Keys.RIGHT
537
        ).send_keys(
538
            Keys.ENTER
539
        ).send_keys(
540
            Keys.UP
541
        ).perform()
542
        self.driver.find_element(
1✔
543
            By.CSS_SELECTOR, ".editor-toolbar .multi-buttons"
544
        ).click()
545
        self.driver.find_element(
1✔
546
            By.CSS_SELECTOR,
547
            ".editor-toolbar .multi-buttons .ui-button:nth-child(5)",
548
        ).click()
549

550
        change_tracking_boxes = self.driver.find_elements(
1✔
551
            By.CSS_SELECTOR, ".margin-box.track:not(.hidden)"
552
        )
553
        self.assertEqual(len(change_tracking_boxes), 6)
1✔
554

555
    def test_share_document(self):
1✔
556
        yeti2_user = self.create_user(
1✔
557
            username="Yeti2", email="yeti2@snowman.com", passtext="otter"
558
        )
559
        self.driver.get(self.base_url)
1✔
560
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti")
1✔
561
        self.driver.find_element(By.ID, "id-password").send_keys("otter")
1✔
562
        self.driver.find_element(By.ID, "login-submit").click()
1✔
563
        self.click_new_document_button(self.driver)
1✔
564
        WebDriverWait(self.driver, self.wait_time).until(
1✔
565
            EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar"))
566
        )
567
        self.driver.find_element(By.CSS_SELECTOR, ".doc-title").click()
1✔
568
        self.driver.find_element(By.CSS_SELECTOR, ".doc-title").send_keys(
1✔
569
            "A test article to share"
570
        )
571
        # Turn on tracked changes
572
        self.driver.find_element(
1✔
573
            By.CSS_SELECTOR, ".header-menu:nth-child(5) > .header-nav-item"
574
        ).click()
575
        self.driver.find_element(
1✔
576
            By.CSS_SELECTOR, "li:nth-child(1) > .fw-pulldown-item"
577
        ).click()
578
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
579
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
580
            "With tracked changes\n"
581
        )
582

583
        # Open share dialog
584
        self.driver.find_element(
1✔
585
            By.CSS_SELECTOR, ".header-menu:nth-child(1) > .header-nav-item"
586
        ).click()
587
        self.driver.find_element(
1✔
588
            By.CSS_SELECTOR, "li:nth-child(1) > .fw-pulldown-item"
589
        ).click()
590
        self.driver.find_element(
1✔
591
            By.CSS_SELECTOR, ".ui-dialog .fw-add-button"
592
        ).click()
593
        self.driver.find_element(By.ID, "new-contact-user-string").click()
1✔
594
        self.driver.find_element(By.ID, "new-contact-user-string").send_keys(
1✔
595
            "yeti2@snowman.com"
596
        )
597
        ActionChains(self.driver).send_keys(Keys.TAB).send_keys(
1✔
598
            Keys.RETURN
599
        ).perform()
600
        self.driver.find_element(
1✔
601
            By.CSS_SELECTOR, ".ui-dialog .fw-add-button"
602
        ).click()
603
        self.driver.find_element(By.ID, "new-contact-user-string").click()
1✔
604
        self.driver.find_element(By.ID, "new-contact-user-string").send_keys(
1✔
605
            "yeti3@snowman.com"
606
        )
607
        ActionChains(self.driver).send_keys(Keys.TAB).send_keys(
1✔
608
            Keys.RETURN
609
        ).perform()
610
        self.driver.find_element(
1✔
611
            By.CSS_SELECTOR, ".ui-dialog .fw-add-button"
612
        ).click()
613
        self.driver.find_element(By.ID, "new-contact-user-string").click()
1✔
614
        self.driver.find_element(By.ID, "new-contact-user-string").send_keys(
1✔
615
            "yeti4@snowman.com"
616
        )
617
        ActionChains(self.driver).send_keys(Keys.TAB).send_keys(
1✔
618
            Keys.RETURN
619
        ).perform()
620
        WebDriverWait(self.driver, self.wait_time).until(
1✔
621
            EC.element_to_be_clickable(
622
                (By.CSS_SELECTOR, ".collaborator-tr .fa-caret-down")
623
            )
624
        ).click()
625
        self.driver.find_element(
1✔
626
            By.XPATH, '//*[normalize-space()="Write"]'
627
        ).click()
628
        self.driver.find_element(By.CSS_SELECTOR, "#my-contacts").click()
1✔
629
        self.driver.find_element(
1✔
630
            By.CSS_SELECTOR, "tr:nth-child(3) .fa-caret-down"
631
        ).click()
632
        self.driver.find_element(
1✔
633
            By.XPATH, '//*[normalize-space()="Write"]'
634
        ).click()
635
        self.driver.find_element(By.CSS_SELECTOR, "#my-contacts").click()
1✔
636
        time.sleep(2)
1✔
637
        self.driver.find_element(
1✔
638
            By.CSS_SELECTOR, ".ui-dialog .fw-dark"
639
        ).click()
640
        outbox = get_outbox(MAIL_STORAGE_NAME)
1✔
641
        # We keep track of the invitation email to open it later.
642
        user4_invitation_email = outbox[-1].body
1✔
643
        #  Reopen the share dialog and add users 5-7
644
        self.driver.find_element(
1✔
645
            By.CSS_SELECTOR, ".header-menu:nth-child(1) > .header-nav-item"
646
        ).click()
647
        self.driver.find_element(
1✔
648
            By.CSS_SELECTOR, "li:nth-child(1) > .fw-pulldown-item"
649
        ).click()
650
        self.driver.find_element(
1✔
651
            By.CSS_SELECTOR, ".ui-dialog .fw-add-button"
652
        ).click()
653
        self.driver.find_element(By.ID, "new-contact-user-string").click()
1✔
654
        self.driver.find_element(By.ID, "new-contact-user-string").send_keys(
1✔
655
            "yeti5@snowman.com,yeti6@snowman.com;yeti7@snowman.com"
656
        )
657
        ActionChains(self.driver).send_keys(Keys.TAB).send_keys(
1✔
658
            Keys.RETURN
659
        ).perform()
660
        # Downgrade the write rights to read rights for user4
661
        self.retry_click(
1✔
662
            self.driver,
663
            (By.CSS_SELECTOR, "tr:nth-child(3) .fa-caret-down.edit-right"),
664
        )
665
        WebDriverWait(self.driver, self.wait_time).until(
1✔
666
            EC.element_to_be_clickable((By.CSS_SELECTOR, "li[title=Read]"))
667
        ).click()
668
        self.driver.find_element(By.CSS_SELECTOR, "#my-contacts").click()
1✔
669
        # Upgrade the read rights to write rights for user7
670
        self.driver.find_element(
1✔
671
            By.CSS_SELECTOR, "tr:nth-child(6) .fa-caret-down.edit-right"
672
        ).click()
673
        WebDriverWait(self.driver, self.wait_time).until(
1✔
674
            EC.element_to_be_clickable((By.CSS_SELECTOR, "li[title=Write]"))
675
        ).click()
676
        WebDriverWait(self.driver, self.wait_time).until(
1✔
677
            EC.presence_of_element_located(
678
                (By.CSS_SELECTOR, "tr:nth-child(6) i.icon-access-write")
679
            )
680
        )
681
        self.driver.find_element(
1✔
682
            By.CSS_SELECTOR, ".ui-dialog .fw-dark"
683
        ).click()
684
        outbox = get_outbox(MAIL_STORAGE_NAME)
1✔
685
        # We keep track of the invitation email to open it later.
686
        last_three_emails = [
1✔
687
            outbox[-3].body,
688
            outbox[-2].body,
689
            outbox[-1].body,
690
        ]
691
        user5_invitation_email = next(
1✔
692
            (s for s in last_three_emails if "yeti5" in s), None
693
        )
694
        user6_invitation_email = next(
1✔
695
            (s for s in last_three_emails if "yeti6" in s), None
696
        )
697
        user7_invitation_email = next(
1✔
698
            (s for s in last_three_emails if "yeti7" in s), None
699
        )
700
        # We close the editor
701
        self.driver.find_element(By.ID, "close-document-top").click()
1✔
702
        WebDriverWait(self.driver, self.wait_time).until(
1✔
703
            EC.element_to_be_clickable((By.ID, "preferences-btn"))
704
        ).click()
705
        self.driver.find_element(
1✔
706
            By.XPATH, '//*[normalize-space()="Log out"]'
707
        ).click()
708
        # Second user logs in, verifies that he has access
709
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti2")
1✔
710
        self.driver.find_element(By.ID, "id-password").send_keys("otter")
1✔
711
        self.driver.find_element(By.ID, "login-submit").click()
1✔
712
        WebDriverWait(self.driver, self.wait_time).until(
1✔
713
            EC.presence_of_element_located(
714
                (By.XPATH, '//*[normalize-space()="Go to contacts"]')
715
            )
716
        ).click()
717
        self.driver.find_element(By.CSS_SELECTOR, ".respond-invite").click()
1✔
718
        # Wait for Accept invite button to appear and use JavaScript click to ensure it works
719
        accept_button = WebDriverWait(self.driver, self.wait_time).until(
1✔
720
            EC.presence_of_element_located(
721
                (By.XPATH, '//*[normalize-space()="Accept invite"]')
722
            )
723
        )
724
        # Use JavaScript click to bypass any event handler timing issues
725
        time.sleep(1)
1✔
726
        self.driver.execute_script("arguments[0].click();", accept_button)
1✔
727
        # Wait for the dialog to close completely
728
        WebDriverWait(self.driver, self.wait_time).until(
1✔
729
            EC.invisibility_of_element_located((By.CSS_SELECTOR, ".ui-dialog"))
730
        )
731
        self.driver.find_element(
1✔
732
            By.XPATH, '//*[normalize-space()="Documents"]'
733
        ).click()
734
        # Wait for document list page to load
735
        WebDriverWait(self.driver, self.wait_time).until(
1✔
736
            EC.presence_of_element_located(
737
                (By.CSS_SELECTOR, ".new_document button")
738
            )
739
        )
740
        documents = self.driver.find_elements(
1✔
741
            By.CSS_SELECTOR, ".fw-contents tbody tr"
742
        )
743
        self.assertEqual(len(documents), 1)
1✔
744
        write_access_rights = self.driver.find_elements(
1✔
745
            By.CSS_SELECTOR, ".fw-contents tbody tr .icon-access-write"
746
        )
747
        self.assertEqual(len(write_access_rights), 1)
1✔
748
        self.driver.find_element(
1✔
749
            By.CSS_SELECTOR, ".fw-contents tbody tr a.fw-data-table-title"
750
        ).click()
751
        WebDriverWait(self.driver, self.wait_time).until(
1✔
752
            EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar"))
753
        )
754
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
755
        ActionChains(self.driver).send_keys("... in the body").send_keys(
1✔
756
            Keys.ENTER
757
        ).perform()
758
        time.sleep(1)
1✔
759
        assert (
1✔
760
            self.driver.find_element(By.CSS_SELECTOR, ".doc-title").text
761
            == "A test article to share"
762
        )
763
        self.check_body(self.driver, "With tracked changes... in the body")
1✔
764
        self.driver.find_element(By.ID, "close-document-top").click()
1✔
765
        WebDriverWait(self.driver, self.wait_time).until(
1✔
766
            EC.element_to_be_clickable((By.ID, "preferences-btn"))
767
        ).click()
768
        self.driver.find_element(
1✔
769
            By.XPATH, '//*[normalize-space()="Log out"]'
770
        ).click()
771
        # First user logs in again, removes access rights of second user
772
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti")
1✔
773
        self.driver.find_element(By.ID, "id-password").send_keys("otter")
1✔
774
        self.driver.find_element(By.ID, "login-submit").click()
1✔
775
        WebDriverWait(self.driver, self.wait_time).until(
1✔
776
            EC.presence_of_element_located(
777
                (By.CSS_SELECTOR, ".new_document button")
778
            )
779
        )
780
        time.sleep(1)
1✔
781
        self.assert_with_retry(self.check_document_count, 1)
1✔
782
        documents = self.driver.find_elements(
1✔
783
            By.CSS_SELECTOR, ".fw-contents tbody tr a.fw-data-table-title"
784
        )
785

786
        documents[0].click()
1✔
787
        WebDriverWait(self.driver, self.wait_time).until(
1✔
788
            EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar"))
789
        )
790
        self.driver.find_element(
1✔
791
            By.CSS_SELECTOR, ".header-menu:nth-child(1) > .header-nav-item"
792
        ).click()
793
        self.driver.find_element(
1✔
794
            By.CSS_SELECTOR, "li:nth-child(1) > .fw-pulldown-item"
795
        ).click()
796
        # Find and click the delete button for the user (not userinvite) collaborator
797
        # The collaborator row has id="collaborator-user-2" for user Yeti2
798
        self.driver.find_element(
1✔
799
            By.CSS_SELECTOR,
800
            f"#collaborator-user-{yeti2_user.id} .delete-collaborator",
801
        ).click()
802
        self.driver.find_element(
1✔
803
            By.CSS_SELECTOR, ".ui-dialog .fw-dark"
804
        ).click()
805
        self.driver.find_element(By.ID, "close-document-top").click()
1✔
806
        WebDriverWait(self.driver, self.wait_time).until(
1✔
807
            EC.element_to_be_clickable((By.ID, "preferences-btn"))
808
        ).click()
809
        self.driver.find_element(
1✔
810
            By.XPATH, '//*[normalize-space()="Log out"]'
811
        ).click()
812
        # Second user logs in again to verify that access rights are gone
813
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti2")
1✔
814
        self.driver.find_element(By.ID, "id-password").send_keys("otter")
1✔
815
        self.driver.find_element(By.ID, "login-submit").click()
1✔
816
        WebDriverWait(self.driver, self.wait_time).until(
1✔
817
            EC.presence_of_element_located(
818
                (By.CSS_SELECTOR, ".new_document button")
819
            )
820
        )
821
        time.sleep(1)
1✔
822
        self.assert_with_retry(self.check_document_count, 0)
1✔
823

824
        WebDriverWait(self.driver, self.wait_time).until(
1✔
825
            EC.element_to_be_clickable((By.ID, "preferences-btn"))
826
        ).click()
827
        self.driver.find_element(
1✔
828
            By.XPATH, '//*[normalize-space()="Log out"]'
829
        ).click()
830
        # Third user signs up
831
        self.driver.find_element(By.CSS_SELECTOR, 'a[title="Sign up"]').click()
1✔
832
        self.driver.find_element(By.ID, "id-username").send_keys("Yeti3")
1✔
833
        self.driver.find_element(By.ID, "id-password1").send_keys(
1✔
834
            "TestPassword123!"
835
        )
836
        self.driver.find_element(By.ID, "id-password2").send_keys(
1✔
837
            "TestPassword123!"
838
        )
839
        self.driver.find_element(By.ID, "id-email").send_keys(
1✔
840
            "yeti3@snowman.com"
841
        )
842
        # Scroll all the way down in case we are on a small screen
843
        self.driver.execute_script(
1✔
844
            "window.scrollTo(0, document.body.scrollHeight);"
845
        )
846
        self.driver.find_element(By.ID, "signup-submit").click()
1✔
847
        WebDriverWait(self.driver, self.wait_time).until(
1✔
848
            EC.presence_of_element_located(
849
                (By.CSS_SELECTOR, 'a[href="mailto:yeti3@snowman.com"]')
850
            )
851
        )
852
        email = EmailAddress.objects.filter(email="yeti3@snowman.com").first()
1✔
853
        self.assertIsNotNone(email)
1✔
854
        confirmation_key = EmailConfirmationHMAC(email).key
1✔
855
        self.driver.get(
1✔
856
            f"{self.base_url}/account/confirm-email/{confirmation_key}/"
857
        )
858
        self.driver.find_element(By.ID, "terms-check").click()
1✔
859
        self.driver.find_element(By.ID, "test-check").click()
1✔
860
        self.driver.find_element(By.ID, "submit").click()
1✔
861
        WebDriverWait(self.driver, self.wait_time).until(
1✔
862
            EC.text_to_be_present_in_element(
863
                (By.CSS_SELECTOR, ".fw-contents h1"), "Thanks for verifying!"
864
            )
865
        )
866
        self.driver.find_element(By.CSS_SELECTOR, 'a[href="/"]').click()
1✔
867
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti3")
1✔
868
        self.driver.find_element(By.ID, "id-password").send_keys(
1✔
869
            "TestPassword123!"
870
        )
871
        self.driver.find_element(By.ID, "login-submit").click()
1✔
872
        WebDriverWait(self.driver, self.wait_time).until(
1✔
873
            EC.presence_of_element_located(
874
                (By.XPATH, '//*[normalize-space()="Go to contacts"]')
875
            )
876
        ).click()
877
        self.driver.find_element(By.CSS_SELECTOR, ".respond-invite").click()
1✔
878
        self.driver.find_element(
1✔
879
            By.XPATH, '//*[normalize-space()="Accept invite"]'
880
        ).click()
881
        self.driver.find_element(
1✔
882
            By.XPATH, '//*[normalize-space()="Documents"]'
883
        ).click()
884
        WebDriverWait(self.driver, self.wait_time).until(
1✔
885
            EC.presence_of_element_located(
886
                (By.CSS_SELECTOR, ".new_document button")
887
            )
888
        )
889
        documents = self.driver.find_elements(
1✔
890
            By.CSS_SELECTOR, ".fw-contents tbody tr"
891
        )
892
        self.assertEqual(len(documents), 1)
1✔
893
        read_access_rights = self.driver.find_elements(
1✔
894
            By.CSS_SELECTOR, ".fw-contents tbody tr .icon-access-read"
895
        )
896
        self.assertEqual(len(read_access_rights), 1)
1✔
897
        self.driver.find_element(
1✔
898
            By.CSS_SELECTOR, ".fw-contents tbody tr a.fw-data-table-title"
899
        ).click()
900
        WebDriverWait(self.driver, self.wait_time).until(
1✔
901
            EC.presence_of_element_located((By.CLASS_NAME, "editor-toolbar"))
902
        )
903
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
904
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
905
            "Some extra content that doesn't show"
906
        )
907
        assert (
1✔
908
            self.driver.find_element(By.CSS_SELECTOR, ".doc-title").text
909
            == "A test article to share"
910
        )
911
        self.check_body(self.driver, "With tracked changes... in the body")
1✔
912
        # Make a copy of the file
913
        old_body = self.driver.find_element(By.CSS_SELECTOR, ".doc-body")
1✔
914
        self.driver.find_element(
1✔
915
            By.CSS_SELECTOR, ".header-menu:nth-child(1) > .header-nav-item"
916
        ).click()
917
        self.driver.find_element(
1✔
918
            By.CSS_SELECTOR, "li:nth-child(4) > .fw-pulldown-item"
919
        ).click()
920
        # Check whether user now has write access
921
        WebDriverWait(self.driver, self.wait_time).until(
1✔
922
            EC.staleness_of(old_body)
923
        )
924
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
925
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").send_keys(
1✔
926
            "Some extra content that does show"
927
        )
928
        self.check_body(
1✔
929
            self.driver,
930
            (
931
                "With tracked changes... in the body"
932
                "Some extra content that does show"
933
            ),
934
        )
935
        # Filter tracks by users
936
        change_tracking_boxes = self.driver.find_elements(
1✔
937
            By.CSS_SELECTOR, ".margin-box.track:not(.hidden)"
938
        )
939
        self.assertEqual(len(change_tracking_boxes), 5)
1✔
940
        self.driver.find_element(
1✔
941
            By.CSS_SELECTOR, "#margin-box-filter-track .show-marginbox-options"
942
        ).click()
943
        self.driver.find_element(
1✔
944
            By.CSS_SELECTOR,
945
            "#margin-box-filter-track .show-marginbox-options-submenu",
946
        ).click()
947
        self.driver.find_elements(
1✔
948
            By.CSS_SELECTOR, ".margin-box-filter-track-author"
949
        )[1].click()
950
        change_tracking_boxes = self.driver.find_elements(
1✔
951
            By.CSS_SELECTOR, ".margin-box.track:not(.hidden)"
952
        )
953
        self.assertEqual(len(change_tracking_boxes), 2)
1✔
954
        self.driver.find_element(
1✔
955
            By.CSS_SELECTOR, "#margin-box-filter-track .show-marginbox-options"
956
        ).click()
957
        self.driver.find_element(
1✔
958
            By.CSS_SELECTOR,
959
            "#margin-box-filter-track .show-marginbox-options-submenu",
960
        ).click()
961
        self.driver.find_elements(
1✔
962
            By.CSS_SELECTOR, ".margin-box-filter-track-author"
963
        )[2].click()
964
        change_tracking_boxes = self.driver.find_elements(
1✔
965
            By.CSS_SELECTOR, ".margin-box.track:not(.hidden)"
966
        )
967
        self.assertEqual(len(change_tracking_boxes), 2)
1✔
968
        self.driver.find_element(
1✔
969
            By.CSS_SELECTOR, "#margin-box-filter-track"
970
        ).click()
971
        change_tracking_boxes = self.driver.find_elements(
1✔
972
            By.CSS_SELECTOR, ".margin-box.track:not(.hidden)"
973
        )
974
        self.assertEqual(len(change_tracking_boxes), 0)
1✔
975
        # Give user 1 write access to document
976
        self.driver.find_element(
1✔
977
            By.CSS_SELECTOR, ".header-menu:nth-child(1) > .header-nav-item"
978
        ).click()
979
        self.driver.find_element(
1✔
980
            By.CSS_SELECTOR, "li:nth-child(1) > .fw-pulldown-item"
981
        ).click()
982
        self.driver.find_element(
1✔
983
            By.CSS_SELECTOR, "#my-contacts > table > tbody > tr > td"
984
        ).click()
985
        self.driver.find_element(By.ID, "add-share-contact").click()
1✔
986
        WebDriverWait(self.driver, self.wait_time).until(
1✔
987
            EC.element_to_be_clickable(
988
                (By.CSS_SELECTOR, ".collaborator-tr .fa-caret-down")
989
            )
990
        ).click()
991
        self.driver.find_element(
1✔
992
            By.XPATH, '//*[normalize-space()="Write"]'
993
        ).click()
994
        time.sleep(1)
1✔
995
        self.driver.find_element(
1✔
996
            By.CSS_SELECTOR, ".ui-dialog .fw-dark"
997
        ).click()
998
        # Tag user 1 in comment
999
        self.driver.find_element(By.CSS_SELECTOR, ".doc-body").click()
1✔
1000
        ActionChains(self.driver).key_down(Keys.SHIFT).send_keys(
1✔
1001
            Keys.LEFT
1002
        ).send_keys(Keys.LEFT).send_keys(Keys.LEFT).send_keys(
1003
            Keys.LEFT
1004
        ).key_up(
1005
            Keys.SHIFT
1006
        ).perform()
1007
        self.driver.find_element(By.CSS_SELECTOR, "button .fa-comment").click()
1✔
1008
        ActionChains(self.driver).send_keys("Hello @Yeti").perform()
1✔
1009
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1010
            EC.element_to_be_clickable((By.CSS_SELECTOR, ".tag-user"))
1011
        ).click()
1012
        outbox = get_outbox(MAIL_STORAGE_NAME)
1✔
1013
        emails_sent_before_comment = len(outbox)
1✔
1014
        self.driver.find_element(
1✔
1015
            By.CSS_SELECTOR, ".comment-btns .submit"
1016
        ).click()
1017
        outbox = get_outbox(MAIL_STORAGE_NAME)
1✔
1018
        self.assertEqual(emails_sent_before_comment + 1, len(outbox))
1✔
1019
        self.driver.find_element(By.ID, "close-document-top").click()
1✔
1020
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1021
            EC.element_to_be_clickable((By.ID, "preferences-btn"))
1022
        ).click()
1023
        self.driver.find_element(
1✔
1024
            By.XPATH, '//*[normalize-space()="Log out"]'
1025
        ).click()
1026
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1027
            EC.element_to_be_clickable(
1028
                (By.CSS_SELECTOR, 'a[href="/account/sign-up/"]')
1029
            )
1030
        )
1031
        # User 4 signs up using invitation link but different email than what
1032
        # was in the invitation email (this should work)
1033
        invitation_link = self.find_urls(user4_invitation_email)[0]
1✔
1034
        self.driver.get(invitation_link)
1✔
1035
        self.driver.find_element(By.CSS_SELECTOR, 'a[title="Sign up"]').click()
1✔
1036
        self.driver.find_element(By.ID, "id-username").send_keys("Yeti4")
1✔
1037
        self.driver.find_element(By.ID, "id-password1").send_keys(
1✔
1038
            "TestPassword123!"
1039
        )
1040
        self.driver.find_element(By.ID, "id-password2").send_keys(
1✔
1041
            "TestPassword123!"
1042
        )
1043
        self.driver.find_element(By.ID, "id-email").send_keys(
1✔
1044
            "yeti4a@snowman.com"
1045
        )
1046
        self.driver.find_element(By.ID, "signup-submit").click()
1✔
1047
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1048
            EC.presence_of_element_located(
1049
                (By.CSS_SELECTOR, 'a[href="mailto:yeti4a@snowman.com"]')
1050
            )
1051
        )
1052
        outbox = get_outbox(MAIL_STORAGE_NAME)
1✔
1053
        confirmation_link = self.find_urls(outbox[-1].body)[0]
1✔
1054
        self.driver.get(confirmation_link)
1✔
1055
        self.driver.find_element(By.ID, "terms-check").click()
1✔
1056
        self.driver.find_element(By.ID, "test-check").click()
1✔
1057
        self.driver.find_element(By.ID, "submit").click()
1✔
1058
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1059
            EC.text_to_be_present_in_element(
1060
                (By.CSS_SELECTOR, ".fw-contents h1"), "Thanks for verifying!"
1061
            )
1062
        )
1063
        self.driver.find_element(By.CSS_SELECTOR, 'a[href="/"]').click()
1✔
1064
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti4")
1✔
1065
        self.driver.find_element(By.ID, "id-password").send_keys(
1✔
1066
            "TestPassword123!"
1067
        )
1068
        self.driver.find_element(By.ID, "login-submit").click()
1✔
1069
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1070
            EC.presence_of_element_located(
1071
                (By.XPATH, '//*[normalize-space()="Go to contacts"]')
1072
            )
1073
        ).click()
1074
        self.driver.find_element(By.CSS_SELECTOR, ".respond-invite").click()
1✔
1075
        self.driver.find_element(
1✔
1076
            By.XPATH, '//*[normalize-space()="Accept invite"]'
1077
        ).click()
1078
        self.driver.find_element(
1✔
1079
            By.XPATH, '//*[normalize-space()="Documents"]'
1080
        ).click()
1081
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1082
            EC.presence_of_element_located(
1083
                (By.CSS_SELECTOR, ".new_document button")
1084
            )
1085
        )
1086
        self.assert_with_retry(self.check_document_count, 1)
1✔
1087
        self.driver.find_element(By.CSS_SELECTOR, "#preferences-btn").click()
1✔
1088
        self.driver.find_element(
1✔
1089
            By.XPATH, '//*[normalize-space()="Log out"]'
1090
        ).click()
1091
        # User 5 signs up with a different email first and then clicks the
1092
        # invitation link and accepts the invite.
1093
        self.create_user(
1✔
1094
            username="Yeti5",
1095
            email="yeti5a@snowman.com",
1096
            passtext="TestPassword123!",
1097
        )
1098
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti5")
1✔
1099
        self.driver.find_element(By.ID, "id-password").send_keys(
1✔
1100
            "TestPassword123!"
1101
        )
1102
        self.driver.find_element(By.ID, "login-submit").click()
1✔
1103
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1104
            EC.presence_of_element_located(
1105
                (By.CSS_SELECTOR, ".new_document button")
1106
            )
1107
        )
1108
        self.assert_with_retry(self.check_document_count, 0)
1✔
1109
        invitation_link = self.find_urls(user5_invitation_email)[0]
1✔
1110
        self.driver.get(invitation_link)
1✔
1111
        self.driver.find_element(By.CSS_SELECTOR, ".respond-invite").click()
1✔
1112
        self.driver.find_element(
1✔
1113
            By.XPATH, '//*[normalize-space()="Accept invite"]'
1114
        ).click()
1115
        self.driver.find_element(
1✔
1116
            By.XPATH, '//*[normalize-space()="Documents"]'
1117
        ).click()
1118
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1119
            EC.presence_of_element_located(
1120
                (By.CSS_SELECTOR, ".new_document button")
1121
            )
1122
        )
1123
        self.assert_with_retry(self.check_document_count, 1)
1✔
1124
        self.driver.find_element(By.CSS_SELECTOR, "#preferences-btn").click()
1✔
1125
        self.driver.find_element(
1✔
1126
            By.XPATH, '//*[normalize-space()="Log out"]'
1127
        ).click()
1128
        # User 6 signs up with a different email first and then clicks the
1129
        # invitation link and declines the invite.
1130
        self.create_user(
1✔
1131
            username="Yeti6",
1132
            email="yeti6a@snowman.com",
1133
            passtext="TestPassword123!",
1134
        )
1135
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti6")
1✔
1136
        self.driver.find_element(By.ID, "id-password").send_keys(
1✔
1137
            "TestPassword123!"
1138
        )
1139
        self.driver.find_element(By.ID, "login-submit").click()
1✔
1140
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1141
            EC.presence_of_element_located(
1142
                (By.CSS_SELECTOR, ".new_document button")
1143
            )
1144
        )
1145
        self.assert_with_retry(self.check_document_count, 0)
1✔
1146
        invitation_link = self.find_urls(user6_invitation_email)[0]
1✔
1147
        self.driver.get(invitation_link)
1✔
1148
        self.driver.find_element(By.CSS_SELECTOR, ".respond-invite").click()
1✔
1149
        self.driver.find_element(
1✔
1150
            By.XPATH, '//*[normalize-space()="Decline invite"]'
1151
        ).click()
1152
        self.driver.find_element(
1✔
1153
            By.XPATH, '//*[normalize-space()="Documents"]'
1154
        ).click()
1155
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1156
            EC.presence_of_element_located(
1157
                (By.CSS_SELECTOR, ".new_document button")
1158
            )
1159
        )
1160
        self.assert_with_retry(self.check_document_count, 0)
1✔
1161
        self.driver.find_element(By.CSS_SELECTOR, "#preferences-btn").click()
1✔
1162
        self.driver.find_element(
1✔
1163
            By.XPATH, '//*[normalize-space()="Log out"]'
1164
        ).click()
1165
        # User3 signs in and accepts the invite of user7. Access rights
1166
        # should be upgraded to write access.
1167
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti3")
1✔
1168
        self.driver.find_element(By.ID, "id-password").send_keys(
1✔
1169
            "TestPassword123!"
1170
        )
1171
        self.driver.find_element(By.ID, "login-submit").click()
1✔
1172
        time.sleep(1)
1✔
1173
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1174
            EC.presence_of_element_located(
1175
                (By.CSS_SELECTOR, ".new_document button")
1176
            )
1177
        )
1178
        self.assert_with_retry(self.check_document_count, 2)
1✔
1179
        read_access_rights = self.driver.find_elements(
1✔
1180
            By.CSS_SELECTOR, ".fw-contents tbody tr .icon-access-read"
1181
        )
1182
        self.assertEqual(len(read_access_rights), 1)
1✔
1183
        invitation_link = self.find_urls(user7_invitation_email)[0]
1✔
1184
        self.driver.get(invitation_link)
1✔
1185
        time.sleep(1)
1✔
1186
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1187
            EC.presence_of_element_located(
1188
                (By.CSS_SELECTOR, ".respond-invite")
1189
            )
1190
        ).click()
1191
        self.driver.find_element(
1✔
1192
            By.XPATH, '//*[normalize-space()="Accept invite"]'
1193
        ).click()
1194
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1195
            EC.presence_of_element_located(
1196
                (By.XPATH, '//*[normalize-space()="Documents"]')
1197
            )
1198
        ).click()
1199
        time.sleep(1)
1✔
1200
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1201
            EC.presence_of_element_located(
1202
                (By.CSS_SELECTOR, ".new_document button")
1203
            )
1204
        )
1205
        self.assert_with_retry(self.check_document_count, 2)
1✔
1206
        time.sleep(1)
1✔
1207
        doc_texts = self.driver.find_elements(
1✔
1208
            By.CSS_SELECTOR, ".fw-searchable"
1209
        )
1210
        self.assertEqual(doc_texts[0].text, "A test article to share")
1✔
1211
        self.assertEqual(doc_texts[1].text, "Yeti")
1✔
1212
        self.assertEqual(doc_texts[2].text, "Copy of A test article to share")
1✔
1213
        self.assertEqual(doc_texts[3].text, "Yeti3")
1✔
1214
        write_access_rights = self.driver.find_elements(
1✔
1215
            By.CSS_SELECTOR, ".fw-contents tbody tr .icon-access-write"
1216
        )
1217
        self.assertEqual(len(write_access_rights), 2)
1✔
1218
        self.driver.find_element(By.CSS_SELECTOR, "#preferences-btn").click()
1✔
1219
        self.driver.find_element(
1✔
1220
            By.XPATH, '//*[normalize-space()="Log out"]'
1221
        ).click()
1222
        # Log in as document owner and downgrade access right of user 3.
1223
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti")
1✔
1224
        self.driver.find_element(By.ID, "id-password").send_keys("otter")
1✔
1225
        self.driver.find_element(By.ID, "login-submit").click()
1✔
1226
        time.sleep(1)
1✔
1227
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1228
            EC.element_to_be_clickable(
1229
                (By.CSS_SELECTOR, ".new_document button")
1230
            )
1231
        )
1232
        self.assert_with_retry(self.check_document_count, 2)
1✔
1233
        self.driver.find_element(
1✔
1234
            By.CSS_SELECTOR, ".fw-contents tbody tr .icon-access-write"
1235
        ).click()
1236
        # Downgrade the write rights to read rights for user 3
1237
        self.driver.find_element(
1✔
1238
            By.CSS_SELECTOR, "tr:nth-child(1) .fa-caret-down.edit-right"
1239
        ).click()
1240
        self.driver.find_element(
1✔
1241
            By.XPATH, '//*[normalize-space()="Read"]'
1242
        ).click()
1243
        self.driver.find_element(By.CSS_SELECTOR, "#my-contacts").click()
1✔
1244
        self.driver.find_element(
1✔
1245
            By.CSS_SELECTOR, ".ui-dialog .fw-dark"
1246
        ).click()
1247
        # Enter contacts page and check number of contacts
1248
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1249
            EC.element_to_be_clickable((By.ID, "preferences-btn"))
1250
        ).click()
1251
        self.driver.find_element(
1✔
1252
            By.XPATH, '//*[normalize-space()="Contacts"]'
1253
        ).click()
1254
        self.assertEqual(
1✔
1255
            len(
1256
                self.driver.find_elements(
1257
                    By.CSS_SELECTOR, ".contacts-table .entry-select.user"
1258
                )
1259
            ),
1260
            4,
1261
        )
1262
        # Delete contact connection of user 4 - doc access should be gone.
1263
        self.driver.find_elements(By.CSS_SELECTOR, ".delete-single-contact")[
1✔
1264
            2
1265
        ].click()
1266
        self.driver.find_element(By.CSS_SELECTOR, "button.fw-dark").click()
1✔
1267
        time.sleep(self.wait_time / 3)
1✔
1268
        self.assertEqual(
1✔
1269
            len(
1270
                self.driver.find_elements(
1271
                    By.CSS_SELECTOR, ".contacts-table .entry-select.user"
1272
                )
1273
            ),
1274
            3,
1275
        )
1276
        self.driver.find_element(By.CSS_SELECTOR, "#preferences-btn").click()
1✔
1277
        self.driver.find_element(
1✔
1278
            By.XPATH, '//*[normalize-space()="Log out"]'
1279
        ).click()
1280
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti3")
1✔
1281
        self.driver.find_element(By.ID, "id-password").send_keys(
1✔
1282
            "TestPassword123!"
1283
        )
1284
        self.driver.find_element(By.ID, "login-submit").click()
1✔
1285
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1286
            EC.element_to_be_clickable(
1287
                (By.CSS_SELECTOR, ".new_document button")
1288
            )
1289
        )
1290
        self.assert_with_retry(self.check_document_count, 2)
1✔
1291
        read_access_rights = self.driver.find_elements(
1✔
1292
            By.CSS_SELECTOR, ".fw-contents tbody tr .icon-access-read"
1293
        )
1294
        self.assertEqual(len(read_access_rights), 1)
1✔
1295
        self.driver.find_element(By.CSS_SELECTOR, "#preferences-btn").click()
1✔
1296
        self.driver.find_element(
1✔
1297
            By.XPATH, '//*[normalize-space()="Log out"]'
1298
        ).click()
1299
        self.driver.find_element(By.ID, "id-login").send_keys("Yeti4")
1✔
1300
        self.driver.find_element(By.ID, "id-password").send_keys(
1✔
1301
            "TestPassword123!"
1302
        )
1303
        self.driver.find_element(By.ID, "login-submit").click()
1✔
1304
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1305
            EC.element_to_be_clickable(
1306
                (By.CSS_SELECTOR, ".new_document button")
1307
            )
1308
        )
1309
        # Wait for document list to be interactive
1310
        WebDriverWait(self.driver, self.wait_time).until(
1✔
1311
            EC.presence_of_element_located((By.CSS_SELECTOR, ".fw-contents"))
1312
        )
1313
        self.assert_with_retry(self.check_document_count, 0)
1✔
1314
        self.driver.find_element(By.CSS_SELECTOR, "#preferences-btn").click()
1✔
1315
        self.driver.find_element(
1✔
1316
            By.XPATH, '//*[normalize-space()="Log out"]'
1317
        ).click()
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