Merge pull request #14950 from SwikritiT/develop

[Tests-Only] Added end-to-end tests
This commit is contained in:
Laurent Destailleur 2020-10-08 19:43:22 +02:00 committed by GitHub
commit e53309fdf3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 804 additions and 0 deletions

13
.gitignore vendored
View File

@ -41,3 +41,16 @@ htdocs/includes/sebastian/
htdocs/includes/squizlabs/
htdocs/includes/webmozart/
htdocs/.well-known/apple-developer-merchantid-domain-association
# Node Modules
build/yarn-error.log
build/node_modules/
node_modules/
#yarn
yarn.lock
#package-lock
package-lock.json
doc/install.lock

27
nightwatch.conf.js Normal file
View File

@ -0,0 +1,27 @@
const admin_username = process.env.ADMIN_USERNAME || 'dolibarr';
const admin_password = process.env.ADMIN_PASSWORD || 'password';
const launch_url = process.env.LAUNCH_URL || 'http://localhost/dolibarr/htdocs/';
module.exports = {
page_objects_path : './test/acceptance/pageObjects/', // jshint ignore:line
src_folders : ['test'],
test_settings : {
default : {
selenium_host : '127.0.0.1',
launchUrl : launch_url,
globals : {
backend_url : launch_url,
adminUsername : admin_username,
adminPassword : admin_password
},
desiredCapabilities : {
browserName : 'chrome',
javascriptEnabled : true,
chromeOptions : {
args : ['disable-gpu'],
w3c : false
}
}
}
}
};

14
package.json Normal file
View File

@ -0,0 +1,14 @@
{
"devDependencies": {
"cucumber": "^6.0.5",
"nightwatch": "^1.4.1",
"nightwatch-api": "^3.0.1"
},
"scripts": {
"test:e2e": "node_modules/cucumber/bin/cucumber-js --require test/acceptance/index.js --require test/acceptance/stepDefinitions -f node_modules/cucumber-pretty"
},
"dependencies": {
"cucumber-pretty": "^6.0.0",
"node-fetch": "^2.6.1"
}
}

56
test/README.md Normal file
View File

@ -0,0 +1,56 @@
#Run End-to-End Tests
###Run Selenium
Selenium has been used for automating the browser.
We can run selenium by two ways:
* Usually, for running tests using selenium we download `selenium standalone server JAR file` and `chrome driver` and start selenium server with a command which usually looks like:
`java -jar selenium-server-standalone-<selenium version>.jar -port <port-no>`
* Run selenium in docker with
`docker run -d -p 4444:4444 -p 5900:5900 -v /dev/shm:/dev/shm selenium/standalone-chrome-debug`
OR
`docker run -d --network="host" -v /dev/shm:/dev/shm selenium/standalone-chrome-debug`
OR
`docker run -d --network host -v /dev/shm:/dev/shm selenium/standalone-chrome-debug`
###Run the acceptance tests
* In `nightwatch.conf.js` file inside the root directory of the project and inside the configuration file following environment variable has been specified. We can change the default values according to our local configuration.
```
const admin_username = process.env.ADMIN_USERNAME || 'dolibarr';
const admin_password = process.env.ADMIN_PASSWORD || 'password';
const launch_url = process.env.LAUNCH_URL || 'http://localhost/dolibarr/htdocs/';
```
* You can run test using following commands
`yarn run test:e2e test/acceptance/features/<feature_file>`
For example: `yarn run test:e2e test/acceptance/features/addUsers.feature`
OR
`LAUNCH_URL='<launch_url>' ADMIN_USERNAME='<admin_username>' ADMIN_PASSWORD='<admin_password>' yarn run test:e2e test/acceptance/features/`
The full script to run the acceptance tests is specified in `scripts` object of `package.json` file inside the project's root directory as :
`"test:e2e": "node_modules/cucumber/bin/cucumber-js --require test/acceptance/index.js --require test/acceptance/stepDefinitions -f node_modules/cucumber-pretty"`
After you run the above command you can see the test running. For that :
* open `Remmina` (Remmina is a Remote Desktop Client and comes installed with Ubuntu)
* choose `VNC` and enter `localhost` on the address bar
* enter `secret` as the password

