Random header image... Refresh for more!

Category — Software Testing

Continuous Integration and You

Continuous Integration is probably the most important development in team programming since the rise of source control systems.  It’s not a recent development, either, but while source control usage is universal, Continuous Integration tends to meet with calls of “It’s too hard” and “We don’t need it yet”.  Even in my company, where the benefits of Continuous Integration are obvious and clear, there always seems to be some resistance or avoidance when it’s time to put a project into the system.

Well, it’s not that hard1, and yes, you do need it right now.

Before I begin, I work at a web company.  Most of our software are fairly small (code-wise) sites, services and tools, that sort of thing.  We don’t really have any monolithic enterprise systems that take seven hours to compile with thousands of dependencies.  This posting is written from my experience in this world.  If that’s not your world, some of these comments might not apply to you, although the basic concepts should still be able to translate into what you do.

Continuous Integration, in its most basic form, is a system that watches source control check-ins, and when it detects a change, will build the code.

And deploy the code.

And run the automated tests against the code.

And build other projects that depend on the module that was just built, and deploy and test those, too.

You may have heard of Continuous Integration before, but didn’t look into it because you think it’s an “Agile process” and that you have to be doing test-driven design and extreme programming and all of that other nonsense in order for it to work.  Well, you don’t have to be doing Agile to do this.  Even if you’re in a barrel and going over a hard-core ISO-9000 Waterfall, you’ll benefit from at least part of it.  Hell, I’ve even set it up for some of my own personal projects, just so I don’t have to deal with the hassles of manual deployment.

Here’s why Continous Integration is Awesomeness:

  • Broken code detected automatically and immediately.  Accidentally check in a file with only half a function written?  Forget to check in a new file or library?  Within a few minutes, CI will fire off a build and choke, so you’ll know right away that there was a problem with what you just checked in.  You don’t have the case where two days later, someone else checks out your code, discovers that it’s broken, then wastes an hour trying to figure out what’s wrong before calling you over to help. 2  Plus, there’s the “public shaming” factor working in favor of quality software.  When someone breaks the build, everyone finds out about it, so people tend to be more careful about what they check in.
  • Automatic deployments.  If you’ve got web sites or services, you can’t use them unless they’re running somewhere.  Obviously, you can install the site by hand every couple of days, but then you have to go through the tedious steps of logging in, finding the latest build package, uninstalling the old code and installing the new code, repeating those steps for every machine you need the code on.  You could easily waste up to an hour or two a day doing this.  (On top of that, you’re running code that’s out of date most of the time.)  CI can do it for you.  Your machines are always up to date and you’re not wasting any of your time to make that happen.  Best of all, pesky testers won’t bug you asking for an updated build or begging you to push your latest fixes.
  • Automatic testing.  There’s test automation, then there’s automated test automation.  Automated tests that don’t run on their own don’t run often enough.  You’ve gone to all the trouble of making a computer do all the work of testing for you, so why do you still have it where someone has to push the button to make it go?  Think that’s job security for you or something?  If you have automated tests, make them run in a CI system.  That way, the developers check in their code and a few minutes later, they get an e-mail that tells them if anything broke, and you don’t have to do anything.  You don’t even have to be in the office!
  • Always up-to-date.  Testers are always looking at the latest code, so they’re not filing bugs about things you fixed last week.  The upper-ups are always looking at the latest code, so they’re not complaining about things that you fixed last week.  And the teams that rely on your component are using the component as it actually is, not as you designed it two months ago.
  • Code builds are clean.  Everything’s built automatically out on the build servers, not on your machine, where you’ve modified config files and replaced critical libraries with the debug versions.  The build servers are clean, and becaue what they’re building is constantly being deployed and tested, you know that the build packages they’re producing work.
  • Integration and deployment aren’t afterthoughts.    In ages past, I worked on a project which had a three month release cycle.  The first month and a half was strictly local development, on the developer’s machines.  Nothing was running anywhere other than the Cassini server in Visual Studio.  Like many systems, there was a front end and a back end.  Of course, the back end wasn’t running anywhere, so the front end was using a mock service that worked like the actual back end would, in theory, at least.  Then came the day of the first drop.  Developers spent hours building and installing everything, then turned it over to QA and went home.  The next day, QA was only able to log a single bug:  “[Drop 1] NOTHING WORKS. (Pri1/Sev1)“ 3  You see, while everything worked according to the spec, in theory, none of the developers actually talked to one another, so neither side mentioned how they filled in the holes in the spec.  The back end was using a different port, because the original port conflicted with something, and the front end was using lowercase parameter names and expecting a different date format.   This sort of trainwreck deployment disaster doesn’t happen when you have continuous integration in place.  Not because there aren’t miscommunications, but because those miscommunications are discovered and resolved immediately, so they don’t pile up.  The front end developers point their code at the live work-in-progress back end servers.  If there’s a port change or a date format mismatch, it can be detected and corrected right away. 
  • Testers get something to test immediately.  In the story above, the testers were forced to try to write their tests against a phantom system for the first month and a half, based on partial technical specs and a handful of incomplete mockups.  After that, we were writing tests on code that was out of date.  It doesn’t have to be like that.  With Continuous Integration,  whatever is checked in is deployed and running somewhere, making it at least partially testable.  Even if there’s no functionality, at the very least, there is a test that the deployment package works.  The argument that testers can just pull the code out of the repository and run the service or website on their local machine and run all of their tests against that is ridiculous.  It’s a waste of time, because every tester has to pull down the code, configure their machines, build the code, then bug the developers for an hour or two because things aren’t working right.  Even after all that, there’s no telling whether some bugs are actual bugs, or just byproducts of the way it’s set up on a particular tester’s machine.  Worse still, the tests that are written aren’t going to be run regularly, because there’s no stable place to run them against.
  • Deployment packages and test runs are easily archived.  The system can automatically keep a record of everything that it does.  Need to compare the performance of builds before and after a change that was made two weeks ago?  You’ll be able to do that, because you still have the .MSIs archived for every build over the last two months.  Need to find out when a particular piece of functionality broke?  You’ll be able to do that, because the tests ran with almost every check-in and the results were saved.  You don’t have drops and test passes that are done two weeks and a hundred check-ins apart.
  • Progress is always visible.  Ever have a manager above you, sometimes far above you, want to see your progress?  Ever have to race to get something set up to demo?  CI takes care of that.  You’re always deploying with every check in.  A demo of the current state is always a link away.
  • It saves time and saving time saves money.  Use this point with skeptical management.  The first time you set up a project in a CI system, yes, you’ll spend several days getting everything running.  However, if you add up all the time that your team will waste without an automated build/deploy/test system, it will easily pay for itself.  You won’t spend any time on getting a build ready, deploying the build, firing off the automated tests, reporting on the automated tests, trying to get something you’re not working on set up on your box, explaining how to set up your code on someone else’s box, creating a demonstration environment to show off what you’ve done to the upper-ups after they asked about your progress in a status meeting, integrating with a service that’s been under development for months but that you’ve never seen, trying to figure out what check in over the last three weeks broke a feature, wasting a morning because a coworker checked in broken code, or being yelled at by coworkers for checking in broken code and causing them to waste their morning.  And, of course, the second time you set up a project in Continuous Integration, you can copy and paste what you did the first time, then only spend a fraction of the time working out the kinks.  It gets easier with every project you do. 

Like almost anything that’s good, there are some points where it’s painful.

  • When the build goes bad, people can be blocked.  Everyone’s relying on the code being deployed and functional.  Now that you have an automatic push of whatever you’ve checked in, there’s always the chance that that what you’ve checked in is garbage, leading to garbage getting pushed out.  However, the flip side to this is that the problem is exposed immedately, and there’s a very real and pressing need to fix the problem.  Otherwise, the blocking issue may have gone undetected for days, perhaps weeks, where it would be far more difficult to track down.
  • It never works right the first time.  Never.  Whenever you set up a project in your Continuous Integration system or make major changes to an existing project, it won’t work.  You’ll spend an hour tracking down little problems here and there.  Even if you get the build scripts running on your local machine, they’ll fail when you put them out on the build server.  It’s just part of the territory.  However, tweaking the build to work on a different machine does mean that you’re more aware of the setup requirements, so when it comes time to ramp up the new guy or to deploy to production, you’ll have a better idea of what needs to be done.
  • Everyone knows when you screw up.  Everyone gets the build failure e-mail.  So don’t screw up.  Where I work, we’ve taken it a step further.  We used to have a “Build Breaker” high score screen, which would keep track of who had left builds broken the longest.  When the build broke, everyone involved would make sure they fixed it as soon as they could, so they’d avoid getting the top score.  However, we also have a watcher set up that will send an e-mail to the specific person responsible for the build break, letting them know that something went wrong.

