diff --git a/.travis.yml b/.travis.yml index 9c02b2151b..fb36776cbc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,16 @@ node_js: - "0.10" git: submodules: false +before_install: + - gem update --system + - gem install sass bourbon + - npm install -g grunt-cli + - git clone git://github.com/n1k0/casperjs.git ~/casperjs + - cd ~/casperjs + - git checkout tags/1.1-beta1 + - export PATH=$PATH:`pwd`/bin + - cd - before_script: - - npm install -g grunt-cli \ No newline at end of file + - phantomjs --version + - casperjs --version + - grunt init \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js index 2d61d3285c..3dc96b9abe 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -6,6 +6,8 @@ var path = require('path'), spawn = require("child_process").spawn, buildDirectory = path.resolve(process.cwd(), '.build'), distDirectory = path.resolve(process.cwd(), '.dist'), + config = require('./config'), + _ = require('underscore'), configureGrunt = function (grunt) { // load all grunt tasks @@ -343,6 +345,34 @@ var path = require('path'), cfg.buildType = type; }); + grunt.registerTask('spawn-casperjs', function () { + var done = this.async(), + options = ['host', 'noPort', 'port', 'email', 'password'], + args = ['test', 'admin/', '--includes=base.js', '--direct', '--log-level=debug']; + + // Forward parameters from grunt to casperjs + _.each(options, function processOption(option) { + if (grunt.option(option)) { + args.push('--' + option + '=' + grunt.option(option)); + } + }); + + grunt.util.spawn({ + cmd: 'casperjs', + args: args, + opts: { + cwd: path.resolve('core/test/functional'), + stdio: 'inherit' + } + }, function (error, result, code) { + if (error) { + grunt.fail.fatal(result.stdout); + } + grunt.log.writeln(result.stdout); + done(); + }); + }); + // Prepare the project for development // TODO: Git submodule init/update (https://github.com/jaubourg/grunt-update-submodules)? grunt.registerTask("init", ["shell:bourbon", "sass:admin", 'handlebars']); @@ -356,8 +386,11 @@ var path = require('path'), // Run migrations tests only grunt.registerTask("test-m", ["mochacli:migrate"]); + // Run casperjs tests only + grunt.registerTask('test-functional', ['express', 'spawn-casperjs']); + // Run tests and lint code - grunt.registerTask("validate", ["jslint", "mochacli:all"]); + grunt.registerTask("validate", ["jslint", "mochacli:all", "test-functional"]); // Generate Docs grunt.registerTask("docs", ["groc"]); diff --git a/core/test/functional/admin/01_login_test.js b/core/test/functional/admin/01_login_test.js index 6e0f4486fe..49bb9cf90c 100644 --- a/core/test/functional/admin/01_login_test.js +++ b/core/test/functional/admin/01_login_test.js @@ -1,4 +1,59 @@ /*globals casper, __utils__, url, user, falseUser */ + +casper.test.begin('Ensure Session is Killed', 1, function suite(test) { + casper.test.filename = 'login_logout_test.png'; + + casper.start(url + 'logout/', function (response) { + test.assert(/\/ghost\//.test(response.url), response.url); + }); + + casper.run(function () { + test.done(); + }); +}); + +casper.test.begin('Ensure a User is Registered', 2, function suite(test) { + casper.test.filename = 'login_user_registered_test.png'; + + casper.start(url + 'ghost/signup/'); + + casper.waitFor(function checkOpaque() { + return this.evaluate(function () { + var loginBox = document.querySelector('.login-box'); + return window.getComputedStyle(loginBox).getPropertyValue('display') === "block" + && window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1"; + }); + }, function then() { + checkUrl = true; + this.fill("#register", user, true); + }); + + casper.waitForSelectorTextChange('.notification-error', function (text) { + test.assertSelectorHasText('.notification-error', 'already registered'); + // If the previous assert succeeds, then we should skip the next check and just pass. + test.pass('Already registered!'); + }, function () { + test.assertUrlMatch(/\/ghost\/$/, 'If we\'re not already registered, we should be logged in.'); + test.pass('Successfully registered.') + }, 2000); + + casper.run(function () { + test.done(); + }); +}); + +casper.test.begin('Ensure Session is Killed after Registration', 1, function suite(test) { + casper.test.filename = 'login_logout2_test.png'; + + casper.start(url + 'logout/', function (response) { + test.assert(/\/ghost\//.test(response.url), response.url); + }); + + casper.run(function () { + test.done(); + }); +}); + casper.test.begin("Ghost admin will load login page", 2, function suite(test) { casper.test.filename = "admin_test.png"; @@ -26,9 +81,9 @@ casper.test.begin('Redirects to signin', 2, function suite(test) { }); }); -casper.test.begin("Can login to Ghost", 3, function suite(test) { +casper.test.begin("Can't spam it", 2, function suite(test) { - casper.test.filename = "login_test.png"; + casper.test.filename = "login_spam_test.png"; casper.start(url + "ghost/signin/", function testTitle() { test.assertTitle("", "Ghost admin has no title"); @@ -42,6 +97,39 @@ casper.test.begin("Can login to Ghost", 3, function suite(test) { && window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1"; }); }, function then() { + this.fill("#login", falseUser, true); + casper.wait(200, function doneWait() { + this.fill("#login", falseUser, true); + }); + }); + casper.wait(1000, function doneWait() { + this.echo("I've waited for 1 seconds."); + }); + + casper.then(function testForErrorMessage() { + test.assertSelectorHasText('.notification-error', 'Slow down, there are way too many login attempts!'); + }); + + casper.run(function () { + test.done(); + }); +}); + +casper.test.begin("Can login to Ghost", 3, function suite(test) { + + casper.test.filename = "login_test.png"; + + casper.start(url + "ghost/login/", function testTitle() { + test.assertTitle("", "Ghost admin has no title"); + }).viewport(1280, 1024); + + casper.waitFor(function checkOpaque() { + return this.evaluate(function () { + var loginBox = document.querySelector('.login-box'); + return window.getComputedStyle(loginBox).getPropertyValue('display') === "block" + && window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1"; + }); + }, function then() { this.fill("#login", user, true); }); @@ -58,38 +146,3 @@ casper.test.begin("Can login to Ghost", 3, function suite(test) { test.done(); }); }); - -casper.test.begin("Can't spam it", 2, function suite(test) { - - casper.test.filename = "login_test.png"; - - casper.start(url + "ghost/login/", function testTitle() { - test.assertTitle("", "Ghost admin has no title"); - }).viewport(1280, 1024); - - casper.waitFor(function checkOpaque() { - return this.evaluate(function () { - var loginBox = document.querySelector('.login-box'); - return window.getComputedStyle(loginBox).getPropertyValue('display') === "block" - && window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1"; - }); - }, function then() { - this.fill("#login", falseUser, true); - casper.wait(200, function doneWait() { - this.fill("#login", falseUser, true); - }); - - }); - casper.wait(200, function doneWait() { - this.echo("I've waited for 1 seconds."); - }); - - casper.then(function testForErrorMessage() { - test.assertSelectorHasText('.notification-error', 'Slow down, there are way too many login attempts!'); - }); - - casper.run(function () { - test.done(); - }); -}); - diff --git a/core/test/functional/admin/03_editor_test.js b/core/test/functional/admin/03_editor_test.js index 8689b1574a..cc1c503beb 100644 --- a/core/test/functional/admin/03_editor_test.js +++ b/core/test/functional/admin/03_editor_test.js @@ -64,7 +64,7 @@ casper.test.begin("Haunted markdown in editor works", 3, function suite(test) { }).viewport(1280, 1024); casper.then(function testImage() { - casper.writeContentToCodeMirror("![some text]()"); + casper.writeContentToCodeMirror("![sometext]()"); }); // We must wait after sending keys to CodeMirror @@ -77,9 +77,9 @@ casper.test.begin("Haunted markdown in editor works", 3, function suite(test) { test.assertEvalEquals(function () { return document.querySelector('.CodeMirror-wrap textarea').value; - }, "![some text]()", 'Editor value is correct'); + }, "![sometext]()", 'Editor value is correct'); - test.assertSelectorHasText('.entry-preview .rendered-markdown', 'Add image of some text', 'Editor value is correct'); + test.assertSelectorHasText('.entry-preview .rendered-markdown', 'Add image of sometext', 'Editor value is correct'); }); casper.run(function () { diff --git a/core/test/functional/admin/05_settings_test.js b/core/test/functional/admin/05_settings_test.js index de35a8bda1..6162560336 100644 --- a/core/test/functional/admin/05_settings_test.js +++ b/core/test/functional/admin/05_settings_test.js @@ -82,7 +82,12 @@ casper.test.begin("Settings screen is correct", 19, function suite(test) { casper.then(function checkUserWasSaved() { casper.removeListener('resource.requested', handleUserRequest); - test.assertExists('.notification-success', 'got success notification'); + }); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); }); casper.thenClick('#main-menu .settings a').then(function testOpeningSettingsTwice() { @@ -105,7 +110,12 @@ casper.test.begin("Settings screen is correct", 19, function suite(test) { casper.then(function checkSettingsWereSaved() { casper.removeListener('resource.requested', handleSettingsRequest); - test.assertExists('.notification-success', 'got success notification'); + }); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); }); casper.run(function () { @@ -130,24 +140,36 @@ casper.test.begin("User settings screen validates email", 6, function suite(test brokenEmail = email.replace('.', '-'); casper.fillSelectors('.user-details-container', { - '#user-email': brokenEmail + '#user-email': brokenEmail }, false); }); - casper.thenClick('#user .button-save').waitForResource('/users/', function () { - test.assertExists('.notification-error', 'got error notification'); + casper.thenClick('#user .button-save'); + + casper.waitForResource('/users/'); + + casper.waitForSelector('.notification-error', function onSuccess() { + test.assert(true, 'Got error notification'); test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'No error notification :('); }); casper.then(function resetEmailToValid() { casper.fillSelectors('.user-details-container', { - '#user-email': email + '#user-email': email }, false); }); - casper.thenClick('#user .button-save').waitForResource('/users/', function () { - test.assertExists('.notification-success', 'got success notification'); + casper.thenClick('#user .button-save'); + + casper.waitForResource('/users/'); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); test.assertSelectorDoesntHaveText('.notification-success', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); }); casper.run(function () { diff --git a/core/test/functional/admin/06_flow_test.js b/core/test/functional/admin/06_flow_test.js index 4c0a092072..e13795bc7c 100644 --- a/core/test/functional/admin/06_flow_test.js +++ b/core/test/functional/admin/06_flow_test.js @@ -22,8 +22,14 @@ casper.test.begin("Ghost edit draft flow works correctly", 7, function suite(tes this.echo("I've waited for 1 seconds."); }); - casper.thenClick('.button-save').waitForResource(/posts/, function then() { - test.assertExists('.notification-success', 'got success notification'); + casper.thenClick('.button-save'); + + casper.waitForResource(/posts/); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); }); casper.thenOpen(url + 'ghost/content/', function then() { @@ -42,8 +48,14 @@ casper.test.begin("Ghost edit draft flow works correctly", 7, function suite(tes test.assertUrlMatch(/editor/, "Ghost doesn't require login this time"); }); - casper.thenClick('.button-save').waitForResource(/posts/, function then() { - test.assertExists('.notification-success', 'got success notification'); + casper.thenClick('.button-save'); + + casper.waitForResource(/posts/); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); }); casper.run(function () { diff --git a/core/test/functional/admin/07_logout_test.js b/core/test/functional/admin/07_logout_test.js index bc1920649d..9fdda25e6a 100644 --- a/core/test/functional/admin/07_logout_test.js +++ b/core/test/functional/admin/07_logout_test.js @@ -19,8 +19,14 @@ casper.test.begin("Ghost logout works correctly", 2, function suite(test) { }); }); - casper.thenClick('.usermenu-signout a').waitForResource(/login/, function then() { - test.assertExists('.notification-success', 'got success notification'); + casper.thenClick('.usermenu-signout a'); + + casper.waitForResource(/signin/); + + casper.waitForSelector('.notification-success', function onSuccess() { + test.assert(true, 'Got success notification'); + }, function onTimeout() { + test.assert(false, 'No success notification :('); }); casper.run(function () { @@ -50,13 +56,12 @@ casper.test.begin("Can't spam signin", 3, function suite(test) { }); }); - casper.wait(200, function doneWait() { - this.echo("I've waited for 1 seconds."); - }); - casper.then(function testForErrorMessage() { - test.assertExists('.notification-error', 'got error notification'); + casper.waitForSelector('.notification-error', function onSuccess() { + test.assert(true, 'Got error notification'); test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'No error notification :('); }); casper.run(function () { @@ -77,9 +82,13 @@ casper.test.begin("Ghost signup fails properly", 5, function suite(test) { }); // should now throw a short password error - casper.waitForResource(/signup/, function () { - test.assertExists('.notification-error', 'got error notification'); + casper.waitForResource(/signup/); + + casper.waitForSelector('.notification-error', function onSuccess() { + test.assert(true, 'Got error notification'); test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'No error notification :('); }); casper.then(function signupWithLongPassword() { @@ -87,9 +96,13 @@ casper.test.begin("Ghost signup fails properly", 5, function suite(test) { }); // should now throw a 1 user only error - casper.waitForResource(/signup/, function () { - test.assertExists('.notification-error', 'got error notification'); + casper.waitForResource(/signup/); + + casper.waitForSelector('.notification-error', function onSuccess() { + test.assert(true, 'Got error notification'); test.assertSelectorDoesntHaveText('.notification-error', '[object Object]'); + }, function onTimeout() { + test.assert(false, 'No error notification :('); }); casper.run(function () { diff --git a/core/test/functional/base.js b/core/test/functional/base.js index 6e9eaebee5..4b91289fac 100644 --- a/core/test/functional/base.js +++ b/core/test/functional/base.js @@ -25,7 +25,7 @@ var host = casper.cli.options.host || 'localhost', noPort = casper.cli.options.noPort || false, port = casper.cli.options.port || '2368', email = casper.cli.options.email || 'ghost@tryghost.org', - password = casper.cli.options.password || 'Sl1m3r', + password = casper.cli.options.password || 'Sl1m3rson', url = "http://" + host + (noPort ? '/' : ":" + port + "/"), user = { email: email,