Thursday, December 21, 2006

Why Is HTML Selenese So Simplistic?

Recently, some people have asked on the Selenium mailing list why HTML Selenese (the language of Selenium Core) doesn't include conditional "if" statements or "for" loops, or, more generally, why there isn't some way to reuse code (with functions or subroutines).  Adding them in would be pretty straightforward.  (Andrey Yegorov has written a "flow control" user extension that provides support for "if/goto" statements in HTML Selenese, but you really shouldn't use it.)  In this article, I'm going to explain why we don't just add support for conditionals and loops directly into Selenium Core (which is also why, in my opinion, humans shouldn't write tests that use the flow control extension).  In some cases, nothing is better than something, when the something is considered harmful.

First, I want to highlight that it is absolutely possible to write Selenium tests with "if" statements and "for" loops, just not in HTML Selenese.  Selenium Remote Control (S-RC) provides support for writing Selenium tests in any language, including Java, .NET, JavaScript, Perl, PHP, Python, and Ruby.  Officially, if you need to write a test that has an "if" statement in it, we recommend that you write your test in S-RC.

Goto Considered Harmful

The main reason why we don't want to add support for a "goto" statement in HTML Selenese is that the Go To Statement Is Considered Harmful.  In his classic 1968 article for the Association for Computing Machinery, Edsger W. Dijkstra successfully argued that use of "go to" statements (or "jump" instructions) makes one's code less intelligible, and that "the go to statement should be abolished from all 'higher level' programming languages (i.e. everything except, perhaps, plain machine code)".

The "goto" statement undermines the intelligibility of your code by eliminating the possibility of a clean representational structure.  Code based on "goto" statements quickly turns into "spaghetti", as the code jumps all over your program, seemingly without reason.  (If you've ever had to maintain a lot of code written in Basic, you know what I mean.)  That's why people invented higher level languages like Java, C#, JavaScript, Perl, PHP, Python, and Ruby.

Adding a "goto" statement would completely undermine the entire point of HTML Selenese: simplicity.  Straightforward non-branching tables are easy for non-programmers to read and understand.  Many Selenium users report that their customers are able to grok Selenium tests (and even fix bugs in them), which gets more people involved in the testing/requirements gathering process.  If we added "goto" to Selenese, and people actually used it, that kind of end-user/developer interaction would come grinding to a halt.

Nobody Needs to Write a Goto Statement

Ultimately, though, all of our programs are ultimately compiled down and assembled into plain machine code, which makes ample use of "jump" instructions in order to get its job done.  Under the hood, all of the fancy object-oriented features available in higher level languages are automatically translated into "goto" statements by compilers (who guarantee the correctness of the resulting assembly code).

[In fact, as I understand it, that's exactly how Yegorov uses his flow control extension: he never (or rarely) writes HTML Selenese by hand, but rather he generates his HTML Selenese from another higher-level programming language that makes no direct reference to "goto" statements.  In a very real sense, he "compiles" a higher-level language into HTML Selenese.  (Please correct me if I'm wrong about this; I'm basing this on an old post of his from March 2006.)]

That's certainly one way to do it, but I argue that it's totally unnecessary.  He could simply run his tests in a high-level language directly with Selenium RC.

Implementing Proper Flow Control

Could we implement "proper" flow control in HTML Selenese, instead of simple "goto" statements?  Think about what that means: if, for, while, do/until, try/catch, scoped variables, iterators, closures...  these are programming language features.  To implement proper flow control in Selenium Core, we'd have to implement (and maintain) an entire language parser and interpreter written in JavaScript.

For one thing, that's too difficult.  While JS interpreters have been getting more and more compatible as time goes on, it's still very difficult to write a complicated program that behaves the same way in all browsers, say nothing of writing a full language parser/interpreter in JS.

For another, this problem has already been solved for us!  Java, .NET, JavaScript, Perl, PHP, Python and Ruby all have excellent parsers, compilers and interpreters.  Why would we reinvent that wheel in JavaScript?

(In another article, I'll write up exactly how far people have gone in this direction... it's not pretty.)

Easier to Read/Translate Without Flow Control

Finally, there are two important advantages to not supporting flow control in HTML Selenese, even if we could do it "right".

1) By limiting the expressive power of the language, we make Selenese substantially easier to translate into other languages, as we do in Selenium IDE. You can use Selenium IDE to translate HTML Selenese directly into any language that we support in Selenium RC.  If we supported writing tests in a "full language", we wouldn't be able to provide translators into all of those other languages.  (Language translation is hard; the problem is substantially simplified when you don't have any sophisticated language constructs to translate.)