Now you know why it’s good to set up Continuous Integration, as well as some of the things that will go wrong when you do.  So, all that’s left is for me to give you some advice based on what I’ve learned in my own experience.  I’m not going to go so far as to say that these are Continuous Integration Best Practices or anything like that, just some tips and tricks you might find helpful. 4

  • Do it.  Just do it.  Stop complaining about how hard it is or how it takes so much time to set up or how we don’t really need it at this point in the project.  Do it.  DO IT NOW.  It’s easier to do it earlier in the project, and the overall benefit is greater.
  • Add new projects to CI immediately.  Create the project, check it in, and add it to CI.  Don’t wait, don’t say “I’ll do it when I’ve added functionality X”.  Get it in the system as soon as you can.  That way, it’s out of the way and you won’t try to find some excuse for putting it off later when the testers are begging you to finally do it.  I view CI as a fundamental part of collaborative software development and the responsibility of any developer who sets up a new project.  If you’re a tester and the devs won’t do it themselves, do it for them.  You need it.  Trust me, you do.
  • Use what makes sense.  Where I work, we use CCNet and nant.  Not necessarily because they’re the best, but because they’re what we’ve used for years and they make sense for us.  make is ancient and confusing, we don’t use Ruby for anything, so Rake would be a bit outside of our world.  If you can do what you need to do with a batch file and a scheduled task, then go for it.  Although, trust me, you’re going to want something that can handle all of the automatic source control checkout stuff and allow wide flexibility in how it fires off builds.  And don’t pay for anything.  Seriously, there’s free stuff out there, you don’t need to be paying thousands of dollars a year just because something that has a great sales pitch.
  • Consistency is the key.  On a major project about a year and a half ago, we standardized the set up of our projects and build scripts.  Ever since then, we’ve reused that model on a number of other projects, and it has been amazingly effective.  As long as we give parts of our projects standard names (project.Setup, project.Tests, etc.), we can copy and paste a small configuration file, change a few values in it, and in just a few minutes, we have that project being built, deployed, and tested in our CI system.  An additional benefit is that our projects now all have a similar organization, so any part of the code you look at will have a sense of familiarity.
  • Flexibility is also the key.  While we’ve gotten enormous gains from standardizing what we can standardize, there’s always going to be something that has to be different.  Don’t paint yourself into a corner by forcing everything you put in the system to act in exactly the same way.  However, don’t be too flexible.  If someone’s trying to take advantage of the flexibility in the system because they’re too lazy to be consistent, then, by all means, break out the Cane of Conformity and make them play by the rules.
  • Your build system is software, too.  Don’t think of it as just a throwaway collection of files to get the job done.  It isn’t.  It’s an integral part of your software.  It might not be deployed to production or shipped to the customer, but everything that is deployed to production or shipped to the customer will run through it.  Do things that make sense.  If you can share functionality, share it.  If you can parameterize something and reuse it, do that.  Check in your build scripts and CI configuration.  Remember, you’re going to have to update, extend and maintain your build system as you go, so it’s in your best interest to spend the time to make a system that’s simple to update, extend and maintain.
  • Be mindful of oversharing build scripts.  You want to try to make your build scripts and CI configs so that they’re modular and reusable, but be careful that you don’t share too many things or share the wrong things between unrelated projects.  At my company, we have a handful of teams, and most of them have one or more build servers.  At one point, one of the teams was reorganized into several smaller teams, and sent to work on wildly divergent projects.  However, they continued to share a single library of build scripts.  Some time later, someone made a change to the one of the scripts in the library that he needed for his project.  His project on his server worked just fine.  Then, two weeks later, every other server that used these shared scripts began to fail randomly.  No one had any idea what was going on, and it took several hours to trace the problem back to the original change.  This illustrates the danger of sharing too widely.  You want to try to structure the build script libraries in such a way that changes are isolated.  Perhaps you can version them, like any other library.  Or, like we’ve done for most of our projects, copy the build script libraries locally into the project’s directory, so everyone’s referencing their own copy. 5
  • Check in your build scripts and CI configuration.  I know I just said that in a point above, but it’s important enough that it deserves its own bullet.  Your build scripts are an important piece of your software, so when they change, you need to rebuild your code, in order to make sure that the process still works.  You want them checked in for the same reasons the rest of your source code is checked in.  We even have our CCNet.config checked in, which means that our Continuous Integration systems themselves are in CI.  In other words, CCNet will detect a change to the CCNet.config that’s checked in, pull down the latest version, and restart itself to pick up the changes.  Under normal circumstances, we never have to touch the build server.  We just check in our changes and everything is automatically picked up.
  • Don’t touch the build server.  Obviously, you’ll have to set up the box, and once in a while, there’s some maintenance to be done, but for day to day operations, don’t log on to the box.  In fact, don’t even manually check anything out other than the CI configuration file.  Everything that needs to be checked out should be checked out automatically by the CI system.  This even extends to tools, where possible.  We’ve got versions of nant and other command line tools checked into SVN, and any build that uses them is responsible for checking them out.  One of the benefits of this is that it makes it easy to move the builds to a different server in an emergency if something goes wrong.  If any of our build servers dies, then we can probably get things back up and running on an alternate server in about half an hour.
  • PSExec is your friend.  If you’re doing stuff on Windows systems, there’s a tool from Sysinternals called “PSExec”, which makes it fairly straightforward to run a command on a remote machine.  Get it.  Use it.  Love it.
  • Every build should check out everything it needs.  Every build should be able to run from scratch. 6  Every build should check out all the code it needs, all the dependencies, all the tools, all the data files.  It should be possible for you to go on to the build box, wipe out the source tree, and have all of your builds succeed the next time they run.  In fact, I’d recommend intentionally deleting the source tree from time to time, just to may sure that everything is self sufficient.  The reason for this is that every build should be using the latest code.  If Build A depends on a library that only Build B checks out, then there’s a chance that Build A will run before Build B updates, leaving Build A potentially out of date and in an unknown state.  Yes, this requirement often means that there are certain libraries or files that are included by pretty much everything, so even a simple change to those libraries will cause a build storm where everything gets rebuilt.  People hate these, but think about it:  You’ve changed a core dependency, therefore everything has to rebuild because everything was changed.
  • Only check out what you need.  Target the checkouts to be only the directories that you need for that particular build.  Don’t have every project watching the root of your repository, because then you’ll get builds for every single check in, no matter how unrelated the check in was.
  • Don’t set up your CI system with a normal employee account.  You want your CI system to run under an account that won’t go on vacation or get fired.
  • Fail builds when unit tests fail.  If you’re doing unit tests, you want those tests to be running as part of the CI build.  You also want them to fail the build.  The philosophy of unit testing is that when a unit test breaks, your code is broken and you need to fix the issue immediately.  This will very strongly encourage developers to make sure that the unit tests are maintained and in good working order and that their new code doesn’t break anything.
  • Don’t fail builds when functional/regression/integration tests fail.  It’s generally expected that at least some of your functional or regression tests will fail with every build.  If you don’t have at least a handful of regression tests failing due to open bugs in the software, then you need more tests.  Where I work, the functional test builds only fail when there is a problem when building or executing the tests, not when one of those tests fail.
  • Don’t deploy if a build fails.  Deployment should be one of the last steps in a build and should only be performed after the build and unit tests (if you have them) are successful.  Don’t wipe out a previous build that was successfully deployed with something that you know is broken.
  • Don’t archive deployment packages if a build fails.  If the build breaks or the unit tests die or the deployment doesn’t work, don’t archive the deployment package.  This will ensure that any MSI that’s saved is of a build that passed all the unit tests and was installed successfully.
  • Split your builds into logical components.  If possible, avoid having a monolithic build that will build everything in your company.  The bigger a build is, the more stuff that’s included, the more chances for the build to go wrong and the longer a build will take.  You want to aim for quick and small builds for faster feedback.  It’s fine if you have cascading builds, where one build triggers the next, as long as the project that was actually changed is built first in order to give immediate feedback.
  • Don’t filter build failure mails.  EVER.  Build failure notices are vital to the health of a CI system.  When a build breaks, you need to know about it.  However, I’ve seen a lot of people who simply set up a mail rule that forwards all mail from the build server into a folder that they never look at.  DON’T DO THAT.  It’s fine if you filter the successes away, but the failures should be front and center.  If anything, you need to set up a rule that flags failures as important.  I have a mail rule that specifically filters mails from the build server only when the subject line contains “build successful” and does not contain “RE:” or “FW:”, etc.
  • Fix your broken builds.  NOW.  Don’t wait.  Fix it.  Now.  NOW!  When a build breaks because of you, it should leap to the front of your priority queue.  Do not go to lunch, do not go for coffee, do not pass go, do not collect $200.  FIX. IT. NOW.
  • Don’t go home without making sure your builds are successful.  And especially don’t go on vacation.
  • Sometimes, broken builds aren’t the end of the world.  Sometimes it’s okay to check in something you know will break the build.  If you’re doing a major refactor of part of the code, it’s fine to do it in stages.  Go ahead and check in bits and pieces as you go.  In fact, in some source control systems, it would be suicidal to attempt to pile up all of the renames, moves, and deletes into a single check in at the end.  In some cases, you might even want to disable a particular build while you make changes.  (Just make sure to turn it back on again.)

Finally, and most importantly, stop your whining, stop your objections, stop reading this post, and go set up your Continuous Integration system immediately.

  1. You people know seven programming languages, yet you’re afraid of a build script?  It’s not hard.  You’re just lazy. []
  2. Except they can’t, because you’re in Cancun for the next two weeks. []
  3. And, of course, QA got blamed for slipping the release date because they didn’t get their testing done according to schedule. []
  4. Plus, I just wanted to say “Continuous Integration Best Practices” a couple of times to get more hits.  Continuous Integration Best Practices.  Okay, that oughta be enough. []
  5. However you choose to organize it, sticking project specific e-mail addresses in a shared file that everyone references is a dumb idea.  There’s nothing shared about it.  Don’t force me to rebuild everything just because you’ve hired a new intern. []
  6. Well, almost every build, at least.  Obviously a test run is going to need the project it’s testing to have been built first. []

February 6, 2011   No Comments

dynamic Has a Use! Partial Verification of Complex Classes in Test Automation

I don’t like dynamic in C#.  It’s one of those features that seems to serve only to confuse the language.  And yeah, I get that Ruby and Python are doing it, but if Ruby and Python jumped off a bridge, would you?

When I first picked up C#1, I liked it for three reasons: 

  1. Garbage collection.
  2. It had a built-in string class.
  3. It actively prevented you from doing stupid things that C++ was fine with.

When they added “var“ to the language, reason #3 died a little.  People started using “var” to declare ordinary variables where the type is known.  Sure, the compiler can figure out what the type is, but a human can’t, at least not by just reading the code without a knowledge of the return type of the function you just called. 

And then along came “dynamic” and walloped reason #3 over the head with a large stick.  Now, not even the compiler knows what you’re doing.  You have to wait until runtime to tell if your code is going to work.  It’s a bit like programming in Basic on a Commodore 64, except you’ll get a RuntimeBinderException instead of “?SYNTAX ERROR IN 150″2.  The end result is the same, a stupid typo broke your code when someone else tried to run it. 

It doesn't make any sense here, either.

 The idea behind dynamic is that it lets you write code against certain types of COM objects and dynamic objects from languages like Python and Ruby.  Of course, COM should be dead by now and that maybe MS Office’s Automation should look at this new-fangled .Net thing that everyone else in Redmond has been using FOR TEN YEARS, and that maybe if you want to use features from Python or Ruby that maybe you should actually be using Python or Ruby, but whatever…  The way dynamic works is that you can declare a variable as type “dynamic”, just like you’d declare it a “string” or an “int”, and this tells the compiler that you have no idea what the object is.  Since you don’t know what it is and since the compiler doesn’t know what it is, you can go ahead and call any method you’d like on it or use and property on it and theoretically, it’ll work.  It’s called “Duck Typing”, as in “quacks-like-a”.  In other words, I don’t care what object X really is and I don’t want to force it to be a subclass of Y or implement interface Z.  All I care about is whether or not it can “Quack()”.  If it can “Quack()”, then it’s enough of a Duck for me to use in my function. 

In most cases, dynamic in C# doesn’t actually mean that you can dynamically change the structure of the underlying object.  Whatever that object is, it’s still that type, even though you’re hiding it behind the dynamic keyword.  Think of dynamic as if it’s “var” with amnesia.  “var” knows what type it is at compile time, so you can ask it “Do you know how to quack?” and it can tell you.  With dynamic, it won’t know whether or not it can quack until the code actually runs, at which point its memory comes back and it remembers how to quack.  Either way, whether it knows how to quack at run-time or compile-time, the thing quacking is still a duck. 

Now, I don’t know about you, but I don’t trust amnesiac ducks. 

You see, since you can call any method or property on the dynamic object, you have to simply hope that whatever the object turns out to be when you run it actually implements that method or property, or else you’re totally screwed.  Also, since there’s no Intellisense to guide you, you’d better hope that you spelled the property or method correctly, or else you’re totally screwed.  Oh, yeah, and since there’s no type checking, you’d better hope that the type of that property or the types of the parameters or return value of the method are what you think they are, or else you’re totally screwed. 

It’s this whole “…or else you’re totally screwed” bit that turns me off about dynamic.  There’s a lot of ways this can go wrong, and when it goes wrong, you don’t know it’s gone wrong until the program is running and it dies.  And, of course, it won’t do that until it’s out live in production and a customer comes across the bug, and the whole site goes down and the closest tester gets blamed for missing the bug.  You can see how that would be a problem for me, so you can understand why I don’t like dynamic

But then I found a way to actually make use of dynamic to solve a testing problem I’ve been having. 

Before I go into details, let me first say that I’m a bit reluctant to share this idea, for several reasons: 

  1. I don’t like dynamic, so it feels dirty to use it in this way.
  2. I haven’t really fully explored this idea, so it could be a complete load of nonsense.
  3. It’s voodoo magic code and I don’t like using voodoo magic code because no one can understand voodoo magic code.  I know that I’m going to spend more time explaining how it works to other people than they’re going to spend using it, because they’re going to look at it and be scared away.
  4. “…or else you’re totally screwed”.  It would not be good to have test cases that fail at runtime because of typos.
  5. It feels like this is half of a good idea, and that once I figure out what the missing half of the puzzle is, I’ll have something that’s useful and won’t scare people away and won’t feel dirty.  I don’t like writing about half an idea.
  6. I don’t like dynamic.

However, I’m sharing it because I want it to be a good idea and hope that writing about it will get me thinking about how to solve those remaining problems. 

Anyway, here’s the scenario: 

Where I work, we have services that return complex objects.  Sometimes, these objects can have upwards of 20 properties, and some of those properties are classes that have more properties on them.  In other words, there’s more properties floating around in these objects than in most Florida land scams.  Testing these objects is relatively straightforward, though.  Simply create a parallel object with the expected values, and walk through the expected and actual objects property by property, comparing the values.  If something doesn’t match, fail the test. 

The problem I have is that for most of my tests, I don’t really care about most of those properties.  Each test only focuses on a handful of values and the rest don’t matter.  The one-size-fits-all approach of walking the properties required an exact match.  If I only care about four or five properties, why should I have to specify values for the other 15?  I might not even know what they are.  One of the properties could be something like response time, which I have no way of knowing at the time I write the test.   But, I don’t want to have to write custom validation for each test to check only the values it cares about, because that’s inefficient and difficult to maintain. 

Let’s give a more concrete example class to work off of here. 

 
public class SearchResult
{
        public string Title { get; set; }
        public string Excerpt { get; set; }
        public string URL { get; set; }
        public double Score { get; set; }
        public int Size { get; set; }
        public int Position { get; set; }
        public DateTime IndexedTime { get; set; }
        public string CacheURL { get; set; }
} 

 

