I've seen a lot of "nestable" For loops in Robot Framework, mostly creating a keyword with a For loop inside, then calling that keyword within another For Loop. I made a nestable For Loop using Python 2.7.13, but because it primarily uses Run Keywords syntax I couldn't create a variable using Robot Framework-style syntax (e.g. ${variable_name}= My Keyword). For the record, this is Run Keywords under the BuiltIn Robot Framework library, which uses the following syntax:
Run Keywords Keyword1 arg11 arg12 AND Keyword2 arg21 arg22
Equivalently, it can be written like this:
Run Keywords Keyword1 arg11 arg12
... AND Keyword2 arg21 arg22
It doesn't typically support variables being created within it. But, I used Run Keywords as a part of the nestable For Loop. Here's the Python code for that keyword.
from robot.libraries.BuiltIn import BuiltIn
class Loops(object):
def __init__(self):
self.selenium_lib = BuiltIn().get_library_instance('ExtendedSelenium2Library')
self.internal_variables = {}
def for_loop(self, loop_type, start, end, index_var, *keywords):
# Format the keywords
keywords = self._format_loop(*keywords)
# Clean out the internal variables from previous iterations
self.internal_variables = {}
# This is the actual looping part
for loop_iteration in range(int(start), int(end)):
keyword_set = self._index_var_swap(loop_iteration, index_var, *keywords)
# If it's a one-keyword list with no arguments, then I can use the fastest possible keyword to run it
if len(keyword_set) == 1:
BuiltIn().run_keyword(keyword_set)
# If it's a one-keyword list with arguments, then I can use a faster keyword to run it
elif 'AND' not in keyword_set:
BuiltIn().run_keyword(*keyword_set)
# If it's a multiple-keyword list, then I have to use Run Keywords
else:
BuiltIn().run_keywords(*keyword_set)
def _format_loop(self, *keywords):
keywords = list(keywords) # I need to format the keywords as a list.
changed = False # Whether or not I changed anything in the previous iteration.
index = 0 # The item index I'm at in the list of keywords
del_list = [] # The list of items I need to delete
swap_list = [] # The list of items i need to swap to AND for the use of Run Keywords
# For each argument
for x in keywords:
# Format it to a string
x = str(x)
# If the keyword in question happens to be one of the 'Assign Internal Variable' keywords, then I need
# to run it now, not later.
# By splitting it up, I add a little complexity to the code but speed up execution when you're just
# assigning a scalar variable as opposed to having to search through the next few items just to find
# what I know is just going to be the next one.
# So, if it's the simple assignment...
if x.lower() == 'assign internal variable':
# ...run the Assign Internal Variable keyword with the two inputs
BuiltIn().run_keyword(x, *keywords[int(index)+1:int(index)+3])
# If it's the more complicated variable...
elif x.lower() == 'assign internal variable to keyword':
# ...initialize variables...
deliminator_search = 0
k_check = x
# ...search the next few keywords for a deliminator...
while k_check != '\\' and k_check != '\\\\':
deliminator_search = deliminator_search + 1
k_check = keywords[int(index)+deliminator_search]
# ...and run the Assign Internal Variable to Keyword keyword with the found keyword
BuiltIn().run_keyword(x, *keywords[int(index)+1:int(index)+2+deliminator_search])
# If the previous element was not changed...
if not changed:
# If the current item is not the last one on the list...
if x != len(keywords) - 1:
# If the current item is a deliminator...
if x == '\\':
# If the next item is a deliminator, delete this item and set changed to True
if keywords[int(index) + 1] == '\\':
del_list.append(index)
changed = True
# If the next item is not a deliminator...
else:
# If this isn't the first deliminator on the list, swap it to an 'AND'
if index != 0:
swap_list.append(index)
changed = True
# If this deliminator is in position index=0, just delete it
else:
del_list.append(index)
changed = True
# If the current element is not a deliminator, then I don't need to touch anything.
# If the current element is the last one, then I don't need to touch anything
# If the previous element was changed, then I don't need to "change" this one...
elif changed:
changed = False
# ...but if it's a deliminator then I do need to set it up for the inner for loop it means.
if keywords[index] == '\\':
keywords[index] = '\\\\'
index = index + 1 # Advance the index
# These actually do the swapping and deleting
for thing in swap_list:
keywords[thing] = 'AND'
del_list.reverse()
for item in del_list:
del keywords[item]
# I also need to activate my variables for this set of keywords to run.
keywords = self._activate_variables(*keywords)
return keywords
@staticmethod
def _index_var_swap(loop_iteration, index_var, *keywords):
# Format the keywords as a list for iteration
keywords = list(keywords)
index = 0
# For every line in keywords
for line in keywords:
# Replace all instances of the index_var in the string with the loop iteration as a string
keywords[index] = str(line).replace(str(index_var), str(loop_iteration))
index = index + 1
return keywords
def assign_internal_variable(self, variable_name, assignment):
# This keyword works like any other keyword so that it can be activated by BuiltIn.run_keywords
# The syntax for an internal variable is '{{varName}}' where varName can be anything
self.internal_variables[variable_name] = assignment
def assign_internal_variable_to_keyword(self, variable_name, keyword, *assignment):
# This keyword works like any other keyword so that it can be activated by BuiltIn.run_keywords
# The syntax for an internal variable is '{{varName}}' where varName can be anything
self.internal_variables[variable_name] = BuiltIn.run_keyword(keyword, *assignment)
def _activate_variables(self, *keywords):
# Initialize variables
keywords = list(keywords) # Cast keywords as a List
index = 0 # The index of the keyword I'm looking at
# For each keyword
for keyword in keywords:
keyword = str(keyword) # Cast keyword as a String
assignment = False # Whether or not the found variable name is in a variable assignment
for key in self.internal_variables.keys():
key = str(key) # Cast key as a String
# If I can find the key in the keyword and it's not an assignment...
if keyword.find(key) > -1 and not assignment:
# ...replace the text of the key in the keyword.
keywords[index] = keyword.replace(str(key), str(self.internal_variables[key]))
# If the keyword I'm looking at is an assignment...
if keyword.lower() == 'assign internal variable'\
and keyword.lower() != 'assign internal variable to keyword':
# ...then my next keyword is going to definitely be a known variable, so I don't want to touch it.
assignment = True
# If the keyword I'm looking at is not an assignment...
else:
# ...set assignment to False just in case the previous one happened to be an assignment.
assignment = False
index = index + 1 # Advance the index
return keywords # Return the list of keywords to be used in the format loop
As you can see, my workaround is to create a new keyword called Assign Internal Variable and its partner Assign Internal Variable to Keyword. However, this changes the syntax of a Robot Framework loop a little too significantly for my liking, and is somewhat limited in that the internal variables are completely separate from the outside variables. An example of this keyword in action in a Robot Framework test case is as follows:
*** Variables ***
${gold_squadron} = Gold
${red_squadron} = Red
*** Test Cases ***
Test For Loop
For Loop IN RANGE 0 1 INDEX0
... \\ For Loop IN RANGE 1 6 INDEX1
... \\ \\ Assign Internal Variable {{standing_by}} Standing By Red Leader
... \\ \\ Run Keyword If INDEX1 == 1 Log to Console ${red_squadron} Leader Standing By
... \\ \\ Run Keyword Unless INDEX1 == 1 Log to Console ${red_squadron} INDEX1 {{standing_by}}
... \\ For Loop IN RANGE 1 6 INDEX2
... \\ \\ Assign Internal Variable {{standing_by_2}} Standing By Gold Leader
... \\ \\ Run Keyword If INDEX2 == 1 Log to Console ${gold_squadron} Leader Standing By
... \\ \\ Run Keyword Unless INDEX2 == 1 Log to Console ${gold_squadron} INDEX2 {{standing_by_2}}
That loop works as intended, assuming you have imported the Loops.py python file as a Library correctly. The syntax I'm looking for, however, is something along the lines of the following:
*** Variables ***
${gold_squadron} = Gold
${red_squadron} = Red
*** Test Cases ***
Test For Loop
For Loop IN RANGE 0 1 INDEX0
... \\ For Loop IN RANGE 1 6 INDEX1
... \\ \\ {standing_by}= Standing By Red Leader
... \\ \\ Run Keyword If INDEX1 == 1 Log to Console ${red_squadron} Leader Standing By
... \\ \\ Run Keyword Unless INDEX1 == 1 Log to Console ${red_squadron} INDEX1 {{standing_by}}
... \\ For Loop IN RANGE 1 6 INDEX2
... \\ \\ {standing_by_2}= Standing By Gold Leader
... \\ \\ Run Keyword If INDEX2 == 1 Log to Console ${gold_squadron} Leader Standing By
... \\ \\ Run Keyword Unless INDEX2 == 1 Log to Console ${gold_squadron} INDEX2 {{standing_by_2}}
For anyone who doesn't feel like tearing into the base code of Robot Framework (not recommended, it's painful), the reason For Loops aren't typically nestable in Robot Framework is because at the basic level, Keywords and For Loops are two completely different objects. Some keywords are coded so that they can use other keywords (like "Run Keyword"), but For Loops are not coded that way. If someone can figure out a way to change up the syntax of my For Loop, that would make the end result a lot more intuitive to use for someone just coming from Robot Framework.
Just to be clear, if it wasn't clear from the examples, I CAN use Robot Framework variables from outside the Test Case and For Loop just fine. I'm asking about creating them in the For Loop itself.
FORsyntax replaces the ugly\\with anENDreserved word. I wish that the... Ifkeywords would do the same, so it could be easier to assign vars in their blocks. For now, I've been usingSet Test Variable--though that only works with right-hand-side expressions, not function/keyword calls.