How To create keywords for only xapth changing test cases

Currently I’m using having code as:

    ${dictionary_data}=  Create Dictionary
    ${column_Count}=  Get Element Count  ${total_columns}
    FOR  ${column_index}  IN RANGE  1  ${column_Count}+1
        ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath}
        ${webelements}=  Run Keyword If  ${xpath_available}  Get Webelements  ${xpath}
        @{element_list}  Create List 
        IF  ${xpath_available}
            FOR  ${element}  IN  @{webelement}
                ${element_text}=  Get Text  ${element}
                Append To List  ${element_list}  ${element_text}
            END
        END
        Set To Dictionary  ${dictionary_data}  ${key}=${element_list}
    END

this code repeats for every test case only the xpath will vary for each time so i want to use keywords instead of writing this code again and again and also instead of creating the list i want to add element one by one when it is iterating in for loop and add to the dictionary can anyone guide how can i acheive expected result

Hi Leo,

User keyword syntax

The [Arguments] setting and RETURN are what you’re after, then do something like this (warning completely untested and typed after 1am :joy:):

*** Test Cases ***
Test Case 001
    ${my_dictionary_data_1}=    Parse Xpath To Dictionary    ${xpath1}

Test Case 002
    ${my_dictionary_data_2}=    Parse Xpath To Dictionary    ${xpath2}

*** Keywords ***
Parse Xpath To Dictionary
    [Arguments]    ${xpath}
    ${dictionary_data}=  Create Dictionary
    ${column_Count}=  Get Element Count  ${total_columns}
    FOR  ${column_index}  IN RANGE  1  ${column_Count}+1
        ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath}
        ${webelements}=  Run Keyword If  ${xpath_available}  Get Webelements  ${xpath}
        @{element_list}  Create List 
        IF  ${xpath_available}
            FOR  ${element}  IN  @{webelement}
                ${element_text}=  Get Text  ${element}
                Append To List  ${element_list}  ${element_text}
            END
        END
        Set To Dictionary  ${dictionary_data}  ${key}=${element_list}
    END
    RETURN    ${dictionary_data}

Dave.

1 Like

@damies13 Thanks for your efforts :grinning:

Actually, I forgot to add some lines

I got error as ${column_index} not found when i used above code actually I want to make keyword which is useful even if the xpath changes. I will have xpath which will have ${column_index} somewhere in the xpath for example:

#ex1
${element_xpath}=  //div[@class='abc']\[${column_index}]/div[1]/div[2]  
#ex2
${element_xpath}=  //div[@class='abc']/div[1]/div[2]\[${column_index}] 

somewhere in my xpath I’ll have ${column_index}

So only xpath will be different in other test cases so can we create Keyword which will work even if the xpath is different but it contains ${column_index} somewhere in xpath can you help me out I’m stuck in this my code looks repetative and i want to use keyword instead of that

@_daryl @HelioGuilherme66 @damies13 @NikosKoul
Sorry to tag you all but anyone can help I’m stuck in this tried all things but didnt get solution

Might be worth sharing your current code snippet for that keyword etc… (id imagine you have adjusted since the og post), as the:

I got error as ${column_index} not found

Sounds like that variable hasn’t been set/initialized, and you are using ${column_index} to that end for which that error is being thrown, I’d imagine the full error is just that “Variable ${column_index} not found” but hard to really say whats happening without seeing the latest code snippet.

1 Like

*** Test Cases ***
Test Case 001
${my_dictionary_data_1}= Parse Xpath To Dictionary /div[@class=‘abc’][${column_index}]/div[1]/div[2]

Test Case 002
${my_dictionary_data_2}= Parse Xpath To Dictionary ${xpath}${columns}[${column_index}]

Parse Xpath To Dictionary
    [Arguments]    ${xpath}
    ${dictionary_data}=  Create Dictionary
    ${column_Count}=  Get Element Count  ${total_columns}
    FOR  ${column_index}  IN RANGE  1  ${column_Count}+1
        ${xpath_with_index}=  Set Variable If  '${xpath}' contains '${column_index}'  '${xpath}'  '${xpath}'.replace('${column_index}', str(${column_index}))
        ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath_with_index}
        ${element_list}=  Create List
        IF  ${xpath_available}
            ${webelements}=  Get Webelements  ${xpath_with_index}
            FOR  ${element}  IN  @{webelement}
                ${element_text}=  Get Text  ${element}
                Append To List  ${element_list}  ${element_text}
            END
        END
        Set To Dictionary  ${dictionary_data}  ${column_index}=${element_list}
    END
    RETURN    ${dictionary_data}

