Created On:  14-05-2010
Is there a white paper describing the use of the SilkTest reference operator?
Below is the SilkTest Reference Operator.  Please note that the archive attached to this white paper was created with SilkTest 2009 R2 and Firefox using the SilkTest Classic Agent. If you use any other versions of these two programs the project may not work out of the box.

The reference operator (the @ symbol) lets you refer to functions and variables indirectly by name. It uses the value of its operand to refer to variables, a field in a record, a function, a method, a property or a child window. It has sometimes been referred to as 'the poor man's pointer' as it operates in a similar fashion to a C-style pointer but there the similarity ends; SilkTest does not use pointers and the reference operator is not a pointer. If anything, it is more closely related to the action of the Variant data type found in Visual Basic.

In addition to the abilities mentioned above, the reference operator also finds itself required for a little-used SilkTest technique: the $StopRunning() method. We will look at this method first as it is easily dealt with.



Please note the caveat listed in the help files with this method:

 'This feature is provided as a convenience to you, but it has not yet been thoroughly tested' 

 $StopRunning() brings SilkTest to an immediate crash stop. It is an emergency-use only method; it does not clean up behind itself, it bypasses the DefaultBasestate, it does not reset any Agent options and it ignores any custom exception handlers you may have created. Looking at that list of 'it doesn't' you could be forgiven for thinking that it isn't really of much use. However, the example quoted in the Help files is a very good example of where to use $StopRunning():-

  [-] void CheckMem()
     [ ] integer i=0
     [ ]
     [ ] // Check the current state of free memory
     [ ] i = SYS_GetFreeMemory()
     [ ]
     [ ] // If available memory falls below 10% then stop
     [ ] // all testing before the system freezes
       [-] if (i < 10)
         [ ] @("$StopRunning")()
     [ ] return

The reference operator probably finds its greatest use in dynamic applications where the variable/window name/etc. will not be known when the testcase starts. It is very easy to get carried away with the reference operator: for example, the following code is a perfectly legal line:-

  WINDOW wWin = @pWin.@cWin.@winFunc(nodeName)


  STRING pWin=""
  STRING cWin=""
  STRING winFunc=""
  STRING nodeName=""

But why would you want to make your code that unreadable?

We will take a look at using the reference operator to read an HTML table that contains hyperlinks. Although in this case the hyperlinks are static, we will make two assumptions:-

1. The hyperlinks are all drawn from a known pool (e.g. a set of internal website links). This means that we can create a set of window declarations for all pages that could be tested.
2. We don't know which links will appear in the tables.
In addition to this, we will be facing a known SilkTest problem in that the first entry in a column is assumed to be the column name whereas (as in this case) it is often column data. We will use the reference operator to return the column 'name' as the data it really is, as well as using it to return the hyperlink we want to select. This is the webpage we will be dealing with:-


To ensure that the testcase works as expected, we will randomise the call to the hyperlinks so that we cannot know which link will be called on any given iteration of the test. The hyperlink that is selected will then be passed in as a parameter to the testcase. We will also allow for our hyperlink table to have empty data cells as in a dynamic situation we could not guarantee that all cells in the table would have data in them. We will break down the testcase in a series of steps and explain the function of each step. The complete project is available as an archive and is attached to this white paper.

The GetHyperLink testcase has a single function to randomise the link selected. As stated above, one assumption is that we know which links are available even if we don't know which links will be displayed, so we will store these links as a List Of String.

[-] STRING GetRandomLink(STRING sData)
 [ ] //*******************************************************
 [ ] //This function selects a random link for use with GetHyperLink
 [ ] //*******************************************************
 [ ] LIST OF STRING lsLinks
 [ ]
 [ ] //List the active links plus a false link (NoLink)
 [-] lsLinks={...}
  [ ] "MicroFocus"
  [ ] "POStenaline"
  [ ] "Queen'sUniversity"
  [ ] "Google"
  [ ] "PikevilleCollege"
  [ ] "Expedia"
  [ ] "BelfastInstitute"
  [ ] "BritishAirways"
  [ ] "FlyBe"
  [ ] "NoLink"
  [ ] "IrishFerries"
 [ ]
 [ ] //Pick one at random
 [ ] sData=RandPick(lsLinks)
 [ ] return sData

To allow for empty data cells we must use the following SetUserOption. If we do not so this, SilkTest will fail with a 'window not found' error message when it encounters an empty cell while enumerating the hyperlinks.

[ ] RefOp.SetUserOption("RowTextIncludesEmptyCells", TRUE)

The next move is to select a random link.

[ ] sLink=GetRandomLink(sLink)

We can now start to do the hard work of defining exactly how big the dynamic table is and what is in it. The initial move here is to count the number of columns and rows and to store the results for later retrieval. As we are searching multiple columns we need to multiply the returned figure from GetRowCount() by iCols to get the total number of links (iLinks).  We know that column one will always have the full number of rows in the table even if one or more cells are empty as this is a function of the structure of a table, so we will use the name of the first column to count the number of possible links in each column. As we don't know the name of this column we will retrieve it via a GetColumnName() and pass the name to the GetRowCount() line using a reference operator. In this case the reference operator is being used to reference the string which contains the name of column one (sCheckLink).

[ ] sCheckLink=Refop.HtmlTable1.ReferenceOperatorTestPage.Hyperlinks2.GetColumnName(1)
[ ]
[ ] iLinks=i*iCols

