Visual Studio, Ruby on Rails, and Old Dudes Who Know Smalltalk

Jan 4 ‘07

NOTE: You might have been redirected here. Don't worry! I've moved my active blog to 40. (with egg)

Normally I don't spam/link post, but this is too important not to shout out to the world: Ruby development, with debugging support, and soon with Rails support, in... wait for it... wait for it...Visual Studio 2005! Check out Steel here:

If you talk to any of my coworkers, they will tell you that I am an Eclipse zealot. I love Eclipse. I love and understand it's way of thinking, and I am the most productive with this IDE. Well, at least I used to be when I was doing full time Java development with Eclipse's Java IDE, which is actually a massive Eclipse plugin. Now I'm a full time Ruby on Rails developer using the painfully poor Ruby/Rails plugins for Eclipse: RDT and RadRails. RDT and RadRails are developed by Some Dudes who are busy either working at real jobs that don't include developing free IDEs for me, or studying for finals and being excited that they are old enough to drink beer. Point being that the IDE support for Ruby and/or Rails is stone age at this point, and 99% of the features I use for Ruby development are not RDT/RadRails features at all, but default Eclipse feature or features from other plugins, such as the HTML, JavaScript, and CSS support from the excellent J2EE Standard Tools project.

If Steel can bring to Ruby/Ruby on Rails development even a small portion of productivity gaining features that I sorely miss from Eclipse's Java IDE, then I will burn my Eclipse Fan Club membership card and send the ashes to Erich Gamma, then buy Visual Studio for my workplace with my own money.

Can you tell I'm excited about this? I'm switching back and forth between writing this post and reading the Sapphire In Steel home page. I've just read the About Us page and I'm getting more insight into why these guys "get it". Here are some snippits:

has spent the last twenty years...

has programmed in languages ranging from Delphi and Java to C# and Smalltalk...

since 1988...

Ah, there it is -- These are Old Dudes! I love Old Dudes! And I really love Old Dudes Who Know Smalltalk! I was nurtured, sculpted, and brainwashed by Old Dudes Who Know Smalltalk from my very first day as a professional programmer, and they universally "get it". Young whipper-snappers out there, take note: if you ever here some Old Dude say the words "in Smalltalk you could blah blah blah" or "In VisualWorks you could yada yada", spend as much time with this person as possible. You will learn more from them about software development than the Young Dude who only wears black and thinks that the bash shell is "too bloated".

And what does "get it" mean? Maybe I'll get into that some other time (it will be ugly, as this is one are where I am very opinionated), but the important thing is this: these guys don't come from the school of web scripting hackery in vi, they come from the land of building real enterprise applications, where real tool support is appreciated. And at this point in the Ruby IDE game, I'd place my bets on them to produce the first a truly usefully development tool.


1 Comments (from old blog):

At 9/30/2006 8:18 AM, Torv said…

Well, finally you get it ;)

www.squeak.org

Making Unit-Testable Environment-Specific Rails Routes

Jan 4 ‘07

NOTE: You might have been redirected here. Don't worry! I've moved my active blog to 40. (with egg)

We faced and common problem on our project recently: we wanted to deploy two public-facing versions of our Ruby on Rails application at the same time -- demo version and a production version. There was a catch, of course: the demo and production deployments needed to expose different functionality to users. We used routes.rb to solve this, but found unit testing the route changes difficult until we discovered the with_routing method in the Rails test framework.

The Details:

Let's say you have a whole lot of functionality that is not ready for prime-time, but you still want to expose a password-protected (demo) version of this application to our client. But, since you paid so much for that domain name, you’d love it if people could go to the production site and see the “Coming Soon!”, “About Us” and “Info” sections. As developers, we certainly don’t want to create two versions of the application, or sprinkle environment/configuration checking code all over the app in an effort to keep certain sections locked-down in “production” mode, while open in “demo” mode.

One solution in Ruby on Rails is to add mappings to routes.rb that check the ENV['RAILS_ENV'], then route traffic appropriately depending on the currently running RAILS_ENV. As good test driven developers, we’ll test-drive these changes.

