Infected by Travis Tidwell (travist)
http://github.com/travist | http://travistidwell.com | @softwaregnome | AllPlayers.com
Follow along @ http://travistidwell.com/drupal-zombie
But... Zombie.js doesn't jive with v0.10 of node.js.
https://github.com/creationix/nvm
curl https://raw.github.com/creationix/nvm/master/install.sh | sh
mac:~ travist$ nvm install 0.8
https://github.com/kennethreitz/osx-gcc-installer
mac:~ travist$ mkdir drupal-zombie
mac:drupal-zombie travist$ cd drupal-zombie
mac:drupal-zombie travist$ nano package.json
{
"name": "drupal-zombie",
"dependencies": {
"zombie": "< 2"
}
}
mac:drupal-zombie travist$ nvm use 0.8
mac:drupal-zombie travist$ npm install
Create a new app.js file.
mac:drupal-zombie travist$ nano app.js
var Browser = require("zombie");
var browser = new Browser();
browser.visit("http://drupal.org/search", function () {
browser.fill('#edit-keys', 'zombie', function() {
browser.pressButton('#edit-submit', function() {
console.log(browser.text('dt.title:eq(0)'));
});
});
});
Waits for one operation to end before starting the next.
function doSomething() {
console.log('something');
}
function doSomethingElse() {
console.log('something else');
}
doSomething();
doSomethingElse();
'something'
'something else'
It gets interesting when they depend on one another.
function getSomething() {
return 'something';
}
function saySomething(what) {
console.log(what);
}
var something = getSomething();
saySomething(something);
'something'
And more interesting when getSomething
depends on an asynchronous source.
function getSomething() {
http.get('http://google.com', function(response) {
return response; // ??????
});
// returns nothing... NULL
}
function saySomething(what) {
console.log(what);
}
var something = getSomething(); // something = NULL
saySomething(something);
NULL
function getSomething(callback) {
http.get('http://google.com', function(response) {
callback(response);
});
}
function saySomething(what) {
console.log(what);
}
getSomething(function(what) {
saySomething(what);
});
'something'
var Browser = require("zombie");
var browser = new Browser();
browser.visit("http://drupal.org/search", function () {
browser.fill('#edit-keys', 'zombie', function() {
browser.pressButton('#edit-submit', function() {
console.log(browser.text('dt.title:eq(0)'));
});
});
});
var Browser = require("zombie");
var browser = new Browser();
browser.visit("http://drupal.org/search", function () {
browser.fill('#edit-keys', 'zombie', function() {
browser.pressButton('#edit-submit', function() {
browser.visit("http://drupal.org/project/mediafront", function() {
browser.clickLink('Read documentation', function() {
browser.doSomething('something', function() {
browser.doSomethingElse('somethingelse', function() {
browser.keepDoingStuff('stuff', function() {
browser.doMoreStuff('more stuff', function() {
browser.evenMoreStuff('oh man...', function() {
...
...
});
});
});
});
});
});
});
});
});
});
somtimes called 'callback hell'.
// Fill in the form and submit.
browser.
fill("Your Name", "Arm Biter").
fill("Profession", "Living dead").
select("Born", "1968").
uncheck("Send me the newsletter").
pressButton("Sign me up", function() {
// Make sure we got redirected to thank you page.
assert.equal(browser.location.pathname, "/thankyou");
});
But when you should and should NOT use the 'promise' varies...
And it breaks down when promises have dependencies on other promises.
Try to iterate over a list of nodes (Views) and do something with them.
var _ = require('underscore');
browser.visit('http://drupal.org/forum', function() {
var nodes = browser.queryAll('tbody tr');
_.each(nodes, function(node) {
var link = browser.query('div.name a', node);
browser.fire("click", link, function() {
console.log(browser.text('h1'));
});
});
});
Community
Community
Community
Installing Drupal
Installing Drupal
Installing Drupal
Installing Drupal
...
...
Each clickLink
will interrupt the previous and only go to the last one...
https://github.com/caolan/async
"dependencies": {
"async": ">=0",
...
mac:drupal-zombie travist$ npm install
async.series
usage
var async = require('async');
async.series([
function(done){
// do some stuff ...
done();
},
function(done){
// do some more stuff ...
done();
}
], function(err, results){
// results is now equal to ['one', 'two']
});
async.series
example
browser.visit("http://drupal.org/search", function () {
browser.fill('#edit-keys', 'zombie', function() {
browser.pressButton('#edit-submit', function() {
console.log(browser.text('dt.title:eq(0)'));
});
});
});
async.series([
function(done) {
browser.visit('http://drupal.org/search', done);
},
function(done) {
browser.fill('#edit-keys', 'zombie', done);
},
function(done) {
browser.pressButton('#edit-submit', done);
}
], function() {
console.log(browser.text('dt.title:eq(0)'));
});
var go = function() {
var args = _.values(arguments);
var method = args.shift();
return function(done) {
args.push(done);
browser[method].apply(browser, args);
};
};
async.series([
go('visit', 'http://drupal.org/search'),
go('fill', '#edit-keys', 'zombie'),
go('pressButton', '#edit-submit')
], function() {
console.log(browser.text('dt.title:eq(0)'));
});
browser.visit('http://drupal.org/forum', function() {
var nodes = browser.queryAll('tbody tr');
_.each(nodes, function(node) {
var link = browser.query('div.name a', node);
browser.fire("click", link, function() {
console.log(browser.text('h1'));
});
});
});
async.series([
go('visit', 'http://drupal.org/forum'),
function(done) {
var nodes = browser.queryAll('tbody tr');
async.eachSeries(nodes, function(node, nodeDone) {
var link = browser.query('div.name a', node);
browser.fire("click", link, function() {
console.log(browser.text('h1'));
nodeDone();
});
}, done);
}
]);
async.series([
go('visit', '/user'),
go('fill', '#edit-name', 'admin'),
go('fill', '#edit-pass', '123password'),
go('pressButton', '#edit-submit')
], function() {
console.log('Logged in as admin.');
});
var login = function(user, pass, done) {
async.series([
go('visit', '/user'),
go('fill', '#edit-name', user),
go('fill', '#edit-pass', pass),
go('pressButton', '#edit-submit')
], done);
};
async.series([
go('login', 'admin', '123password')
], function() {
console.log('Logged in as admin.');
});
Let's change this code to use a configuration file.
Using a library called nconf.
"dependencies": {
"nconf": ">=0",
...
mac:drupal-zombie travist$ npm install
{
"host": "http://drupal.local",
"user": "admin",
"pass": "123password"
}
{
"host": "http://drupal.local",
"user": "admin",
"pass": "123password"
}
var Browser = require('zombie');
var config = require('nconf');
config.argv().env().file({file:'config.json'});
...
...
async.series([
go('login', config.get('user'), config.get('pass'))
], function() {
console.log('Logged in as admin.');
});
Change the password so it prompts you for it.
Using a library called prompt.
"dependencies": {
"prompt": ">=0",
...
mac:drupal-zombie travist$ npm install
Change the password so it prompts you for it.
Using a library called prompt.
var prompt = require('prompt');
prompt.start();
...
...
async.series([
function(done) {
prompt.get({name: 'pass', hidden: true}, function(pass) {
config.set('pass', pass);
done();
});
},
go('login', config.get('user'), config.get('pass'))
], function() {
console.log('Logged in as admin.');
});
A node.js package to automate and test Drupal using Zombie.js.
https://github.com/travist/drupal.go.js
mac:drupal-zombie travist$ npm install drupalgo
{
"name": "drupal-automate",
"dependencies": {
"drupalgo": ">=0",
"async": ">=0"
}
}
mac:drupal-zombie travist$ nvm use 0.8
mac:drupal-zombie travist$ npm install
{
"host": "http://drupal.org",
"user": "travist"
}
var drupal = require('drupalgo');
var async = require('async');
// Load the configuration file.
drupal.load('config.json');
async.series([
drupal.go('login')
], function() {
console.log('Done');
});
drupal.go
- Wrapper function to return async promises.drupal.login(user, [pass], done)
- Login to Drupal.drupal.get(param, [value], done)
- Get or prompt a configuration.drupal.set(param, value, done)
- Sets a configuration variable.drupal.createContent(node, done)
- Create a piece of content.drupal.createMultipleContent(nodes, done)
- Create multiple content.drupal.eachViewItem(context, item, callback, done)
- Iterates over every item within a view (including pagination).Note: Every API can be passed to drupal.go
var drupal = require('drupalgo');
var async = require('async');
drupal.load('example1.json');
async.series([
drupal.go('login'),
drupal.go('get', 'title'),
drupal.go('createContent', function() {
return {
type: 'article',
title: drupal.config.get('title')
};
})
]);
{
"host": "http://drupal.local",
"user": "admin",
"title": "My node"
}
var drupal = require('drupalgo');
var async = require('async');
drupal.load('example2.json');
async.series([
drupal.go('login'),
function(done) {
async.whilst(
function() { return drupal.config.get('title') !== ''; },
function(nodeDone) {
async.series([
drupal.go('set', 'title', ''),
drupal.go('get', 'title'),
drupal.go('createContent', function() {
return {
type: 'article',
title: drupal.config.get('title')
};
})
], nodeDone);
},
done
);
}
]);
{
"host": "http://drupal.local",
"user": "admin",
"nodes": [
{
"type": "article",
"title": "Hello There",
"body": "This is very cool!"
},
{
"type": "article",
"title": "This is another node",
"body": "Nice!"
}
]
}
var drupal = require('drupalgo');
var async = require('async');
drupal.load('example3.json');
async.series([
drupal.go('login'),
drupal.go('createMultipleContent', drupal.config.get('nodes'))
]);
{
"host": "http://drupal.local",
"user": "admin",
"nodes": [
{
"type": "article",
"title": "Hello There",
"body": "Body still works...",
"fields": {
"select[name='field_shirt_size[und]']": {
"action": "select",
"value": "m"
},
"input[name='field_favorite_color[und]'][value='orange']": {
"action": "choose"
},
"input[name='field_interests[und][math]']": {
"action": "check"
},
"input[name='field_interests[und][english]']": {
"action": "check"
},
"input[name='field_approved[und]']": {
"action": "check"
}
}
},
{
"type": "article",
"title": "This is another node",
"body": "Nice!",
"fields": {
...
...
}
}
]
}
var drupal = require('drupalgo');
var async = require('async');
var browser = drupal.load('example5.json');
async.series([
drupal.go('login'),
drupal.go('visit', '/project/issues/mediafront'),
function(done) {
drupal.eachViewItem('div.view-id-project_issue_project', 'td.views-field-title', function(node, done) {
var version = browser.text('td.views-field-version', browser.xpath('./..', node).value[0]);
console.log(browser.text('a', node) + ' : ' + version);
done();
}, done);
}
]);
var drupal = require('drupalgo');
drupal.load('config.json');
drupal.editNode = function(nid, title, done) {
async.series([
this.do('visit', 'node/' + nid + '/edit'),
this.do('fill', '#edit-title', title),
this.do('pressButton', '#edit-submit')
], done);
};
async.series([
drupal.go('login'),
drupal.go('createMultipleContent', drupal.config.get('nodes')),
drupal.go('editNode', 12345, 'Testing!')
]);
var assert = require('assert');
var drupal = require('drupalgo');
drupal.load('config.json');
drupal.editNode = function(nid, title, done) {
async.series([
this.do('visit', 'node/' + nid + '/edit'),
this.do('fill', '#edit-title', title),
this.do('pressButton', '#edit-submit'),
function(done) {
assert.ok(browser.success);
assert.equal(browser.text("title"), title);
}
], done);
};
async.series([
drupal.go('login'),
drupal.go('createMultipleContent', drupal.config.get('nodes')),
drupal.go('editNode', 12345, 'Testing!')
]);