1
votes

I have a following example keyword in my custom Robot Framework library that uses Robot Framework's BuiltIn library to call another keyword inside the test sequence using parameters:

# MyLibrary.py
from robot.libraries.BuiltIn import BuiltIn

class MyLibrary(object):
    def run_a_keyword(self, keywordName):
        builtinLib = BuiltIn().get_library_instance("BuiltIn")
        parameters = ['hi', 'second param']
        # Runs the keyword with the given parameters
        builtinLib.run_keyword(keywordName, *parameters)

I would be running for example a following simplified test to check that the keyword works property by testing the errors as well:

*** Settings ***
Library MyLibrary.py

*** Test Cases ***
Test Case
    Run A Keyword   My Keyword
    Run Keyword And Expect Error    Keyword 'Another Keyword' expected 3 arguments, got 2.  Run A Keyword   Another Keyword

*** Keywords ***
My Keyword
    [Arguments] ${arg1} ${arg2}
    Log To Console  Keyword Executed 

Another Keyword
    [Arguments] ${arg1} ${arg2} ${arg3}
    Log To Console  Keyword Executed

I would expect that this test case would pass, but the test case fails in the second step with Run Keyword and Expect Error despite I had stated that the error was expected?

My workaround for this was to catch the exception thrown by builtInLib's call inside my keyword and re-throw it, after this the test sequence with Run Keyword And Expect Error works properly:

def run_a_keyword(self, keywordName):
        builtinLib = BuiltIn().get_library_instance("BuiltIn")
        parameters = ['hi', 'second param']
        try:
            builtinLib.run_keyword(keywordName, *parameters)
        except Exception as err:
            raise Exception(err.message)

However, I need a listener to pass the errors to a service:

class MyListener(object):
    ROBOT_LISTENER_API_VERSION = 2
    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self):
        self.ROBOT_LIBRARY_LISTENER = self

    def log_message(self, message):
        level = message['level']
        if level == 'FAIL':
            send_the_error_to_somewhere(message['message'])

The log_message is called twice (when the built-in function is called and when I raise the new exception). This causes that the same error would be handled and logged twice which is not what I want.

So: How can I Run Keyword And Expect Error with a keyword that calls a built in function and still handle the error only once?

1

1 Answers

2
votes

First and foremost, the Run Keyword And Expect Error does not handle syntax errors - see its documentation, it clearly states it at the end, and also its implementation - this error is in the dont_continue category.
You are passing 2 arguments to a keyword that has 3 required parameters, which is clearly a syntax error.


Why is log_message called/storing the error twice? Because two exceptions are raised - one in the RF's builtin keyword, and then when you reraise it in your custom one. The framework logs on fatal level in every exception, thus you get the 2.

The first solution that comes to my mind is to communicate with your log handler through the message content itself.
The most optimal solutuon world have been modifying the exception, say adding an attribute 'producer' with a set value to it, and checking in the handler hasattr(err, 'producer'), but it does not have access to the exception anymore, just to the message text. Prefix the error message with a special identifier:

except Exception as err:
            raise Exception("SPECIAL_IDENTIFIER:{}".format(err.message))

, and then send_the_error_to_somewhere only if it has it:

if level == 'FAIL' and message['message'].startswith('SPECIAL_IDENTIFIER:'):
            # get rid of the special identifier prefix, and then
            send_the_error_to_somewhere(message['message'])