e2e tests (draft)

This commit is contained in:
witzig 2017-05-10 01:40:02 +02:00
parent 408db13fd4
commit 6c35046ab2
26 changed files with 1659 additions and 29 deletions

2
.gitignore vendored
View file

@ -3,8 +3,10 @@ npm-debug.log
.DS_Store .DS_Store
config/development.* config/development.*
config/production.* config/production.*
config/test.*
workers/reports/config/development.* workers/reports/config/development.*
workers/reports/config/production.* workers/reports/config/production.*
workers/reports/config/test.*
dump.rdb dump.rdb
# generate POT file every time you want to update your PO file # generate POT file every time you want to update your PO file

View file

@ -9,7 +9,7 @@ module.exports = function (grunt) {
}, },
nodeunit: { nodeunit: {
all: ['test/**/*-test.js'] all: ['test/nodeunit/**/*-test.js']
}, },
jsxgettext: { jsxgettext: {

5
app.js
View file

@ -183,6 +183,11 @@ app.use((req, res, next) => {
res.locals.customStyles = config.customstyles || []; res.locals.customStyles = config.customstyles || [];
res.locals.customScripts = config.customscripts || []; 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) => { settingsModel.list(['ua_code', 'shoutout'], (err, configItems) => {
if (err) { if (err) {
return next(err); return next(err);

View file

@ -110,16 +110,6 @@ host="0.0.0.0"
# VERP hostname is in the same domain as the From address. # VERP hostname is in the same domain as the From address.
# disablesenderheader=true # 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] [ldap]
# enable to use ldap user backend # enable to use ldap user backend
enabled=false 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, # 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. # then it's safer to switch off the reporting functionality below.
enabled=false 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"

View file

@ -107,13 +107,14 @@ function getSql(path, data, callback) {
if (err) { if (err) {
return callback(err); return callback(err);
} }
let renderer = Handlebars.compile(source); const rendered = data ? Handlebars.compile(source)(data) : source;
return callback(null, renderer(data || {})); return callback(null, rendered);
}); });
} }
function runInitial(callback) { 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); let path = pathlib.join(__dirname, '..', 'setup', 'sql', fname);
log.info('sql', 'Loading tables from %s', fname); log.info('sql', 'Loading tables from %s', fname);
applyUpdate({ applyUpdate({

View file

@ -8,12 +8,17 @@
"test": "grunt", "test": "grunt",
"start": "node index.js", "start": "node index.js",
"sqlinit": "node setup/sql/init.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", "sqldrop": "node setup/sql/drop.js",
"sqlgen": "npm run sqldrop && DB_FROM_START=Y npm run sqlinit && npm run sqldump", "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: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: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": { "repository": {
"type": "git", "type": "git",
@ -26,12 +31,18 @@
"node": ">=5.0.0" "node": ">=5.0.0"
}, },
"devDependencies": { "devDependencies": {
"babel-eslint": "^7.2.3",
"chai": "^3.5.0",
"eslint-config-nodemailer": "^1.0.0", "eslint-config-nodemailer": "^1.0.0",
"grunt": "^1.0.1", "grunt": "^1.0.1",
"grunt-cli": "^1.2.0", "grunt-cli": "^1.2.0",
"grunt-contrib-nodeunit": "^1.0.0", "grunt-contrib-nodeunit": "^1.0.0",
"grunt-eslint": "^19.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": { "optionalDependencies": {
"posix": "^4.1.1" "posix": "^4.1.1"

View file

@ -4,12 +4,37 @@ let log = require('npmlog');
let config = require('config'); let config = require('config');
let crypto = require('crypto'); let crypto = require('crypto');
let humanize = require('humanize'); let humanize = require('humanize');
let http = require('http');
let SMTPServer = require('smtp-server').SMTPServer; let SMTPServer = require('smtp-server').SMTPServer;
let simpleParser = require('mailparser').simpleParser;
let totalMessages = 0; let totalMessages = 0;
let received = 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 // Setup server
let server = new SMTPServer({ let server = new SMTPServer({
@ -74,8 +99,12 @@ let server = new SMTPServer({
// Handle message stream // Handle message stream
onData: (stream, session, callback) => { onData: (stream, session, callback) => {
let hash = crypto.createHash('md5'); let hash = crypto.createHash('md5');
let message = '';
stream.on('data', chunk => { stream.on('data', chunk => {
hash.update(chunk); hash.update(chunk);
if (/^keep/i.test(session.envelope.rcptTo[0].address)) {
message += chunk;
}
}); });
stream.on('end', () => { stream.on('end', () => {
let err; let err;
@ -84,6 +113,12 @@ let server = new SMTPServer({
err.responseCode = 552; err.responseCode = 552;
return callback(err); 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++; received++;
callback(null, 'Message queued as ' + hash.digest('hex')); // accept the message once the stream is ended 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); 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 => { module.exports = callback => {
if (config.testserver.enabled) { if (config.testserver.enabled) {
server.listen(config.testserver.port, config.testserver.host, () => { server.listen(config.testserver.port, config.testserver.host, () => {
@ -112,8 +182,11 @@ module.exports = callback => {
} }
}, 60 * 1000); }, 60 * 1000);
mailBoxServer.listen(config.testserver.mailboxserverport, config.testserver.host, () => {
log.info('Test SMTP', 'Mail Box Server listening on port %s', config.testserver.mailboxserverport);
setImmediate(callback); setImmediate(callback);
}); });
});
} else { } else {
setImmediate(callback); setImmediate(callback);
} }

View file

@ -1,17 +1,23 @@
'use strict'; '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 config = require('config');
let spawn = require('child_process').spawn; let spawn = require('child_process').spawn;
let log = require('npmlog'); let log = require('npmlog');
let path = require('path'); let path = require('path');
let fs = require('fs');
log.level = 'verbose'; 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) { function createDump(callback) {
let cmd = spawn(path.join(__dirname, 'drop.sh'), [], { let cmd = spawn(path.join(__dirname, 'drop.sh'), [], {
env: { env: {

View file

@ -1,15 +1,22 @@
'use strict'; '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 dbcheck = require('../../lib/dbcheck');
let log = require('npmlog'); let log = require('npmlog');
let path = require('path');
let fs = require('fs');
log.level = 'verbose'; 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 => { dbcheck(err => {
if (err) { if (err) {
log.error('DB', err); log.error('DB', err);

1023
setup/sql/mailtrain-test.sql Normal file

File diff suppressed because it is too large Load diff

11
test/e2e/.eslintrc Normal file
View 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
View 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
View 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

View 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
}
};

View 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;

View 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
View 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());
});

View 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)'
}
});

View 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'
}
});

View 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;

View 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}"]`
}
})
});

View 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
View 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());
});

View 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());
});

View file

@ -35,7 +35,7 @@
</head> </head>
<body class="{{#if user}}logged-in user-{{user.username}}{{/if}}"> <body class="{{bodyClass}}">
<nav class="navbar navbar-default navbar-static-top"> <nav class="navbar navbar-default navbar-static-top">
<div class="container"> <div class="container">