Is there a Way to Instantiate Global Variables Dynamically? Page Object Model (POM)

Hi,

I want to instantiate global variables dynamically in Robot Framework, but I’ve encountered challenges with making this simple and user friendly, and getting my IDE to recognize these dynamically created variables for auto-completion for users to use them. Despite trying various methods, none seem to fully work:

Why is this needed?

I am working on testing a web application where the XPaths of web elements vary depending on the version of the product. Since this is an old, legacy application and I cannot modify how it operates, I need to test multiple versions of the product—some older and some newer. I retrieve the version of the web app automatically, and based on this, I need Robot Framework to instantiate variables accordingly.

For example:

  1. If you define global variables in the *** Variables *** section, you cannot use conditional logic (e.g., if statements) to determine their values dynamically.
  2. Setting variables within a Python library allows for conditional logic but does not always provide IDE support for auto-completion of these dynamically created variables in Robot Framework files.
  3. Using resource files or variable files with predefined values also doesn’t adapt to the dynamic needs based on conditions evaluated at runtime.

Questions:

  • Is there a recommended approach or workaround to achieve dynamic global variable instantiation in a way that also supports IDE auto-completion?
  • Can you suggest best practices or strategies for handling variable values that depend on runtime conditions or external inputs?

Thank you for any assistance or insights you can provide!

EXAMPLE CODE:
As you can see this is not user friendly, and the variables names wont be autocompleted in IDE’s, making the users more hard to use the tool if the whant to code and use those variables.:

from robot.libraries.BuiltIn import BuiltIn

class common:
  def __init__(self, server):
      # Example: Read conditions from environment variables or set defaults
      if server.version == '2.1':
          self.set_global_variable('variable1','value')

  def set_global_variable(self, variable_name, value):
      BuiltIn().set_global_variable(f"${{{variable_name}}}", value)

Hi,

I have encountered this, for example to access non standard html lists, where xpath needs to contain list items dynamically.
Depending of differences in xpath (just a variable or complete path), I would do this:

  1. The difference is just a variable in the xpath:
    For now the solution I’m using is a resource file containing a “build” keyword, that will take items as arguments and build the xpath with eventual axis

    Something like this:

     BuildXpathB
         [Documentation]    Keyword to build partial xpath with axis
         [Arguments]    ${mainxpath}    ${variable}    ${axis}=${EMPTY}
         ${var_xpath}     Catenate    SEPARATOR=    ${mainxpath}    ${variable}    ']    ${axis}
         RETURN    ${var_xpath}
    

    I didn’t rework it yet with VAR though, but could be simplified.
    Also I use actually 2 of this keywords (BuildXpathP also with parenthesis).

  2. Xpath are completely different between versions

    In this case I would build a file/dictionary as csv with the xpaths (one column per version)
    Then load this file with pandas library, then read and define variables (as Global or Suite) in a loop based on the column/versions expected (from a IF statement)

In first case too anyway, loading a file with the variables and define them in loop would be a solution.

Hope this helps.

Regards
Charlie

1 Like

Thanks for your help. I managed to work around the issue using the following approach:

Run Before Every Selenium Test Case
    Run Before Every Test    ${hostname}
    Initialize TS7700 Locators

Initialize TS7700 Locators
    Set Locators For Categories Page
    Set Locators For Cluster Summary
    Set Locators For Events Page
    Set Locators For Performance Page
    Set Locators For Virtual Mounts Page
    Set Locators For Host Throughput Page
    Set Locators For Cache Throttling
    Set Locators For cache page
    Set Locators For Tasks Page
    Set Locators For Virtual Tape Drives Page
    Set Locators For Virtual Volumes
    Set Locators For Storage Groups Page
    Set Locators For Management Classes Page
    Set Locators For Storage Classes page
    Set Locators For Data Classes Page
    Set Locators For Security Settings Page
    Set Locators For Roles and Permissions Page
    Set Locators For SSL Certificates Page
    Set Locators For InfoCenter Settings Page
    Set Locators For Dual Control Page
    Set Locators For Library Port Access Groups Page
    Set Locators For Cluster Settings Page
    Set Locators For Feature Licenses
    Set Locators For Select SNMP
    Set Locators For Rest Encryption
    Set Locators For Write Protect Mode
    Set Locators For Backup Settings
    Set Locators For restore settings Page
    Set Locators For Select RSyslog
    Set Locators For Notification Settings Page
    Set Locators For Repair Virtual Volumes Page
    Set Locators For Network Diagnostics Page
    Set Locators For Data Collection Page
    Set Locators For Update System

