diff --git a/gratipay/testing/browser.py b/gratipay/testing/browser.py index 9d9441590c..9d1cec8dcc 100644 --- a/gratipay/testing/browser.py +++ b/gratipay/testing/browser.py @@ -5,9 +5,12 @@ import atexit import os import time +from contextlib import contextmanager from splinter.browser import _DRIVERS from selenium.common.exceptions import StaleElementReferenceException +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support.expected_conditions import staleness_of from gratipay.security import user @@ -148,6 +151,13 @@ def wait_for_error(self, message=None): """ return self.wait_for_notification('error', message) + @contextmanager + def page_reload_afterwards(self, timeout=2): + # www.obeythetestinggoat.com/how-to-get-selenium-to-wait-for-page-load-after-a-click.html + old_page = self._browser.driver.find_element_by_tag_name('html') + yield + WebDriverWait(self._browser, timeout).until(staleness_of(old_page)) + def __getattr__(self, name): try: out = self.__getattribute__(name) diff --git a/js/gratipay/emails.js b/js/gratipay/emails.js index ae759e1eed..b159f00b80 100644 --- a/js/gratipay/emails.js +++ b/js/gratipay/emails.js @@ -19,9 +19,6 @@ Gratipay.emails.post = function() { case 'resend': Gratipay.notification(msg, 'success'); break; - case 'remove': - $row.slideUp(function() { $row.remove() }); - break; default: window.location.reload(); }; diff --git a/tests/ttw/test_email_verification.py b/tests/ttw/test_email_verification.py index 57be0d31f5..7e3990f1fa 100644 --- a/tests/ttw/test_email_verification.py +++ b/tests/ttw/test_email_verification.py @@ -5,100 +5,156 @@ from gratipay.testing import BrowserHarness, QueuedEmailHarness +class Row(object): + """Represent one row in the listing on the emails page. + """ + + def __init__(self, harness, element): + self.harness = harness + self.element = element + + def buttons(self): + """Return a list of the text of all buttons in the row. + """ + return [b.text for b in self.harness.css('button', self.element)] + + def click(self, text): + """Given the text of a button in the row, click the button. + """ + [b for b in self.harness.css('button', self.element) if b.text == text][0].click() + + def add(self, email_address, expecting_reload=True): + """Given an email address, add it via the UI. Only works for add rows! + """ + self.click('Add an email address') + self.harness.css('input', self.element).fill(email_address) + if expecting_reload: + with self.harness.page_reload_afterwards(): + self.click('Send verification') + else: + self.click('Send verification') + + def remove(self): + with self.harness.page_reload_afterwards(): + self.click('Remove') + + +class Page(object): + """Represent the emails page. + """ + + def __init__(self, harness): + self.harness = harness + + @property + def rows(self): + return [Row(self.harness, element) for element in self.harness.css('.emails.listing tr')] + + @property + def names(self): + out = [x.text for x in self.harness.css('.emails.listing tr.existing .listing-name')] + add_row = self.harness.css('.emails.listing tr.add') + if add_row: + out.append('') + return out + + def add_and_verify(self, email_address): + self.add(email_address) + assert self.harness.pop_email_message()['subject'] == 'New activity on your account' + self.harness.visit(self.harness.get_verification_link()) + + class Tests(BrowserHarness, QueuedEmailHarness): + def get_verification_link(self): + """Return the verification link sent via email. + """ + verification_email = self.pop_email_message() + verification_link = re.match( r'.*?(http.*?$).*' + , verification_email['body_text'] + , re.DOTALL | re.MULTILINE + ).groups()[0] + return verification_link def test(self): self.alice = self.make_participant('alice', claimed_time='now') self.sign_in('alice') self.visit('/~alice/emails/') - - # Helpers - - buttons = lambda: [b.text for b in self.css('button', row)] - click = lambda text: [b for b in self.css('button', row) if b.text == text][0].click() - - def add(email_address): - click('Add an email address') - self.css('input', row).fill(email_address) - click('Send verification') - - def get_verification_link(): - verification_email = self.pop_email_message() - verification_link = re.match( r'.*?(http.*?$).*' - , verification_email['body_text'] - , re.DOTALL | re.MULTILINE - ).groups()[0] - return verification_link - + page = Page(self) # state manager # Starts clean. - rows = self.css('#content tr') - assert len(rows) == 1 - row = rows[0] - assert buttons() == ['Add an email address', '', ''] + assert len(page.rows) == 1 + row = page.rows[0] + assert row.buttons() == ['Add an email address', '', ''] # Can toggle add form on and off and on again. - click('Add an email address') - assert buttons() == ['', 'Send verification', 'Cancel'] - click('Cancel') - assert buttons() == ['Add an email address', '', ''] + row.click('Add an email address') + assert row.buttons() == ['', 'Send verification', 'Cancel'] + row.click('Cancel') + assert row.buttons() == ['Add an email address', '', ''] # Can submit add form. - add('alice@example.com') - row = self.wait_for('tr.existing') - assert buttons() == ['Resend verification'] - self.pop_email_message() # throw away verification message + row.add('alice@example.com') + assert len(page.rows) == 1 # no add form when verification pending + row = page.rows[0] + assert row.buttons() == ['Resend', 'Remove'] # Can resend verification. - click('Resend verification') + self.pop_email_message() # oops! lost the verification message + row.click('Resend') assert self.wait_for_success() == 'Check your inbox for a verification link.' # Can verify. - self.visit(get_verification_link()) + self.visit(self.get_verification_link()) assert self.css('#content h1').text == 'Success!' # Now listed as primary -- nothing to be done with primary. self.visit('/~alice/emails/') - rows = self.css('.emails.listing tr') - assert len(rows) == 2 - row = rows[0] - assert buttons() == [] - assert row.text.endswith('Your primary email address') + assert len(page.rows) == 2 + row = page.rows[0] + assert row.buttons() == [] + assert row.element.text.endswith('Your primary email address') # ... but the add form is back. Can we re-add the same? - row = rows[1] - add('alice@example.com') + row = page.rows[1] + row.add('alice@example.com', expecting_reload=False) assert self.wait_for_error() == 'You have already added and verified that address.' # No! - click('Cancel') + row.click('Cancel') # Let's add another! - add('alice@example.net') - self.wait_for('.emails.listing tr') + row.add('alice@example.net') assert self.pop_email_message()['subject'] == 'New activity on your account' - self.visit(get_verification_link()) + self.visit(self.get_verification_link()) + with self.page_reload_afterwards(): + self.css('#content a').click() # back over to /emails/ # Now we should have a primary and a linked address, and an add row. - self.css('#content a').click() - rows = self.wait_for('.emails.listing tr') - assert len(rows) == 3 - email_addresses = [x.text for x in self.css('.existing .listing-name')] - assert email_addresses == ['alice@example.com', 'alice@example.net'] - row = rows[2] - assert buttons() == ['Add an email address', '', ''] + assert page.names == ['alice@example.com', 'alice@example.net', ''] + assert page.rows[2].buttons() == ['Add an email address', '', ''] # We can promote the secondary account to primary. - row = rows[1] - click('Set as primary') - rows = self.wait_for('.emails.listing tr') - assert len(rows) == 3 + with self.page_reload_afterwards(): + page.rows[1].click('Set as primary') # ... and now the order is reversed. - email_addresses = [x.text for x in self.css('.existing .listing-name')] - assert email_addresses == ['alice@example.net', 'alice@example.com'] + assert page.names == ['alice@example.net', 'alice@example.com', ''] # We can remove the (new) secondary account. - row = rows[1] - click('Remove') - self.wait_to_disappear('tr[data-email="alice@example.com"]') - assert len(self.css('.emails.listing tr')) == 2 + page.rows[1].remove() + assert page.names == ['alice@example.net', ''] + + # Add another again. + page.rows[1].add('alice@example.org') + assert self.pop_email_message()['subject'] == 'New activity on your account' + verification_link = self.get_verification_link() + + # No add form while pending + assert page.names == ['alice@example.net', 'alice@example.org'] + + # But we can remove a pending verification. + page.rows[1].remove() + assert page.names == ['alice@example.net', ''] + + # The link no longer works, of course. + self.visit(verification_link) + assert self.css('#content h1').text == 'Bad Info' diff --git a/www/~/%username/emails/index.spt b/www/~/%username/emails/index.spt index 89e075ddc8..c1e47fcd4c 100644 --- a/www/~/%username/emails/index.spt +++ b/www/~/%username/emails/index.spt @@ -41,15 +41,16 @@ page_id = 'emails' {% else %} {{ icons.STATUS_ICONS['success']|safe }} - {{ _('Linked') }} - · - · + {{ _('Linked') }} · + · + {% endif %} {% else %} {{ icons.STATUS_ICONS['warning']|safe }} {{ _('Check your inbox') }} · - + · + {% endif %}