Client tests for Lettuce + Selenium

Introduction


At the moment, the most popular solution for client testing is selenium. It is worth noting that it is well deserved - the opportunities that selenium provides in conjunction with a webdriver really cover almost the entire spectrum of user interaction with web applications.

For small projects, the option with browser plug-ins is great, the functionality of which can be expanded by adding third-party components (for example, UI-element) But when the project becomes large enough, and many of its parts are repeatedly changed and even completely re-sawed, after each change in the page structure or method of data submission, it is necessary to replace whole blocks of tests at once, looking into almost every test. After such a selenium, plugins no longer seem so convenient. And here the selenium libraries come to the rescue, implemented for many languages ​​associated with web development ( documentation on the official website). I

suggest you look at the possibilities of client testing (in particular django projects) that python-selenium gives in conjunction with the lettuce library.


Lettuce + Selenium


Let's look at the features that these components provide individually:

Python-Selenium
  • as mentioned above, selenium has a rich arsenal of interaction with web applications through a browser
  • unlike plugins, it is possible to use the extensive python functionality
  • integration with the project itself


Lettuce
  • separation of tests into loosely coupled parts
  • control over each stage of testing
  • beautiful output in the terminal :)


And immediately into battle


Using simple registration and authorization tests as an example, I will try to demonstrate the main aspects of working with lettuce + selenium.

Problem statement

It is necessary to create 2 tests that will perform the following actions:
Registration:
  1. Go to the registration page
  2. Fill in the fields on the registration form
  3. Click on the registration button
  4. See message about successful registration

Authorization:
  1. Go to the login page
  2. Fill in the fields on the authorization form
  3. Click on the authorization button
  4. See message about successful authorization


Already from the statement of the problem it can be seen that these 2 tests perform similar actions, but in a different context. Let's get down to the task.

Performing the task

By adding lettuce to our django project, as described in the official documentation , and installing all the necessary dependencies (in our case, it is firefox system packages and the Python modules lettuce and selenium), we can start writing tests.
PS: to run in headless mode, you will need xvfb and pyvirtualdisplay.

To work with lettuce, you need to create the following files:
  • terrain.py at the root of the project contains instructions that are executed at the specified testing stage
  • * .feature files in the myapp / features folder contains a step-by-step description of each test
  • * .py files in the myapp / features folder contain descriptions of the steps used in * .feature files

I can advise myself to include in the project a file containing the structure of the pages of your application (similar to PageObject used in selenium). Call this file mapping.py and put it in the root of the project. This separation of the page structure from the tests reduces the need to rewrite the tests themselves when changing the layout: just correct the corresponding page in mapping.py. It also significantly increases the readability of the tests (this will be clearly demonstrated later).

mapping.py

host_url = 'http://example.com'
site_mapping = {
    "registration": {
        "url": host_url + "/registration/",
        "username": "//input[@id='username']",
        "email": "//input[@id='email']",
        "password": "//input[@name='password1']",
        "verify password": "//input[@name='password2']",
        "signup button": "//button[@name='signup']",
        "status message": "//div[@id='status']",
    },
    "authorization": {
        "url": host_url + "/login/",
        "username": "//input[@id='username']",
        "password": "//input[@name='password']",
        "login button": "//button[@name='login']",
        "status message": "//div[@id='status']",
    },
}

PS: I use xpath because I think this is the best way to find elements

terrain.py

from lettuce import before, after, world
from selenium import webdriver
from mapping import site_mapping
@before.harvest
def setup(server):
    # world - переменная, используемая lettuce между всеми стадиями тестов, т.е. хранящая в себе информацию между тестами
    world.browser = webdriver.Firefox() # открываем браузер
    world.mapping = site_mapping # сохраняем структуру в world
@after.all
def teardown(total):
    world.browser.close() # закрываем браузер


myapp / features / auth.feature

Feature: Authorization
  Scenario: Registration
    Open "registration" page
    Fill "username" with "myusername"
    Fill "email" with "user@example.com"
    Fill "password" with "1234"
    Fill "verify password" with "1234"
    Click "signup button"
    See "Welcome aboard" in "status message"
  Scenario: Authorization
    Open "authorization" page
    Fill "username" with "myusername"
    Fill "password" with "1234"
    Click "login button"
    See "Hello! Again..." in "status message"

As you can see, the lack of information about the location of the elements played a role in the readability of the scripts. Thanks mapping.py for this

myapp / features / steps.py

from lettuce import step, world
@step(r'Open "(.*)" page')
def open_page(step, page):
    world.current_page = world.mapping[page] # взять url искомой страницы
    world.browser.get(world.current_page['url']) # открыть этот url в браузере
@step(r'Fill "(.*)" with "(.*)"')
def fill_element_with_text(step, element, value):
    elem = world.browser.find_element_by_xpath(world.current_page[element])
    elem.send_keys(value)
@step(r'Click "(.*)"')
def i_click_xpath(step, element):
    elem = world.browser.find_element_by_xpath(world.current_page[element])
    elem.click()
@step(r'See "(.*)" in "(.*)"')
def i_see_text_in_element(step, text, element):
    elem = world.browser.find_element_by_xpath(world.current_page[element])
    assert elem.text == text


That's all. It remains only to run the tests and see how they successfully (or not) pass.

It seems that too many gestures to write any two tests - it is. But if you want to cover a project with a large number of tests, then as you write scripts and steps to them, you will less and less need to write new steps, because almost all possible user interaction with your site will already be described in the existing steps - all that remains is to write new scripts, which is no more difficult than writing the “task setting” item a little higher.

Total


What we have in the balance:
  • A tool that can be used by both developers and testers.
  • High resistance to changes in layout and structure of the site as a whole thanks to mapping.py
  • You can run tests in almost anything. Firefox, chrome, phantomjs (maybe there are other options, but this is what I tried).
  • Run tests anywhere: on your computer, on a virtual machine, on a remote server
  • For each test run, you can create a test database, include a separate instance of your application, load test data, and at the end, safely delete all this (as the test framework does in django itself).
  • You can attach log storage. And if asynchronous tasks are timed to coincide with this, then you can get information about the status of the entire project every N hours.
  • With a large database of steps (steps.py), it will be sufficient to describe only scripts that are quite simple to write “translating” TK into them.
  • Deploying such tests on one project will not be difficult to transfer them to another project. When porting to a new project, scripting will take the most time, which does not seem so difficult (compared, for example, to creating another hundred tests in selenium plugins)


Useful links on the topic:




PS: I hope that someone will be interested in this topic and will reasonably write about the pitfalls that I encountered using lettuce + selenium. I will be glad to answer your questions.

PPS: I apologize in advance for the style of presentation, punctuation and in general. The first article and all that ...

Also popular now: