[eggPlant Functional] Useful SenseTalk Functions

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:

http://img186.imageshack.us/img186/8826/callchriskw2.th.png

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

  0866633#06062844266
  0707070788776667778#0707070733777
  888444222337777#062662433777

In some previous posts I have discussed a need for generalized solutions to changing values of state in the Eggplant global variable environments. Generally a push/pop approach to state change works allowing someone to quickly store/restore value sets. We offer to you a simple Last-In-First-Out approach which can be viewed as a stack of dinner plates of which the topmost ‘plate’ holds the entire set of keys and values of your Eggplant environment. Below is a demonstration Main.script and the supporting StoreEggplantProperties/RestoreEggplantProperties scripts.

(**
   Start state:  SUT, OS X FUS 10.4.10 w/HD icon unselected
   Eggplant, MouseDragSpeed = 0 for 'instant' moves
**)

MoveHardDriveIcon (300,300)  -- drag speed set to zero.
StoreEggplantProperties  -- for restoration later.

set the mouseDragSpeed to 1  -- slow the drag down
MoveHardDriveIcon  -- visible drag speed...

RestoreEggplantProperties  -- reset to last EP settings.
MoveHardDriveIcon  -- move at speed restored.

The MoveHardDriveIcon needs little explanation, it moves the first found Hard Drive icon from its current location to a point provided as an optional parameter, or ( 100, 100 ) if a valid point is not provided. Of course this is for demonstration purposes, and likely would rather have it toss an exception if a non valid point is provided.

The process of storing every single Eggplant setting is a little tedious. Discover and description of each setting is located and clear in the Eggplant Referenence manual. For ease of reading, each section of the preferences settings panes has been defined in a seperate list, then combined in AllSettings as a list. Each item is then used as a key in the dynamic construction of a property list of name value pairs. This is inserted into the end of the universal variable EggplantPropertyList which is in turn used by the complimentary and accurately named RestoreEggplantProperties handler.

-- The meat and potatoes of the storage process.
repeat with each item aSetting in AllSettings
		set EggplantPropertyList.(aSetting) to the ( aSetting )
end repeat
insert EggplantPropertyList into EggplantPropertyLists

And finally the core of the restoration process (see the accompany download suite for the entire scripts) which reads the last item of the EggplantPropertyList variable and uses that property list to restore Eggplant to a previously stored state.

-- Core loop for Eggplant restoration from a property list
repeat with each key in the keys of anEggplantPropertyList
		set the ( key ) to anEggplantPropertyList.(key)
end repeat
delete last item in EggplantPropertyLists   -- remove it

NOTE: although this was demonstrated in Main.script against an OS X SUT, you can use this set of scripts with any SUT system.

Left as a “Reader Challenge”, you may desire to expand the RestoreEggplantProperties handler script to allow the passing of a positive numerical value indicating which state of the LIFO Eggplant state stack is used during restoration and then subsequently removed. You may also want to abstract this into a state stack object, which manages itself, as well as allowing duplicatation and removal of states (more advanced but a much more powerful abstraction), allowing for rapid state to state transitions. Included in either state management approach, additionally naming states may be of value in your environment, allowing rapid identification, location and re/use states for various testing environments.

As a point of ease, there is a function that slipped past my scrutiny screen and it is called GetOptions() which of course returns the Eggplant global options/preferences. Instead of a nifty little loop, you could simply call that and store it.

insert GetOptions() into universal EggplantGlobalPropertys

I hope this did not discourage others from taking the simpler route as Doug Simons pointed out after the above post. The beauty of having so many read this forum is many ways of doing the same thing come to light.

Quick and dirty little utility to do extension conversion.

I use this extensively while working with .script files and then moving them to command line production SenseTalk .st files. You will have to connect up with Thoughtful Software (the folks that license SenseTalk for use in Eggplant and other embedded language tools) for information on the STcli availability. Yet, this is just as a handy utility to convert files on your system from possibly ‘.txt’ to other compatible extension file formats like ‘.text’. You could even remove extensions by playing around with the ext1 and ext2 values, setting the destination extension to empty for instance.

universal myLastFolder

-- change these if needed, possibly from .txt <-> .text
set ( ext1, ext2 ) to ( ".st", ".script" )

if myLastFolder is empty or myLastFolder is undefined then
 set myLastFolder to "~"
end if

answer directory prompt ext1 & " -> " & ext2 & " : Select folders" in \
  folder myLastFolder allow multiple
set dirs to it
if dirs is empty then exit all
if number of items in dirs is 1 then set myLastFolder to dirs
else set myLastFolder to the folder of ( item 1 of dirs )

answer "Rename files to what extension?" with ext1 or ext2 or "Cancel"
if it is Cancel then exit all
if it is ext1 then set origExtension to ext2 else set origExtension to ext1

