codeceptjs
Version:
Modern Era Acceptance Testing Framework for NodeJS
299 lines (287 loc) • 8.73 kB
JavaScript
const fs = require('fs');
const path = require('path');
const { fileExists } = require('../utils');
const container = require('../container');
const store = require('../store');
const recorder = require('../recorder');
const debug = require('../output').debug;
const isAsyncFunction = require('../utils').isAsyncFunction;
const defaultUser = {
fetch: I => I.grabCookie(),
restore: (I, cookies) => {
I.amOnPage('/'); // open a page
I.setCookie(cookies);
},
};
const defaultConfig = {
saveToFile: false,
inject: 'login',
};
/**
* Logs user in for the first test and reuses session for next tests.
* Works by saving cookies into memory or file.
* If a session expires automatically logs in again.
*
* > For better development experience cookies can be saved into file, so a session can be reused while writing tests.
*
* #### Usage
*
* 1. Enable this plugin and configure as described below
* 2. Define user session names (example: `user`, `editor`, `admin`, etc).
* 3. Define how users are logged in and how to check that user is logged in
* 4. Use `login` object inside your tests to log in:
*
* ```js
* // inside a test file
* // use login to inject auto-login function
* Before(login => {
* login('user'); // login using user session
* });
*
* // Alternatively log in for one scenario
* Scenario('log me in', (I, login) => {
* login('admin');
* I.see('I am logged in');
* });
* ```
*
* #### Configuration
*
* * `saveToFile` (default: false) - save cookies to file. Allows to reuse session between execution.
* * `inject` (default: `login`) - name of the login function to use
* * `users` - an array containing different session names and functions to:
* * `login` - sign in into the system
* * `check` - check that user is logged in
* * `fetch` - to get current cookies (by default `I.grabCookie()`)
* * `restore` - to set cookies (by default `I.amOnPage('/'); I.setCookie(cookie)`)
*
* #### How It Works
*
* 1. `restore` method is executed. It should open a page and set credentials.
* 2. `check` method is executed. It should reload a page (so cookies are applied) and check that this page belongs to logged in user.
* 3. If `restore` and `check` were not successful, `login` is executed
* 4. `login` should fill in login form
* 5. After successful login, `fetch` is executed to save cookies into memory or file.
*
* #### Example: Simple login
*
* ```js
* autoLogin: {
* enabled: true,
* saveToFile: true,
* inject: 'login',
* users: {
* admin: {
* // loginAdmin function is defined in `steps_file.js`
* login: (I) => I.loginAdmin(),
* // if we see `Admin` on page, we assume we are logged in
* check: (I) => {
* I.amOnPage('/');
* I.see('Admin');
* }
* }
* }
* }
* ```
*
* #### Example: Multiple users
*
* ```js
* autoLogin: {
* enabled: true,
* saveToFile: true,
* inject: 'loginAs', // use `loginAs` instead of login
* users: {
* user: {
* login: (I) => {
* I.amOnPage('/login');
* I.fillField('email', 'user@site.com');
* I.fillField('password', '123456');
* I.click('Login');
* },
* check: (I) => {
* I.amOnPage('/');
* I.see('User', '.navbar');
* },
* },
* admin: {
* login: (I) => {
* I.amOnPage('/login');
* I.fillField('email', 'admin@site.com');
* I.fillField('password', '123456');
* I.click('Login');
* },
* check: (I) => {
* I.amOnPage('/');
* I.see('Admin', '.navbar');
* },
* },
* }
* }
* ```
*
* #### Example: Keep cookies between tests
*
* If you decide to keep cookies between tests you don't need to save/retrieve cookies between tests.
* But you need to login once work until session expires.
* For this case, disable `fetch` and `restore` methods.
*
* ```js
* helpers: {
* WebDriver: {
* // config goes here
* keepCookies: true; // keep cookies for all tests
* }
* },
* plugins: {
* autoLogin: {
* users: {
* admin: {
* login: (I) => {
* I.amOnPage('/login');
* I.fillField('email', 'admin@site.com');
* I.fillField('password', '123456');
* I.click('Login');
* },
* check: (I) => {
* I.amOnPage('/dashboard');
* I.see('Admin', '.navbar');
* },
* fetch: () => {}, // empty function
* restore: () => {}, // empty funciton
* }
* }
* }
* }
* ```
*
* #### Example: Getting sessions from local storage
*
* If your session is stored in local storage instead of cookies you still can obtain sessions.
*
* ```js
* plugins: {
* autoLogin: {
* admin: {
* login: (I) => I.loginAsAdmin(),
* check: (I) => I.see('Admin', '.navbar'),
* fetch: (I) => {
* return I.executeScript(() => localStorage.getItem('session_id'));
* },
* restore: (I, session) => {
* I.amOnPage('/');
* I.executeScript((session) => localStorage.setItem('session_id', session), session);
* },
* }
* }
* }
* ```
*
* #### Tips: Using async function in the autoLogin
*
* If you use async functions in the autoLogin plugin, login function should be used with `await` keyword.
*
* ```js
* autoLogin: {
* enabled: true,
* saveToFile: true,
* inject: 'login',
* users: {
* admin: {
* login: async (I) => { // If you use async function in the autoLogin plugin
* const phrase = await I.grabTextFrom('#phrase')
* I.fillField('username', 'admin'),
* I.fillField('password', 'password')
* I.fillField('phrase', phrase)
* },
* check: (I) => {
* I.amOnPage('/');
* I.see('Admin');
* },
* }
* }
* }
* ```
*
* ```js
* Scenario('login', async (I, login) => {
* await login('admin') // you should use `await`
* })
* ```
*
*
*
*/
module.exports = function (config) {
config = Object.assign(defaultConfig, config);
Object.keys(config.users).map(u => config.users[u] = Object.assign({}, defaultUser, config.users[u]));
if (config.saveToFile) {
// loading from file
for (const name in config.users) {
const fileName = path.join(global.output_dir, `${name}_session.json`);
if (!fileExists(fileName)) continue;
const data = fs.readFileSync(fileName).toString();
try {
store[`${name}_session`] = JSON.parse(data);
} catch (err) {
throw new Error(`Could not load session from ${fileName}\n${err}`);
}
debug(`Loaded user session for ${name}`);
}
}
const loginFunction = async (name) => {
const userSession = config.users[name];
const I = container.support('I');
const cookies = store[`${name}_session`];
const shouldAwait = isAsyncFunction(userSession.login)
|| isAsyncFunction(userSession.restore)
|| isAsyncFunction(userSession.check);
const loginAndSave = async () => {
if (shouldAwait) {
await userSession.login(I);
} else {
userSession.login(I);
}
store.debugMode = true;
const cookies = await userSession.fetch(I);
if (config.saveToFile) {
debug(`Saved user session into file for ${name}`);
fs.writeFileSync(path.join(global.output_dir, `${name}_session.json`), JSON.stringify(cookies));
}
store[`${name}_session`] = cookies;
store.debugMode = false;
};
if (!cookies) return loginAndSave();
store.debugMode = true;
recorder.session.start('check login');
if (shouldAwait) {
await userSession.restore(I, cookies);
await userSession.check(I);
} else {
userSession.restore(I, cookies);
userSession.check(I);
}
recorder.session.catch((err) => {
debug(`Failed auto login for ${name} due to ${err}`);
debug('Logging in again');
recorder.session.start('auto login');
return loginAndSave().then(() => {
recorder.add(() => recorder.session.restore('auto login'));
recorder.catch(err => debug('continue'));
}).catch((err) => {
recorder.session.restore('auto login');
recorder.session.restore('check login');
recorder.throw(err);
});
});
recorder.add(() => {
store.debugMode = false;
recorder.session.restore('check login');
});
return recorder.promise();
};
// adding this to DI container
const support = {};
support[config.inject] = loginFunction;
container.append({ support });
};