diff --git a/automation-tests/browserid/.gitignore b/automation-tests/browserid/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..c1c3e1d2badf37f9f939493b88913eee6e521e18
--- /dev/null
+++ b/automation-tests/browserid/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+*.komodoproject
+build
+results
diff --git a/automation-tests/browserid/.travis.yml b/automation-tests/browserid/.travis.yml
new file mode 100644
index 0000000000000000000000000000000000000000..89d46cb5070cca8418940f3cf71b558ba484e945
--- /dev/null
+++ b/automation-tests/browserid/.travis.yml
@@ -0,0 +1,19 @@
+before_script:
+  - sh -e /etc/init.d/xvfb start
+
+language: python
+python:
+  - 2.6
+  - 2.7
+
+script: py.test --baseurl=http://dev.123done.org --driver=firefox -m travis tests
+
+env:
+  - DISPLAY=':99.0'
+
+notifications:
+  email:
+    - dave.hunt@gmail.com
+  irc:
+    - "irc.mozilla.org#automation"
+    - "irc.mozilla.org#identity"
diff --git a/automation-tests/browserid/README.md b/automation-tests/browserid/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..df702c27ced028d9a83ae20ef4494914525b6b7a
--- /dev/null
+++ b/automation-tests/browserid/README.md
@@ -0,0 +1,15 @@
+**B**rowser**ID** **P**age **O**bject **M**odel
+===============================================
+Selenium compatible page object model for Mozilla's BrowserID.
+
+Documentation
+-------------
+See the project's [wiki](https://github.com/mozilla/bidpom/wiki).
+
+License
+-------
+This software is licensed under the [MPL](http://www.mozilla.org/MPL/2.0/) 2.0:
+
+    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/.
diff --git a/automation-tests/browserid/__init__.py b/automation-tests/browserid/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..087f0766badd9866e8596230707814a76a96ac1f
--- /dev/null
+++ b/automation-tests/browserid/__init__.py
@@ -0,0 +1 @@
+from browser_id import BrowserID
diff --git a/automation-tests/browserid/browser_id.py b/automation-tests/browserid/browser_id.py
new file mode 100644
index 0000000000000000000000000000000000000000..21aea5ebce650d688cc8c32e64b05373c47c9e87
--- /dev/null
+++ b/automation-tests/browserid/browser_id.py
@@ -0,0 +1,25 @@
+#!/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 selenium
+
+
+class BrowserID(object):
+
+    VERIFY_URL_REGEX = 'https?:\/\/(\S+)\/verify_email_address\?token=(.{48})'
+    CONFIRM_URL_REGEX = 'https?:\/\/(\S+)\/confirm\?token=(.{48})'
+    RESET_URL_REGEX = 'https?:\/\/(\S+)\/reset_password\?token=(.{48})'
+    INCLUDE_URL_REGEX = '(https?:\/\/(\S+))\/include\.js'
+
+    def __init__(self, selenium, timeout=60):
+        self.selenium = selenium
+        self.timeout = timeout
+
+    def sign_in(self, email, password):
+        """Signs in using the specified email address and password."""
+        from pages.sign_in import SignIn
+        sign_in = SignIn(self.selenium, timeout=self.timeout, expect='new')
+        sign_in.sign_in(email, password)
diff --git a/automation-tests/browserid/conftest.py b/automation-tests/browserid/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..048e3efc767ae0bf352ece75fefde1ebc9d8a265
--- /dev/null
+++ b/automation-tests/browserid/conftest.py
@@ -0,0 +1,29 @@
+#!/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 py
+
+
+def pytest_runtest_setup(item):
+    pytest_mozwebqa = py.test.config.pluginmanager.getplugin('mozwebqa')
+    pytest_mozwebqa.TestSetup.email = item.config.option.email
+    pytest_mozwebqa.TestSetup.password = item.config.option.password
+
+
+def pytest_addoption(parser):
+    group = parser.getgroup('persona', 'persona')
+    group._addoption('--email',
+                     action='store',
+                     metavar='str',
+                     help='email address for persona account')
+    group._addoption('--password',
+                     action='store',
+                     metavar='str',
+                     help='password for persona account')
+
+
+def pytest_funcarg__mozwebqa(request):
+    return request.getfuncargvalue('mozwebqa')
diff --git a/automation-tests/browserid/mocks/__init__.py b/automation-tests/browserid/mocks/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/automation-tests/browserid/mocks/user.py b/automation-tests/browserid/mocks/user.py
new file mode 100644
index 0000000000000000000000000000000000000000..8be1e3bfd9a2b390989be5cd4cec9552e27e4e2b
--- /dev/null
+++ b/automation-tests/browserid/mocks/user.py
@@ -0,0 +1,21 @@
+#!/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 uuid
+
+
+class MockUser(dict):
+
+    def __init__(self, **kwargs):
+        self['id'] = 'bidpom_%s' % uuid.uuid1()
+        self['primary_email'] = '%s@restmail.net' % self.id
+        self['password'] = 'password'
+        self['additional_emails'] = []
+
+        self.update(**kwargs)
+
+    def __getattr__(self, attr):
+        return self[attr]
diff --git a/automation-tests/browserid/mozwebqa.cfg b/automation-tests/browserid/mozwebqa.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..8b6ad1effcda45be9e2f84987c804c5d56f21eec
--- /dev/null
+++ b/automation-tests/browserid/mozwebqa.cfg
@@ -0,0 +1,3 @@
+[DEFAULT]
+baseurl = http://123done.org
+tags = bidpom
diff --git a/automation-tests/browserid/pages/__init__.py b/automation-tests/browserid/pages/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/automation-tests/browserid/pages/account_manager.py b/automation-tests/browserid/pages/account_manager.py
new file mode 100644
index 0000000000000000000000000000000000000000..0428acd806d1a3d5efe2a357da802e53654085b3
--- /dev/null
+++ b/automation-tests/browserid/pages/account_manager.py
@@ -0,0 +1,76 @@
+#!/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 AccountManager(Base):
+
+    _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')
+
+    def __init__(self, selenium, timeout):
+        Base.__init__(self, selenium, timeout)
+
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._emails_locator).is_displayed())
+
+    @property
+    def signed_in(self):
+        return 'not_authenticated' not in self.selenium.find_element(By.TAG_NAME, 'body').get_attribute('class')
+
+    @property
+    def emails(self):
+        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):
+        self.selenium.find_element(*self._sign_out_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: not self.signed_in)
diff --git a/automation-tests/browserid/pages/base.py b/automation-tests/browserid/pages/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..e8cd99b7bf7ec042cfafafe60fc043dbfbcf5edd
--- /dev/null
+++ b/automation-tests/browserid/pages/base.py
@@ -0,0 +1,18 @@
+#!/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/.
+
+
+class Base(object):
+
+    _page_title = 'Mozilla Persona: A Better Way to Sign In'
+
+    def __init__(self, selenium, timeout=60):
+        self.selenium = selenium
+        self.timeout = timeout
+        self._main_window_handle = self.selenium.current_window_handle
+
+    def switch_to_main_window(self):
+        self.selenium.switch_to_window(self._main_window_handle)
diff --git a/automation-tests/browserid/pages/complete_registration.py b/automation-tests/browserid/pages/complete_registration.py
new file mode 100644
index 0000000000000000000000000000000000000000..141c1ea1fe33a2e89594cc6d625ab34d85423d83
--- /dev/null
+++ b/automation-tests/browserid/pages/complete_registration.py
@@ -0,0 +1,58 @@
+#!/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):
+
+    _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, selenium, timeout, expect='success'):
+        Base.__init__(self, selenium, timeout)
+
+        if expect == 'success':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.find_element(*self._thank_you_locator).is_displayed())
+        elif expect == 'verify':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.find_element(*self._password_locator).is_displayed())
+        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/browserid/pages/sign_in.py b/automation-tests/browserid/pages/sign_in.py
new file mode 100644
index 0000000000000000000000000000000000000000..2a22a4a435b8b26d8d2b625bb449130253917475
--- /dev/null
+++ b/automation-tests/browserid/pages/sign_in.py
@@ -0,0 +1,224 @@
+#!/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 SignIn(Base):
+
+    _this_is_not_me_locator = (By.ID, 'thisIsNotMe')
+    _signed_in_email_locator = (By.CSS_SELECTOR, 'label[for=email_0]')
+    _emails_locator = (By.CSS_SELECTOR, 'label[for^=email_]')
+    _email_locator = (By.ID, 'email')
+    _password_locator = (By.ID, 'password')
+    _verify_password_locator = (By.ID, 'vpassword')
+    _next_locator = (By.CSS_SELECTOR, 'button.start')
+    _sign_in_locator = (By.CSS_SELECTOR, 'button.returning')
+    _sign_in_returning_user_locator = (By.ID, 'signInButton')
+    _verify_email_locator = (By.ID, 'verify_user')
+    _forgot_password_locator = (By.ID, 'forgotPassword')
+    _reset_password_locator = (By.ID, 'password_reset')
+    _check_email_at_locator = (By.CSS_SELECTOR, '#wait .contents h2 + p strong')
+    _add_another_email_locator = (By.ID, 'useNewEmail')
+    _new_email_locator = (By.ID, 'newEmail')
+    _add_new_email_locator = (By.ID, 'addNewEmail')
+
+    def __init__(self, selenium, timeout, expect='new'):
+        Base.__init__(self, selenium, timeout)
+
+        if self.selenium.title != self._page_title:
+            for handle in self.selenium.window_handles:
+                self.selenium.switch_to_window(handle)
+                WebDriverWait(self.selenium, self.timeout).until(lambda s: s.title)
+                if self.selenium.title == self._page_title:
+                    break
+            else:
+                raise Exception('Popup has not loaded')
+
+        if expect == 'new':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.find_element(*self._email_locator).is_displayed())
+        elif expect == 'returning':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.find_element(
+                    *self._sign_in_returning_user_locator).is_displayed())
+            import time
+            time.sleep(2) # TODO: Remove this sleep
+        else:
+            raise Exception('Unknown expect value: %s' % expect)
+
+    def close_window(self):
+        self.selenium.close()
+
+    @property
+    def signed_in_email(self):
+        """Get the value of the email that is currently signed in."""
+        return self.selenium.find_element(*self._signed_in_email_locator).text
+
+    def click_this_is_not_me(self):
+        """Clicks the 'This is not me' button."""
+        self.selenium.find_element(*self._this_is_not_me_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(*self._email_locator).is_displayed())
+
+    @property
+    def emails(self):
+        """Get the emails for the returning user."""
+        return [element.text for element in
+                self.selenium.find_elements(*self._emails_locator)]
+
+    @property
+    def email(self):
+        """Get the value of the email field."""
+        return self.selenium.find_element(*self._email_locator).text
+
+    @email.setter
+    def email(self, value):
+        """Set the value of the email field."""
+        email = self.selenium.find_element(*self._email_locator)
+        email.clear()
+        email.send_keys(value)
+
+    @property
+    def new_email(self):
+        """Get the value of the new email field."""
+        return self.selenium.find_element(*self._new_email_locator).text
+
+    @new_email.setter
+    def new_email(self, value):
+        """Set the value of the new email field."""
+        email = self.selenium.find_element(*self._new_email_locator)
+        email.clear()
+        email.send_keys(value)
+
+    @property
+    def selected_email(self):
+        """Return the value of the selected email of returning user's multiple emails"""
+        for email in self.selenium.find_elements(*self._emails_locator):
+            if email.find_element(By.TAG_NAME, 'input').is_selected():
+                return email.text
+
+    def select_email(self, value):
+        """Select email from the returning user's multiple emails."""
+        for email in self.selenium.find_elements(*self._emails_locator):
+            if email.text == value:
+                email.click()
+                break
+        else:
+            raise Exception('Email not found: %s' % value)
+
+    @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)
+
+    @property
+    def verify_password(self):
+        """Get the value of the verify password field."""
+        return self.selenium.find_element(*self._verify_password_locator).text
+
+    @verify_password.setter
+    def verify_password(self, value):
+        """Set the value of the verify password field."""
+        password = self.selenium.find_element(*self._verify_password_locator)
+        password.clear()
+        password.send_keys(value)
+
+    @property
+    def check_email_at_address(self):
+        """Get the value of the email address for confirmation."""
+        return self.selenium.find_element(*self._check_email_at_locator).text
+
+    def click_next(self, expect='password'):
+        """Clicks the 'next' button."""
+        self.selenium.find_element(*self._next_locator).click()
+        if expect == 'password':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.find_element(
+                    *self._password_locator).is_displayed())
+        elif expect == 'verify':
+            WebDriverWait(self.selenium, self.timeout).until(
+                lambda s: s.find_element(
+                    *self._verify_email_locator).is_displayed())
+        else:
+            raise Exception('Unknown expect value: %s' % expect)
+
+    def click_sign_in(self):
+        """Clicks the 'sign in' button."""
+        self.selenium.find_element(*self._sign_in_locator).click()
+        self.switch_to_main_window()
+
+    def click_sign_in_returning_user(self):
+        """Clicks the 'sign in' button."""
+        self.selenium.find_element(
+            *self._sign_in_returning_user_locator).click()
+        self.switch_to_main_window()
+
+    def click_verify_email(self):
+        """Clicks 'verify email' button."""
+        self.selenium.find_element(*self._verify_email_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(
+                *self._check_email_at_locator).is_displayed())
+
+    def click_forgot_password(self):
+        """Clicks 'forgot password' link (visible after entering a valid email)"""
+        self.selenium.find_element(*self._forgot_password_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(
+                *self._reset_password_locator).is_displayed())
+
+    def click_reset_password(self):
+        """Clicks 'reset password' after forgot password and new passwords entered"""
+        self.selenium.find_element(*self._reset_password_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(
+                *self._check_email_at_locator).is_displayed())
+
+    def click_add_another_email_address(self):
+        """Clicks 'add another email' button."""
+        self.selenium.find_element(*self._add_another_email_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(
+                *self._add_new_email_locator).is_displayed())
+
+    def click_add_new_email(self):
+        """Clicks 'Add' button to insert new email address."""
+        self.selenium.find_element(*self._add_new_email_locator).click()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: s.find_element(
+                *self._check_email_at_locator).is_displayed())
+
+    def sign_in(self, email, password):
+        """Signs in using the specified email address and password."""
+        self.email = email
+        self.click_next(expect='password')
+        self.password = password
+        self.click_sign_in()
+
+    def sign_in_new_user(self, email, password):
+        """Requests verification email using the specified email address."""
+        self.email = email
+        self.click_next(expect='verify')
+        self.password = password
+        self.verify_password = password
+        self.click_verify_email()
+        self.close_window()
+        self.switch_to_main_window()
+
+    def sign_in_returning_user(self):
+        """Signs in with the stored user."""
+        self.click_sign_in_returning_user()
diff --git a/automation-tests/browserid/requirements.txt b/automation-tests/browserid/requirements.txt
new file mode 100644
index 0000000000000000000000000000000000000000..94fd2ed89d1ebf52c93c3bb428e02bb6c1d98812
--- /dev/null
+++ b/automation-tests/browserid/requirements.txt
@@ -0,0 +1,2 @@
+pytest-mozwebqa==1.0
+requests==0.13.3
diff --git a/automation-tests/browserid/setup.cfg b/automation-tests/browserid/setup.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..5ca77d16be0357283249ae1e9f1456a748bccee1
--- /dev/null
+++ b/automation-tests/browserid/setup.cfg
@@ -0,0 +1,2 @@
+[pytest]
+python_files=check_*.py
diff --git a/automation-tests/browserid/tests/__init__.py b/automation-tests/browserid/tests/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/automation-tests/browserid/tests/base.py b/automation-tests/browserid/tests/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..8adf6357ce14e817412fbad8532374c0df8309fa
--- /dev/null
+++ b/automation-tests/browserid/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
+
+import requests
+from selenium.webdriver.support.ui import WebDriverWait
+
+from .. import BrowserID
+from .. mocks.user import MockUser
+import restmail
+
+
+class BaseTest(object):
+
+    def browserid_url(self, base_url):
+        response = requests.get('%s/' % base_url, verify=False)
+        match = re.search(BrowserID.INCLUDE_URL_REGEX, response.content)
+        if match:
+            return match.group(1)
+        else:
+            raise Exception('Unable to determine BrowserID URL from %s.' % base_url)
+
+    def log_out(self, selenium, timeout):
+        WebDriverWait(selenium, timeout).until(
+            lambda s: s.find_element_by_id('loggedin').is_displayed())
+        selenium.find_element_by_css_selector('#loggedin a').click()
+        WebDriverWait(selenium, timeout).until(
+            lambda s: s.find_element_by_css_selector('#loggedout button').is_displayed())
+
+    def create_verified_user(self, selenium, timeout):
+        user = MockUser()
+        from .. pages.sign_in import SignIn
+        signin = SignIn(selenium, timeout, expect='new')
+        signin.sign_in_new_user(user.primary_email, user.password)
+        mail = restmail.get_mail(user.primary_email, timeout=timeout)
+        verify_url = re.search(BrowserID.VERIFY_URL_REGEX,
+                               mail[0]['text']).group(0)
+
+        selenium.get(verify_url)
+        from .. pages.complete_registration import CompleteRegistration
+        complete_registration = CompleteRegistration(selenium,
+                                                     timeout,
+                                                     expect='success')
+        assert 'Thank you' in complete_registration.thank_you
+        return user
diff --git a/automation-tests/browserid/tests/check_add_email.py b/automation-tests/browserid/tests/check_add_email.py
new file mode 100644
index 0000000000000000000000000000000000000000..79e1e00f9be379298ef5a7654125f8a9736c8a92
--- /dev/null
+++ b/automation-tests/browserid/tests/check_add_email.py
@@ -0,0 +1,55 @@
+#!/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
+
+import pytest
+
+from .. import BrowserID
+from base import BaseTest
+import restmail
+
+
+@pytest.mark.nondestructive
+class TestSignIn(BaseTest):
+
+    @pytest.mark.travis
+    def test_add_email(self, mozwebqa):
+        user = self.create_verified_user(mozwebqa.selenium, mozwebqa.timeout)
+        user.additional_emails.append('%s_1@restmail.net' % user.id)
+
+        mozwebqa.selenium.get('%s/' % mozwebqa.base_url)
+        self.log_out(mozwebqa.selenium, mozwebqa.timeout)
+        mozwebqa.selenium.find_element_by_css_selector('#loggedout button').click()
+
+        from .. pages.sign_in import SignIn
+        signin = SignIn(mozwebqa.selenium, mozwebqa.timeout, expect='returning')
+        signin.click_add_another_email_address()
+        signin.new_email = user.additional_emails[0]
+        signin.click_add_new_email()
+        signin.close_window()
+        signin.switch_to_main_window()
+
+        mail = restmail.get_mail(user.additional_emails[0],
+                                 timeout=mozwebqa.timeout)
+        assert 'Click to confirm this email address' in mail[0]['text']
+        confirm_url = re.search(BrowserID.CONFIRM_URL_REGEX,
+            mail[0]['text']).group(0)
+
+        mozwebqa.selenium.get(confirm_url)
+        from .. pages.complete_registration import CompleteRegistration
+        complete_registration = CompleteRegistration(mozwebqa.selenium,
+            mozwebqa.timeout,
+            expect='success')
+        assert 'Your address has been verified' in complete_registration.thank_you
+
+        mozwebqa.selenium.get('%s/' % mozwebqa.base_url)
+        self.log_out(mozwebqa.selenium, mozwebqa.timeout)
+        mozwebqa.selenium.find_element_by_css_selector('#loggedout button').click()
+
+        signin = SignIn(mozwebqa.selenium, mozwebqa.timeout, expect='returning')
+        assert user.additional_emails[0] in signin.emails
+        assert signin.selected_email == user.additional_emails[0]
diff --git a/automation-tests/browserid/tests/check_change_password.py b/automation-tests/browserid/tests/check_change_password.py
new file mode 100644
index 0000000000000000000000000000000000000000..cb9e0501ae30a8c8b2b509e0b65c78caa649e586
--- /dev/null
+++ b/automation-tests/browserid/tests/check_change_password.py
@@ -0,0 +1,45 @@
+#!/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 selenium.webdriver.support.ui import WebDriverWait
+
+from .. import BrowserID
+from base import BaseTest
+
+
+@pytest.mark.nondestructive
+class TestSignIn(BaseTest):
+
+    @pytest.mark.travis
+    def test_change_password(self, mozwebqa):
+        user = self.create_verified_user(mozwebqa.selenium, mozwebqa.timeout)
+
+        mozwebqa.selenium.get(self.browserid_url(mozwebqa.base_url))
+        from .. pages.account_manager import AccountManager
+        account_manager = AccountManager(mozwebqa.selenium, mozwebqa.timeout)
+
+        assert user.primary_email in account_manager.emails
+
+        account_manager.click_edit_password()
+        account_manager.old_password = user.password
+        user.password += '_new'
+        account_manager.new_password = user.password
+        account_manager.click_password_done()
+        account_manager.click_sign_out()
+
+        mozwebqa.selenium.get('%s/' % mozwebqa.base_url)
+
+        login_locator = '#loggedout button'
+        WebDriverWait(mozwebqa.selenium, mozwebqa.timeout).until(
+            lambda s: s.find_element_by_css_selector(login_locator).is_displayed())
+        mozwebqa.selenium.find_element_by_css_selector(login_locator).click()
+
+        browser_id = BrowserID(mozwebqa.selenium, mozwebqa.timeout)
+        browser_id.sign_in(user.primary_email, user.password)
+
+        WebDriverWait(mozwebqa.selenium, mozwebqa.timeout).until(
+            lambda s: s.find_element_by_id('loggedin').is_displayed())
diff --git a/automation-tests/browserid/tests/check_reset_password.py b/automation-tests/browserid/tests/check_reset_password.py
new file mode 100644
index 0000000000000000000000000000000000000000..6dced83572eded7d8ac17a8b7cdd173f22cd9f8e
--- /dev/null
+++ b/automation-tests/browserid/tests/check_reset_password.py
@@ -0,0 +1,53 @@
+#!/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
+
+import pytest
+
+from .. import BrowserID
+from base import BaseTest
+import restmail
+
+
+@pytest.mark.nondestructive
+class TestSignIn(BaseTest):
+
+    @pytest.mark.travis
+    def test_reset_password(self, mozwebqa):
+        user = self.create_verified_user(mozwebqa.selenium, mozwebqa.timeout)
+        mozwebqa.selenium.get('%s/' % mozwebqa.base_url)
+        self.log_out(mozwebqa.selenium, mozwebqa.timeout)
+        mozwebqa.selenium.find_element_by_css_selector('#loggedout button').click()
+
+        from .. pages.sign_in import SignIn
+        signin = SignIn(mozwebqa.selenium, mozwebqa.timeout, expect='returning')
+        signin.click_this_is_not_me()
+        signin.email = user.primary_email
+        signin.click_next()
+        signin.click_forgot_password()
+        user.password += '_new'
+        signin.password = user.password
+        signin.verify_password = user.password
+        signin.click_reset_password()
+        assert signin.check_email_at_address == user.primary_email
+
+        signin.close_window()
+        signin.switch_to_main_window()
+        mail = restmail.get_mail(user.primary_email,
+                                 message_count=2,
+                                 timeout=mozwebqa.timeout)
+        assert 'Click to reset your password' in mail[1]['text']
+
+        reset_url = re.search(BrowserID.RESET_URL_REGEX,
+            mail[1]['text']).group(0)
+        mozwebqa.selenium.get(reset_url)
+
+        from .. pages.complete_registration import CompleteRegistration
+        complete_registration = CompleteRegistration(mozwebqa.selenium,
+            mozwebqa.timeout,
+            expect='success')
+        assert 'Your address has been verified!' in complete_registration.thank_you
diff --git a/automation-tests/browserid/tests/check_sign_in.py b/automation-tests/browserid/tests/check_sign_in.py
new file mode 100644
index 0000000000000000000000000000000000000000..81a5ab4765fa28c215c23e07a473a9cfe90832cd
--- /dev/null
+++ b/automation-tests/browserid/tests/check_sign_in.py
@@ -0,0 +1,70 @@
+#!/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 selenium.webdriver.support.ui import WebDriverWait
+
+from .. import BrowserID
+from .. mocks.user import MockUser
+from base import BaseTest
+import restmail
+
+
+@pytest.mark.nondestructive
+class TestSignIn(BaseTest):
+
+    def test_sign_in_helper(self, mozwebqa):
+        browser_id = BrowserID(mozwebqa.selenium, mozwebqa.timeout)
+        browser_id.sign_in(mozwebqa.email, mozwebqa.password)
+
+        WebDriverWait(mozwebqa.selenium, mozwebqa.timeout).until(
+            lambda s: s.find_element_by_id('loggedin').is_displayed())
+
+    def test_sign_in(self, mozwebqa):
+        from .. pages.sign_in import SignIn
+        signin = SignIn(mozwebqa.selenium, mozwebqa.timeout, expect='new')
+        signin.email = mozwebqa.email
+        signin.click_next(expect='password')
+        signin.password = mozwebqa.password
+        signin.click_sign_in()
+
+        WebDriverWait(mozwebqa.selenium, mozwebqa.timeout).until(
+            lambda s: s.find_element_by_id('loggedin').is_displayed())
+
+    @pytest.mark.travis
+    def test_sign_in_new_user_helper(self, mozwebqa):
+        user = MockUser()
+        from .. pages.sign_in import SignIn
+        signin = SignIn(mozwebqa.selenium, mozwebqa.timeout, expect='new')
+        print 'signing in as %s' % user.primary_email
+        signin.sign_in_new_user(user.primary_email, 'password')
+        mail = restmail.get_mail(user.primary_email, timeout=mozwebqa.timeout)
+        assert 'Click to confirm this email address' in mail[0]['text']
+
+    @pytest.mark.travis
+    def test_sign_in_new_user(self, mozwebqa):
+        user = MockUser()
+        from .. pages.sign_in import SignIn
+        signin = SignIn(mozwebqa.selenium, mozwebqa.timeout, expect='new')
+        print 'signing in as %s' % user.primary_email
+        signin.email = user.primary_email
+        signin.click_next(expect='verify')
+        signin.password = user.password
+        signin.verify_password = user.password
+        signin.click_verify_email()
+        assert signin.check_email_at_address == user.primary_email
+
+        signin.close_window()
+        signin.switch_to_main_window()
+        mail = restmail.get_mail(user.primary_email, timeout=mozwebqa.timeout)
+        assert 'Click to confirm this email address' in mail[0]['text']
+
+    @pytest.mark.travis
+    def test_sign_in_returning_user(self, mozwebqa):
+        self.create_verified_user(mozwebqa.selenium, mozwebqa.timeout)
+        mozwebqa.selenium.get('%s/' % mozwebqa.base_url)
+        WebDriverWait(mozwebqa.selenium, mozwebqa.timeout).until(
+            lambda s: s.find_element_by_id('loggedin').is_displayed())
diff --git a/automation-tests/browserid/tests/conftest.py b/automation-tests/browserid/tests/conftest.py
new file mode 100644
index 0000000000000000000000000000000000000000..172c531145870ee29862ddc22e3c33bcb00d3716
--- /dev/null
+++ b/automation-tests/browserid/tests/conftest.py
@@ -0,0 +1,20 @@
+#!/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.support.ui import WebDriverWait
+
+
+def pytest_runtest_setup(item):
+    item.config.option.api = 'webdriver'
+
+
+def pytest_funcarg__mozwebqa(request):
+    mozwebqa = request.getfuncargvalue('mozwebqa')
+    mozwebqa.selenium.get('%s/' % mozwebqa.base_url)
+    WebDriverWait(mozwebqa.selenium, mozwebqa.timeout).until(
+        lambda s: s.find_element_by_css_selector('#loggedout button').is_displayed())
+    mozwebqa.selenium.find_element_by_css_selector('#loggedout button').click()
+    return mozwebqa
diff --git a/automation-tests/browserid/tests/restmail.py b/automation-tests/browserid/tests/restmail.py
new file mode 100644
index 0000000000000000000000000000000000000000..910b549fcdf2a0118c8c1eefbf2798eb91b7f72d
--- /dev/null
+++ b/automation-tests/browserid/tests/restmail.py
@@ -0,0 +1,32 @@
+#!/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 json
+import time
+
+import requests
+
+
+def get_mail(username, message_count=1, timeout=60):
+    username = username.partition('@restmail.net')[0]
+    end_time = time.time() + timeout
+    while(True):
+        response = requests.get(
+            'https://restmail.net/mail/%s' % username,
+            verify=False)
+        restmail = json.loads(response.content)
+        if len(restmail) == message_count:
+            return restmail
+        time.sleep(0.5)
+        if(time.time() > end_time):
+            break
+    raise Exception('Timeout after %(TIMEOUT)s seconds getting restmail for '
+                    '%(USERNAME)s. Expected %(EXPECTED_MESSAGE_COUNT)s '
+                    'messages but there were %(ACTUAL_MESSAGE_COUNT)s.' % {
+                        'TIMEOUT': timeout,
+                        'USERNAME': username,
+                        'EXPECTED_MESSAGE_COUNT': message_count,
+                        'ACTUAL_MESSAGE_COUNT': len(restmail)})