View File

@ -0,0 +1,86 @@
Feature: Add user
As an admin
I want to add users
So that the authorized access is possible
Background:
Given the administrator has logged in using the webUI
And the administrator has browsed to the new users page
Scenario: Admin adds user without permission
When the admin creates user with following details
| last name | Potter |
| login | harrypotter@gmail.com |
| password | password |
Then new user "Potter" should be created
And message "This user has no permissions defined" should be displayed in the webUI
Scenario Outline: Admin adds user with permission
When the admin creates user with following details
| last name | Potter |
| login | harrypotter@gmail.com |
| password | password |
| administrator | <administrator> |
| gender | <gender> |
Then message "This user has no permissions defined" <shouldOrShouldNot> be displayed in the webUI
And new user "Potter" should be created
Examples:
| administrator | gender | shouldOrShouldNot |
| No | | should |
| No | Man | should |
| No | Woman | should |
| Yes | | should not |
| Yes | Man | should not |
| Yes | Woman | should not |
Scenario Outline: Admin adds user with last name as special characters
When the admin creates user with following details
| last name | <last name> |
| login | harry |
| password | password |
Then message "This user has no permissions defined" should be displayed in the webUI
And new user "<last name>" should be created
Examples:
| last name |
| swi@ |
| g!!@%ui |
| swikriti@h |
| !@#$%^&*()-_+=[]{}:;,.<>?~ |
| $w!kr!t! |
| España§àôœ |
| |
| ि $%#?&@name.txt |
Scenario Outline: Admin adds user with incomplete essential credentials
When the admin creates user with following details
| last name | <last name> |
| login | <login> |
| password | <password> |
Then message "<message>" should be displayed in the webUI
And new user "<last name>" should not be created
Examples:
| last name | login | password | message |
| | | | Name is not defined.\nLogin is not defined. |
| Joseph | | | Login is not defined. |
| | john@gmail.com | | Name is not defined. |
| Joseph | | hihi | Login is not defined. |
Scenario: Admin adds user with incomplete essential credentials
When the admin creates user with following details
| last name | Doe |
| login | John |
| password | |
Then message "This user has no permissions defined" should be displayed in the webUI
And new user "Doe" should be created
Scenario: Admin tries to add user with pre-existing login credential
Given a user has been created with following details
| login | last name | password |
| Tyler | Joseph | pass1234 |
And the administrator has browsed to the new users page
When the admin creates user with following details
| last name | Dun |
| login | Tyler |
| password | pass1234 |
Then message "Login already exists." should be displayed in the webUI
And new user "Dun" should not be created

View File

@ -0,0 +1,29 @@
Feature: list users
As an admin user
I want to view the list of users
So that I can manage users
Background:
Given the administrator has logged in using the webUI
Scenario: Admin user should be able to see list of created users when no new users are created
When the administrator browses to the list of users page using the webUI
Then following users should be displayed in the users list
| login | last name |
| dolibarr | SuperAdmin |
And the number of created users should be 1
Scenario: Admin user should be able to see number of created users
Given the admin has created the following users
| login | last name | password |
| Harry | Potter | hello123 |
| Hermoine | Granger | hello123 |
| Ron | Weasley | hello123 |
When the administrator browses to the list of users page using the webUI
Then following users should be displayed in the users list
| login | last name |
| dolibarr | SuperAdmin |
| Harry | Potter |
| Hermoine | Granger |
| Ron | Weasley |
And the number of created users should be 4

View File

@ -0,0 +1,27 @@
Feature: user login
As a user/admin
I want to login to my account
So that I can have access to my functionality
Background:
Given the user has browsed to the login page
Scenario: Admin user should be able to login successfully
When user logs in with username "dolibarr" and password "password"
Then the user should be directed to the homepage
Scenario: Admin user with empty credentials should not be able to login
When user logs in with username "" and password ""
Then the user should not be able to login
Scenario Outline: user logs in with invalid credentials
When user logs in with username "<username>" and password "<password>"
Then the user should not be able to login
And error message "Bad value for login or password" should be displayed in the webUI
Examples:
| username | password |
| dolibar | pass |
| dolibarr | passw |
| dolibar | |
| dolibarr | |
| dolibar | password |

View File

@ -0,0 +1,10 @@
Feature: user logs out
As a user
I want to log out of my account
So that I can protect my work, identity and be assured of my privacy
Scenario: User can logout
Given the administrator has logged in using the webUI
When the user opens the user profile using the webUI
And the user logs out using the webUI
Then the user should be logged out successfully

14
test/acceptance/index.js Normal file
View File

@ -0,0 +1,14 @@
const { setDefaultTimeout, After, Before } = require('cucumber')
const { createSession, closeSession, startWebDriver, stopWebDriver } = require('nightwatch-api')
setDefaultTimeout(60000)
Before(async () => {
await startWebDriver();
await createSession();
})
After(async () => {
await closeSession();
await stopWebDriver();
})

View File

@ -0,0 +1,128 @@
const util = require('util');
module.exports = {
url: function () {
return this.api.launchUrl + 'user/card.php?leftmenu=users&action=create';
},
commands: [
{
adminCreatesUser: async function (dataTable) {
const userDetails = dataTable.rowsHash();
let administrator = userDetails['administrator'];
let gender = userDetails['gender'];
await this.waitForElementVisible('@newUserAddOption')
.useXpath()
.waitForElementVisible('@lastnameField')
.clearValue('@lastnameField')
.setValue('@lastnameField', userDetails['last name'])
.waitForElementVisible('@loginField')
.clearValue('@loginField')
.setValue('@loginField', userDetails['login'])
.waitForElementVisible('@newUserPasswordField')
.clearValue('@newUserPasswordField')
.setValue('@newUserPasswordField', userDetails['password']);
if (userDetails['administrator']) {
const admin = util.format(this.elements.administratorSelectOption.selector, administrator);
await this.waitForElementVisible('@administratorField')
.click('@administratorField')
.waitForElementVisible(admin)
.click(admin);
}
if (userDetails['gender']) {
const genderValue = util.format(this.elements.genderSelectOption.selector, gender)
await this.waitForElementVisible('@genderField')
.click('@genderField')
.waitForElementVisible(genderValue)
.click(genderValue);
}
return this.waitForElementVisible('@submitButton')
.click('@submitButton')
.useCss();
},
noPermissionMessage: async function (message) {
await this.useXpath()
.waitForElementVisible('@noPermissionDefinedMessage')
.expect.element('@noPermissionDefinedMessage')
.text.to.equal(message);
return this.useCss();
},
newUserShouldBeCreated: async function (lastname) {
await this.useXpath()
.waitForElementVisible('@newUserCreated')
.expect.element('@newUserCreated')
.text.to.equal(lastname);
return this.useCss();
},
noPermissionDefinedMessageNotShown: function (message) {
return this.useXpath()
.waitForElementNotPresent('@noPermissionDefinedMessage')
.useCss();
},
userNotCreated: function (lastname) {
return this.waitForElementVisible('@newUserAddOption');
}
}
],
elements: {
newUserAddOption: {
selector: '.fiche'
},
lastnameField: {
selector: '//table[@class="border centpercent"]/tbody/tr/td//input[@id="lastname"]',
locateStrategy: 'xpath'
},
loginField: {
selector: '//table[@class="border centpercent"]/tbody/tr/td//input[@name="login"]',
locateStrategy: 'xpath'
},
newUserPasswordField: {
selector: '//table[@class="border centpercent"]/tbody/tr/td//input[@name="password"]',
locateStrategy: 'xpath'
},
submitButton: {
selector: '//div[@class="center"]/input[@class="button"]',
locateStrategy: 'xpath'
},
administratorField: {
selector: '//table[@class="border centpercent"]/tbody/tr/td//select[@id="admin"]',
locateStrategy: 'xpath'
},
administratorSelectOption: {
selector: '//select[@id="admin"]/option[.="%s"]',
locateStrategy: 'xpath'
},
genderField: {
selector: '//table[@class="border centpercent"]/tbody/tr/td//select[@id="gender"]',
locateStrategy: 'xpath'
},
genderSelectOption: {
selector: '//select[@id="gender"]/option[.="%s"]',
locateStrategy: 'xpath'
},
noPermissionDefinedMessage: {
selector: '//div[@class="jnotify-message"]',
locateStrategy: 'xpath'
},
newUserCreated: {
selector: '//div[contains(@class,"valignmiddle")]//div[contains(@class,"inline-block floatleft valignmiddle")]',
locateStrategy: 'xpath'
}
}
};

