I am using selenium with pylons to test user interactions. Everything was fine until I added testing for an authentication UI -- a login screen with user name and password. It appears that Chrome (the browser I am using under selenium) is popping up a dialog to request whether to store the credentials. All the tests fail after this point.
Does anyone have any pointers on using selenium with authentication/UIs that may pop up dialog boxes? I am aware of this selenium warning. "Don't do this. Do this other thing instead" would be an acceptable answer.
project/tests/lib/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):
# Logout before beginning test
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):
# Logout before beginning test
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_开发者_JS百科form()
project/templates/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>
etc/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
project/config/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
# The ErrorController route (handles 404/500 error pages); it should
# likely stay at the top, ensuring it can always be resolved
mapper.connect('/error/{action}', controller='error')
mapper.connect('/error/{action}/{sid}', controller='error')
# CUSTOM ROUTES HERE
...
mapper.connect('/authentication/login',
controller='authentication',
action='index')
mapper.connect('/authentication/logout',
controller='authentication',
action='logout')
project/controllers/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 we're already authenticated, redirect us to where we started.
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)
project/lib/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)
Ideally you should use a profile for your tests and in this profile you should specify to never ask for saving the credentials. Unfortunately it is impossible to specify a customized Chrome profile with Selenium 1, but it is possible to do for Firefox in Selenium 1 or move to Selenium 2 (WebDriver) - see this thread for more information
精彩评论