For this code Variable ${column_index} not found error is showing and in above code xpath will be diffrent for different test cases but xpath will have ${column_index} somewhere . so I want to replace ${column_index} with the value iterating through the xpath i.e(FOR ${column_index} IN RANGE 1 ${column_Count}+1)

So I want to use Keyword in test cases rather than repeating same code again and again only xpath of the keyword will be diffrent

You need to have ${column_index} here and build the xpath expression inside the keyword. When you pass it like you are doing, you are passing the values, no variables by reference.

Other solution, but a bad practice one, is to declare ${column_index} as a global/test suite/test case/ variable.

1 Like

Thanks, @HelioGuilherme66 beat us to it

As an alternative🤢 , if @mk111 were to keep it as it was, and replace the ${column_index} from within their XPath variables/passed xpaths with just column_index (no $ or { } ) and then replace the first line after the FOR starting statement with the below in the:

${xpath}= Replace String ${xpath} column_index ${col_index}

Then that should also work, if they wanted to check if contains them prior to replacing the column_index with the FOR value, then do the below or an equivalent:

${xpath_contains_index}= Evaluate "column_index" in "${xpath}"

Then they could handle cases where the XPath doesn’t hold the temp column_index and possibly BREAK/CONTINUE if it wasn’t to exist depending on their handling, but there really are so many ways.

EDIT:
Figured it best to provide what changes to the snippet if you weren’t to handle it the way @HelioGuilherme66 proposed

so replace these two:

${xpath_with_index}=  Set Variable If  '${xpath}' contains '${column_index}'  '${xpath}' '${xpath}'.replace('${column_index}', str(${column_index}))
${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath_with_index}

with:

   ${xpath_available}=     Set Variable    ${False} 
   ${contains_column_index}=     Evaluate     "column_index" in "${xpath}"
   IF  ${contains_column_index}
       ${xpath_with_index}=     Replace String     ${xpath}     column_index     ${column_index}
       ${xpath_available}=    Run Keyword And Ignore Status      Element Should Be Visible      ${xpath_with_index}
       # rest of code here... for what's relevant...ofc
   ELSE    
      # Handle cases however when XPath doesn't hold temp column_index BREAK/CONTINUE/FAIL etc...
   END

Calling the keyword would look like this (see column_index with no $ or { } ):

${my_dictionary_data_1}=       Parse Xpath To Dictionary    /div[@class=‘abc’][column_index]/div[1]/div[2]
2 Likes

Im little bit confuse how to use this in code can you please show if possible

@_daryl I’ll always have “column_index” somewhere in xpath currently I used solution as:

*** Test Cases ***
Test Case 001
    ${my_dictionary_data_1}=    Parse Xpath To Dictionary    /div[@class='abc'][column_index]/div[1]/div[2]    

Test Case 002
    ${my_dictionary_data_2}=    Parse Xpath To Dictionary    ${xpath}${columns}[column_index]    
*** Keywords ***
Parse Xpath To Dictionary
    [Arguments]    ${xpath}    ${column_index}
    ${dictionary_data}=    Create Dictionary
    ${column_Count}=    Get Element Count    ${total_columns}
    FOR    ${column_index}    IN RANGE    1    ${column_Count}+1
        ${xpath}=     Replace String     ${xpath}     column_index     ${column_index} 
        ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath_with_index}
        ${element_list}=  Create List
        IF  ${xpath_available}
            ${webelements}=  Get Webelements  ${xpath_with_index}
            FOR  ${element}  IN  @{webelement}
                ${element_text}=  Get Text  ${element}
                Append To List  ${element_list}  ${element_text}
            END
        END
        Set To Dictionary  ${dictionary_data}  ${column_index}=${element_list}
    END
    RETURN    ${dictionary_data}

By using above code I got error for line

Replace String     ${xpath}     column_index     ${column_index}

TypeError: replace() argument 2 must be str,not int

Can you please guide @_daryl @HelioGuilherme66 @damies13

Just to keep it simple:

${str_col_index}=    Convert To String      ${column_index}
Replace String     ${xpath}     column_index     ${str_col_index}

If interested… you can log the type, which can be useful if in doubt but the message in your case is as it reads, is saying your passing and int not a string, and also looking at documentation is also very useful:

${type}=    Evaluate     type($variable).__name__
${type}=    Evaluate     type($variable)

The top one will return the name property ‘int’, ‘string’ etc… and the bottom one you’ll see
<class ‘type’> type being int, string etc…

in addition, you can use the isinstance($variable, int) and that will return true or false but less relevant here, but nice to know.

@_daryl @HelioGuilherme66 @damies13 Thank you for the solution It helped me a lot now my code runs successfully

1 Like

