Is there a way to control how Robot parses the testcase?

Robot’s design seems to assume that everything must be a single line (unless preceded by "… "). FitNesse only read the testcase file until it read a “keyword”, and then allowed the keyword code (what they called a fixture) to consume as much of the testcase as it wanted. Which allowed you to create fixtures that could gobble up multiline data.

It seems that Robot parses the whole file first, into a token tree, and then executes from the tree.

I looked into the get_tokens() method, but it didn’t seem to report the error tokens. I was hoping I could “fix up” the model before executing it, but alas, I don’t think I can.

Ultimately, I would love to be able to create a testcase like below.

Does anyone know if/how I could hack into the token tree creation logic to allow a callback to the keyword class so that it could direct the parser how to handle the testcase data?
My thoughts are that if the parser doesn’t understand what the line is, instead of assuming an error, it could find the previous keyword and attempt a callback (would it know what library/class it came from?) to see if it should classify the line as “data”. But then I am not certain if that is the right direction to go.

*** Test Cases ***
TEST_POST
Post Request ${url}/resturi
Set Headers
Content-Type=application/json
Auth=Basic Y24zNTIzOjY2JCFCYWlsZXlMaW51cw
Set Body
{
“clusterInfo”: {
“tfwNumber”: “TX-9709-00”,
“clusterId”: 1,
“projectStatus”: “Initial”,
“tfwSpreadsheetURL”: “url”,
“tfwRequestURL”: “url”
},
“nodes”: [
{
“nodeNum”: 1,
“nodeId”: 119,
“totalCost”: 999,
“fiberConstDays”: 99,
“estimatedLoopLoss”: 9999,
“directNewReusable”: 99.99,
“directNewNonReusable”: 99.99,
“associatedNewReusable”: 99.99,
“associatedNewNonReusable”: 99.99
},
{
“clusterNodeId”: 1,
“nodeNum”: 2,
“nodeId”: 22,
“dropCost”: 888,
“totalCost”: 888,
“directNewReusable”: 99.99,
“directNewNonReusable”: 99.99,
“associatedNewReusable”: 99.99,
“associatedNewNonReusable”: 99.99
}
]
}
Expect
nodes.nodeId == 119
Done

Yes, I know you can use the continuation token, but then the developer has to prepend "… " in front of every line of data.
I want to create keywords that can parse the testcase itself, and pull the data directly from the file… and not have to assign it to a variable. Also, I know you can import data from a file, but that goes against our testcase design philosophy, a testcase must be “standalone”… and no rely on any outside data or configuration.

I know, I know… I am trying to stretch Robot in ways that it was never designed to go… but if it could, you could really increase the flexibility and power of the tool, without causing the developer to write huge amounts of Robot code.

FYI, the format of the rest function is:

Post Request
Set Headers
…data
Set Body
…data
Expect
…data
Done

If you want json data like that, why not just put it as a variable in a Python variable file and pass the variable to the request / keyword?

Robot Framework support variables and keywords, both in .robot / .resource files as well as in Python files. The headers and body can simply be variables defined in the format that’s most suited for their type of data (in this case I’d opt for Python dicts). There is no need to put everything in a single stretch of text.

FitNesse is much less advanced in this field, so wall-of-text solutions are often your only option. This is not the case with RF.

In your case, the .robot file could look something like this:

*** Settings ***
Resource    my_vars.py

*** Variables ***
&{headers}    Content-Type=application/json    Auth=Basic etc

*** Test Cases ***
Test Post
    Post Request    ${uri}/resturi
    Set Headers    ${headers}
    Set Body    ${body}
    Expect    nodes.nodeId == 119
    Done

And the variables file:

body = {
    “clusterInfo”: {
        “tfwNumber”: “TX-9709-00”,
        “clusterId”: 1,
        “projectStatus”: “Initial”,
        “tfwSpreadsheetURL”: “url”,
        “tfwRequestURL”: “url”
    },
    “nodes”: [
        {
            “nodeNum”: 1,
            “nodeId”: 119,
            “totalCost”: 999,
            “fiberConstDays”: 99,
            “estimatedLoopLoss”: 9999,
            “directNewReusable”: 99.99,
            “directNewNonReusable”: 99.99,
            “associatedNewReusable”: 99.99,
            “associatedNewNonReusable”: 99.99
        },
        {
            “clusterNodeId”: 1,
            “nodeNum”: 2,
            “nodeId”: 22,
            “dropCost”: 888,
            “totalCost”: 888,
            “directNewReusable”: 99.99,
            “directNewNonReusable”: 99.99,
            “associatedNewReusable”: 99.99,
            “associatedNewNonReusable”: 99.99
         }
    ]
}

Thank you for the response. Unfortunately, we have a design principle where ALL data must be in the testcase.

If that is the only solution, we will do so, but I was hoping to embed the data in the test case.

Look at the release planning for the new robot version 6.1 > v6.1 Milestone · GitHub

there is a new feature > Possibility to use custom parser for test data · Issue #1283 · robotframework/robotframework · GitHub

maybe this is one solution, but you have to wait a little bit :wink:

Another approach I used in a customer project (but unfortunately don’t have the source code anymore)

I misused the *** Comment *** section to store test data as Python code.
Then I wrote a listener that reads this comment section at suite start and makes it available as normal robot variables. These robot variables can then be used in the test cases.

It wasn’t really complicated, just a few lines of code.

1 Like

that looked something like this:

*** Settings ***
Library    OperatingSystem

*** Comments ***
--- python ---

json_data_1 = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3",
}

json_data_2 = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3",
}

*** Variables ***
${json_data_1}=    ${empty}
${json_data_2}=    ${empty}


*** Test Cases ***
first
    Log    ${json_data_1}
    Log    ${json_data_2}

Thanks, that looks interesting.

For the moment, we are going with the continuation “keyword” (…) and then parsing the array of strings in the keywords.

For Exampple:

PostTo ${URL}
… {
… “clusterInfo”: {
… “tfwNumber”: “TX-9709-00”,
… “clusterId”: 1,
… “projectStatus”: “Initial”,
… “tfwSpreadsheetURL”: “url”,
… “tfwRequestURL”: “url”
… }
Expect
… Result.*OK

And have the PostTo keyword catenate the array of strings, and save it in a suite variable.
The Expect keyword would capture a list of regex to compare the results to, and then finally execute the actual post command.

We then hope to create an extension to Notepad++ that would allow a dev to add/remove “\t…\t” at the beginning of all of the selected lines so they could easily paste in relevant data.

But yes, it would be great if we didn’t have to do that :wink:

Hi Chris,

FYI your JSON data in the post message above was missing a closing } so I guessed where it should go, but I might be wrong?

Here’s a suggestion that might work for you, when the dev’s add new lines to the JSON data they’ll still need to prefix with the triple dots (…) but other than that they can leave their formatting (tabs, spaces, etc).

*** Test Cases ***
JSON data as string

 	@{Jlist}= 	Set Variable
	... 	{
	... 		“clusterInfo”: {
	... 			“tfwNumber”: “TX-9709-00”,
	... 			“clusterId”: 1,
	... 			“projectStatus”: “Initial”,
	... 			“tfwSpreadsheetURL”: “url”,
	... 			“tfwRequestURL”: “url”
	... 		}
	... 	}
	Log 	${Jlist}
	${JString}= 	Evaluate 	"".join(${Jlist})
	Log 	${JString}

Each line becomes a new list item in a list and the tabs are ignored, when then use the python string join function that is normally used for converting a list in to a delimited string, but in this case we use an empty string as the delimiter. Doing it like this means you won’t need to create an extension to Notepad++.

Here is the result of my example

Hope that helps,

Dave.

Unfortunately, we have a design principle where ALL data must be in the testcase.

If that principle is not helping you in this context, why maintain it? It sounds like a principle that makes you write extra code that’s an obscure workaround. Basically it reduces the maintainability / quality of your code base. Any design principle that does that should be reconsidered.

3 Likes

It does, thank you. I have implemented a keyword that reads the string array created from the continuation keyword "… ".

1 Like

I understand your objection, however, it seems odd that we should abandon a long used testing paradigm because the tool doesn’t allow us to do it.

As a programmer, I feel that we should be able to modify the tool instead of change our process.

Fortunately, with a lot of external help, we have created keywords that work (almost) exactly as I described.

We use the continuation feature to allow a tester/developer to paste in an example JSON object (and then an editor macro to prepend "… " to the front of those lines). As shown by damies13.

So we are able to work inside the tools limitations. It would be nice if we did not have to use the continuations, but since the underlying design of Robot is to parse each line FIRST, before evaluation, there is no getting around that.

1 Like

You should try RIDE. We have a Ctrl-Shift-J to allow a prettyfied editor for JSON inside a single cell.
(Also you could try RIDE testrunner with ANSI colors as you asked in other thread).

1 Like