View File

@ -0,0 +1,44 @@
module.exports = {
url: function () {
return this.api.launchUrl + 'admin/index.php?mainmenu=home&leftmenu=setup&mesg=setupnotcomplete';
},
commands: [
{
browsedToNewUserPage: function () {
return this.useXpath()
.waitForElementVisible('@usersAndGroups')
.click('@usersAndGroups')
.waitForElementVisible('@newUser')
.click('@newUser')
.useCss();
},
browsedToListOfUsers: function () {
return this.useXpath()
.waitForElementVisible('@usersAndGroups')
.click('@usersAndGroups')
.waitForElementVisible('@listOfUsers')
.click('@listOfUsers')
.useCss();
}
}
],
elements: {
usersAndGroups: {
selector: '//div[@class="menu_titre"]/a[@title="Users & Groups"]',
locateStrategy: 'xpath'
},
newUser: {
selector: '//div[@class="menu_contenu menu_contenu_user_card"]/a[@title="New user"]',
locateStrategy: 'xpath'
},
listOfUsers: {
selector: '//a[@class="vsmenu"][@title="List of users"]',
locateStrategy: 'xpath'
}
}
};

View File

@ -0,0 +1,47 @@
const util = require('util');
module.exports = {
url: function () {
return this.api.launchUrl + 'user/list.php?leftmenu=users';
},
commands: [
{
listOfUsersDisplayed: async function (dataTable) {
const usersList = dataTable.hashes();
this.useXpath();
for (const row of usersList) {
let login = row['login'];
let lastName = row['last name'];
const userDetail = util.format(this.elements.userList.selector, login, lastName);
await this.waitForElementVisible('@userRow')
.waitForElementVisible(userDetail);
}
return this.useCss();
},
numberOfUsersDisplayed: async function (number) {
const userCount = util.format(this.elements.numberOfUsers.selector, number);
await this.useXpath()
.waitForElementVisible(userCount);
return this.useCss();
}
}
],
elements: {
userRow: {
selector: '//table[contains(@class,"tagtable liste")]/tbody/tr[position()>2]',
locateStrategy: 'xpath'
},
numberOfUsers: {
selector: '//div[contains(@class, "titre inline-block") and contains(., "List of users")]/span[.="(%d)"]',
locateStrategy: 'xpath'
},
userList: {
selector: '//table[contains(@class,"tagtable liste")]/tbody/tr[position()>2]/td/a//span[normalize-space(@class="nopadding usertext")][.="%s"]/../../following-sibling::td[.="%s"]',
locateStrategy: 'xpath'
}
}
};

View File

