I am trying to add Selenium tests to a Flask application that relies heavily on Flask-SocketIO. However, when accessing a page from the Selenium browser that does a database query, I get the following error message in the browser window:
Traceback (most recent call last):
File "/home/bbbart/.virtualenvs/heistmanager/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 346, in connection
return self.__connection
AttributeError: 'Connection' object has no attribute '_Connection__connection'
During handling of the above exception, another exception occurred:
[...]
File "/home/bbbart/.virtualenvs/heistmanager/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 429, in _revalidate_connection
raise exc.ResourceClosedError("This Connection is closed")
sqlalchemy.exc.ResourceClosedError: This Connection is closed
bbbart
is my usernameheistmanager
is the name of the application- I am using the following PyPI package (among others):
- Flask==0.12.2
- SQLAlchemy==1.1.13
- Flask-SQLAlchemy==2.2
- Flask-SocketIO==2.9.2
- Flask-Script==2.0.5
- eventlet==0.21.0
- pytest==3.2.1
- pytest-flask==0.10.0
- pytest-selenium==1.11.0
I have an extensive pytest suite for this application running perfectly already, including tests relying on the database and tests using the client
fixture from pytest-flask. It's only when starting the server externally for Selenium that this error appears.
In order for the app to start correctly with support for websockets I have overridden the LiveServer class and the live_server fixture from pytest_flask as follows (is this necessary?) in conftest.py
:
class LiveSocketIOServer(LiveServer):
"""The helper class to manage live socketio server. Handles creation and
stopping of the application in a separate process.
:param app: The application to run.
:param port: The port to run application.
"""
def start(self, socketio):
"""Start application in a separate process."""
def worker(app, port):
return socketio.run(app, port=port, use_reloader=False)
self._process = multiprocessing.Process(
target=worker,
args=(self.app, self.port)
)
self._process.start()
# We must wait for the server to start listening with a maximum
# timeout of 5 seconds.
timeout = 5
while timeout > 0:
time.sleep(1)
try:
urlopen(self.url())
timeout = 0
except BaseException:
timeout -= 1
def __repr__(self):
return '<LiveSocketIOServer listening at %s>' % self.url()
and
@pytest.yield_fixture(scope='function')
def live_server(request, app, monkeypatch):
"""Run application in a separate process.
When the ``live_server`` fixture is applied, the ``url_for`` function
works as expected::
def test_server_is_up_and_running(live_server):
index_url = url_for('index', _external=True)
assert index_url == 'http://localhost:5000/'
res = urllib2.urlopen(index_url)
assert res.code == 200
"""
# Find an open port
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('', 0))
port = sock.getsockname()[1]
sock.close()
server_name = app.config['SERVER_NAME'] or 'localhost'
monkeypatch.setitem(app.config, 'SERVER_NAME',
_rewrite_server_name(server_name, str(port)))
server = LiveSocketIOServer(app, port)
if request.config.getvalue('start_live_server'):
server.start(SOCKETIO)
yield server
server.stop()
My test looks like this:
@pytest.mark.usefixtures('live_server')
class TestSockets:
"""Tests for socket.io (with selenium)."""
@pytest.mark.nondestructive
def test_admin_login(self, config, selenium):
selenium.get(url_for('main.index', _external=True))
assert 'Hello stranger!' in selenium.page_source
selenium.find_element_by_link_text('Log in to administer').click()
assert '<h1>Login</h1>' in selenium.page_source
selenium.find_element_by_name('username').send_keys(
config['USERNAME_ADMIN'])
selenium.find_element_by_name('password').send_keys('*******')
selenium.find_element_by_name('submit').click()
assert 'Logged in as %s' % config['USERNAME_ADMIN'] in selenium.page_source
(You might recognise some elements from Miguel Grinberg's excellent Flasky demo. :-))
The error above is raised after the second click
call in the penultimate line of the test. This subsequently of course makes the assert
statement fail, but that's not the point here.
The tests are ran from a flask-script Manager command:
APP = create_app(os.getenv('HEIST_CONFIG') or 'default')
MANAGER = Manager(APP)
@MANAGER.command
def test():
"""Run the tests."""
import pytest
pytest.main(['--driver', 'Chrome',
os.path.join(basedir, 'tests')])
I spent a few hours now on trying to figure out why the SQLAlchemy connection is closed (or never even opened?). I added all sorts of DB stuff to the fixtures, but that never felt right and also never worked.
I'm lost and would appreciate any pointer here towards a solution. Thanks!