From 956d7be0224d4fbf9ebd6c411038b6e22b8679e4 Mon Sep 17 00:00:00 2001
From: Jared Hirsch <ohai@6a68.net>
Date: Wed, 1 Aug 2012 13:39:43 -0700
Subject: [PATCH] Copy over 6a68/BrowserID-Tests minus bidpom

  We'll add bidpom as a git-subtree as a next step
---
 automation-tests/123done/conftest.py          | 25 ++++++
 automation-tests/123done/mocks/__init__.py    |  0
 automation-tests/123done/mocks/mock_user.py   | 22 +++++
 automation-tests/123done/mozwebqa.cfg         |  4 +
 automation-tests/123done/page.py              | 44 +++++++++
 automation-tests/123done/pages/__init__.py    |  0
 automation-tests/123done/pages/home.py        | 70 +++++++++++++++
 automation-tests/123done/restmail/__init__.py |  0
 automation-tests/123done/restmail/restmail.py | 89 +++++++++++++++++++
 automation-tests/123done/tests/__init__.py    |  0
 .../123done/tests/test_add_another_email.py   | 61 +++++++++++++
 .../123done/tests/test_change_password.py     | 54 +++++++++++
 automation-tests/123done/tests/test_logout.py | 22 +++++
 .../123done/tests/test_new_user.py            | 39 ++++++++
 .../123done/tests/test_sign_in.py             | 20 +++++
 automation-tests/README.md                    |  3 +
 automation-tests/credentials.yaml             | 37 ++++++++
 automation-tests/myfavoritebeer/mozwebqa.cfg  |  4 +
 automation-tests/myfavoritebeer/page.py       | 44 +++++++++
 .../myfavoritebeer/pages/__init__.py          |  0
 automation-tests/myfavoritebeer/pages/home.py | 44 +++++++++
 .../myfavoritebeer/tests/__init__.py          |  0
 .../myfavoritebeer/tests/test_logout.py       | 21 +++++
 .../myfavoritebeer/tests/test_sign_in.py      | 20 +++++
 automation-tests/requirements.txt             | 11 +++
 25 files changed, 634 insertions(+)
 create mode 100644 automation-tests/123done/conftest.py
 create mode 100644 automation-tests/123done/mocks/__init__.py
 create mode 100644 automation-tests/123done/mocks/mock_user.py
 create mode 100644 automation-tests/123done/mozwebqa.cfg
 create mode 100644 automation-tests/123done/page.py
 create mode 100644 automation-tests/123done/pages/__init__.py
 create mode 100644 automation-tests/123done/pages/home.py
 create mode 100644 automation-tests/123done/restmail/__init__.py
 create mode 100644 automation-tests/123done/restmail/restmail.py
 create mode 100644 automation-tests/123done/tests/__init__.py
 create mode 100644 automation-tests/123done/tests/test_add_another_email.py
 create mode 100644 automation-tests/123done/tests/test_change_password.py
 create mode 100644 automation-tests/123done/tests/test_logout.py
 create mode 100644 automation-tests/123done/tests/test_new_user.py
 create mode 100644 automation-tests/123done/tests/test_sign_in.py
 create mode 100644 automation-tests/README.md
 create mode 100644 automation-tests/credentials.yaml
 create mode 100644 automation-tests/myfavoritebeer/mozwebqa.cfg
 create mode 100644 automation-tests/myfavoritebeer/page.py
 create mode 100644 automation-tests/myfavoritebeer/pages/__init__.py
 create mode 100644 automation-tests/myfavoritebeer/pages/home.py
 create mode 100644 automation-tests/myfavoritebeer/tests/__init__.py
 create mode 100644 automation-tests/myfavoritebeer/tests/test_logout.py
 create mode 100644 automation-tests/myfavoritebeer/tests/test_sign_in.py
 create mode 100644 automation-tests/requirements.txt

diff --git a/automation-tests/123done/conftest.py b/automation-tests/123done/conftest.py
new file mode 100644
index 000000000..e8062b476
--- /dev/null
+++ b/automation-tests/123done/conftest.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 py
+
+def pytest_runtest_setup(item):
+    pytest_mozwebqa = py.test.config.pluginmanager.getplugin("mozwebqa")
+    pytest_mozwebqa.TestSetup.server_base_url = item.config.option.server_base_url
+
+
+def pytest_addoption(parser):
+    parser.addoption("--serverbaseurl",
+                     action="store",
+                     dest='server_base_url',
+                     metavar='str',
+                     default="https://login.dev.anosrep.org",
+                     help="specify the server base url")
+
+
+def pytest_funcarg__mozwebqa(request):
+    pytest_mozwebqa = py.test.config.pluginmanager.getplugin("mozwebqa")
+    return pytest_mozwebqa.TestSetup(request)
diff --git a/automation-tests/123done/mocks/__init__.py b/automation-tests/123done/mocks/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/123done/mocks/mock_user.py b/automation-tests/123done/mocks/mock_user.py
new file mode 100644
index 000000000..f5e4d2a55
--- /dev/null
+++ b/automation-tests/123done/mocks/mock_user.py
@@ -0,0 +1,22 @@
+#!/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 MockUser(dict):
+
+    def __init__(self, **kwargs):
+        # set your default values
+        import time
+
+        self['email'] = '123donetest_%s@restmail.net' % repr(time.time())
+        self['password'] = 'Password12345'
+
+        # update with any keyword arguments passed
+        self.update(**kwargs)
+
+    # allow getting items as if they were attributes
+    def __getattr__(self, attr):
+        return self[attr]
diff --git a/automation-tests/123done/mozwebqa.cfg b/automation-tests/123done/mozwebqa.cfg
new file mode 100644
index 000000000..2faf04e06
--- /dev/null
+++ b/automation-tests/123done/mozwebqa.cfg
@@ -0,0 +1,4 @@
+[DEFAULT]
+api = webdriver
+baseurl = http://dev.123done.org
+tags = 123done
diff --git a/automation-tests/123done/page.py b/automation-tests/123done/page.py
new file mode 100644
index 000000000..7ca4b63b2
--- /dev/null
+++ b/automation-tests/123done/page.py
@@ -0,0 +1,44 @@
+#!/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 unittestzero import Assert
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import NoSuchElementException
+from selenium.common.exceptions import ElementNotVisibleException
+
+
+class Page(object):
+
+    def __init__(self, testsetup):
+        self.testsetup = testsetup
+        self.base_url = testsetup.base_url
+        self.selenium = testsetup.selenium
+        self.timeout = testsetup.timeout
+
+    @property
+    def is_the_current_page(self):
+        if self._page_title:
+            WebDriverWait(self.selenium, self.timeout).until(lambda s: s.title)
+
+        Assert.equal(self.selenium.title, self._page_title)
+        return True
+
+    def is_element_present(self, *locator):
+        self.selenium.implicitly_wait(0)
+        try:
+            self.selenium.find_element(*locator)
+            return True
+        except NoSuchElementException:
+            return False
+        finally:
+            # set back to where you once belonged
+            self.selenium.implicitly_wait(self.testsetup.default_implicit_wait)
+
+    def is_element_visible(self, *locator):
+        try:
+            return self.selenium.find_element(*locator).is_displayed()
+        except NoSuchElementException, ElementNotVisibleException:
+            return False
diff --git a/automation-tests/123done/pages/__init__.py b/automation-tests/123done/pages/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/123done/pages/home.py b/automation-tests/123done/pages/home.py
new file mode 100644
index 000000000..debde2a99
--- /dev/null
+++ b/automation-tests/123done/pages/home.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/.
+
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.ui import WebDriverWait
+
+from page import Page
+
+
+class HomePage(Page):
+
+    _page_title = '123done - your tasks, simplified'
+
+    _sign_in_locator = (By.CSS_SELECTOR, '#loggedout > button')
+    _logout_locator = (By.CSS_SELECTOR, '#loggedin > a')
+    _logged_in_user_email_locator = (By.CSS_SELECTOR, '#loggedin > span')
+    _loading_spinner_locator = (By.CSS_SELECTOR, "li.loading img")
+
+    def go_to_home_page(self):
+        self.selenium.get(self.base_url + '/')
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: not self.is_element_visible(*self._loading_spinner_locator),
+            'Timeout waiting for sign-in button to appear.')
+        self.is_the_current_page
+
+    def sign_in(self, user='default'):
+        credentials = self.testsetup.credentials[user]
+        browserid = self.click_sign_in()
+        browserid.sign_in(credentials['email'], credentials['password'])
+        self.wait_for_user_login()
+
+    def logout(self):
+        self.click_logout()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: self.is_element_visible(*self._sign_in_locator) and not \
+                      self.is_element_visible(*self._loading_spinner_locator),
+            'Timeout waiting for user to log out.')
+
+    def click_sign_in(self, expect='new'):
+        """Click the 'sign in' button.
+
+        Keyword arguments:
+        expect -- the expected resulting page
+                  'new' for user that is not currently signed in (default)
+                  'returning' for users already signed in or recently verified
+
+        """
+        self.selenium.find_element(*self._sign_in_locator).click()
+        from browserid.pages.webdriver.sign_in import SignIn
+        return SignIn(self.selenium, self.timeout, expect=expect)
+
+    def click_logout(self):
+        self.selenium.find_element(*self._logout_locator).click()
+
+    @property
+    def is_logged_in(self):
+        return self.is_element_visible(*self._logout_locator)
+
+    @property
+    def logged_in_user_email(self):
+        return self.selenium.find_element(*self._logged_in_user_email_locator).text
+
+    def wait_for_user_login(self):
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: self.is_element_visible(*self._logout_locator) and not \
+                      self.is_element_visible(*self._loading_spinner_locator),
+            'Timeout waiting for user to login.')
diff --git a/automation-tests/123done/restmail/__init__.py b/automation-tests/123done/restmail/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/123done/restmail/restmail.py b/automation-tests/123done/restmail/restmail.py
new file mode 100644
index 000000000..e3a96034d
--- /dev/null
+++ b/automation-tests/123done/restmail/restmail.py
@@ -0,0 +1,89 @@
+#!/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 requests
+import json
+import re
+from time import sleep
+
+
+class RestmailInbox(object):
+    """
+    This wrapper loads restmail for the given email address.
+    It will loop and wait for an email to arrive if there is not one present.
+    find_by_* methods can be used to find an email and return it as Email() class.
+    """
+
+    _restmail_mail_server = "https://restmail.net/mail/"
+
+    def __init__(self, email):
+        self.email = email
+        self.username = email.split('@')[0]
+        self.json = self._wait_and_return_json_response(self.username)
+
+    def _wait_and_return_json_response(self, username, timeout=60):
+        # Loop for 60 attempts until the restmail json returned is not empty
+
+        timer = 0
+        response_json = []
+
+        while timer < timeout:
+            sleep(1)
+            timer += 1
+
+            response = requests.get(self._restmail_mail_server + self.username, verify=False)
+            response_json = json.loads(response.content)
+            if response_json != []:
+                return response_json
+
+        raise Exception("Failed to find an email before timeout")
+
+    def delete_all_mail(self):
+        # Delete all of the mail in the inbox
+
+        requests.delete(self._restmail_mail_server + self.username, verify=False)
+
+    def find_by_index(self, index):
+        return Email(self.json[index])
+
+    def find_by_sender(self, sender):
+        # Loop through the address and name objects for each sender and match at least one
+
+        for json_object in self.json:
+            for from_source in json_object['from']:
+                if from_source['address'] == sender or from_source['name'] == sender:
+                    return Email(json_object)
+        else:
+            raise Exception("Sender not found")
+
+
+class Email():
+    """
+    This returns a class representation of an email from restmail inbox
+    """
+
+    def __init__(self, json):
+        self.json = json
+
+    @property
+    def body(self):
+        return(self.json['text'])
+
+    @property
+    def verify_user_link(self):
+        # This returns the link for verifying the email address of a new account
+        regex = 'https:\/\/.*verify_email_address\?token=.{48}'
+
+        verify_link = re.search(regex, self.body).group(0)
+        return verify_link
+
+    @property
+    def add_email_address_link(self):
+        # This returns the link for adding the email address of a new account
+        regex = 'https:\/\/.*confirm\?token=.{48}'
+
+        add_email_link = re.search(regex, self.body).group(0)
+        return add_email_link
diff --git a/automation-tests/123done/tests/__init__.py b/automation-tests/123done/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/123done/tests/test_add_another_email.py b/automation-tests/123done/tests/test_add_another_email.py
new file mode 100644
index 000000000..542b0cc09
--- /dev/null
+++ b/automation-tests/123done/tests/test_add_another_email.py
@@ -0,0 +1,61 @@
+#!/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 unittestzero import Assert
+from restmail.restmail import RestmailInbox
+from mocks.mock_user import MockUser
+from pages.home import HomePage
+
+
+class TestAddAnotherEmail:
+
+    def test_can_add_another_email(self, mozwebqa):
+        user = MockUser()
+        home_pg = HomePage(mozwebqa)
+
+        home_pg.go_to_home_page()
+        bid_login = home_pg.click_sign_in()
+        bid_login.sign_in_new_user(user['email'], user['password'])
+
+        # Open restmail inbox, find the email
+        inbox = RestmailInbox(user['email'])
+        email = inbox.find_by_index(0)
+
+        # Load the BrowserID link from the email in the browser
+        mozwebqa.selenium.get(email.verify_user_link)
+        from browserid.pages.webdriver.complete_registration import CompleteRegistration
+        complete_registration = CompleteRegistration(mozwebqa.selenium, mozwebqa.timeout)
+
+        # Check the message on the registration page reflects a successful registration!
+        Assert.contains("Thank you for signing up with Persona.", complete_registration.thank_you)
+
+        home_pg.wait_for_user_login()
+        Assert.equal(home_pg.logged_in_user_email, user['email'])
+
+        home_pg.click_logout()
+
+        second_user = MockUser()
+        bid_login = home_pg.click_sign_in(expect='returning')
+        bid_login.sign_in_add_another_email(second_user['email'])
+
+        # Open restmail inbox, find the email
+        inbox = RestmailInbox(second_user['email'])
+        email = inbox.find_by_index(0)
+
+        # Load the BrowserID link from the email in the browser
+        mozwebqa.selenium.get(email.add_email_address_link)
+        from browserid.pages.webdriver.complete_registration import CompleteRegistration
+        complete_registration = CompleteRegistration(mozwebqa.selenium, mozwebqa.timeout)
+
+        home_pg.wait_for_user_login()
+        Assert.equal(home_pg.logged_in_user_email, second_user['email'])
+        home_pg.click_logout()
+
+        bid_login = home_pg.click_sign_in(expect='returning')
+        
+        expected_emails = [user['email'], second_user['email']]
+        Assert.equal(expected_emails, bid_login.emails)
+        Assert.equal(second_user['email'], bid_login.selected_email)
diff --git a/automation-tests/123done/tests/test_change_password.py b/automation-tests/123done/tests/test_change_password.py
new file mode 100644
index 000000000..6c98686ca
--- /dev/null
+++ b/automation-tests/123done/tests/test_change_password.py
@@ -0,0 +1,54 @@
+#!/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 pages.home import HomePage
+from restmail.restmail import RestmailInbox
+from mocks.mock_user import MockUser
+from unittestzero import Assert
+
+import pytest
+
+
+class TestChangePassword:
+
+    def test_can_change_user_password(self, mozwebqa):
+        user = MockUser()
+        home_pg = HomePage(mozwebqa)
+
+        home_pg.go_to_home_page()
+        bid_login = home_pg.click_sign_in()
+        bid_login.sign_in_new_user(user['email'], user['password'])
+
+        # Open restmail inbox, find the email
+        inbox = RestmailInbox(user['email'])
+        email = inbox.find_by_index(0)
+
+        # Load the BrowserID link from the email in the browser
+        mozwebqa.selenium.get(email.verify_user_link)
+        from browserid.pages.webdriver.complete_registration import CompleteRegistration
+        CompleteRegistration(mozwebqa.selenium, mozwebqa.timeout)
+
+        mozwebqa.selenium.get(mozwebqa.server_base_url)
+        from browserid.pages.webdriver.account_manager import AccountManager
+        account_manager = AccountManager(mozwebqa.selenium, mozwebqa.timeout)
+
+        Assert.contains(user['email'], account_manager.emails)
+
+        account_manager.click_edit_password()
+        account_manager.old_password = user['password']
+        new_password = "newpass12345"
+        account_manager.new_password = new_password
+        account_manager.click_password_done()
+
+        account_manager.click_sign_out()
+
+        home_pg.go_to_home_page()
+
+        bid_login = home_pg.click_sign_in()
+        bid_login.sign_in(user['email'], new_password)
+
+        home_pg.wait_for_user_login()
+        Assert.true(home_pg.is_logged_in)
diff --git a/automation-tests/123done/tests/test_logout.py b/automation-tests/123done/tests/test_logout.py
new file mode 100644
index 000000000..1267b890e
--- /dev/null
+++ b/automation-tests/123done/tests/test_logout.py
@@ -0,0 +1,22 @@
+#!/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 pages.home import HomePage
+from unittestzero import Assert
+
+import pytest
+
+
+class TestLogout:
+
+    @pytest.mark.nondestructive
+    def test_that_user_can_logout(self, mozwebqa):
+        home_pg = HomePage(mozwebqa)
+        home_pg.go_to_home_page()
+        home_pg.sign_in()
+
+        home_pg.logout()
+        Assert.false(home_pg.is_logged_in)
diff --git a/automation-tests/123done/tests/test_new_user.py b/automation-tests/123done/tests/test_new_user.py
new file mode 100644
index 000000000..3f3a19ce5
--- /dev/null
+++ b/automation-tests/123done/tests/test_new_user.py
@@ -0,0 +1,39 @@
+#!/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 pages.home import HomePage
+from restmail.restmail import RestmailInbox
+from mocks.mock_user import MockUser
+from unittestzero import Assert
+
+import pytest
+
+
+class TestNewAccount:
+
+    def test_can_create_new_user_account(self, mozwebqa):
+        user = MockUser()
+        home_pg = HomePage(mozwebqa)
+
+        home_pg.go_to_home_page()
+        bid_login = home_pg.click_sign_in()
+        bid_login.sign_in_new_user(user['email'], user['password'])
+
+        # Open restmail inbox, find the email
+        inbox = RestmailInbox(user['email'])
+        email = inbox.find_by_index(0)
+
+        # Load the BrowserID link from the email in the browser
+        mozwebqa.selenium.get(email.verify_user_link)
+        from browserid.pages.webdriver.complete_registration import CompleteRegistration
+        complete_registration = CompleteRegistration(mozwebqa.selenium, mozwebqa.timeout)
+
+        # Check the message on the registration page reflects a successful registration!
+        Assert.contains("Thank you for signing up with Persona.", complete_registration.thank_you)
+
+        home_pg.go_to_home_page()
+
+        Assert.equal(home_pg.logged_in_user_email, user['email'])
diff --git a/automation-tests/123done/tests/test_sign_in.py b/automation-tests/123done/tests/test_sign_in.py
new file mode 100644
index 000000000..caac0ed4f
--- /dev/null
+++ b/automation-tests/123done/tests/test_sign_in.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 pages.home import HomePage
+from unittestzero import Assert
+
+import pytest
+
+
+class TestSignIn:
+
+    @pytest.mark.nondestructive
+    def test_that_user_can_sign_in(self, mozwebqa):
+        home_pg = HomePage(mozwebqa)
+        home_pg.go_to_home_page()
+        home_pg.sign_in()
+        Assert.true(home_pg.is_logged_in)
diff --git a/automation-tests/README.md b/automation-tests/README.md
new file mode 100644
index 000000000..249f651fd
--- /dev/null
+++ b/automation-tests/README.md
@@ -0,0 +1,3 @@
+This repository is just a link between mozilla/BrowserID-Tests, the original test location, and mozilla/browserid, the new test location.
+
+The browserid repo contains information on how to run the selenium tests.
diff --git a/automation-tests/credentials.yaml b/automation-tests/credentials.yaml
new file mode 100644
index 000000000..2ce533315
--- /dev/null
+++ b/automation-tests/credentials.yaml
@@ -0,0 +1,37 @@
+# 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/.
+
+# File contains users data.
+#
+# Each user is a section named with its role
+# and any number of values. At least email,
+# password and name should be present.
+#
+# Example:
+#     admin:
+#         email: email@site.com
+#         password: password
+#         name: Test User
+#
+# Still, you are free to add any more data you wish. It will be kept
+# in the same dictionary.
+#
+# Example:
+#     admin:
+#         email: email@site.com
+#         password: password
+#         name: Test User
+#         username: testuser
+#         some_user_data: data
+#
+# The contents of this file are accessible via the pytest-mozwebqa plugin:
+#
+# Example:
+#   credentials = mozwebqa.credentials['default']
+#   credentials['email']
+
+default:
+    email: <value>
+    password: <value>
+    name: <value>
diff --git a/automation-tests/myfavoritebeer/mozwebqa.cfg b/automation-tests/myfavoritebeer/mozwebqa.cfg
new file mode 100644
index 000000000..8e79b8254
--- /dev/null
+++ b/automation-tests/myfavoritebeer/mozwebqa.cfg
@@ -0,0 +1,4 @@
+[DEFAULT]
+api = webdriver
+baseurl = http://myfavoritebeer.org
+tags = browserid
diff --git a/automation-tests/myfavoritebeer/page.py b/automation-tests/myfavoritebeer/page.py
new file mode 100644
index 000000000..7ca4b63b2
--- /dev/null
+++ b/automation-tests/myfavoritebeer/page.py
@@ -0,0 +1,44 @@
+#!/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 unittestzero import Assert
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.common.exceptions import NoSuchElementException
+from selenium.common.exceptions import ElementNotVisibleException
+
+
+class Page(object):
+
+    def __init__(self, testsetup):
+        self.testsetup = testsetup
+        self.base_url = testsetup.base_url
+        self.selenium = testsetup.selenium
+        self.timeout = testsetup.timeout
+
+    @property
+    def is_the_current_page(self):
+        if self._page_title:
+            WebDriverWait(self.selenium, self.timeout).until(lambda s: s.title)
+
+        Assert.equal(self.selenium.title, self._page_title)
+        return True
+
+    def is_element_present(self, *locator):
+        self.selenium.implicitly_wait(0)
+        try:
+            self.selenium.find_element(*locator)
+            return True
+        except NoSuchElementException:
+            return False
+        finally:
+            # set back to where you once belonged
+            self.selenium.implicitly_wait(self.testsetup.default_implicit_wait)
+
+    def is_element_visible(self, *locator):
+        try:
+            return self.selenium.find_element(*locator).is_displayed()
+        except NoSuchElementException, ElementNotVisibleException:
+            return False
diff --git a/automation-tests/myfavoritebeer/pages/__init__.py b/automation-tests/myfavoritebeer/pages/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/myfavoritebeer/pages/home.py b/automation-tests/myfavoritebeer/pages/home.py
new file mode 100644
index 000000000..c71e36f4d
--- /dev/null
+++ b/automation-tests/myfavoritebeer/pages/home.py
@@ -0,0 +1,44 @@
+#!/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 page import Page
+
+
+class HomePage(Page):
+
+    _page_title = 'My Favorite Beer, a BrowserID example'
+
+    _sign_in_locator = (By.CSS_SELECTOR, '#loginInfo .login')
+    _logout_locator = (By.ID, 'logout')
+
+    def go_to_home_page(self):
+        self.selenium.get(self.base_url + '/')
+        self.is_the_current_page
+
+    def sign_in(self, user='default'):
+        credentials = self.testsetup.credentials[user]
+        self.click_sign_in()
+        from browserid import BrowserID
+        browserid = BrowserID(self.selenium, self.timeout)
+        browserid.sign_in(credentials['email'], credentials['password'])
+
+    def logout(self):
+        self.click_logout()
+        WebDriverWait(self.selenium, self.timeout).until(
+            lambda s: not self.is_element_present(*self._logout_locator))
+
+    def click_sign_in(self):
+        self.selenium.find_element(*self._sign_in_locator).click()
+
+    def click_logout(self):
+        self.selenium.find_element(*self._logout_locator).click()
+
+    @property
+    def is_logged_in(self):
+        return self.is_element_visible(*self._logout_locator)
diff --git a/automation-tests/myfavoritebeer/tests/__init__.py b/automation-tests/myfavoritebeer/tests/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/automation-tests/myfavoritebeer/tests/test_logout.py b/automation-tests/myfavoritebeer/tests/test_logout.py
new file mode 100644
index 000000000..94304f9ef
--- /dev/null
+++ b/automation-tests/myfavoritebeer/tests/test_logout.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/.
+
+from pages.home import HomePage
+from unittestzero import Assert
+
+import pytest
+
+
+class TestLogout:
+
+    @pytest.mark.nondestructive
+    def test_that_user_can_logout(self, mozwebqa):
+        home_pg = HomePage(mozwebqa)
+        home_pg.go_to_home_page()
+        home_pg.sign_in()
+        home_pg.logout()
+        Assert.false(home_pg.is_logged_in)
diff --git a/automation-tests/myfavoritebeer/tests/test_sign_in.py b/automation-tests/myfavoritebeer/tests/test_sign_in.py
new file mode 100644
index 000000000..caac0ed4f
--- /dev/null
+++ b/automation-tests/myfavoritebeer/tests/test_sign_in.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 pages.home import HomePage
+from unittestzero import Assert
+
+import pytest
+
+
+class TestSignIn:
+
+    @pytest.mark.nondestructive
+    def test_that_user_can_sign_in(self, mozwebqa):
+        home_pg = HomePage(mozwebqa)
+        home_pg.go_to_home_page()
+        home_pg.sign_in()
+        Assert.true(home_pg.is_logged_in)
diff --git a/automation-tests/requirements.txt b/automation-tests/requirements.txt
new file mode 100644
index 000000000..00b26b5d0
--- /dev/null
+++ b/automation-tests/requirements.txt
@@ -0,0 +1,11 @@
+PyYAML==3.10
+UnittestZero
+certifi==0.0.8
+chardet==1.0.1
+execnet==1.1
+py==1.4.9
+pytest==2.2.4
+pytest-mozwebqa==1.0
+pytest-xdist==1.8
+requests==0.13.2
+selenium
-- 
GitLab