This class is a simplification of a result returned from a search engine.  Each result has a title, an excerpt of text from the page, a URL for the page and internal information, like the confidence score of the result, the size of the page, the date the page was last indexed, the position of the result, and, if available, a URL for a cached copy of the page. 

Here’s the property by property checker: 

 
public void CompareResults(SearchResult expected, SearchResult actual)
{
    Assert.AreEqual(expected.CacheURL, actual.CacheURL, "CacheURL mismatch");
    Assert.AreEqual(expected.Excerpt, actual.Excerpt, "Excerpt mismatch");
    Assert.AreEqual(expected.IndexedTime, actual.IndexedTime, "IndexedTime mismatch");
    Assert.AreEqual(expected.Position, actual.Position, "Position mismatch");
    Assert.AreEqual(expected.Score, actual.Score, "Score mismatch");
    Assert.AreEqual(expected.Size, actual.Size, "Size mismatch");
    Assert.AreEqual(expected.Title, actual.Title, "Title mismatch");
    Assert.AreEqual(expected.URL, actual.URL, "URL mismatch");
}

 

Now, let’s write a test:  If I search for “dogs”, then I expect to get the Wikipedia entry for “Dogs” as my top result. 

 
public static void CheckThatWikipediaIsInResultsForDogs()
{
    SearchResult expected = new SearchResult();
    expected.URL = "http://en.wikipedia.org/wiki/Dogs";
    expected.Title = "Dog - Wikipedia, the free encyclopedia"; 
    expected.Position = 0;

    SearchEngine engine = new SearchEngine();
    SearchResult actual = engine.Search("dogs"); 

    CompareResults(expected, actual);
} 

 

Now we run it and…  Aw crap.

Assert Failed! != http://www.mathpirate.net/cache?=http%3A%2F%2Fen.wikipedia.org%2Fwiki%2FDogs : CacheURL mismatch

The test failed, not because the Wikipedia entry wasn’t the result, which is what I cared about, but because the CacheURL was wrong.  I don’t care about the CacheURL.  There are other tests somewhere else that will deal with CacheURL.  Now, I can specify it if I want to, but I also have to specify the excerpt and the page size and the last indexed time, etc.  But I’m not going to .  I don’t give a flying monkey dance about those values in this test, so it’s stupid to specify them.  The “CompareResults” method has to change.

One fairly simple way to take care of that problem is to check for non-null or non-default values and not check the values if the value is the default.  Here’s the new and improved CompareResults:

public static void CompareResults(SearchResult expected, SearchResult actual)
{
    if (expected.CacheURL != null) { Assert.AreEqual(expected.CacheURL, actual.CacheURL, "CacheURL mismatch"); }
    if (expected.Excerpt != null) { Assert.AreEqual(expected.Excerpt, actual.Excerpt, "Excerpt mismatch"); }
    if (expected.IndexedTime != default(DateTime)) { Assert.AreEqual(expected.IndexedTime, actual.IndexedTime, "IndexedTime mismatch"); }
    if (expected.Position != default(int)) { Assert.AreEqual(expected.Position, actual.Position, "Position mismatch"); }
    if (expected.Score != default(double)) { Assert.AreEqual(expected.Score, actual.Score, "Score mismatch"); }
    if (expected.Size != default(int)) { Assert.AreEqual(expected.Size, actual.Size, "Size mismatch"); }
    if (expected.Title != null) { Assert.AreEqual(expected.Title, actual.Title, "Title mismatch"); }
    if (expected.URL != null) { Assert.AreEqual(expected.URL, actual.URL, "URL mismatch"); }
}

Run it now and it passes.  It didn’t care about the missing CacheURL.  Hey, problem solved!

Except…   The new and improved CompareResults is new and improved and still broken.

Here’s what was returned from the Search method:

Notice that Position is set to 5.  In my test, I explicitly set that position should be 0.  CompareResults missed it because  of the check that I added: if(expected.Position != default(int)).  default(int) isn’t some magic value, like “undefined”.  It’s “0″.  Plain old zero.  So when that line executed, it became if(0 != 0)3.  And so, the check was skipped and it missed the fact that the test should have failed.  The same problem will occur if you’re explicitly checking for a null property value.

So…  What to do?

There are multiple options to handle this case, none of them very pleasant:

  • Live with it.  One-size-fits-all never actually fits you.  Deal.
  • Write custom validation for every test that needs to check a default or null value explicitly.  Hope that everyone remembers this needs to be done.
  • Write a parallel set of classes that mimic the actual class, but have boolean properties like “PositionSet” for every single property on the object. 
  • Go back to the original method that checks everything exactly, but write custom pre-treater methods that can go in and clear out values that you don’t care about before you do the comparison. 4

Or…

Use dynamic.

Here’s how it works:

Instead of creating an instance of the type of object that you want to create, you create a dynamic object, and only set the properties you care about.  Then, in the comparison method, it can check if you’ve set the value, and then do the comparison if you have.  If not, it gets ignored.  This solves the problem of forcing a check on values you don’t care about as well as the problem of default values.  If you didn’t set the value up front, it’s not there, so the verification method won’t look at it.

Let’s see it in action:

public static void CheckThatWikipediaIsInResultsForDogsDynamic()
{
    dynamic expected = new ExpandoObject();
    expected.URL = "http://en.wikipedia.org/wiki/Dogs";
    expected.Title = "Dog - Wikipedia, the free encyclopedia";
    expected.Position = 0;

    SearchEngine engine = new SearchEngine();
    SearchResult actual = engine.Search("dogs");

    CompareResultsDynamic(expected, actual);
}

The test method looks almost identical.  The only notable difference is the first line.  I changed “SearchResult expected = new SearchResult();” to “dynamic expected = new ExpandoObject();”5.  The properties are set on the object the same way, the function is called the same way.

public static void CompareResultsDynamic(dynamic expected, SearchResult actual)
{
    if (PropertyExists(expected, "CacheURL")) { Assert.AreEqual(expected.CacheURL, actual.CacheURL, "CacheURL mismatch"); }
    if (PropertyExists(expected, "Excerpt")) { Assert.AreEqual(expected.Excerpt, actual.Excerpt, "Excerpt mismatch"); }
    if (PropertyExists(expected, "IndexedTime")) { Assert.AreEqual(expected.IndexedTime, actual.IndexedTime, "IndexedTime mismatch"); }
    if (PropertyExists(expected, "Position")) { Assert.AreEqual(expected.Position, actual.Position, "Position mismatch"); }
    if (PropertyExists(expected, "Score")) { Assert.AreEqual(expected.Score, actual.Score, "Score mismatch"); }
    if (PropertyExists(expected, "Size")) { Assert.AreEqual(expected.Size, actual.Size, "Size mismatch"); }
    if (PropertyExists(expected, "Title")) { Assert.AreEqual(expected.Title, actual.Title, "Title mismatch"); }
    if (PropertyExists(expected, "URL")) { Assert.AreEqual(expected.URL, actual.URL, "URL mismatch"); }
}

This is the new CompareResults function.  First, the ”expected” parameter has changed.  It’s now “dynamic” instead of “SearchResult”.  The Asserts themselves are identical, and they’re still wrapped inside of if statements, but the if conditions have changed.  Instead of looking for nulls or default values, they’re checking that the property exists on the dynamic object.

This is one part where the whole dynamic thing is sorely lacking.  There’s no built-in way to tell if an object can do what you want it to do without trying to do it.  It’s like you’re commanding the duck to “Quack, damn you!”, without first asking “Can you actually quack?”.  Ruby’s got “respond_to?(‘Quack’)”, while C# has “Do it yourself and don’t forget to account for all of the different possibilities of how the quacking can be done, otherwise just try to  ‘Quack’ and catch the exception”.  Here’s a very abbreviated and probably error-prone example of how to ask if an object supports the property you want to call:

public static bool PropertyExists(object dynamicObject, string propertyName)
{
    if (dynamicObject is ExpandoObject)
    {
        IDictionary<string, object> expando = (IDictionary<string, object>)dynamicObject;
        return expando.ContainsKey(propertyName);
    }
    else
    {
        return dynamicObject.GetType().GetProperty(propertyName) != null;
    }
}

First, it checks if the object is an ExpandoObject.  If it is, then it can use the explicit IDictionary implementation on IDictionary in order to find out if the property exists or not.  Otherwise, it uses basic Reflection to ask if the object has the property you’re looking for.  This PropertyExists method will probably need a bit of work for general consumption, but it’s a start and works well enough for this example.

But…  Does it work?

Assert Failed! 0 != 5 : Position mismatch

It didn’t complain about the CacheURL, like the first solution did, but it did catch the Position mismatch, which the second solution missed.  I’d call that a success!

There are, of course, problems with this solution…

  • There’s no Intellisense, so you have to know what the object looks like.  Of course, what you can do there is write the initialization with the actual class, then change it to dynamic once you have all of the properties in place.
  • There’s no compile-time checking, so if you make a typo or if a property disappears or changes names, you don’t know about it until everything breaks at runtime.  Or worse, nothing breaks at all, but you end up not checking the values you think you’re checking. 6
  • It’s voodoo magic code.  You’re going to be the only one who understands how it works, so you’re on the hook for making sure it actually does work and you’ll going to be the one that gets blamed or cursed every time something goes wrong.

Like I said, it’s half of a good idea and I haven’t really explored all of its benefits and consequences yet.  The core is here and I know this is a good place to start and build something awesome on top of.  I already have some ideas of where this could go.  Like toward a generic exapandable data-driven object comparison framework.

But that’s for another time…

Here’s the code from this post:  http://www.mathpirate.net/svn/Projects/AutomationUsingDynamic/

  1. I came through C++ and hadn’t been exposed to Java at that time. []
  2. Okay, it’s more like writing JavaScript, but whatever. []
  3. if(0!=0){ throw new UniverseDestroyingParadoxException(“Critical Mathematical Foundation Error.  Please Reboot.”); } []
  4. I admit that I’ve done this once.  It was not one of my better moments. []
  5. ExpandoObject is a type that does some magic with the C# dynamic runtime binder.  I’m not going to explain how it works, after all, there are search engines for that.  However, using ExpandoObject lets you dynamically add members to your dynamic object.  If you tried to do this with an ordinary object, you’d get a RuntimeBinderException because the property you’re setting isn’t there. []
  6. Why, oh why didn’t they do something like “dynamic<T>” for at least some compile-time checking? []

January 1, 2011   No Comments

FULL OF WIN

Oh yeah.  That’s right.  Built in.

December 6, 2010   No Comments

Maybe they need to read their own best practices.

I got this error today.  It is a magnificent demonstration of several layers of FAIL.

August 19, 2010   No Comments

I think this can be optimized.

I was playing around with some Linked Data SPARQL query engine stuff today and it generated the following query against its SQL data store.  Now, I’m not a database expert or anything, but something tells me that you can probably optimize this query somewhat.

SELECT id,  value FROM rdf_entities WHERE id IN (1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1159, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1169, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1172, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1197, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1199, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1206, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1208, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1431, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1433, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1435, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1439, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1442, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1444, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1446, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1448, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1450, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1452, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1468, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1475, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1477, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1492, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1500, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1584, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1599, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1632, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 1649, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2550, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2552, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2554, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2596, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2615, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2623, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 2628, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878, 7878)

July 29, 2010   No Comments

Automated Testing and TinyMCE

Ever come across one of these TinyMCE editors in the stuff you have to test?  Well, it’s probably going to give you a headache when you do.  There’s a <textarea> in the page, and normally, you’d grab the element, set its value, and call it good.

Not here.

You see…  TinyMCE doesn’t use content of the text area.  Instead, it seems to be dynamically building an HTML document within an <iframe> and the text area is just there to be a placeholder.  Now, I’m sure that if you really wanted to, you can probably manipulate the HTML document in that <iframe>, but if you want to do that, there’s probably something wrong with you.  Fortunately, there’s a better way.

