Remove support for Marionette client and migrate to PyPOM (#90)

* Remove support for the Marionette client

* Migrate page objects to use PyPOM
This commit is contained in:
Dave Hunt 2017-03-15 00:12:25 +00:00 коммит произвёл Stephen Donner
Родитель 0f3b3cdb16
Коммит d591cc0967
20 изменённых файлов: 48 добавлений и 262 удалений

Просмотреть файл

@ -14,20 +14,15 @@ matrix:
env: TOXENV=flake8
- python: 2.7
env: TOXENV=py27
- python: 2.7
env: TOXENV=py27-marionette
- python: 3.5
env: TOXENV=flake8
- python: 3.5
env: TOXENV=py35
- python: 3.5
env: TOXENV=py35-marionette
exclude:
- python: 2.7
- python: 3.5
allow_failures:
- env: TOXENV=py35
- env: TOXENV=py35-marionette
install:
- pip install tox
before_script:

Просмотреть файл

@ -1,6 +1,15 @@
Release Notes
-------------
**1.10.0 (unreleased)**
* Remove support for the Marionette client
* The latest versions of Selenium and Firefox use Marionette through
GeckoDriver.
* Migrate page objects to PyPOM
**1.9.0 (2016-07-15)**
* Wait for the sign in page to load when not in a popup

Просмотреть файл

@ -50,10 +50,6 @@ or the production environment, which are:
* ``fxapom.DEV_URL`` - the url for the development environment
* ``fxapom.PROD_URL`` - the url for the production environment
FxAPOM is now able to handle tests written using both Selenium WebDriver and Marionette.
Based on the type of driver being used, the package will automatically handle the tests in the way
best suited for that driver including all error handling.
Example of creating an account on the development environment, using the default:
.. code-block:: python
@ -75,13 +71,11 @@ passing in the email address and password:
.. code-block:: python
from fxapom.fxapom import WebDriverFxA
fxa = WebDriverFxA(driver)
fxa = WebDriverFxA(selenium)
fxa.sign_in(email_address, password)
Note that we are passing ``driver`` into the constructor of ``WebDriverFxA``,
which it then uses to interact with the Firefox Accounts web pages. This driver will
be identified as either an instance of Selenium or Marionette and the tests will be
handled accordingly.
Note that we are passing ``selenium`` into the constructor of ``WebDriverFxA``,
which it then uses to interact with the Firefox Accounts web pages.
To create an account and then use it to sign in, use both tools described above:
@ -89,7 +83,7 @@ To create an account and then use it to sign in, use both tools described above:
from fxapom.fxapom import FxATestAccount, WebDriverFxA
account = FxATestAccount()
fxa = WebDriverFxA(driver)
fxa = WebDriverFxA(selenium)
fxa.sign_in(account.email, account.password)
Running The Tests

Просмотреть файл

@ -11,8 +11,6 @@ from fxa.core import Client
from fxa.errors import ClientError
from fxa.tests.utils import TestEmailAccount
from selenium.webdriver.remote.webdriver import WebDriver
# Constants for available FxA environments
DEV_URL = 'https://stable.dev.lcip.org/auth/'
PROD_URL = 'https://api.accounts.firefox.com/'
@ -26,17 +24,14 @@ class AccountNotFoundException(Exception):
class WebDriverFxA(object):
def __init__(self, driver, timeout=TIMEOUT):
self.driver = driver
def __init__(self, selenium, timeout=TIMEOUT):
self.selenium = selenium
self.timeout = timeout
def sign_in(self, email=None, password=None):
"""Signs in a user, either with the specified email address and password, or a returning user."""
if isinstance(self.driver, WebDriver):
from .pages.sign_in import SignIn
else:
from .pages.marionette.sign_in import MarionetteSignIn as SignIn
sign_in = SignIn(self.driver, self.timeout)
from .pages.sign_in import SignIn
sign_in = SignIn(self.selenium, self.timeout)
sign_in.sign_in(email, password)

Просмотреть файл

@ -2,18 +2,19 @@
# 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 pypom import Page
from fxapom.fxapom import TIMEOUT
from .page import Page
class Base(Page):
def __init__(self, driver, timeout=TIMEOUT):
super(Base, self).__init__(driver, timeout)
self._main_window_handle = self.driver.current_window_handle
def __init__(self, selenium, timeout=TIMEOUT):
super(Base, self).__init__(selenium, timeout=timeout)
self._main_window_handle = self.selenium.current_window_handle
def switch_to_main_window(self):
self.driver.switch_to_window(self._main_window_handle)
self.selenium.switch_to_window(self._main_window_handle)
def close_window(self):
self.driver.close()
self.selenium.close()

Просмотреть файл

Просмотреть файл

@ -1,19 +0,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/.
from fxapom.fxapom import TIMEOUT
from fxapom.pages.marionette.page import Page
class Base(Page):
def __init__(self, driver, timeout=TIMEOUT):
super(Page, self).__init__(driver, timeout)
self._main_window_handle = self.driver.current_window_handle
def switch_to_main_window(self):
self.driver.switch_to_window(self._main_window_handle)
def close_window(self):
self.driver.close()

Просмотреть файл

@ -1,20 +0,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/.
from marionette_driver.errors import NoSuchElementException
from fxapom.fxapom import TIMEOUT
class Page(object):
def __init__(self, driver, timeout=TIMEOUT):
self.driver = driver
self.timeout = timeout
def is_element_visible(self, *locator):
try:
return self.driver.find_element(*locator).is_displayed()
except NoSuchElementException:
return False

Просмотреть файл

@ -1,81 +0,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/.
from marionette_driver import expected, Wait, By
from fxapom.fxapom import TIMEOUT
from fxapom.pages.marionette.base import Base
class MarionetteSignIn(Base):
_fox_logo_locator = (By.ID, 'fox-logo')
_email_input_locator = (By.CSS_SELECTOR, '.input-row .email')
_next_button_locator = (By.ID, 'email-button')
_password_input_locator = (By.ID, 'password')
_sign_in_locator = (By.ID, 'submit-btn')
def __init__(self, driver, timeout=TIMEOUT):
super(Base, self).__init__(driver, timeout)
self._sign_in_window_handle = None
self.popup = False
self.check_for_popup(self.driver.window_handles)
@property
def email(self):
"""Get the value of the email field."""
return self.driver.find_element(*self._email_input_locator).get_attribute('value')
@email.setter
def email(self, value):
"""Set the value of the email field."""
email = Wait(self.driver, self.timeout).until(
expected.element_present(*self._email_input_locator))
Wait(self.driver, self.timeout).until(
expected.element_displayed(email))
email.clear()
email.send_keys(value)
@property
def login_password(self):
"""Get the value of the login password field."""
return self.driver.find_element(*self._password_input_locator).get_attribute('value')
@login_password.setter
def login_password(self, value):
"""Set the value of the login password field."""
password = self.driver.find_element(*self._password_input_locator)
password.clear()
password.send_keys(value)
def click_next(self):
self.driver.find_element(*self._next_button_locator).click()
def check_for_popup(self, handles):
if len(self.driver.window_handles) > 1:
self.popup = True
for handle in handles:
self.driver.switch_to.window(handle)
if self.is_element_visible(*self._fox_logo_locator):
Wait(self.driver, self.timeout).until(
expected.element_displayed(*self._email_input_locator))
self._sign_in_window_handle = handle
break
else:
raise Exception('Popup has not loaded')
def click_sign_in(self):
self.driver.find_element(*self._sign_in_locator).click()
if self.popup:
Wait(self.driver, self.timeout).until(
lambda s: self._sign_in_window_handle not in self.driver.window_handles)
self.switch_to_main_window()
def sign_in(self, email, password):
"""Signs in using the specified email address and password."""
self.email = email
self.login_password = password
if self.is_element_visible(*self._next_button_locator):
self.click_next()
self.click_sign_in()

Просмотреть файл

@ -1,26 +0,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/.
from selenium.common.exceptions import NoSuchElementException
from fxapom.fxapom import TIMEOUT
class Page(object):
def __init__(self, driver, timeout=TIMEOUT):
self.driver = driver
self.timeout = timeout
def is_element_present(self, *locator):
try:
return self.driver.find_element(*locator)
except NoSuchElementException:
return False
def is_element_visible(self, *locator):
try:
return self.driver.find_element(*locator).is_displayed()
except NoSuchElementException:
return False

Просмотреть файл

@ -3,8 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait as Wait
from selenium.webdriver.support import expected_conditions as expected
from fxapom.fxapom import TIMEOUT
from fxapom.pages.base import Base
@ -18,37 +17,37 @@ class SignIn(Base):
_password_input_locator = (By.ID, 'password')
_sign_in_locator = (By.ID, 'submit-btn')
def __init__(self, driver, timeout=TIMEOUT):
super(SignIn, self).__init__(driver, timeout)
def __init__(self, selenium, timeout=TIMEOUT):
super(SignIn, self).__init__(selenium, timeout)
self._sign_in_window_handle = None
self.popup = False
self.check_for_popup(self.driver.window_handles)
self.check_for_popup(self.selenium.window_handles)
@property
def login_password(self):
"""Get the value of the login password field."""
return self.driver.find_element(*self._password_input_locator).get_attribute('value')
return self.selenium.find_element(*self._password_input_locator).get_attribute('value')
@login_password.setter
def login_password(self, value):
"""Set the value of the login password field."""
password = self.driver.find_element(*self._password_input_locator)
password = self.selenium.find_element(*self._password_input_locator)
password.clear()
password.send_keys(value)
def click_next(self):
self.driver.find_element(*self._next_button_locator).click()
self.selenium.find_element(*self._next_button_locator).click()
@property
def email(self):
"""Get the value of the email field."""
return self.driver.find_element(*self._email_input_locator).get_attribute('value')
return self.selenium.find_element(*self._email_input_locator).get_attribute('value')
@email.setter
def email(self, value):
"""Set the value of the email field."""
email = Wait(self.driver, self.timeout).until(
EC.visibility_of_element_located(self._email_input_locator))
email = self.wait.until(expected.visibility_of_element_located(
self._email_input_locator))
email.clear()
email.send_keys(value)
@ -56,19 +55,20 @@ class SignIn(Base):
if len(handles) > 1:
self.popup = True
for handle in handles:
self.driver.switch_to.window(handle)
self.selenium.switch_to.window(handle)
if self.is_element_present(*self._fox_logo_locator):
self._sign_in_window_handle = handle
break
else:
raise Exception('Popup has not loaded')
Wait(self.driver, self.timeout).until(EC.visibility_of_element_located(self._email_input_locator))
self.wait.until(expected.visibility_of_element_located(
self._email_input_locator))
def click_sign_in(self):
self.driver.find_element(*self._sign_in_locator).click()
self.selenium.find_element(*self._sign_in_locator).click()
if self.popup:
Wait(self.driver, self.timeout).until(
lambda s: self._sign_in_window_handle not in self.driver.window_handles)
self.wait.until(
lambda s: self._sign_in_window_handle not in self.selenium.window_handles)
self.switch_to_main_window()
def sign_in(self, email, password):
@ -76,7 +76,7 @@ class SignIn(Base):
self.email = email
self.login_password = password
if self.is_element_present(*self._next_button_locator):
Wait(self.driver, self.timeout).until(
EC.visibility_of_element_located(self._next_button_locator))
self.wait.until(expected.visibility_of_element_located(
self._next_button_locator))
self.click_next()
self.click_sign_in()

Просмотреть файл

@ -1 +0,0 @@
marionette-driver==2.2.0

Просмотреть файл

@ -23,5 +23,5 @@ setup(name='fxapom',
license='MPL 2.0',
packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
setup_requires=['setuptools_scm'],
install_requires=['PyFxA==0.3.0'],
install_requires=['PyFxA==0.3.0', 'PyPOM'],
include_package_data=True)

Просмотреть файл

Просмотреть файл

@ -1,28 +0,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/.
import pytest
from marionette_driver import By, Wait
from marionette_driver.marionette import Marionette
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
@pytest.fixture
def click_login(base_url, marionette, timeout):
fxa_login_button_locator = (By.CSS_SELECTOR, 'button.signin')
marionette.navigate('%s/' % base_url)
Wait(marionette, timeout).until(
lambda m: m.find_element(*fxa_login_button_locator).is_displayed())
marionette.find_element(*fxa_login_button_locator).click()
@pytest.fixture
def marionette(request):
firefox_binary = FirefoxBinary()
m = Marionette(bin=firefox_binary._get_firefox_start_cmd())
m.start_session()
m.set_prefs({'signon.rememberSignons': False})
request.addfinalizer(m.delete_session)
return m

Просмотреть файл

@ -1,20 +0,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/.
from marionette_driver import By, expected, Wait
from fxapom.fxapom import WebDriverFxA
class TestLogin(object):
_fxa_logged_in_indicator_locator = (By.ID, 'loggedin')
def test_user_can_sign_in(self, base_url, marionette, dev_account, click_login, timeout):
fxa = WebDriverFxA(marionette, timeout)
fxa.sign_in(dev_account.email, dev_account.password)
# We sometimes need to wait longer than the standard 10 seconds
logged_in = Wait(marionette, timeout).until(
expected.element_present(*self._fxa_logged_in_indicator_locator))
Wait(marionette, timeout).until(expected.element_displayed(logged_in))

Просмотреть файл

Просмотреть файл

@ -6,14 +6,16 @@ from mock import Mock
import pytest
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.remote.webdriver import WebDriver
from pypom.selenium_driver import ISelenium
from zope.interface import alsoProvides
from fxapom.pages.sign_in import SignIn
@pytest.fixture
def webdriver(request):
driver = Mock(spec=WebDriver)
driver = Mock()
alsoProvides(driver, ISelenium)
def element(*locator):
element = Mock()

Просмотреть файл

17
tox.ini
Просмотреть файл

@ -1,31 +1,16 @@
[tox]
envlist = py{27,35}, py{27,35}-marionette, flake8
envlist = py{27,35}, flake8
[testenv]
passenv = DISPLAY PYTEST_ADDOPTS PYTEST_BASE_URL SAUCELABS_API_KEY \
SAUCELABS_USERNAME JENKINS_URL JOB_NAME BUILD_NUMBER
deps = -rrequirements/tests.txt
commands = pytest \
--ignore tests/marionette \
--junit-xml=results/{envname}.xml \
--html=results/{envname}.html \
--log-raw=results/{envname}.log \
{posargs:tests}
[marionette]
deps =
{[testenv]deps}
-rrequirements/marionette.txt
commands = pytest {posargs:tests/marionette}
[testenv:py27-marionette]
deps = {[marionette]deps}
commands = {[marionette]commands}
[testenv:py35-marionette]
deps = {[marionette]deps}
commands = {[marionette]commands}
[testenv:flake8]
skip_install = true
deps = -rrequirements/flake8.txt