Fumbling toward automation: running JavaScript tests with Watir

A group at work is doing some web-app development, and they’re using a tool called WatiN for end-to-end tests. I’m aware of that but didn’t think much of it.

Last night I went to Brian Marick‘s talk at the odynug meeting, and in passing, he mentioned Watir, which it turns out is infinitely cooler than WatiN, because Watir (a) came first, (b) is in Ruby, and (c) can automate IE, FireFox, and (via a separate library) Chrome.

I’m working on a JavaScript-based video-game development kit, and I spend a lot of time Alt+Tabbing to a browser, Ctrl+Tabbing to the tab with the automated test runner, refreshing the page, waiting (especially in IE) for the tests to finish, and (in Chrome) deciding whether the failures are actual test failures or just Chrome failures. Then it’s Alt+Tab to the next browser in the round-robin to try it again.

It shouldn’t be this hard to run all the tests. And with Watir, it looks like it won’t be. I think I’ll be able to write a Ruby script that

  • finds the Chrome tab that’s already open to the test URL (or opens a new tab);
  • reloads the Screw.Unit test page;
  • waits for the tests to complete;
  • scrapes the HTML to decide whether the tests passed or failed; and
  • repeats with FireFox and then with IE.

It won’t be trivial, because the Watir documentation really has nothing to say about finding stuff in the DOM. They’re heavily oriented toward clicking links and filling in forms, so if you want to manipulate a button or a hyperlink or a text field, they’ve got you covered. A myriad of examples, cheat sheets, and FAQs will get you on your way using methods like text_field and button. But if you want to find an <h3> with a particular CSS class, I wish you a lot of luck. The documentation does not go there. You need a lot of Google searches, a lot of luck, and a lot of lucky guessing.

I did scrape together something useful, and I’m noting it here. My tests currently report their results in an <h3>, which happens to be the only h3 on the page. This snippet of Ruby code will display the contents of that h3 (assuming the variable ie already refers to a Watir::IE instance):

ie.document.getElementsByTagName("h3")["0"].innerHTML

Huh. I get it that getElementsByTagName would return an array, so I would understand — and expect — having to do [0] on it. But [0] gives me an error: “TypeError: can’t convert Fixnum into String”. ["0"] works fine, though. I do not understand why, but as long as it works, I’ll accept that for now.

That’s just a start, though, because that snippet only works for classic Watir (for IE). FireWatir (the FireFox version, which is part of the same install but apparently not a compatible Ruby API) fails with “NoMethodError: undefined method `getElementsByTagName'”, and ChromeWatir (separate install) fails with “NoMethodError: undefined method `document'”.

Ah well. I came up with the above snippet by stealing shamelessly from the code for Watir’s show_spans method. Maybe I can do the same for the FireWatir and ChromeWatir versions of show_spans (if they have it). We shall see.

Released: Tyler v0.01 alpha

Tyler version 0.01 is now available for download. Currently there’s just a source distribution, but it includes compiled EXEs. The ZIP download is no longer available, but you can check the code out of the Subversion repository: version 0.01 or latest code.

The feature set is pretty sparse at the moment, but what’s there, works. It’s the bare minimum that I decided would be worth releasing. Here’s a brief rundown:

  • Pluggable display libraries. You can select from “compatibility mode” (basic GDI via TCanvas), Asphyre eXtreme, and (un)DelphiX (but see note 1 below). Windowed mode only — no fullscreen support in this version.
  • Graphics. These are primitive — everything is a solid-color square. Walls are light gray, floors are dark gray, the hero is yellow, the wandering NPC is white, and that’s all you get. (Well, except for black.)
  • Movement. You can use the cursor keys to move the hero around the map. There’s nothing yet to make you stop when you run into a wall or the NPC, though.
  • Map editing. TylerEdit.exe lets you edit the map and save your changes. It’s primitive, but it works. (But see note 2 below.)

Currently you can only have one map. Later you’ll be able to have lots of them — a world map, a map for each town, a map for each dungeon level.

Some stuff is hard-coded in this version, like the initial position of the hero, and the position and movement of the NPC. This will be improved in the next few versions.

Note 1: The license agreement for (un)DelphiX does not allow me to redistribute its source code. So, the source distribution is (un)DelphiX-less. But it still compiles: if you download and compile the source distribution, (un)DelphiX support will be automatically disabled. To enable (un)DelphiX, see the notes in the vendor\undelphix directory.

