Skip to content

Instantly share code, notes, and snippets.

@AlphaGit
Last active February 13, 2017 02:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AlphaGit/d4564d5103c1a2b452c99d4bd5940d21 to your computer and use it in GitHub Desktop.
Save AlphaGit/d4564d5103c1a2b452c99d4bd5940d21 to your computer and use it in GitHub Desktop.
Protractor and Travis, cross-platform scripts

(Note: this post was originally a question in StackOverflow, but has been removed as the question received no answers or activity in over a year, and was deemed an abandoned question.)

Protractor and Travis, cross-platform scripts

"...and other horror stories."

I've been trying for the past few days to get this to work with no luck. As such, I have a long story to tell but I'll break it down to parts so that it is easier to understand.

My goal:

I want to have a cross-platform simple script that I can execute with npm test to run protractor and clean up after itself.

Requirements:

  • It needs to start the application server
  • It needs to start up webdriver-manager
  • It needs to stop the webdriver-manager
  • It needs to stop the application server
  • It needs to return the error code from Protractor
  • It needs to work transparently in Windows and *nix systems
  • It needs to run on Travis CI

Nice to have:

  • I'm wanting to avoid grunt/gulp since this should be an easy task and I don't want to add unnecessary bulk to the application dependencies.

Assumptions:

  • node installed can be assumed
  • protractor installed can be assumed (nice to have: local project dependency instead of global dependency)

Approach 1: npm test

I created several scripts under package.json that performed the tasks I needed, and I just concatenate them together.

{
    // ...
    "scripts": {
        "test": "npm run testServer && npm run webdriver && npm run protractor",
        "testServer": "node server.js",
        "webdriver": "webdriver-manager start",
        "protractor": "protractor protractor-conf.js"
    }
}

I didn't bother to try to kill the processes because this was not going to work anyway. Using && is no use since it waits for the return code of the previous command to execute the next, but the testServer needs to run on the background.

When trying to send jobs to the background, this gets hairy, as Linux uses the & operator, while in Windows you need to start /b.


Approach 2: child_process.spawn

(This seems like the most feasible approach so far, but it didn't quite work)

I wrote a nodejs script that would run the processes in background and kill them as needed:

function checkErr(err) {
  if (err) console.log(err);
}

var serverProcess = spawn('node', ['server.js']);
serverProcess.on('error', checkErr);

var webDriverProcess = spawn('webdriver-manager', ['start']);
webDriverProcess.on('error', checkErr);

var protractorProcess = spawn('protractor', ['protractor-conf.js']);
protractorProcess.on('error', checkErr);

protractorProcess.on('close', function() {
  webDriverProcess.kill();
  serverProcess.kill();
  process.exit();
});

This did achieve the execution behavior that I wanted but it had a few gotchas:

  • the processes are not correctly killed on kill() on Windows. I've tried combinations of signals like SIGHUP, SIGTERM, SIGKILL and implementing handlers on the ones that I could (server.js), with no luck.
  • it does not correctly find the file to execute (I tried both exec and spawn), because of this raging bug from 2011 on node. In short: Windows command prompt is complicated.

Approach 3: superspawn and promises

I moved to superspawn, which is an override of child_process.spawn that manages a few differences in Windows. It does return promises, which makes it more semantically appropriate to handle.

var spawn = require('superspawn').spawn;
var Q = require('q');

function spawnAndOutput(command, args) {
  return spawn(command, args)
    .then(console.log, console.log, console.log);
}

spawnAndOutput('node', ['server.js']);

spawnAndOutput('java', ['-jar', 'selenium-server-standalone-2.45.0.jar']);

spawnAndOutput('protractor', ['protractor-conf.js']).then(function() {
  return spawnAndOutput('webdriver-manager', ['stop']);
}).then(function(webDriverStopOutput) {
  // one of these is going to fail, ignore it
  return Q.any([
    spawnAndOutput('killall', ['node']), // Unix
    spawnAndOutput('taskkill', ['/F', '/IM', 'node.exe']) // Windows
  ]);
}).done(function() {
  process.exit(0);
}, function(err) {
  console.log('Error:', err);
  process.exit(1);
});

This correctly starts and kills the processes, and returns the right error code, but has a few problems of its own:

  • It could be killing itself (and I have no idea how it would behave in such a case)
  • It does not kill correctly the java webdriver (as I found out later, there's no "stop" command)
  • It does not find the processes to start when running in Travis. If I were to enter paths instead of commands, I would need to switch to execFile and we're back to Windows against Linux. Nope.

Approach 4: bash / batch

I thought that if both Linux and Windows will give priority to their known type of executable files, then I could just have two different script files named the same (one .sh and one .bat) and rely on the OS picking the right one.

test_protractor.sh:

node server.js &
webdriver-manager start
protractor protractor-conf.js
exitCode=$?
webdriver-manager stop
kill %1
exit exitCode

test_protractor.bat:

start /b node server.js
start /b webdriver-manager start
sleep 3
protractor protractor-conf.js
set exitCode = %errorLevel%
wmic process where "CommandLike like '%node.exe server.js%'" delete
wmic process where "CommandLike like '%webdriver-manager%'" delete
exit /b %exitCode%

I could not get to try the Linux version (first I need to solve the location of the protractor and webdriver executables), but the Windows approach seems like a mess. I would be able to solve the "process suicide" problem by using wmic instead of taskkill, but these command spit out a wrong query syntax error.

At this point I'm wondering if I should just go with grunt and accept my fate, but it's node anyway, so if there are solutions to this, I should be able to code them into a simple script, right?

Any help would be appreciated. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment