I'm trying to use grunt-exec to run a javascript test runner with a deployed link variable passed in.
I am trying to do so by setting an environment variable grunt.option('link') using exec:setLink. In my test_runner.js I grab the variable with process.env.TEST_LINK. Unfortunately, it appears that grunt-exec won't run bash commands such as export(?)
Really, I don't care how the variable gets to my test_runner.js so any other ideas would be welcome.
exec: {
// DOESN'T WORK: Sets env variable with link for selenium tests
setLink: {
cmd: function () {
return "export TEST_LINK=" + "'" + grunt.option('link') + "'";
}
},
// Integration tests, needs TEST_LINK
selenium: {
cmd: function () {
return "node test/runner/jasmine_runner.js";
}
}
With grunt-exec, environment variables for the child process can be specified in the env option:
exec: {
selenium: {
cmd: function () {
return "node test/runner/jasmine_runner.js";
},
options: {
env: {
'TEST_LINK': grunt.option('link')
}
}
}
}
One thing to bear in mind is that if only TEST_LINK is specified in the env option, that will be the only environment variable for the child process. If you want the current process's environment variables to be passed, too, you can do something like this:
exec: {
selenium: {
cmd: function () {
return "node test/runner/jasmine_runner.js";
},
options: {
env: Object.assign({}, process.env, { 'TEST_LINK': grunt.option('link') })
}
}
}
I ended up just using node process.env['TEST_LINK'] = grunt.option('link');
Then retrieved in my javascript with process.env['TEST_LINK'];
Related
I am trying to transfer an old node-express project over to be able to use es6. I have seen many posts about using gulp with es6. Most of them discuss using a syntax like this:
const gulp = require("gulp");
const babel = require("gulp-babel");
gulp.src('./index.js')
.pipe(
babel({
presets: [
["#babel/env", { modules: false }],
],
})
)
However my existing project's gulpfile does't use gulp.src at all. Instead, it uses gulp-develop-server. The gulpfile looks like this:
const gulp = require("gulp");
const devServer = require("gulp-develop-server");
const spawn = require("child_process").spawn;
const fs = require("fs");
const basedir = ".";
function serverRestart(done) {
// perform some cleanup code here
devServer.restart();
done();
}
function serverStart() {
devServer.listen({
path: basedir + "/index.js",
});
}
function serverWatch() {
serverStart();
gulp.watch(
[
basedir + "/paths/**/*",
// more directories to watch
],
serverRestart
);
}
function reload(done) {
serverWatch();
done();
}
function defaultTask() {
let p;
gulp.watch(["gulpfile.js"], killProcess);
spawnChild();
function killProcess(e) {
if (p && !p.killed) {
devServer.kill();
p.kill("SIGINT");
spawnChild();
}
}
function spawnChild() {
p = spawn("gulp", ["reload"], { stdio: "inherit" });
}
}
process.stdin.resume();
process.on("exit", handleExit.bind(null, { cleanup: true }));
process.on("SIGINT", handleExit.bind(null, { exit: true }));
process.on("uncaughtException", handleExit.bind(null, { exit: true }));
function handleExit(options, err) {
// perform some cleanup code here
if (options.cleanup) {
devServer.kill();
}
if (err) {
console.log(err.stack);
}
if (options.exit) {
process.exit();
}
}
gulp.task("serverRestart", serverRestart);
gulp.task("serverStart", serverStart);
gulp.task("serverWatch", serverWatch);
gulp.task("reload", reload);
gulp.task("default", defaultTask);
The existing flow is important because it executes needed code for setup and cleanup every time I hit save, which runs serverRestart. I've been trying a few different methods based on the other questions which recommended using gulp.src().pipe(), but I havne't had much luck integrating it with the existing pattern which uses gulp-develop-server. I am trying to not have to rewrite the whole gulpfile. Is there a simple way to integrate babel with my existing gulpfile such that I can use es6 in my source code?
There's an example with CoffeeScript in the gulp-develop-server documentation.
Using that as a model, try this:
function serverStart() {
devServer.listen({
path: "./dist/index.js",
});
}
function serverWatch() {
serverStart();
gulp.watch(
[
basedir + "/paths/**/*",
],
serverRestart
);
}
function serverRestart() {
gulp.src('./index.js')
.pipe(
babel({
presets: [
["#babel/env", { modules: false }],
],
})
)
.pipe( gulp.dest( './dist' ) )
.pipe( devServer() );
}
Other suggestions
That being said, your existing Gulp file doesn't actually really use Gulp. That is, everything is defined as a function and it doesn't leverage any of Gulp's useful features, like managing task dependencies. This is because (pre-es6), this was a very simple project. The Gulp tasks in that file are an over-elaborate way to watch files and run a server. The same could be done (with less code) using nodemon.
With the introduction of React and more complicated build processes, Gulp seems to have fallen out of favor with the community (and in my personal experience, Gulp was a time sinkhole anyhow).
If the main change you want to make is to use import, you can simply use a more recent Node version. You'll surely run into the error SyntaxError: Cannot use import statement outside a module. Simply rename the file to .mjs and it will work. This provides a way to incrementally migrate files to import syntax. Other features should automatically work (and are all backwards-compatible, anyhow). Once your project is mostly, or all, compliant, you can add "type": "module" to your package.json file, then rename all of your require-style js files to .cjs, and rename all of your .mjs files to .js, or leave them as .mjs. Read more about the rules of mixing CommonJS and Module imports in the Node.js blog post (note that some things may have changed since that article was written).
How do I read in a page from localhost into a headless Jasmine spec so test cases can work on the DOM elements?
My Gulp task is successfully running Jasmine specs for unit testing, and now I need to build integration tests to verify full web pages served from localhost. I'm using the gulp-jasmine-browser plugin to run PhantomJS.
Example:
gulpfile.js
var gulp = require('gulp');
var jasmineBrowser = require('gulp-jasmine-browser');
function specRunner() {
gulp.src(['node_modules/jquery/dist/jquery.js', 'src/js/*.js', 'spec/*.js'])
.pipe(jasmineBrowser.specRunner({ console: true }))
.pipe(jasmineBrowser.headless());
}
gulp.task('spec', specRunner);
spec/cart-spec.js
describe('Cart component', function() {
it('displays on the gateway page', function() {
var page = loadWebPage('http://localhost/'); //DOES NOT WORK
var cart = page.find('#cart');
expect(cart.length).toBe(1);
});
});
There is no loadWebPage() function. It's just to illustrate the functionality I believe is needed.
End-to-End testing frameworks like a Selenium, WebdriverIO, Nightwatch.js, Protractor and so on are more suitable in such case.
The gulp-jasmine-browser plugin still is about the Unit testing in the browser environment. It is not possible to navigate between pages.
I put together the following code that appears to work. Please feel free to check out my repo and confirm in your own environment.
package.json
{
"name": "40646680",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"test": "gulp jasmine"
},
"devDependencies": {
"gulp": "^3.9.1",
"gulp-jasmine-browser": "^1.7.1",
"jasmine": "^2.5.2",
"phantomjs": "^2.1.7"
}
}
gulpfile.js
(() => {
"use strict";
var gulp = require("gulp"),
jasmineBrowser = require("gulp-jasmine-browser");
gulp.task("jasmine", () => {
return gulp.src("test/*.js")
.pipe(jasmineBrowser.specRunner({
console: true
}))
.pipe(jasmineBrowser.headless());
});
})();
test/sampleJasmine.js
describe("A suite", function() {
it("contains spec with an expectation", function() {
expect(true).toBe(true);
});
it("contains failing spec with an expectation", function() {
expect(true).toBe(false);
});
});
Execution
Bob Chatman#CHATBAG42 F:\Development\StackOverflow\40646680
> npm test
> 40646680#1.0.0 test F:\Development\StackOverflow\40646680
> gulp jasmine
[21:56:44] Using gulpfile F:\Development\StackOverflow\40646680\gulpfile.js
[21:56:44] Starting 'jasmine'...
[21:56:44] Jasmine server listening on port 8000
.F
Failures:
1) A suite contains failing spec with an expectation
1.1) Expected true to be false.
2 specs, 1 failure
Finished in 0 seconds
[21:56:49] 'jasmine' errored after 4.26 s
[21:56:49] Error in plugin 'gulp-jasmine-browser'
Message:
1 failure
npm ERR! Test failed. See above for more details.
Dependencies
node 7.2
npm 3.9.3
jasmine 2.5.2
phantomjs 2.1.7
gulp 3.9.1
jsdom to the rescue!
It turns out it's pretty easy to load a web page into a headless Jasmine spec... but you need to swap out PhantomJS for jsdom.
Strategy:
Use Jasmine's beforeAll() to call a function that will run JSDOM.fromURL() to request the web page.
Once the web page has been loaded into the DOM, expose window and jQuery for use in your test cases.
Finally, call done() to indicate the tests are now ready to run.
Make sure to close the window after the tests have run.
spec.js
const url = 'http://dnajs.org/';
const { JSDOM } = require('jsdom');
let window, $;
function loadWebPage(done) {
function handleWebPage(dom) {
function waitForScripts() {
window = dom.window;
$ = dom.window.jQuery;
done();
}
dom.window.onload = waitForScripts;
}
const options = { resources: 'usable', runScripts: 'dangerously' };
JSDOM.fromURL(url, options).then(handleWebPage);
}
function closeWebPage() { window.close(); }
describe('The web page', () => {
beforeAll(loadWebPage);
afterAll(closeWebPage);
it('has the correct URL', () => {
expect(window.location.href).toBe(url);
});
it('has exactly one header, main, and footer', () => {
const actual = {
header: $('body >header').length,
main: $('body >main').length,
footer: $('body >footer').length
};
const expected = { header: 1, main: 1, footer: 1 };
expect(actual).toEqual(expected);
});
});
Test output
Note: Above screenshot is from a similar Mocha spec since Mocha has a nice default reporter.
Project
It's on GitHub if you want try it out yourself:
https://github.com/dnajs/load-web-page-jsdom-jasmine
EDITED: Updated for jsdom 11
I am running my intern test using the following code
node node_modules/intern/runner.js config=tests/intern
on my local machine. The application is using Dojo.
Basically I am trying to override the window.alert function as one of my test is failing because of unexpected alert.
window.alert = function(msg) {
//override alert function
//...
}
I tried putting this in my intern test and got the error. After some search I learned that window object is not available on node environment. Where can I override the alert?
The intern file looks like
define(['intern/lib/args'], function(args) {
var DEFAULT_PORT = "8080";
var urlInfo = {
PORT: args.port || DEFAULT_PORT,
BASE_URL : "http://localhost:".concat(args.port || DEFAULT_PORT, "/webtest"),
};
var config = {
proxyPort: 9000,
proxyUrl: 'http://localhost:9000',
capabilities: {
'selenium-version': '2.45.0',
},
...
...
};
return config;
});
Intern Test file example
define([
'intern!object',
'intern/chai!assert',
'intern/dojo/node!leadfoot/helpers/pollUntil',
'intern',
'intern/dojo/node!fs'
], function(registerSuite, assert, Pages, intern, fs) {
registerSuite ({
name: 'Tests',
setup: function() {
window.alert = function(msg){
console.log("Unexpected Alert: "+msg);
}
return this.remote.get(require.toUrl( intern.config.functionalInfo.BASE_URL)).maximizeWindow();
},
beforeEach: function() {
return
},
afterEach: function() {
return
},
'Test1' : function() {
this.timeout = 600000;
return this.remote
.setFindTimeout(5000)
....
},
}
window does not exist in node, you have to override its alert from code that runs on the browser (the code being tested), not on node itself. I would do it in the setup code for each test that uses it.
I have Grunt build automation added in my project.
I have created one custom task which sets a variable for other task and now I want to run the task using the value I set.
grunt.registerTask('dist-flow', function () {
if (!grunt.option('env')) {
grunt.option('env', 'prod');
console.log(grunt.option('env'));
}
grunt.registerTask('dist',['dev_prod_switch']);
grunt.task.run('distdev');
});
But whenever I run the dist-flow task it will set env to prod but dev_prod_switch always take default value which I set for dev_prod_switch.
So I want set the options from task and run specific task using that new value.
The problem:
Based on your question and comments, I'm assuming your Gruntfile.js looks something like this.
Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
dev_prod_switch: {
options: {
environment: grunt.option('env') || 'dev',
env_char: '#',
env_block_dev: 'env:dev',
env_block_prod: 'env:prod'
},
all: {
files: {
'appCommon/config.js': 'appCommon/config.js',
}
}
},
});
grunt.registerTask('dist-flow', function () {
if (!grunt.option('env') ) {
grunt.option('env', 'prod');
console.log(grunt.option('env'));
}
grunt.registerTask('dist',['dev_prod_switch']);
grunt.task.run('distdev');
});
};
Your issue is that you are trying to set the option inside a task, and read it back in the initConfig object. The trouble is, initConfig runs before your tasks, so environment has already been set to the default when your dist-flow task is run.
This line:
environment: grunt.option('env') || 'dev',
Runs before this line:
grunt.option('env', 'prod');
A Solution:
Inside your task, you can access your config option through grunt.config, so you could modify the value in the config object like so.
grunt.config.data.dev_prod_switch.options.environment = grunt.option('env');
Example Gruntfile.js:
module.exports = function(grunt) {
grunt.initConfig({
dev_prod_switch: {
options: {
environment: grunt.option('env') || 'dev',
env_char: '#',
env_block_dev: 'env:dev',
env_block_prod: 'env:prod'
},
all: {
files: {
'appCommon/config.js': 'appCommon/config.js',
}
}
},
});
grunt.registerTask('dist-flow', function () {
if (!grunt.option('env') ) {
grunt.option('env', 'prod');
console.log(grunt.option('env'));
grunt.config.data.dev_prod_switch.options.environment = grunt.option('env');
console.log(grunt.config.data.dev_prod_switch.options.environment);
}
grunt.registerTask('dist',['dev_prod_switch']);
grunt.task.run('distdev');
});
};
I'm trying to set the current Git SHA in my project's Grunt configuration, but when I try to access it from another task it isn't available, What am I missing?
grunt.registerTask('sha', function () {
var done = this.async();
grunt.util.spawn({
cmd: 'git',
args: ['rev-parse', '--short', 'HEAD']
}, function (err, res) {
if (err) {
grunt.fail.fatal(err);
} else {
grunt.config.set('git', {sha: res.stdout});
if (grunt.option('debug') || grunt.option('verbose')) {
console.log("[sha]:", res.stdout);
}
}
done();
});
});
After running the task, I expect the config to be available in another task configuration:
requirejs: {
dist: {
...
out: '<%= app.dist %>/scripts/module_name.<%= git.sha %>.js'
...
}
}
So... What's the problem?
The problem is that Require JS is writing to the file public/scripts/module_name..js, the SHA is not available in the configuration (when the filename should be public/scripts/module_name.d34dc0d3.js).
UPDATE:
The problem is that I'm running requirejs tasks with grunt-concurrent, so the Grunt configuration is not available for requirejs.
grunt.registerTask('build', [
...
'getsha',
'concurrent:dist',
...
]);
And the concurrent task, looks like:
concurrent: {
dist: [
...
'requirejs',
...
]
}
Since grunt-concurrent will spawn tasks in child processes, they do not have access to the context of the parent process. Which is why doing grunt.config.set() within the parent context is not available in the config of the child context.
Some of the solutions to make the change available in the child context are:
Write the data to the file system
Write the data to a temporary file with grunt.file.write('./tmp/gitsha', res.stdout) and then have the task being ran in a child process read the temporary file:
out: (function() {
var out = grunt.config('app.dist') + '/scripts/module_name.';
if (grunt.file.exists('./tmp/gitsha')) {
out += grunt.file.read('./tmp/gitsha');
} else {
out += 'unknown';
}
return out + '.js';
}())
Use a socket
This is a very convoluted solution but a solution nonetheless. See the net node docs: http://nodejs.org/api/net.html#net_net_createserver_options_connectionlistener on creating a server on the parent process then have the child process connect to the socket for the data.
Or check out https://github.com/shama/blackbox for a library that makes this method a bit simpler.
Fork the parent process instead of spawn/exec
Another method is to use fork: http://nodejs.org/api/child_process.html#child_process_child_process_fork_modulepath_args_options instead of grunt-concurrent. Fork lets you send messages to child processes with child.send('gitsha') and receive them in the child with process.on('message', function(gitsha) {})
This method also can get very convoluted.
Use a proxy task
Have your sha task set the config as you're currently doing:
grunt.registerTask('sha', function() {
grunt.config.set('git', { sha: '1234' });
});
Change your concurrent config to call a proxy task with the sha:
grunt.initConfig({
concurrent: {
dist: [
'proxy:requirejs:<%= git.sha %>'
]
}
});
Then create a proxy task that runs a task with setting the passed value first:
grunt.registerTask('proxy', function(task, gitsha) {
grunt.config.set('git', { sha: gitsha });
grunt.task.run(task);
});
The above can be simplified to set values specifically on requirejs but just shown here as a generic example that can be applied with any task.