e2e tests (draft)
This commit is contained in:
parent
408db13fd4
commit
6c35046ab2
26 changed files with 1659 additions and 29 deletions
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());
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue