Automating Unity
2011-09-22 : Amundsen-Scott South Pole Station
So we upgraded from what could generously be described as ancient versions of Cisco Unity (and CallManager) to much more modern versions. Sadly my hopes for a more featured and streamlined UI on these apps were dashed. Version 8.x is nearly identical in appearance to 4.x. One change I did notice was that the Unity web interface migrated from a form-based login to an only-slightly-better HTTP basic authentication login scheme. Anyway, in both the old and “new” versions it takes about 100 clicks to get to anything, so one of the first things I wanted to do when I got here was to automate my common Unity administration tasks.
My first stop was Perl’s WWW::Mechanize, which is the only reason I’d still normally use Perl for a screen-scraping web automation task instead of Python. Unfortunately when I tried this, the interaction between multiple mandatory framesets became difficult to manage. So my next stop was Fake.app.
Using Fake in an environment that uses tons of frames of non-conforming HTML4 is difficult, but not impossible, it just requires you to come up with some workarounds. One Unity task that frequently requires my time is resetting a voice mailbox password. My Fake.app workflow for accomplishing this starts off easily enough: it asks the user what extensions they would like to reset using a JavaScript prompt dialog box, like this one which stores the response to a Fake.app JS variable:
var inExt, x, extensionsToReset, searchReg;
inExt = prompt("What extensions would you like to reset? (Separate by spaces or commas)");
if (inExt === null) { throw "Need valid extensions to continue."; }
extensionsToReset = [];
searchReg = /\b(\d+)\b/g;
while ((x = searchReg.exec(inExt)) !== null) { extensionsToReset.push(x[0]); }
if (extensionsToReset.length < 1) { throw "Need valid extensions to continue."; }
window.fake.set('extensionsToReset', extensionsToReset);
The next step is logging in, which was easier with this version of Unity because of the aforementioned switch to basic auth, because that allows users to embed credentials in the client’s request URL field. Once I was in and had reached the subscribers screen, I ran into an issue where none of Fake’s methods for following/clicking on a link were working on the search button. When I would try to use Fake’s control-linking to connect to the search button (a link-wrapped image) it would produce an xpath or an element name that didn’t work and also didn’t produce any errors when running the workflow. document.evaluate()
would return NOT_SUPPORTED_ERR: DOM Exception 9
for Fake’s generated path. After some time in the integrated web inspector, I came up with this workaround, implemented in a “Do JavaScript” action, window.frames[1].frames[0].OnFind()
, it executes the same command that clicking the link would. My guess is that Fake’s underlying webkit framework didn’t like all the frames.
From then on out, it’s all normal filling out forms, clicking buttons, and notifying the user with Growl. Some parts of webkit have changed since my first revision of this batch job and JSLint was a recent discovery that helped me debug the old JavaScript code.
So here’s my Fake.app workflow for automating voicemail password resets. If you want to have a look at the sourcecode of this .fakeworkflow file, it’s just a binary plist and this: alias plcat="plutil -convert xml1 -o -"
is a nice shell alias for looking at those.