3

I am building a Flask-backed web app where all the interesting pages are behind a login. I would like to run automated tests against it using the Selenium WebDriver. I cannot seem to figure out how to log in a user and associate it with Selenium. Everything I try results in Selenium being presented with the "Please log in" page.

Unacceptable Solution 1: Scripted Selenium Log In

Many of the resources I have seen (1, 2, 3, among many others) suggest manually logging in with a scripted test, which is to say navigating Selenium to the log in page, filling out forms, and clicking buttons. This is a terrible solution for many reasons including but not limited to:

  • Requires hardcoding credentials in plaintext somewhere (my app uses social media log in so there are no test or made-up credentials available)
  • Introduces a dependency; login routine must now be run in a test or fixture and no other tests can be run in isolation of it
  • Slow; must be run before every test as a browser action

Unacceptable Solution 2: Require Manual Log In Before Selenium Tests Run

Something else I could do is open the browser I'm targeting myself before running the Selenium tests and log in. This keeps the credentials out of Selenium's hands. Problems:

  • PITA; Many times I will forget to do this, causing me to have to rerun tests. Because it's such a hassle I may be motivated to run the test suite less.
  • Not scalable; if I ever tried testing multiple browsers or on a different environment this is right out the window as an option
  • Slow; still requires Selenium to click through login page and hit 'Authorize app' on a third-party site.

Failed Attempted Solution 1: Session Fakery

Using something like this:

with client.session_transaction() as session:
    session['user_id'] = test_user.id
    session['_fresh'] = True

