16 May 2014

Guide to Javascript on Classic ASP

Disclaimer: As the title says, this is for Classic ASP with "Javascript".  If your project is using Visual Basic, you may be able to glean some information from this article, but it's not written with you in mind.

I've had some recent experience with a legacy system using Javascript (technically "JScript") on Classic ASP, and the thing I found most frustrating was the lack of coherent documentation available. Despite it's reputation, w3schools is still one of the best references available on the web.

I guess this shouldn't be a surprise.  It is a deprecated platform, and they don't call it "Classic" for nothing. But there are still legacy systems out there which need to be maintained. If the system is purely in maintenance mode, you can probably get along just by reading the existing code and holding your nose. But if you need to add features and make significant changes, it's worth knowing some of Classic ASP's secrets so you can take advantage of the modern Javascript ecosystem.

Note: This is meant to be a reference for anyone who's forced to work with Classic ASP. In no way am I condoning using it by choice.  But if you've got to use it - use it right.

Javascript in Classic ASP is ECMAScript 3


First thing to be aware of is that the code you're writing, at it's core, is ECMAScript 3.  Another way to think of it: If your code runs in IE8 (minus the DOM API, obviously) it'll run in Classic ASP.

I've seen some developers approach Classic ASP code like it's some ancient writing which only the old masters knew how to interpret.  It's not.  There are only 6 things which are not, strictly speaking, Javascript: Request, Response, Server, Application, Session, and ASPError.
Your biggest challenge is learning to live without features from ES5, or finding appropriate shims.

The global scope cannot be directly manipulated

This is the biggest WTF to get your head around if you're used to coding in browsers or NodeJS. Trying to work with the global scope object as "this" will cause errors, giving developers the false impression that modern libraries won't work with Classic ASP.

Say you've got a third party library (like UnderscoreJS) that declares itself like so:

(function () {
  this.myExport = {};
}).call(this);

If I try to run this in Classic ASP, it will throw an error.
You can easily work around it, though it is somewhat tedious:

var surrogate = {};
(function () {
  this.myExport = {};
}).call(surrogate);
var myExport = surrogate.myExport;

Use <script src="" runat="server"> to include code without tags


Most Classic ASP I know uses the #include directive provided by IIS to compose source files.  The directive works by essentially pasting the entire file content into the directive location, like so:

include.asp:
<script runat="server">
var myInclude = {};
</script>

main.asp:
<!-- #include file="include.asp" -->
<script runat="server">
var myProgram = {include: myInclude};
</script>

Results in:
<script runat="server">
var myInclude = {};
</script>
<script runat="server">
var myProgram = {include: myInclude};
</script>

The downside to this is that you can't use any Javascript code quality tools.  They'll start to parse your file, find the tags, and throw syntax errors. This prevents you from doing style checks, code coverage, and code metrics. It can also cause headaches for editors and IDEs.

Instead you can use <script src="" runat="server"> to include files into your tags, just like any ".js" file into HTML:

include.js
var myInclude = {};

main.asp:
<script src="include.js" runat="server"></script>
<script runat="server">
var myProgram = {include: myInclude};
</script>

Using this method, there's noth... very little to stop you using the same tools enjoyed by NodeJS developers. Though you obviously need to be configure IIS so that it doesn't expose your code files as static resources.
That would be bad...

Code in <% %> tags is parsed before <script runat="server"></script> tags

This was a bit of a head scratcher when I first discovered it.  But sure enough, code in <% %> tags executes before <script runat="server"></script> tags (stackoverflow).

<script runat="server">Response.Write("first");</script>
<%Response.Write("second");%>

Result: second, first

My recommendation is: Don't use <% %> tags.

It's too easy to create tag soup, and you're better off using <script src="" runat="server"> anyway for JS code tooling.

Core ASP objects don't produce Javascript primitives

var param = Request.QueryString('param');
param == "test"; // true
param === "test"; // false
String(param) === "test"; true

This means you tried to call "param.substring(1)", it would throw an error saying that "substring" was undefined.  So you need to make sure you wrap results from core ASP objects in String(), Number(), or Boolean() before you try to use them.

That about does it.
To all the poor bastards out there stuck working on Classic ASP: This is for you.

See more on Know Your Meme

E2E testing AngularJS with Protractor