Note 2: The map file format will change in future versions. Any maps you create in this version probably will not work in v0.02 and later.

What’s next

The next few releases will be working toward a major milestone: a finished game. Not a long game — it’ll probably take less than a minute to play from start to finish. And not a very exciting one. In fact, it’ll be pretty stupid. But it’ll be playable, and everything will be editable in the game editor. Stay tuned for more news of… Stupid Quest.

DUnitLite 0.4: bugfixes and Should.ReferTo

Version 0.4 of DUnitLite is available. It fixes a couple of fairly major bugs (noted below) and adds a minor new feature.

Download it here: DUnitLite 0.4

What is DUnitLite?

For those who don’t remember, DUnitLite is my set of add-ons to DUnit to make assertions more readable, in the spirit of NUnit’s Assert.That(...) assertions (originally from NUnitLite). For example:

Specify.That(Foo, Should.Equal(45));
Specify.That(Bar, Should.Not.Equal(45));
Specify.That(Baz, Should.Be.AtLeast(40));
Specify.That(Quux, Should.Be.OfType(TSpecialQuux));

What’s new in 0.4

  • BUG: When you used TInsulatedTest (or TSpecification), DUnit was unable to count the number of passing tests — it just reported 0 tests run. Fixed.
  • BUG: When you used TInsulatedTest (or TSpecification), the test-suite nodes were labeled “TInsulatedTest” instead of the actual class name. Fixed.
  • NEW: Added Should.ReferTo(...) (reference equality check). Currently only works for objects, not interfaces.

Score one for dogfood

I’m using DUnitLite to write the tests / specifications for Tyler, so I’m quickly running into its bugs and limitations. This is the first time I’ve seriously tried to use DUnitLite in a project (its use of records-with-methods would reliably crash the pre-SR1 Delphi 2006 compiler we had at work at the time, and I never got back to it), so this is the first time I’ve actually noticed these gaping holes.

Fortunately, I wrote DUnitLite, so it’s really easy for me to fix the bugs. Don’t be surprised if you see a few more releases in the near future.

Indecision and pluggability

There are several DirectX libraries for Delphi: (un)DelphiX, Asphyre, Pyrogine. I don’t know their relative strengths and weaknesses well enough to pick one.

So I decided not to decide. Instead, I wrote a pluggable display engine, so you can switch between different libraries at runtime.

I think it’s going to be an interesting strategy. Each time I want to add a new feature, I need to figure out how to implement it for every library. This has the obvious disadvantage of being more work. But on the other hand, I’ll learn all of the libraries really well — and that’s what sold me on the idea. I won’t just be playing with their demo apps, or making toy programs to poke at in isolation; I’ll be implementing real features, features I care about, in each of these libraries, and seeing how they stack up.

Down the road, I may drop some of these libraries, or add more. What’s really cool is that each time I look at the libraries and decide “yeah, I’ll still keep this one” and “I’ll quit supporting that one”, it’ll be an informed choice, from actually learning and using the library, and knowing its strengths and weaknesses for what I’m trying to do.

That said, I’m not very far down that road yet. The only drawing operation I have so far is “filled rectangle”. No fullscreen mode, and no Pyrogine support yet. But I do have smooth animation and frames-per-second display, a trivial demo, a couple of running apps, and a base to start from.

(As well as a few bugs…)

A “Hello world” (un)DelphiX application

There comes a time, in any development project, where you have to actually write some code. So let’s see about doing some of that.

This post is part of my “Tyler” series, which aims to make a tile-based game-development engine in Delphi.

Why should I read this article, instead of just opening one of the samples that comes with (un)DelphiX?

How is this article any different from looking at the “Hello, world” (un)DelphiX sample application? Well, three ways:

  1. This doesn’t use third-party components on the designer, so it’ll work in the free Turbo Delphi Explorer.
  2. This code uses frames, which may help reusability when it comes time to make a game IDE.
  3. And last, this code has an author who knows about a bug that needs to be fixed. It’s not an (un)DelphiX bug, though, so the (un)DelphiX samples don’t fix it.

Prerequisite #1: a RAR extractor

(un)DelphiX is available in two forms: as an installer, and as a RAR file. Component installers are a huge pain, and probably wouldn’t work with Turbo Delphi anyway. So I recommend downloading the RAR. It’s a pain too, but less so than an installer.