repeat with each dir in dirs
 repeat with each name in the files of dir
  if name ends with origExtension then
   set dest to name
   replace origExtension in dest with it
   try
    rename file name to dest
    put name & " --> " & dest
   catch  e
    put "FAILURE: to rename " & name & " --> " & dest & " - " & e
   end try
  end if
 end repeat
end repeat

This script will prompt you for the folders which contain files to rename. It will also prompt you for which direction you want to rename, from ext1 to ext2 or the other way around.

I used a exception handler just in case a rename didn’t work properly. You can safely remote it with little chance of it failing. Its there to show and sell safe computing with SenseTalk :slight_smile: You will also notice a one line if … then … else variable setting conditional. Most often you would likely put this on a 5 line

if condition then
  doSomething wonderful
else
  doSomething magical
end if

but in this case it made sense to include it all on one line. Looks better, and is appropriately terse, to the point.

And finally. This script will remember the directory you last selected or the parent of multiple folders last selected during the host application run (in this case Eggplant’s current run) by using the staying power of a universal variable ‘myLastFolder’. The name isn’t magical, but the declaration clause ‘universal’ is special. Once declared, that variable will retain an assigned value between script and suite runs. A very handy offering when I go back and forth in a root folder with many sub folders and projects needing renaming.

I’m sure you will find this powerful and useful too. Explore, there is a lot of goodness in this example of a handy utility to add to your SenseTalk power toolbelt.

Here you will find a simple search image handler allowing you up and down search directions. Most of the time it makes sense to standardized by moving the scroll bar to the top of the view, and proceeding downward. This helps in debugging by simplifying things a bit. However, there are times when it is clearly faster to go to the bottom of the list and start there. Using the homeKey and endKey for TypeText, you can pick either quickly.

to scrollTo  imageName, direction, waitPeriod
 if direction is not in ( "top", "bottom", "left", "right" ) then \
   set direction to Bottom
 if waitPeriod is not a positive number then set waitPeriod to one quarter second
 repeat until ImageFound(waitPeriod, (imageName,(direction & Scroller)))
  if direction is Top then
   TypeText  pageUp
  else if direction is Bottom then
   TypeText pageDown
  end if
 end repeat
end scrollTo

and finally some sample code to get things rolling. You will see in the first call to the handler scrollTo, I have left out the direction and wait period, both default to bottom and .25 seconds. Remember, default top down, and 1/4 second wait time, which is fine for most fast machines. Increase this period to 1/2 or 1 second, or change the script to use the default value set in the Eggplant preferences panel. You are in charge, so take it :slight_smile:

TypeText  homeKey
RefreshScreen  -- to avoid false positives
scrollTo  871

TypeText  endKey
RefreshScreen
scrollTo  871, top

In the second call, we go to the end of the web page in question, and force a refresh of the screen to avoid false positives. Then we scroll to the top of the page looking for the image in question. Again, this time using the .25 seconds as default. Slower machine or connection, play with increasing this value.

Here is a little ditty I wrote to scrape some jokes off a page for validation and reuse. You can expand this script to test against various browsers simply by turning the topLeft, bottomRight, topScroller, bottomScroller into image collections while adding additional SUT images to your suite. Also to note, is the trick to scroll up a bit, scrape, then scroll back down if the bottom of the page has not been reached and scraped.

-- URL in browser set to
-- http://4q.cc/index.php?pid=top100&person=chuck

set path to "/tmp/chucknorrisjokes.txt"
delete file path
TypeText  homeKey

repeat forever
 set topLefts to EveryImageLocation(topLeft)
 set bottomRights to EveryImageLocation(bottomRight)
 if not aboveAndLeftOf(item 1 of topLefts, item 1 of bottomRights) then \
   delete item 1 of bottomRights
 repeat with each aBottomRight in bottomRights
  DragAndDrop item repeatIndex() of topLefts, aBottomRight
  TypeCommand  "c"
  get remoteClipboard()
  if it is not in file path then put it & return after file path
 end repeat
 if ImageFound(bottomScroller) then exit repeat
 else TypeText upArrow, upArrow, upArrow
 TypeText pageDown
end repeat

to aboveAndLeftOf  pt1, pt2
 return ( item 1 of pt1 <= item 1 of pt2 ) and \
   ( item 2 of pt1 <= item 2 of pt2 )
end aboveAndLeftOf

The scraped data is stored uniquely into a file, and at the end of the run we would need to either manually validate the existence of 100 expected unique jokes, or do it with script. Writing such a script would need a baseline data source of expected values. This is often the case in proper test conditions, but unique enough of a problem set to be left to the reader.

Although DragAndDrop has been deprecated to the EP 4.x Drag and Drop commands, I still like the concise nature of DragAndDrop. You also may note the use of a inline helper handler called AboveAndLeftOf pt1, pt2… This should be moved to a shared SenseTalk library. Last year I discussed it and related relative positional handlers for points in a posting.

