zulip/frontend_tests/casper_lib/common.js

382 lines
12 KiB
JavaScript

var util = require("util");
var test_credentials = require('../../var/casper/test_credentials.js').test_credentials;
function timestamp() {
return new Date().getTime();
}
// The timestamp of the last message send or get_events result.
var last_send_or_update = -1;
function log_in(credentials) {
if (credentials === undefined) {
credentials = test_credentials.default_user;
}
casper.test.info('Logging in');
casper.fill('form[action^="/accounts/login/"]', {
username: credentials.username,
password: credentials.password,
}, true /* submit form */);
}
exports.init_viewport = function () {
casper.options.viewportSize = {width: 1280, height: 1024};
};
exports.initialize_casper = function () {
if (casper.zulip_initialized !== undefined) {
return;
}
casper.zulip_initialized = true;
// These initialization steps will fail if they run before
// casper.start has been called.
// Fail if we get a JavaScript error in the page's context.
// Based on the example at http://phantomjs.org/release-1.5.html
//
// casper.on('error') doesn't work (it never gets called) so we
// set this at the PhantomJS level.
casper.page.onError = function (msg, trace) {
casper.test.error(msg);
casper.echo('Traceback:');
trace.forEach(function (item) {
casper.echo(' ' + item.file + ':' + item.line);
});
casper.exit(1);
};
// Capture screens from all failures
var casper_failure_count = 1;
casper.test.on('fail', function failure() {
if (casper_failure_count <= 10) {
casper.capture("var/casper/casper-failure" + casper_failure_count + ".png");
casper_failure_count += 1;
}
});
// Update last_send_or_update whenever get_events returns.
casper.on('resource.received', function (resource) {
if (/\/json\/get_events/.test(resource.url)) {
last_send_or_update = timestamp();
}
});
casper.on('load.finished', function () {
casper.evaluateOrDie(function () {
$(document).trigger($.Event('phantom_page_loaded'));
return true;
});
});
// This function should always be enclosed within a then() otherwise
// it might not exist on casper object.
casper.waitForSelectorText = function (selector, text, then, onTimeout, timeout) {
this.waitForSelector(selector, function _then() {
this.waitFor(function _check() {
var content = this.fetchText(selector);
if (util.isRegExp(text)) {
return text.test(content);
}
return content.indexOf(text) !== -1;
}, then, onTimeout, timeout);
}, onTimeout, timeout);
return this;
};
casper.evaluate(function () {
window.localStorage.clear();
});
// This captures console messages from the app.
casper.on('remote.message', function (msg) {
casper.echo("app console: " + msg);
});
};
exports.then_log_in = function (credentials) {
casper.then(function () {
log_in(credentials);
});
};
exports.start_and_log_in = function (credentials, viewport) {
var log_in_url = "http://zulip.zulipdev.com:9981/accounts/login/";
exports.init_viewport();
casper.start(log_in_url, function () {
exports.initialize_casper(viewport);
log_in(credentials);
});
};
exports.then_click = function (selector) {
casper.then(function () {
casper.waitUntilVisible(selector, function () {
casper.click(selector);
});
});
};
exports.then_log_out = function () {
var menu_selector = '#settings-dropdown';
var logout_selector = 'a[href="#logout"]';
casper.waitUntilVisible(menu_selector, function () {
casper.click(menu_selector);
casper.waitUntilVisible(logout_selector, function () {
casper.test.info('Logging out');
casper.click(logout_selector);
});
});
casper.waitUntilVisible(".login-page-container", function () {
casper.test.assertUrlMatch(/accounts\/login\/$/);
casper.test.info("Logged out");
});
};
// Put the specified string into the field_selector, then
// select the menu item matching item by typing str.
exports.select_item_via_typeahead = function (field_selector, str, item) {
casper.then(function () {
casper.test.info('Looking in ' + field_selector + ' to select ' + str + ', ' + item);
casper.evaluate(function (field_selector, str, item) {
// Set the value and then send a bogus keyup event to trigger
// the typeahead.
$(field_selector)
.focus()
.val(str)
.trigger($.Event('keyup', { which: 0 }));
// You might think these steps should be split by casper.then,
// but apparently that's enough to make the typeahead close (??),
// but not the first time.
// Trigger the typeahead.
// Reaching into the guts of Bootstrap Typeahead like this is not
// great, but I found it very hard to do it any other way.
var tah = $(field_selector).data().typeahead;
tah.mouseenter({
currentTarget: $('.typeahead:visible li:contains("' + item + '")')[0],
});
tah.select();
}, {field_selector: field_selector, str: str, item: item});
});
};
exports.check_form = function (form_selector, expected, test_name) {
var values = casper.getFormValues(form_selector);
var k;
for (k in expected) {
if (expected.hasOwnProperty(k)) {
casper.test.assertEqual(values[k], expected[k],
test_name ? test_name + ": " + k : undefined);
}
}
};
exports.wait_for_message_actually_sent = function () {
casper.waitFor(function () {
return casper.evaluate(function () {
return !current_msg_list.last().locally_echoed;
});
});
};
exports.turn_off_press_enter_to_send = function () {
var enter_send_selector = '#enter_sends';
casper.waitForSelector(enter_send_selector);
var is_checked = casper.evaluate(function (enter_send_selector) {
return document.querySelector(enter_send_selector).checked;
}, enter_send_selector);
if (is_checked) {
casper.click(enter_send_selector);
}
};
exports.pm_recipient = {
set: function (recip) {
casper.evaluate(function (recipient) {
$("#private_message_recipient").text(recipient)
.trigger({ type: "keydown", keyCode: 13 });
}, { recipient: recip });
},
expect: function (expected_value) {
var displayed_recipients = casper.evaluate(function () {
return compose_state.private_message_recipient();
});
casper.test.assertEquals(displayed_recipients, expected_value);
},
};
// Wait for any previous send to finish, then send a message.
exports.then_send_message = function (type, params) {
casper.then(function () {
casper.waitForSelector('#compose-send-button:enabled');
casper.waitForSelector('#compose-textarea');
});
casper.then(function () {
if (type === "stream") {
casper.page.sendEvent('keypress', "c");
} else if (type === "private") {
casper.page.sendEvent('keypress', "x");
} else {
casper.test.assertTrue(false, "send_message got valid message type");
}
exports.pm_recipient.set(params.recipient);
delete params.recipient;
if (params.stream) {
params.stream_message_recipient_stream = params.stream;
delete params.stream;
}
if (params.subject) {
params.stream_message_recipient_topic = params.subject;
delete params.subject;
}
casper.fill('form[action^="/json/messages"]', params);
exports.turn_off_press_enter_to_send();
casper.then(function () {
casper.click('#compose-send-button');
});
});
casper.then(function () {
casper.waitFor(function emptyComposeBox() {
return casper.getFormValues('form[action^="/json/messages"]').content === '';
});
exports.wait_for_message_actually_sent();
casper.evaluate(function () {
compose_actions.cancel();
});
});
casper.then(function () {
last_send_or_update = timestamp();
});
};
// Get message headings (recipient rows) and bodies out of the DOM.
// casper.evaluate plays weird tricks with a closure, evaluating
// it in the web page's context. Passing arguments from the test
// script's context is awkward (c.f. the various appearances of
// 'table' here).
exports.get_rendered_messages = function (table) {
return casper.evaluate(function (table) {
var tbl = $('#' + table);
return {
headings: $.map(tbl.find('.recipient_row .message-header-contents'), function (elem) {
var $clone = $(elem).clone(true);
$clone.find(".recipient_row_date").remove();
return $clone.text();
}),
bodies: $.map(tbl.find('.message_content'), function (elem) {
return elem.innerHTML;
}),
};
}, {
table: table,
});
};
exports.get_form_field_value = function (selector) {
return casper.evaluate(function (selector) {
return $(selector).val();
}, selector);
};
// Inject key presses by running some jQuery code in page context.
// PhantomJS and CasperJS don't provide a clean way to insert key
// presses by code, only strings of printable characters.
exports.keypress = function (code) {
casper.evaluate(function (code) {
$('body').trigger($.Event('keydown', { which: code }));
}, {
code: code,
});
};
// Send a whole list of messages using then_send_message.
exports.then_send_many = function (msgs) {
msgs.forEach(function (msg) {
exports.then_send_message(
msg.stream !== undefined ? 'stream' : 'private',
msg);
});
};
// Wait to receive queued messages.
exports.wait_for_receive = function (step) {
// Wait until the last send or get_events result was more than 1000 ms ago.
casper.waitFor(function () {
return timestamp() - last_send_or_update > 1000;
}, step);
};
// Wait until the loading spinner goes away (helpful just after logging in).
exports.wait_for_load = function (step) {
casper.waitWhileVisible('#page_loading_indicator', step);
};
// innerText sometimes gives us non-breaking space characters, and occasionally
// a different number of spaces than we expect.
exports.normalize_spaces = function (str) {
return str.replace(/\s+/g, ' ');
};
exports.ltrim = function (str) {
return str.replace(/^\s+/g, '');
};
exports.rtrim = function (str) {
return str.replace(/\s+$/g, '');
};
exports.trim = function (str) {
return exports.rtrim(exports.ltrim(str));
};
// Call get_rendered_messages and then check that the last few headings and
// bodies match the specified arrays.
exports.expected_messages = function (table, headings, bodies) {
casper.test.assertVisible('#' + table, table + ' is visible');
var msg = exports.get_rendered_messages(table);
casper.test.assertEquals(
msg.headings.slice(-headings.length).map(exports.normalize_spaces).map(exports.trim),
headings.map(exports.trim),
'Got expected message headings');
casper.test.assertEquals(
msg.bodies.slice(-bodies.length),
bodies,
'Got expected message bodies');
};
exports.un_narrow = function () {
casper.test.info('Un-narrowing');
if (casper.visible('.message_comp')) {
// close the compose box
common.keypress(27); // Esc
}
common.keypress(27); // Esc
};