@ -0,0 +1,83 @@
module.exports = {
url: function () {
return this.api.launchUrl;
},
commands: [
{
waitForLoginPage: function () {
return this.waitForElementVisible('@loginTable');
},
userLogsInWithUsernameAndPassword: function (username, password) {
return this.waitForElementVisible('@userNameField')
.setValue('@userNameField', username)
.waitForElementVisible('@passwordField')
.setValue('@passwordField', password)
.useXpath()
.waitForElementVisible('@loginButton')
.click('@loginButton')
.useCss();
},
successfulLogin: function () {
return this.waitForElementNotPresent('@loginTable')
.waitForElementVisible('@userProfileDropdown');
},
userIsLoggedIn: async function (login) {
await this.useXpath()
.waitForElementVisible('@userLogin')
.expect.element('@userLogin')
.text.to.equal(login);
return this.useCss();
},
unsuccessfulLogin: function () {
return this.waitForElementVisible('@loginTable')
.waitForElementNotPresent('@userProfileDropdown');
},
loginErrorDisplayed: async function (errorMessage) {
await this.useXpath()
.waitForElementVisible('@loginError')
.expect.element('@loginError')
.text.to.equal(errorMessage);
return this.useCss();
}
}
],
elements: {
loginButton: {
selector: '//div[@id="login-submit-wrapper"]/input[@type="submit"]',
locateStrategy: 'xpath'
},
userNameField: {
selector: '#username'
},
passwordField: {
selector: '#password'
},
loginTable: {
selector: '.login_table'
},
userProfileDropdown: {
selector: '#topmenu-login-dropdown'
},
userLogin: {
selector: '//div[@id="topmenu-login-dropdown"]/a//span[contains(@class,"atoploginusername")]',
locateStrategy: 'xpath'
},
loginError: {
selector: '//div[@class="center login_main_message"]/div[@class="error"]',
locateStrategy: 'xpath'
}
}
};

View File

@ -0,0 +1,34 @@
module.exports = {
url: function () {
return this.api.launchUrl + 'admin/index.php?mainmenu=home&leftmenu=setup&mesg=setupnotcomplete';
},
commands:
[
{
userOpensProfile: async function () {
await this.useXpath()
.waitForElementVisible('@userProfileDropdown')
.click('@userProfileDropdown')
return this.useCss();
},
userLogsOut: function () {
return this.waitForElementVisible('@logoutButton')
.click('@logoutButton');
}
}
],
elements: {
logoutButton: {
selector: '.pull-right'
},
userProfileDropdown: {
selector: '//div[@id="topmenu-login-dropdown"]',
locateStrategy: 'xpath'
}
}
};

View File