There are many times when I’m writing interfaces to services like a server for gaming or data from a database where I need to know the contents of a string. However, often working in UTF-8 leaves me with white space at the start or end of a string, or even in the middle that I’m unaware or not easily noticable with my tired eyes.

To strip the start and end white space off, SenseTalk has a handy little trick to do this with the chunk expression words…

get words first to last of someContainer

which will end up stripping the leading and trailing white spaces. However this does not do any manipulation of white space to the ‘inside’ of a string. This is great news, leaving tabs and spaces as they were. In the case of my gaming parse scripts, I need then to know what are words two and three to identify the player which a command was issued, and the command itself.

set ( player, command ) to ( second word of it, third word of it )

This method of using list assignment is compact and saves an extra line of script and to me personally is a best practice in use where 2, 3 or 4 short named variables are going to be used. However this quickly becomes ackward using long descriptive container names. You will want to move to single line assignments for such cases.

set thePlayersNameOrLogin to word 2 of it
[...]
set aCommandIssuedByAPlayer to word 3 of it

and finally what I was hoping to share with you in the first place, was the ability to find odd unprintable UTF-8 characters in a string that may be causing problems or even signify a special or critical condition in data transmission quality. In my case I have a server that at times pumps out extra characters, linefeeds and returns in the communications coming back from the server. This can cause issues, so I simply delete them all in a line of text so that my log data doesn’t have extra vertical line feeds.

set ( debug, verbose ) to ( true, true )
repeat until someCondition
  read a line from socket gameServer
  delete all return from it
  delete all linefeed from it
  get words first to last of it
  if first word of it is 12 then  -- 12 means a personal message
    set ( player, command ) to ( word 2 of it, word 3 of it )
    [...]
    if debug then put it
    if verbose then put stringToNums(it)  -- big list of numbers
  end if  -- ignore anything but 12 type commands
end repeat

and here we have the function to output a nice bracketed list of UTF-8 chars to numbers making it fairly easy to identify weird or unexpected characters that may otherwise not be seen in the output.

to stringToNums  aString
  set r to empty
  repeat with each char in aString
    put "[" & charToNum(it) & "]" after r
  end repeat
  return  r
end stringToNum

This should help in situations where a string may otherwise look like you may expect it should, yet the string ends with unprintable characters and causing problems in your character by character parsing. I have found this handler handy to have in my SenseTalk function library. I hope you find it useful too.

Proper bug reporting helps in quick solutions/fixes/workarounds. This set of handlers and downloadable example set will give you quick and easy access to your system information. This is only one of many ways you could perform this all too repeatative task, which of course computers excel… Mundane.


get SystemGlobalProperites()
put the long date & " - " & the long time
repeat with each key in the keys of it
	put format("%15s : %s", key, it.(key))
end repeat

and of course the horse that does all the work…


set props to <<hostName>>

repeat with each prop in props
	get words 1 to -1 of first item delimited by "(" of prop
	set r.(it) to the ( it )
end repeat

return  r

Property values were taken from the Release Notes, which I highly recommend reading from start to finish.

Expanding on this theme further and shove the data into the appropriate fields of a Mail.app message is left to the astute SenseTalker.

Just thought I would add this little suggestion in response to an earlier post. Instead of doing this:

set ( player, command ) to ( second word of it, third word of it )

note that it’s possible to simplify by selecting a list of specific words, like this:

set ( player, command ) to words (2,3) of it

This could be especially nice when you’re using a longer variable name than ‘it’. :slight_smile:

Doing an inventory of script files can be challenging. For instance, if you want to know all files that say end with “.st” and “.script” from a particular folder down in the folder hierarchy. You can do this using the UNIX ‘find’ (try “man find”) command which is powerful but cryptic at best. There is the Mac OS X Spotlight find feather, a powerful tool worthy knowing, but at times annoying and no very specific enough for my needs. I also know many people turn of Spotlight due to the CPU consumption it takes for dynamic indexing. What I often want to accomplish is a simple directory and subdirectory catalog of all scripts, so this handler was born. You may want to insert this into your EggplantCommon.suite, found inside the Resources directory of your Eggplant.app package.

params  path, outputPath

if path is not a folder then	 set path to my folder
else if char -1 of path <> "/" then put "/" after path

if outputPath is not a boolean then set outputPath to false

repeat with each subfolder in the folders in path
 HandlerNames  path & subfolder, outputPath
end repeat

get ( item 1 delimited by "." of each item of ( the files in path ) where each ends with ".script" or each ends with ".st" ) joined by ", "
if word 1 of it is not empty then
 if outputPath then write path & " : " to output
 put it
end if

This is a recursive handler, which means it can and often does call itself. It does this by isolating the folders of the script’s location folder. If however a folder is provided to the handler, a new base folder search other than the default script folder is used. This allows you to drag and drop folder paths into the run window as well to output the paths with an optional second parameter boolean value.

HandlerNames
HandlerNames aPath, true  -- would output verbosely