where client is an instance of app.test_client(). (This is in a pytest fixture; I'm using Flask_login). This allows me to log in during regular unit tests, but I have not been able to figure out how I could get Selenium to use this same session.

Failed Attempted Solution 2: login_user

Similar to the above I can try calling Flask_login.login_user(test_user) in a fixture, but I have not successfully been able to associate Selenium with this user. Since the session already failed, the only thing I could think would be to set a cookie for this logged in user on Selenium driver.set_cookie, but I have no idea how to get a proper one out of the test client or Flask login to do so.

Failed Solution #3: LOGIN_DISABLED

According to the Flask-Login docs you can set LOGIN_DISABLED in your app config to bypass the login_required decorator. I did this but I'm still seeing a login page.

I dug into this a bit more and the issue is not that LOGIN_DISABLED isn't working but that a lot of views depend on the behavior of Flask_login's current_user.is_anonymous. If I were going to go this route I'd also need a solution to make sure that current_user is set to the test user, so that actions in the app are correctly grouped with the user that's "logged in".

How do I authenticate once without using Selenium and then get Selenium to use that authenticated user for the rest of its session?

Two-Bit Alchemist
  • 17,966
  • 6
  • 47
  • 82
  • Can you add the code for your login page of your Flask server and a simple test page? – Maximilian Peters Jan 30 '17 at 14:38
  • @MaximilianPeters The login is quite similar to [this](https://blog.miguelgrinberg.com/post/oauth-authentication-with-flask), which I'm pretty sure I used as a reference. I'm not sure what you mean by a simple test page. It seems like you're interested in showing me how to have Selenium get the form elements and perform a scripted login, which does not work for me (see **Unacceptable Solution 1**). – Two-Bit Alchemist Jan 30 '17 at 15:05
  • Can you extract a cookie after login with any tool, and then add them to selenium? Another variant: Se doesn't try to fill a form, but posts to hardcoded URL hardcoded values to obtain a session cookie. – Eugene Lisitsky Jan 31 '17 at 11:39
  • @EugeneLisitsky I thought about the cookie extraction but I have no idea how to get a valid one with this setup. Post it as an answer if you know how. – Two-Bit Alchemist Jan 31 '17 at 20:21
  • For `LOGIN_DISABLED` to work it needs to be set True in `app.config` prior to initializing flask-login. It would apply when your endpoints are protected with `login_required` or `fresh_login_required`. Since I used `roles_required` from flask-security instead, I ended up triggering the identity in a `before_request` hook. How are your resources protected? Perhaps you can show an example. – user650881 Feb 01 '17 at 11:58
  • @Two-BitAlchemist, I've posted below how to get the session cookie with python. Pls feel free to contact me if there're some troubles. – Eugene Lisitsky Feb 01 '17 at 20:01
  • @user650881 I'm using a config object with `LOGIN_DISABLED` set in the second line of my app's `__init__.py` file, and Flask-login isn't initialized until several lines later when something is imported by my register blueprints routine. My views are all decorated with `login_required`. – Two-Bit Alchemist Feb 02 '17 at 19:56
  • @Two-BitAlchemist since the variable is set on the app used to initialize flask-login and you are using login_required it should unequivocally skip authentication. As a sanity check you might dump `flask_login._login_disabled` in your route handler to verify it is True. – user650881 Feb 02 '17 at 20:59
  • @user650881 I figured out (should have been obvious but it's been a while since I wrote most auth and view-handling code) that it's not because the login isn't disabled but because the views are handling things based on `current_user.is_anonymous`, which of course is true if login is simply disabled and something hits the page. May be a dead end solution for me because of that. – Two-Bit Alchemist Feb 04 '17 at 13:34
  • @Two-BitAlchemist actually I may have a solution for you. It depends a bit on whether you are using flask-security, but I'll post it and hopefully it will help. – user650881 Feb 04 '17 at 14:29

4 Answers4

2

How to get cookies after login. One can simulate login process, find you form take action attribute from it and names of fields for username and password. Look what cookie is used for storing session key, suppose it is sid.

get_cookie.py:

import requests
r = requests.post(action, 
                  data={'login': 'your_username', 'password': 'You_p@ssw0rd'}, 
                  allow_redirects=False)
# now r.cookies contains all returned cookies,
# print cookie you need:
print(r.cookie['sid'])

Try to do it in a webdriver session initialisation. Or you may run it and feed this cookie to Selenium, for example as a command line parameter (just use your correct parameters):

selenium .... --session=$(./get_cookie.py)

Two-Bit Alchemist
  • 17,966
  • 6
  • 47
  • 82
Eugene Lisitsky
  • 12,113
  • 5
  • 38
  • 59
  • Have you tried PhantomJS? In one of my questions there's a recipe to obtain cookies in pure client-side. – Eugene Lisitsky Feb 02 '17 at 19:59
  • It's become clear to me that some of my criteria are basically impossible and I'm not going to be able to do this the way I'd prefer. I'm giving you the bounty for presenting the fastest solution and helping me avoid the most costs in the implementation. Thanks for all your work tracking this stuff down and the alternate suggestions. – Two-Bit Alchemist Feb 04 '17 at 13:40
  • Thank you. Pls feel free to ask, I'm glad to help. – Eugene Lisitsky Feb 06 '17 at 09:24
1

Cam you please read following link ?

Login using selenium

In this, login is done using selenium and cookie is stored in a file for the later use. And after that you can use requests with the saved cookie to access other pages.

Community
  • 1
  • 1
Ujjaval Moradiya
  • 222
  • 1
  • 12
1

You can create an AuthenticatedUser and make it the current_user before the tests.

In my case I use flask-security with Roles so I also create and embed a specific Role and call hooks so it integrates with flask-principal and overrides the user-lookup that occurs in flask-security.

Since you are seemingly not using these extra libraries I will try to recraft it to your case. When you want to disable login you would call what I have named force_authenticated(app) before your first request occurs. (It will error if called after requests have already occurred.)

# This is a non-anonymous, authenticated user
class AuthenticatedUser(flask_login.UserMixin):
    def get_id(self):
        return 'TestUser'

def force_authenticated(app):
    @app.before_request
    def set_identity():
        user = AuthenticatedUser()
        flask_login.login_user(user)

Here is a full, really minimal, confirmed-working example:

from flask import Flask
import flask_login

class AuthenticatedUser(flask_login.UserMixin):
    def get_id(self):
        return 'TestUser'

def force_authenticated(app):
    @app.before_request
    def set_identity():
        user = AuthenticatedUser()
        flask_login.login_user(user)

app = Flask(__name__)
app.secret_key = 'blahblah'

lm = flask_login.LoginManager(app)
lm.init_app(app)

force_authenticated(app)

@app.route('/')
@flask_login.login_required
def hello_world():
    return 'Hello, World!'

app.run()
user650881
  • 2,214
  • 19
  • 31
0

We have a form just like what you are using flask. What we have done is create a default configuration file in the GIT project. Then I pulled this down to my box and created my own configuration file from this default and put my credentials in this file. It seems to work fine for us. I run all my tests from Eclipse using pydev. I recently started using the HTMLTestRunner to get a better picture of results. I hope this helps you out.

selva
  • 1,175
  • 1
  • 19
  • 39