TinyMCE has a JavaScript API.  (Details here…)  This API lets you do all sorts of crazy things when you’re developing a page with a TinyMCE editor on it.  Of course, we’re not developing the page, but fortunately, the TinyMCE editor doesn’t know that.  JavaScript called from the test is just as valid as JavaScript called from the page in its view, so we can call the function to insert text into the document.  Like so:

window.tinyMCE.execCommand(‘mceInsertContent’,false,’contentHTML‘);

where “contentHTML” is the stuff you want to insert.

I’m assuming that all you have to do is insert a bit of content into the editor field, much like it were a regular text input box.  If you have to do more in depth testing of various TinyMCE controls, you’re either testing the wrong thing (the editor instead of your app), or you’re on the dev team for TinyMCE, in which case you shouldn’t be reading my blog post on this subject, you should be writing your own explaining this so that outside testers don’t waste the better part of their day to figure this sort of stuff out.

Now, as far as how to actually call JavaScript code when you’re automating the browser through the DOM…  I thought I’d written something about that before, but now I can’t seem to find it.  I guess that’ll have to be a topic for the future.

May 11, 2010   No Comments

Temporal Mechanics: Changing the Speed of Time

Until last week, I never really thought about my computer’s system clock.  I simply thought that it hummed along at a constant, smooth rate, always heading toward the future.  I figured that once a week, it would call a time server tethered to atomic clock somewhere and make sure that it’s in sync.  If not, it would immediately correct the time and go about its business for another week.

Turns out, I was wrong.  The way Windows handles time, particularly when a member of a domain is a tad more complicated.  And, as it turns out, you generally want it that way.

Think about the implications of the method I described above.  If your system clock is bad, that means that in the span of a week, you can get ten or fifteen minutes off of real time.  Then, in one big jump, your clock gets corrected, jumping your system ten minutes into the future, or forcing it to re-live the past ten minutes over again.  On your home system, that’s probably not a huge deal.  Timestamps on all the movies you’re downloading might be off by a bit, big deal.  But in the business world, that would be bad.  Your Outlook reminders would fire off at different times for different people, e-mails and instant messages would have odd timestamps, and the Kerberos system used for authentication would randomly block people for being outside of an acceptable time range.

In short:  Mayhem.

So, when you’re joined to a domain, Windows typically gets a bit stricter over how it handles time synchronization.  It does this to keep all machines in line.  That’s why you hear the meeting reminder bell rising from fifteen machines in your cubicle farm at nearly the same moment.  The Windows Time Service is keeping everyone synchronized.

Now, there’s a good chance that you already knew that, if you’ve been around networks long enough.  But, there’s an equally good chance that you don’t know how the Windows Time Service is keeping everyone synchronized.  I had always assumed that the Time Service was keeping everyone in sync by resetting everyone’s clocks to the correct time every hour or so, and that no one ever noticed because your internal system clock kept good time.  In other words, at 1:03:27 PM, your computer would be told that it was 1:03:27 PM, so it would set the time, and it would still be 1:03:27 PM.

That ain’t what it does.

In fact, your computer’s clock doesn’t really matter to Windows.  Windows really only pays attention to it when it first starts up.  Beyond that, the magic of the Windows Time Service keeps things in line.

The Windows Time Service doesn’t really like setting your time at all, once it’s running.  It will do so, if your clock gets too far away from the time authority, but in general, it won’t set your time.  So, how does it keep a flock of computers running in perfect Outlook harmony?

Simple.

It changes the speed of time.

I’ll get into that, but first, let’s change your perception of time.  Time, at least from the perspective of a Windows machine, is not linear.  Instead, it’s a step function.  The exact values vary from system to system, but these are the basics:  Every 15 ms or so, the clock bumps its time value by 15 ms or so.  If you’ve ever done a Console.WriteLine(DateTime.Now.Ticks); inside a tight while loop1, you’ve seen the effects of this.  The value will remain constant for many iterations, then leap up, then stay at the new level for a while, then jump.  Similarly, if you’ve ever mistakenly tried to use DateTime for recording performance numbers, you’ve run into something similar.  All of your perf times are reporting 0ms, 15ms, or 31ms.  It’s always one of those three.  Never 7, never 12, never 24.  That’s because the resolution of the time is 15ms.

In other words, time looks like this:

The smooth black line is “real time” and the stepping red line is your computer’s time.

All is well in graph-land.  The time your computer reads might move in discrete chunks, but it’s always centered around real time, so you’re never more than 7.5 ms off of reality.

But what happens if the clock in your computer is bad?  The quartz crystal has gone on a vacation to a big sphinx with the Jackdaws or the cesium atom is vibrating 9192631771 times a second.  Your computer will think that it’s adding 15ms, but it’s actually adding slightly more or less time.  Let’s take a 5% error rate.  For every 15 ms of real time, your computer adds 15.75 ms to its clock.  That looks like this:

You’ve gone off the chart.  The error accumulates and time starts drifting.  At the end of this sample graph, which represents only 135 ms, your clock has already drifted so far that it now will never be correct.  At this rate, after ten minutes, you’ll be 30 seconds off.

Obviously, that’s not good.  What the system could do is reset your clock every ten minutes to the correct time.  Except that you’d notice a jump like that and you’d get annoyed.  Granted, 5% is a large error for a system clock, but the premise is the same regardless of the size of the error.  Small errors add up to big problems.  Even if you only drifted a second every ten minutes, that one second skip would cause trouble to enough people that it would be a major issue.  So, like I said, the Windows Time Service will change the speed of time to correct the clock.

It’s a bit like putting your finger on the second hand of a clock.  You can push it forward gently and the clock will move ahead, or you can resist the motion and the clock will fall behind.  That 15ms that the system adds to the clock is just a number.  On my machine, that number is 156250.2  That’s the number of 100ns units of time that will get added to the clock every time it ticks. 3  But if your clock is too far ahead, all the system has to do is add a smaller number (Say, 150000) for a period of time, causing your computer’s clock to slow down, and letting reality catch up.

Back to the graph, this time with a 50% error for illustration purposes.

It’s adding 22.5ms for every 15ms of real time.  Very quickly, your clock is going off the rails.  Let’s change the speed of time and fix this problem.

The Windows Time Service saw the fact that the time was way off and changed the speed of time, slowing down the clock to two-thirds of its normal speed.  It started adding only 10ms of computer time every 15ms of real time.  This solved the problem very quickly, but has the unfortunate side effect of leaving your computer’s clock running too slow.  Now, instead of zooming ahead, it’s going to be falling further and further behind reality.  The solution, of course, is to speed time back up.

Now, the corrections don’t happen on this scale.  This is a huge simplification of the bizarre reality you’ll uncover if you ever watch the Windows Time Service work its magic.  Usually you’ll see something like a drift of 20ms over 10 minutes, then over the next ten minutes, the time service will slow the clock to lose those extra 20ms.  All day long, you’ll see your system drift in and out of phase with reality, but it should never get that far out of tune, and you’ll never notice what it does just by watching the clock.

Unless, of course, you deliberately set your clock ahead or behind…

You have to be careful, because the time service has a couple of cutoff thresholds where it will force a corrective time jump in order to fix the problem.  However, if you stay within those thresholds, you can watch w32time do some pretty awesome things.  Like make your system time gain seven minutes over the course of five real minutes, or make every second last two seconds.

TAKE THAT, EINSTEIN.

Not that I’m suggesting you try this or anything, but while you’re at work tomorrow (On a computer that’s on a domain and synching regularly with an authoritative time source), get some popcorn, set your clock ahead or behind about 3.5 minutes, and compare your computer’s clock (You have to watch the view with the second hand) to some external time source, like your watch or cell phone.  When I’ve done this, it’ll start slowly.  The time will remain where it is for a while, then all of a sudden, time will accelerate (or decelerate) drastically for a period of a few minutes, then it will level off.  Within about half an hour, that 3.5 minute gap will be pretty much closed, and it never had to skip or repeat a second.

Of course, the specifics change with different machines, different Windows versions, and different network time configurations.  You may have to fiddle and twizzle with knobs and dials to see any noticeable result.  Fortunately, there’s Registry Settings located under HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\w32time that give you lots of knobs and dials to fiddle and twizzle.  You don’t necessarily want to mess with any of them, and I’d strongly recommend finding a reference to what they do before you set out on a time bending adventure using them.  I’d also strongly recommend exporting a backup copy of that reg key before you go mucking about, otherwise be prepared to be late for everything for the rest of your life.

Oh, and did I mention that you can change the speed of time yourself?  That makes this so much more fun, especially if it’s not your computer that you’re changing the speed of time on.  I’ll show you how to do that next time.

(If there is a next time…  I think I may have triggered a paradox and my hand seems to be disappearing now…)

  1. Go ahead, try it. []
  2. So, I lied.  It’s not 15ms, it’s 15.6ms, but I’m not typing 15.6ms all the time. []
  3. 100 nanoseconds is the base unit of time in the Windows world.  Windows File Time is given in the number of 100ns ticks since January 1st, 1601.  Why that date, I’m not sure, but I think it has something to do with the Illuminati and a Star Trek fan who wasn’t good at remembering numbers exactly.  There are 10000 of these in 1ms, and 10000000 of them in a second. []

March 14, 2010   2 Comments

Web Automation (or: How To Write A Bot To Steal Porn)

A while back, I wrote about using the System.Windows.Automation libraries to write automation to drive Windows applications. With SWA and UIAutomation, you can write code to use Win32 apps, Windows.Forms programs, WPF and even Silverlight. That’s all happy and fun, as long as you’re only dealing with Windows applications. Trouble is, there this thing called “The Web” that’s all the rage with kids these days, and sooner or later, you’ll probably have to use it, too. If you pull out your handy installation of UISpy and try to inspect a web page, you get a whole big block of nothing. The red rectangle will outline the window and tell you that all of those things that look like text boxes and buttons aren’t really text boxes and buttons. That means you can’t use SWA for web sites.

That, well, that kinda sucks.  So, what do you do about it?

Obviously, the correct solution here is to admit defeat:   The tool you know about doesn’t work, so it’s too hard to do.  Time to give up and pay thousands of dollars a seat for some whiz-bang tool that promises to do what you need and even has a handy-dandy recorder, so you don’t even have to think about what you’re doing!

Or…  Not.

That whiz-bang tool is only going to cost you money and it’s not going to do a damn thing for you.  You’ll have to pay high-priced consultants and high-priced support engineers just to figure out how it works.  You see, their model is to cram so many features in and make it so complicated to use that you think that you must be stupid because you can’t understand it and as soon as you figure out that one last thing, you’ll be more productive than you ever were before.

And oh, will that test recorder make you productive!  You’ll be able to hire a monkey to point and click your way to hundreds of test cases with ease!  Except that they’re hundreds of useless test cases, because either the verification that the tool provides is hopelessly limited and unable to actually verify your website, or, well, you hired a monkey to do your testing and they have no idea how to do anything beyond pointing and clicking.  But that’s all right.  You see, as soon as a single line in the HTML of your web page changes in just the tiniest way, every last one of those recorded tests will break and you’ll have to completely redo them.

So, SWA is out and the big expensive tool is a total waste.  What else is there?

Well, there’s things like Selenium or WebAii or WatiN.  They’re free or open source libraries that you can use to drive web browsers to do your bidding.  They all support IE and Firefox and possibly other browsers.  And they’re all written by people who don’t seem to have ever tried to write web automation.

  • Selenium:  The default mode is to write your tests in HTML tables, with the thinking that “Anyone can write HTML tables, so anyone can write tests.”  That’s not what happens.  What happens is that you set it up, all the devs and PMs excitedly chatter about how “Anyone can write tests now!”, you give a training session, two devs out of a team of seven will ever write tests using it, creating a grand total of thirteen absolutely worthless tests before giving up, yet somehow, two months later, the director of software engineering will be talking to the EVP of product development and tell him how great it is that we’re using Selenium because “Anyone can write tests now!”, so when you try to tell them what a complete waste it is and how you hate having to maintain the intermediary server and how unstable the test automation is and that we should dump the whole system, they look at you like you’re trying to kill a basketful of cute puppies.
  • WebAii, in my experience, is a tad unstable, and since it’s not open source, you can’t even try to fix it.  Additionally, it needs a plug-in to work, so again, you have to maintain a test machine.
  • WatiN hasn’t even been compelling enough for me to try to use.  That’s not saying it’s bad, it’s just that nothing about it has really stood out to me.

