Skip to main content

pytest plugin that let you play a json file describing some actions and assertions. Supports by default Selenium/Splinter actions

Project description

See Build Status on Travis CI Documentation Status https://codecov.io/gh/tierratelematics/pytest-play/branch/develop/graph/badge.svg

pytest-play is a pytest plugin that let you play a json file describing some actions and assertions. You can extend pytest-play with your own commands thanks to its pluggable architecture and by default it supports browser interactions. For example it can be used for running previously recorded selenium splinter actions driving your browser for your UI test.

pytest-play is also your friend when page object approach (considered best practice) is not possible. For example:

  • limited time, and/or

  • lack of programming skills

Instead if you are interested in a page object pattern have a look at pypom_form or pypom.

pytest-play supports automatic waiting that should help to keep your tests more reliable with implicit waits before moving on. By default it waits for node availability and visibility but it supports also some wait commands and wait until a given Javascript expression is ok. So it is at the same time user friendly and flexible.

How it works

Given a json file (eg: login.json):

{
    "steps": [
            {
                    "type": "get",
                    "url": "$base_url"
            },
            {
                    "type": "setElementText",
                    "locator": {
                            "type": "css",
                            "value": "input[name=\"email\"]"
                    },
                    "text": "$root_name"
            },
            {
                    "type": "setElementText",
                    "locator": {
                            "type": "css",
                            "value": "input[name=\"password\"]"
                    },
                    "text": "$root_pwd"
            },
            {
                    "type": "clickElement",
                    "locator": {
                            "type": "css",
                            "value": ".label-submit"
                    }
            },
            {
                    "type": "waitForElementPresent",
                    "locator": {
                            "type": "css",
                            "value": ".logged"
                    }
            },
            {
                    "type": "assertElementPresent",
                    "locator": {
                            "type": "css",
                            "value": ".user-info"
                    }
            }
    ]
}

you define a test test_login.py like this:

def test_login(play_json, data_getter):
    data = data_getter('/my/path/etc', 'login.json')
    play_json.execute(data)

you get things moving on your browser!

Commands syntax

Project status is pre-alpha so commands could change and the following list will be extended.

Some useful commands is missing, for example:

  • url assertions

  • text in page

  • interaction with other input elements like radio buttons

Conditional commands

{
  "type": "clickElement",
  "locator": {
       "type": "css",
       "value": "body"
       },
  "condition": "'$foo' === 'bar'"
}

Supported locators

Supported selector types:

  • css

  • xpath

  • tag

  • name

  • text

  • id

  • value

Open a page

With parametrization:

{
  "type": "get",
  "url": "$base_url"
}

or with a regular url:

{
  "type": "get",
  "url": "https://google.com"
}

Pause

{
  "type": "pause",
  "waitTime": 1500
}

Click an element

{
  "type": "clickElement",
  "locator": {
       "type": "css",
       "value": "body"
       }
}

Fill in a text

{
  "type": "setElementText",
  "locator": {
     "type": "css",
     "value": "input.title"
     },
  "text": "text value"
}

Interact with select input elements

Select by label:

{
  "type": "select",
  "locator": {
       "type": "css",
       "value": "select.city"
  },
  "text": "Turin"
}

or select by value:

{
  "type": "select",
  "locator": {
       "type": "css",
       "value": "select.city"
  },
  "value": "1"
}

Eval a Javascript expression

{
  "type": "eval",
  "script": "alert("Hello world!")"
}

Create a variable starting from a Javascript expression

The value of the Javascript expression will be stored in pytest_play.variables under the name count:

{
  "type": "storeEval",
  "variable": "count",
  "script": "document.getElementById('count')[0].textContent"
}

Assert if a Javascript expression matches

If the result of the expression does not match an AssertionError will be raised and the test will fail:

{
  "type": "verifyEval",
  "value": "3",
  "script": "document.getElementById('count')[0].textContent"
}

Verify that the text of one element contains a string

If the element text does not contain the provided text an AssertionError will be raised and the test will fail:

{
  "type": "verifyText",
  "locator": {
     "type": "css",
     "value": ".my-item"
  },
  "text": "a text"
}

Send keys to an element

All selenium.webdriver.common.keys.Keys are supported:

{
  "type": "sendKeysToElement",
  "locator": {
     "type": "css",
     "value": ".confirm"
  },
  "text": "ENTER"
}

Supported keys:

KEYS = [
    'ADD', 'ALT', 'ARROW_DOWN', 'ARROW_LEFT', 'ARROW_RIGHT',
    'ARROW_UP', 'BACKSPACE', 'BACK_SPACE', 'CANCEL', 'CLEAR',
    'COMMAND', 'CONTROL', 'DECIMAL', 'DELETE', 'DIVIDE',
    'DOWN', 'END', 'ENTER', 'EQUALS', 'ESCAPE', 'F1', 'F10',
    'F11', 'F12', 'F2', 'F3', 'F4', 'F5', 'F6', 'F7', 'F8',
    'F9', 'HELP', 'HOME', 'INSERT', 'LEFT', 'LEFT_ALT',
    'LEFT_CONTROL', 'LEFT_SHIFT', 'META', 'MULTIPLY',
    'NULL', 'NUMPAD0', 'NUMPAD1', 'NUMPAD2', 'NUMPAD3',
    'NUMPAD4', 'NUMPAD5', 'NUMPAD6', 'NUMPAD7', 'NUMPAD8',
    'NUMPAD9', 'PAGE_DOWN', 'PAGE_UP', 'PAUSE', 'RETURN',
    'RIGHT', 'SEMICOLON', 'SEPARATOR', 'SHIFT', 'SPACE',
    'SUBTRACT', 'TAB', 'UP',
]