But most people don’t have any programs that can open RAR files. So you’ll probably need to install one.

I recommend 7-Zip. It’s free and open-source. It has shell integration (i.e., you get a nifty right-click menu in Explorer). It supports ZIP files, and it can read (but not create) RAR files. And did I mention that it’s free?

Prerequisite #2: (un)DelphiX

Okay, this one is no surprise: if we’re going to use the (un)DelphiX components, we need to download them first.

Find them at the (un)DelphiX page; scroll down to the “Source file” section. Decide whether you care about having sample code for the DelphiX components, and download either the “all in one” (includes sample code, much larger) or the “source only” (no sample code) archive. If you’re not sure, “source only” is fine.

These directions were written for (un)DelphiX version 1.07f.

Note: I’m not going to walk you through installing the design-time components. If you want to look at the sample code, you’ll need to install the packages yourself. (Actually, their sample code will compile just fine without the design packages, you just won’t be able to use the form designer.)

Design principles

Let me explain the ideas behind some of the decisions I made in the following code.

  • Use frames. You could create all the (un)DelphiX components directly on the form, but I prefer to put them on a frame. Partly that’s because I think the form is going to end up with enough to do, what with supporting fullscreen mode and all. Partly it’s because I think I’ll want to reuse the animation GUI later, when I make a map designer. I’ll let you decide whether that’s YAGNI or just good separation of responsibilities.

  • Don’t drop frames onto other design surfaces at design-time. Delphi’s visual form inheritance (and I include “dropping frames onto other design surfaces” into this category) is unusable. I have a theory that one day, the Delphi developers said, “Encapsulation? Pshaw. Who needs it?”, and wrote Visual Form Inheritance. I, on the other hand, like to separate concerns, so that my code won’t be quite as fragile as VFI-infested code tends to be. In the long run, I find it’s far better to instantiate frames in code, rather than using the visual form inheritance and then wondering why my properties aren’t updating.

  • Enjoy the goodness that is strict private. Encapsulation. Something Delphi should have had from the beginning (see my Theory of Visual Form Inheritance, above). I only use “loose private” and “loose protected” when I absolutely can’t get away without “friend” semantics, and for whatever odd reason can’t live with something being public either. All other times, I use the real thing. Delphi is the only programming language I know of that makes you add an extra keyword to do the Right Thing (TM).

</rant>

Now, let’s make some code happen.

Setting up a development directory and creating a project

  • Create a directory on your hard drive to hold all of the source files. For the sake of discussion, I’ll assume you named this folder Tyler.
  • Create two subdirectories under Tyler named:
    • Dcu
    • UnDelphiX
  • Copy everything from the (un)DelphiX archive’s Source directory into your Tyler\UnDelphiX directory. (If you don’t plan to install the design-time components, you can leave out the .dcr files and the package files.)
  • Open Delphi.
  • Create a new app (File > New > VCL Forms Application – Delphi for Win32).
  • Go into Project > Options > Directories/Conditionals, and make these changes:
    • Unit output directory = Dcu
    • Search path = UnDelphiX

Creating a frame and setting some properties

  • Set the following properties on the form:
    • BorderIcons = [biSystemMenu, biMinimize] (i.e., turn off biMaximize)
    • BorderStyle = bsSingle
    • Caption = Tyler
    • Name = frmMain
  • Add a new frame with File > New > Frame (or File > New > Other > Delphi Projects > Delphi Files > Frame).
  • Set the following property on the frame:
    • Name = fraViewer
  • Save the form as Tyler\uiMain.pas.
  • Save the frame as Tyler\frViewer.pas.
  • Save the project as Tyler\Tyler.dpr.

Setting up the animation-viewer frame