Oh, and before you ding me for the DRY principle, hold your horses… we’ll address that towards the end.

Demo – Everything is exposed (but password-protected): Production: Only “Coming Soon!”, “About Us” and “Info” is allowed:

require File.dirname(__FILE__) + '/../test_helper'

class RoutesTest
  def test_demo_routes
    opts ="{:controller"> "movie", :action => "show", :id => "1"}
    assert_routing "movie/show/1", opts, "should have normal routing"
  end

  def test_production_routes
    ENV['RAILS_ENV'] = 'production'
    contact_route = {:controller => "about_us", :action => "contact"}
    assert_routing "about_us/contact", contact_route, "contact is allowed"

    sign_up_route = {:controller => "info", :action => "sign_up"}
    assert_routing "info/sign_up", sign_up_route, "sign up is allowed"

    path = "movie/show/1"
    coming_soon_route = {:anything => path.split("/"), :controller => 'temp', :action => 'coming_soon'}
    assert_routing(path, coming_soon_route, "everything else goes to coming_soon")
  end
end

test_demo_routes passes since it uses the default generated routes, but test_production_routes fails as expected: we haven’t written the code that treats production differently yet. Let’s edit routes.rb next.

routes.rb (just the goodies, leaving out the comments and block)

map.connect 'info/:action', :controller => 'info'
map.connect 'about_us/:action', :controller => 'about_us'

map.connect ':controller/:action/:id' unless ENV['RAILS_ENV'] == 'production'

map.connect '*anything', :controller => 'temp', :action => "coming_soon"

Now we run our tests again and guess what? test_production_routes still fails. Why? Because routes.rb is loaded once at system startup and is not reevaluated; in other words, the routes are built once and only once, based on the RAILS_ENV at startup time. And since our RAILS_ENV was ‘test’ when we launched our tests, ENV['RAILS_ENV'] == 'production' evaluated to false, and thus the default, wide-open route is in effect for test_production_routes. So, how do we test this stuff?

with_routing, and no, that’s not a typo: with_routing is a method in the Rails testing framework that allows you to override the routes build at system startup. At the time of this writing, the only mention of with_routing in the entire Googleverse is its own sparse documentation, so I’m not sure how many people use it. Here’s how it works:

  with_routing do |map|
    map.draw do 
      #add code here that would
   #normally go in routes.rb
      map.connect 'some_controller/:action/:id'
    end
    #assert some stuff while still in the with_routing block
    assert_routing(x,y, “this applies while in with_routing”)
  end

Let’s edit test_production_routes:

    def test_production_routes1
      ENV['RAILS_ENV'] = 'production'
      with_routing do |map|
          map.draw do 
            map.connect 'info/:action', :controller => 'info'
        map.connect 'about_us/:action', :controller => 'about_us'

        map.connect ':controller/:action/:id' unless ENV['RAILS_ENV'] == 'production'

        map.connect '*anything', :controller => 'temp', :action => "coming_soon"
      end

    contact_route = {:controller => "about_us", :action => "contact"}

    assert_routing "about_us/contact", contact_route, "contact is allowed"

    sign_up_route = {:controller => "info", :action => "sign_up"}
    assert_routing "info/sign_up", sign_up_route, "sign up is allowed"

    path = "movie/show/1" coming_soon_route = {:anything => path.split("/"), :controller => 'temp', :action => 'coming_soon'}
    assert_routing(path, coming_soon_route, "everything else goes to coming_soon")
  end 
end

It’s kind of ugly, but I wanted to add enough detail to show how it works. Wrapping all of the routing and asserts in with_routing allows us to change the RAILS_ENV within the test, and the routes will obey the change.

Now just copy and paste those routes within with_routing into routes.rb… and keep them both in synch every time you change them… bleh, I don’t think so. That violates DRY (Don’t Repeat Yourself) for no good reason. What we really need to do is be able to reference the routes inside routes.rb whenever we want to. Let’s wrap them in a Class and method for easy referencing.

