e2e tests (draft)
This commit is contained in:
parent
408db13fd4
commit
6c35046ab2
26 changed files with 1659 additions and 29 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,8 +3,10 @@ npm-debug.log
|
|||
.DS_Store
|
||||
config/development.*
|
||||
config/production.*
|
||||
config/test.*
|
||||
workers/reports/config/development.*
|
||||
workers/reports/config/production.*
|
||||
workers/reports/config/test.*
|
||||
dump.rdb
|
||||
|
||||
# generate POT file every time you want to update your PO file
|
||||
|
|
|
@ -9,7 +9,7 @@ module.exports = function (grunt) {
|
|||
},
|
||||
|
||||
nodeunit: {
|
||||
all: ['test/**/*-test.js']
|
||||
all: ['test/nodeunit/**/*-test.js']
|
||||
},
|
||||
|
||||
jsxgettext: {
|
||||
|
|
5
app.js
5
app.js
|
@ -183,6 +183,11 @@ app.use((req, res, next) => {
|
|||
res.locals.customStyles = config.customstyles || [];
|
||||
res.locals.customScripts = config.customscripts || [];
|
||||
|
||||
let bodyClasses = [];
|
||||
app.get('env') === 'test' && bodyClasses.push('page--' + (req.path.substring(1).replace(/\//g, '--') || 'home'));
|
||||
req.user && bodyClasses.push('logged-in user-' + req.user.username);
|
||||
res.locals.bodyClass = bodyClasses.join(' ');
|
||||
|
||||
settingsModel.list(['ua_code', 'shoutout'], (err, configItems) => {
|
||||
if (err) {
|
||||
return next(err);
|
||||
|
|
|
@ -110,16 +110,6 @@ host="0.0.0.0"
|
|||
# VERP hostname is in the same domain as the From address.
|
||||
# disablesenderheader=true
|
||||
|
||||
[testserver]
|
||||
# Starts a vanity server that redirects all mail to /dev/null
|
||||
# Mostly needed for local development
|
||||
enabled=false
|
||||
port=5587
|
||||
host="0.0.0.0"
|
||||
username="testuser"
|
||||
password="testpass"
|
||||
logger=false
|
||||
|
||||
[ldap]
|
||||
# enable to use ldap user backend
|
||||
enabled=false
|
||||
|
@ -177,3 +167,17 @@ templates=[["demo", "Demo Template"]]
|
|||
# The bottom line is that if people who are creating report templates or have write access to the DB cannot be trusted,
|
||||
# then it's safer to switch off the reporting functionality below.
|
||||
enabled=false
|
||||
|
||||
[testserver]
|
||||
# Starts a vanity server that redirects all mail to /dev/null
|
||||
# Mostly needed for local development
|
||||
enabled=false
|
||||
port=5587
|
||||
mailboxserverport=3001
|
||||
host="0.0.0.0"
|
||||
username="testuser"
|
||||
password="testpass"
|
||||
logger=false
|
||||
|
||||
[seleniumwebdriver]
|
||||
browser="phantomjs"
|
||||
|
|
|
@ -107,13 +107,14 @@ function getSql(path, data, callback) {
|
|||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
let renderer = Handlebars.compile(source);
|
||||
return callback(null, renderer(data || {}));
|
||||
const rendered = data ? Handlebars.compile(source)(data) : source;
|
||||
return callback(null, rendered);
|
||||
});
|
||||
}
|
||||
|
||||
function runInitial(callback) {
|
||||
let fname = process.env.DB_FROM_START ? 'base.sql' : 'mailtrain.sql';
|
||||
let dump = process.env.NODE_ENV === 'test' ? 'mailtrain-test.sql' : 'mailtrain.sql';
|
||||
let fname = process.env.DB_FROM_START ? 'base.sql' : dump;
|
||||
let path = pathlib.join(__dirname, '..', 'setup', 'sql', fname);
|
||||
log.info('sql', 'Loading tables from %s', fname);
|
||||
applyUpdate({
|
||||
|
|
17
package.json
17
package.json
|
@ -8,12 +8,17 @@
|
|||
"test": "grunt",
|
||||
"start": "node index.js",
|
||||
"sqlinit": "node setup/sql/init.js",
|
||||
"sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain.sql",
|
||||
"sqldump": "node setup/sql/dump.js | sed -e '/^\\/\\*.*\\*\\/;$/d' -e 's/.[0-9]\\{4\\}-[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]./NOW()/g' > setup/sql/mailtrain${DUMP_NAME_SUFFIX}.sql",
|
||||
"sqldrop": "node setup/sql/drop.js",
|
||||
"sqlgen": "npm run sqldrop && DB_FROM_START=Y npm run sqlinit && npm run sqldump",
|
||||
"langs:hbs": "jsxgettext -L handlebars -k translate -o langs/hbs.pot views/layout.hbs views/index.hbs",
|
||||
"langs:js": "jsxgettext -o languages/js.pot routes/index.js",
|
||||
"langs": "npm run langs:hbs && npm run langs:js"
|
||||
"langs": "npm run langs:hbs && npm run langs:js",
|
||||
"sqldumptest": "NODE_ENV=test DUMP_NAME_SUFFIX=-test npm run sqldump",
|
||||
"sqlresettest": "NODE_ENV=test npm run sqldrop && NODE_ENV=test npm run sqlinit",
|
||||
"starttest": "NODE_ENV=test node index.js",
|
||||
"_e2e": "PATH=$PATH:./node_modules/phantomjs/lib/phantom/bin:./test/e2e/bin NODE_ENV=test ./node_modules/.bin/mocha test/e2e/index.js",
|
||||
"e2e": "npm run sqlresettest && npm run _e2e"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -26,12 +31,18 @@
|
|||
"node": ">=5.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^7.2.3",
|
||||
"chai": "^3.5.0",
|
||||
"eslint-config-nodemailer": "^1.0.0",
|
||||
"grunt": "^1.0.1",
|
||||
"grunt-cli": "^1.2.0",
|
||||
"grunt-contrib-nodeunit": "^1.0.0",
|
||||
"grunt-eslint": "^19.0.0",
|
||||
"jsxgettext-andris": "^0.9.0-patch.1"
|
||||
"jsxgettext-andris": "^0.9.0-patch.1",
|
||||
"mailparser": "^2.0.5",
|
||||
"mocha": "^3.3.0",
|
||||
"phantomjs": "^2.1.7",
|
||||
"selenium-webdriver": "^3.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"posix": "^4.1.1"
|
||||
|
|
|
@ -4,12 +4,37 @@ let log = require('npmlog');
|
|||
let config = require('config');
|
||||
let crypto = require('crypto');
|
||||
let humanize = require('humanize');
|
||||
let http = require('http');
|
||||
|
||||
let SMTPServer = require('smtp-server').SMTPServer;
|
||||
let simpleParser = require('mailparser').simpleParser;
|
||||
|
||||
let totalMessages = 0;
|
||||
let received = 0;
|
||||
|
||||
let mailstore = {
|
||||
accounts: {},
|
||||
saveMessage(address, message) {
|
||||
if (!this.accounts[address]) {
|
||||
this.accounts[address] = [];
|
||||
}
|
||||
this.accounts[address].push(message);
|
||||
},
|
||||
getMail(address, callback) {
|
||||
if (!this.accounts[address] || this.accounts[address].length === 0) {
|
||||
let err = new Error('No mail for ' + address);
|
||||
err.status = 404;
|
||||
return callback(err);
|
||||
}
|
||||
simpleParser(this.accounts[address].shift(), (err, mail) => {
|
||||
if (err) {
|
||||
return callback(err.message || err);
|
||||
}
|
||||
callback(null, mail);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Setup server
|
||||
let server = new SMTPServer({
|
||||
|
||||
|
@ -74,8 +99,12 @@ let server = new SMTPServer({
|
|||
// Handle message stream
|
||||
onData: (stream, session, callback) => {
|
||||
let hash = crypto.createHash('md5');
|
||||
let message = '';
|
||||
stream.on('data', chunk => {
|
||||
hash.update(chunk);
|
||||
if (/^keep/i.test(session.envelope.rcptTo[0].address)) {
|
||||
message += chunk;
|
||||
}
|
||||
});
|
||||
stream.on('end', () => {
|
||||
let err;
|
||||
|
@ -84,6 +113,12 @@ let server = new SMTPServer({
|
|||
err.responseCode = 552;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
// Store message for e2e tests
|
||||
if (/^keep/i.test(session.envelope.rcptTo[0].address)) {
|
||||
mailstore.saveMessage(session.envelope.rcptTo[0].address, message);
|
||||
}
|
||||
|
||||
received++;
|
||||
callback(null, 'Message queued as ' + hash.digest('hex')); // accept the message once the stream is ended
|
||||
});
|
||||
|
@ -94,6 +129,41 @@ server.on('error', err => {
|
|||
log.error('Test SMTP', err.stack);
|
||||
});
|
||||
|
||||
let mailBoxServer = http.createServer((req, res) => {
|
||||
let renderer = data => (
|
||||
'<!doctype html><html><head><title>' + data.title + '</title></head><body>' + data.body + '</body></html>'
|
||||
);
|
||||
|
||||
let address = req.url.substring(1);
|
||||
mailstore.getMail(address, (err, mail) => {
|
||||
if (err) {
|
||||
let html = renderer({
|
||||
title: 'error',
|
||||
body: err.message || err
|
||||
});
|
||||
res.writeHead(err.status || 500, { 'Content-Type': 'text/html' });
|
||||
return res.end(html);
|
||||
}
|
||||
|
||||
let html = mail.html || renderer({
|
||||
title: 'error',
|
||||
body: 'This mail has no HTML part'
|
||||
});
|
||||
|
||||
// https://nodemailer.com/extras/mailparser/#mail-object
|
||||
delete mail.html;
|
||||
delete mail.textAsHtml;
|
||||
delete mail.attachments;
|
||||
|
||||
let script = '<script> var mailObject = ' + JSON.stringify(mail) + '; console.log(mailObject); </script>';
|
||||
html = html.replace(/<\/body\b/i, match => script + match);
|
||||
html = html.replace(/target="_blank"/g, 'target="_self"');
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||||
res.end(html);
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = callback => {
|
||||
if (config.testserver.enabled) {
|
||||
server.listen(config.testserver.port, config.testserver.host, () => {
|
||||
|
@ -112,7 +182,10 @@ module.exports = callback => {
|
|||
}
|
||||
}, 60 * 1000);
|
||||
|
||||
setImmediate(callback);
|
||||
mailBoxServer.listen(config.testserver.mailboxserverport, config.testserver.host, () => {
|
||||
log.info('Test SMTP', 'Mail Box Server listening on port %s', config.testserver.mailboxserverport);
|
||||
setImmediate(callback);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
setImmediate(callback);
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log('This script does not run in production'); // eslint-disable-line no-console
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let config = require('config');
|
||||
let spawn = require('child_process').spawn;
|
||||
let log = require('npmlog');
|
||||
let path = require('path');
|
||||
let fs = require('fs');
|
||||
|
||||
log.level = 'verbose';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
log.error('sqldrop', 'This script does not run in production');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test' && !fs.existsSync(path.join(__dirname, '..', '..', 'config', 'test.toml'))) {
|
||||
log.error('sqldrop', 'This script only runs in test if config/test.toml (i.e. a dedicated test database) is present');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function createDump(callback) {
|
||||
let cmd = spawn(path.join(__dirname, 'drop.sh'), [], {
|
||||
env: {
|
||||
|
|
|
@ -1,15 +1,22 @@
|
|||
'use strict';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
console.log('This script does not run in production'); // eslint-disable-line no-console
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
let dbcheck = require('../../lib/dbcheck');
|
||||
let log = require('npmlog');
|
||||
let path = require('path');
|
||||
let fs = require('fs');
|
||||
|
||||
log.level = 'verbose';
|
||||
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
log.error('sqlinit', 'This script does not run in production');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'test' && !fs.existsSync(path.join(__dirname, '..', '..', 'config', 'test.toml'))) {
|
||||
log.error('sqlinit', 'This script only runs in test if config/test.toml (i.e. a dedicated test database) is present');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
dbcheck(err => {
|
||||
if (err) {
|
||||
log.error('DB', err);
|
||||
|
|
1023
setup/sql/mailtrain-test.sql
Normal file
1023
setup/sql/mailtrain-test.sql
Normal file
File diff suppressed because it is too large
Load diff
11
test/e2e/.eslintrc
Normal file
11
test/e2e/.eslintrc
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"rules": {
|
||||
"strict": 0,
|
||||
"no-invalid-this": 0,
|
||||
"no-unused-expressions": 0
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
}
|
||||
}
|
6
test/e2e/README.md
Normal file
6
test/e2e/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
Running e2e tests requires Node 7.6 or later and a dedicated test database.
|
||||
|
||||
1. Start Mailtrain with `npm run startest`
|
||||
2. Start e2e tests with `npm run e2e`
|
||||
|
||||
By default the tests run with `phantomjs`. To use different browsers see `test/e2e/bin/README.md`.
|
8
test/e2e/bin/README.md
Normal file
8
test/e2e/bin/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
This directory serves for custom browser drivers.
|
||||
|
||||
1. https://seleniumhq.github.io/selenium/docs/api/javascript/
|
||||
2. Download a driver of your choice and put it into this directory
|
||||
3. chmod +x driver
|
||||
4. Edit config/test.toml
|
||||
|
||||
Current Firefox issue (and patch): https://github.com/mozilla/geckodriver/issues/683
|
31
test/e2e/helpers/config.js
Normal file
31
test/e2e/helpers/config.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('config');
|
||||
|
||||
module.exports = {
|
||||
app: config,
|
||||
baseUrl: 'http://localhost:' + config.www.port,
|
||||
users: {
|
||||
admin: {
|
||||
username: 'admin',
|
||||
password: 'test'
|
||||
}
|
||||
},
|
||||
lists: {
|
||||
one: {
|
||||
id: 1,
|
||||
cid: 'Hkj1vCoJb',
|
||||
publicSubscribe: 1,
|
||||
unsubscriptionMode: 0
|
||||
}
|
||||
},
|
||||
settings: {
|
||||
'service-url' : 'http://localhost:' + config.www.port + '/',
|
||||
'default-homepage': 'https://mailtrain.org',
|
||||
'smtp-hostname': config.testserver.host,
|
||||
'smtp-port': config.testserver.port,
|
||||
'smtp-encryption': 'NONE',
|
||||
'smtp-user': config.testserver.username,
|
||||
'smtp-pass': config.testserver.password
|
||||
}
|
||||
};
|
15
test/e2e/helpers/driver.js
Normal file
15
test/e2e/helpers/driver.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
const webdriver = require('selenium-webdriver');
|
||||
|
||||
const driver = new webdriver.Builder()
|
||||
.forBrowser(config.app.seleniumwebdriver.browser || 'phantomjs')
|
||||
.build();
|
||||
|
||||
if (global.USE_SHARED_DRIVER === true) {
|
||||
driver.originalQuit = driver.quit;
|
||||
driver.quit = () => {};
|
||||
}
|
||||
|
||||
module.exports = driver;
|
16
test/e2e/helpers/exit-unless-test.js
Normal file
16
test/e2e/helpers/exit-unless-test.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('./config');
|
||||
const log = require('npmlog');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
if (process.env.NODE_ENV !== 'test' || !fs.existsSync(path.join(__dirname, '..', '..', '..', 'config', 'test.toml'))) {
|
||||
log.error('e2e', 'This script only runs in test and config/test.toml (i.e. a dedicated test database) is present');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (config.app.testserver.enabled !== true) {
|
||||
log.error('e2e', 'This script only runs if the testserver is enabled. Check config/test.toml');
|
||||
process.exit(1);
|
||||
}
|
36
test/e2e/index.js
Normal file
36
test/e2e/index.js
Normal file
|
@ -0,0 +1,36 @@
|
|||
'use strict';
|
||||
|
||||
require('./helpers/exit-unless-test');
|
||||
|
||||
global.USE_SHARED_DRIVER = true;
|
||||
|
||||
const driver = require('./helpers/driver');
|
||||
const only = 'only';
|
||||
const skip = 'skip';
|
||||
|
||||
|
||||
|
||||
let tests = [
|
||||
['tests/login'],
|
||||
['tests/subscription']
|
||||
];
|
||||
|
||||
|
||||
|
||||
tests = tests.filter(t => t[1] !== skip);
|
||||
|
||||
if (tests.some(t => t[1] === only)) {
|
||||
tests = tests.filter(t => t[1] === only);
|
||||
}
|
||||
|
||||
describe('e2e', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
tests.forEach(t => {
|
||||
describe(t[0], () => {
|
||||
require('./' + t[0]); // eslint-disable-line global-require
|
||||
});
|
||||
});
|
||||
|
||||
after(() => driver.originalQuit());
|
||||
});
|
25
test/e2e/page-objects/flash.js
Normal file
25
test/e2e/page-objects/flash.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
const Page = require('./page');
|
||||
let flash;
|
||||
|
||||
class Flash extends Page {
|
||||
getText() {
|
||||
return this.element('alert').getText();
|
||||
}
|
||||
clear() {
|
||||
return this.driver.executeScript(`
|
||||
var elements = document.getElementsByClassName('alert');
|
||||
while(elements.length > 0){
|
||||
elements[0].parentNode.removeChild(elements[0]);
|
||||
}
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = driver => flash || new Flash(driver, {
|
||||
elementToWaitFor: 'alert',
|
||||
elements: {
|
||||
alert: 'div.alert:not(.js-warning)'
|
||||
}
|
||||
});
|
12
test/e2e/page-objects/home.js
Normal file
12
test/e2e/page-objects/home.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
const Page = require('./page');
|
||||
let home;
|
||||
|
||||
module.exports = driver => home || new Page(driver, {
|
||||
url: '/',
|
||||
elementToWaitFor: 'body',
|
||||
elements: {
|
||||
body: 'body.page--home'
|
||||
}
|
||||
});
|
61
test/e2e/page-objects/page.js
Normal file
61
test/e2e/page-objects/page.js
Normal file
|
@ -0,0 +1,61 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('../helpers/config');
|
||||
const webdriver = require('selenium-webdriver');
|
||||
const By = webdriver.By;
|
||||
const until = webdriver.until;
|
||||
|
||||
class Page {
|
||||
constructor(driver, props) {
|
||||
this.driver = driver;
|
||||
this.props = props || {
|
||||
elements: {}
|
||||
};
|
||||
}
|
||||
|
||||
element(key) {
|
||||
return this.driver.findElement(By.css(this.props.elements[key] || key));
|
||||
}
|
||||
|
||||
navigate() {
|
||||
this.driver.navigate().to(config.baseUrl + this.props.url);
|
||||
return this.waitUntilVisible();
|
||||
}
|
||||
|
||||
waitUntilVisible() {
|
||||
let selector = this.props.elements[this.props.elementToWaitFor];
|
||||
if (!selector && this.props.url) {
|
||||
selector = 'body.page--' + (this.props.url.substring(1).replace(/\//g, '--') || 'home');
|
||||
}
|
||||
return selector ? this.driver.wait(until.elementLocated(By.css(selector))) : this.driver.sleep(1000);
|
||||
}
|
||||
|
||||
submit() {
|
||||
return this.element('submitButton').click();
|
||||
}
|
||||
|
||||
click(key) {
|
||||
return this.element(key).click();
|
||||
}
|
||||
|
||||
getText(key) {
|
||||
return this.element(key).getText();
|
||||
}
|
||||
|
||||
getValue(key) {
|
||||
return this.element(key).getAttribute('value');
|
||||
}
|
||||
|
||||
setValue(key, value) {
|
||||
return this.element(key).sendKeys(value);
|
||||
}
|
||||
|
||||
containsText(str) {
|
||||
// let text = await driver.findElement({ css: 'body' }).getText();
|
||||
return this.driver.executeScript(`
|
||||
return (document.documentElement.textContent || document.documentElement.innerText).indexOf('${str}') > -1;
|
||||
`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Page;
|
84
test/e2e/page-objects/subscription.js
Normal file
84
test/e2e/page-objects/subscription.js
Normal file
|
@ -0,0 +1,84 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('../helpers/config');
|
||||
const Page = require('./page');
|
||||
|
||||
class Web extends Page {
|
||||
enterEmail(value) {
|
||||
this.element('emailInput').clear();
|
||||
return this.element('emailInput').sendKeys(value);
|
||||
}
|
||||
}
|
||||
|
||||
class Mail extends Page {
|
||||
navigate(address) {
|
||||
this.driver.sleep(100);
|
||||
this.driver.navigate().to(`http://localhost:${config.app.testserver.mailboxserverport}/${address}`);
|
||||
return this.waitUntilVisible();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = (driver, list) => ({
|
||||
|
||||
webSubscribe: new Web(driver, {
|
||||
url: `/subscription/${list.cid}`,
|
||||
elementToWaitFor: 'form',
|
||||
elements: {
|
||||
form: `form[action="/subscription/${list.cid}/subscribe"]`,
|
||||
emailInput: '#main-form input[name="email"]',
|
||||
submitButton: 'a[href="#submit"]'
|
||||
}
|
||||
}),
|
||||
|
||||
webConfirmSubscriptionNotice: new Web(driver, {
|
||||
url: `/subscription/${list.cid}/confirm-notice`,
|
||||
elementToWaitFor: 'homepageButton',
|
||||
elements: {
|
||||
homepageButton: `a[href="${config.settings['default-homepage']}"]`
|
||||
}
|
||||
}),
|
||||
|
||||
mailConfirmSubscription: new Mail(driver, {
|
||||
elementToWaitFor: 'confirmLink',
|
||||
elements: {
|
||||
confirmLink: `a[href^="${config.settings['service-url']}subscription/subscribe/"]`
|
||||
}
|
||||
}),
|
||||
|
||||
webSubscribedNotice: new Web(driver, {
|
||||
elementToWaitFor: 'homepageButton',
|
||||
elements: {
|
||||
homepageButton: 'a[href^="https://mailtrain.org"]'
|
||||
}
|
||||
}),
|
||||
|
||||
mailSubscriptionConfirmed: new Mail(driver, {
|
||||
elementToWaitFor: 'unsubscribeLink',
|
||||
elements: {
|
||||
unsubscribeLink: 'a[href*="/unsubscribe/"]',
|
||||
manageLink: 'a[href*="/manage/"]'
|
||||
}
|
||||
}),
|
||||
|
||||
webUnsubscribe: new Web(driver, {
|
||||
elementToWaitFor: 'submitButton',
|
||||
elements: {
|
||||
submitButton: 'a[href="#submit"]'
|
||||
}
|
||||
}),
|
||||
|
||||
webUnsubscribedNotice: new Web(driver, {
|
||||
elementToWaitFor: 'homepageButton',
|
||||
elements: {
|
||||
homepageButton: 'a[href^="https://mailtrain.org"]'
|
||||
}
|
||||
}),
|
||||
|
||||
mailUnsubscriptionConfirmed: new Mail(driver, {
|
||||
elementToWaitFor: 'resubscribeLink',
|
||||
elements: {
|
||||
resubscribeLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}"]`
|
||||
}
|
||||
})
|
||||
|
||||
});
|
35
test/e2e/page-objects/users.js
Normal file
35
test/e2e/page-objects/users.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
'use strict';
|
||||
|
||||
const Page = require('./page');
|
||||
|
||||
class Login extends Page {
|
||||
enterUsername(value) {
|
||||
// this.element('usernameInput').clear();
|
||||
return this.element('usernameInput').sendKeys(value);
|
||||
}
|
||||
enterPassword(value) {
|
||||
return this.element('passwordInput').sendKeys(value);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = driver => ({
|
||||
|
||||
login: new Login(driver, {
|
||||
url: '/users/login',
|
||||
elementToWaitFor: 'submitButton',
|
||||
elements: {
|
||||
usernameInput: 'form[action="/users/login"] input[name="username"]',
|
||||
passwordInput: 'form[action="/users/login"] input[name="password"]',
|
||||
submitButton: 'form[action="/users/login"] [type=submit]'
|
||||
}
|
||||
}),
|
||||
|
||||
account: new Page(driver, {
|
||||
url: '/users/account',
|
||||
elementToWaitFor: 'emailInput',
|
||||
elements: {
|
||||
emailInput: 'form[action="/users/account"] input[name="email"]'
|
||||
}
|
||||
})
|
||||
|
||||
});
|
57
test/e2e/tests/login.js
Normal file
57
test/e2e/tests/login.js
Normal file
|
@ -0,0 +1,57 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('../helpers/config');
|
||||
const expect = require('chai').expect;
|
||||
const driver = require('../helpers/driver');
|
||||
const home = require('../page-objects/home')(driver);
|
||||
const flash = require('../page-objects/flash')(driver);
|
||||
const {
|
||||
login,
|
||||
account
|
||||
} = require('../page-objects/users')(driver);
|
||||
|
||||
describe('login', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
before(() => driver.manage().deleteAllCookies());
|
||||
|
||||
it('can access home page', async () => {
|
||||
await home.navigate();
|
||||
});
|
||||
|
||||
it('can not access restricted content', async () => {
|
||||
driver.navigate().to(config.baseUrl + '/settings');
|
||||
flash.waitUntilVisible();
|
||||
expect(await flash.getText()).to.contain('Need to be logged in to access restricted content');
|
||||
await flash.clear();
|
||||
});
|
||||
|
||||
it('can not login with false credentials', async () => {
|
||||
login.enterUsername(config.users.admin.username);
|
||||
login.enterPassword('invalid');
|
||||
login.submit();
|
||||
flash.waitUntilVisible();
|
||||
expect(await flash.getText()).to.contain('Incorrect username or password');
|
||||
await flash.clear();
|
||||
});
|
||||
|
||||
it('can login as admin', async () => {
|
||||
login.enterUsername(config.users.admin.username);
|
||||
login.enterPassword(config.users.admin.password);
|
||||
login.submit();
|
||||
flash.waitUntilVisible();
|
||||
expect(await flash.getText()).to.contain('Logged in as admin');
|
||||
});
|
||||
|
||||
it('can access account page as admin', async () => {
|
||||
await account.navigate();
|
||||
});
|
||||
|
||||
it('can logout', async () => {
|
||||
driver.navigate().to(config.baseUrl + '/users/logout');
|
||||
flash.waitUntilVisible();
|
||||
expect(await flash.getText()).to.contain('logged out');
|
||||
});
|
||||
|
||||
after(() => driver.quit());
|
||||
});
|
101
test/e2e/tests/subscription.js
Normal file
101
test/e2e/tests/subscription.js
Normal file
|
@ -0,0 +1,101 @@
|
|||
'use strict';
|
||||
|
||||
const config = require('../helpers/config');
|
||||
const shortid = require('shortid');
|
||||
const expect = require('chai').expect;
|
||||
const driver = require('../helpers/driver');
|
||||
const Page = require('../page-objects/page');
|
||||
|
||||
const page = new Page(driver);
|
||||
const flash = require('../page-objects/flash')(driver);
|
||||
const {
|
||||
webSubscribe,
|
||||
webConfirmSubscriptionNotice,
|
||||
mailConfirmSubscription,
|
||||
webSubscribedNotice,
|
||||
mailSubscriptionConfirmed,
|
||||
webUnsubscribe,
|
||||
webUnsubscribedNotice,
|
||||
mailUnsubscriptionConfirmed
|
||||
} = require('../page-objects/subscription')(driver, config.lists.one);
|
||||
|
||||
const testuser = {
|
||||
email: 'keep.' + shortid.generate() + '@mailtrain.org'
|
||||
};
|
||||
|
||||
// console.log(testuser.email);
|
||||
|
||||
describe('subscribe (list one)', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
before(() => driver.manage().deleteAllCookies());
|
||||
|
||||
it('visits web-subscribe', async () => {
|
||||
await webSubscribe.navigate();
|
||||
});
|
||||
|
||||
it('submits invalid email (error)', async () => {
|
||||
webSubscribe.enterEmail('foo@bar.nope');
|
||||
webSubscribe.submit();
|
||||
flash.waitUntilVisible();
|
||||
expect(await flash.getText()).to.contain('Invalid email address');
|
||||
});
|
||||
|
||||
it('submits valid email', async () => {
|
||||
webSubscribe.enterEmail(testuser.email);
|
||||
await webSubscribe.submit();
|
||||
});
|
||||
|
||||
it('sees web-confirm-subscription-notice', async () => {
|
||||
webConfirmSubscriptionNotice.waitUntilVisible();
|
||||
expect(await page.containsText('Almost Finished')).to.be.true;
|
||||
});
|
||||
|
||||
it('receives mail-confirm-subscription', async () => {
|
||||
mailConfirmSubscription.navigate(testuser.email);
|
||||
expect(await page.containsText('Please Confirm Subscription')).to.be.true;
|
||||
});
|
||||
|
||||
it('clicks confirm subscription', async () => {
|
||||
await mailConfirmSubscription.click('confirmLink');
|
||||
});
|
||||
|
||||
it('sees web-subscribed-notice', async () => {
|
||||
webSubscribedNotice.waitUntilVisible();
|
||||
expect(await page.containsText('Subscription Confirmed')).to.be.true;
|
||||
});
|
||||
|
||||
it('receives mail-subscription-confirmed', async () => {
|
||||
mailSubscriptionConfirmed.navigate(testuser.email);
|
||||
expect(await page.containsText('Subscription Confirmed')).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('unsubscribe (list one)', function() {
|
||||
this.timeout(10000);
|
||||
|
||||
it('clicks unsubscribe', async () => {
|
||||
await mailSubscriptionConfirmed.click('unsubscribeLink');
|
||||
});
|
||||
|
||||
it('sees web-unsubscribe', async () => {
|
||||
webUnsubscribe.waitUntilVisible();
|
||||
expect(await page.containsText('Unsubscribe')).to.be.true;
|
||||
});
|
||||
|
||||
it('clicks confirm unsubscription', async () => {
|
||||
await webUnsubscribe.submit();
|
||||
});
|
||||
|
||||
it('sees web-unsubscribed-notice', async () => {
|
||||
webUnsubscribedNotice.waitUntilVisible();
|
||||
expect(await page.containsText('Unsubscribe Successful')).to.be.true;
|
||||
});
|
||||
|
||||
it('receives mail-unsubscription-confirmed', async () => {
|
||||
mailUnsubscriptionConfirmed.navigate(testuser.email);
|
||||
expect(await page.containsText('You Are Now Unsubscribed')).to.be.true;
|
||||
});
|
||||
|
||||
after(() => driver.quit());
|
||||
});
|
|
@ -35,7 +35,7 @@
|
|||
|
||||
</head>
|
||||
|
||||
<body class="{{#if user}}logged-in user-{{user.username}}{{/if}}">
|
||||
<body class="{{bodyClass}}">
|
||||
|
||||
<nav class="navbar navbar-default navbar-static-top">
|
||||
<div class="container">
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue