[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

  try
    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

  try
    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.

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:

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

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

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

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:

AskForValidEmail "Enter Email Address", \
  "Destination Email For Reports", \
  "errors@yourcompany.com"

set validEmailAddress to the result

put validEmailAddress

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.

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.

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.

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.

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.

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.

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

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.

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

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 ā€™ 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
else
 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.

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.

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

/tmp/20070722230937500207
/tmp/20070722230937525050
/tmp/PreFix_20070722230937594034
/tmp/UniqTemp_20070722230937638956.tmp
/tmp/20070722230937679527.toss
/tmp/2007-07-22-23-09-37-713546.chuckout

Thanks go out to Doug Simons of Redstoneā€™s Engineering Team for some input on this one.

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.

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.

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
else
 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.

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...

 try
  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: "person@superdomain.com", \
    Subject: "WARNING: Something is wrong with site checking!!!", \
    Body: e & return & "===========" & return & it )
 end try
 
end CheckSite