2) HTML Selenese is about simplicity; that's the whole reason the language exists.  Turning HTML Selenese into a full-blown scripting language, with all the advantages that would bring, would still undermine its simplicity (though not as badly as implementing "goto").

If you want a scripting language, use a scripting language!  We support that 100%! :-)

In conclusion: implementing flow control mechanisms in Selenium Core means supporting "goto" statements (which sucks) or supporting a full language parser/interpreter (which is brutally hard).  Since Selenium RC already supports the native flow control mechanisms available in Java, C#, JavaScript, Perl, PHP, Python and Ruby, and since the whole point of HTML Selenese is its simplicity, there's almost no payoff in implementing yet another parser/interpreter in JavaScript.

In another article I'll discuss a few of our failed attempts to support a full language parser/interpreter in JS.


Anonymous said...

Well at least implement setup and teardown so I can cleanup if the test fails.

Stuart said...

I have to agree with anonymous- Selenium HTML tests are so sensitive to the initial state of the web page (forms, check boxes, etc.) that if any previous tests have failed (in our case these are not even Selenium/web tests, but tests on the command line of our app) then the Selenium tests are guaranteed to fail. I need a way to test the waters first, set everything to the right value/state, and then run the batch of Selenium HTML tests. People don't want massive conditional power, just a little bit, and it would be nice to be able to keep all of the tests in Selenium HTML. But you're not going to read this, this is 2 years old now anyway... (sigh)

Anonymous said...

After spending several hours looking, I am still unable to find a means to determine simple conditionals.... That is, I’m looking for a way – for example to determine if a variable is set. We have something on the order of 300 different tests that will need to be run. Any given test may depend on a previous test being run (or at least the initialization of the variables needed to run the test). Currently, there is no way to determine if this is the case. I was hoping to find something like a [storeglobal | TRUE | TC-001_Run] --- With a test something like: [“assertvariablevalue” | TRUE | TC-001_Run] that would be able to allow me to determine that previous dependant test cases have been run. Unfortunately, I cannot find any means to accomplish this task in a straight forward manor. As the previous posting indicated, these notes were posted two years ago, and two years later there is no standardized solution to the problem. Back to Google….

jps said...

If your tests are that sensitive to initial conditions, begin each one with a deleteAllVisibleCookies instruction. That ought to reset all initial state in the browser. If it doesn't then there might be something wrong with your application's RESTfulness, or the way you've specified certain elements of navigation in your tests (if that changes over time). I'd quite like setUp and tearDown but I've not found it essential.

If your tests really need proper scripting, use a proper scripting language! HTML tests are a great way to get up and running with Selenium but this blogpost details a lot of reasons why their support simply isn't going to get any more complex, none of which reasons are addressed in the complaining comments.

Using the Selenium RC (Python) bindings to write tests which support full control flow, setup, teardown and the whole of the (Python) bundled libraries is not difficult. It's quicker to write a basic test than it is to write a comment on a blogpost. I can recommend it as an alternative.

Darren said...

To me this post seems misguided. The argument against goto was made in the context of "higher level languages" and makes sense for languages that have more advanced flow control mechanisms. Selenium is not one of those higher level languages. Like assembly, where the jmp instruction is key, Selenese can benefit from conditional goto as a flow control mechanism. The popularity of the flow control plugin shows that there are a whole lot of use cases that require it. My suggestion would be to start listening to the users.

Humble tester said...

Im sorry to jump in the forray..its 2012 and I stumbled here trying to get an idea behind the philosophy at hand. As a tester, with limited language chops, cant tell you how many times I've been grateful for flexibility and power on my testscripts. I agree with the anonymous posters, if your audience is pure hard core coders, fine got to S-RC, but given that some of us have a hard time, we stretch Selenium -IDE loops, branches, multi-column variable files etc. so please if you want more people to adopt your tool, make it flexible and universal. Those who wanna port the HTML into new languages can keep plain selenese, those who for whatver reason cannot or will not, let them eat cake. Dont dumb things down for me or make it ultra complex, there has to be a middle ground.