Another thing that really bugs me about these solutions is that many of them don’t really work that well with continuous integration situations, despite claims that they’re designed for that very use.  At my company, our CI servers are all using CCNet, which is running as a service.  When running as a service, you don’t typically get an interactive window station.  In general, that’s fine.  You don’t need one.  Our build servers are spare boxes stuffed in a cabinet somewhere or rack machines living in an off-site datacenter.  Once they’re set up, it’s pretty much all automatic.  We can log into the build box remotely in the rare instance that something does go wrong, but we never stay logged in.  In fact, we can’t.  You see,  in my company (and probably in yours), there are computing security policies in place that prohibit leaving an unattended computer logged in and unlocked.  If you leave your computer unlocked, it will be locked for you.1  Trouble is, most of these web automation libraries I mentioned above require a logged in and unlocked session to function at all. 2

Okay, so no SWA, no expensive tool, and now the free stuff is shot down, as well.  What’s left?

Wouldn’t it be great if there’s something that’s free?

Wouldn’t it be great if there’s something that’s already installed on pretty much every copy of Windows since 95 OSR2?

Wouldn’t it be great if there’s something that works in headless service environments?

Wouldn’t it be great if there’s something that uses the same technology as the majority of web users?

In other words, why don’t you just use Internet Explorer to do your web automation?

Now, I’m guessing that you just answered my question with some sarcastic remark regarding Firefox, so let me address that before continuing.  Yes, using IE means you’re not using Firefox.  I understand that you like Firefox and all, but in the real world, people use IE.  Additionally and importantly, it usually doesn’t really matter that you’re only using IE.  Most of the differences between browsers are cosmetic things, like Firefox’s strange habit of occasionally making oversized divs that mask clickable areas or IE6 generally making every page look as attractive as cat vomit.  Normal web automation, regardless of what tool you’re using, will typically not pick that sort of thing up.  Web automation looks at the structure and functionality of the page, but it’s blind to the looks.  Many of the other tools I mentioned do support Firefox, if you need it, but you probably don’t need it.   After several years of web testing, I’ve only come across a handful of cases where running an automated test in Firefox would have picked up issues that would not have been seen in IE. 3  For the most part, going the extra mile to support Firefox in your automation is unnecessary and simply complicates things.

So, let’s look at using IE to solve all of your automation problems!

Okay, it won’t solve all your problems.  In fact, it’ll create new ones, I guarantee it.  But still, it’s very useful.

But first, a little warning…

You’re going to have to use COM.

Well, okay, you don’t have to use COM.  There is a .Net Web Browser class that you can probably do most of these things with, but I don’t use it.  I don’t use it because, as far as I’ve found, there’s no way to attach it to a real instance of IE.  Instead, you’d have to write your own little Windows Forms app, stick the control on it, and use it that way.  That might work for you, but I’ll stick to the full instance of IE that I can watch and manually interact with if necessary, even if it means using COM.  It is COM in .Net, though, so it’s not as bad as straight COM in C++.  There’s no QueryInterface or CComPtr<>s anything like that.  There are slightly weird things now and then, but they’re not that bad.

Right.  Disclaimer out of the way, let’s get started.

First, you need to add two references to your project.  Add a reference to your project, go to the COM tab in the dialog, and select “Microsoft HTML Object Library”, which will give you MSHTML, and “Microsoft Internet Controls”, which will give you SHDocVw.

 MSHTML is where all of the HTML parsing and related classes live.  SHDocVw is where the Internet Explorer classes are.

Now that you’ve added those references, add your using statements for the libraries, so you won’t have those ugly namespaces all over your code.

using mshtml;
using SHDocVw;

Note that although the reference to MSHTML gives the name in all caps, the namespace is, in fact, lower case.  SHDocVw is the same case both places.

Once you’re set up, you can create an instance of Internet Explorer that will launch and be ready for you to drive it through your code with one line:

InternetExplorer ieBrowser = new InternetExplorerClass();

Of course, there’s a slight problem here.  You can’t actually see the browser.  It’s there, trust me, it’s there, and pretty much everything I’m about to talk about will still work, even though you can’t see it.  However, in the interest of proving to you that what I’m talking about does, in fact, actually work, let’s make a minor modification so you can see things.

InternetExplorer ieBrowser = new InternetExplorerClass();
ieBrowser.Visible = true;

There, if you run that, IE will pop open.  It won’t do much yet, but at least there’s some progress being made.

A brief aside:  You may have noticed that I created an instance of “InternetExplorerClass”, but assigned it to a variable of type “InternetExplorer”.  I did that because InternetExplorer is actually an interface, so you can’t create an instance of it. 4  InternetExplorerClass is the actual class that you need an instance of.  You could probably also do something with Activator.CreateInstance(), but I’m not going there.  I’ll have more about interfaces in a bit.

Back to the fun, to prove that we’re in control, and to start doing something actually useful, let’s point the browser at a website.  Let’s have our browser go to everybody’s favorite search engine:  Dogpile.com.  To navigate the browser you’re in control of, you use the .Navigate() method.  Unfortunately, .Navigate is all COMtaminated and ugly. 5

No, that’s not Intellisense having a freak out.  The signature of the Navigate method is actually void IWebBrowser2.Navigate(string URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers).  You only care about the URL, but it’s not going to provide you with an overload that only uses the URL.  Instead, you get all of this “ref object” crap. 6

I bet your first instinct is to think that you’ll just pass nulls to the parameters you don’t care about, and compile it and be happy.  Well, that ain’t gonna work.  See, the “ref” part of the signature means that it expects an actual object reference.  null is not an object reference, null is nothing.  The compiler won’t let you pass in nulls directly.  However, you can pass in a null object reference, and that’ll work.  Like so:

public static void NavigateToUrl(InternetExplorer ieBrowser, string url)
{
    object nullObject = null;
    ieBrowser.Navigate(url, ref nullObject, ref nullObject, ref nullObject, ref nullObject);
}

You may have noticed that I put the Navigate call inside a helper method.  Helper methods and wrapper classes are one of your closest friends in the world of SHDocVw and MSHTML.  It’ll help hide all of the IE COM object’s interesting personality quirks in much the same way that girl you met on Match hid her interesting personality quirks until the fifth date.  Trust me, you don’t want “ref nullObject” a thousand different places in your tests, largely because it’ll scare the hell out of anyone reading your code.

If you go back to the main function and call NavigateToUrl(ieBrowser, "http://www.dogpile.com");, then run the code, you’ll have a browser that will open up and go to Dogpile all by itself.  Of course, if you are running the code as we go, you’ll probably have noticed that the browser remains open after your program exits.  Let’s take care of that before your computer explodes under the weight of a thousand IEs, shall we?  Just call .Quit() on the browser and it’ll go away.

If you call .Quit() immediately after navigating, the browser will probably close before the page even loads, so let’s add a sleep for a few seconds so you can see what’s going on.

For those of you playing the home game, here’s what my main function looks like at this point:

InternetExplorer ieBrowser = new InternetExplorerClass();
ieBrowser.Visible = true;
NavigateToUrl(ieBrowser, "http://www.dogpile.com");
Thread.Sleep(5000);

//Do stuff here...

ieBrowser.Quit();

At this point, the code above is fairly useless.  Sure, you can use this to build a program that forces IE to navigate to web pages all day, but that’s not terribly exciting.  We’re having the browser navigate to a search engine, why don’t we search for something?

(By the way, you’ll want to leave that Thread.Sleep(5000); where it is.  I’ll come back to it later, but for now, DON’T TOUCH!)

When you search for something, what do you do?  Type a word in a box and click a button, right?  That’s what we need to do here.  The InternetExplorer object allows you to access all of the HTML elements on the page and interact with them, including text boxes and buttons.  If you’ve ever used JavaScript and dealt with the Document Object Model, or DOM, the methods and properties you’ll find in MSHTML will be very familiar, because they’re another implementation of the DOM standard.  The way you gain access to HTML elements is through the .Document property.

If you try to use it, Intellisense will be really helpful and tell you that the .Document property is an object.  A plain object.  A plain, useless object.  So what is the .Document property returning?

An IHTMLDocument object.

Or an IHTMLDocument2 object.

Or an IHTMLDocument3 or 4 or 5 object…

Now’s probably the time to talk about the use of interfaces in MSHTML.

In the land of .Net, if you had an IHTMLDocument5 interface, it would probably derive from IHTMLInterface4, which would derive from 3 and so on.  IHTMLDocument5 would have all of the stuff that was on the previous four interfaces, so that would be the only one you’d ever need to use, at least until IHTMLDocument6 comes along.  Not so in the land of MSHTML.  I’m not sure if it’s a COM restriction, a C++ thing, the way .Net deals with COM interfaces, or some strange design decision on the part of MSHTML, the end result is that IHTMLDocument3 and IHTMLDocument2 are pretty much independent.    If you want the title of the page, you need a reference to an IHTMLDocument2 object. If you want to call .getElementById(), you need IHTMLDocument3.

But that’s only if you want to do it the “Right” way.  If you want to do it the quick and easy way, then the .Document property is returning an HTMLElement object.  That’s the class that implements IHTMLDocument*, so it’s got everything on it.  If you want the page title and if you want to call .getElementById(), HTMLDocument will work for you.

Of course, it’s slightly riskier to do it that way.  The interfaces guarantee the contract, the class does not.  Microsoft could change the class at any time and you’d be screwed.  However, I highly doubt they’re going to do anything like that, because it would screw them over far more than it’ll screw you over.  In other words, just use HTMLDocument and you’ll have access to all the available properties, functions, events, etc., without having to cast between the interface types three hundred different places in each method.

It’s important to know that IHTMLDocument*s exist, since that’s where you’ll find much of the documentation.  And on a similar note, all of the HTMLElements that I’m going to talk about have corresponding interface types, and they’re usually what’s documented or talked about.  So, if you can’t find something about how HTMLElement works, try looking for IHTMLElement.  Or IHTMLElement2.  Or 3.  Or 4.

Now that we’ve taken that little vacation, let’s get back to work here.  I made such a big fuss about getting the page title, so let’s do that here.

HTMLDocument htmlDoc = (HTMLDocument)ieBrowser.Document;
string pageTitle = htmlDoc.title;
Console.WriteLine(pageTitle);

Before you can interact with an element on a page, you have to find it in the document.  There are two easy ways to find things, along with a few ways that aren’t quite that easy.  Here are the ones I find the most useful.

  •  .getElementById(string):  This method takes the ID of the HTML element you want and returns the IHTMLElement with that ID. In HTML, an ID is supposed to be a unique identifier, identifying a single element.  Of course, certain popular HTML editors (like Notepad, for instance) won’t enforce a unique ID, so if there are multiple elements with the same ID, this method will return one of them.  This one is good if you know exactly what you’re looking for.
  • .getElementsByName(string):  This method takes the name of HTML elements and returns an IHTMLElementCollection of all of the elements with that name.  This one is good if you have an element or a handful of elements with a known name.
  • .getElementsByTagName(string):  This method takes a tag name, like “a” or “img” or “div” and will return an IHTMLElementCollection of all of the elements with that tag name.  Use this method to quickly get a collection of all of the links or images on a page, or if you’re looking for an element of a certain type with certain characteristics and need to run through the list to find it.
  • .documentElement:  This property returns an IHTMLElement of the root of the HTML content of the page.  On a page that plays by the rules, this will be the <html> element.  If your page doesn’t play by the rules, good luck.  This is a good starting point if you want to walk the tree.
  • .childNodes:  This property will give you an IHTMLElementCollection of the direct children of the current node.
  • .all:  This property returns an IHTMLElementCollection containing a flattened list of all of the elements in the document.  Use this when you don’t care about structure and need to do something that involves lots of nodes of different types.

Unfortunately, as far as I’ve found, there’s no support for something like XPath, which would let you give the node tree path of the elements you want in a simple string format.  If you enjoy pain, you could build something like that yourself.

 The specified return type for most of these methods is IHTMLElement, which is the base element type in MSHTML.  In reality, the element instances are all specific element types.  For instance, an <img> tag will return an IHTMLImageElement object, and an <a> will give you an IHTMLAnchorElement object.7  The specific types will have specific properties, so if you know what element type you have and you need to use it for something (Say, for instance, if you need to get the src attribute from an <img>), then you should cast it to the specific type.

