Someone asked (on Twitter) about using WatiN from PowerShell, and pointed to this old post by Scott Hanselman saying he was having the same problems … so I wrote this to help them out:
Note: WatiN requires Single Threaded Apartment mode, so you need to be using PowerShell 2.0 (currently in CTP3) in order for any of this to work, and you need to pass the -STA parameter to PowerShell. Regardless, I thought I’d throw two tips out here:
LoadFile, use LoadFromI’m not 100% sure when it’s appropriate to use LoadFile in PowerShell, but I can tell you that if your assembly is in a folder with a bunch of other assemblies upon which it depends … you need to use [Reflection.Assembly]::LoadFrom( $path ) instead — because the .net loader will be able to find the dependencies.
It’s trivial to do code-generation in PowerShell, and WatiN is not friendly to the PowerShell syntax, so you’re going to want to generate a bunch of them. To get you started, here’s a set of Find-* functions to let you find each type of element that WatiN recognizes and automates… by name, id, class, style … well, by any attribute, really:
With just that little bit of code, I generated all of these functions:
And each function takes the name of an attribute, like say: Alt, Class, For, Id, Index, Name, Src, Style, Text, Title, Url, Value … or just “Element” ... and a regular expression to match against the value of that attribute, and finds the control(s) that match. So for instance … since I’ve got a browser open to Google, I could trigger a search for my name by just doing:
Drat, the lawyer and that pottery guy still beat me out, maybe I should stop using “Jaykul” everywhere and start using my real name more …
Someone asked in the comments about how to parse a table, and I thought I’d post an example, because it’s pretty cool how well PowerShell works for stuff like this. Lets say I wanted to scan down a column in a table, and depending on the values, return a value from another column. For an example, I’ll take a table off Wikipedia which lists multi-protocol IM clients, and try to return the names of the clients which support IRC.
Clearly WatiN needs to fix the missin <th> TableHeader problem, but other than that, this wasn’t too painful. Of course, it would be a lot easier if we could cast ScriptBlocks as the generic Predicate<T> that the various .Find methods accept, but if I try this, it just freezes up and never returns:
You could easily take this a lot further: dump those functions into a file for reusing next time (so you don’t have to regenerate them each time), and in fact, it would be relatively simple to make functions for each of the methods on each of the types we found above, so you would have a Click-Button function, and a TypeText-TextField function which could take those same search parameters (attribute and regular expression) and you could even generate appropriate parameters to take, for example, the text to type. In fact, you could generate a whole DSL for yourself … this guy did it using MGrammar … but why not PowerShell.
I don’t do web testing anymore, but if someone wants to send me a hefty bounty, I could probably be convinced to take this further another day — it’s pretty cool, and would coincide fairly well to my WASP project.
Hello Joel,
having been enthusiastic of the way to generate a bunch of functions from the DLL, I tried to reproduce your results.
Naively I copied the code into the ISE and called “Start-Watin” with no functions being generated:
___________________________________________________________________________________________________________________________________
PS C:\Dokumente und Einstellungen\Schulte> dir Function:F*
CommandType Name Definition
—————- —— —————
Function F: Set-Location F:
OK … I did find out, that the functions have been defined inside the “Start-Watin” function itself and that they are out of scope when that function ends.
That brings up the question: Is there a way to let the generated functions “survive” the end of the generator function? .. something like the $global: prefix?
( I’m starting to learn PS … and I hope the question is not too silly
kind regards, Klaus
Oh my goodness, yes. That was not a silly question at all. It should have said
New-Item -Path "Function:Global:Find-… to start with, and I’ve changed it to that now.Nice catch. Originally I had written it as a SCRIPT file, which I could dot-source, and I missed that when I converted it into a function. Incidentally, as a Bonus Tip: you can dot-source functions.
Hi Joel. Good stuff, last year we actually worked on an engine that would push all Watin calls to an STA thread running in the background, and then pull the results of the call back to our original thread….it was quite the nightmare, and while it worked, it was buggy.
Needless to say, I am excited for the possibilities in Powershell CTP3.
I messed around with your code and tried some of my own, and the one thing I’m noticing is that attempting to find an element that isn’t there takes a very long time to fail …sometimes it doesn’t come back at all. It just sits there. I’m wondering if this is related to powershell itself. Perhaps the way that Powershell handles STA results in some weird threading issue which doesn’t allow the timeout to happen? Have you seen this as well?
Thanks,
Adam
I haven’t messed with it very much … I assume that Find*ing by anything other than id or index is going to take awhile, particularly if you’re looking at an element-type that there are a lot of (say, paragraphs, or divs … or whatever).
But hey, I dono
— Like I said, I haven’t worked on Web testing in ages, and I haven’t used WatiN for anything but playing with it from PowerShell to write this post as an answer to a Twitter question
I love this article. It’s a great primer! Jaykul, how do you parse a column in table list on a website?
Well, WatiN doesn’t expose the columns, only the rows (because it just mirrors the HTML). So you have to iterate on the TableRows … just for fun, I’ll add an example to the main article, and re-publish it.
Joel, you are my hero!