3. Forms and Tester Feedback Tutorial

Using Custom Forms

Introduction

Customizable forms allows spintop-openhtf developers to include complex form inputs in their test plans in order to interact with the test operators using simple dictionnary definitions. For example, the following form shows an input field, allowing the tester to enter the measured impedance.

FORM_LAYOUT = {
    'schema':{
        'title': "Impedance",
        'type': "object",
        'required': ["impedance"],
        'properties': {
            'impedance': {
                'type': "string",
                'title': "Measure Impedance on test point X\nEnter value in Ohms"
            },
        }
    },
    'layout':[
        "impedance"
    ]
}

When executed the above form entry will result in the following being displayed on the web interface.

Normal Form

This FORM_LAYOUT variable contains two top level attributes which are essential to differentiate: schema and layout. The schema defines the fields while layout defines the ordering and display of these fields.

JSON Schema Forms

The schema part is actually a generic JSON schema vocabulary used to validate JSON documents named *JSON Schema*.

As quoted from their homepage,

JSON Schema is a vocabulary that allows you to annotate and validate JSON documents.

Example Schema

The format allows you to build the schema of complex JSON structures and afterward validate that a certain JSON document respects or not that structure. Let’s take the schema part of our previous example:

{
    'title': "Impedance",
    'type': "object",
    'required': ["impedance"],
    'properties': {
        'impedance': {
            'type': "string",
            'title': "Measure Impedance on test point X\nEnter value in Ohms"
        },
    }
}

