Skip to content

List/dict syntax when using @{list} and &{dict} as embedded arguments #3218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions atest/robot/keywords/embedded_arguments.robot
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ Embedded Arguments as Variables
Check Keyword Data ${tc.kws[0]} User \${42} Selects \${EMPTY} From Webshop \${name}, \${item}
Check Keyword Data ${tc.kws[2]} User \${name} Selects \${SPACE * 10} From Webshop \${name}, \${item}

Embedded Arguments as Lists
Check Test Case ${TEST NAME}

Embedded Arguments as Dicts
Check Test Case ${TEST NAME}

Non-Existing Variable in Embedded Arguments
${tc} = Check Test Case ${TEST NAME}
Check Keyword Data ${tc.kws[0]} User \${non existing} Selects \${variables} From Webshop status=FAIL
Expand Down
13 changes: 13 additions & 0 deletions atest/testdata/keywords/embedded_arguments.robot
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*** Settings ***
Library Collections
Resource resources/embedded_args_in_uk_1.robot
Resource resources/embedded_args_in_uk_2.robot

Expand Down Expand Up @@ -38,6 +39,12 @@ Embedded Arguments as Variables
Should Be Equal ${name} ${42}
Should Be True ${item} == []

Embedded Arguments as Lists
Sublist ["foo", 53] is in [1, 53, "foo", 25, 36, 42, 24, "bar"]

Embedded Arguments as Dicts
Key x is in {"a": "b", "x": "y"}

Non-Existing Variable in Embedded Arguments
[Documentation] FAIL Variable '${non existing}' not found.
User ${non existing} Selects ${variables} From Webshop
Expand Down Expand Up @@ -280,3 +287,9 @@ It is totally ${same}

It is totally ${same}
Fail Not executed

Sublist @{sublist} is in @{list}
List Should Contain Sub List ${list} ${sublist}

Key ${key} is in &{dictionary}
Dictionary Should Contain Key ${dictionary} ${key}
20 changes: 20 additions & 0 deletions doc/userguide/src/CreatingTestData/CreatingUserKeywords.rst
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,26 @@ names. They are, however, case-insensitive like other keywords. For
example, the keyword in the example above could be used like
:name:`select x from list`, but not like :name:`Select x fromlist`.

Starting from Robot Framework 3.2, embedded arguments support literal
list/dict Python syntax as long as you use `@{list}`, `&{dict}`
respectively in keyword name.

.. sourcecode:: robotframework

*** Test Cases ***
Example
Select cat from ['dog', 'cat', 'rabbit']
Get cat from {'dog': 2, 'cat': 3, 'rabbit': 0}

*** Keywords ***
Select ${animal} from @{animal_list}
Open Page Pet Selection
Select Item From List ${animal_list} ${animal}

Get ${animal} from &{animal_dict}
Open Page Pet Selection
Get From Dictionary ${animal_dict} ${animal}

Embedded arguments do not support default values or variable number of
arguments like normal arguments do. Using variables when
calling these keywords is possible but that can reduce readability.
Expand Down
6 changes: 3 additions & 3 deletions src/robot/running/arguments/embedded.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
class EmbeddedArguments(object):

def __init__(self, name):
if '${' in name:
if '${' in name or '@{' in name or '&{' in name:
self.name, self.args = EmbeddedArgumentParser().parse(name)
else:
self.name, self.args = None, []
Expand All @@ -44,9 +44,9 @@ class EmbeddedArgumentParser(object):
def parse(self, string):
args = []
name_regexp = ['^']
for before, variable, string in VariableIterator(string, identifiers='$'):
for before, variable, string in VariableIterator(string, identifiers='$@&'):
name, pattern = self._get_name_and_pattern(variable[2:-1])
args.append(name)
args.append((variable[0], name))
name_regexp.extend([re.escape(before), '(%s)' % pattern])
name_regexp.extend([re.escape(string), '$'])
name = self._compile_regexp(name_regexp) if args else None
Expand Down
18 changes: 15 additions & 3 deletions src/robot/running/userkeywordrunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.

from ast import literal_eval
from itertools import chain

from robot.errors import (ExecutionFailed, ExecutionPassed, ExecutionStatus,
ExitForLoop, ContinueForLoop, DataError,
PassExecution, ReturnFromKeyword,
UserKeywordExecutionFailed, VariableError)
from robot.result import Keyword as KeywordResult
from robot.utils import getshortdoc, DotDict, prepr, split_tags_from_doc
from robot.utils import getshortdoc, DotDict, prepr, split_tags_from_doc, is_list_like, is_dict_like
from robot.variables import is_list_var, VariableAssignment

from .arguments import DefaultValue
Expand Down Expand Up @@ -227,14 +228,25 @@ def __init__(self, handler, name):
match = handler.embedded_name.match(name)
if not match:
raise ValueError('Does not match given name')
self.embedded_args = list(zip(handler.embedded_args, match.groups()))
self.embedded_args = []
for n, v in zip(handler.embedded_args, match.groups()):
self.embedded_args.append(n + (v,))

def _resolve_arguments(self, args, variables=None):
# Validates that no arguments given.
self.arguments.resolve(args, variables)
if not variables:
return []
return [(n, variables.replace_scalar(v)) for n, v in self.embedded_args]
return list(self._replace(variables))

def _replace(self, variables):
for var_type, name, value in self.embedded_args:
if var_type == '$':
yield name, variables.replace_scalar(value)
elif var_type == '@' and is_list_like(literal_eval(value)):
yield name, variables.replace_list(literal_eval(value))
elif var_type == '&' and is_dict_like(literal_eval(value)):
yield name, DotDict(literal_eval(value))

def _set_arguments(self, embedded_args, context):
variables = context.variables
Expand Down