There are other techniques/ways, however I find this powerful and useful tool quickly allows me to get a handle on handler names. Ninety plus percent of the time I use a simple “HandlerNames”, and because I have set up a chain of start using and handlers in the aforementioned suite I’m able to do much much more (explained in a future posting). I also find that if I’m using a handler enough, I push it down into a common suite or library and begin using it as part of my workflow. You too may find this the case, and find this handler very helpful.

One final step to include would be for the astute sensetalk to include and handler that would parse help requests to objects. Say a usage/help object, which helps all objects. This way you could do a “HandlerNames” and then quickly see a handler you want to try, but you don’t maybe remember its usage. Then you could just ‘send usage to objName’ or just type ‘help object’ and output of the proper usage would be presented. Very powerful, very simple to implement, you just need to spend a bit of time understanding the message passing system of SenseTalk.

Do some homework by digging into the SenseTalk Reference manual, in particular Objects and Messages. You’ll be amazed at what you come up with for a custom solution to your needs.

NOTE: Please do not confused proper and thoughtful design and workflow analysis to gratifying haste makes waste approach. Understand your environment needs, your business needs, nobody will question you if you have thought about it even if it is tailored to your personal needs. This adds a lot of value to your position and adds to your knowledge of various approaches. Knowing why one is better than another and how they work in your world is key to being a continued asset to your company and its stake holders.

Also note that almost every single smalltalk system on the planet uses “.st” suffix, so don’t be surprised if you come with a lot of non SenseTalk scripts when doing a system side “HandlerNames /Users/” or other high level searches.

A little fun here today, its Friday after all.

10.5.x Leopard has a much improved and understandable Text-To-Speech system and a new voice called Alex. Alex can and will say stupid (and useful) things to you and others if you just command it. If you dare, man page the ‘say’ command in the Terminal/iTerm.app vt100 environment.

Here we have a not so big challenge, announcing the time and suggesting doing something else for a bit is a good thing for those of us that sit at the computer far far far too long at spells. With the healthy problems computer professionals have, RSS, pooling of blood in the extremities (particularly the legs), shoulder problems and even eye strain issues this little ditty does the job well to remind me to shower :). I laugh, and think about how it is a good thing its just a computer telling me to take a rest and not some nagging impotent boss from “The Office” or “The Drew Carey Show”.

get the time

if the hour is zero then  -- announce for first hour of day what day it is.
 shell "say Today is " & the long date
 put "00" into chars 1 to 2 of it  -- correct a bug with say command
end if

shell "say The" && \
  any item in ( "bleeping", "freaking", "", "darn", "lovely" ) && \
  "time is" && it && \
  any item in ( ", so", ", " ) && \
  any item in ( "wake up", "make a change" ) && \
  any item in ( "and", "or", "exclusively or", "in a boolean fashion and" ) && \
  any item in ("possibly", "maybe", "definitely") && \
  " you" && \
  any item in ("should", "would", "could") && \
  any item in ( "think about", "contemplate, to consider, to evaluate, to decide on", "deciding on" ) && \
  any item in ( "taking", "doing", "exiting and doing" ) && \
  any item in ( "a cup of" & \
  any item in ( "java", "coffee", "tea", "soda", "water" ), "a walk", "some sleep", "a bit of rest", "a" && \
  any item in ( "hot", "cold", "warm", "boiling", "freezing", "" ) && \
  "shower", "dinner", "lunch", "breakfast", "a meal" )

