[eggPlant Functional] Useful SenseTalk Functions

Thought some folks would like an easy to use, quick and dirty way to get field contents on a remote system (System Under Test, or SUT). Stick this into your own Toolbelt.script and start using it today.
-- Set the contents of a field to a value.  The image hot spot
-- determines where the click occurs relative to the image
-- found location.
to setContentsOfField   fieldName, fieldValue

    Click fieldName
    TypeCommand "A", "C"  -- copy the text contents
    TypeText fieldValue
  catch e
    LogError("Error Clicking Image", e)
  end try
end setContentsOfField

and here is the getter function for the above, setting the field contents.
-- Grab a field's contents, marked by the image name as the
-- first parameter the image hot spot determines where the
-- click occurs relative to the image found location.
to contentsOfField  fieldName

    Click fieldName
    TypeCommand "A", "C"  -- copy the text contents
    return remoteClipboard(10)
  catch e
    LogError("Error Clicking Image", e)
  end try
end contentsOfField

Try grabbing images that uniquely identify a text field. These handlers will quickly allow you to valid field data. As a test of utility and speed, I created a new test suite, added these handlers, created another driver handler and validated 5 fields of data in a repeat loop. It took me about 2 minutes, and should a new Eggplanter about ~10 mins.


  • ToddTodd Member
    edited June 2007
    Thanks for all the great suggestions and comments from all our Eggplant users. Keep the questions coming!

    This one comes from a large Bay Area company with dozens of Eggplant seats and is creating a fairly large testing and validation group around Eggplant.

    Transitioning from, but still using a fairly manual test policy where testers run Eggplant scripts by hand, enter an email address at the start of the script so any validation information and reports can be sent to the proper engineers upon successful/failure of script/suite completion. The idea of email validation was needed, to at least ensure someone was entering a valid email and then use it for the SenseTalk SendMail command.

    This AskForValidEmail was born as a result:
    [color=red]params aPrompt, aTitle, anAddress, useLast, allowCancel
    -- Remember the last value entered ...
    if useLast is not a boolean then set useLast to true
    -- ... and allow cancel by default
    if allowCancel is not a boolean then set allowCancel to true
    repeat forever
      ask aPrompt title aTitle with anAddress
      -- get our results stored for later use.
      set ( theResult, email ) to ( the result, it )
      if email is empty and theResult is "Cancel" \
        and allowCancel then exit repeat
      if emailIsValid(email) then exit repeat
      if useLast then set anAddress to email
    end repeat
    return email[/color]

    and this AskForValidEmail.script in turns calls a simple email validation EmailIsValid.script which checks basic email address validation rules.

    [color=red]params  address
    if address is empty then return false
    split address by "@"
    if the number of items in address <> 2 then return false
    set ( name, domain ) to ( items 1 to 2 of address )
    -- ensure no inter-word spaces and one word for each
    if number of words in name <> 1 then return false
    if number of words in domain <> 1 then return false
    -- and no outer-word spaces
    if words 1 to -1 of name <> name then return false
    if words 1 to -1 of domain <> domain then return false
    return  true[/color]

    You'll see 5 parameters to the AskForValidEmail, however none are required. It is a good idea however to provide at least the first 2, the prompt and title of the ask window. The 3rd is an optional email address. The 4th parameter to the AskForValidEmail handler is the optional useLast, which by default if left out from the call, then defaults to true. This will allow the user to quickly make changes to their email address data. If you set it to false, then the user will be represented with the original calling email address in the ask panel. And finally, the optional 5th parameter is the allowCancel, which enables or disables the ability of the user to cancel out of the ask panel. By default, this is true.

    Its not often we want a user 'trapped' in the use of an ask panel. However there are rare cases where this would be desirable and supported. Most likely, you will use this handler in the fashion shown.

    Included is the EmailValidation.suite and its Demonstration.script demonstrating how simple these handlers work together. See how simple it is to do data validation with SenseTalk:
    [color=red]AskForValidEmail "Enter Email Address", \
      "Destination Email For Reports", \
      "[email protected]"
    set validEmailAddress to the result
    put validEmailAddress[/color]
  • ToddTodd Member
    edited July 2007
    Often is the case where due to the Internet being rather centric to Unicode, one has to send streams or text on a clipboard to a remote SUT in the form of Unicode. Requested by a number of large Eggplant installations drove me to finally abstract and document the ASCII->X and X->ASCII conversion scripts in this ConversionExamples.suite.

    Nothing really fancy here this time around, except of the inclusion of Redstone's EggDoc comments. You may want to download EggDoc (search in forums for it), and use it to document these scripts. Just Run EggDoc where it is, promting you for the path to the suite you wan to document.

    The scripts take ASCII and Hex/Unicode and convert it one way or another. Basic filter stuff, but very useful when you have to encode before sending via a socket or pasteboard where the destination application expects only Unicode.

    Download the attatchment suite, and run the Demonstration.script, mess around in the Playground.script and see how basic Unit Level Testing can be handled with a generic approach using the UnitTest.script.
  • ToddTodd Member
    Interestingly, few people actually leverage the list assignment and vector/list math features of SenseTalk. However, doing point math can be as important as breathing in some cases and problems arise when doing math with what seems like points and lists, but are actually strings. One must convert a string to a real point/list (a list with two numeric values) first before doing vector math. Here is a problematic example, using the ask command which returns a string regardless of the validity of the values being points or lists:
    ask "Enter pt1" with "1,1"
    set pt1 to it
    ask "Enter pt2" with "3,3"
    set pt2 to it
    put pt2 - pt1 --> 5270400, or some time in seconds

    This is tricky because of what SenseTalk is doing for you in the background. SenseTalk for the most part considers things as typeless, optimizing objects when it can, and leaving objects as strings whenever possible to minimize speed costs when dealing with data. In the above case, two points that are also strings. Thus they are not lists, and will be first attempted to be converted by default to dates, which they are (yes, "1,1" is a date, and so is "G3", as well as "yesterday"). Don't believe me? Execute this selection of ST code...
    put yesterday is a date
    put tomorrow is a date
    put "1,1" is a date
    put "G3" is a date  --> all output true

    To help avoid making this into a date conversion issue, and keeping it as a list issue, I won't go into why conversion to dates is done first. When performing list math, the 2..N coordinate data must always be converted to a list. In some cases, using (variable) notation works, but few and far between does it work reliably with explicit conversion needs. SenseTalk has its own ideas when in context of how certain things should take place.

    So, come the forced conversion routine:
    to StringToList  @vars ...
      repeat with each list item in vars by reference
         split it
      end repeat
      return  vars
    end StringToList

    This very small routine converts each item in the variable length list argument @vars to list values by using the split command. Defaulting to comma, this routine expects lists to be in the form of strings, like "1,2,3,4,5", etc.

    Finally a working example of code is well compressed into just a few lines:
    set ( pt1, pt2 ) to ( "1,1", "3,3" )
    StringToList @pt1, @pt2
    put pt2 - pt1

    To show even more flexibility, the StringToList handler is a 'to handler', which not only changes values by reference, but also will return the changed values themselves. This gives you the flexibility to call the handler two ways, either in the 'on handler' fashion, or in the 'function handler' fashion. Above the former was used above while below the function method of call is used.
    set ( pt1, pt2 ) to StringToList( "1,1", "3,3" )
    put pt2 - pt1

    Download and play around, included in the StringToListExample.suite below are all the code examples above including the broken out code of StringToList. This makes it a drag and drop away from use in your own suites/libraries.
  • ToddTodd Member
    I wanted to do some char and word usage stats on some large volumes of text, and came up with this handler to help. It leverages property lists, and how to dynamically perform math on empty containers value slots of key/value pairs. The result is a dynamic runtime definition of a value defined by a key.
    to CharUsageStats  string
      repeat with each char of string
        add 1 to r's (it)
      end repeat
      return r
    end CharUsageStats

    If you wanted to take it to the next level and do word usage, the modification to the handler would be one word change...
      repeat with each word of string

    and obviously renaming the handler to WordUsageStats.

    And finally, we could redefine the function to be a more generic solution. The code could be told to give back stats on char, words, lines and paragraph usage stats.
    to ChunkStats  string, chunkType
      if chunkType is not in ( char, word, line, paragraph ) then
        set chunkType to char
      end if
      -- special case, itemize with split by return & return
      if chunkType is paragraph then
        set ( chunkType, delim ) to ( item, return & return )
        split string by delim
      end if
      -- Send modified code to SenseTalk compiler and runtime.
      do merge(<< repeat with each [[chunkType]] of string
        add 1 to r's (it)
      end repeat>>  ) 
      return r
    end ChunkStats

    and you can test the code with the following (assuming you get some text first into the global 'it' variable.
    repeat with each item type in ( char, word, line, paragraph ) 
      put ChunkStats(it, type)
    end repeat

    Happy chunking.
  • ToddTodd Member
    One of our fav customers sent in an email, wanting to know how to format some numbers to comma delimited format. ie. 1234 to 1,234. I have taken liberty on what Doug sent back to our trusted fellow scripter, and expanded it to format to dollars and "sense". 1234.567 -> $1,234.57
    to commaFormat number
      repeat with n = length of number - 3 down to 1 step 3
        put comma after char n of number
      end repeat
      return number as text
    end commaFormat

    the above handle goes backwards (right to left) in 3 char chunks inserting commas along the way. This works because the left of a number is going to be flexible and have either 3 or 2 or even 1 number to the left of the leftmost comma.
    to dollarsAndCents number
      set the numberFormat to ".00"
      set cents to round(number - trunc(number), 2)
      return "$" & commaFormat(trunc(number)) & last 3 chars of cents
    end dollarsAndCents

    Then the dollarsAndCents conversion works simply by taking the difference of the commaFormat() function above, and appends the rounded to 2 decimal places cents value and returning it all as the result.
    set val = "6003905587.190000000"
    put dollarsAndCents(val)

    The "val"ue is set to a large arbitrary number. The call to DollarsAndCents with val as the parameter. We then convert back to the original number. Since we lost the rounded portion, that is not possible to recover, the best we can do is get back the unformatted dollars and cents value.
    to dollarsToNumber  number
      delete all "$" from number
      delete all "," from number
      return number
    end dollarsToNumber

    And finally we get to test the code...
    repeat 20 times
      get dollarsAndCents(randomValue(3,4))
      write it && "<->" && dollarsToNumber(it) & return
    end repeat

    Along with some random data:
    to randomValue  x, y
      if x is empty then set x to 5
      if y is empty then set y to 2
      return random(10^x) & "." & random(10^y)
    end randomValue

    Thanks for Doug Simons for the quick reply and trick for doing the comma insertion, going backwards in the string as well as fuel for the ideas to convert to real dollar format as well as back.
  • ToddTodd Member
    At times there is a need at the start of a suite run to check and see if series of files exists. Maybe they are data files, maybe they are image files, maybe they are configuration files. Here you will find a set of functions offering flexibility in checking to see if a single or list of files exists, or is absent. You may want to write your own 'whichFilesAreMissing()' function as well.
  • ToddTodd Member
    edited June 2007
    Here are a quick collection of functions that allow you to determine if one image is located to the right, left, above, below, and combinations there of from each other.
  • ToddTodd Member
    Often someone has to establish a known start state, or in this case eliminate the existing cookies in an browser so that old user names or passwords are not autofilled when testing a login page during script execution.

    Deciding to take the TIG approach, to dynamically create the images on the fly with just the text to search, the following script was generated in about the same time as the image grab would have taken.
    -- Small script'ette to delete all the cookies in safari
    Click  (Text: "Safari", TextStyle: ApplicationMenu )
    Click  ( Text: "Preferences", TextStyle: Menu ) -- or TypeCommand ","
    Click ( Text: "Security", TextFont: "LucidaGrande", \
      TextSize: 11 )
    Click ( Text: "Show Cookies", TextFont: "LucidaGrande", \
      TextSize: 13 )
    get ( Text: "Remove All", TextFont: "LucidaGrande", \
      TextSize: 13, Tolerance: 80 )
    if ImageFound(30, it) then
      Click FoundImageLocation()
      WaitFor 30, ( Text: "Remove All", TextFont: "LucidaGrande", \
        TextSize: 13, TextBackgroundColor:(120,167,237))
      Click FoundImageLocation()
    end if
    Click ( Text: "Done", TextFont: "LucidaGrande", \
      TextSize: 13, TextBackgroundColor:(120,167,237))
    TypeCommand "w"

    Since this is a OS X type posting, where these things will work only on OS X, here is how you could open an application installed in the standard /Applications directory (in this case, Safari):
    TypeText  commandDown, shiftDown, "g", shiftUp, commandUp
    TypeText "/Applications"  -- the default path to applications on OS X
    TypeText return  -- makes Finder open a window to previous typed path
    TypeText "Safari"  -- selects the app
    TypeCommand "o"  -- opens up the app

    and finally, to answer how someone could easily get the login and password into the field, you could use the following:
    Click ( Text: "label", TextStyle: aStyle, HotSpot: offset )
    TypeCommand "a"  -- select all
    TypeCommand "x"  -- delete all, or could have used  TypeText  backspace
    TypeText "newlogin"  -- your new login
    TypeText tab  -- tab to the password field
    TypeText "newpassword"  -- your new password
    TypeText return
  • ToddTodd Member
    From one of our Middle East clients, the issue of elapsed time format came into play today. Wanting to know how to format a script's duration time into HH:MM:SS with SenseTalk's FormattedTime() function, I was perplexed by the results. Asking the SenseTalk creator, Doug Simons on how to handle various results that looked to be based on GMT, the sticky and complex issue of time zones came into play. Our Israeli friend had asked how to take X seconds and format into HH:MM:SS and was using
    put FormattedTime("%H:%M:%S", the result's duration seconds)

    However this returned 2:00:02, if the scripts took 2 seconds to run to completion. In my case, in the CMT zone, the result was 18:00:02. Ok, so why? Immediately I thought time zone to GMT. To find out how many hours from GMT you are, try
    put the secondsFromGMT / 1 hour

    which in my case will output -5, or 5 hours behind the Greenwich Mean Time. I'm currently in daylight savings. Seconds are calculated using January 1st, 2001 as the epoch, and January doesn't have daylight savings. It was standard time during that period of the year. We have to resort to using something more current as reference, maybe today would work. Thus the following proved usable to figure out elapsed time and have it formatted in actual localized time, not GMT referenced time.
    params   aTime
    return formattedTime("%H:%M:%S", today - 12 hours + aTime)

    We take today, subtract 12 hours (cause today is at noon in SenseTalk), and add aTime to it, thus giving us an elapsed time that is in absolute time. Now put this into a newly minted handler named FormatElapsedTime and can get 'zone adjusted' elapsed time any time you want.
    put FormatElapsedTime(3599 seconds)  --> 00:59:59

    We hope this has helped clear up some time/date issues which may have arisen when considering, formatting and calculating times.
  • ToddTodd Member
    A question from the iEverywhere division of the bay area fruit company about plist management. In particular accessing property lists and their associated values. This quickly turned into the following FamousNames example handler, which returns a fairly long list of famous names as property lists with the keys (first, middle, last). Dealing with single name people (Prince, Cher, Bono, Pink, String), as well as names with multi middle names shows to be quit simple with the relative chunking.

    Use your own web data source, flat file, database CLI calls, or data scraping tool as the rawdata() function allows you to source your data anywhere.

    repeat with each line name in my rawdata
     if the number of words in names < 1 then next repeat
     if comma is in name then \
       set name to item -1 of name && \
       items 2 to -2 of name && \
       item 1 of name
     set it's first to word 1 of name
     set it's middle to words 2 to -2 of name
     set it's last to word -1 of name
     if it's last = it's first then set it's last to empty
     insert it into names
    end repeat
    put names  -- output a big fat list of names
    to rawdata  -- some names from the web.
     return shell(<<curl --silent http://www.loc.gov/rr/print/list/235_alph.html | grep "</a><BR>" | grep "href" | awk -F\< '{print $(NF-2)}' | awk -F\> '{print $NF}'>>)
    end rawdata
  • ToddTodd Member
    edited July 2007
    I got a call today, from none other than one of the top security testing firms in the world. And they are leveraging Eggplant to unobtrusively test devices and systems leveraging VNC enabled KVM switches. This gives a level of abstraction which with today's fast computers can cause the KVM interpretation of events to get out of sync with the remote SUT. Most of the higher quality KVM manufactures, Adder, Raritan have syncronization features forcing the mapping of events before continuing (much like an event queue 'flush' command). In most cases this works well. However in some limited cases its best to allow the KVM time to catch up as if a real world person were using the system under test.

    To do so, our friends were using a handler aptly called MyClick, and of course a replacement for the venerable Eggplant Click command. MyClick was a simulation of the Click command, moving the cursor first on the SUT, then waiting a period of time (0.7 seconds in their case worked well), and then clicking where the mouse was located.
    to MyClick  anImage
      MoveTo  anImage
      Wait  0.7
      Click  -- at the cursor location
    end MyClick

    This works well, yet requires a script developer to rename all automated scripting performed in the capture/script sequence of auto script, from Click to MyClick. We can do this simple enough with Eggplant's script window find and replace features, while introducing potential for errors, incompleteness and forgetfulness in performing the chore, or myspelling or typos. Any way you slice this task, settling for faulty and error prone test suites is not proper practice.

    To help solve this challenge, there are some powerful message passing features which can be used within SenseTalk which allow you to take control where messages are sent or even passed. One way to intercept commands is by using the 'start using scriptName' in SenseTalk placing the scriptName.script in the backscripts of the message path. The backscripts allow messages to be handled prior to their arrival at the SenseTalk runtime. This gives us the power to override and act upon a message we feel needs to be replaced in the SenseTalk system, or having it globally changes/altered.

    There are other means to do this same thing, like the very powerful and often misunderstood 'to handle <any>' handler, for instance. In our case, we want to supplant the Click Command across the board injecting a time delay for the Adder switch box.
    (** A handler to add a bit of delay in between the move and click of the Eggplant Click
        command.  We basically are replacing the Eggplant Click Command
    @param  n  optional delay value in seconds
    @param  images  the image list to find and click
    params  n, images...
    -- if a delay is provided, use it, otherwise assume an image
    -- and insert it into the images list for use in a loop
    if n is a positive number then
     set omtcd to universal MoveToClickDelay  -- store univ delay if n is provided.
     set universal MoveToClickDelay to n
     insert n before images
    end if
    -- for each image, move, delay and click at it.
    repeat with each item image in images
     MoveTo image
     Wait universal MoveToClickDelay
     send Click FoundImageLocation() to SenseTalk  -- send to ST runtime, avoid recursion.
    end repeat
    if omtcd is a number then set universal MoveToClickDelay to omtcd

    Naming the above Click.script (its in the provided project suite), we can use it in any script by issuing the 'start using click' where the click script is in a helper suite our main suite, or is part of the main suite itself.

    -- set a delay here, you can set it at any point, our replacement script will
    -- use this value to delay xyz seconds.
    start using Click  -- put our click handler into backscripts.
    set universal MoveToClickDelay to 5.0 second  -- time to wait from move to click
    Click TextEdit  --  Click with delay using the universal MoveToClickDelay
    TypeCommand "q"  -- quit TextEdit program
    Click 1, TextEdit  -- Click using our override handler with a delay parameter.
    TypeCommand "q"
    send Click TextEdit to SenseTalk  -- bypass our handler, go directly to ST runtime.
    TypeCommand "q"

    In this case, we see 3 different ways to get various results with move, delay and click transparently (or mostly) from our system without renaming handlers or calls that we have in older or legacy scripts. You can see we set up the universal MoveToClickDelay to a suitable delay time for the KVM switch to clear out the event queue. Then just as we have before, we make calls to the Click (our handler) which is used by issuing the 'start using Click' command.

    The astute Eggplant test developer will also notice in the 2nd invocation there is an override value of 1 being used for the delay value. This will override the universal MoveToClickDelay, without changing the value permanently when called. You can set your universal MovetoClickDelay value and in some special cases where delay times can or should be changed for tuning purposes. You could also just call your Click command with an initial parameter value less or more as needed. Maybe finding the right value is a matter of using a repeat loop and issuing a few commands, leveraging the new delay value override of this Click command.

    Finally, the last Click example command is sent directly to SenseTalk in which no override handler traps the message. Direct sends are powerful in this context, as you may not want your own handler most of the time, yet at times Eggplant's native or direct handlers are necessary for performance and speed tuning when the KVM is bogged down or the added functionality of the override command are not desired.
  • ToddTodd Member
    Recently one of our friends at a famous music software company mentioned to me they would like to have all global variables as a property list. Included here is a quick function call that will give you all globals.
    to GlobalsAsPropertyList  filters...
    	set r to (:)
    	if filters is empty then set filters to globalNames()
    	repeat with each item name in globalNames()
    		repeat with each item filter in filters
    			get value( "global" && name )
    			if filter is in name then set r's (name) to it
    		end repeat
    	end repeat
    	return  r
    end GlobalsAsPropertyList
    This handler can be called with a list of filters, or none at all. You can expect the following results:
    global test1, test2
    set ( test1, test2 ) to ( GUITest, BackEndTest)
    put GlobalsAsPropertyList()  --> (test1:"GUITest", test2:"BackEndTest")
    put GlobalsAsPropertyList(1)  --> (test1:"GUITest")
    put GlobalsAsPropertyList(2)  --> (test2:"BackEndTest")
    put GlobalsAsPropertyList(1, 2)  --> (test1:"GUITest", test2:"BackEndTest")

    You could easily adjust this for universal variables as well, and left to the reader. Also, any local, global or universal variables may be deleted with the 'delete variable' command.
  • ToddTodd Member
    Got some email a long while back, about temp file names in SenseTalk. I finally got the time to spend on the plane back from Italy to write some code and here is one of the many things I'll be posting in the next few weeks. A function to return a temp file name, giving you the power to specify the directory/folder, the seperator used between name components, and also finally the optional file extension.
    params  dir, pre, ext, sep
    if dir is empty then set dir to "/tmp"
    if dir is not a folder then Throw "Not a valid directory exception"
    if last char of dir is not "/" then put "/" after dir
    if ext is not empty and if first char of ext is not "." then put "." before ext
    set the numberFormat to "00"
    get the year & sep & the month & sep & the day
    get it & sep & the hour & sep & the minute & sep & the second & sep & the microsecond
    return  dir & pre & it & ext

    Without too much explaination of the obvious, some sample calls:
    put TempFileName()
    put TempFileName("/tmp")
    put TempFileName("/tmp", "PreFix_")
    put TempFileName( , "UniqTemp_", "tmp")
    put TempFileName("/tmp", , "toss")
    put TempFileName( , , "chuckout", "-")

    produce some sample output

    Thanks go out to Doug Simons of Redstone's Engineering Team for some input on this one.
  • ToddTodd Member
    There are times when cleaning up a line of text is necessary, and deleting all the whitespace (tabs, spaces, non printables) must be done. Here is a little routine to take a string of any line count, and iterate through each line stripping leading and trailing whitespace.
    (** Strips the leading and trailing whitespace on each line of a string
    @param  aString  The string to strip leading and trailing whitespace
    @returns  A string with leading and trailing whitespace stripped
    params  aString
    repeat with each line in aString by reference
    	set it to first to last words of it
    end repeat
    return aString

    A little geeky, and less readable, you could also use the
    set it to words 1 to -1 of it

    Readability, style and intent shall dictate how you script, and we hope this little example proves valuable as part of your scripting arsenal.
  • ToddTodd Member
    When folks turn to Redstone for solutions, we try hard to come up with them... One recent request was a need for random data sources to Black Box text an application, to test it by sending random data to it and see what happens (sometimes called monkey testing, where figuratively putting a monkey in front of the application and computer and have it bang away on the keyboard can produce some interesting application state/conditions to arise).

    First was to develop some scripts to generate city, state and name values.
    (** Returns a random first name from the FirstNames.txt file in the /path/to/this.suite/Data/ folder
    @param path  A data file path, or defaults to "Data/Firstnames.txt" in the suite
    @returns  A random line of the file designated by path
    params  path
    if path is empty then set path to my folder's folder & "Data/FirstNames.txt"
    return  any line of file path

    which accesses a default file in a folder Data in the suite of the handler. The data source could be any place if provided.

    Another handler returns a list of full names (first, middle and last) as property lists which can then be accessed by using obj's first, middle or last properties making for easy access and readable scripts.
    (** Returns a random full name composed of the random first, middle and last as a list of property lists
    @param  n  the number of random names to return
    @returns  a list of full names provided as a property list
    params  n
    set r to ()
    if n is not a number then set n to 1
    repeat n times
    	set j to (:)
    	set j's First to RandomFirstName()
    	set j's Middle to RandomMiddleName()
    	set j's Last to RandomLastName()
    	insert j into r
    end repeat
    return  r

    As a side, geeky way to do the above inner loop you could
    insert ( First: RandomFirstName(), \
    			Middle: RandomMiddleName(), \
    			Last: RandomLastName()) into r

    The family of Random*Name() are provided below in the downloadable suite. Download, unzip and inspect. There are default data files in the suite itself with some random names to start you off.
  • ToddTodd Member
    Often I find myself in need of quick reference to a Eggplant function, or SenseTalk handler. I also like to quit applications when I'm not using them, and Preview goes away immediately after I'm done with a search. Heading to the Eggplant->Help Menu all the time gets old. So, digging into the EggplantCommon.suite, I added the following 'OpenDocs' handler.
    params  toOpen...
    if toOpen is empty then set toOpen to ("SenseTalk Reference","Eggplant Reference","Using Eggplant","Getting Started")
    set folder to my folder's folder's folder & "Documentation/" 
    set files to the files in folder
    if toOpen = "all" then
     set typesAllowed to ( pdf, rtf, rtfd, html, txt, doc )
     repeat with each item in files
      if the fileExtension of it is in typesAllowed then shell "open " & quote & folder & it & quote
     end repeat
     repeat with each item chunk in toOpen
      repeat with each item in files
       if chunk is in it then shell "open " & quote & folder & it & quote
      end repeat
     end repeat
    end if

    Now you can open up the default 4 top documents, or all of the supplied documents, or even filter out by keyword on file name in the AdHocDoBox or call these directly from within your scripts (maybe when a throw is caught and handled for instance).
    OpenDocs  -- default 4
    OpenDocs all  -- opens all the supplied docs
    OpenDocs Eggplant  -- opens only the Eggplant documents

    I also see this as a starting point of a more robust EggDoc opening filter. This would allow someone working on a project to AHDB instruct Eggplant to find and open the current suite/script/project's EggDoc files.

    NOTE: inclusion of scripts into the /path/to/Eggplant.app/... file structure will likely need to be moved when you upgrade the application folder. Ensure your changes are not lost by either linking from within the Eggplant.app folder to your added suite files, or make copies before you delete old Eggplant app folders.
  • ToddTodd Member
    Detecting an operating system is simple, we do it by image or icon visualization. In the Mac world, we look for Mac elements that make it unique; rounded windows, window drop shadows, the dock, etc. In the world of Windows we look for things like shelf at the bottom of the desktop, maybe the default back ground image (in XP, the landscape shot), or even the blue screen of death (just kidding). However, we are looking for images or elements of visual nature, we identify unique things that tell us at some point there is a definitive possibility of a positive identification.

    In the world of Eggplant, it is no different. We just pick image elements that are unique to the operating system, that will always be visible and then associate those with the operating system. For Mac, I choose the apple menu icon at the top left. For Windows XP the bottom start menu. I also for completeness choose the normal and clicked/moused over yet not in the image area. For mac, this is 2 images, and for windows you have three image states.

    Then using the power of the image collections, these are each grouped into unique folders. Optionally you could just have single images as well, both collections and allowable image formats are allowed. Remember, collections are just folders with captured image elements as their contents.
    (** assumes images to use for OS search are in the Suite's images folder
    and each is properly tagged with a meaningful description field **)
    set allowableTypes to ( tiff, icns, ico, pict, gif, bmp, png, pdf, jpg, jpeg, empty )
    set dirPath to my folder's folder & "Images/"
    repeat with each item name in ( the files of dirPath &&& the folders in dirPath )
    	if fileExtension(name) is not in allowableTypes then next repeat
    	if ImageFound(name) then return FoundImageInfo()'s  Description
    end repeat
    Throw "OS Detection Exception", "No known operating system was detected."

    The crux of the code is the above handler, called DetectOS. It first enters the folder and determines the images that are going to be used for the operating system type detection. Then a check of all files and folders is performed, and only file types allowed or folder are used. If a file is usable and located, it's description is returned giving the calling handler a pretty strong clue as to the OS detected. Since the image's description property is used, one must add a meaningful text string to this field in the image element info drawer in the Images tab in a suite.
    get AllConnectionInfo()
    repeat with each item aSUT in it by reference
    	Connect aSUT
    	set aSUT's DetectedOS to DetectOS()
    end repeat
    put it

    And finally here we just loop thru all the known open connections, making a connection to each, and then adding a property to each by reference of it's known operating system type. You could proceed to actually just set the overridden name property and make reference by that name instead in subsequent connections. However, this would need to be sequenced in number if you are testing against more than one of a given type of SUT at a given time. I preferred to just add the DetectedOS property, and leverage that for any setup that is needed as a result of specific OS testing and configuration (start states and such).
  • If you generate exceptions a lot like me, learning the power of logging exceptions is important. Log files can then be used for script metrics, in turn to help during analysis and correcting faulty logic and SUT conditions. This all may help you grasp your current state of Eggplanting.

    I use this LogException handler to log exceptions. You could change the LogWarning calls (I personally like the orange output) to more simple Log calls to avoid the warning count on your script results.
    params  title, e
    if title is empty then set title to "Exception Alert!"
    if e is empty then set e to the Exception
    LogError  title
    LogWarning  "Exception Name: " & e.name
    LogWarning  "Exception Reason: " & e.reason
    LogWarning  "Exception Location: " & e.location
    LogWarning  "Exception CallStack: " & e.callStack
    LogWarning  "Exception ScriptError: " & e.scriptError

    Pretty straight forward, just drop it into your Eggplant Library suite and include as a helper suite when you create a new test suite during your daily Eggplanting.
  • Just had a client reviewing Eggplant for command line only type testing. They didn't want to do any GUI verification. The notion of a site verification script came to mind to show how Eggplant's SenseTalk 'url' command was right for the picking.
    repeat with each item dataRecord in stdin
     CheckSite  dataRecord
    end repeat
    to handle CheckSite  site, aHint, emails...
      get url site
      if it doesn't contain aHint then
       SendMail(To: emails, \
         Subject: "ALERT: a website appears to not be valid...", \
         Body: site & return & "==========" & it )
      end if
     catch  e
      SendMail(To: "[email protected]", \
        Subject: "WARNING: Something is wrong with site checking!!!", \
        Body: e & return & "===========" & return & it )
     end try
    end CheckSite
  • I've been out on the road visiting with folks using Eggplant from the East to the West coasts. Some of our friends are developing or have in market mobile devices in need of GUI testing.

    One in particular is the Nokia N95 (http://www.nseries.com/index.html) running the S60 (http://www.s60.com/life) operating system. With mVNC installed to do VNC <-> bluetooth stack translations, this little device and operating system are prime candidates for device automation and testing.

    To make it easy to test contact information, a simple script was created for our friends in Europe testing this configuration to reduce the tedium of typing characters from a standard telephone's numeric pad. An example of how you may not want a script to look due to the difficult way it reads may be considered:


    As you can see, there quite a few sequences to just punch in a simple name. Why not allow Eggplant and its embedded scripting language Sensetalk along with some thought take the choir out of the task. A powerful idea like repeat looping gives the power take redundant tasks like testing and compress ideas into a series of values or in this case mappings of values. Embedding the keys, and how they are to be mapped to the keypad of a mobile phone is a simple process yet one that needs some thought.
    -- Take a set of values and map to keys in a property list
    set keypad to ( a:(2,1), b:(2,2), c:(2,3), \
      d:(3,1), e:(3,2), f:(3,3), \
      g:(4,1), h:(4,2), i:(4,3), \
      j:(5,1), k:(5,2), l:(5,3), \
      m:(6,1), n:(6,2), o:(6,3), \
      p:(7,1), q:(7,2), r:(7,3), s:(7,4), \
      t:(8,1), u:(8,2), v:(8,3), \
      w:(9,1), x:(9,2), y:(9,3), z:(9,4), \
      " ":("#", 1))
    Each character of the alphabet and a few special keys are handled in this simple SenseTalk Property List. Allowing for simple lookup by character, we also have as the property value a list which is the phone pad numeric key to type and it's count. For instance, to type on the remote mobile device the capital letter "A" the following SenseTalk script will output "01".
    repeat with each item aKey in keys
      repeat with each char aChar in aKey
        repeat item 2 of keypad.(aChar) times
          if aChar is an uppercase then TypeText 0
          TypeText item 1 of keypad.(aChar)
        end repeat
      end repeat
    end repeat
    So, simply each character in the variable keys is swept, we look up the value in the property list, we then use that key to get the count (item 2) and if the character is an upper case letter the "0" is punched so capitalization results. Please remember when reading SenseTalk script, parenthetical notation is used to reference the actual value in a variable rather than as an unquoted literal referencing a particular key as a string. This point is one of the biggest pitfalls with experienced and new users alike and one of the most likely places for errors to occur in scripts.

    We also want to slow down the RemoteWorkInterval to make the characters seem as if someone was punching on the device with their thumbs. There is no need to set the RWI down to less than .25 as a human would be very hard pressed (no pun intended) to type more than 4 chars per second, or about 240 chars per min on a device regularly. If turned down to 0.1, a dual thumbed user would be typing 600 chars per minute, or about 100 words per minute without errors. This is based on the average english word length of approximately six characters including a space of separation. This is simply not possible by even highly trained people using QWERTY keyboards, common enough on DVORAK, and definitely nearly if not impossible on a two thumb device.
    params   n, keys...
    if n is not a positive number then
      insert n before keys
      set n to .25 seconds
    end if
    set orwi to the RemoteWorkInterval
    set the RemoteWorkInterval to n

    Its good form/style and courtesy to Eggplant's global values the way you found them if altering for speed and performance issues during script execution. Please find another posting describing the ins and outs of the StoreEggplantSettings and RestoreEggplantSettings scripts included in this project.

    And finally, the result allows a more readable form to be used when typing text to a remote mobile device.
    TypePhoneKey  "Todd Nathan"
    TypePhoneKey  "Support Services Manager"

    which will in this case instruct the mVNC server to output
Sign In or Register to comment.