From fb3511b26414da07c7fdbfbdff2376b7b2e37749 Mon Sep 17 00:00:00 2001 From: vjrj Date: Thu, 1 Feb 2018 17:00:30 +0100 Subject: [PATCH] Added cucumber tests (wip) --- cucumber/features/login.feature | 38 +++++ cucumber/features/steps_definitions/helper.js | 30 ++++ cucumber/features/steps_definitions/hooks.js | 39 ++++++ cucumber/features/steps_definitions/login.js | 130 ++++++++++++++++++ .../AuthenticatedNavigation.js | 7 +- .../ui/components/Navigation/Navigation.js | 7 +- .../PublicNavigation/PublicNavigation.js | 7 +- imports/ui/components/Utils/TestUtils.js | 9 ++ imports/ui/pages/Login/Login.js | 5 +- imports/ui/pages/Profile/Profile.js | 6 +- imports/ui/pages/Signup/Signup.js | 5 +- 11 files changed, 272 insertions(+), 11 deletions(-) create mode 100644 cucumber/features/login.feature create mode 100644 cucumber/features/steps_definitions/helper.js create mode 100644 cucumber/features/steps_definitions/hooks.js create mode 100644 cucumber/features/steps_definitions/login.js create mode 100644 imports/ui/components/Utils/TestUtils.js diff --git a/cucumber/features/login.feature b/cucumber/features/login.feature new file mode 100644 index 0000000..36cc4de --- /dev/null +++ b/cucumber/features/login.feature @@ -0,0 +1,38 @@ +Feature: Allow users to login and logout + + As a user + I want to login and logout + So that I can prove my identity and see personalized data + + Background: + Given I am on the home page + + @watch + Scenario: A user can login + Given I have an account and I logged in + When I sign out + When I enter my email and password + Then I should be logged in + And I can edit my profile + + @watch + Scenario: A user cannot login with bad information + Given I have an account and I logged in + When I sign out + When I enter incorrect authentication information + Then I should see a user not found error + + @watch + Scenario: A user can register and login + Given I register with some name, password and email + Then I should be registered + And I should be logged in + When I sign out + When I enter my email and password + Then I should be logged in + And I can edit my profile + + @skip + Scenario: A user cannot register without accepting service conditions + Given I register with some name, password and email but without accept conditions + Then I shouldn't be registered diff --git a/cucumber/features/steps_definitions/helper.js b/cucumber/features/steps_definitions/helper.js new file mode 100644 index 0000000..9961796 --- /dev/null +++ b/cucumber/features/steps_definitions/helper.js @@ -0,0 +1,30 @@ +// https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript +function makeid(size) { + let text = ''; + const possible = 'abcdefghijklmnopqrstuvwxyz0123456789'; + for (let i = 0; i < size; i += 1) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} + +export const randomUsername = () => makeid(7); + +export const randomPassword = () => randomUsername(); + +export const randomEmail = () => `${randomUsername()}@example.com`; + +export const goHome = (client) => { + client.url(process.env.ROOT_URL); + client.waitForVisible('#react-root', 10000); + expect(client.isVisible('#react-root')).toBe(true); + client.waitUntil(() => client.isVisible('.pg-loading-center-middle') === false, 5000); + client.waitUntil(() => client.isVisible('.pg-loading-center-middle') === false, 5000); + // Close alert + if (client.isVisible('.bert-content')) { + client.click('.bert-content'); + } + if (client.isVisible('#acceptCookies')) { + client.click('#acceptCookies'); + } +}; diff --git a/cucumber/features/steps_definitions/hooks.js b/cucumber/features/steps_definitions/hooks.js new file mode 100644 index 0000000..5d97a50 --- /dev/null +++ b/cucumber/features/steps_definitions/hooks.js @@ -0,0 +1,39 @@ +/* global client */ + +(function () { + module.exports = function () { + this.Before(() => { + // global.expect = require('@xolvio/jasmine-expect').expect; + + if (!this.initMyCmds) { + client.addCommand('waitForClickable', function elementClickable(selector, timeout) { + this.waitForVisible(selector, timeout); + this.waitForEnabled(selector, timeout); + }); + this.initMyCmds = true; + } + + // server.call('fixtures/reset'); + // server.call('emailStub/reset'); + // server.call('emailStub/stub'); + // server.call('fixtures/stubCloudFrontClient'); + + // go to the page first so we have access to the Meteor object + // console.log('Before all tests...'); + // client.url(process.env.ROOT_URL); + /* client.executeAsync(function (done) { + * var customer = { + * id: 'randomId', + * email: 'me@example.com', + * subscriptions: { + * data: [{ + * current_period_start: 1436716844, + * current_period_end: 1436716844 + * }] + * } + * }; */ + // this call will stub stripe both on the client and server + // Meteor.call('stripeStub/stub', customer, done); + }); + }; +}()); diff --git a/cucumber/features/steps_definitions/login.js b/cucumber/features/steps_definitions/login.js new file mode 100644 index 0000000..84f4d26 --- /dev/null +++ b/cucumber/features/steps_definitions/login.js @@ -0,0 +1,130 @@ +/* global module expect require process client */ + +const { + goHome, randomUsername, randomPassword, randomEmail +} = require('./helper.js'); + +let firstName; +let lastName; +let pass; +let email; + +const setUserValues = (isEdit) => { + client.waitForVisible('input[name="firstName"]', 5000); + firstName = randomUsername(); + lastName = randomUsername(); + email = randomEmail(); + client.setValue('input[name="firstName"]', firstName); + client.setValue('input[name="lastName"]', lastName); + client.setValue('input[name="emailAddress"]', email); + if (isEdit) { + client.setValue('input[name="currentPassword"]', pass); + pass = randomPassword(); + client.setValue('input[name="newPassword"]', pass); + } else { + pass = randomPassword(); + client.setValue('input[name="password"]', pass); + } +}; + +module.exports = function doSteps(notos) { + this.Given(/^I am on the home page$/, () => { + goHome(client); + }); + + function register() { + if (client.isVisible('#logout')) { + client.click('#logout'); + } + client.waitForVisible('#signup', 5000); + client.click('#signup'); + setUserValues(); + if (!notos) { + client.click('input[name="tos"]'); + } + client.click('#signUpSubmit'); + } + + this.Given(/^I have an account and I logged in$/, () => { + register(); + }); + + function checkName() { + client.waitForVisible('#profile', 5000); + client.waitForText('#profile', firstName); + client.waitForText('#profile', lastName); + } + + this.Then(/^I should be logged in$/, () => { + client.waitUntil(() => client.isVisible('#logout') === true, 10000); + }); + + /* this.Given(/^I am signed out$/, () => { + * client.waitUntil(() => client.isVisible('#logout') === true, 10000); + * }); + */ + + function closeAlert() { + client.waitForVisible('.bert-alert', 5000); + client.click('.bert-content'); + client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000); + } + + this.When(/^I sign out$/, () => { + if (client.isVisible('.bert-alert')) { + client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000); + } + client.waitForVisible('#logout', 5000); + client.click('#logout'); + }); + + function login(badpass) { + client.waitForVisible('#login', 10000); + client.waitUntil(() => client.isVisible('.bert-alert') === false, 10000); + client.click('#login'); + client.waitForVisible('#loginSubmit', 5000); + client.setValue('input[name="emailAddress"]', email); + client.setValue('input[name="password"]', badpass ? randomPassword() : pass); + client.click('#loginSubmit'); + closeAlert(); + } + + this.When(/^I enter my email and password$/, () => { + login(); + }); + + this.Then(/^I can edit my profile$/, () => { + client.waitForVisible('#profile', 5000); + client.click('#profile'); + setUserValues(true); + client.click('#profileSubmit'); + closeAlert(); + checkName(); + }); + + this.When(/^I enter incorrect authentication information$/, () => { + login(true); + }); + + this.Then(/^I should see a user not found error$/, () => { + client.waitForVisible('.alert-danger', 10000, true); + }); + + this.Given(/^I register with some name, password and email$/, () => { + register(); + }); + + this.Then(/^I should be registered$/, () => { + client.waitForVisible('.bert-alert', 10000, true); + closeAlert(); + }); + + this.Given(/^I register with some name, password and email but without accept conditions$/, () => { + register(true); + }); + + this.Then(/^I shouldn't be registered$/, () => { + client.waitForVisible('.bert-alert', 10000, true); + client.waitForVisible('#login', 10000, true); + }); +}; diff --git a/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js b/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js index b98ec58..e479f8f 100644 --- a/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js +++ b/imports/ui/components/AuthenticatedNavigation/AuthenticatedNavigation.js @@ -1,9 +1,12 @@ +/* eslint-disable import/no-absolute-path */ import React from 'react'; import PropTypes from 'prop-types'; import { withRouter } from 'react-router-dom'; import { LinkContainer } from 'react-router-bootstrap'; /* import { Nav, NavDropdown } from 'react-bootstrap'; */ import { translate, Trans } from 'react-i18next'; +import { testId } from '/imports/ui/components/Utils/TestUtils'; + /* FIXME: navitem needs a nav-link class but does not works @@ -22,10 +25,10 @@ const AuthenticatedNavigation = ({ name, history, props }) => ( {/* Mis alertas */} - + {name} - + history.push('/logout')} href="/logout">Cerrar sesión {/* */} diff --git a/imports/ui/components/Navigation/Navigation.js b/imports/ui/components/Navigation/Navigation.js index 6a7a158..883a4b5 100644 --- a/imports/ui/components/Navigation/Navigation.js +++ b/imports/ui/components/Navigation/Navigation.js @@ -1,9 +1,12 @@ +/* eslint-disable import/no-absolute-path */ + import React from 'react'; import PropTypes from 'prop-types'; import { Navbar } from 'react-bootstrap'; import { Link } from 'react-router-dom'; import { LinkContainer } from 'react-router-bootstrap'; import { Trans, translate } from 'react-i18next'; +import { testId } from '/imports/ui/components/Utils/TestUtils'; import BetaRibbon from '../../components/BetaRibbon/BetaRibbon'; import PublicNavigation from '../PublicNavigation/PublicNavigation'; import AuthenticatedNavigation from '../AuthenticatedNavigation/AuthenticatedNavigation'; @@ -38,12 +41,12 @@ const Navigation = props => ( {/* Sandbox */} - + {props.authenticated ? Mis zonas : Participar} - + {props.t('activeFires')} diff --git a/imports/ui/components/PublicNavigation/PublicNavigation.js b/imports/ui/components/PublicNavigation/PublicNavigation.js index 901d384..d59714e 100644 --- a/imports/ui/components/PublicNavigation/PublicNavigation.js +++ b/imports/ui/components/PublicNavigation/PublicNavigation.js @@ -1,8 +1,11 @@ +/* eslint-disable import/no-absolute-path */ import React from 'react'; import PropTypes from 'prop-types'; import { LinkContainer } from 'react-router-bootstrap'; /* import { Nav } from 'react-bootstrap'; */ import { translate } from 'react-i18next'; +import { testId } from '/imports/ui/components/Utils/TestUtils'; + /* FIXME: navitem needs a nav-link class but does not works @@ -15,10 +18,10 @@ import NavItem from '../NavItem/NavItem'; const PublicNavigation = props => (
    {/* */} diff --git a/imports/ui/components/Utils/TestUtils.js b/imports/ui/components/Utils/TestUtils.js new file mode 100644 index 0000000..23cf121 --- /dev/null +++ b/imports/ui/components/Utils/TestUtils.js @@ -0,0 +1,9 @@ +import { Meteor } from 'meteor/meteor'; +import { Random } from 'meteor/random'; + +export const testId = (id) => { + if (Meteor.isDevelopment) { + return id; + } + return Random.id; +}; diff --git a/imports/ui/pages/Login/Login.js b/imports/ui/pages/Login/Login.js index bdbb696..8d7dc5c 100644 --- a/imports/ui/pages/Login/Login.js +++ b/imports/ui/pages/Login/Login.js @@ -1,3 +1,5 @@ +/* eslint-disable import/no-absolute-path */ + import React from 'react'; import PropTypes from 'prop-types'; import { Row, FormGroup, ControlLabel, Button } from 'react-bootstrap'; @@ -6,6 +8,7 @@ import { Meteor } from 'meteor/meteor'; import { Bert } from 'meteor/themeteorchef:bert'; import { translate } from 'react-i18next'; import { T9n } from 'meteor-accounts-t9n'; +import { testId } from '/imports/ui/components/Utils/TestUtils'; import Col from '../../components/Col/Col'; import OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons'; import AccountPageFooter from '../../components/AccountPageFooter/AccountPageFooter'; @@ -101,7 +104,7 @@ class Login extends React.Component { className="form-control" /> - +

    {this.t('¿No tienes una cuenta?')} {this.t('Regístrate')}.

    diff --git a/imports/ui/pages/Profile/Profile.js b/imports/ui/pages/Profile/Profile.js index 21d59ed..26766bb 100644 --- a/imports/ui/pages/Profile/Profile.js +++ b/imports/ui/pages/Profile/Profile.js @@ -1,13 +1,15 @@ /* eslint-disable no-underscore-dangle */ +/* eslint-disable import/no-absolute-path */ import React from 'react'; import PropTypes from 'prop-types'; import { Row, FormGroup, ControlLabel, Button } from 'react-bootstrap'; -import Col from '../../components/Col/Col'; import _ from 'lodash'; import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { Bert } from 'meteor/themeteorchef:bert'; +import { testId } from '/imports/ui/components/Utils/TestUtils'; +import Col from '../../components/Col/Col'; import { createContainer } from 'meteor/react-meteor-data'; import InputHint from '../../components/InputHint/InputHint'; import validate from '../../../modules/validate'; @@ -232,7 +234,7 @@ class Profile extends React.Component { /> {this.t('Usa al menos seis caracteres.')} - + ) :
    ; } diff --git a/imports/ui/pages/Signup/Signup.js b/imports/ui/pages/Signup/Signup.js index 7857921..ba8f595 100644 --- a/imports/ui/pages/Signup/Signup.js +++ b/imports/ui/pages/Signup/Signup.js @@ -8,6 +8,7 @@ import { Link } from 'react-router-dom'; import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; import { Bert } from 'meteor/themeteorchef:bert'; +import { testId } from '/imports/ui/components/Utils/TestUtils'; import Icon from '../../components/Icon/Icon'; import OAuthLoginButtons from '../../components/OAuthLoginButtons/OAuthLoginButtons'; import InputHint from '../../components/InputHint/InputHint'; @@ -186,11 +187,11 @@ class Signup extends React.Component { /> {t('Usa al menos seis caracteres.')} - this.setTermsAccept(e.target.checked)}> + this.setTermsAccept(e.target.checked)}> Acepto las condiciones de servicio de este sitio - +

    {t('¿Ya tienes un cuenta?')} {t('Iniciar sesión')}.