Switch to the frame file (frViewer) and make these changes:

  • Add DXClass, DXDraws to the interface uses clause.
  • Add the following to the class declaration:
    strict private
      FDXDraw: TDXDraw;
      FDXTimer: TDXTimer;
      procedure DXDrawFinalize(Sender: TObject);
      procedure DXDrawInitialize(Sender: TObject);
      procedure DXTimerTimer(Sender: TObject; LagCount: Integer);
    public
      constructor Create(AOwner: TComponent); override;
      procedure Initialize;
  • Implement the methods:
    constructor TfraViewer.Create(AOwner: TComponent);
    begin
      inherited;
      FDXDraw := TDXDraw.Create(Self);
      FDXDraw.Align := alClient;
      FDXDraw.AutoInitialize := False;
      FDXDraw.AutoSize := False;
      FDXDraw.OnFinalize := DXDrawFinalize;
      FDXDraw.OnInitialize := DXDrawInitialize;
    
      FDXTimer := TDXTimer.Create(Self);
      FDXTimer.Enabled := False;
      FDXTimer.Interval := 0;
      FDXTimer.OnTimer := DXTimerTimer;
    end;
    
    procedure TfraViewer.DXDrawFinalize(Sender: TObject);
    begin
      if FDXTimer <> nil then
        FDXTimer.Enabled := False;
    end;
    
    procedure TfraViewer.DXDrawInitialize(Sender: TObject);
    begin
      if FDXTimer <> nil then
        FDXTimer.Enabled := True;
    end;
    
    procedure TfraViewer.DXTimerTimer(Sender: TObject; LagCount: Integer);
    begin
      if not FDXDraw.CanDraw then
        Exit;
    
      FDXDraw.Surface.Fill(0);
      with FDXDraw.Surface.Canvas do
      begin
        Brush.Style := bsClear;
        Font.Color := clWhite;
        Font.Size := 30;
        TextOut(30, 30, 'FPS: ' + IntToStr(FDXTimer.FrameRate));
        Release;
      end;
    
      FDXDraw.Flip;
    end;
    
    procedure TfraViewer.Initialize;
    begin
      FDXDraw.Parent := Self;
      FDXDraw.Initialize;
      FDXDraw.SetSize(ClientWidth, ClientHeight);
    end;

Setting up the form

Switch to the form file (uiMain) and make these changes:

  • Add frViewer to the interface section’s uses clause.
  • Add the following to the class declaration:
    strict private
      FViewer: TfraViewer;
    public
      constructor Create(AOwner: TComponent); override;
  • Implement the constructor:
    constructor TfrmMain.Create(AOwner: TComponent);
    begin
      inherited;
      ClientWidth := 640;
      ClientHeight := 480;
      FViewer := TfraViewer.Create(Self);
      FViewer.Align := alClient;
      FViewer.Parent := Self;
      FViewer.Initialize;
    end;

Just add CPU cycles

Compile and run. Don’t be surprised if you see a lot of hints and warnings from the (un)DelphiX code… it was originally written for Delphi 3, and hasn’t had a control freak like me maintaining it.

If all the code is right, you should see a black window with big white text in the top left, saying “FPS:” and a number. That number is the Frames Per Second: how often the screen is getting updated.

No, it’s not that fancy, but we do have an animation loop going here. The screen is furiously updating as fast as it can. We’re filling in the background with a default color, and we’re drawing stuff onto the window. Given the complexity of DirectX, that’s not too shabby.

What we’ve worked around already

DelphiX was written with the assumption that it would be put on the form’s design surface. That assumption is baked into it pretty deeply, and it’s brittle if it’s used any other way (like creating it at runtime, or putting it on a frame). A lot of this code is here to work around that weakness. (I already did the troubleshooting and the working-around, so you don’t have to.)

AutoInitialize, in particular, simply doesn’t work if the TDXDraw isn’t on a design surface (the auto-initialization happens in Loaded, which is only called by the DFM-loading mechanism), so we set it to False and call Initialize manually.

AutoSize is a little weirder. This isn’t the usual VCL AutoSize you already know. Instead, it means “automatically set the DirectX drawing surface to be the same size as the TDXDraw control”. It would be pretty silly to do anything else, really. But (at least when you’re creating controls at runtime) it only seems to work when the TDXDraw is parented directly to the form. If you so much as put it on a panel, things break — all the DirectX drawing is suddenly on a small square in the middle of the control, instead of filling the entire TDXDraw area. So once again, we turn off the automatic stuff and set the size manually.

The last trick is parenting the TDXDraw to the frame only after the frame is parented to the form. That’s because the TDXDraw looks up its parent form, and caches the reference, from its SetParent method. That’s the wrong thing to do when you need to support frames. (Handle-creation time would be better. But I digress.) The upshot is that the parentage chain, all the way up to the form, needs to be stable before you ever set the TDXDraw‘s Parent property. Otherwise you’ll get “Form not found” exceptions.

