From 3b4899ac7934161344b070cf6aee92de0ea5c64a Mon Sep 17 00:00:00 2001
From: Leah Klearman <lklrmn@gmail.com>
Date: Tue, 14 Aug 2012 15:54:53 -0700
Subject: [PATCH] working for WinXP FF13

---
 .gitignore                                    |   1 +
 automation-tests/persona_server/__init__.py   |   0
 .../persona_server/pages/__init__.py          |   0
 .../persona_server/pages/account_manager.py   | 115 +++++++++++++
 automation-tests/persona_server/pages/base.py |  35 ++++
 .../pages/complete_registration.py            |  77 +++++++++
 automation-tests/persona_server/pages/home.py |  41 +++++
 .../persona_server/pages/sign_in.py           | 162 ++++++++++++++++++
 .../persona_server/tests/__init__.py          |   0
 automation-tests/persona_server/tests/base.py |  49 ++++++
 .../persona_server/tests/conftest.py          |  11 ++
 .../tests/test_manage_account.py              | 124 ++++++++++++++
 .../persona_server/tests/test_sign_in_unit.py |  42 +++++
 13 files changed, 657 insertions(+)
 create mode 100644 automation-tests/persona_server/__init__.py
 create mode 100644 automation-tests/persona_server/pages/__init__.py
 create mode 100644 automation-tests/persona_server/pages/account_manager.py
 create mode 100644 automation-tests/persona_server/pages/base.py
 create mode 100644 automation-tests/persona_server/pages/complete_registration.py
 create mode 100644 automation-tests/persona_server/pages/home.py
 create mode 100644 automation-tests/persona_server/pages/sign_in.py
 create mode 100644 automation-tests/persona_server/tests/__init__.py
 create mode 100644 automation-tests/persona_server/tests/base.py
 create mode 100644 automation-tests/persona_server/tests/conftest.py
 create mode 100644 automation-tests/persona_server/tests/test_manage_account.py
 create mode 100644 automation-tests/persona_server/tests/test_sign_in_unit.py

diff --git a/.gitignore b/.gitignore
index dd88ea040..10f154cca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@
 Thumbs.db
 /locale
 /resources/email_templates/email-test.html