(Note: I’m not the best ruby programmer, so there might be a way of doing this that is more ruby-friendly, but since I’m coming into this after years of Java my first instinct is to wrap stuff in methods.)

routes.rb:

class RoutesLoader
def self.build_routes(map)
  map.connect 'info/:action'
  map.connect 'about_us/:action'

  map.connect ':controller/:action/:id'
  unless ENV['RAILS_ENV'] == 'production'

  map.connect '*anything', :controller => 'temp', :action => "coming_soon"
end
end

ActionController::Routing::Routes.draw do |map|
    RoutesLoader.build_routes(map)
end

This allows us to call RoutesLoader.build_routes(map) inside test_production_routes:

def test_production_routes
  ENV['RAILS_ENV'] = 'production'
  with_routing do |map|
      map.draw do 
        RoutesLoader.build_routes(map)
      end
    contact_route = {:controller => "about_us", :action => "contact"}
    …..

I hope this at least gives a bit more documentation for with_routing, and perhaps one more example for using routes.rb. Comments are welcome!


1 Comments (from old blog):

At 12/15/2006 7:26 AM, Rich said…

Thanks for the info. I've got an issue in a related area, and I've been scouring the net and documentation for answers but to no avail. Please help me out if you can!

I'm making a wiki based site, and after the www.mysite.com domain I wanted to have a wiki name before the controller/action/etc... stuff.

So I made an entry in the routes.rb file like this...

 map.connect ':wiki/:controller/:action/:id'

This works a treat when using the app, but when I try to write a functional test for my log in page like this...

 options = {:wiki => "acme", :controller => "account", :action => "log_in"}
  assert_routing "acme/account/log_in", options

  post :log_in, {:wiki => "acme", :controller => "account", :username=>"me", :password=>"me"}

... the assert_routing works, but the post to login comes back with an Exception saying "Need controller and action!"

Any ideas? Cheers, Rich.

Leave No Trace vs. Schmutz vs. The Promise

Jan 4 ‘07

Right after I posted Unit Testing: Leave No Trace vs. Schmutz, our team went to the opposite extreme regarding test data: we decided to load all test data (that is, all fixtures) before each test method executed. This took advantage of Rails fixture "feature" of deleting all data from the target fixture's table prior to that fixture's data being loaded into the table. Using this technique, we include all fixtures, thus deleting all Schmutz in the database prior to each test run.

At first, I was against this due to my Leave No Trace Rules philosophy, but then I started thinking about what we, as developers are guaranteeing to the tests, what the "contract" is for them... what is The Promise. I realized that The Promise is not strictly that we will start them with a clean database, but that we will start them from a known starting point. Based on that, deleting-then-loading all data is not really so different from starting from a clean database, because all tests start from the same known state.

Unit Testing: Leave No Trace vs. Schmutz

Jan 4 ‘07

I discovered something interesting about Ruby on Rails fixtures. Fixtures, for those who don't know, are how Rails loads test data for unit and functional testing. YAML files are loaded with sample data, which is loaded into your database when each test executes. If you want to load "users" and "addresses" data, you indicate such by stating "fixtures :users, :addresses" in your tests. Pretty handy!

I would expect this data to be cleared from the database at test teardown time, leaving the DB in a pristine state for the next test but this is not the case. Instead, the target fixture's data is deleted, then reloaded at test setup time, while teardown does not clear this data, leaving schmutz -- left over test data.

When it comes to test data, I have always practiced a "leave no trace" philosophy, so my inclination is to disagree with how the Rails folks implemented this: teardown should not leave schmutz.

And before you say anything, transactional fixtures don't fix this. More on that later.

Let's say we're running a test suite consisting of UserTest and UploadTest, each with a bunch of test methods. UserTest loads "fixtures :users, :addresses" while UploadTest loads"fixtures :files". If UserTest executes first, it's last test method will reload users and address, potentially doing a bunch of stuff with those, and then leave it behind. There will be users and addresses polluting the database, potentially interfering with UploadTest, or not, or sometimes, or never or... who knows? Schmutz! Schmutz!