Hellow @_daryl Sorry to disturb you again and again but I have one major concern
The xpath is taking only 1 index and for other it is continuing with the same xpath I tried alot of things but not able to figure it out

/div[@class='abc'][column_index]/div[1]/div[2]  
/div[@class='abc'][1]/div[1]/div[2]

execution continuing with same xpath but it is expected that column_index will change IN RANGE of ${column_Count}
Can you please help?

Looking at what you last shared as a snippet, the replace string is assigned to ${xpath} but you don’t use it at all in the statements thereafter, see these two lines taken from the last shared snippet:

 ${xpath}=     Replace String     ${xpath}     column_index     ${column_index} 
 ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath_with_index}

So that wouldn’t work as the ${xpath_with_index} doesn’t exist, but I’d guess and say if you shared you’re latest snippet it would differ slightly, and at an assumption, it will show like the below or somewhat similar:

${xpath}=     Replace String     ${xpath}     column_index     ${column_index} 
${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath}

If my assumption is right, and based on your comment… that what you will need to do/ensure is to change those two lines to:

${xpath_with_index}=     Replace String     ${xpath}     column_index     ${column_index} 
${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath_with_index}

This is because the replace has assigned back to the original variable ${xpath} so it will no longer holds the [column_index] part in its original state and will hold [1] in its place, so on the second iteration the replace isn’t actually replacing anything, hence why it’s only ever showing [1]

But purely assumptions based on not knowing how the current block sits, and looking above the last snippet you shared would have thrown an error at that point as the variable ${xpath_with_index} never existed, but it had likely fallen over on the calling keyword as you had two arguments expected to be passed, where one only showed to be passed (id imagine you have adjusted this already).

But ultimately, if that is not the problem then sharing the code block would be very helpful.

@_daryl thanks and sorry if I look like demanding but can you please show in code what changes i have to make to so that the code will work fine I’m new to robot framework and literally working on this code since last month

You able to share your current code block?

*** Test Cases ***
Test Case 001
    ${my_dictionary_data_1}=    Parse Xpath To Dictionary    /div[@class='abc'][column_index]/div[1]/div[2]    

Test Case 002
    ${my_dictionary_data_2}=    Parse Xpath To Dictionary    ${xpath}${columns}[column_index]    
*** Keywords ***
Parse Xpath To Dictionary
    [Arguments]    ${xpath}    
    ${dictionary_data}=    Create Dictionary
    ${column_Count}=    Get Element Count    ${total_columns}
    FOR    ${column_index}    IN RANGE    1    ${column_Count}+1
        ${str_col_index}=    Convert To String      ${column_index}
        ${xpath}=  Replace String     ${xpath}     column_index     ${str_col_index} 
        ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath_with_index}
        ${element_list}=  Create List
        IF  ${xpath_available}
            ${webelements}=  Get Webelements  ${xpath_with_index}
            FOR  ${element}  IN  @{webelement}
                ${element_text}=  Get Text  ${element}
                Append To List  ${element_list}  ${element_text}
            END
        END
        Set To Dictionary  ${dictionary_data}  ${column_index}=${element_list}
    END
    RETURN    ${dictionary_data}

This is my latest snippet I also tried by ${column_index} as second argument and used in test case also but it didnt worked

*** Keywords ***
Parse Xpath To Dictionary
    [Arguments]    ${xpath}    
    ${dictionary_data}=    Create Dictionary
    ${column_Count}=    Get Element Count    ${total_columns}
   
    FOR    ${column_index}    IN RANGE    1    ${column_Count}+1
        ${str_col_index}=         Convert To String      ${column_index}
        ${xpath_with_index}=      Replace String         ${xpath}     column_index     ${str_col_index} 
        ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath_with_index}
        ${element_list}=  Create List
        IF  ${xpath_available}
            ${webelements}=  Get Webelements  ${xpath_with_index}
            FOR  ${element}  IN  @{webelement}
                ${element_text}=  Get Text  ${element}
                Append To List  ${element_list}  ${element_text}
            END
        END
        Set To Dictionary  ${dictionary_data}  ${column_index}=${element_list}
    END
    RETURN    ${dictionary_data}

That should now replace the column_index and not stay sat at [1]

1 Like
${xpath}=  Replace String     ${xpath}     column_index     ${str_col_index} 
        ${xpath_available}=  Run Keyword And Ignore Status  Element Should Be Visible  ${xpath}

little correction this is used in my snippet

That’s fine and looks to be as per my assumption, if you replace what I shared with your current then it should increment, as the original XPath was being changed, so it just needs assigning to a new variable, as on the second loop and thereafter it can’t replace the column_index as it no longer exists in the ${xpath} for that reason, so it will always hold [1] in its place.