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:
Родитель
0f3b3cdb16
Коммит
d591cc0967
|
@ -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
|
||||
|
|
14
README.rst
14
README.rst
|
@ -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
|
2
setup.py
2
setup.py
|
@ -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
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
|
||||
|
|
Загрузка…
Ссылка в новой задаче