We now know just how big the table is in terms of rows, columns and cells. Having obtained this information we can now loop through the data contained in the table looking for the randomised link name. There is a large caveat here: on the first loop we must check the column names only so that we can convert them to readable data. We can do this by starting at j=0 because GetRowCount() is one-based. We need to check ALL column names before continuing down the individual columns in the table. At the same time we will use the MatchStr() method to identify if any of the column names is equal to the randomised string.

 [-] for (j=0;j<=iLinks;j++)
    [-] if(j==0)
      [ ]
      [ ] //Loop across the column names and store them in a list for use
      [ ] //in identifying the individual columns
      [-] for (k=0;k<=iCols;k++)
  [ ] sCheckLink=Refop.HtmlTable1.ReferenceOperatorTestPage.Hyperlinks2.GetColumnName(k)
  [ ] ListAppend(lsColumnName,sCheckLink)
  [ ]
  [ ] //Compare the hyperlink to be executed against the link that is currently being
        [ ] //examined. If MatchStr returns true then execute the link and exit the loop.
        [ ] //The current link is held in the sCheckLink string; if this needs to be executed
        [ ] //we will call a reference to it in the following Click() method.
  [-] if(MatchStr(sLink,sCheckLink))
    [ ] Refop.SetActive()
    [ ]
    [ ] iFlag=1
    [ ] break

If the column names have not contained the hyperlink to be executed, we will need to continue on down each column until we identify it. However, we do need to check that a link has not been executed (iFlag) as if it has, we can exit the testcase at this point. The initial break() statement above will only release us from the column name loop and not from the entire testcase. We will also need to check when we need to change columns; this will initially happen on the first time through this loop as we have already moved to the last column when checking column names (above) and thereafter whenever we reach the bottom of the table. Although we should never overrun the number of columns, we will trap a possible column miscount (iFlag=2) and abort the testcase. Be aware that some lines wrap round.

 [ ] else
   [-] if(iFlag==1)
     [ ] break
     [ ]
     [-] if(iCount==i || j==1)
       [ ]
       [ ] iCount=0
       [-] if(iColCount<=iCols)
         [ ] iColCount++
         [ ] sColumn=Refop.HtmlTable1.ReferenceOperatorTestPage.Hyperlinks2.GetColumnName(iColCount)
       [-] else
         [ ] LogWarning("Column count is now greater than the actual number of columns - checking aborted")
         [ ] iFlag=2
         [ ] break

Each time we change columns we put the new column 'name' (actually the first hyperlink) into the string variable sColumn. To get the hyperlink from the current row, we need to use the reference operator to pass in sColumn to the GetRowtext() method. iCount is the variable that is tracking the row number.

     [ ] sCheckLink=Refop.HtmlTable1.ReferenceOperatorTestPage.Hyperlinks2.@sColumn.GetRowText(iCount)

sCheckLink now contains the hyperlink in the cell that is being read by a combination of @sColumn and iCount. As above in the section that turns the column names into usable data, we need to do a MatchStr() to see if we have found our randomised hyperlink. If we have, to execute the click event we need to pass in both the column name and the row name (actually the hyperlink) using the reference operator. To reiterate: at this point sColumn contains the column name and sCheckLink contains the data in the cell being accessed by iCount i.e. the actual hyperlink itself.

      [-] if(MatchStr(sLink,sCheckLink))
        [ ] Refop.SetActive()
        [ ] Refop.HtmlTable1.ReferenceOperatorTestPage.Hyperlinks2.@sColumn.@sCheckLink.Click()
        [ ] iFlag=1
        [ ] break

Finally, we'll add a nice success/fail message into the results file. This is where we trap failures and invalid links.

   [-] if (iFlag!=2)
     [-] if(iFlag==1)
       [ ] print(sLink+" has been launched")
     [-] else
       [ ] print(sLink+" is not a valid link")

Please see the attached archive for the full testcase.

The following code shows how to use the reference operator to read from a record rather than from a simple string variable as illustrated above. To make it more awkward, we are referencing a string within a record within a list of record. Again, please be aware of line wraps.

[ ] type MyRecordList is list of MyRecords
[ ]
[-] type MyRecords is record
 [ ] string s1
 [ ] string s2
 [ ] string s3
[ ]
[-] testcase RecordsTest() appstate none
       [ ] //Declare a new record
 [ ] MyRecords rNewFile
       [ ] //Declare a new list of records and fill it
 [ ] MyRecordList lrRecordList1= {{"Bobby","1","Baldur's Gate"}, {"Dai","2","Shadows of Amn"}, {"Mick","3","Mass Effect"},{"Rabbie","4","DragonAge"}}
 [ ] int i=0
 [ ]
 [-] print("List of Record listcount = "+str(listcount(lrRecordList1)))
   [ ]
         [ ] //Now print the contents of the recordlist using the reference operator
         [ ] //to retrieve each record within the recordlist
         [-] for(i=1;i<=4;i++)
     [ ] print(lrRecordList1[i].@("s1"))
     [ ] print(lrRecordList1[i].@("s2"))
     [ ] print(lrRecordList1[i].@("s3"))

SilkTest will output the following result:-

[-] Testcase one - Passed
 [ ] (local)      List of Record listcount = 4
 [ ] (local)      Bobby
 [ ] (local)      1
 [ ] (local)      Baldur's Gate
 [ ] (local)      Dai
 [ ] (local)      2
 [ ] (local)      Shadows of Amn
 [ ] (local)      Mick
 [ ] (local)      3
 [ ] (local)      Mass Effect
 [ ] (local)      Rabbie
 [ ] (local)      4
 [ ] (local)      DragonAge

Finally, just to prove that the reference operator will work with integers as well as strings, consider the following very simple testcase:-

[-] testcase MyRefopIntegers() appstate none
 [ ] int i=1
 [ ] int j=1
 [ ]
 [ ] print(

This shows the catch when working with numbers (REAL or INTEGER) in that they must be presented to the reference operator as a string.