Right-o, let’s start doing useful stuff, shall we?  Back before we took a wild turn and ended up hopelessly sidetracked, we had Internet Explorer going to the front page of the search engine Dogpile.com.  Now, let’s make IE do a search.  To do that, we need to grab the search box, put text in it, then grab the search button and click it.  We’ll use the .getElementById method I talked about to get the search box.  Using something like the IE developer tools or Firebug8 or even viewing the page source in Notepad, you can find that the search box is an <input> element with an ID of “icePage_SearchBoxTop_qkw”.

IHTMLInputTextElement textBox = (IHTMLInputTextElement)htmlDoc.getElementById("icePage_SearchBoxTop_qkw");
textBox.value = "powered by awesome";

If you run this, the browser will open, and the phrase “powered by awesome” will appear in the search box.

A couple of points to note.  Even though in the HTML, the search box is an <input> tag, the element you’ll get back is an IHTMLInputTextElement.  The different <input> types are all represented by distinct classes, which is very helpful, because there’s not much in common between a checkbox, a text box, or a button.  Then, once you have the element, it has a .value property, which acts as a getter and setter for the contents of the text box.

Grabbing the submit button is similar:

IHTMLInputButtonElement submitButton = (IHTMLInputButtonElement)htmlDoc.getElementById("icePage_SearchBoxTop_qkwsubmit");

Unfortunately, when you try to click the button, you’ll run into this:

There’s no click there.  There’s nothing remotely resembling a click.  A button’s sole reason for existing is to be clicked, yet you can’t click this button.

Actually, you can.  Just not on IHTMLInputButtonElement, where you’d think you should be able to.  You see, you can actually click on any HTML element, so the .click() method is on the base IHTMLElement.  This goes back to the interfaces I went rambling on and on about a while back.  To find the functionality you want, you sometimes have to bounce around almost randomly until you find what you need.  So, to hell with the interfaces, let’s go directly with the concrete class again, like we did with the document.  In this case, it’s HTMLInputButtonElement. 9

HTMLInputButtonElement submitButton = (HTMLInputButtonElement)htmlDoc.getElementById("icePage_SearchBoxTop_qkwsubmit");
submitButton.click();
Thread.Sleep(5000);

Again, there’s a Thread.Sleep() after the action, so the program will wait long enough for the page to finish loading.  And again, I promise I’ll talk about it later, but for now, trust me and just leave it there.

Now we’re on an entirely new page.  If you try to use the document or the elements you grabbed before, the results will be, uh, shall we say, unpredictable…  The old page no longer exists, so don’t try to use anything from it.  You have to grab a new reference to the document, as well as new elements to play around with.

We’re on a search results page now, so let’s do something like print out all the result titles and the URLs to all of the images.

Console.WriteLine("Links by tag name:");
foreach(IHTMLElement anchorElement in htmlDoc.getElementsByTagName("a"))
{
    if(anchorElement.className == "resultLink")
    {
        Console.WriteLine(anchorElement.innerText);
    }
}

Console.WriteLine("Links by result walking:");
IHTMLElement resultContainerDiv = htmlDoc.getElementById("icePage_SearchResults_ResultsRepeaterByRelevance_ResultRepeaterContainerWeb");
foreach (HTMLDivElement resultDiv in (IHTMLElementCollection)resultContainerDiv.children)
{
    IHTMLElement resultLink = (IHTMLElement)resultDiv.firstChild;
    Console.WriteLine(resultLink.innerText);
}

Console.WriteLine("img src:");
foreach (IHTMLImgElement imgElement in htmlDoc.getElementsByTagName("img"))
{
    Console.WriteLine(imgElement.src);
}

Console.WriteLine("img src 2:");
foreach (IHTMLImgElement imgElement in htmlDoc.images)
{
    Console.WriteLine(imgElement.src);
}

The first bit walks through all of the links, which are <a> tags, looking for elements with the class “resultLink”.  When it finds one, it prints out the .innerText property, which contains the flattened text content of the element.  The second section finds the same elements, but walks through a bit of the tree structure to find the links among children nodes.

I should probably point out now that the structure of websites tends to change over time, so if you try to run this and you get a bunch of exceptions, that’s what’s going on.  If anything on the page changes, this code is likely to break.  It works for me right now, and that’s really all that matters anyway.

The last bit, the part with the “img src” is walking through the page and printing out the URLs of all of the images on the page in two different ways.  First by using the tag name method you already have seen, and the second time by using the .images convenience property on the document.  There are a few other properties like that, so take a look at what Intellisense shows you and play around a bit to get a feel for what’s there.

BUT WAIT, THERE’S MORE!

We’ve got all this access to stuff on the page.  We can put text in text boxes, we can click buttons, we can read the links and images, so why not step it up a notch and modify the page in some crazy way.  Like, I don’t know, maybe we could put a box around every div on the page?

Like so:

foreach (HTMLDivElement divElement in htmlDoc.getElementsByTagName("div"))
{
    divElement.runtimeStyle.borderStyle = "groove";
    divElement.runtimeStyle.borderWidth = "3";
}

KABOOM!

Of course, a crazy box cascade is of little practical value, but you get the basic idea of what you’re able to do.  You’re inside the page being rendered, so you can completely rewrite it if you want.  You’re not stuck with a static, read-only page, so learn how and where to use that to your advantage.  I’ve used this ability inside tests to write out debug information or inject JavaScript functions to be called by the automation.

Here’s the full example code from today:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using mshtml;
using SHDocVw;
using System.Threading;

namespace IEAutomationSample
{
    class Program
    {
        static void Main(string[] args)
        {
            InternetExplorer ieBrowser = new InternetExplorerClass();
            ieBrowser.Visible = true;
            NavigateToUrl(ieBrowser, "http://www.dogpile.com");
            Thread.Sleep(5000);

            //Do stuff here...
            HTMLDocument htmlDoc = (HTMLDocument)ieBrowser.Document;
            string pageTitle = htmlDoc.title;
            Console.WriteLine(pageTitle);

            IHTMLInputTextElement textBox = (IHTMLInputTextElement)htmlDoc.getElementById("icePage_SearchBoxTop_qkw");
            textBox.value = "powered by awesome";

            HTMLInputButtonElement submitButton = (HTMLInputButtonElement)htmlDoc.getElementById("icePage_SearchBoxTop_qkwsubmit");
            submitButton.click();
            Thread.Sleep(5000);

            htmlDoc = (HTMLDocument)ieBrowser.Document;

            Console.WriteLine("Links by tag name:");
            foreach(IHTMLElement anchorElement in htmlDoc.getElementsByTagName("a"))
            {
                if(anchorElement.className == "resultLink")
                {
                    Console.WriteLine(anchorElement.innerText);
                }
            }

            Console.WriteLine("Links by result walking:");
            IHTMLElement resultContainerDiv = htmlDoc.getElementById("icePage_SearchResults_ResultsRepeaterByRelevance_ResultRepeaterContainerWeb");
            foreach (HTMLDivElement resultDiv in (IHTMLElementCollection)resultContainerDiv.children)
            {
                IHTMLElement resultLink = (IHTMLElement)resultDiv.firstChild;
                Console.WriteLine(resultLink.innerText);
            }

            Console.WriteLine("img src:");
            foreach (IHTMLImgElement imgElement in htmlDoc.getElementsByTagName("img"))
            {
                Console.WriteLine(imgElement.src);
            }

            Console.WriteLine("img src 2:");
            foreach (IHTMLImgElement imgElement in htmlDoc.images)
            {
                Console.WriteLine(imgElement.src);
            }

            foreach (HTMLDivElement divElement in htmlDoc.getElementsByTagName("div"))
            {
                divElement.runtimeStyle.borderStyle = "groove";
                divElement.runtimeStyle.borderWidth = "3";
            }   

            Thread.Sleep(5000);
            ieBrowser.Quit();
        }

        public static void NavigateToUrl(InternetExplorer ieBrowser, string url)
        {
            object nullObject = null;
            ieBrowser.Navigate(url, ref nullObject, ref nullObject, ref nullObject, ref nullObject);
        }
    }
}

As always, you can pull the project out of SVN:  http://www.mathpirate.net/svn/Projects/IEAutomationSample/

That’s about all I wanted to get into as far as a hands-on demonstration.  Now, it’s time for warnings about what can and will go wrong.  So watch out.

First, as promised, let’s talk about those Thread.Sleep()s that I scattered throughout the code.  They’re there because you have to wait for the browser to finish its work, otherwise you’ll get random exceptions.  Exceptions that will never happen when you step through in a debugger, either.  However, it’s not a good practice to rely on sleeping for a fixed amount of time in your automation.  If the browser loads the page in half a second, but you’re sleeping for five seconds, then you’ve wasted four and a half seconds.  That kind of time adds up fast.  On the other hand, if the process is slow, five seconds might not be enough.  Your application will wake up too early and die.

In most cases, I’d suggest polling.  Check the status of something, or look to see if something exists fairly frequently, but keep looking for a reasonable amount of time.  For instance, you could check the .Busy flag on the InternetExplorer object every 100 ms for 30 seconds.  That way, you’ll never sit around for more than 100 ms longer than you need to, plus, you’ll keep checking long enough to be sure that it will finish.  If the page isn’t done loading in 30 seconds, you should probably fail right there.

Except that polling the .Busy flag doesn’t actually work reliably.

If you try to poll the Busy flag exclusively, you’ll find that your tests will sometimes randomly fail.  They’ll look like they should be working.  IE will be loading the page you expect it to load and everything will look right, but you’ll get an exception.  You see, you’re not synchronously driving IE.  You’re talking to an intermediate layer that’s relaying your commands to IE, and IE will respond eventually.  What that means is that you’ll tell IE to load a page, then you’ll check the Busy flag.  Most of the time, Busy will return true because it’s loading the page or false because it’s done loading the page.  But sometimes, your check on the Busy flag will get to IE before it’s started loading the page.  In this case, Busy will return false.  As far as IE is concerned, it’s not busy.  It’s done loading the page.  Trouble is, it’s telling you that it’s done loading the last page, not the page you just told it to load.

One way to counteract this is to sleep for a small amount of time before starting the polling, perhaps 250 ms.  This usually gives IE a chance to start moving and will increase the reliability.  However, it’s going to have the same problem as sleeping did originally.  You’ll often be wasting time waiting around for something that’s already done, and occasionally, you still won’t be waiting long enough.

Another way to combat this is to listen to some of the events hanging on the InternetExplorer interface.  There are events, such as NavigateComplete2, DocumentComplete, and DownloadComplete that you might be able to handle and set your own status flags in.  For instance, you can set a flag before you start to navigate, then have your NavigateComplete2 event handler unset that flag when it’s called.  If it’s called…  And if it’s called for the correct navigation event.  You have to be very careful with some of these events.  I believe DownloadComplete is fired by XMLHttpRequests used by AJAX calls, so that could trip up your detection.  NavigateComplete2 will get called when the main page finishes loading as well as when a frame finishes loading, so if you have a hidden iframe on your page for something like tracking and analytics, watch out for that.

I still have not found a flawless way to wait for page completion.  I’ve found a complicated tangle of states and flags and events that make it work in most cases, but not all.  So, good luck with that.

Security will also get in your way when dealing with IE Automation.  Microsoft rightfully doesn’t want script kiddies and other assorted bastards being able to do things like automatically download files to your computer.  Unfortunately, script kiddies are using the same bit of DOM technology that you’re trying to use, and MS has no way to tell you apart, so that means that sometimes you’ll be blocked from doing things.  I don’t think you can read from a password text box and I don’t think you can directly write to a file upload control.  Sometimes when you click links or buttons that launch certain actions like file downloads, you’ll get a yellow bar that wouldn’t be there if you’d clicked the button yourself.  You have to find crazy workarounds for these issues.  Sometimes you’ll spend all day trying circumvent IE’s security just to click one stupid button.

