Tester une application flask avec selenium et drone.io ====================================================== ![category](développement) ![tag](flask) ![tag](docker) ![tag](test) ![tag](python) Cet article a pour but de présenter un exemple de mise en oeuvre de test métier d'une application web *flask* via une plateforme de developpement continue comme *drone.io* en utilisant *selenium* La première chose est de mettre en place une platefome continue minimale: git et drone. Pour cela j'utilise le fichier docker-compose suivant ``` version: '2' services: gitea-server: image: gitea/gitea:latest ports: - 3000:3000 volumes: - ./gitea:/data drone-server: image: drone/drone:latest ports: - 8000:8000 - 9000 volumes: - ./drone:/var/lib/drone/ restart: always environment: - DRONE_OPEN=true - DRONE_HOST=http://192.168.0.16:8000 - DRONE_GITEA=true - DRONE_GITEA_URL=http://192.168.0.16:3000 - DRONE_SECRET=1234567890 - DRONE_ADMIN=fraoustin depends_on: - gitea-server drone-agent: image: drone/agent:latest command: agent restart: always depends_on: - drone-server volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - DRONE_SERVER=drone-server:9000 - DRONE_SECRET=1234567890 ``` Que je lance ainsi ```bash docker-compose up -d ``` Vous pouvez trouver - un projet python d'une application Flask avec test unitaire [ici](images/201810252008.zip)` - un projet API REST avec test unitaire et fonctionnels [ici](images/201810252008_2.zip)` Vous pouvez lancer l'application via la commande ```bash python myapp.py ``` Vous avez alors accès un site web sur l'url http://127.0.0.1:8080 qui comprend une page web avec une application javascript minimale ```html Test Hello Hello

   

``` le fichier .drone.yml permettant de lancer les tests fonctionnels ``` pipeline: functionaltest: image: python:3.6.1-alpine commands: - pip install -r requirements.txt - pip install -r test-requirements.txt - python -m unittest discover -s test when: branch: [ master, develop ] end: image: alpine commands: - echo "end" when: branch: [ master, develop ] services: seleniumf: image: selenium/standalone-firefox seleniumc: image: selenium/standalone-chrome ``` et enfin notre fichier test (utilisant unittest) ```python # coding: utf-8 from __future__ import absolute_import import sys from flask import json from six import BytesIO import urllib.request from urllib.parse import urlparse import socket import logging from flask import Flask from flask_testing import LiveServerTestCase import myapp from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class TestFlask(LiveServerTestCase): def create_app(self): logging.getLogger('connexion.operation').setLevel('ERROR') app = myapp.app app.run = self.getRun() if sys.version_info[0] >= 3: import warnings warnings.simplefilter("ignore") return app def getRun(self): def run(host=None, port=None, debug=None, load_dotenv=True, **options): Flask.run(self.app, host='0.0.0.0', port=port, debug=debug, load_dotenv=load_dotenv, **options) return run def setUp(self): self.driver_firefox = webdriver.Remote( command_executor='http://seleniumf:4444/wd/hub', desired_capabilities= { "browserName": "firefox", "version": "", "platform": "ANY", "javascriptEnabled": True }) self.driver_chrome = webdriver.Remote( command_executor='http://seleniumc:4444/wd/hub', desired_capabilities= { "browserName": "chrome", "version": "", "platform": "ANY", "javascriptEnabled": True }) self._ip="localhost" data = urllib.request.urlopen("http://seleniumf:4444/wd/hub") addr = socket.gethostbyname(urlparse(data.geturl()).hostname).split('.') for p in socket.gethostbyname_ex(socket.gethostname())[2]: if p.split('.')[0:2] == addr[0:2]: self._ip = p def get_expose_url(self): return 'http://%s:%s' % (self._ip,self.get_server_url().split(":")[-1]) def test_server_is_up_and_running(self): response = urllib.request.urlopen(self.get_expose_url()) self.assertEqual(response.code, 200) def test_connection_firefox(self): with self.driver_firefox as c: c.get(self.get_expose_url()) self.assertIn("Test Hello", c.title) def test_connection_chrome(self): with self.driver_chrome as c: c.get(self.get_expose_url()) self.assertIn("Test Hello", c.title) def test_connection_js(self): with self.driver_firefox as c: c.get(self.get_expose_url()) elem = c.find_element_by_id("src") elem.clear() elem.send_keys("tutu") c.find_element_by_id("button").click() self.assertIn("tutu", c.find_element_by_id("dst").get_attribute("value")) ``` Ce fichier permet de Tester - accès au serveur de test qui se monte automatiquement via drone - connexion à l'application par firefox - connexion à l'application par chrome. - test de l'application Notre fichier de test comporte certains éléments important - désactivation des warnings - identification de l'adresse ip du serveur local (tous es lancer dans des containers il faut donc connaitre l'ip pour la fournir à selenium) - usage d'une url via l'ip du serveur (et pas localhost) - lancement du serveur avec une écoute sur l'ensemble du réseau (0.0.0.0 et non comme par défaut localhost) ```warning par defaut *LiveServerTestCase* permet de lancer un serveur de test sur uen écoute restreinte (localhost) ce qui interdit l'usage de selenium si ce dernier n'a pas la même ip que le LiveServerTestCase. Hors dans un cas d'usage de container, LiveServerTestCase a son adresse ip et le serveur selenium la sienne d'ou les quelques modifications a insérer dans le code de test