Set Locators For InfoCenter Settings Page
    IF    ${is_8_52}
        Set Global Variable    ${INFOCENTER_FILE}     id=form2:fileupload1
        Set Global Variable    ${INFOCENTER_UPLOAD}    id=form2:uploadButton
        # More variables ....
    ELSE IF    ${is_8_54}
        Set Global Variable    ${INFOCENTER_FILE}    xpath=/html/body/form[2]/a
        Set Global Variable    ${INFOCENTER_UPLOAD}    xpath=/html/body/form[2]/b[2
        # More variables ....
    END
 
Set Locators For Categories Page
    # Here i set the conditions for setting more global variables

Set Locators For Cluster Summary
    # Here i set the conditions for setting more global variables
    
Set Locators For Events Page
    # Here i set the conditions for setting more global variables
    
Set Locators For Performance Page
    # Here i set the conditions for setting more global variables
    
# and the list goes on 

I then call the Run Before Every Selenium Test Case keyword in my test suite, which initializes the global variables. However, this feels more like a workaround than a solution. I was surprised to find that Robot Framework doesn’t support conditional global variables directly, leading to quite a bit of extra work on my part. Additionally, my IDE doesn’t autocomplete these variables when I try to use them in keywords or test cases, which is a bit inconvenient, to the point that it is very difficult for my colleagues to call these variables in their code.

It would be fantastic if there were a way to use IF conditions in the *** Variables *** section to handle this more gracefully.

Thanks again for your assistance!

1 Like

Haven’t tested this but could using python file as variable file be able to solve this ? At least if you pass the server version from cli to robot, variable file could get that and then return the correct xpath’s for that particular version ?

It is possible to pass some variables from CLI, im actually passing some variables using CLI, but Robot Framework does not currently offer a direct way to pass arguments to Python variable files (any kind of variable, even if they come from the cli, you can not pass variables/arguments to Variables file), which limits the ability to instantiate variables conditionally. For example:

  • Instantiate certain variables if my web platform version is 2.1.
  • Define specific XPaths if the servers hosting the web platform are part of a grid.
  • And the list of conditions goes on and on.

Ideally, I would be able to do something like this:

test.robot

*** Settings ***
Variables    locators.py    ${SERVER}    
# The SERVER object contains all the server
# information I need to determine which 
# XPaths to define. The Server object contains
#  such as the web platform version, server version,
# grid status, etc. But I can't pass any variables as arguments to the Variables file.

variables.py

# Calling the object $SERVER inside Variables files is not possible, 
# its just an example, because i cant pass $SERVER object as an 
# argument when i import variables.py, But be clear that these 
# conditions that I am going to show are used in other parts of my 
# code and they work perfectly.

if "CSA" in SERVER['disk_model']:
    # Instatiate the global variables with specific xpaths
if "VEF" in SERVER['model']:
    # Instatiate the global variables with specific xpaths
if "3500" in SERVER['attach']:
    # Instatiate the global variables with specific xpaths
else:
    if "2.1" in SERVER['version']:
        if "CC9" in SERVER['disk_model']:
            # Instatiate the global variables with specific xpaths
        elif "CSA" in SERVER['disk_model']:
            # Instatiate the global variables with specific xpaths
        elif "CSB" in SERVER['disk_model']:
            # Instatiate the global variables with specific xpaths
        elif "CFC" in SERVER['disk_model']:
            # Instatiate the global variables with specific xpaths
        elif "CFD" in SERVER['disk_model']:
            # Instatiate the global variables with specific xpaths

This way, I would be implementing a true Page Object Model (POM), but unfortunately, as it stands, i cant pass arguments to Variables files, and Robot Framework makes creating a proper POM structure somewhat arcane and complex. This is disappointing to me, as Robot Framework, instead of simplifying things, sometimes complicates them. It forces users to find workarounds for things that aren’t directly possible in Robot, And which are possible in python, which I believe goes against the philosophy of Robot Framework—to provide an intuitive, human-friendly framework.

Second And Easier Alternative

Allowing users to use conditionals at *** Variables *** Section:

*** Variables ***
 IF    "2.1" in $SEVER[version]
        IF    "CC9" in '$SERVER[disk_model]'
               # Instatiate the global variables with specific xpaths
        ELSE IF    "CSA" in '$SERVER[disk_model]'
                # Instatiate the global variables with specific xpaths
        ELSE IF    "CSB" in '$SERVER[disk_model]'
                # Instatiate the global variables with specific xpaths
        ELSE IF    "CFC" in '$SERVER[disk_model]'
                # Instatiate the global variables with specific xpaths
        ELSE IF    "CFD" in '$SERVER[disk_model]'
                # Instatiate the global variables with specific xpaths
        END
    END
etc ........

I made a poc of what i was suggesting;

dyn.py:

from robot.libraries.BuiltIn import BuiltIn
_rf = BuiltIn()
_ver = _rf.get_variable_value("${SERVER_VERSION}")
if _ver != None:
    FOO=_ver
else:
    FOO="FOOBAR!!"

And demo.robot:

*** Settings ***
Variables     dyn.py
*** Test Cases ***
Try Something Dynamic
  Log To Console    ${FOO}

So, if you pass -v SERVER_VERSION:0.0.0 to robot from cli, value of ${FOO} that gets printed out will be 0.0.0 and you omit the -v completely, output will be FOOBAR!!

3 Likes

In a Python variables file, if there is a def get_variables method, RF will call it when importing the variables file. That get_variables implementation can take arguments.

So your Variables locators.py ${SERVER} in combination with variables.py:

def get_variables(server: dict[str, str) -> dict[str, str]:
    if "CSA" in server["disk_model"]:
        ...
    
    return dict_of_variables

See Robot Framework User Guide

3 Likes

Awesome solutions, guys! I can’t believe I didn’t notice that earlier.

4 Likes