Merge branch 'master' of github.com:Mailtrain-org/mailtrain

This commit is contained in:
Tomas Bures 2017-05-17 09:20:46 -04:00
commit 4c4a0da0ce
26 changed files with 1716 additions and 29 deletions

2
.gitignore vendored
View file

@ -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

View file

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

5
app.js
View file

@ -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);

View file

@ -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"

View file

@ -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({

View file

@ -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": "NODE_ENV=test 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"

View file

@ -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);

View file

@ -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: {

View file

@ -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

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

44
test/e2e/README.md Normal file
View file

@ -0,0 +1,44 @@
# e2e Tests
Running e2e tests requires Node 7.6 or later and a dedicated test database. It uses mocha, selenium-webdriver and phantomjs.
## Installation
These e2e tests have to be performed against predefined resources (e.g. lists, users, etc.) and therefore a dedicated test database and test config is required.
Both can be created by running `sudo sh test/e2e/install.sh` from within your mailtrain directory. This creates a MYSQL user and database called `mailtrain_test`, and generates the required `config/test.toml`.
## Running e2e Tests
For tests to succeed Mailtrian must be started in `test` mode on port 3000 (as http://localhost:3000/ is the predefined service url). The tests itself have to be started in a second Terminal window.
1. Start Mailtrain with `npm run starttest`
2. Start e2e tests with `npm run e2e`
## Using Different Browsers
By default e2e tests use `phantomjs`. If you want to use a different browser you need to install its driver and adjust your `config/test.toml`.
* Install the `firefox` driver with `npm install geckodriver`
* Install the `chrome` driver with `npm install chromedriver`
* Other drivers can be found [here](https://seleniumhq.github.io/selenium/docs/api/javascript/)
Then adjust your config:
```
[seleniumwebdriver]
browser="firefox"
```
Current Firefox issue (and patch): https://github.com/mozilla/geckodriver/issues/683
## Writing e2e Tests
You should spend your time on features rather than writing tests, yet in some cases, like for example the subscription process, manual testing is just silly. You best get started by reading the current test suites, or just open an issue describing the scenario you want to get tested.
Available commands:
* `npm run sqldumptest` - exports the test DB to `setup/sql/mailtrain-test.sql`
* `npm run sqlresettest` - drops all tables then loads `setup/sql/mailtrain-test.sql`
* `npm run _e2e` - just runs e2e tests
* `npm run e2e` - runs `sqlresettest` then `_e2e`

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,21 @@
'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);
}
if (config.app.www.port !== 3000) {
log.error('e2e', 'This script requires Mailtrain to be running on port 3000. 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());
});

36
test/e2e/install.sh Normal file
View file

@ -0,0 +1,36 @@
#!/bin/bash
# This installation script works on Ubuntu 14.04 and 16.04
# Run as root!
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root" 1>&2
exit 1
fi
set -e
export DEBIAN_FRONTEND=noninteractive
MYSQL_PASSWORD=`pwgen 12 -1`
# Setup MySQL user for Mailtrain Tests
mysql -u root -e "CREATE USER 'mailtrain_test'@'localhost' IDENTIFIED BY '$MYSQL_PASSWORD';"
mysql -u root -e "GRANT ALL PRIVILEGES ON mailtrain_test.* TO 'mailtrain_test'@'localhost';"
mysql -u mailtrain_test --password="$MYSQL_PASSWORD" -e "CREATE database mailtrain_test;"
# Setup installation configuration
cat >> config/test.toml <<EOT
[www]
port=3000
[mysql]
user="mailtrain_test"
password="$MYSQL_PASSWORD"
database="mailtrain_test"
[testserver]
enabled=true
[seleniumwebdriver]
browser="phantomjs"
EOT
echo "Success! The test database has been created.";

View file

@ -0,0 +1,21 @@
'use strict';
const page = require('./page');
module.exports = driver => Object.assign(page(driver), {
elementToWaitFor: 'alert',
elements: {
alert: 'div.alert:not(.js-warning)'
},
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]);
}
`);
}
});

View file

@ -0,0 +1,11 @@
'use strict';
const page = require('./page');
module.exports = driver => Object.assign(page(driver), {
url: '/',
elementToWaitFor: 'body',
elements: {
body: 'body.page--home'
}
});

View file

@ -0,0 +1,55 @@
'use strict';
const config = require('../helpers/config');
const webdriver = require('selenium-webdriver');
const By = webdriver.By;
const until = webdriver.until;
module.exports = driver => ({
driver,
elements: {},
element(key) {
return this.driver.findElement(By.css(this.elements[key] || key));
},
navigate(path) {
this.driver.navigate().to(config.baseUrl + (path || this.url));
return this.waitUntilVisible();
},
waitUntilVisible() {
let selector = this.elements[this.elementToWaitFor];
if (!selector && this.url) {
selector = 'body.page--' + (this.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;
`);
}
});

View file

@ -0,0 +1,84 @@
'use strict';
const config = require('../helpers/config');
const page = require('./page');
const web = {
enterEmail(value) {
this.element('emailInput').clear();
return this.element('emailInput').sendKeys(value);
}
};
const mail = {
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: Object.assign(page(driver), web, {
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: Object.assign(page(driver), web, {
url: `/subscription/${list.cid}/confirm-notice`,
elementToWaitFor: 'homepageButton',
elements: {
homepageButton: `a[href="${config.settings['default-homepage']}"]`
}
}),
mailConfirmSubscription: Object.assign(page(driver), mail, {
elementToWaitFor: 'confirmLink',
elements: {
confirmLink: `a[href^="${config.settings['service-url']}subscription/subscribe/"]`
}
}),
webSubscribedNotice: Object.assign(page(driver), web, {
elementToWaitFor: 'homepageButton',
elements: {
homepageButton: 'a[href^="https://mailtrain.org"]'
}
}),
mailSubscriptionConfirmed: Object.assign(page(driver), mail, {
elementToWaitFor: 'unsubscribeLink',
elements: {
unsubscribeLink: 'a[href*="/unsubscribe/"]',
manageLink: 'a[href*="/manage/"]'
}
}),
webUnsubscribe: Object.assign(page(driver), web, {
elementToWaitFor: 'submitButton',
elements: {
submitButton: 'a[href="#submit"]'
}
}),
webUnsubscribedNotice: Object.assign(page(driver), web, {
elementToWaitFor: 'homepageButton',
elements: {
homepageButton: 'a[href^="https://mailtrain.org"]'
}
}),
mailUnsubscriptionConfirmed: Object.assign(page(driver), mail, {
elementToWaitFor: 'resubscribeLink',
elements: {
resubscribeLink: `a[href^="${config.settings['service-url']}subscription/${list.cid}"]`
}
})
});

View file

@ -0,0 +1,32 @@
'use strict';
const page = require('./page');
module.exports = driver => ({
login: Object.assign(page(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]'
},
enterUsername(value) {
// this.element('usernameInput').clear();
return this.element('usernameInput').sendKeys(value);
},
enterPassword(value) {
return this.element('passwordInput').sendKeys(value);
}
}),
account: Object.assign(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')(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>
<body class="{{#if user}}logged-in user-{{user.username}}{{/if}}">
<body class="{{bodyClass}}">
<nav class="navbar navbar-default navbar-static-top">
<div class="container">