diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d428c69 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.sw[op] +_temp/ +venv/ +*.pyc +*.egg-info/ +*.egg +dist/ +.tox/ +.DS_Store + +# pyenv +.python-version + +#IDE +.idea diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/__init__.py @@ -0,0 +1,3 @@ +# 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/apps/__init__.py b/apps/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/apps/__init__.py @@ -0,0 +1,3 @@ +# 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/apps/activitystream/__init__.py b/apps/activitystream/__init__.py new file mode 100644 index 0000000..12027ae --- /dev/null +++ b/apps/activitystream/__init__.py @@ -0,0 +1 @@ +__author__ = 'jdorlus' diff --git a/apps/activitystream/app.py b/apps/activitystream/app.py new file mode 100644 index 0000000..d82c828 --- /dev/null +++ b/apps/activitystream/app.py @@ -0,0 +1,30 @@ +# 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 apps.base import Base + +from firefox_puppeteer.testcases import BaseFirefoxTestCase + +from marionette_driver import By + + +class ActivityStream(Base, BaseFirefoxTestCase): + + _activitystream_top_sites_header = (By.XPATH, '//h3[text()="Top Sites"]') + + def __init__(self, puppeteer): + super(ActivityStream, self).__init__(puppeteer.marionette) + + self.set_context(self.CHROME) + self.browser = self.windows.current + + self.browser.focus() + self.browser.tabbar.open_tab() + self.set_context(self.CONTENT) + self.wait_for_element_displayed(*self._activitystream_top_sites_header) + + @property + def top_sites(self): + pass + diff --git a/apps/base.py b/apps/base.py new file mode 100644 index 0000000..281eb90 --- /dev/null +++ b/apps/base.py @@ -0,0 +1,96 @@ +# 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 marionette_driver import expected, Wait, Actions +from marionette_driver.errors import NoSuchElementException + +import re + + +class Base(object): + + def __init__(self, marionette): + self.marionette = marionette + self.CHROME = 'chrome' + self.CONTENT = 'content' + self.set_context(self.CONTENT) + self.action = Actions(marionette) + + def launch(self, url): + if url is not None: + regex = re.compile( + r'^(?:http|ftp)s?://' + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)' + r'+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' + r'localhost|' + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' + r'(?::\d+)?' + r'(?:/?|[/?]\S+)$', re.IGNORECASE) + if regex.match(url): + self.marionette.navigate(url) + else: + raise ValueError('Url is malformed.') + else: + raise ValueError("Url must contain a value.") + + def is_element_present(self, by, locator): + try: + self.marionette.find_element(by, locator) + return True + except NoSuchElementException: + return False + + def is_element_displayed(self, by, locator): + try: + return self.marionette.find_element(by, locator).is_displayed() + except NoSuchElementException: + return False + + def wait_for_element_displayed(self, by, locator): + return Wait(self.marionette).until(expected.element_displayed( + Wait(self.marionette).until(expected.element_present(by, locator)))) + + def wait_for_element_present(self, by, locator): + Wait(self.marionette).until(expected.element_present(by, locator)) + + def wait_for_element_enabled(self, by, locator): + Wait(self.marionette).until( + expected.element_enabled(lambda m: m.find_element(by, locator))) + + def wait_for_element_not_displayed(self, by, locator): + Wait(self.marionette).until(expected.element_not_displayed( + Wait(self.marionette).until(expected.element_present(by, locator)))) + + def wait_for_element_not_present(self, by, locator): + Wait(self.marionette).until(expected.element_not_present(by, locator)) + + def wait_for_element_not_enabled(self, by, locator): + Wait(self.marionette).until( + expected.element_not_enabled(lambda m: m.find_element(by, locator))) + + def set_context(self, context): + if context != self.CHROME and context != self.CONTENT: + raise AttributeError( + '{} is not a context that you can switch to'.format(context)) + else: + self.marionette.set_context(context) + + def click_element(self, by, locator): + self.marionette.find_element(by, locator).click() + + def send_keys_to_element(self, by, locator, string): + self.marionette.find_element(by, locator).send_keys(string) + + def wait(self, time): + self.action.wait(time).perform() + + def get_element_text(self, by, locator): + return self.marionette.find_element(by, locator).text + + +class PageRegion(Base): + + def __init__(self, marionette, element): + self.root_element = element + Base.__init__(self, marionette) diff --git a/apps/pocket/__init__.py b/apps/pocket/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/apps/pocket/__init__.py @@ -0,0 +1,3 @@ +# 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/apps/pocket/app.py b/apps/pocket/app.py new file mode 100644 index 0000000..d92e6c8 --- /dev/null +++ b/apps/pocket/app.py @@ -0,0 +1,75 @@ +# 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 marionette_driver import By + + +from apps.base import Base, PageRegion + + +class Pocket(Base): + _login_url = 'https://www.getpocket.com/login' + _pocket_chrome_button = (By.ID, "#pocket-button") + _login_with_firefox_button_locator = ( + By.CSS_SELECTOR, 'a.btn.login-btn-firefox') + _pocket_start_saving_button_locator = ( + By.XPATH, '//a[text()="Start Saving"]') + _pocket_logo_header_locator = (By.CSS_SELECTOR, 'h1.pocket_logo') + _pocket_username_nav_locator = (By.CSS_SELECTOR, 'span.nav-username') + _pocket_all_pocket_items_locator = (By.CSS_SELECTOR, '#queue li.item') + + def __init__(self, marionette): + super(Pocket, self).__init__(marionette) + self.wait_for_element_displayed(*self._pocket_logo_header_locator) + self.wait_for_element_displayed(*self._pocket_all_pocket_items_locator) + + def click_pocket_chrome_button(self): + with self.marionette.using_context(self.CHROME): + self.wait_for_element_displayed( + *self._pocket_chrome_button).click() + + @property + def username(self): + return self.get_element_text(*self._pocket_username_nav_locator) + + @property + def queue_items(self): + return [self.QueueItem(self.marionette, queue_item) for queue_item in + self.marionette.find_elements(*self._pocket_all_pocket_items_locator)] + + class QueueItem(PageRegion): + # TODO: Create methods for all of these different properties and functions. + + @property + def header(self): + pass + + @property + def body(self): + pass + + @property + def url(self): + pass + + @property + def tags(self): + pass + + def toggle_favorite(self): + pass + + def delete(self): + pass + + def archive(self): + pass + + def add_tag(self): + pass + + def delete_tag(self): + pass + diff --git a/apps/testpilot/__init__.py b/apps/testpilot/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/apps/testpilot/__init__.py @@ -0,0 +1,3 @@ +# 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/apps/testpilot/app.py b/apps/testpilot/app.py new file mode 100644 index 0000000..09c3a00 --- /dev/null +++ b/apps/testpilot/app.py @@ -0,0 +1,33 @@ +# 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 marionette_driver import By + +from firefox_puppeteer.ui.browser.notifications import ( + AddOnInstallBlockedNotification, + AddOnInstallConfirmationNotification, + AddOnInstallCompleteNotification) + +from apps.base import Base + + +class TestPilot(Base): + _login_with_firefox_button_locator = ( + By.CSS_SELECTOR, 'div.cta-layout a.fxa-alternate') + _testpilot_install_addon_button_locator = (By.CSS_SELECTOR, '[data-l10n-id="landingInstallButton"]') + + def __init__(self, puppeteer, firefox, url): + super(TestPilot, self).__init__(puppeteer.marionette) + self.wait_for_element_displayed(*self._testpilot_install_addon_button_locator) + self.click_element(*self._testpilot_install_addon_button_locator) + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + if 'dev' in url: + firefox.wait_for_notification(AddOnInstallBlockedNotification) + print self.marionette.find_element(By.ID, 'notification-popup') + firefox.notification.allow() + firefox.wait_for_notification(AddOnInstallConfirmationNotification) + firefox.notification.install() + firefox.wait_for_notification(AddOnInstallCompleteNotification) + firefox.notification.close() + diff --git a/helpers/__init__.py b/helpers/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/helpers/__init__.py @@ -0,0 +1,3 @@ +# 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/helpers/utils.py b/helpers/utils.py new file mode 100644 index 0000000..e2f577d --- /dev/null +++ b/helpers/utils.py @@ -0,0 +1,30 @@ +# 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 urllib2 + +_rest__email_client = 'http://restmail.net/mail/' + + +def generate_random_email_address(domain=None): + domain = 'restmail.net' if domain is None else str(domain) + return 'test' + str(time.time()) + '@' + domain + + +def check_generated_email(email_address): + try: + data = pull_email_messages(email_address) + activation_link = data['headers']['x-link'] + return activation_link + except IndexError: + check_generated_email(email_address) + + +def pull_email_messages(email_address): + response = urllib2.urlopen( + _rest__email_client + email_address.split('@')[0]) + data = json.load(response)[0] + time.sleep(.5) + return data diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..bd77b7b --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +# 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 setuptools import setup, find_packages + +PACKAGE_VERSION = '0.0.2' + +deps = [ + 'marionette-client == 2.0.0', + 'marionette-driver == 1.1.1', + 'mozfile == 1.2', + 'mozinfo == 0.8', + 'mozinstall == 1.12', + 'mozlog == 3.0', + 'firefox-puppeteer == 4.0.0' +] + +setup(name='firefox-services-tests', + version=PACKAGE_VERSION, + description='A collection of Mozillas Cloud Services tests run with Marionette', + long_description='See https://github.com/mozilla-services/services-test/tree/dev/services-marionette', + classifiers=['Environment :: Console', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', + 'Natural Language :: English', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Topic :: Software Development :: Libraries :: Python Modules', + ], + keywords='mozilla services', + author='Mozilla Cloud Services QA Team', + author_email='cloud-services-qa@mozilla.com', + url='https://github.com/mozilla-services/services-test', + license='MPL 2.0', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + install_requires=deps) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,3 @@ +# 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/tests/activity_stream.xpi b/tests/activity_stream.xpi new file mode 100644 index 0000000..a53c9b8 Binary files /dev/null and b/tests/activity_stream.xpi differ diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/tests/functional/__init__.py @@ -0,0 +1,3 @@ +# 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/tests/functional/activity_stream.xpi b/tests/functional/activity_stream.xpi new file mode 100644 index 0000000..a53c9b8 Binary files /dev/null and b/tests/functional/activity_stream.xpi differ diff --git a/tests/functional/activitystream/__init__.py b/tests/functional/activitystream/__init__.py new file mode 100644 index 0000000..12027ae --- /dev/null +++ b/tests/functional/activitystream/__init__.py @@ -0,0 +1 @@ +__author__ = 'jdorlus' diff --git a/tests/functional/activitystream/activity_stream.xpi b/tests/functional/activitystream/activity_stream.xpi new file mode 100644 index 0000000..a53c9b8 Binary files /dev/null and b/tests/functional/activitystream/activity_stream.xpi differ diff --git a/tests/functional/activitystream/conftest.py b/tests/functional/activitystream/conftest.py new file mode 100644 index 0000000..2f6f2ee --- /dev/null +++ b/tests/functional/activitystream/conftest.py @@ -0,0 +1,49 @@ +# 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 os +import pytest + +from fxapom.fxapom import FxATestAccount, WebDriverFxA, PROD_URL + +from marionette_driver import By, expected, Wait + + +@pytest.fixture(scope='function') +def install_xpi(marionette, timeout): + from marionette_driver.addons import Addons + from urllib import URLopener + activity_stream_xpi = URLopener() + + xpi_path = os.path.dirname(os.getcwd()) + xpi_file_path = os.path.join(xpi_path, 'activity_stream.xpi') + activity_stream_xpi.retrieve('https://moz-activity-streams-dev.s3.amazonaws.com/dist/activity-streams-latest.xpi', + xpi_file_path) + addons = Addons(marionette) + print xpi_file_path + addons.install(xpi_file_path) + + +@pytest.fixture(scope='function') +def add_browsing_history(marionette, timeout): + _reddit_post_list = (By.ID, 'siteTable') + + marionette.navigate('https://www.reddit.com/r/mozilla/') + Wait(marionette, timeout).until( + expected.element_displayed(Wait(marionette, timeout).until( + expected.element_present(*_reddit_post_list)) + ) + ) + marionette.navigate('https://www.reddit.com/r/firefox/') + Wait(marionette, timeout).until( + expected.element_displayed(Wait(marionette, timeout).until( + expected.element_present(*_reddit_post_list)) + ) + ) + marionette.navigate('https://www.reddit.com/r/yahoo/') + Wait(marionette, timeout).until( + expected.element_displayed(Wait(marionette, timeout).until( + expected.element_present(*_reddit_post_list)) + ) + ) diff --git a/tests/functional/activitystream/test_activitystream_addon_installed.py b/tests/functional/activitystream/test_activitystream_addon_installed.py new file mode 100644 index 0000000..5a202eb --- /dev/null +++ b/tests/functional/activitystream/test_activitystream_addon_installed.py @@ -0,0 +1,11 @@ +# 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 apps.activitystream.app import ActivityStream + + +class TestActivityStreamAddonInstalled(object): + + def test_activitystream_addon_installed(self, install_xpi, puppeteer): + activity_stream = ActivityStream(puppeteer) diff --git a/tests/functional/activitystream/test_activitystream_verify_activities.py b/tests/functional/activitystream/test_activitystream_verify_activities.py new file mode 100644 index 0000000..6adb700 --- /dev/null +++ b/tests/functional/activitystream/test_activitystream_verify_activities.py @@ -0,0 +1,11 @@ +# 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 apps.activitystream.app import ActivityStream + + +class TestActivityStreamVerifyActivities(object): + + def test_activitystream_verify_activities(self, install_xpi, add_browsing_history, puppeteer): + activity_stream = ActivityStream(puppeteer) diff --git a/tests/functional/conftest.py b/tests/functional/conftest.py new file mode 100644 index 0000000..8662b67 --- /dev/null +++ b/tests/functional/conftest.py @@ -0,0 +1,75 @@ +# 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 firefox_puppeteer import Puppeteer +from firefox_puppeteer.ui.browser.window import BrowserWindow + +from marionette_driver.marionette import Marionette + +TIMEOUT = 20 + + +def pytest_addoption(parser): + parser.addoption( + '--bin', + default='/Applications/FirefoxNightly.app/Contents/MacOS/firefox-bin', + help='path for Firefox binary') + parser.addoption( + '--base-url', + metavar='url', + help='base url for the application under test.' + ) + parser.addoption( + '--addon-path', + metavar='addon path', + help='path to addon to be installed before test' + ) + + +@pytest.fixture +def marionette(request, timeout): + """Return a marionette instance""" + m = Marionette(bin=request.config.option.bin) + m.start_session() + m.set_prefs({'signon.rememberSignons': False}) + request.addfinalizer(m.delete_session) + m.set_search_timeout(timeout) + return m + + +@pytest.fixture +def puppeteer(marionette): + puppeteer = Puppeteer() + puppeteer.marionette = marionette + # enable browser toolbox + puppeteer.prefs.set_pref('devtools.chrome.enabled', True) + puppeteer.prefs.set_pref('devtools.debugger.remote-enabled', True) + # prevent ui popups auto-hiding + puppeteer.prefs.set_pref('ui.popup.disable_autohide', True) + return puppeteer + + +@pytest.fixture +def firefox(puppeteer): + firefox = puppeteer.windows.current + with puppeteer.marionette.using_context(puppeteer.marionette.CONTEXT_CHROME): + firefox.focus() + puppeteer.marionette.set_context(puppeteer.marionette.CONTEXT_CONTENT) + return firefox + + +@pytest.fixture +def timeout(): + """Return default timeout""" + return TIMEOUT + + +@pytest.fixture +def install_addons(marionette, request): + from marionette_driver.addons import Addons + addons = Addons(marionette) + addons.install(request.config.option.addon) + marionette.restart() diff --git a/tests/functional/experimental/__init__.py b/tests/functional/experimental/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/functional/experimental/conftest.py b/tests/functional/experimental/conftest.py new file mode 100644 index 0000000..2575efd --- /dev/null +++ b/tests/functional/experimental/conftest.py @@ -0,0 +1,55 @@ +# 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 firefox_puppeteer import Puppeteer +from firefox_puppeteer.ui.browser.window import BrowserWindow +from marionette_driver.marionette import Marionette +import pytest + + +def pytest_addoption(parser): + pass + # parser.addoption( + # '--base-url', + # default='https://addons-dev.allizom.org', + # help='base url for the application under test.') + # parser.addoption( + # '--firefox-path', + # default='/usr/local/bin/firefox', + # help='path for Firefox binary') + + +@pytest.fixture(scope='session') +def base_url(request): + """Return a base URL""" + return request.config.getoption('base_url') or 'https://addons-dev.allizom.org' + + +@pytest.fixture +def marionette(request): + marionette = Marionette(bin=request.config.getoption('bin')) + marionette.start_session() + request.addfinalizer(marionette.delete_session) + return marionette + + +@pytest.fixture +def puppeteer(marionette): + puppeteer = Puppeteer() + puppeteer.marionette = marionette + # enable browser toolbox + puppeteer.prefs.set_pref('devtools.chrome.enabled', True) + puppeteer.prefs.set_pref('devtools.debugger.remote-enabled', True) + # prevent ui popups auto-hiding + puppeteer.prefs.set_pref('ui.popup.disable_autohide', True) + return puppeteer + + +@pytest.fixture +def firefox(marionette, puppeteer): + firefox = puppeteer.windows.current + with marionette.using_context(marionette.CONTEXT_CHROME): + firefox.focus() + marionette.set_context(marionette.CONTEXT_CONTENT) + return firefox diff --git a/tests/functional/experimental/test_install.py b/tests/functional/experimental/test_install.py new file mode 100644 index 0000000..6dc9f9b --- /dev/null +++ b/tests/functional/experimental/test_install.py @@ -0,0 +1,18 @@ +from firefox_puppeteer.ui.browser.notifications import ( + AddOnInstallBlockedNotification, + AddOnInstallConfirmationNotification, + AddOnInstallCompleteNotification) +from marionette_driver import By + + +def test_install(base_url, firefox, marionette): + marionette.navigate('{0}/en-US/firefox/addon/memchaser/'.format(base_url)) + marionette.find_element(By.CSS_SELECTOR, '#addon .install-button a').click() + with marionette.using_context(marionette.CONTEXT_CHROME): + if 'allizom' in base_url: + firefox.wait_for_notification(AddOnInstallBlockedNotification) + firefox.notification.allow() + firefox.wait_for_notification(AddOnInstallConfirmationNotification) + firefox.notification.install() + firefox.wait_for_notification(AddOnInstallCompleteNotification) + firefox.notification.close() diff --git a/tests/functional/pocket/__init__.py b/tests/functional/pocket/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/tests/functional/pocket/__init__.py @@ -0,0 +1,3 @@ +# 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/tests/functional/pocket/conftest.py b/tests/functional/pocket/conftest.py new file mode 100644 index 0000000..1eff1a6 --- /dev/null +++ b/tests/functional/pocket/conftest.py @@ -0,0 +1,59 @@ +# 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 fxapom.fxapom import FxATestAccount, WebDriverFxA, PROD_URL + +from marionette_driver import By, expected, Wait + + +@pytest.fixture(scope='session') +def base_url(request): + """Return a base URL""" + return request.config.getoption('base_url') or 'https://www.getpocket.com/login' + +@pytest.fixture +def fxa_account(): + """Return a new fxa account""" + return FxATestAccount(PROD_URL) + + +@pytest.fixture +def user(fxa_account): + """Return user credentials""" + return { + 'email': fxa_account.email, + 'password': fxa_account.password, + 'name': fxa_account.email.split('@')[0] + } + + +@pytest.fixture(scope='function') +def sign_in(base_url, marionette, user, timeout): + """Handle logging into fxa""" + _pocket_log_in_firefox_locator = ( + By.CSS_SELECTOR, '.login-btn-firefox') + _pocket_accept_permission_locator = ( + By.CSS_SELECTOR, '#accept' + ) + _pocket_email_address_accept_locator = ( + By.CSS_SELECTOR, 'div.fxa-checkbox__value' + ) + marionette.navigate('%s/' % base_url) + Wait(marionette, timeout).until( + expected.element_displayed(Wait(marionette, timeout).until( + expected.element_present(*_pocket_log_in_firefox_locator)) + ) + ) + marionette.find_element(*_pocket_log_in_firefox_locator).click() + driver = WebDriverFxA(marionette, timeout) + driver.sign_in(user['email'], user['password']) + Wait(marionette, timeout).until( + expected.element_displayed(Wait(marionette,timeout).until( + expected.element_present(*_pocket_accept_permission_locator)) + ) + ) + assert marionette.find_element(*_pocket_email_address_accept_locator).text == user['email'] + marionette.find_element(*_pocket_accept_permission_locator).click() diff --git a/tests/functional/pocket/test_pocket_save_pocket.py b/tests/functional/pocket/test_pocket_save_pocket.py new file mode 100644 index 0000000..eff1905 --- /dev/null +++ b/tests/functional/pocket/test_pocket_save_pocket.py @@ -0,0 +1,13 @@ +# 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 apps.pocket.app import Pocket + + +class TestPocketFxASignIn(object): + + def test_sign_into_pocket_with_fxa(self, sign_in, user, marionette): + pocket_page = Pocket(marionette) + assert pocket_page.username == user['email'] + assert len(pocket_page.queue_items) > 2 diff --git a/tests/functional/pocket/test_pocket_signup_fxa.py b/tests/functional/pocket/test_pocket_signup_fxa.py new file mode 100644 index 0000000..edb2013 --- /dev/null +++ b/tests/functional/pocket/test_pocket_signup_fxa.py @@ -0,0 +1,16 @@ +# 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 apps.pocket.app import Pocket + + +class TestPocketFxASignIn(object): + + def test_sign_into_pocket_with_fxa(self, sign_in, user, marionette): + pocket_page = Pocket(marionette) + assert pocket_page.username == user['email'] + assert len(pocket_page.queue_items) == 3 + + + diff --git a/tests/functional/setup.cfg b/tests/functional/setup.cfg new file mode 100644 index 0000000..569df0a --- /dev/null +++ b/tests/functional/setup.cfg @@ -0,0 +1,6 @@ +[flake8] +ignore=E501 + +[pytest] +sensitive_url=mozilla\.(com|org) + diff --git a/tests/functional/testpilot/__init__.py b/tests/functional/testpilot/__init__.py new file mode 100644 index 0000000..6fbe815 --- /dev/null +++ b/tests/functional/testpilot/__init__.py @@ -0,0 +1,3 @@ +# 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/tests/functional/testpilot/conftest.py b/tests/functional/testpilot/conftest.py new file mode 100644 index 0000000..d9f0a0b --- /dev/null +++ b/tests/functional/testpilot/conftest.py @@ -0,0 +1,61 @@ +# 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 fxapom.fxapom import FxATestAccount, WebDriverFxA, DEV_URL + +from marionette_driver import By, Wait, expected + + +@pytest.fixture(scope='session') +def base_url(request): + """Return a base URL""" + return request.config.getoption('base_url') or 'http://testpilot.dev.mozaws.net/?butimspecial' + + +@pytest.fixture +def fxa_account(): + """Return a new fxa account""" + return FxATestAccount(DEV_URL) + + +@pytest.fixture +def user(fxa_account): + """Return user credentials""" + # return { + # 'email': 'jdorlus@mozilla.com', + # 'password': 'password', + # 'name': fxa_account.email.split('@')[0] + # } + return { + 'email': fxa_account.email, + 'password': fxa_account.password, + 'name': fxa_account.email.split('@')[0] + } + + +@pytest.fixture(scope='function') +def sign_in(base_url, marionette, user, timeout): + """Handle logging into fxa""" + _testpilot_sign_get_started_fxa_button_locator = ( + By.XPATH, '//button[text()="Sign in"]') + _fxa_login_link_locator = ( + By.CSS_SELECTOR, 'div.links a.sign-in') + + marionette.navigate('%s/' % base_url) + Wait(marionette, timeout).until( + expected.element_displayed(*_testpilot_sign_get_started_fxa_button_locator) + ) + marionette.find_element(*_testpilot_sign_get_started_fxa_button_locator).click() + Wait(marionette, timeout).until( + expected.element_displayed( + Wait(marionette, timeout).until( + expected.element_present(*_fxa_login_link_locator))) + ) + marionette.find_element(*_fxa_login_link_locator).click() + driver = WebDriverFxA(marionette, timeout) + driver.sign_in(user['email'], user['password']) diff --git a/tests/functional/testpilot/test_testpilot_login.py b/tests/functional/testpilot/test_testpilot_login.py new file mode 100644 index 0000000..3dea0fc --- /dev/null +++ b/tests/functional/testpilot/test_testpilot_login.py @@ -0,0 +1,15 @@ +# 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 marionette_driver import By, Wait, expected + +from apps.testpilot.app import TestPilot + + +class TestTestPilotLogin(object): + + def test_user_can_log_in(self, sign_in, puppeteer, firefox, base_url): + test_pilot_page = TestPilot(puppeteer, firefox, base_url) + diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 0000000..12027ae --- /dev/null +++ b/tests/integration/__init__.py @@ -0,0 +1 @@ +__author__ = 'jdorlus' diff --git a/tests/manifest.ini b/tests/manifest.ini new file mode 100644 index 0000000..42ceb73 --- /dev/null +++ b/tests/manifest.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[include:functional/manifest.ini] diff --git a/tests/requirements.txt b/tests/requirements.txt new file mode 100644 index 0000000..5004ec0 --- /dev/null +++ b/tests/requirements.txt @@ -0,0 +1,35 @@ +autopep8==1.2.1 +blessings==1.6 +cffi==1.5.0 +cryptography==1.2.1 +enum34==1.1.2 +flake8==2.5.1 +fxapom==1.7 +hawkauthlib==0.1.1 +idna==2.0 +ipaddress==1.0.16 +marionette-driver==1.1.1 +marionette-transport==1.0.0 +mccabe==0.3.1 +mozdevice==0.48 +mozfile==1.2 +mozinfo==0.9 +mozlog==3.1 +moznetwork==0.27 +mozprocess==0.22 +mozprofile==0.28 +mozrunner==6.11 +pep8==1.7.0 +py==1.4.31 +pyasn1==0.1.9 +PyBrowserID==0.9.2 +pycparser==2.14 +pyflakes==1.0.0 +PyFxA==0.0.8 +pytest==2.7.3 +pytest-html==1.7 +pytest-variables==1.3 +requests==2.9.1 +selenium==2.48.0 +six==1.10.0 +WebOb==1.5.1