You could include this in an .st or .script file, and direct either your embedded ST application to run it, or even talk to Thoughtful Software (http://www.thougthful.com) about other means of executing SenseTalk (http://www.sensetalk.com) scripts from the CLI. I run it as an .st script, and let Cronnix (http://www.abstracture.de/projects-en/cronnix) do the scheduling.

Either way this is a good and fun way to remind us that we need to take a few mins every 30 to 60 mins and stretch and exercise something other than our brains.

Try this on for size:

shell "say super calla fragia listic expee alla docious"

And finally you can leverage all this for announcements that change due to failures or success in your scripts and environment.

try to do Something
sayRunStatus the exception

try to do "Something(goAheadAndFail)"
sayRunStatus the exception

to sayRunStatus  e
 if e.name is empty then
  shell "say" && "SUCCESS, your scripts works fine!"
 else
  Log  e.name && "-" && e.reason
  shell "say" && "Uh oh, you have encountered a problem."
  shell "say The Exception information follows"
  wait 1 second
  shell "say" && e.name && e.reason
 end if
end sayRunStatus

to something  trigger  -- do nothing but throw an exception
 if trigger is not empty then
  Throw "Some Type of Exception", \
    "Please place exception information here, which should be detailed."
 end if
 return "goodness"
end something

Enjoy, and have a great time with your SenseTalk’ing (literally)

I do all kinds of stuff generating temp files instead of using dynamic variable creation. These serve purposes like storing intermediate variable or conditions/results that I want to track. This can be done with Log, LogWarning or even LogError and exception handling… However, often my needs are for template files that will be merged later (being generated programmatically rather than statically). This leads to the need of temporary file names and generating those names.

Once again, a simple but powerful solution is presented. You’ll notice also a clean and simple comment header that should be employed whenever you are moving scripts and handlers into your libraries. These headers are not only a best practice in software engineering, but also in automation and functional programming/scripting. You can use the Doug Simons radioactive EggDoc to run against this which will generate a nice clean html file for your documentation efforts, you are doing documentation of your ‘agile’ efforts I hope.

(**
  Returns a unique file name that is almost uarenteed not exist.
  Will try up to 10000 times to create a filename that is unique
  with a large spread of 90 million possibilities.  Chances of this
  routine not creating a unique file name are very remote

@param  dir     optional directory, defaults to "/tmp/"
@param  prefix  the front, defaults to "TempFile"
@param  suffix  end which is often left empty.
@param  ext     the file name extension, defaults to empty.
@returns        a unique full path name
**)

params  dir, prefix, suffix, ext

set numOfAttempts to 10000
set defaultDir to "/tmp/"

if there is not a folder dir then
 LogWarning  "Passed parameter 'dir' invalid, passed as '" & \
   dir & "'... doesn't exist."
 set dir to defaultDir
 LogWarning "The parameter is now defaulted to '" & dir & "'"
else if dir is empty then
 set dir to "/tmp/"
end if

if last char of dir is not "/" then put "/" after dir
if prefix is empty then set prefix to "TempFile"
if ext is not empty and first char of ext is not "." then \
  set ext to "." & ext

repeat numOfAttempts times
 set path to dir & prefix & \
   random(10000000, 99999999) & suffix & ext
 if there is not a file path then return path -- exit when unique
end repeat

-- as a last resort, we'll toss an exception if we are unable
-- to generate a unique filename numOfAttempts times
Throw  "Unique File Name Creation Exception", \
  "Was not able to create a file name that was unique trying" && \
  numOfAttempts && "times."

Some example calls to this and returned values follow…

put UniqueFileName("/tmp")
/tmp/TempFile78212738
put UniqueFileName("/tmp")
/tmp/TempFile61775655
put UniqueFileName("/tmp", Todd)
/tmp/Todd26460678
put UniqueFileName("/tmp", Todd, Nathan)
/tmp/Todd18014018Nathan
put UniqueFileName("/tmp", Todd, Nathan, abc123)
/tmp/Todd24873800Nathan.abc123
put UniqueFileName("/tmp", , , rtf)
/tmp/TempFile52695153.rtf

And of course for those folks that want to use the underpinnings of the hosting operating system, in my case currently OS X, you can use the ‘secure’ means of mkdir and mktmp using the bash shell $RANDOM generator. To read more on this, you can turn to http://wooledge.org:8000/BashFAQ/062 which has a terse but good example set.

You could easily make the above SenseTalk handler deal with a unique folder name generated by changing the

if there is not a file path then

to the equivalent for a folder

if there is not a folder path then

and that effort to make it work is left up to the astute SenseTalker. Good luck, and happy scripting.

And of course a final note. If you want to avoid looping for uniqueness you could also use milliseconds, or a date-time combination to stamp each file as unique. This technique works close to 100% of the time since time is a continuum which if using small enough time stamp increments (as in milliseconds) you will not likely have a single command execute in less than that time to execute in SenseTalk (at least not until we get to 20-30Ghz mega count core computers, and we’ll worry about that later.)

Just for fun exercise today. I was reading along on metrics in C++ and C for hysterically historic meloncholic reasons today. And what did I find in the SQE PowerPass access (www.sqe.com) area a little article on the right way to handle checking for items in a iterator before iterating over the iterator (what a mouthful). I found this almost self folding, solving itself as stated which is often a hint of unnecessary complexity.

For our example, lets start with some variable setting for the simple elapsed time testing…

set aList to ()
set iterations to 10 ^ 5

Then we’ll execute a simple repeat loop with some time wrapper script around it for reporting purposes.

set sTime to now  -- the really wrong way to do things.
repeat iterations times
	if the number of items in aList is not zero then
		repeat with each item in aList
			do somethingWonderful
		end repeat
	end if
end repeat
put "Elapsed Time: " & now - sTime

and then execute a simple repeat loop with some time wrapper code around it for reporting. What we find when we alter the object checking (either for existance, or contents of items, or emptiness) is significantly different timings. Some cases are useful to note…

set sTime to now  -- the possibly right way to do things.
repeat iterations times
	if aList is not an empty list then
		-- [[ ... loop removed for brevity ... ]]
	end if
end repeat
put "Elapsed Time: " & now - sTime && seconds


set sTime to now  -- the maybe right way to do things
repeat iterations times
	if aList is not () then
		-- [[ ... loop removed for brevity ... ]]
	end if
end repeat
put "Elapsed Time: " & now - sTime && seconds


set sTime to now  -- the right way to do things
repeat iterations times
	-- [[ ... loop removed for brevity ... ]]
end repeat
put "Elapsed Time: " & now - sTime && seconds

The point of this exercise is not to point out the flaws, weakeness or particular right or wrong way to do things… But more of a method of understanding your needs. In this case, we don’t need to check for the object being empty or void of items at all, due to the nature of the repeat loop inherintly doing that for us by default. A loop over any chuck in a container which has non of those chunk types in it will result in the fastest execution with no checking for that chuck type at all.

Here are the numbers I got on an iMac with 4 gigs of RAM, 2.0 ghz system running SenseTalk in a hosted application called Eggplant. The numbers are clear on the winning approach.

Thu, 5/8/08 9:56:21 PM	START		Running BenchmarkContainerChecking.script
Elapsed Time: 6.357546 seconds
Elapsed Time: 5.063503 seconds
Elapsed Time: 2.60024 seconds
Elapsed Time: 0.5716 seconds
Thu, 5/8/08 9:56:35 PM	SUCCESS		Execution Time 0:00:14 BenchmarkContainerChecking.script

Experiment, write your own metric and timing scripts. Just like a master mechanic learns the tools and language of the trade, it is in your best interest to do the same. Read at least once a month the entire SenseTalk Reference Manual, there are so many tidbits of goodness tucked away in it that I still find fun to discover weekly.

The need arose last week to click on all instances of a image element, as they become visible as a result of elements moving out of the way. The power of recursion (a technique where a function calls itself with possibly terminal values for call depth control) is what first came to mind. Many problems can be solved recursively.

params  image

if image is empty then set image to "close_window"
repeat with each loc in EveryImageLocation(image)
	Click  loc
end repeat

if ImageFound(1, image) then CloseAll

return

You also have to be careful that this can loop forever if the elements eventually don’t go away from the screen. For intance, if you were to supply an image element of the zoom button on a window title bar, this loop would go forever until either the system ran out of memory and Eggplant or SenseTalk would exceed the default recursion count limit.

You can take a look at how to tweek the call depth limit in Eggplant.

http://www.redstonesoftware.com/phpBB2/viewtopic.php?p=1931

Developing vocabulary in scripting system for automation is often part of what we all do, eventually establishing some standards in our organization to facilitate effective communications of ideas, quality and easy of maintainance of scripts. Externalizing script creation and management, decoupling if you will, is often part of the keyword driven approach in test automation.

Eggplant has build in functions and vocabs, which form the foundation to a much richer custom solution for any organization. In the search to describe a limiting search region, I have come up with a fairly flexible “LimitSearchTo regionName, additionalRegions” handler, with its own help/usage system (for you unix heads)…

properties
 help: "Usage: LimitSearchTo  [ Menus | Dashboard | Content | Help ], optionalProperties"
end properties

params  regionByName, optionalProperties

if regionByName is "help" then
 put my help
 return
end if

set regionProperties to ( "All": the remoteScreenRectangle, "Menus": Menus, "Dashboard": Dashboard, "Content": Content )
if optionalProperties is a property list then \
  add properties optionalProperties to regionProperties

if regionByName is in the keys of regionProperties then
 get regionProperties.(regionByName)
 if it is not a rectangle then get everyImageLocation(it)
 set the searchRectangle to it  -- should be two image locations
end if

Without belaboring the details, fundamentally this handler embraces a LOT of SenseTalk and objects. You have initial properties of which is a static help string property by name ‘help’. This allows you to quickly ask the object/script for some help in using itself. This string is internalized, so if you change the script’s behaviour you also will need to be in the habit of changing/updating both the help property and the EggDoc comments. I encourage as a standard in house to use EggDoc comments for automatic documentation creation along with test runs being part of your organizations build cycles (daily or more often).

You’ll also notice this handler does some fun stuff with a mix of data types. We dynamically get the screen rectangle and use it for the “All” directive. We also make an assumption that the name of the region is a collection of 2 images likely with offsetting corners of a rectangle of interest. The optionalProperties also gives you the flexibility to add dynamically a list of regions with names or rectangles on the fly. This gives you the added power to expand your vocabulary dynamically as projects and practices demand. Nothing is static, so developing your scripts to be open ended with well defined behavior is a good idea in all your efforts.

Here is some demonstration script to drive validation of the LimitSearchTo handler:

-- Demonstrate vocabulary development...  Each 'region' is a two or more collection
-- of images, or a reference to a rectangle (within the LimitSearchTo handler itself)
set additionalRegions to ( region1: (1,2,3,4), region2: (4,5,6,7) )
repeat with each aRegionName in (( "Menus", "Dashboard", "Content", "All", "Help" ) \
  &&& the keys of additionalRegions )
 LimitSearchTo  aRegionName, additionalRegions
 if region is not in ( "All", "Help" ) then LimitSearchTo  All
end repeat

Please take this as a base for your own vocabulary development efforts, and comment about what you have found to be useful. Below is a run result output for the following exercise script for LimitSearchTo…

Wed, 5/21/08 8:51:34 AM	START		Running Selection from Sandbox.script
Wed, 5/21/08 8:51:34 AM	everyimagelocation	Menus	found 2 images
Wed, 5/21/08 8:51:34 AM	set		SEARCHRECTANGLE = ((5,245),(199,714))
Wed, 5/21/08 8:51:34 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Wed, 5/21/08 8:51:35 AM	everyimagelocation	Dashboard	found 2 images
Wed, 5/21/08 8:51:35 AM	set		SEARCHRECTANGLE = ((2,113),(1024,204))
Wed, 5/21/08 8:51:35 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Wed, 5/21/08 8:51:35 AM	everyimagelocation	Content	found 2 images
Wed, 5/21/08 8:51:35 AM	set		SEARCHRECTANGLE = ((201,206),(1020,713))
Wed, 5/21/08 8:51:35 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Wed, 5/21/08 8:51:35 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Wed, 5/21/08 8:51:36 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Usage: LimitSearchTo  [ Menus | Dashboard | Content | Help ], optionalProperties
Wed, 5/21/08 8:51:36 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Wed, 5/21/08 8:51:36 AM	set		SEARCHRECTANGLE = (1,2,3,4)
Wed, 5/21/08 8:51:36 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Wed, 5/21/08 8:51:36 AM	set		SEARCHRECTANGLE = (4,5,6,7)
Wed, 5/21/08 8:51:36 AM	set		SEARCHRECTANGLE = (0,0,1024,768)
Selection Executed in 0:00:02

I hope you have found this useful, please feel free to share in your own threads findings and ideas about your particular situation, needs, wants, practices and visions.

While debugging some text streams I have been working on, I decided to make my life a bit easier in knowing what a particular character string contained. Remember what 09 is, or 10, or even 13 means… linefeed, newline, return (or did I get this wrong too) is a bit much to recall let along get correct. Instead you can use the handy charToDesc() handler to spit out the details of a string’s character constituents.

put charsToDesc("Todd" & space & "was" & tab & "here!!!" & return)

Offset    Char  Oct     Dec     Hex     Cntrl   Description
       1: T	124	84	54	Uppercase T
       2: o	157	111	6f	Lowercase o
       3: d	144	100	64	Lowercase d
       4: d	144	100	64	Lowercase d
       5: SP	40	32	20	Space
       6: w	167	119	77	Lowercase w
       7: a	141	97	61	Lowercase a
       8: s	163	115	73	Lowercase s
       9: HT	11	9	9	^I	Horizontal tab, move to next tab stop
      10: h	150	104	68	Lowercase h
      11: e	145	101	65	Lowercase e
      12: r	162	114	72	Lowercase r
      13: e	145	101	65	Lowercase e
      14: !	41	33	21	Exclamation mark
      15: !	41	33	21	Exclamation mark
      16: !	41	33	21	Exclamation mark
      17: LF	12	10	a	^J	Line Feed

A bit of a warning is in order. If you feed this handler long strings, it will produce long results.

(** a long version of the numToChar() function. 
Gives details of the characters (mostly designed for hidden chars,
but does deal with most of the ASCII table)
Most table data ripped/borrowed/lifted directly from
http://www.robelle.com/smugbook/ascii.html
Blame them for any errors or omissions.
**)

params  aString

get asciiTableDetails()

put "Offset    Char  Oct     Dec     Hex     Cntrl   Description"
repeat with each char aChar in aString
	set s to each line of ( lines 2 to -1 of it ) where word 3 of each is charToNum(aChar)
	write format("%8d: ", repeatIndex())
	if number of items in s is not 1 then put aChar
	else put item 1 of s
end repeat


(** assumed unique entries for each char in the list.
ASCII based, does not deal with UNICODE currently,
however you are welcome to expand and do this work yourself :) **)