In the beginning, there was JSTestDriver.
It was a dark time, with much wailing and gnashing of teeth.

Then came Testacular: The spectacular test runner.
For a time, once everyone stopped sniggering like teenagers, it was good.
Unit tests ran quick as lightning on any browser that could call a web page.

Finally, to please the squeamish who were too embarrassed to speak of Testacular to colleagues and managers, the creator moved heaven and earth to rename it Karma.
And it was, and still is, good.

But there was still unrest.
While unit tests were as quick as the wind, E2E (end-to-end) tests were constrained from within the Javascript VM.
"Free me from this reverse proxy! Treat me as though I were a real user!"
And thus Protractor was born.

*ahem*
Protractor is the official E2E testing framework for AngularJS applications, working as a wrapper around Web Driver (ie. Selenium 2.0) which is a well established and widely used platform for writing functional test for web applications.  What makes it different from Karma is that Karma acts as a reverse proxy in front of your live AngularJS code, while Web Driver accesses the browser directly. So your tests become more authentic in regards to the user's experience.

One cool thing about Web Driver, which I didn't realise till recently, is that it's API is currently being drafted up as a W3 standard.  We're also seeing a number of services appear for running your selenium tests using their VMs, which is useful for doing CI and performance testing without taking on the operational overhead yourself.

Let's go!

The App

I've created a simple application to write tests for.  So first we'll clone the application from github, install the local NodeJS modules, and then install the required bower components:

git clone https://github.com/rolaveric/protractorDemo
cd protractorDemo
npm install
node node_modules/bower/bin/bower install

Now you should have a copy of the application with node modules 'bower' and 'protractor' installed, and AngularJS installed as a bower component.

The application is dead simple.  It has a button with the label "Click to reverse".  When you click it, it (you guessed it) reverses the label. So our tests should look something like this:
  • Load App
  • Click button
  • Assert that label is now reversed
  • Click button again
  • Assert that label is now back to normal

Installing Selenium

Protractor comes with a utility program for installing and managing a selenium server locally: webdriver-manager
Calling it with "update" will download a copy of the selenium standalone server to run.

node node_modules/protractor/bin/webdriver-manager update

Setting up for tests

First thing we need is a configuration file for protractor.  It tells protractor everything it needs to know to run your tests:  Where to find or how to start Selenium, where to find the web application, and where to find the tests.

Since we're using webdriver-manager to run selenium server, we'll tell it the default address to find it: http://localhost:4444/wd/hub
Optionally you could give it the location of the selenium server JAR file to start itself, or a set of credentials to use SauceLabs.

The tests we'll place in "test/e2e", and npm start spins up a local web server at "http://localhost:8000/".  So the basic configuration file stored in "config/protractor.conf.js" looks like this:

exports.config = {
seleniumAddress: 'http://localhost:4444/wd/hub',
specs: ['../test/e2e/*.js'],
baseUrl: 'http://localhost:8000/'
};

There's actually a lot more you can do with the configuration file, but this is all we need to get going. Check out the reference config in protractor's github for more options.  You can do things like pass parameters to selenium, your testing framework, and even to your tests (eg. login details).

Writing tests

In "test/e2e/click.js" is a simple test for the "Click to reverse" behaviour:

The process behind writing E2E tests is pretty simple: Perform an action, get some data, then test that data.  Generally each action or query also involves finding a particular element on the page, either by CSS selector, ng-model name, or template binding.

First it opens the "index.html" file (which it finds relative to the baseUrl in the configuration file), finds the button by it's binding, clicks the button, then gets the button's text value and tests it.  Then we click the button again, get it's text value, and test that it's changed back to normal.

Running tests


Now for the payoff - the running of the tests.

First we'll start up the web and webdriver servers:

npm start
node node_modules/protractor/bin/webdriver-manager start

Then we tell protractor to run the tests according to our configuration file:

node node_modules/protractor/bin/protractor config/protractor.conf.js

If everything's gone well, you should soon be rewarded with the following result:

Finished in 2.061 seconds
2 tests, 2 assertions, 0 failures

And there you have it.  Webdriver tests for your AngularJS application with minimum pain.  If you already have angular-scenario based tests, converting them to Protractor should be a trivial "search & replace" exercise with the right regular expressions.