I have heard some arguments for what I will affectionately term the "schmutzy philosophy":

  • If you leave schmutz behind, you can look at the database and see the affects your test had upon the data.
  • If schmutz messes up future tests, it is actually exposing an untested area of the system.
  • In the real world, functionality does not exist in a vacuum, so it's better to test with schmutz around.

And my favorite ...

  • Clearing all the test data is hard. Eventually you load tons of it, and your tests create a bunch more, then they mangle it, and you can't keep track of it all, and so we give up.

None of these arguments are strong enough for me to embrace the schmutzy testing philosophy. Unit testing in isolation has more benefits than any random side effects schmutz might reveal. And as test suites grow and change, schmutz's side affects will be constantly changing, and often muddle the true intent of your unit tests -- I've seen it many times before. Are the features of "save user preferences" truly broken, or is there just a bunch of random junk in the database messing up the results? You won't be able to see the true bugs from the side effect problems.

Ok, now for transactional fixtures. From Clarkware:

Transactional fixtures use database transactions to isolate tests. Rather than deleting and re-inserting fixtures for each test method, transactional fixtures are loaded once at the beginning of the test case. The fixture data in the test database is restored to its original state after each test by doing a transaction rollback.

This still leaves schmutz! It just leaves clean schmutz, which is kind of like clean dirt -- not clean. It seems like transactional fixtures could have solved this problem: perform everything within the transaction: the data inserts, the test's data manipulations, everything, and perform a ROLLBACK in teardown, thus rolling back the inserts as well. Transactional fixtures focus on performance, not on clean tests.

What are your opinions? Does testing with a schmutzy database have merits?


3 Comments (from old blog):

At 1/10/2006 12:01 PM, Chris Nolan.ca said…

I have no problem with it. I don't do anything with the test db except run the tests. Each of my tests have the appropriate fixtures loaded so it doesn't matter to me what is left behind.


At 8/23/2006 5:47 AM, James Mead said…

I totally agree with you, Joseph. In fact this is one of the many reasons we are avoiding the use of fixtures altogether. You might find Mocha gives you another way to approach unit testing.


At 8/31/2006 8:20 AM, Casey Helbling said…

Also be aware of using fixtures, acts_as_enumerated, observers and an enumerated type in an association. You can run into problems when the enumeration fixture data isnt loaded yet but the observer loads the model with the association (trying to use the enum). This is terribly annoying and even loading all fixtures in your test case wont fix it. And to Chris's point, I too load individual fixtures in each test but it is sometimes dificult to know if you add a relationship that depends on a new fixture what other fixtures are required. The arguement could be made that the testing isnt being done at the right granularity but that is different story. Also be aware of problems with rails transactions in general and save points.

http://dev.rubyonrails.org/ticket/5457

Transactions in general in rails do NOT behave as you expect!

Ruby on Rails? Only when not in Jails

Jan 4 ‘07

Just a quick note on the Ruby on Rails irony: there's hardly any ruby to write. This is great for getting things done quickly, but not so great when people really want to learn ruby. A roving consultant friend of ours has dropped by several times to pair program with us, but he's getting a bit frustrated because there is so little ruby to dive into. "It's all JavaScript and CSS and HTML and all that crap!" he says. Right now we're doing mostly UI overhauls, so that's true! It's amazing how little application logic ruby we've written lately compared to the amount of unit and functional test ruby, and compared to the amount of JavaScript, there's no comparison.

Of course, this a good thing -- when the application is solid, you can spend lots of time implementing UIs that designers create.

While chatting about this over lunch someone noted that, since we are in a phase of not doing much RoR and instead writing lots of JavaScript, we're not on Rails... we're in Jails.

SwitchTower Installation: Problems and Solutions

Jan 4 ‘07

Today I paired with a coworker to install SwitchTower to handle our automated application deployment for our Ruby on Rails projects. All in all it seems to work pretty well, though it took all day to install and there appear to either be several confusing bugs. It’s also possible that we missed some documentation, since today is the first day either of us have installed SwitchTower.

Here is a general log of our installation process:

9am – We hit the link above and started reading. The recipe concept looked pretty strait forward so we dove right in. Since our newly minted Subversion server, a Linux box, didn’t have Rails installed, we decided to try using SwitchTower on our Windows development machine. This was our first mistake.

10am – Installation via Gems was a breeze. We followed the instructions and “SwitchTowerized” our project, which created 2 new files in our file system:

  • config/deploy.rb
  • lib/tasks/SwitchTower.rake

We edited deploy.rb, filling in all of our configuration info, and tried to deploy to our offsite demo box.

12pm – After a couple of hours debugging SwitchTower and Rake on Windows we broke for lunch. Rake could not find the tasks specified in config/deploy.rb, and running the default rake deploy did nothing. We fiddled with SwitchTower.rake a bit and attempted to explicitly reference deploy.rb, only get generate some unhappy errors:

rake aborted!
undefined method `set' for main:Object
./config/deploy.rb:13

“set” is used to set variable for SwitchTower, such as the :deploy_to directory for your application. It seemed bad that this was not working, and after reading some stuff about how SwitchTower only begrudgingly works on Windows, we decided to install Rails on the Linux server that would be running this anyway.

1pm – Luckily my pair knew a lot about compiling and installing apps on Linux using gcc and make, because we had all kinds of problems. From our Fedora Core 3 machine:

  • gcc was not installed: we installed it using yum install gcc
  • Rails install failed with the following
/usr/local/lib/ruby/site\_ruby/1.8/rubygems/custom\_require.rb:18:in require__': 
    No such file to load -- zlib (LoadError)

After some searching we found that we needed zlib-devel, and thus we installed it using yum install zlib-devel

  • We recompiled and installed ruby, rails, gems, and SwitchTower, only to be stopped by openssl
/usr/local/lib/ruby/site\_ruby/1.8/rubygems/custom\_require.rb:18:in require__':
    No such file to load -- openssl (LoadError)

We rolled the dice and did a yum install openssl-devel, since grabbing the –devel version worked for zlib. It worked.

  • And now, with the utmost confidence: Recompile… ruby… rails… gems … SwitchTower… ack! ack! md5… digest… ack! ack! ack! Try to install md5-devel… nothing! ack! ack!...
  • Oh well let’s try again just for fun: … Recompiled… ruby… rails… gems … SwitchTower… bingo! Sometimes you just have to reinstall. Linux zealots are hereby banned from making fun of Windows for this.

3pm – After all of that noise we were finally able to get SwitchTower to talk to our demo box and at least attempt to deploy our app. This is where found that when SwitchTower performs checkouts from version control, it does this from the server running SwitchTower. It seems obvious now, but we were confused at first. Having the SwitchTower server perform all of the source control work is much better than installing Subversion on a bunch of servers.

All was not working but seemed to be well on its way. Here are the final steps:

  • We implemented the restart task as suggested in the documentation to bounce our server
  • Since SwitchTower changed our directory structure a bit, namely adding a current directory in the middle of things, we updated lighttpd.conf as needed.

5:30pm – Everything is working. In addition to normal config changes, we had a few SwitchTower-specific problems, which are either bugs or misunderstandings on our part:

  • SwitchTower did not create the “shared” directory by default as stated in the documentation. More importantly, the shared/log directory was not created automatically, which is problematic since SwitchTower created symbolic links to this directory.

Setting the shared_dir variable explicitly in the deploy.rb file did not fix this. As a result the application would not start until we manually created the ourapp/shared/log directory.

  • According to the documentation, we have many after_ and before_ tasks. Thus we attempted to work around the non-creation of the shared_dir by implementing after_setup in config/deploy.rb.

No dice – it was never executed; maybe we needed to explicitly call it somehow, be we haven’t figured it out yet (please let me know if you know how). We ended up implementing after_update_code instead.

Note that we used the old >/dev/null 2>&1 || true trick to prevent the deployment from failing when the shared_dir already exists.

Not bad for a day’s work. I tried to include many googleable errors and solutions. I hope it helps.

Subversion: bdb veriation Evil?

Jan 4 ‘07

I'm in charge of migrating our version control system from CVS to [Subversion}(http://subversion.tigris.org/) and it's been quite an adventure so far. Subversion (SVN), for those who don't know, is being pushed by the open source community as the successor to CVS, and it has several compelling advantages over CVS:

  • Atomic commits: no half-committed changes.
  • Ancestor tracking: Rename or copy a file? SVN remember where it came from.
  • Cheap tags and branches: tags and branches don't really take up any disk space, so feel to go for it. Just follow some good practices.
  • Revisions apply to the entire tree: it's not "revision 10 of file Foo" it's "Foo in revision 10 of the repository".
  • Much more.

SVN has two installation options for actual storage of the repository data: file system (fsfs) or Berkeley DB (bdb). So far, it looks like bdb is.... PURE EVIL. Am I an expert? Nope, but here are the errors I've faced so far, before changing over to fsfs this evening:

svnadmin: bdb: PANIC: No such file or directory
svnadmin: bdb: PANIC: fatal region error detected; run recovery

Well, PANIC seems bad to me. I was able to run the following to recover from this:

svnadmin recover /path/to/repository

That seemed to fix that problem. Next up:

Berkeley DB error while opening 'uuids' table for filesystem
/path/to/repository
Cannot allocate memory: bdb: Lock table is out of available locker entries

Oh man, that doen't sound good. After a bit of googleage, I found that I could jack up the locker entries in /path/to/repository/db/DB_CONFIG, but that didn't work.

I ended up converting to fsfs by doing the following, and it's been smooth sailing so far:

$ svnadmin create --fs-type fsfs /path/to/new/repository
$ svnadmin dump /path/to/repository | svnadmin load /path/to/new/repository

I'll be looking at SwitchTower next to see if that will work for us.


1 Comments (from old blog):

At 12/19/2006 7:56 AM, thijs said…

Thanks that worked great! I installed Subversion on Debian with apt-get a week ago and it looks like it's a very old packag of SVN that still uses Berkeley DB as default storage system.

Using Selenium's waitForValue, waitForCondition for Ajax Tests

Jan 4 ‘07

NOTE: You might have been redirected here. Don't worry! I've moved my active blog to 40. (with egg)

We're trying to use Selenium to test our ajax-ruby-on-rails projects (a-rails? r-jax? rail-jax? We gotta get some marketing folks on this...) We've ended up extending Selenium via their extension framework to write our own custom asserts and such, and things are going pretty well, though we've had to spelunk into the Selenium code quite a bit. One of the biggest problem has been the Ajax -- since we don't know when our app is done doing it's thing (that would be the asynchronous part) the test cases would charge ahead, testing results that weren't there yet. Selenium has many "AndWait" functions that are handy for synchronous actions: clickAndWait, openAndWait, getMeAChanticoâ„¢WithAShotAndWait, etc., but they often do not work well for Ajax, since the asynchronous nature of the action does not lend itself (for us anyway) to have an action "complete" in the traditional submit-reply paradigm.

Luckily, Selenium has a feature that is horribly documented: waitForValue, which you can use in the test case directly, and waitForCondition, callable in a JavaScript-based extension. Using a combination of these, you can make your Ajax call and wait for any condition that should happen, such as the DOM changing or values updating. Why the Selenium folks don't have bright red "hey, use this for your Ajax testing!" flag on these two items is beyond me. We've only just had our first success using this and I'll write more details later.


8 Comments (from old blog):

At 11/30/2005 2:04 PM, Jason Huggins said…

That's it-- I'm firing my marketing department. Seriously though, thanks for your post and we'll try to do a better job at getting the word out about testing Ajax with Selenium.

Jason "the guy who wrote Selenium" Huggins


At 12/05/2005 11:48 PM, Joseph Moore said…

Hi Jason -- I hope I didn’t come across as harsh! We're having a lot of fun with Selenium and writing more tests every day, and staring to use it to test other projects. I'll admit that my Ajax knowledge is still green (as in young, not as in not-red :) ) so I was surprised when I found that waitForValue and waitForCondition were exactly what we were looking for.

Also, I have a history of making a fool of myself: a few years ago I was at the Eclipse BOF at JavaOne when I started talking to a guy running a demo. He was showing off a Swing GUI builder that looked very much like VisualAge for Java's GUI builder, and I mentioned this. "Oh yeah, what did you think of the VisualAge tool?" He asked, provoking a major rant from me about how I hated VAJ's Swing tools.

"Oh," he said. "I wrote it".

Find rock. Crawl under. Hide.


At 12/08/2005 3:17 PM, Dan Fabulich said…

Where exactly did you find the waitForCondition user extension?


At 12/08/2005 7:01 PM, Joseph Moore said…

waitForCondition is implemented in Selenium's JavaScript; it's actually how they implemented waitForValue.

Take a look at Selenium's API for waitForValue: selenium-api.js, line 508. For examples of how they used it, do a search for waitForCondition on in their source. For example, selenium-executionloop.js, line 81:

      this.lastCommandResult = result;
          if (result.processState == SELENIUM_PROCESS_WAIT) {
              this.waitForCondition = function() {
              return selenium.browserbot.isNewPageLoaded();
          };
      }

At 12/16/2005 11:38 AM, Dan Fabulich said…

Thanks. I've taken your suggestion and baked it into a packaged user extension, including a new feature allowing you to specify a timeout for how long you want to wait:

http://wiki.openqa.org/display/SEL/waitForCondition


At 12/18/2005 5:01 PM, Joseph Moore said…

Hi Dan --

Nice! We'll have to download it and give it a try

-- Joe


At 6/16/2006 9:38 AM, DrydenMaker said…

Indeed. The wait function worls well. Selenium is certainly at the top of my testing tools list.


At 10/25/2006 1:50 PM, Evan Farrar said…

wait_for_condition('Ajax.activeRequestCount == 0','5000')

A Non CPU Intensive JavaScript Wait Loop?

Jan 4 ‘07

NOTE: You might have been redirected here. Don't worry! I've moved my active blog to 40. (with egg)

I spent much of the week pairing on UI changes on 2 projects, and again the power of CSS is very impressive. It took us about half a day on Monday implementing the UI mockups created by a design firm in CSS, and it make a big difference in the look and feel for the app. I also was exposed to some IE vs. Firefox CSS issues, so that's good stuff to know.

We are now trying to convert Selenium to drive our UI acceptance tests. After investigating Selenium for a few days we realized that we needed a lot more control over the how we wrote our tests, and we also need to take advantage of the drag-and-drop support from the Prototype JavaScript libraries that ship with Ruby on Rails. Selenium definitely does not have the support we need, but it has solved what has turned out to be the Hardest Problem Ever: getting the browser to sit and wait for some content to load. Here's the issue:

  • We load our test in an iframe
  • The setup code in the test loads our target page in another iframe.
  • The test needs to load the target page and wait for it to be fully loaded before simulating clicking on links buttons, etc.

Good luck. Firefox doen't have document.readyState, which is an IE extension. So, we wrote a "wait" loop which just spins for a second or two while the content loads... except that JavaScript loops such as these cause the browser to consume 100% of the CPU, as described in this conversation, preventing the content from loading.

Another option is to use setTimeout('loadMyPage()', 2000). The problem here is that setTimeout() schedules the function to be fired XXX milliseconds in the future, and returns control back to the browser in the mean time. That didn’t do us much good, as we need to sit and wait for the content to load, not to schedule it to load in the future.

After digging into Selenium's code, it looks like they have a clever solution: they use setTimeout() to recursively call the same method again and again, stacking up the calls so that no other browser actions can happen until a state is detected. It's something like this, but don’t quote me since I haven't validated this code yet:

function waitForLoad() {
   if (isThePageLoaded()){
      goOnWithLife();
   } else {
      // THIS IS RECERSIVE!  
      // WE'LL KEEP SCHEDULING
// THE CHECK UNTIL isThePageLoaded()==true
      setTimeout(waitForLoad, 10);
   }
}

That's the idea -- keep scheduling the check until the state is reached. Right now we're don't have it working completely yet, but the idea makes sense and I think we're close.


6 Comments (From Old Blog):

At 5/02/2006 12:06 PM, DHTML Kitchen said… Hi there fellow selenium user!

Just in reading your post, I'm wondering: can't you just add event listener to the iframe?

iframe.contentWindow.addEventListener("DOMContentLoaded", 
    iframeLoaded, false);

I haven't tested it, but it should work...


At 6/28/2006 8:37 AM, SebiF said…

Does waitForLoad work in IE? It doesn't seem to work for me. Any ideas?


At 6/29/2006 8:01 AM, Joseph Moore said… Hi Sebif --

I'm not sure about IE, but there must be a solution because Selenium uses some veriation of this for it's waitForCondition loop. If you're using this for Selenium, take a look at the Using Selenium's waitForValue, waitForCondition for Ajax Tests thread.

I'll try to whip up an example and try it in both browsesrs.


At 10/09/2006 12:20 AM, Anonymous said…

Hi,

Just happened across your blog via Google.

I'd suggest that if you're waiting for something to occur, that you use a while() loop, rather than a recursive function call.

The reason is that if it's done recursively, every time your interval times out, you get stuff accumulating on your stack which could eventually chew up all your memory.

Instead, use this:

while( !isThePageLoaded() )
  {
    setTimeout('waitForLoad()', 100)
  }

At 11/03/2006 6:02 AM, Anonymous said…

Check out the jQuery javascript library. This has a cross browser 'document ready' function...


At 11/03/2006 8:09 AM, Joseph Moore said…

Thanks for the comments, folks. I'll check out jQuery and will (hopefully) post the latest incarnation of this loop that we use in out apps.

Top 10 Lists: The Only OrganizerThat Has Ever Worked

Jan 4 ‘07

I've updated the site's sidebar on the right-hand side of the page with a "Random Blue Top 10 Card" section. Click there and see one of the 74 (so far) "Top 10" cards that I've accumulated since I've been using blue 8x5 index cards to keep myself organized. I'll explain.

For as long as I can remember, I've loved things that organize stuff: tool boxes, personal organizes, PDAs, wallets, backpacks, even those clear plastic compartmentized boxes that fisherman use for tackle. I really like the idea of being organized, but, as it turns out, I really hate actually staying organized. Personal organizers are bulky to carry around and I get tired to opening them, finding the right page, refilling them, etc. PDAs are still clunky, with interfaces that still don't satisfy me. I doubt I'll ever faithfully use a PDA until I can safely stuff it in my back pocket without worry that it will break, or until they are integrated into a flip styled cell phone without losing their usability.

So one day a few years back I was talking to my mother about this problem, and she mentioned that she had become a "list person". She leaves lists for herself all over the place, although she now forgets to take her lists with her, or what the items on the list really mean, and other such age-related fun that I can't wait to experience.

I remembered this conversation one day while wandering around a small mom-and-pop stationary store in San Francisco, and on a whim I bought a pack of blue, lined, 8x5 inch index cards. Over the next few weeks I developed a pretty low-tech to-do list system: I would carry a few cards around, bound with a small binder clip. I'd draw little sections with lists of tasks I'd need to do, and cross them off when I finished them. The most important thing I would do is force myself to write down on the card everything that came up in meetings or conversations that I wanted to follow up upon. Traditionally this has been a bad trait of mine: I forget the little things that I need to do for people, especially when I get bombarded with tasks from every different direction. By writing down everything, even if I've already finished the task, I get the pleasure of crossing it off the list when it's done, which effectively crosses it off of my conscious, too.

Several times people noted that the cards looked like those used by David Letterman for his Top 10 List every night, so naturally I started labeling my cards as my own personal Top 10.

Creating a new Top 10 is a pleasurable ritual that I try to do every Monday. I get a fresh card, write the date on both of the top corners, and write "Top 10" in the middle in some funky text: sometimes it's boxy, sometimes rounded, sometimes normal, but I take my time because it's cathartic for me to dedicate myself, once again, to keeping track of my thoughts.

Powered by aintablog