to asciiTableDetails

	return <<Char>>)

Which would return to your script the bare essential text of the page in question. This can be very powerful for simple text search in a page result instead of wading thru a bunch of possibly troublesome tags in the result.

As a side note, yet marginally interesting to a technical historian, is the history of the ASCII character set with some tidbits of how it may be related to Morse Code (digital, or descrete signals). A good read while you are processing possibly large file sets with hidden characters and their meanings [NOTE: you may want to perform all this within SenseTalk containers instead of output to a Eggplant run window].

http://www.wps.com/projects/codes/index.html

And finally you may want to use the tried and true tool ‘cat -vt’ to help detect hidden unprintable characters. First ‘put’ the contents into a file path, and then issue ‘cat -vt filepath’, you can ‘man cat’ for more information on the CLI options to cat. The latter two steps would normally be done from a VT100 terminal window, of which Terminal.app comes with OSX. Another CLI tool that just rocks blocks, is iTerm.app (available online at

http://www.versiontracker.com/dyn/moreinfo/macosx/17484

Good luck and happy SenseTalkin’ with ya!

Here you go, a quick and easy front end for searching a file for a string and placing the search results into a file, pasteboard, or into the run results window. Along with GUI and speed, this script remembers old used values between script runs, as well as being a tutorial on how to make life easy on yourself and others by scripting gently.

params  resultsPath, aStringToFind, saveToPath

universal  lastSearchStringEntered, lastSearchPath, lastSavePath

set tempFilePath to "/tmp/TempFile-" & the long seconds split by "." joined by ""

if resultsPath is empty or resultsPath is not a file then
 -- Get the file path to search
 if lastSearchPath is empty then set lastSearchPath to UserInfo().HomeDirectory
 answer file "Enter or select the results file path:" in folder lastSearchPath
 if it is in ( "Cancel", empty ) then exit all
 set resultsPath to it
 set lastSearchPath to the folder of resultsPath
end if

if aStringToFind is empty then
 -- Ask for the string to search for
 if lastSearchStringEntered is empty then set lastSearchStringEntered to "LogError"
 ask "Enter a string:" title "Find a substring" with lastSearchStringEntered
 if it is in ( "Cancel", empty ) then exit all
 set aStringToFind to it
 set lastSearchStringEntered to aStringToFind
end if

-- Request if user wants to save to file or pasteboard
answer "Save to Pasteboard or a File" with \
  "Pasteboard" or "File" or "Standard Out" title "A place for salvation..."
if it is in ( "Cancel", empty ) then exit all

set foundStuff to each line of file resultsPath where each contains aStringToFind
if saveToPath is empty or saveToPath is not a file then
 -- Either act on the File, or stuff it all into a pasteboard.
 if it is "File" then
  if lastSavePath is empty then set lastSavePath to UserInfo().HomeDirectory
  ask file "Where to save results" title "Save data to file" in folder lastSavePath 
  if it is in ( "Cancel", empty ) then exit all
  put foundStuff into file it
  set lastSavePath to the folder of it
  open it with TextEdit  -- could put a open or not here???
 else if it is "Pasteboard" then
  put foundStuff into file tempFilePath
  shell "cat " & quote & tempFilePath & quote & " | pbcopy"
  delete file tempFilePath
 else
  put foundStuff
 end if
else
 put foundStuff into file saveToPath
end if

Most of this script is a wrapper around various parts of a simple need to get data from a file. In eccense there is one line that matters, where each contains aStringToFind :slight_smile:

To help get the IP numbers of connections from Eggplant, here is a filter handler allowing quick and easy access to the same data with actual IP values for ServerID instead of the Name override property set to ServerID.

params  aFilter

set aList to an empty list
repeat with each connection in AllConnectionInfo()
 if ( each item in the values of connection where each contains aFilter ) is not () then
  set aNewConnection to an empty property list
  repeat with each key in the keys of connection
   set aNewConnection.(key) to QuoteString(connection.(key))
  end repeat
  insert aNewConnection into aList
 end if
end repeat

return  aList

to QuoteString  aString
 if char 1 of aString is not quote then put quote before aString
 if char -1 of aString is not quote then put quote after aString
 return  aString
end QuoteString
-- See: http://www.trap17.com/index.php/ipv4-vs-ipv6_t24034.html
-- returns the IP if it is valid, or the items of a list of IPs that are valid.
-- A resonable filter/verification tool.

params   IPs...

repeat with each anIP in IPs by reference
	set validNumbers to each item delimited by "." of anIP where each is between 0 and 255
	get number of items in validNumbers
	if it is not in ( 4, 16 ) or it <> number of items delimited by "." in anIP then delete anIP
end repeat

return  IPs

Here is a revised WaitForClick handler that does what it implies by name. We first parse out the parameters which are delay, images… and an optional ‘hidden’ value which specifies the instance if provided by a positive number. As a convenience handler, this is a staple in my bag of SenseTalk tricks. Hope you find it as useful in your testing practices.

params  delay, images...

set instance to 1
get a reference to the last item of images
if it is a positive number then
 set instance to it
 delete it
end if

if delay is not a positive number then
 insert delay before images
 set delay to 30 seconds
end if

-- if an image, it will become a location...
repeat with each image in images
 if image is not a point then
  MoveTo  RemoteScreenSize()
  WaitFor  delay, image
  set imageLocations to EveryImageLocation(image)
  get number of items in imageLocations
  if instance > it then
   LogWarning "Setting image instance to be selected to the maximum number of images found (" & it & \
     "), you specified a larger number of images to be selected " & instance & "."
   set instance to it
  end if
  set image to item instance of imageLocations
 end if
 Click  Image
end repeat

I have so many version of WaitForClick, I’m not sure which is best or least likely to cause issues, however this one seems to be my favorite right now.