If you come away from this with the feeling that it’s not easy to write high-quality Delphi components, you’re right.

Next time: working around Delphi

In later posts, I’ll talk more about this code and what it does. But I want to talk about working code. This code works okay much of the time, but there’s still a bug in here, and this time it’s not an (un)DelphiX limitation that needs to be worked around — it’s actually a bug in the VCL.

But this post is long enough already, so I’ll save that topic till next time, when I talk about DelphiX’s “pause on inactive” feature, and the TApplication bug that gives it grief.

Game programming in Delphi: a journey begins

I’ve always wanted to write a video game. So, never one to start small, I’m going to write a game development kit in Delphi.

I’ve been wanting to do this for years, ever since I downloaded Verge v1 and was sorely disappointed in the development experience. It’s entirely possible that there are better game-development engines out there today, but dang it, I still want to write my own. It poses a number of intriguing challenges — and hey, I’ve never been one to pass up a chance to overengineer something.

So prepare yourself for a revolutionary, Delphi-like, RAD, OO game development environment. If I ever finish writing it.

This post is the first in my “Tyler” series, which aims to make a tile-based game-development engine in Delphi.

DelphiX, (un)DelphiX, and beyond

The first step is figuring out how to do decent animation. Video games kinda need, er, video.

I started tinkering with this last weekend. I had a couple of false starts — one was overengineered, with background threads (worked, but didn’t get good enough performance to be worth the complexity); one was underpowered, with the OnPaint handler ending with a call to Invalidate (so-so performance, too slow at larger window sizes). So I settled on (un)DelphiX, a set of Delphi components for DirectX development.

(un)DelphiX has questionable legal status. The original DelphiX was written by Hiroyuki Hori, who ceased development on it sometime in 1998 or 1999. The original included source code, but was not licensed as open source, and did not allow redistribution of modified versions. Later, someone named Micrel picked it up, made some enhancements, made it run on the latest versions of Delphi, plugged in the Jedi header translations, and made it available as the “unofficial version of DelphiX”, aka (un)DelphiX. Early versions of (un)DelphiX were distributed as a patch on top of the unmodified DelphiX, and so were clearly legal. But the latest versions just have the updated, (un)DelphiX code — packaged along with Hori’s original copyright notice, and the requirement that Hori’s original archive be distributed unmodified. So obviously Hori never gave permission for the new version. But it doesn’t look like he’s complained, either.

I’ve decided that, since I’m not the one distributing it, I’m in kind of a light-gray area. But I’m kind of figuring to eventually work my way toward my own implementation, independent of DelphiX. We’ll see how it goes.

A note on design-time components

The latest version of Delphi that I personally own is Delphi 3 or 5, I don’t remember which. It’s in a box somewhere in the basement. And I won’t use it anymore. I’m too spoiled by the recent features that made Delphi a reasonably competitive programming language again — things like strict private and records with methods.

So I’ll be developing with the free Turbo Delphi Explorer for Win32. It’s not quite the latest and greatest, but it’s only one version behind the expensive editions, and it’s got the good features.

What it doesn’t have is support for third-party design-time components. So I’ll be creating the DelphiX components in code, not with drag-and-drop. This raises a few interesting issues, since they weren’t designed to be used that way, but they’re easily worked around.

It also doesn’t have a command-line compiler, so expect me to grumble about that from time to time when I wish I could have a rakefile.

Every project needs a good name

And a good logo.

My main goal is to be able to develop tile-based RPGs, along the lines of Final Fantasy 1 through 6. And since it’s all about tiles, I’m naming the project “Tyler”.

Actually, I started on another, similar (but .NET-based) “Tyler” project back in 2003, so I’m really just recycling the name. And I also get to recycle the spiffy logo I made:

Tune in next time…

I’ll try to keep my game-programming posts from getting too long. This choice was inspired by Scott Guthrie, who posts absolutely amazing content — but he does not post articles, he posts chapters. It’s impossible to read one of his posts in a single sitting. So I’ll err on the side of multiple, shorter posts, so you can read a few at a time (and then mark them as read in your feed reader, so you know where you left off).

So, until next time, when I build a simple “Hello, world” program with (un)DelphiX, and then work around the Delphi bug that makes it not work quite right.