I use selenium with pylons to test user interactions. Everything was fine until I added testing for the authentication interface - the login screen with the username and password. It looks like Chrome (the browser I use under selenium) opens a dialog to ask if credentials should be stored. All tests end after this point.
Does anyone have pointers to using selenium with authentication / user interfaces that can appear in dialogs? I know this warning of selenium . “Do not do this. Instead, do this different” will be an acceptable answer.
Project / tests / Library / selenium / login_page.py
"""
LoginPage represents the page where users are authenticated for the application
"""
class LoginPage(object):
"""This is the LoginPage class."""
login = '/authentication/login'
logout = '/authentication/logout'
home = '/home'
def __init__(self, browser):
self._browser = browser
def goto_login(self, baseurl):
return self._browser.go_to(baseurl + self.login)
def goto_logout(self, baseurl):
return self._browser.go_to(baseurl + self.logout)
def goto_home(self, baseurl):
return self._browser.go_to(baseurl + self.home)
def enter_credentials(self, username, password):
element_by_id = self._browser.get_element_by_id
element_by_id("login").value = username
element_by_id("password").value = password
def submit_form(self):
element = self._browser.get_element_by_id('submit')
return self._browser.click(element)
Project / tests / selenium / test_login_page.py
"""Tests the login page."""
import nose.tools as nt
from nose.plugins.skip import SkipTest
from assess.tests.selenium import SeleniumTestBase
from assess.tests.lib.selenium.login_page import LoginPage
class TestLoginPage(SeleniumTestBase):
"""Tests the login page."""
def _login_page(self):
return self.baseurl + '/authentication/login'
def setUp(self):
nt.set_trace()
super(TestLoginPage, self).setUp()
self.page = LoginPage(self.browser)
def tearDown(self):
super(TestLoginPage, self).tearDown()
self.page = None
def test_login_page_fail(self):
self.page.goto_logout(self.baseurl)
self.page.goto_login(self.baseurl)
nt.assert_true(self.browser.get_url().startswith(self._login_page()))
self.page.enter_credentials('foo', 'random')
self.page.submit_form()
def test_login_page_success(self):
self.page.goto_logout(self.baseurl)
self.page.goto_login(self.baseurl)
nt.assert_true(self.browser.get_url().startswith(self._login_page()))
self.page.enter_credentials('user', 'good-password')
self.page.submit_form()
//login.html.mako
<%inherit file="/layout.html.mako" />
${h.stylesheet_link('login.css')}
<form action="/__do_login" method="POST">
<fieldset>
<p>
<label for="login">User name</label><br />
<input id="login" name="login" type="text" maxlength="40" value="" />
</p>
<p>
<label for="password">Password</label><br />
<input id="password" name="password" type="password" maxlength="40" value="" />
</p>
<p class="submit">
<input id="submit" type="submit" value="Submit" />
</p>
</fieldset>
</form>
.. /who.ini
[general]
request_classifier = repoze.who.classifiers:default_request_classifier
challenge_decider = repoze.who.classifiers:default_challenge_decider
[identifiers]
plugins = foo
friendly_form;browser
[authenticators]
plugins = foo
[challengers]
plugins = friendly_form;browser
foo
[plugin:foo]
use = ...
host = ...
logout_path_regex = /authentication/logout
httponly = True
[plugin:friendly_form]
use = repoze.who.plugins.friendlyform:FriendlyFormPlugin
login_form_url = /authentication/login
login_handler_path = /__do_login
post_login_url = /home
logout_handler_path = /authentication/logout
post_logout_url = /authentication/login
//routing.py
def make_map(config):
"""Create, configure and return the routes Mapper"""
mapper = Mapper(directory=config['pylons.paths']['controllers'],
always_scan=config['debug'])
mapper.minimization = False
mapper.explicit = False
mapper.connect('/error/{action}', controller='error')
mapper.connect('/error/{action}/{sid}', controller='error')
...
mapper.connect('/authentication/login',
controller='authentication',
action='index')
mapper.connect('/authentication/logout',
controller='authentication',
action='logout')
//authentication.py
"""
This module contains the login controller.
"""
import logging
from pylons.controllers.util import redirect
from pylons import url, tmpl_context as c, request
from project.lib.base import BaseController
from project.lib.authorize import user_is_authenticated
logger = logging.getLogger(__name__)
class AuthenticationController(BaseController):
""" This controller serves the login page."""
template = '/login.html.mako'
def index(self):
return self.render(self.template)
def validate(self):
""" render a login page if we're not logged in """
c.came_from = request.params.get("came_from", url("home"))
if user_is_authenticated():
msg = "User is authenticated: redirecting to %s" % c.came_from
logger.info(msg)
redirect(c.came_from)
msg = "User is not authenticated: rendering %s" % self.template
logger.info(msg)
return self.render(self.template)
/Library/authorize.py
''' Helper functions for the authorization and authentication mechanisms. '''
from pylons import request
from decorator import decorator
from pylons.controllers.util import abort
def user_is_authenticated():
""" Returns True if is authenticated, else returns False.
"""
identity = request.environ.get('repoze.who.identity')
return identity and 'xxx' in identity
@decorator
def authenticated(func, *args, **kwargs):
""" Check if is authenticated. If not authenticated, abort with
401 status.
"""
if not user_is_authenticated():
abort(401, 'You are not authenticated')
return func(*args, **kwargs)