@ -0,0 +1,142 @@
const { Before, Given, When, Then, After } = require('cucumber');
const { client } = require('nightwatch-api');
const fetch = require('node-fetch');
let initialUsers = {};
let dolApiKey = '';
Given('the administrator has logged in using the webUI', async function () {
await client.page.loginPage().navigate().waitForLoginPage();
await client.page.loginPage().userLogsInWithUsernameAndPassword(client.globals.adminUsername, client.globals.adminPassword);
return client.page.loginPage().userIsLoggedIn(client.globals.adminUsername);
});
Given('the administrator has browsed to the new users page', function () {
return client.page.homePage().browsedToNewUserPage();
});
When('the admin creates user with following details', function (datatable) {
return client.page.addUsersPage().adminCreatesUser(datatable);
});
Then('new user {string} should be created', function (lastname) {
return client.page.addUsersPage().newUserShouldBeCreated(lastname);
});
Then('message {string} should be displayed in the webUI', function (message) {
return client.page.addUsersPage().noPermissionMessage(message);
});
Then('message {string} should not be displayed in the webUI', function (message) {
return client.page.addUsersPage().noPermissionDefinedMessageNotShown(message);
});
Then('new user {string} should not be created', function (lastname) {
return client.page.addUsersPage().userNotCreated(lastname);
});
Given('a user has been created with following details', function (dataTable) {
return adminHasCreatedUser(dataTable);
});
Given('the admin has created the following users', function (dataTable) {
return adminHasCreatedUser(dataTable);
});
const getUsers = async function () {
const header = {};
const url = client.globals.backend_url + 'api/index.php/users';
const users = {};
header['Accept'] = 'application/json';
header['DOLAPIKEY'] = dolApiKey;
await fetch(url, {
method: 'GET',
headers: header
})
.then(async (response) => {
const json_response = await response.json();
for (const user of json_response) {
users[user.id] = user.id;
}
});
return users;
};
const adminHasCreatedUser = async function (dataTable) {
const header = {};
const url = client.globals.backend_url + 'api/index.php/users';
header['Accept'] = 'application/json';
header['DOLAPIKEY'] = dolApiKey;
header['Content-Type'] = 'application/json';
const userDetails = dataTable.hashes();
for (const user of userDetails) {
await fetch(url, {
method: 'POST',
headers: header,
body: JSON.stringify(
{
login: user['login'],
lastname: user['last name'],
pass: user['password']
}
)
})
.then((response) => {
if (response.status < 200 || response.status >= 400) {
throw new Error('Failed to create user: ' + user['login'] +
' ' + response.statusText);
}
return response.text();
});
}
};
Before(async () => {
const header = {}
const adminUsername = client.globals.adminUsername;
const adminPassword = client.globals.adminPassword;
const params = new URLSearchParams()
params.set('login', adminUsername)
params.set('password', adminPassword)
const apiKey = `http://localhost/dolibarr/htdocs/api/index.php/login?${params.toString()}`;
header['Accept'] = 'application/json'
await fetch(apiKey, {
method: 'GET',
headers: header
})
.then(async (response) => {
const jsonResponse = await response.json()
dolApiKey = jsonResponse['success']['token']
})
})
Before(async () => {
initialUsers = await getUsers();
});
After(async () => {
const finalUsers = await getUsers();
const header = {};
const url = client.globals.backend_url + 'api/index.php/users/';
header['Accept'] = 'application/json';
header['DOLAPIKEY'] = dolApiKey;
let found;
for (const finaluser in finalUsers) {
for (const initialuser in initialUsers) {
found = false;
if (initialuser === finaluser) {
found = true;
break;
}
}
if (!found) {
await fetch(url + finaluser, {
method: 'DELETE',
headers: header
})
.then(res => {
if (res.status < 200 || res.status >= 400) {
throw new Error("Failed to delete user: " + res.statusText);
}
});
}
}
});

View File

@ -0,0 +1,14 @@
const { When, Then } = require('cucumber');
const { client } = require('nightwatch-api');
When('the administrator browses to the list of users page using the webUI', function () {
return client.page.homePage().browsedToListOfUsers();
});
Then('following users should be displayed in the users list', function (dataTable) {
return client.page.listUsersPage().listOfUsersDisplayed(dataTable);
});
Then('the number of created users should be {int}', function (number) {
return client.page.listUsersPage().numberOfUsersDisplayed(number);
});

View File

@ -0,0 +1,22 @@
const { Given, When, Then } = require('cucumber')
const { client } = require('nightwatch-api')
Given('the user has browsed to the login page', function () {
return client.page.loginPage().navigate();
});
When('user logs in with username {string} and password {string}', function (username, password) {
return client.page.loginPage().userLogsInWithUsernameAndPassword(username, password);
});
Then('the user should be directed to the homepage', function () {
return client.page.loginPage().successfulLogin();
});
Then('the user should not be able to login', function () {
return client.page.loginPage().unsuccessfulLogin();
});
Then('error message {string} should be displayed in the webUI', function (errormessage) {
return client.page.loginPage().loginErrorDisplayed(errormessage);
});

View File

@ -0,0 +1,14 @@
const { When, Then } = require('cucumber');
const { client } = require('nightwatch-api');
When('the user opens the user profile using the webUI', function () {
return client.page.logoutPage().userOpensProfile();
});
When('the user logs out using the webUI', function () {
return client.page.logoutPage().userLogsOut();
});
Then('the user should be logged out successfully', function () {
return client.page.loginPage().waitForLoginPage();
});