Here's what's going on (generically) with my python-based test.
==============================================================================
***Settings***
Variables variable_file.py
Library library.py
***Test Cases***
A Test Case
myTest ${some string} [I want to pass an object here]
=============================================================================
At the end, I want to pass an actual class object which has been defined inside my variable file. I've looked far and wide without finding a way to pass anything beyond a string or a list. Does anyone know how?
By default, ${variable} are strings, but it can also contain objects.
And you can pass it to keywords as argument.
Take a simple example like that:
${mydict} = Create Dictionary a 1 b 2
=> Then you have ${mydict} = {'a': '1', 'b': '2'}
And then you can call a keyword with your object as argument. For example:
Dictionary Should Contain Key ${mydict} a
I am using that kind of thing to test a REST API that sends me JSON. I store JSON objects in Robot variable and check the results by exploring the content of the dict. I explained it a little bit in a blog post.
You can instantiate object variables in variable files as stated in User guide
Here is a generic example
Variable file:
#var_file.py:
class foo(object):
a = "foo"
b = "bar"
MY_VAR = foo()
MY_SECOND_VAR = foo
Test case file:
#var_file.txt
*** Test cases ***
log variables
log ${MY VAR.a}
log ${MY VAR.b}
Execute using:
pybot -V var_file.py var_file.txt
Related
I have several Robot Framework keywords that return a basic string.
#keyword
def keyword_one():
return 'one'
#keyword
def keyword_two():
return 'two'
In a robot test case, I try to build a list with this items, but I can't figure out how to do that is one line.
*** Test Cases ***
Test Case List
#{my_list}= Create List Keyword One Keywork Two
I tried several syntax but can't make it work.
Of course, something like below works (hardcoded values).
*** Test Cases ***
Test Case List
#{my_list}= Create List one two
Thanks for your help.
At the time that I write this, robot doesn't have the ability to call keywords inline, so what you want isn't directly possible.
You could write your own keyword to do this, however. The keyword can accept multiple keywords as arguments, and use run keyword from the built-in library to run the keyword.
For example, the following keyword definition creates a keyword that creates a list of results from multiple keywords:
Keyword written in python
If you want to try this out, name the file example.py
from robot.libraries.BuiltIn import BuiltIn
builtin = BuiltIn()
def create_list_from_keywords(*keywords):
result = []
for keyword in keywords:
result.append(builtin.run_keyword(keyword))
return result
Example test
*** Settings ***
Library example.py
*** Keywords ***
Keyword one
return from keyword one
Keyword two
return from keyword two
*** Test cases ***
Example
#{actual}= create list from keywords Keyword one Keyword two
#{expected}= create list one two
Should be equal ${actual} ${expected}
Robot-based keyword definition
If you're uncomfortable with python, here's a robot-based keyword definition:
Create list from keywords
[Arguments] #{keywords}
[Return] #{result}
#{result}= create list
FOR ${keyword} IN #{keywords}
${keyword result}= Run keyword ${keyword}
Append to list ${result} ${keyword result}
END
At the moment you are adding the keywords to the list, not the values returned from running those keywords
You would need to call the keywords to get the returned values and add them to the list e.g.
*** Test Cases ***
Test Case List
${keyword_one_val} Keyword One
${keyword_two_val} Keyword Two
#{my_list}= Create List ${keyword_one_val} ${keyword_two_val}
log to console ${my_list}
which outputs:
['one', 'two']
It is very common to have a keyword that tests a similar behaviour, with a slightly different semantic like so:
The ${element_a} (gets updated with reference|has a reference) to the ${element_b}
However I wasn't able to find this feature documented anywhere. I know other libraries for testing such as Cucumber support matching steps through regexp, and I imagine since robotframework support embedded arguments in keyword names this can also work in robot.
Is there a way to write a keyword once and re-use it in slightly different situations?
*** Keywords ***
The ${element_a} ${updated_or_has} to the ${element_b}
IF """${update_or_has}""" == 'gets updated with reference'
Do Actions For Updated
ELSE IF """${update_or_has}""" == 'has a reference'
Do Other Actions
ELSE
Fail Unsupported value
END
That's one way to do it, branch the execution based on what string was part of the keyword name.
Calling it The element_a gets updated with reference to the element_b will have the keyword take one path, while The element_y has a reference to the element_z - a different one.
By the way, you might reconsider changing the verbing slightly, as the framework will have a bit hard time distinguishing what substring goes in the variable ${element_a} and what in ${updated_or_has} - put a static word, that'll be a part of the keyword name & act as a separator b/n the two vars.
Based on the comments, the branching is not needed, just a confirmation the caller used one of the two possible/supported values. This can be done with this check:
Run Keyword If """${update_or_has}""".lower() not in ('gets updated with reference', 'has a reference', ) Fail Unsupported value
You can specify an argument with a regular expression. So, you could treat part you want to match against as if it was an argument.
From the official user guide, in a section titled Using custom regular expressions:
A custom embedded argument regular expression is defined after the base name of the argument so that the argument and the regexp are separated with a colon.
In your particular case it might look something like this:
The ${element_a} ${foo:gets updated with reference|has a reference} to the ${element_b}
${foo} will be a variable that has whatever that little part is, which you can just ignore.
Here is a working example:
*** Keywords ***
The ${element_a} ${foo:gets updated with reference|has a reference} to the ${element_b}
log the foo argument is '${foo}'
*** Test Cases ***
Example
The foo gets updated with reference to the bar
The bar has a reference to the foo
# verify that the keyword only matches those two variations
Run keyword and expect error
... No keyword with name 'The something blah blah to the yada' found.
... The something blah blah to the yada
Consider the following code:
In Utils.py:
#keyword
def get_compound_dictionary():
"""
https://docs.python.org/3/library/copy.html
An example compound dictionary
"""
return {'key1': 'value1', 'deep_dict': {'key2': 'value2'}}
In collection-library-tests.robot
*** Settings ***
Documentation A test suite utilizing all collection library keywords
Library Collections
Library Utils.py
# To run:
# robot --pythonpath Resources --noncritical failure-expected -d Results/ Tests/collection-
library-tests.robot
*** Test Cases ***
Use "Copy Dictionary" : Shallow Copy
${compound_python_dictionary} = get compound dictionary
&{shallow_copy} = Copy Dictionary ${compound_python_dictionary} deepcopy=False
# if we modify the contained objects (i.e. deep_dict) through the shallow_copy,
# the original compound_python_dictionary will see the changes in the contained objects
Set To Dictionary ${shallow_copy}[deep_dict] key2=modified
Log ${shallow_copy}
Log ${compound_python_dictionary}
Should Be Equal ${compound_python_dictionary}[deep_dict][key2] modified # fails, why?
The goal is stated in the test case as:
if we modify the contained objects (i.e. deep_dict) through the shallow_copy,
the original compound_python_dictionary will see the changes in the contained objects
Expected Result
Should Be Equal ${compound_python_dictionary}[deep_dict][key2] modified # passes
Observed Result
Note that I am using Robot FW version: Robot Framework 3.1.2 (Python
3.7.4 on linux)
Acc.to the documentation about Copy Dictionary:
The deepcopy argument controls should the returned dictionary be a
shallow or deep copy. By default returns a shallow copy, but that can be
changed by giving deepcopy a true value (see Boolean arguments). This > is a new option in Robot Framework 3.1.2. Earlier versions always
returned shallow copies.
Acc.to the documentation about Boolean Arguments:
Some keywords accept arguments that are handled as Boolean values true or false. If such an argument is given as a string, it is considered false if it is an empty string or equal to FALSE, NONE, NO, OFF or 0, case-insensitively. Other strings are considered true regardless their value.
Note also that i tried also deepcopy=${False}, which yielded the same observed result.
The problem is not with the RF keyword (it very seldom is, they have extensive UT), but with the way you call it, namely this argument:
deepcopy=False
You may be thinking you are passing a boolean value, but in fact you are passing the string "False".
Inside the keyword's implementation there is this branching:
if deepcopy:
return copy.deepcopy(dictionary)
, and as a non-empty string evaluates to True, you are in fact getting a deep copy.
This is the way to pass a real False:
deepcopy=${False}
I have a scenario in which I would have to read from a dictionary and create variables with key as the variable name and value as the variable's value.
For example, if "hello": "world" is a dictionary item, then I have to set hello = world. This variable also needs to be later called in a test case. I tried to achieve this via below method but not sure if it has the right syntax and anyway it fails with No keyword with name '${$k}} found error.
Can anyone let me know if this can be done?
*** Keywords ***
Spin Variables
${items}= get dictionary items ${arg}
:For ${k} ${v} IN #{items}
\ ${${k}}= set variable ${v}
I can call ^methods on an object and list the method names I can call:
my $object = 'Camelia';
my #object_methods = $object.^methods;
#object_methods.map( { .gist } ).sort.join("\n").say;
^methods returns a list which I store in #object_methods, then later I transform that list of method thingys by calling gist on each one to get the human-sensible form of that method thingy.
But, the ^ in ^methods is an implied .HOW, as show at the end of the object documentation this should work too:
my $object = 'Camelia';
my #object_methods = $object.HOW.methods;
But, I get an error:
Too few positionals passed; expected 2 arguments but got 1
in any methods at gen/moar/m-Metamodel.nqp line 490
in block <unit> at...
And, for what it's worth, this is an awful error message for a language that's trying to be person-friendly about that sort of thing. The file m-Metamodel.nqp isn't part of my perl6 installation. It's not even something I can google because, as the path suggests, it's something that a compilation generates. And, that compilation depends on the version.
A regular method call via . passes the invocant as implicit first argument to the method. A meta-method call via .^ passes two arguments: the meta-object as invocant, and the instance as first positional argument.
For example
$obj.^can('sqrt')
is syntactic sugar for
$obj.HOW.can($obj, 'sqrt')
In your example, this would read
my #object_methods = $object.HOW.methods($object);