Wait until a Javascript expression matches

Wait until the given expression matches or raise a selenium.common.exceptions.TimeoutException if takes too time.

At this time of writing there is a global timeout (20s) but in future releases you will be able to override it on command basis:

{
  "type": "waitUntilCondition",
  "script": "document.body.getAttribute("class") === 'ready'"
}

Wait for element present in DOM

Present:

{
  "type": "waitForElementPresent",
  "locator": {
     "type": "css",
     "value": "body"
  }
}

or not present:

{
  "type": "waitForElementPresent",
  "locator": {
     "type": "css",
     "value": "body"
  },
  "negated": true
}

Wait for element visible

Visible:

{
  "type": "waitForElementVisible",
  "locator": {
     "type": "css",
     "value": "body"
  }
}

or not visible:

{
  "type": "waitForElementVisible",
  "locator": {
     "type": "css",
     "value": "body"
  },
  "negated": true
}

Assert element is present in DOM

An AssertionError will be raised if assertion fails.

Present:

{
  "type": "assertElementPresent",
  "locator": {
     "type": "css",
     "value": "div.elem"
     }
}

or not present:

{
  "type": "assertElementPresent",
  "locator": {
     "type": "css",
     "value": "div.elem"
     },
  "negated": true
}

Assert element is visible

An AssertionError will be raised if assertion fails.

Present:

{
  "type": "assertElementVisible",
  "locator": {
     "type": "css",
     "value": "div.elem"
     }
}

or not present:

{
  "type": "assertElementVisible",
  "locator": {
     "type": "css",
     "value": "div.elem"
     },
  "negated": true
}

How to reuse steps

You can split your commands and reuse them using the include command avoiding duplication:

{
    "steps": [
        {"provider": "included-scenario.json", "type": "include"},
        ... other commands ...
    ]
}

registering included-scenario.json’s contents as follows:

@pytest.fixture(autouse=True)
def included_scenario(play_json, data_getter, data_base_path):
    data = data_getter(data_base_path, 'included-scenario.json')
    play_json.register_steps(
        data, 'included-scenario.json')

This way other json files will be able to include the included-scenario.json file.

How to install pytest-play

You can see pytest-play in action creating a pytest project using the cookiecutter-qa scaffolding tool:

This is the easiest way, otherwise you’ll need to setup a pytest project by your own and install pytest-play.

pytest-play is pluggable and extensible

pytest-play has a pluggable architecture and you can extend it.

For example you might want to support your own commands, support non UI commands like making raw POST/GET/etc calls, simulate IoT devices activities, provide easy interaction with complex UI widgets like calendar widgets and so on.

How to register a new command provider

Let’s suppose you want to extend pytest-play with the following command:

command = {'type': 'print', 'provider': 'newprovider', 'message': 'Hello, World!'}

You just have to implement a command provider:

class NewProvider(object):
    def __init__(self, engine):
        self.engine = engine

    def this_is_not_a_command(self):
        """ Commands should be command_ prefixed """

    def command_print(self, command):
        print(command['message'])

    def command_yetAnotherCommand(self, command):
        print(command)

and register your new provider in your setup.py adding an entrypoint:

entry_points={
    'playcommands': [
        'print = your_package.providers:NewProvider',
    ],
},

You can define new providers also for non UI commands. For example publish MQTT messages simulating IoT device activities for integration tests.

If you want you can generate a new command provider thanks to:

Third party pytest-play plugins

  • play_mqtt, pytest-play plugin for MQTT support. Thanks to play_mqtt you can test the integration between a mocked IoT device that sends commands on MQTT and a reactive web application with UI checks.

    You can also build a simulator that generates messages for you.

Feel free to add your own public plugins with a pull request!

Twitter

pytest-play tweets happens here:

Changelog

0.3.0 (2018-01-04)

  • you are able to update variables when executing commands

  • you can extend pytest-play with new pluggable commands coming from third party packages thanks to setuptools entrypoints

0.2.0 (2018-01-02)

  • no more open browser by default pytest-play is a generic test engine and it could be used for non UI tests too.

    So there is no need to open the browser for non UI tests (eg: API tests)

0.1.0 (2017-12-22)

  • implement reusable steps (include scenario)

  • minor documentation changes

0.0.1 (2017-12-20)

  • First release

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pytest-play-0.3.0.tar.gz (18.3 kB view hashes)

Uploaded Source

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page