How Robot Framework evaluates keywords with embedded arguments
Let's look at how Robot Framework takes a statement like this:
Add 3 and 4
And turns it into a function call like this:
add('1', '2')
Take an Arithmetic
library that provides a few
keywords with embedded arguments:
from robot.api import deco
class Arithmetic:
@deco.keyword("Add ${first} and ${second}")
def add(self, first: str, second: str) -> float:
return float(first) + float(second)
@deco.keyword("Subtract ${first} from ${second}")
def subtract(self, first: str, second: str) -> float:
return float(second) - float(first)
@deco.keyword("Multiply ${first} and ${second}")
def multiply(self, first: str, second: str) -> float:
return float(second) - float(first)
At the beginnning of a test case, Robot Framework creates an instance of this class:
arithmetic = Arithmetic()
Robot Framework adds a robot_name
attribute to
keywords with embedded arguments:
print(arithmetic.add.robot_name)
We can do a little bit of magic with the inspect
library to find all methods in the Arithmetic
class
that have a robot_name
attribute. We'll store the
text of the keyword and the function associated with
the keyword in a namedtuple
so that we can retrieve
them later.
import inspect
from collections import namedtuple
Keyword = namedtuple("Keyword", ["name", "method"])
methods = [
i[1] for i in inspect.getmembers(arithmetic, inspect.ismethod)
]
keywords = [
Keyword(getattr(method, "robot_name"), method)
for method in methods
if hasattr(method, "robot_name")
]
for keyword in keywords:
print(f"Name: {keyword.name}")
print(f"Method: {keyword.method.__name__}\n")
Now, given a string like:
Add 1 and 2
How do we match that to one of our keywords? We can
substitute all instances of Robot Framework's variable
syntax (that is, everything around a ${}
) with a
(.*)
to create a regular expression that matches
any uses of the keyword. The .*
matches any string
in the variable's place and the ()
creates a capturing
group that we can use to retrieve the values being
passed as arguments.
import re
regex_keywords = []
for keyword in keywords:
# substitute all instances of ${} with (.*)
regex_name = re.sub("\$\{.+?\}", "(.*)", keyword.name)
print(regex_name)
regex_keyword = Keyword(regex_name, keyword.method)
regex_keywords.append(regex_keyword)
Given a string like:
Add 1 and 2
We can check for a match against each regular expression.
keyword_str = "Add 1 and 2"
matching_keywords = [
keyword
for keyword in regex_keywords
if re.fullmatch(keyword.name, keyword_str)
]
matching_keyword = matching_keywords[0]
print("Found regular expression match:")
print(matching_keywords[0].name)
Now that we have a match, all that's left is to pull the
values 1
and 2
out of the keyword so that we can call
the matching function. We've already captured them as regex
groups which can be retrieved from
Match.groups().
match = re.fullmatch(matching_keyword.name, keyword_str)
keyword_params = match.groups()
print(keyword_params)
And all that's left is to unpack these as parameters to the matching function!
matching_keyword.method(*keyword_params)