Another issue you’re likely to run into are random, unexplained failures, often with useless error messages, like “COMException -21234115153″ or “RPC server has exploded, try again.”  Many of these exceptions will be timing problems.  Wait just a little longer and you’ll be fine.  I’ve had the constructor for the IE COM object give me an instance of IE that had already been destroyed.  Some errors I’ve seen are obscure COM threading issues.  You’ll get InvalidCastExceptions trying to access some of the properties, like  .location or .frames, even though you’re not casting anything.  You can sometimes fix those by setting your application to run in a Single-Threaded Apartment (Whatever in the hell that means) by putting the [STAThread] attribute on your Main method…  If you have a Main method.  If you’re in some library, or someplace like NUnit or VS Unit Tests, well, then, you’re just plain screwed.  And just this past week, I ran into a case where ieBrowser.HWND would throw an InvalidCastException every other time I called it.  Seriously, odd numbered of calls led to an exception, while even calls gave me a number.  The fix?

try { hwnd = ieBrowser.HWND; }
catch { hwnd = ieBrowser.HWND; }

Seriously.  I wrote that this week.  WTF?

I still feel dirty.

And finally, speaking of dirty, writing a bot to steal porn is left as an exercise for the reader.

  1. After some kind soul Hasslehoffs your desktop… []
  2. For that matter, so does SWA, but that’s a different story. []
  3. A Firefox specific toolbar and some Javascript issues []
  4. It really bugs me, too, because it should be IInternetExplorer… []
  5. And to make it even better, there’s a Navigate2() method, which is even uglier. []
  6. In the C++ world, the ref objects are all VARIANT*s.  The .Net magic that lets you use COM translates the VARIANT to object and the * to the ref.  Unfortunately, every one of those parameters could have had a strong type.  Flags is an int, TargetFrameName is a string (Well, BSTR, but whatever), and so on.  It didn’t have to be like this!  ARGH COM. []
  7. Okay, they’re really HTMLImageElements and HTMLAnchorElements, but who’s keeping track? []
  8. Yeah, Firebug is for Firefox, but a good web tester will have at least two or three browsers at the ready at all times. []
  9. Just don’t look too closely at the definition of HTMLInputButtonElement or HTMLDocument or any of the other things I called concrete classes, or you’ll discover that they, too, are interfaces.  The actual class is HTMLInputButtonElementClass or HTMLDocumentClass.  Whatever.  I don’t know what’s right and what’s real anymore… []

February 13, 2010   2 Comments

UI Automation: Tricks and Traps

UI Automation and testing can be among the trickiest areas of software testing.  Directly testing an API is relatively easy.  You’ve got functions to call, well defined inputs and outputs.  It’s meant to be used in the way you’re using it when you write your tests.  You can spend most of your time writing real test cases that will generally work correctly with minimal effort.  UI Automation, however, isn’t nearly as friendly.  You’ll sometimes spend hours twisting and tweaking one test case to get it running, and even then it’ll still randomly fail 25% of the time.

A large part of this is due to the fact that a UI is meant to present an interaction model for a human.  It’s not actually meant for another computer program to deal with.  A person is clicking the buttons and typing text in the text boxes and so on.  Allowing a computer to interact with it is usually an afterthought, hacked together using technologies that will work, sometimes, and only if the application programmer followed the rules.  If they’re not using a button, but instead are using something that they’re drawing themselves to look and act like a button, it’s not going to be a button for you and your UI tests aren’t going to be able click it easily.

Another major problem with UI tests is that the user interface frequently changes.  That’s not supposed to be a radio button, it’s supposed to be a check box.  Move that button after the text box.  Make that list box a combo box.  The UI is often the most fluid piece of a software application.  Once the API is in place, your API tests have a decent chance of working version over version, because an API isn’t subject to focus groups or marketing studies.  But it’s very rare to leave the UI untouched between versions.

There’s also a problem of perception regarding what UI tests do.  People often think that since UI automation is testing the UI, that means that it’s covering the look of the UI, as well.  Most of the time, it won’t because it can’t.  It’s very difficult to have automated visual testing.  Sure, you can compare screenshots, but what if the window size changes?  It’ll break if you move a button or box.  Your graphical verification tests will report complete and total failures if you took the screenshots on plain XP and someone later uses Vista with Aero to run them.  Hell, they’ll likely die if you turn font smoothing on or off.  Doing something so fragile is what we testers call “A Waste of Time”.  UI testing generally doesn’t cover the look of the application.  Instead, it verifies the correct functionality of the controls in the application.  It’s possible to have your UI tests reporting a 100% success rate when nothing is shown on the screen.  As long as the controls are accessible in the way you specify in your tests, they’ll run.

So then, what can be done about automated UI testing?  It’s obviously very valuable to have, despite the difficulties.  Here’s a few tips and tricks, as well as some traps to avoid.

Name Everything:

In web applications, you can give elements IDs or names.  In regular Windows apps, you can use SWA or  MSAA to identify things.  At any rate, anything you interact with should be uniquely identifiable in some way.  If you’re a developer, do this.  If you’re a tester, get your devs to do this.  If they refuse, do it for them.  Naming things will tend to make your automation resilient in the face of most general changes.  Bits and pieces can move around, but as long as they’re named the same and work the same way, your test will probably survive.

You don’t have to give a completely unique identifier to absolutely everything.  What I’ve found that tends to work well is giving logical groups a unique id for the current window or page, then naming repeated controls.  Consider, for example, a page of results from a search engine.  You’ll have a search box at the top of the page and at the bottom, and you’ll have multiple sets of results in the middle area.  Give the logical areas unique IDs, like “SearchBoxTop” and “SearchBoxBottom” for the search boxes, and “MainResults”, “AdResultsRight” and ”AdResultsTop” for the result sections.  Then, those areas can share names across them.  For instance, I don’t really care that I’m dealing with the top search button or the bottom search button specifically.   All I need at that point is “Button”.   “Button” can be used as a name for fifteen controls on the page, but I already know that it’s the top search button I’m using because I got it in the context of SearchBoxTop. 

Turn UI Testing Into API Testing.  Sort Of…:

I’ve seen UI test code that’s an unreadable mess of copied and pasted bits to extract controls or elements followed by copied and pasted unreadable messes where the controls or elements are fiddled with followed by messes of bits that had been copied and pasted to the point of unreadability which extract results from controls or elements.  In fact, that’s what pretty much any test recorder will spit out at you.  It’s a total nightmare to look at and deal with even on a good day, and if you’re looking at it and dealing with it, chances are it’s not a good day.  Chances are all your tests broke last night and now you have to dig through a hundred separate tests and repair the element extraction code in each one of them, all because your UI developer made a “quick change” from tables to divs in the page layout.  Even though the page looks identical, the entire structure is different now and nothing is going to work.

I mentioned in the intro that API testing was relatively easy, because it’s typically well defined what you’re doing and how things are expected to function.  Things may fail, but usually they’ll fail in somewhat predictable ways.  Well, the best way I’ve found to make UI testing easier is to make it closer to API testing.  Wrap the code that interacts with the UI that you’re testing in classes and functions that behave somewhat predictably and expose the bits and pieces of the UI in ways that make sense.1  I prefer to create a class with ordinary properties or methods that operate on a web page or dialog or whatever.  Going back to the web search example, you’ll have a page with a text box and a button next to it.  That translates to a simple class along these lines:2

public class SearchPage
{
    public string SearchText { get; set; }
    public void ClickSearch();
}

Then it’s up to the SearchPage class to determine how to find the text box how to click the button, and to deal with all of the nonsense and WTFery that the UI throws at you.  Your test case that needs to do a search then only needs these two lines:

...
    SearchPage page = new SearchPage();
    page.SearchText = "nuclear manatee seesaw detector";
    page.ClickSearch();
...

In that example, it should be clear to anyone looking at the code what’s going on.  It’s not full of element paths and SWA control patterns.  I’m just setting the text of the search box and clicking a button.  Your test usually doesn’t care about the mechanics of getting the textbox filled in or what kind of stupid tricks are required to click the button, and it shouldn’t.  Doing it this way means that it won’t.  And then the next time the devs make a “quick change” that breaks everything, you only have to make a “quick change” yourself to the code of the wrapper classes and everything should be fixed.

Always Have A Plan B.  And A Plan C.  (And D…):

Successful UI Automation often requires hacks.  Not just hacks, but dirty hacks.  If you feel completely clean after writing UI automation, then there’s a good chance your tests won’t actually work.  Start by trying to do everything the “right” way, using the controls provided to you by SWA or the browser DOM or what have you.  They’ll work, most of the time.  Unfortunately, every so often you’ll run into a button or a dialog that just doesn’t behave.  Sometimes there are security measures put in place to prevent automated tasks from doing certain things, for instance downloading files in a browser.  You have to be ready to defeat whatever is thrown in your way.  Remember, you’re dealing with UI elements, so if you have to, you can act like an actual user.  Can’t “click” a button using SWA’s InvokePattern?  Try simulating a mouse click or sending keystrokes to the application (Space or Enter will usually activate a button that has focus).  Hell, if you need to, don’t be afraid to buy a Lego Mindstorms kit and build a robot that can click a physical mouse button for you.

SendKeys is Your Worst Enemy

 Available through the Windows API, as well as exposed in the .Net Framework, there’s a function called “SendKeys”.  It lets you send keystrokes to windows.  The application will then respond as if an actual user pressed the keys.  You can use keyboard shortcuts, tab through dialogs, type text into textboxes.  Pretty much anything a user can do from a keyboard, you can do with SendKeys.  It might be tempting to write all of your UI automation using SendKeys, but don’t.  Just don’t.  SendKeys is one of the least reliable and most fragile ways to try to interact with your software.  It won’t survive any kind of change to the interface, and even when it is set up properly, it doesn’t always work right.  Keys will get lost or come early or late, and if the focus changes at all for some reason, you’re screwed.

SendKeys is Your Best Friend

When all else fails, SendKeys will get the job done.  I once ran across a pretty normal looking Windows dialog, with normal looking buttons.  Unfortunately, for whatever reason, the dialog refused to respond to any kind of standard attempts to reach it.  I tried SWA first, and although I could find the button I wanted to click, Invoking it did nothing.  So I tried sending the button a Windows Message to tell it that it had been clicked.  Still nothing.  Then I tried setting its focus and sending the Enter key and still nothing.  In the end, what worked was SendKeys(“{Right}{Right}{Enter}”), which selected the button and triggered it completely from the keyboard.  Not a happy solution by any means, but it worked and that’s all that matters.  It’s definitely worth learning its syntax for those obscure cases where you need to hold down ALT for twenty seconds or whatever.3

Beware of “Don’t Show This Dialog Again” and Similar Conditions

You know that dialog option.  It’s everywhere and you always check it.  It turns off stupid things like the “Tip of the Day” or warnings about the mean and scary hackers that want to steal your life on the Internet.  And it will come back to bite you when you try to do UI automation.  You’ll write your tests on your machine and they’ll run beautifully.  Then you’ll put them on your automation box and they’ll fall apart because there’s some window or dialog that appears that you had long forgotten about.   You’ll need to alter your test to take into account the possibility that an optional dialog might be there and handle it if it is or move along quickly if it isn’t.  Speaking of which…

Waiting, Waiting, Waiting…

Pretty much any piece of UI automation will have some kind of timing dance.  Normal API testing is usually synchronous.  You call a method and are blocked until it returns or have some clear way of waiting for an asynchronous operation to complete.  This is often not the case with UI automation.  After all, you’re trying to run something that doesn’t know about you and doesn’t care about your schedule.  As a human, you click an icon, wait a few seconds, and continue when the application has finished opening.  You can’t just do that with your automated test.  You click, fine, that’s easy.  Then what?  You have to wait, but for how long?  One second?  Two?  What happens if your virus scanner kicked on when the test is running and now it takes ten seconds to open the application?  It’s ridiculous to force your test to wait for ten seconds every time just in case something goes wrong, but it’s equally bad to only wait one second and fail the test one out of ten times when something does go wrong.  The common solution is to poll, looking for something you expect, like a window with a certain title.  Every 100 ms or so, see if the window (or whatever) you’re waiting for is there yet.  But don’t wait forever, because if it doesn’t show up, you don’t want to be stuck.  Use a reasonable timeout that you’re willing to wait before giving up and fail after you reach that point. 