+automation-tests/persona_server/results/*
\ No newline at end of file
diff --git a/automation-tests/persona_server/__init__.py b/automation-tests/persona_server/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/persona_server/pages/__init__.py b/automation-tests/persona_server/pages/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/persona_server/pages/account_manager.py b/automation-tests/persona_server/pages/account_manager.py
new file mode 100644
index 000000000..d1ed766f6
--- /dev/null
+++ b/automation-tests/persona_server/pages/account_manager.py
@@ -0,0 +1,115 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+from base import Base
+
+
+class AccountManager(Base):
+    '''AccountManager is used when logged in. Use HomePage page if not logged in.'''
+
+    _page_url = '/'
+    _emails_locator = (By.CSS_SELECTOR, '#emailList .email')
+    _edit_password_button_locator = (By.CSS_SELECTOR, '#edit_password button.edit')
+    _old_password_field_locator = (By.ID, 'old_password')
+    _new_password_field_locator = (By.ID, 'new_password')
+    _change_password_done_locator = (By.ID, 'changePassword')
+    _sign_in_locator = (By.CSS_SELECTOR, 'a.signIn')
+    _sign_out_locator = (By.CSS_SELECTOR, 'a.signOut')
+    _cancel_account_locator = (By.ID, 'cancelAccount')
+
+    def load_page(self):
+        Base.load_page(self)
+        self.wait_for_page_to_load()
+
+    def wait_for_page_to_load(self):
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._emails_locator).is_displayed())
+
+    @property
+    def emails(self):
+        """Returns a textual list of email addresses associated with the currently signed in user."""
+        return [element.text for element in self.selenium.find_elements(*self._emails_locator)]
+
+    def click_edit_password(self):
+        """Click edit password to show the new/old password fields"""
+        self.selenium.find_element(*self._edit_password_button_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._old_password_field_locator).is_displayed())
+
+    @property
+    def old_password(self):
+        """Get the value of the old password field."""
+        return self.selenium.find_element(*self._old_password_field_locator).text
+
+    @old_password.setter
+    def old_password(self, value):
+        """Set the value of the old password field."""
+        password = self.selenium.find_element(*self._old_password_field_locator)
+        password.clear()
+        password.send_keys(value)
+
+    @property
+    def new_password(self):
+        """Get the value of the new password field."""
+        return self.selenium.find_element(*self._new_password_field_locator).text
+
+    @new_password.setter
+    def new_password(self, value):
+        """Set the value of the new password field."""
+        password = self.selenium.find_element(*self._new_password_field_locator)
+        password.clear()
+        password.send_keys(value)
+
+    def click_password_done(self):
+        """Click password done to save the new password."""
+        self.selenium.find_element(*self._change_password_done_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._edit_password_button_locator).is_displayed())
+
+    def click_sign_out(self):
+        """Click the Sign Out button"""
+        self.selenium.find_element(*self._sign_out_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: not self.signed_in)
+
+    def click_cancel_account(self):
+        """Click the cancel account link."""
+        self.selenium.find_element(*self._cancel_account_locator).click()
+
+    def change_password(self, old_password, new_password):
+        """
+        Helper function change_password(old_password, new_password) performs the
+        series of actions necessary to change the password.
+        """
+
+        self.click_edit_password()
+        self.old_password = old_password
+        self.new_password = new_password
+        self.click_password_done()
+
+    def sign_out(self):
+        """
+        Helper function sign_out() performs the series of actions necessary to
+        sign out.
+        """
+
+        self.click_sign_out()
+        from home import HomePage  # circular reference
+        return HomePage(self.mozwebqa)
+
+    def cancel_account(self):
+        """
+        Helper function cancel_account() performs the series of actions necessary
+        to cancel the account of the currently signed in user.
+        """
+
+        self.click_cancel_account()
+        self.selenium.switch_to_alert().accept()
+        from home import HomePage  # circular reference
+        return HomePage(self.mozwebqa)
diff --git a/automation-tests/persona_server/pages/base.py b/automation-tests/persona_server/pages/base.py
new file mode 100644
index 000000000..80e3746b5
--- /dev/null
+++ b/automation-tests/persona_server/pages/base.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+class Base(object):
+
+    _body_locator = (By.TAG_NAME, 'body')
+
+    def __init__(self, mozwebqa):
+        self.mozwebqa = mozwebqa
+        self.selenium = mozwebqa.selenium
+        self.timeout = mozwebqa.timeout
+        self.base_url = mozwebqa.base_url
+
+    @property
+    def signed_in(self):
+        """Returns True/False whether a user is signed in."""
+        return 'not_authenticated' not in self.selenium.find_element(*self._body_locator).get_attribute('class')
+
+    def load_page(self):
+        if self._page_url:
+            self.selenium.get(self.base_url + self._page_url)
+            self.wait_for_ajax()
+
+    def wait_for_ajax(self):
+        """Waits for the script 'jQuery.active == 0'."""
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.execute_script("return jQuery.active == 0"),
+            "Wait for AJAX timed out after %s seconds" % self.timeout)
diff --git a/automation-tests/persona_server/pages/complete_registration.py b/automation-tests/persona_server/pages/complete_registration.py
new file mode 100644
index 000000000..abcc5ce80
--- /dev/null
+++ b/automation-tests/persona_server/pages/complete_registration.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from base import Base
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+
+class CompleteRegistration(Base):
+
+    _page_title = 'Mozilla Persona: Complete Registration'
+    _email_locator = (By.ID, 'email')
+    _password_locator = (By.ID, 'password')
+    _finish_locator = (By.CSS_SELECTOR, 'div.submit > button')
+    _thank_you_locator = (By.ID, 'congrats')
+
+    def __init__(self, mozwebqa, url, expect='redirect'):
+        """
+        class init method
+        :Args:
+        - url - the confirmation url from the email
+        - expect - redirect/success/reset/verify (default redirect)
+        """
+        Base.__init__(self, mozwebqa)
+
+        self.selenium.get(url)
+
+        if expect == 'redirect':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.title != self._page_title,
+                "Complete Registration page did not redirect")
+        elif expect == 'success':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: 'Thank you' in s.find_element(*self._thank_you_locator).text,
+                "Complete Registration did not succeed")
+        elif expect == 'reset':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: 'verified' in s.find_element(*self._thank_you_locator).text,
+                "Complete Registration did not succeed")
+        elif expect == 'verify':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.find_element(*self._password_locator).is_displayed(),
+                "password field did not become visible")
+        else:
+            raise Exception('Unknown expect value: %s' % expect)
+
+    @property
+    def email(self):
+        """Get the value of the email field."""
+        return self.selenium.find_element(*self._email_locator).text
+
+    @property
+    def password(self):
+        """Get the value of the password field."""
+        return self.selenium.find_element(*self._password_locator).text
+
+    @password.setter
+    def password(self, value):
+        """Set the value of the password field."""
+        password = self.selenium.find_element(*self._password_locator)
+        password.clear()
+        password.send_keys(value)
+
+    def click_finish(self):
+        """Clicks the 'finish' button."""
+        self.selenium.find_element(*self._finish_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._thank_you_locator).is_displayed())
+
+    @property
+    def thank_you(self):
+        """Returns the 'thank you' message."""
+        return self.selenium.find_element(*self._thank_you_locator).text
diff --git a/automation-tests/persona_server/pages/home.py b/automation-tests/persona_server/pages/home.py
new file mode 100644
index 000000000..50ef74bea
--- /dev/null
+++ b/automation-tests/persona_server/pages/home.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import time
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+from base import Base
+from sign_in import SignIn
+
+
+class HomePage(Base):
+    '''HomePage is used when not logged in. Use AccountManager page if logged in.'''
+
+    _page_title = 'Mozilla Persona: A Better Way to Sign In'
+    _page_url = '/'
+    _sign_in_locator = (By.CSS_SELECTOR, 'a.signIn')
+    _sign_up_locator = (By.CSS_SELECTOR, 'a.button.create')
+    _manage_section_locator = (By.ID, 'manage')
+    _sign_out_locator = (By.CSS_SELECTOR, 'a.signOut')
+
+    def __init__(self, mozwebqa):
+        Base.__init__(self, mozwebqa)
+
+        WebDriverWait(self.selenium, self.timeout * 2).until(
+                lambda s: s.find_element(*self._sign_in_locator) and \
+                s.find_element(*self._sign_in_locator).is_displayed(),
+                "the sign in button has not appeared within %s" % self.timeout * 2)
+
+    def click_sign_up(self):
+        """Clicks the Sign Up button."""
+        self.selenium.find_element(*self._sign_up_locator).click()
+        return SignIn(self.mozwebqa)
+
+    def click_sign_in(self):
+        """Clicks the Sign In button."""
+        self.selenium.find_element(*self._sign_in_locator).click()
+        return SignIn(self.mozwebqa)
diff --git a/automation-tests/persona_server/pages/sign_in.py b/automation-tests/persona_server/pages/sign_in.py
new file mode 100644
index 000000000..6ed314efe
--- /dev/null
+++ b/automation-tests/persona_server/pages/sign_in.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+from base import Base
+from account_manager import AccountManager
+
+
+class SignIn(Base):
+
+    _email_locator = (By.ID, 'email')
+    _next_locator = (By.ID, 'next')
+    _sign_in_locator = (By.ID, 'signIn')
+    _verify_email_locator = (By.ID, 'verifyEmail')
+    _password_locator = (By.ID, 'password')
+    _password_verify_locator = (By.ID, 'vpassword')
+    _forgot_password_locator = (By.CSS_SELECTOR, 'a.forgot')
+    _reset_password_locator = (By.CSS_SELECTOR, '#signUpForm button')
+    _check_your_email_locator = (By.CSS_SELECTOR, '.notification.emailsent > h2')
+
+    def __init__(self, mozwebqa):
+        Base.__init__(self, mozwebqa)
+
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._email_locator) and \
+            s.find_element(*self._email_locator).is_displayed(),
+            "email field did not appear within %s" % self.timeout)
+
+    @property
+    def email(self):
+        """Get the value of the email field."""
+        return self.selenium.find_element(*self._email_locator).get_attribute('value')
+
+    @email.setter
+    def email(self, value):
+        """Set the value of the email field."""
+        field = self.selenium.find_element(*self._email_locator)
+        field.clear()
+        field.send_keys(value)
+
+    def click_next(self):
+        """Click the 'next' button (after filling in email)."""
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._next_locator) and \
+            s.find_element(*self._next_locator).is_displayed(),
+            "next button is not found / not visible")
+        self.selenium.find_element(*self._next_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._password_locator).is_displayed(),
+            "Password field did not appear within %s" % self.timeout
+        )
+
+    def click_sign_in(self):
+        """Click the 'Sign In' button (after filling in password in the sign-in flow)."""
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._sign_in_locator) and \
+            s.find_element(*self._sign_in_locator).is_displayed(),
+            "sign in button not found / not visible")
+        self.selenium.find_element(*self._sign_in_locator).click()
+        self.wait_for_ajax()
+        print "clicked sign in"
+
+    def click_verify_email(self):
+        """Click the 'Verify Email' button (after filling in password and verify in the sign-up flow)."""
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._verify_email_locator) and \
+            s.find_element(*self._verify_email_locator).is_displayed(),
+            "verify email button not found / not visible")
+        self.selenium.find_element(*self._verify_email_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._check_your_email_locator).is_displayed(),
+            "check your email message did not appear")
+
+    @property
+    def password(self):
+        """Get the value of the password field."""
+        return self.selenium.find_element(*self._password_locator).get_attribute('value')
+
+    @password.setter
+    def password(self, value):
+        """Sets the value of the password field."""
+        field = self.selenium.find_element(*self._password_locator)
+        field.clear()
+        field.send_keys(value)
+
+    @property
+    def verify_password(self):
+        """Get the value of the verify password field."""
+        return self.selenium.find_element(*self._password_verify_locator).get_attribute('value')
+
+    @verify_password.setter
+    def verify_password(self, value):
+        """Set the value of the verify password field."""
+        field = self.selenium.find_element(*self._password_verify_locator)
+        field.clear()
+        field.send_keys(value)
+
+    def click_forgot_password(self):
+        """Clicks the forgot password link."""
+        self.selenium.find_element(*self._forgot_password_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._password_verify_locator).is_displayed(),
+            "verify password field did not appear within %s" % self.timeout)
+
+    def click_reset_password(self):
+        """Clicks the reset password button."""
+        self.selenium.find_element(*self._reset_password_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._check_your_email_locator).is_displayed(),
+            "check your email message did not appear")
+
+    @property
+    def check_your_email_title_text(self):
+        """Get the text of the result notification title."""
+        return self.selenium.find_element(*self._check_your_email_locator).text
+
+    @property
+    def is_sign_up_flow(self):
+        """Returns true if the current page has the password verify field"""
+        return self.selenium.find_element(*self._password_verify_locator).is_displayed()
+
+    def sign_in(self, email, password):
+        """
+        Helper method sign_in(email, password) signs in with the provided email
+        address and password.
+        """
+        self.email = email
+        self.click_next()
+        self.password = password
+        self.click_sign_in()
+        # should redirect to Account Manager (home, logged in) page
+        account_manager = AccountManager(self.mozwebqa)
+        account_manager.wait_for_page_to_load()
+        return account_manager
+
+    def sign_up(self, email, password):
+        """
+        Helper method sign_up(email, password) signs up with the provided email
+        address and password.
+        """
+        self.email = email
+        self.click_next()
+        self.password = password
+        self.verify_password = password
+        self.click_verify_email()
+        # does not redirect to anywhere
+
+    def forgot_password(self, email, new_password):
+        """
+        Helper method forgot_password(email, new_password) performs the series of
+        actions required to reset the user's password.
+        """
+        self.click_forgot_password()
+        self.password = new_password
+        self.verify_password = new_password
+        self.click_reset_password()
+        # does not redirect to anywhere
diff --git a/automation-tests/persona_server/tests/__init__.py b/automation-tests/persona_server/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/persona_server/tests/base.py b/automation-tests/persona_server/tests/base.py
new file mode 100644
index 000000000..8e175aa22
--- /dev/null
+++ b/automation-tests/persona_server/tests/base.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+import re
+
+from browserid.mocks.user import MockUser
+from browserid.tests import restmail
+from persona_server.pages.home import HomePage
+from persona_server.pages.complete_registration import CompleteRegistration
+from persona_server.pages.account_manager import AccountManager
+
+
+class BaseTest(object):
+
+    # move this to BrowserID when personatestuser.org comes online
+    def create_verified_user(self, mozwebqa):
+        user = MockUser()
+
+        # create the user
+        home = HomePage(mozwebqa)
+        signup = home.click_sign_up()
+        signup.sign_up(user.primary_email, user.password)
+
+        # do email verification
+        complete_registration = CompleteRegistration(mozwebqa,
+            self.get_confirm_url_from_email(user.primary_email),
+            expect='success')
+        assert 'Thank you' in complete_registration.thank_you
+
+        # go sign out and reload page for preconditions
+        account_manager = AccountManager(mozwebqa)
+        account_manager.load_page()
+        account_manager.sign_out()
+        home.load_page()  # test will instantiate HomePage
+
+        return user
+
+    def get_confirm_url_from_email(self, email, message_count=1, regex='(https?:.*?token=.{48})'):
+        '''
+        Checks the restmail inbox for the specified address
+        and returns the confirm url.
+        Specify message_count if you expect there to be more than one message for the user.
+        Specify regex if you wish to use a specific regex. By default searches for a url with a 48 char token."
+        '''
+        mail = restmail.get_mail(email, message_count=message_count, timeout=60)
+        message_text = mail[message_count - 1]['text']
+        return re.search(regex, message_text).group(0)
diff --git a/automation-tests/persona_server/tests/conftest.py b/automation-tests/persona_server/tests/conftest.py
new file mode 100644
index 000000000..bf3facbce
--- /dev/null
+++ b/automation-tests/persona_server/tests/conftest.py
@@ -0,0 +1,11 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+
+def pytest_funcarg__mozwebqa(request):
+    mozwebqa = request.getfuncargvalue('mozwebqa')
+    mozwebqa.selenium.get('%s/' % mozwebqa.base_url)
+    return mozwebqa
diff --git a/automation-tests/persona_server/tests/test_manage_account.py b/automation-tests/persona_server/tests/test_manage_account.py
new file mode 100644
index 000000000..f469056a8
--- /dev/null
+++ b/automation-tests/persona_server/tests/test_manage_account.py
@@ -0,0 +1,124 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import pytest
+from unittestzero import Assert
+
+from persona_server.pages.home import HomePage
+from persona_server.pages.complete_registration import CompleteRegistration
+from persona_server.pages.account_manager import AccountManager
+from browserid.mocks.user import MockUser
+from browserid.tests import restmail
+
+from base import BaseTest
+
+
+class TestManageAccount(BaseTest):
+
+    @pytest.mark.moztrap(272)
+    def test_can_create_new_user_account(self, mozwebqa):
+        user = MockUser()
+        home = HomePage(mozwebqa)
+
+        # sign up
+        signup = home.click_sign_up()
+        signup.sign_up(user.primary_email, user.password)
+        Assert.equal(signup.check_your_email_title_text, 'Confirm your email address')
+
+        # do email verification
+        CompleteRegistration(mozwebqa,
+            self.get_confirm_url_from_email(user.primary_email),
+            expect='success')
+
+        # verify now logged in
+        account_manager = AccountManager(mozwebqa)
+        account_manager.load_page()
+        Assert.true(account_manager.signed_in)
+
+    @pytest.mark.moztrap(273)
+    @pytest.mark.nondestructive
+    def test_that_user_can_sign_in_and_out(self, mozwebqa):
+        # the dev server is being continually wiped, verified user must be fresh
+        user = self.create_verified_user(mozwebqa)
+        home = HomePage(mozwebqa)
+
+        # sign in
+        signin = home.click_sign_in()
+        account_manager = signin.sign_in(user.primary_email, user.password)
+        Assert.true(account_manager.signed_in)
+
+        # sign out
+        home = account_manager.sign_out()
+        Assert.false(home.signed_in)
+
+    @pytest.mark.moztrap(274)
+    def test_that_user_can_change_password(self, mozwebqa):
+        user = self.create_verified_user(mozwebqa)
+
+        # sign in with old password
+        home = HomePage(mozwebqa)
+        signin = home.click_sign_in()
+        account_manager = signin.sign_in(user.primary_email, user.password)
+        Assert.contains(user.primary_email, account_manager.emails)
+
+        # change password
+        old_password = user.password
+        user.password += '_new'
+        account_manager.change_password(old_password, user.password)
+
+        # sign out
+        home = account_manager.sign_out()
+
+        # sign in with new password
+        signin = home.click_sign_in()
+        account_manager = signin.sign_in(user.primary_email, user.password)
+        Assert.true(account_manager.signed_in)
+        Assert.contains(user.primary_email, account_manager.emails)
+
+    @pytest.mark.moztrap(275)
+    def test_that_user_can_cancel_account_with_one_email(self, mozwebqa):
+        user = self.create_verified_user(mozwebqa)
+
+        # sign in
+        home = HomePage(mozwebqa)
+        signin = home.click_sign_in()
+        account_manager = signin.sign_in(user.primary_email, user.password)
+
+        # cancel account
+        home = account_manager.cancel_account()
+
+        # verify email not recognized
+        signin = home.click_sign_in()
+        signin.email = user.primary_email
+        signin.click_next()
+        Assert.true(signin.is_sign_up_flow)
+
+    def test_that_user_can_reset_password(self, mozwebqa):
+        user = self.create_verified_user(mozwebqa)
+
+        # start to sign in
+        home = HomePage(mozwebqa)
+        signin = home.click_sign_in()
+
+        # forgot password
+        user.password += '_new'
+        signin.forgot_password(user.primary_email, user.password)
+        Assert.equal(signin.check_your_email_title_text, 'Confirm your email address')
+
+        # confirm email
+        CompleteRegistration(mozwebqa,
+            self.get_confirm_url_from_email(user.primary_email, message_count=2),
+            expect='reset')
+
+        # sign out
+        account_manager = AccountManager(mozwebqa)
+        account_manager.load_page()
+        home = account_manager.sign_out()
+
+        # sign in with new password
+        signin = home.click_sign_in()
+        account_manager = signin.sign_in(user.primary_email, user.password)
+        Assert.true(account_manager.signed_in)
diff --git a/automation-tests/persona_server/tests/test_sign_in_unit.py b/automation-tests/persona_server/tests/test_sign_in_unit.py
new file mode 100644
index 000000000..0136c94d3
--- /dev/null
+++ b/automation-tests/persona_server/tests/test_sign_in_unit.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import pytest
+from unittestzero import Assert
+
+from persona_server.pages.home import HomePage
+from browserid.mocks.user import MockUser
+
+from base import BaseTest
+
+
+class TestSignInUnit(BaseTest):
+
+    def test_getters_sign_in(self, mozwebqa):
+        user = self.create_verified_user(mozwebqa)
+        home = HomePage(mozwebqa)
+
+        # sign in
+        signin = home.click_sign_in()
+        signin.email = user.primary_email
+        Assert.equal(signin.email, user.primary_email)
+        signin.click_next()
+        signin.password = user.password
+        Assert.equal(signin.password, user.password)
+
+    def test_getters_sign_up(self, mozwebqa):
+        user = MockUser()
+        home = HomePage(mozwebqa)
+
+        # sign up
+        signup = home.click_sign_up()
+        signup.email = user.primary_email
+        Assert.equal(signup.email, user.primary_email)
+        signup.click_next()
+        signup.password = user.password
+        signup.verify_password = user.password
+        Assert.equal(signup.password, user.password)
+        Assert.equal(signup.verify_password, user.password)
-- 
GitLab