In order, this defines that:

  • "title": "Impedance": The title of this form is ‘Impedance’.

  • "type": "object": The type of the top level JSON object is object, which means that it contains other properties.

  • "required": ["impedance]: The property named `impedance is required.

  • "properties": {: Begins the list of properties in this object. Note that these are unordered.

  • "impedance": { "type": "string",  "title": "Measure Impedance on test point X\nEnter value in Ohms"     }

    The property named impedance is a string with as label the instructions passed to the operator : ‘Measure Impedance on test point XnEnter value in Ohms’

  • And so on.

Note

Of all the keys shown here, only required is specific to JSON Schema Forms. The rest is part of the JSON Schema format. You can use the following playground to experiment with the forms: JSON Schema Form. However, the renderer is not the same that spintop-openhtf internally uses and therefore results may vary. You can use the getting started example in the spintop-openhtf repo for a quick demo with forms.

To add the custom form to the test bench defined previously in the Running a First Test Bench tutorial, first insert the FORM_LAYOUT definition at the top of the main.py file, then modify the test case definition to use the new custom form prompts as shown below.

@plan.testcase('Hello-Test')
@plan.plug(prompts=UserInput)
def hello_world(test, prompts):
    """Displays the custom from defined above"""
    prompts.prompt_form(FORM_LAYOUT)

Run the testbench again to see the new form appear.

Tutorial source

Extracting Data from the Custom Forms Responses

The custom forms output a response in the form of a dictionary when the form display returns. The response dictionary is returned once the operator hits the Okay button.

Normal Form

To gather the response from the form, simply create a ‘response’ variable to receive the prompt return, such as illustrated below.

@plan.testcase('Hello-Test')
@plan.plug(prompts=UserInput)
def hello_world(test, prompts):
    """Displays the custom from defined above"""
    response = prompts.prompt_form(FORM_LAYOUT)

In the case where the operator enters a value of 1200 Ohms as impedance, the dictionary returned by the will be the following:

{'impedance': '1200'}

To access the returned value, simply index the dictionary with the key used in the form definition:

test.impedance = response['impedance']

To define different types of custom forms to receive types different responses, refer to the Form Reference article.

Tutorial source

Form Reference

This page lists the different form types that can be used in a spintop-openhtf test bench.

Example Data

The previous form would then successfully validate the following JSON Data:

{
  "firstname": "foo",
  "lastname": "bar"
}

This is the dictionnary that is returned when you call UserInput.prompt_form(...).

Layout

The layout aspect of our previous example is specific to JSON Schema Forms, and, more specifically, to the renderer we use.

Select Choices (Dropdown)

If we re-use the previous form and wish to limit the values allowed for a specific string field, we can use the layout attribute to impose a select field.

In the following snippet, the simple lastname key is replaced by a complex object which identifies the field using the "key": "lastname" attribute. By adding the "type": "select" with the titleMap, we impose specific choices to the user.

This does not make much sense in the case of a last name, but we use the same example for consistency.

FORM_LAYOUT = {
    'schema':{
        'title': "First and Last Name",
        'type': "object",
        'required': ["firstname", "lastname"],
        'properties': {
            'firstname': {
                'type': "string",
                'title': "First Name"
            },
            'lastname': {
                'type': "string",
                'title': "Last Name"
            },
        }
    },
    'layout':[
        "firstname",
        {
            "key": "lastname",
            "type": "select",
            "titleMap": [
                { "value": "Andersson", "name": "Andersson" },
                { "value": "Johansson", "name": "Johansson" },
                { "value": "other", "name": "Something else..."}
            ]
        }
    ]
}
Select Form

Radio Buttons

Same example with lastname:

FORM_LAYOUT = {
    'schema':{
        'title': "First and Last Name",
        'type': "object",
        'required': ["firstname", "lastname"],
        'properties': {
            'firstname': {
                'type': "string",
                'title': "First Name"
            },
            'lastname': {
                'type': "string",
                'title': "Last Name"
            },
        }
    },
    'layout':[
        "firstname",
        {
            "key": "lastname",
            "type": "radiobuttons",
            "titleMap": [
                { "value": "one", "name": "One" },
                { "value": "two", "name": "More..." }
            ]
        }
    ]
}
Radiobuttons Form

Text

Adding text within the form is very useful to guide or otherwise give more information to the user. This can be done using the "type": "help" layout.

Note

The markdown function was added in spintop-openhtf version 0.5.5. It transforms the text into HTML, which is the only understood format of the helpvalue.

from spintop_openhtf.util.markdown import markdown

FORM_LAYOUT = {
    'schema':{
        'title': "First and Last Name",
        'type': "object",
        'required': ["firstname", "lastname"],
        'properties': {
            'firstname': {
                'type': "string",
                'title': "First Name"
            },
            'lastname': {
                'type': "string",
                'title': "Last Name"
            },
        }
    },
    'layout':[
        "firstname",
        {
            "type": "help",
            "helpvalue": markdown("# Well Hello There!")
        },
        "lastname
    ]
}
Text Form

Images

To seamlessly serve one or more image in your custom form or prompt message, the test plan image_url method needs to be used. This will create a temporary url that points to the local file you are targeting and allow browsers to load this image successfully.

Warning

The url returned by image_url is strictly temporary. It represents an in-memory mapping between the url and the filepath you specified. It follows the lifecycle of the TestPlan object, which means that as long as you keep the same test plan object, the url will live.

There are no cleanup mecanisms. However, each image is a simple key: value entry in a dictionnary, which means that its memory footprint is negligible.

from spintop_openhtf.util.markdown import markdown, image_url

plan = TestPlan('examples.getting_started')

helpvalue = markdown("""

# Well Hello There
<img src="%s" width="200px" />

""" % plan.image_url('spinhub-app-icon.png'))


FORM_LAYOUT = {
    'schema':{
        'title': "First and Last Name",
        'type': "object",
        'required': ["firstname", "lastname"],
        'properties': {
            'firstname': {
                'type': "string",
                'title': "First Name"
            },
            'lastname': {
                'type': "string",
                'title': "Last Name"
            },
        }
    },
    'layout':[
        "firstname",
        {
            "type": "help",
            "helpvalue": helpvalue
        },
        {
            "key": "lastname",
            "type": "radiobuttons",
            "titleMap": [
                { "value": "one", "name": "One" },
                { "value": "two", "name": "More..." }
            ]
        }
    ]
}
Image Form