Okay, so you’ve waited for the window to show up, so you can continue with your test.  CRASH!  Well, sure, the window is there, but the control you’re trying to use won’t actually be visible for another 20 ms, so your test dies in a fire.  Watch out for things like that.

Wherever possible, use some indicator within the application itself as a guide for when something is done.  If your app has a status bar that reads “Working” when it’s working and “Done” when it’s done, then watch that status bar text for a change.  If a file is supposed to be written, then look for that file.  You have to be careful, though, don’t always trust the application outright.  As you’ll soon see, the application isn’t always telling you what you really want to know.

My absolute favorite brainbender of a timing issue is dealing with the IE COM object that lets you run browser automation through the IE DOM.  With this COM object, your commands are shipped off to be executed in another process, largely asynchronously.  You call the navigate method to open a web page.  Obviously, since you’re opening a web page, that can take some time, so you make sure that you wait for the page to finish loading before you begin the test.  Your test runs perfectly about 70-80% of the time.  But there’s that remaining chunk where your test reports that it can’t find the page element you’re trying to use.  So, you watch the test run.  It opens the browser and navigates to the page, the element is clearly present on the page you see, yet your test whines that it’s missing and it dies.  WTF?  As far as you can tell, everything is doing exactly what it should be doing except for the failing miserably part somewhere in the middle.  You step through in a debugger, hoping to catch the bug in action, but it works every time.  Here’s where it gets fun:  The IE instance that you’re driving lives in another process and operates on its own time.  You send off an asynchronous request to load a page, then almost immediately thereafter, you ask it if it’s done.  Most of the time, the browser will say “Not yet”, and your test goes to sleep.  But, once in a while, the browser responds, “Yeah, I’m done” on that first request.  You continue, and die because obviously it hasn’t loaded your page yet.  Why is it saying that it has?  Well, you’re not asking if it’s loaded the page you’re looking for.  You’ve asked if it’s done loading.  It says “Sure”, because as far as it’s concerned, it is done…  It’s done loading the LAST page you sent it to.  It hasn’t even started loading the page you just told it to go to.

Debugger == FAIL:

Stepping through your automated UI test case in a debugger is a blueprint for fail.  It won’t work right.  It just won’t.  Your test is happily humming along, driving controls, setting text, having fun, when all of a sudden, a breakpoint is hit.  Your trusty debugger IDE comes to the foreground and you tell it to step to the next line.

Where are you now?

The debugger stole focus.  Does it give focus back to the window you were at before?  Does it give it back to the same control?  When the debugger steps in, it FUBARs the state of your test.  Things might work.  Maybe.  Then again, your test might go completely off the rails and start opening menus and typing things in whatever application you land in.  It could go catastrophically wrong, and while it’s often entertaining to sit back and watch your computer flip out, it usually doesn’t help you solve the original problem.

You can try stepping through an automated test using a debugger, but dust off your Console.WriteLine or printf debugging skills, because there’s a good chance you’ll need them.

Make Sure You Have A UI To Test:

Standard operating prodcedure for automated tests in a Continuous Integration environment is to have some automated process kick off your tests in response to a check-in or a build.  Trouble is, these automated processes typically live as a service or a scheduled task on a machine hidden in a closet that no one ever logs in to.  If no one is logged into a machine, then there’s a good chance that the application you’re trying to test won’t be running in an interactive window station, and if it’s not in an interactive window station, then your application probably won’t have things like, oh, windows.  It’s pretty hard to test a UI when the UI doesn’t exist.  Make sure that you’re running your UI tests somewhere that they they’ll have an interactive window station.  If you can leave a machine unlocked and open all the time, then that’s the easiest thing to do.  Unfortunately, things like “Corporate Computing Security Policies” tend to get in the way of you getting done what you need to do.   If you can’t leave a machine unlocked, then another possible solution is to use a virtual machine.  It’s not as scary to set up a simple VM as it might sound initially4, and it’s possible to have a VM running and unlocked and with a nice shiny interactive window station, even on a physical box that no one is logged in to.

Now, some of you might be thinking of using Remote Desktop to solve your problems, but good luck with that.  I’ve found that any place big enough to have a computing security policy that prohibits unlocked machines also tends to have a computing security policy that will log you out of inactive remote sessions.  Even without a policy, remote desktop sessions tend to log themselves out when they get bored.  And, to top it all off, even if you don’t get logged out, I’ve had problems with UI things over Remote Desktop, so use at your own risk.  You might have better luck than I did…

Beware of Outside Influences:

With direct API or service testing, you’re usually insulated from whatever’s happening on the machine.

“Windows has just updated your computer and it will restart automatically in 3, 2, 1…”

Unfortunately, that’s not the case with UI automation.

“There is an update available for Flash.  Download NOW!”

You’re much more at the mercy of unexpected windows and popups and dialogs.

“This application has encountered an error and will be shut down.”

There’s not much you can do about it.

“You need administrative rights to perform this action.  Allow?”

You can always try to eliminate or tune down the things that you can predict, but there will always be the unexpected willing to come along and bite you.

“You need administrative rights to allow this action to obtain administrative rights to perform this action.  Allow?  Are you REALLY sure this time?”

Basically, your only option is to be defensive.  You can’t always recover from some random dialog or other interference5, but you can make sure that your tests don’t hang forever and at least report that something went wrong.  If you’re looking for a window or a control, don’t look forever.  If it’s not there within a minute, it ain’t coming, so kill the test and move on.  And whenever possible, have your tests take screenshots of unexpected failures.  You’d be amazed how much frustration you’ll avoid if you have a screenshot that clearly shows what went wrong.

Take Screenshots Whenever Possible If Something Goes Wrong:

Yeah, I know I just said that above, but it needed its own headline.

Sometimes It’s Just Plain Flaky

Even when you’ve tailored the environment to be exactly what the test needs, even when you’ve taken care of all the stupid timing issues and dialog interference, even when it should work, sometimes, it just won’t work.  UI testing should always be treated as your sworn enemy because it hates you.  And there’s nothing you can do about it.  A good rule to live by is that if a UI test fails once, run it again, if it fails twice, run it once more, and if it fails a third time in a row, it’s an actual bug in the software.  It is a waste of time to attempt to get UI automation running flawlessly 100% of the time.  Shoot for 90% and call it a day.  You’ll find more bugs in the software if you write 30 slightly imperfect tests than if you spend all that time writing 5 perfect tests.

And When All Else Fails…

Thread.Sleep(5000);

  1. And really, if you’re an SDET, you already have been thinking of a solution of some form along these lines.  If not, then give the D in your title back and get the hell out of my pay grade because you have no business calling yourself a Software Development Engineer, in Test or otherwise. []
  2. I actually follow a slightly more complicated model where I have wrapper classes for the controls, too.  For this SearchPage example, I’d actually have something like a “UITextBox” class or interface with a Text property, and a “UIButton” class with a “Click” method and a “Text” property, etc.  This lets me expand the functionality of the controls without having to change the container class.  (For instance, if I need a “Focus()” method on the button, I just add it on my “UIButton” class and it’s accessible on every button I have.)  Additionally, it allows for subclassing/inheritance, so if I have a stupid button that requires keystrokes to press, I can have “StupidButton” derive from UIButton, then make the class return a StupidButton instance, and the test cases are none the wiser. []
  3. Helpful tip:  If you want to send a space, call SendKeys(” “);.  Seems obvious now, but it’s amazing how your mind shuts out that possibility when you’re trying to do it. []
  4. MS gives away Virtual PC and Virtual Server for free, and chances are you have an OS install disc around somewhere, and that’s all you need. []
  5. Keep in mind that interference need not be from the system.  If you’re running on an open machine somewhere, they have a bad habit of being used to check Facebook in the middle of a test run, and that’s not good for your UI driving automation… []

December 23, 2009   7 Comments

Test-Driven Disaster

When first considering using Test-Driven Development, many people will consult their local tester.  This is, of course, the wrong thing to do, because their local tester doesn’t actually care about Test-Driven Development.  It’s not a testing methodology, it’s a development methodology.  Asking a tester about it because it has the word “Test” in it is just as wrong as asking a bus driver about it because it has the word “Drive” in it.  And if you’re assigning your tester the task of “Test-Driven Development”, you need to stop before you damage something, because you’re doing it wrong.

For those who don’t know, “Test-Driven Development” is an Agile methodology for designing classes and developing code, where you begin with writing an automated test, stubbing out a method as you go, then after you have the test, you implement the method until the test case passes.  What many people seem to miss is that testing is merely a side-effect, it’s not the central goal.  Instead, the goal is to develop code from the top down by looking at how you’re going to use it.  There’s virtually no difference between TDD and stubbing out a low level module while implementing a higher level module.  The approach is the same:  You focus on how you’re going to use the functions you’re writing by actually using them, rather than trying to list all the operations you might need without the context on how they’ll be called.  If you don’t understand that’s what you’re doing, you’re bound to foul it up and hurt something.  You can’t do TDD after you’ve already specced out the method signatures, so don’t even try.

Beyond the rampant misunderstanding regarding what TDD is, my biggest reservation about it is the fact that most people who want to try it don’t know how to write tests.  Bad tests are far worse than no tests.  By strictly adhering to the principles behind TDD, you’ll write five tests and think you have everything covered because you red-green-refactored your way to perfection.   In fact, since TDD and the Wide World of Agile will encourage you to use code coverage you’re guaranteed to have tested everything.  Trouble is, you’ve only covered five specific cases, not the five hundred cases that would be apparent if you actually looked at the problem and nowhere near the five thousand cases your users will throw your way.  Code coverage will only tell you that you’ve executed lines of code, not that they’re correctly executing according to your plan.  By exactly following the process of Test-Driven Development, you’re pretty much assured to write nice shiny code that doesn’t actually work, even though it passes all of your tests.  To successfully write effective unit tests, you have to go beyond the initial requirements (Which are incomplete) and the initial design (Which has changed) and look at the solution you actually implemented to see where the problems are.  In other words, your job isn’t over after “Refactor”, you still need to go back and enhance your initial suite of tests before you’re done.

Take, for example, the requirement that you write a function that takes any two numbers and returns their sums.  In the world of TDD, you start with a test:

[Test]
public void TestSumMethod()
{
    Assert.AreEqual(5, SumMethod(2, 3), "2 + 3 = 5 Check");
}

Okay, so, we have the test.  Now we need the function stub.

public int SumMethod(int a, int b)
{
    return 0;
}

Compile, run tests, and blammo!  Your test failed, as it should, because you haven’t implemented anything yet.  This was the “Red” step.  Now it’s time to go Green.  For that, you implement the function you stubbed out.

public int SumMethod(int a, int b)
{
    return a + b;
}

Run the test again and it’ll pass.  Run code coverage and -look at that- 100%.  Now you refactor, rerun the tests and code coverage, and everything’s green, so you’re done!

Except…  You’re not done.  The requirements said “Any two numbers”.  So, you need more tests.  Does it work with negatives?  Big numbers?  Small numbers?  Any tester worth paying will immediately break your function by trying to pass in 2147483647, 2147483647.  And that’s just the beginning.  What about real numbers?  Can it do π + e?  And let’s not even get into complex numbers.  During your refactoring, did you change the inputs to a type that includes NaNs and Infinities?

The point is that following Test-Driven Development left you thinking that you had written a function that was adequately tested, when in reality, it was woefully under-tested.  Obviously, this was a simplified example.  The consequences of doing this with some real, worthwhile code could be disastrous.

Now, I like developers writing unit tests (Well, I like it when they write good unit tests, as I’ve already written…).  I don’t care if they come first, last, or during.  Just don’t fool yourself into thinking that practicing TDD will mean that you’ll have all of your unit tests written as part of your red-green-refactor cycle.

All in all, I like what TDD tries to do.  Thinking about code before you write it and writing unit tests are generally good things to do.  What I don’t like is the shiny glowing path to failure that TDD sets out for those who are unprepared.  I have no doubt that certain Agile practitioners can do TDD and do TDD well.  Unfortunately, I don’t think those people live in The Real World.  If you run your own software consulting firm, then great, go off and do things how you want.  But for everyone else, you’re going to get fired if you try to do that sort of thing, because you’re going to do it wrong and waste a lot of time in the process.

November 4, 2009   No Comments