diff --git a/README.md b/README.md
index a80af1ebac..44b83ab859 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# HollaEx Kit [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-green.svg)](https://github.com/facebook/create-react-app/pulls)
-HollaEx Kit is an open source while label crypto software suite with a range of features from exchange and trading to user management and onboarding as well as wallet system. In order to run the HollaEx Kit, you need to run the Server as the back-end and Web as your front-end user interface. HollaEx Kit runs as a stand alone node and for trading and blockchain functionalities require to connect to HollaEx Network. By default the node connects to the public HollaEx Network.
+HollaEx Kit is an open source white label crypto software suite with a range of features from exchange and trading to user management and onboarding as well as wallet system. In order to run the HollaEx Kit, you need to run the Server as the back-end and Web as your front-end user interface. HollaEx Kit runs as a stand alone node and for trading and blockchain functionalities require to connect to HollaEx Network. By default the node connects to the public HollaEx Network.
## Get Started
diff --git a/server/api/controllers/admin.js b/server/api/controllers/admin.js
index a45a669cfa..4864aae542 100644
--- a/server/api/controllers/admin.js
+++ b/server/api/controllers/admin.js
@@ -1172,7 +1172,7 @@ const putBurn = (req, res) => {
processing,
waiting,
email,
- updated_description
+ description
} = req.swagger.params.data.value;
loggerAdmin.info(
@@ -1193,8 +1193,8 @@ const putBurn = (req, res) => {
updated_transaction_id,
'updated_address',
updated_address,
- 'updated_description',
- updated_description
+ 'description',
+ description
);
toolsLib.wallet.updatePendingBurn(transaction_id, {
@@ -1206,7 +1206,7 @@ const putBurn = (req, res) => {
updatedTransactionId: updated_transaction_id,
updatedAddress: updated_address,
email,
- updatedDescription: updated_description,
+ updatedDescription: description,
additionalHeaders: {
'x-forwarded-for': req.headers['x-forwarded-for']
}
@@ -2709,6 +2709,125 @@ const changeUserEmail = (req, res) => {
});
};
+const getUserBalanceHistoryByAdmin = (req, res) => {
+ loggerAdmin.verbose(
+ req.uuid,
+ 'controllers/admin/getUserBalanceHistoryByAdmin/auth',
+ req.auth
+ );
+ const { limit, page, order_by, order, start_date, end_date, format, user_id } = req.swagger.params;
+
+ if (start_date.value && !isDate(start_date.value)) {
+ loggerAdmin.error(
+ req.uuid,
+ 'controllers/admin/getUserBalanceHistoryByAdmin invalid start_date',
+ start_date.value
+ );
+ return res.status(400).json({ message: 'Invalid start date' });
+ }
+
+ if (end_date.value && !isDate(end_date.value)) {
+ loggerAdmin.error(
+ req.uuid,
+ 'controllers/admin/getUserBalanceHistoryByAdmin invalid end_date',
+ end_date.value
+ );
+ return res.status(400).json({ message: 'Invalid end date' });
+ }
+
+ if (order_by.value && typeof order_by.value !== 'string') {
+ loggerAdmin.error(
+ req.uuid,
+ 'controllers/admin/getUserBalanceHistoryByAdmin invalid order_by',
+ order_by.value
+ );
+ return res.status(400).json({ message: 'Invalid order by' });
+ }
+
+ toolsLib.user.getUserBalanceHistory({
+ user_id: user_id.value,
+ limit: limit.value,
+ page: page.value,
+ orderBy: order_by.value,
+ order: order.value,
+ startDate: start_date.value,
+ endDate: end_date.value,
+ format: format.value
+ })
+ .then((data) => {
+ if (format.value === 'csv') {
+ toolsLib.user.createAuditLog({ email: req?.auth?.sub?.email, session_id: req?.session_id }, req?.swagger?.apiPath, req?.swagger?.operationPath?.[2], req?.swagger?.params);
+ res.setHeader('Content-disposition', `attachment; filename=${toolsLib.getKitConfig().api_name}-balance_history.csv`);
+ res.set('Content-Type', 'text/csv');
+ return res.status(202).send(data);
+ } else {
+ return res.json(data);
+ }
+ })
+ .catch((err) => {
+ loggerAdmin.error(
+ req.uuid,
+ 'controllers/admin/getUserBalanceHistoryByAdmin',
+ err.message
+ );
+ return res.status(err.statusCode || 400).json({ message: errorMessageConverter(err) });
+ });
+};
+
+
+const createTradeByAdmin = (req, res) => {
+ loggerAdmin.verbose(
+ req.uuid,
+ 'controllers/admin/createTradeByAdmin auth',
+ req.auth
+ );
+
+ const {
+ symbol,
+ side,
+ price,
+ size,
+ maker_id,
+ taker_id,
+ maker_fee,
+ taker_fee
+ } = req.swagger.params.data.value;
+
+ loggerAdmin.info(
+ req.uuid,
+ 'controllers/admin/createTradeByAdmin',
+ symbol,
+ side,
+ price,
+ size,
+ maker_id,
+ taker_id,
+ maker_fee,
+ taker_fee
+ );
+
+ toolsLib.order.createTrade({symbol, side, price, size, maker_id, taker_id, maker_fee, taker_fee },
+ {
+ 'x-forwarded-for': req.headers['x-forwarded-for']
+ })
+ .then((data) => {
+ toolsLib.user.createAuditLog({ email: req?.auth?.sub?.email, session_id: req?.session_id }, req?.swagger?.apiPath, req?.swagger?.operationPath?.[2], req?.swagger?.params?.data?.value);
+ loggerAdmin.info(
+ req.uuid,
+ 'controllers/admin/createTradeByAdmin successful'
+ );
+ return res.status(200).json(data);
+ })
+ .catch((err) => {
+ loggerAdmin.error(
+ req.uuid,
+ 'controllers/admin/mintAsset err',
+ err
+ );
+ return res.status(err.statusCode || 400).json({ message: errorMessageConverter(err) });
+ });
+};
+
module.exports = {
createInitialAdmin,
getAdminKit,
@@ -2776,5 +2895,7 @@ module.exports = {
changeUserEmail,
getTransactionLimits,
updateTransactionLimit,
- deleteTransactionLimit
+ deleteTransactionLimit,
+ getUserBalanceHistoryByAdmin,
+ createTradeByAdmin
};
diff --git a/server/api/controllers/broker.js b/server/api/controllers/broker.js
index 453915da95..6a922a5954 100644
--- a/server/api/controllers/broker.js
+++ b/server/api/controllers/broker.js
@@ -143,13 +143,15 @@ const testRebalance = (req, res) => {
const {
exchange_id,
api_key,
- api_secret
+ api_secret,
+ password
} = req.swagger.params;
toolsLib.broker.testRebalance({
exchange_id: exchange_id.value,
api_key: api_key.value,
- api_secret: api_secret.value
+ api_secret: api_secret.value,
+ password: password.value
})
.then((data) => {
return res.json(data);
diff --git a/server/api/controllers/user.js b/server/api/controllers/user.js
index 8a165f39bd..168c7fef9e 100644
--- a/server/api/controllers/user.js
+++ b/server/api/controllers/user.js
@@ -27,11 +27,12 @@ const {
LOGIN_NOT_ALLOW,
NO_IP_FOUND,
INVALID_OTP_CODE,
- OTP_CODE_NOT_FOUND
+ OTP_CODE_NOT_FOUND,
+ INVALID_CAPTCHA
} = require('../../messages');
const { DEFAULT_ORDER_RISK_PERCENTAGE, EVENTS_CHANNEL, API_HOST, DOMAIN, TOKEN_TIME_NORMAL, TOKEN_TIME_LONG, HOLLAEX_NETWORK_BASE_URL, NUMBER_OF_ALLOWED_ATTEMPTS } = require('../../constants');
const { all } = require('bluebird');
-const { each } = require('lodash');
+const { each, isInteger } = require('lodash');
const { publisher } = require('../../db/pubsub');
const { isDate } = require('moment');
const DeviceDetector = require('node-device-detector');
@@ -300,10 +301,18 @@ const loginPost = (req, res) => {
return toolsLib.security.checkCaptcha(captcha, ip);
})
.catch(async (err) => {
+ if (!otp_code) {
+ throw new Error(INVALID_OTP_CODE);
+ }
await toolsLib.user.createUserLogin(user, ip, device, domain, origin, referer, null, long_term, false);
const loginData = await toolsLib.user.findUserLatestLogin(user, false);
const message = createAttemptMessage(loginData, user, domain);
- throw new Error(err.message + message);
+
+ if (err.message === INVALID_CAPTCHA) {
+ throw new Error(err.message);
+ } else {
+ throw new Error(err.message + message);
+ }
})
]);
}
@@ -1154,6 +1163,99 @@ const userDelete = (req, res) => {
});
};
+const getUserBalanceHistory = (req, res) => {
+ loggerUser.verbose(
+ req.uuid,
+ 'controllers/user/getUserBalanceHistory/auth',
+ req.auth
+ );
+ const user_id = req.auth.sub.id;
+ const { limit, page, order_by, order, start_date, end_date, format } = req.swagger.params;
+
+ if (start_date.value && !isDate(start_date.value)) {
+ loggerUser.error(
+ req.uuid,
+ 'controllers/user/getUserBalanceHistory invalid start_date',
+ start_date.value
+ );
+ return res.status(400).json({ message: 'Invalid start date' });
+ }
+
+ if (end_date.value && !isDate(end_date.value)) {
+ loggerUser.error(
+ req.uuid,
+ 'controllers/user/getUserBalanceHistory invalid end_date',
+ end_date.value
+ );
+ return res.status(400).json({ message: 'Invalid end date' });
+ }
+
+ if (order_by.value && typeof order_by.value !== 'string') {
+ loggerUser.error(
+ req.uuid,
+ 'controllers/user/getUserBalanceHistory invalid order_by',
+ order_by.value
+ );
+ return res.status(400).json({ message: 'Invalid order by' });
+ }
+
+ if (!user_id || !isInteger(user_id)) {
+ loggerUser.error(
+ req.uuid,
+ 'controllers/user/getUserBalanceHistory invalid user_id',
+ user_id
+ );
+ return res.status(400).json({ message: 'Invalid user id' });
+ }
+
+ toolsLib.user.getUserBalanceHistory({
+ user_id,
+ limit: limit.value,
+ page: page.value,
+ orderBy: order_by.value,
+ order: order.value,
+ startDate: start_date.value,
+ endDate: end_date.value,
+ format: format.value
+ })
+ .then((data) => {
+ if (format.value === 'csv') {
+ res.setHeader('Content-disposition', `attachment; filename=${toolsLib.getKitConfig().api_name}-balance_history.csv`);
+ res.set('Content-Type', 'text/csv');
+ return res.status(202).send(data);
+ } else {
+ return res.json(data);
+ }
+ })
+ .catch((err) => {
+ loggerUser.error(
+ req.uuid,
+ 'controllers/user/getUserBalanceHistory',
+ err.message
+ );
+ return res.status(err.statusCode || 400).json({ message: errorMessageConverter(err) });
+ });
+};
+
+const fetchUserProfitLossInfo = (req, res) => {
+ loggerUser.verbose(
+ req.uuid,
+ 'controllers/user/fetchUserProfitLossInfo/auth',
+ req.auth
+ );
+
+ const user_id = req.auth.sub.id;
+
+ toolsLib.user.fetchUserProfitLossInfo(user_id)
+ .then((data) => {
+ return res.json(data);
+ })
+ .catch((err) => {
+ loggerUser.error(req.uuid, 'controllers/user/fetchUserProfitLossInfo', err.message);
+ return res.status(err.statusCode || 400).json({ message: 'Something went wrong' });
+ });
+};
+
module.exports = {
signUpUser,
@@ -1184,5 +1286,7 @@ module.exports = {
revokeUserSession,
getUserSessions,
userLogout,
- userDelete
+ userDelete,
+ getUserBalanceHistory,
+ fetchUserProfitLossInfo
};
diff --git a/server/api/swagger/admin.yaml b/server/api/swagger/admin.yaml
index a2f47ec562..9dc04e37d5 100644
--- a/server/api/swagger/admin.yaml
+++ b/server/api/swagger/admin.yaml
@@ -1903,6 +1903,84 @@ paths:
- bearer
x-security-scopes:
- admin
+ /admin/user/balance-history:
+ x-swagger-router-controller: admin
+ get:
+ description: Get the user balance history for admin
+ operationId: getUserBalanceHistoryByAdmin
+ parameters:
+ - in: query
+ name: user_id
+ description: User id
+ required: false
+ type: number
+ format: int32
+ - in: query
+ name: limit
+ description: "Number of elements to return. Default: 50. Maximun: 100"
+ required: false
+ type: number
+ format: int32
+ - in: query
+ name: page
+ description: Page of data to retrieve
+ required: false
+ type: number
+ format: int32
+ - in: query
+ name: order_by
+ description: Field to order data
+ required: false
+ type: string
+ enum: ['id', 'user_id', 'total']
+ - in: query
+ name: order
+ description: direction to order
+ required: false
+ type: string
+ enum: ['asc', 'desc']
+ - in: query
+ name: start_date
+ description: Starting date of queried data
+ required: false
+ type: string
+ format: date-time
+ - in: query
+ name: end_date
+ description: Ending date of queried data
+ required: false
+ type: string
+ format: date-time
+ - in: query
+ name: format
+ description: Specify data format
+ required: false
+ enum: ['csv', 'all']
+ type: string
+ tags:
+ - Admin
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: "#/definitions/ObjectResponse"
+ 202:
+ description: CSV
+ schema:
+ type: string
+ default:
+ description: Error
+ schema:
+ $ref: "#/definitions/MessageResponse"
+ security:
+ - Token: []
+ x-security-types:
+ - bearer
+ - hmac
+ x-security-scopes:
+ - admin
+ x-token-permissions:
+ - can_read
/admin/orders:
x-swagger-router-controller: order
get:
@@ -2069,7 +2147,7 @@ paths:
- admin
- supervisor
x-token-permissions:
- - can_withdraw
+ - can_trade
/admin/trades:
x-swagger-router-controller: trade
get:
@@ -3758,4 +3836,67 @@ paths:
x-security-scopes:
- admin
x-token-permissions:
- - can_read
\ No newline at end of file
+ - can_read
+ /admin/trade:
+ x-swagger-router-controller: admin
+ post:
+ description: Create trade by admin
+ operationId: createTradeByAdmin
+ tags:
+ - Admin
+ parameters:
+ - name: data
+ in: body
+ required: true
+ schema:
+ type: object
+ required:
+ - symbol
+ - side
+ - price
+ - size
+ - maker_id
+ - taker_id
+ - maker_fee
+ - taker_fee
+ properties:
+ symbol:
+ type: string
+ maxLength: 256
+ side:
+ type: string
+ enum: ['buy', 'sell']
+ price:
+ type: number
+ size:
+ type: number
+ maker_id:
+ type: number
+ format: int32
+ taker_id:
+ type: number
+ format: int32
+ maker_fee:
+ type: number
+ format: double
+ taker_fee:
+ type: number
+ format: double
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: "#/definitions/ObjectResponse"
+ default:
+ description: Error
+ schema:
+ $ref: "#/definitions/MessageResponse"
+ security:
+ - Token: []
+ x-security-types:
+ - bearer
+ - hmac
+ x-security-scopes:
+ - admin
+ x-token-permissions:
+ - can_trade
\ No newline at end of file
diff --git a/server/api/swagger/broker.yaml b/server/api/swagger/broker.yaml
index 7d64206a9f..a2923875c0 100644
--- a/server/api/swagger/broker.yaml
+++ b/server/api/swagger/broker.yaml
@@ -200,6 +200,12 @@ paths:
required: true
type: string
maxLength: 256
+ - in: query
+ name: password
+ description: exchange password
+ required: false
+ type: string
+ maxLength: 256
responses:
200:
description: Success
diff --git a/server/api/swagger/swagger.js b/server/api/swagger/swagger.js
index d180f69c06..be54d4c17f 100644
--- a/server/api/swagger/swagger.js
+++ b/server/api/swagger/swagger.js
@@ -4,7 +4,7 @@ const definition = {
swagger: '2.0',
info: {
title: 'HollaEx Kit',
- version: '2.9.5'
+ version: '2.10.0'
},
host: 'api.hollaex.com',
basePath: '/v2',
diff --git a/server/api/swagger/user.yaml b/server/api/swagger/user.yaml
index d54d495c4d..5a06d24f4f 100644
--- a/server/api/swagger/user.yaml
+++ b/server/api/swagger/user.yaml
@@ -655,6 +655,107 @@ paths:
- user
x-token-permissions:
- can_read
+ /user/balance-history:
+ x-swagger-router-controller: user
+ get:
+ description: Get the user balance history
+ operationId: getUserBalanceHistory
+ parameters:
+ - in: query
+ name: limit
+ description: "Number of elements to return. Default: 50. Maximun: 100"
+ required: false
+ type: number
+ format: int32
+ - in: query
+ name: page
+ description: Page of data to retrieve
+ required: false
+ type: number
+ format: int32
+ - in: query
+ name: order_by
+ description: Field to order data
+ required: false
+ type: string
+ enum: ['id', 'user_id', 'total']
+ - in: query
+ name: order
+ description: direction to order
+ required: false
+ type: string
+ enum: ['asc', 'desc']
+ - in: query
+ name: start_date
+ description: Starting date of queried data
+ required: false
+ type: string
+ format: date-time
+ - in: query
+ name: end_date
+ description: Ending date of queried data
+ required: false
+ type: string
+ format: date-time
+ - in: query
+ name: format
+ description: Specify data format
+ required: false
+ enum: ['csv', 'all']
+ type: string
+ tags:
+ - User
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: "#/definitions/ObjectResponse"
+ 202:
+ description: CSV
+ schema:
+ type: string
+ default:
+ description: Error
+ schema:
+ $ref: "#/definitions/MessageResponse"
+ security:
+ - Token: []
+ x-security-types:
+ - bearer
+ - hmac
+ x-security-scopes:
+ - user
+ x-token-permissions:
+ - can_read
+ /user/balance-pl:
+ x-swagger-router-controller: user
+ get:
+ description: Get the user p/l info
+ operationId: fetchUserProfitLossInfo
+ tags:
+ - User
+ responses:
+ 200:
+ description: Success
+ schema:
+ $ref: "#/definitions/ObjectResponse"
+ 202:
+ description: CSV
+ schema:
+ type: string
+ default:
+ description: Error
+ schema:
+ $ref: "#/definitions/MessageResponse"
+ security:
+ - Token: []
+ x-security-types:
+ - bearer
+ - hmac
+ x-security-scopes:
+ - user
+ x-token-permissions:
+ - can_read
/user/create-address:
x-swagger-router-controller: user
get:
diff --git a/server/constants.js b/server/constants.js
index 4cccb7c296..4930e72357 100644
--- a/server/constants.js
+++ b/server/constants.js
@@ -203,6 +203,7 @@ exports.KIT_CONFIG_KEYS = [
'user_payments',
'dust',
'coin_customizations',
+ 'balance_history_config',
'transaction_limits',
];
@@ -635,10 +636,10 @@ exports.EXCHANGE_PLAN_INTERVAL_TIME = {
boost: 60
};
exports.EXCHANGE_PLAN_PRICE_SOURCE = {
- fiat: ['hollaex', 'oracle', 'binance', 'bitfinex', 'coinbase', 'kraken', 'bybit', 'gateio', 'uniswap'],
- boost: ['hollaex', 'oracle', 'binance', 'bitfinex', 'coinbase', 'kraken', 'bybit', 'gateio', 'uniswap'],
+ fiat: ['hollaex', 'oracle', 'binance', 'bitfinex', 'coinbase', 'kraken', 'bybit', 'gateio', 'okx', 'uniswap'],
+ boost: ['hollaex', 'oracle', 'binance', 'bitfinex', 'coinbase', 'kraken', 'bybit', 'gateio', 'okx', 'uniswap'],
crypto: ['hollaex', 'oracle', 'binance'],
- ALL: [ 'hollaex', 'oracle', 'binance', 'bitfinex', 'coinbase', 'kraken', 'bybit', 'gateio', 'uniswap']
+ ALL: [ 'hollaex', 'oracle', 'binance', 'bitfinex', 'coinbase', 'kraken', 'bybit', 'gateio', 'okx', 'uniswap']
};
@@ -650,6 +651,10 @@ exports.STAKE_SUPPORTED_PLANS = ['fiat', 'boost', 'enterprise'];
//STAKE CONSTANTS END
+//BALANCE HISTORY CONSTANTS START
+exports.BALANCE_HISTORY_SUPPORTED_PLANS = ['fiat', 'boost', 'enterprise'];
+//BALANCE HISTORY CONSTANTS END
+
exports.CUSTOM_CSS = `
.topbar-wrapper img {
content:url('${exports.GET_KIT_CONFIG().logo_image}}');
diff --git a/server/db/migrations/20231215163265-create-balance-history.js b/server/db/migrations/20231215163265-create-balance-history.js
new file mode 100644
index 0000000000..f4fc19fca5
--- /dev/null
+++ b/server/db/migrations/20231215163265-create-balance-history.js
@@ -0,0 +1,48 @@
+'use strict';
+
+const TABLE_NAME = 'BalanceHistories';
+
+module.exports = {
+ up: (queryInterface, Sequelize) => {
+ return queryInterface.createTable(TABLE_NAME, {
+ id: {
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true,
+ type: Sequelize.INTEGER
+ },
+ user_id: {
+ type: Sequelize.INTEGER,
+ onDelete: 'CASCADE',
+ allowNull: false,
+ references: {
+ model: 'Users',
+ key: 'id'
+ }
+ },
+ balance: {
+ type: Sequelize.JSONB,
+ allowNull: false,
+ },
+ total: {
+ type: Sequelize.DOUBLE,
+ allowNull: false,
+ },
+ created_at: {
+ allowNull: false,
+ type: Sequelize.DATE,
+ defaultValue: Sequelize.literal('NOW()')
+ },
+ updated_at: {
+ allowNull: false,
+ type: Sequelize.DATE,
+ defaultValue: Sequelize.literal('NOW()')
+ }
+ },
+ {
+ timestamps: true,
+ underscored: true
+ });
+ },
+ down: (queryInterface) => queryInterface.dropTable(TABLE_NAME)
+};
\ No newline at end of file
diff --git a/server/db/models/balanceHistory.js b/server/db/models/balanceHistory.js
new file mode 100644
index 0000000000..78bbd37896
--- /dev/null
+++ b/server/db/models/balanceHistory.js
@@ -0,0 +1,40 @@
+'use strict';
+
+module.exports = function (sequelize, DataTypes) {
+
+ const BalanceHistory = sequelize.define(
+ 'BalanceHistory',
+ {
+ id: {
+ type: DataTypes.INTEGER,
+ allowNull: false,
+ autoIncrement: true,
+ primaryKey: true,
+ },
+ user_id: {
+ type: DataTypes.INTEGER,
+ onDelete: 'CASCADE',
+ allowNull: false,
+ references: {
+ model: 'Users',
+ key: 'id'
+ }
+ },
+ balance: {
+ type: DataTypes.JSONB,
+ allowNull: false,
+ },
+ total: {
+ type: DataTypes.DOUBLE,
+ allowNull: false,
+ }
+ },
+ {
+ timestamps: true,
+ underscored: true,
+ tableName: 'BalanceHistories'
+ }
+ );
+
+ return BalanceHistory;
+};
diff --git a/server/db/models/index.js b/server/db/models/index.js
index 16055d8064..ebb34a2f2c 100644
--- a/server/db/models/index.js
+++ b/server/db/models/index.js
@@ -50,6 +50,8 @@ model = require(path.join(__dirname, './staker'))(sequelize, Sequelize.DataTypes
db[model.name] = model;
model = require(path.join(__dirname, './transactionLimit'))(sequelize, Sequelize.DataTypes);
db[model.name] = model;
+model = require(path.join(__dirname, './balanceHistory'))(sequelize, Sequelize.DataTypes);
+db[model.name] = model;
Object.keys(db).forEach(function (modelName) {
if ('associate' in db[modelName]) {
diff --git a/server/messages.js b/server/messages.js
index 3b477cc41d..f0efba40a1 100644
--- a/server/messages.js
+++ b/server/messages.js
@@ -191,7 +191,6 @@ exports.PLUGIN_ALREADY_ENABELD = (plugin) => `Plugin ${plugin} already enabled`;
exports.PLUGIN_ALREADY_DISABLED = (plugin) => `Plugin ${plugin} already disabled`;
exports.INVALID_NETWORK = (network, validNetworks) => `Invalid network: ${network}${validNetworks ? `. Valid networks: ${validNetworks}` : ''}`;
exports.NETWORK_REQUIRED = (coin, validNetworks) => `Must specify network for coin: ${coin}${validNetworks ? `. Valid networks: ${validNetworks}` : ''}`;
-exports.AUTH_NOT_MATCHED = 'Auth doesn\'t match';
exports.BROKER_NOT_FOUND = 'Broker pair could not be found';
exports.BROKER_SIZE_EXCEED = 'Size should be between minimum and maximum set size of broker';
exports.BROKER_PAUSED = 'Broker pair is paused';
@@ -200,11 +199,9 @@ exports.BROKER_EXISTS = 'A deal for this symbol alreadys exists';
exports.BROKER_FORMULA_NOT_FOUND = 'Broker formula not found';
exports.SPREAD_MISSING = 'Spread is missing';
exports.REBALANCE_SYMBOL_MISSING = 'Rebalance symbol for hedge account is missing';
-exports.MANUAL_BROKER_CREATE_ERROR = 'Manual broker cannot select an exchange';
exports.DYNAMIC_BROKER_CREATE_ERROR = 'Cannot create a dynamic broker without required fields';
exports.DYNAMIC_BROKER_UNSUPPORTED = 'Selected exchange is not supported by your exchange plan';
exports.DYNAMIC_BROKER_EXCHANGE_PLAN_ERROR = 'Cannot create a dynamic broker with Basic plan';
-exports.EXCHANGE_NOT_FOUND = 'Exchange not found';
exports.SYMBOL_NOT_FOUND = 'Symbol not found';
exports.INVALID_TOKEN_TYPE = 'invalid token type';
exports.NO_AUTH_TOKEN = 'no auth token sent';
@@ -260,4 +257,5 @@ exports.CANNOT_CHANGE_ADMIN_EMAIL = 'Cannot change admin email';
exports.EMAIL_IS_SAME = 'New email cannot be same as the existing one';
exports.EMAIL_EXISTS = 'This email already exists';
exports.CANNOT_CHANGE_DELETED_EMAIL = 'Cannot change deleted email';
-exports.FAILED_GET_QUOTE = 'Failed to get the quote';
\ No newline at end of file
+exports.FAILED_GET_QUOTE = 'Failed to get the quote';
+exports.BALANCE_HISTORY_NOT_ACTIVE = 'This feature is not active on the exchange';
\ No newline at end of file
diff --git a/server/package.json b/server/package.json
index e4490a54c3..d20203346e 100644
--- a/server/package.json
+++ b/server/package.json
@@ -1,5 +1,5 @@
{
- "version": "2.9.5",
+ "version": "2.10.0",
"private": false,
"description": "HollaEx Kit",
"keywords": [
@@ -29,7 +29,7 @@
"express-validator": "6.7.0",
"file-type": "16.5.2",
"flat": "5.0.0",
- "geoip-lite": "1.4.7",
+ "geoip-lite": "1.4.10",
"helmet": "3.12.0",
"hollaex-network-lib": "file:utils/hollaex-network-lib",
"hollaex-tools-lib": "file:utils/hollaex-tools-lib",
diff --git a/server/plugins/job.js b/server/plugins/job.js
index 48470d693e..17142c4e43 100644
--- a/server/plugins/job.js
+++ b/server/plugins/job.js
@@ -108,6 +108,65 @@ const unstakingCheckRunner = () => {
err.message
);
}
+
+
+ loggerPlugin.verbose(
+ '/plugins balance history job start'
+ );
+
+ try {
+ const balanceHistoryModel = toolsLib.database.getModel('balanceHistory');
+ const statusModel = toolsLib.database.getModel('status');
+ const status = await statusModel.findOne({});
+
+ const exchangeCoins = toolsLib.getKitCoins();
+ if (exchangeCoins.length === 0) return;
+ if (!status?.kit?.balance_history_config?.active) return;
+ const native_currency = status?.kit?.balance_history_config?.currency;
+ const conversions = await toolsLib.getAssetsPrices(exchangeCoins, native_currency || 'usdt', 1);
+ const balances = await toolsLib.user.getAllBalancesAdmin({ format: 'all' });
+
+ const userBalances = balances?.data?.reduce((groups, item) => {
+ const group = (groups[item.user_id] || []);
+ group.push(item);
+ groups[item.user_id] = group;
+ return groups;
+ }, {});
+
+ for (const userId of Object.keys(userBalances)) {
+ if (userId === 'undefined') continue;
+
+ let symbols = {};
+
+ (userBalances[userId] || []).forEach(balance => { symbols[balance.symbol] = balance.balance });
+
+ const coins = Object.keys(symbols);
+
+ let total = 0;
+ let history = {};
+ for (const coin of coins) {
+ if (!conversions[coin]) continue;
+ if (conversions[coin] === -1) continue;
+
+ const nativeCurrencyValue = new BigNumber(symbols[coin]).multipliedBy(conversions[coin]).toNumber();
+
+ history[coin] = { original_value: new BigNumber(symbols[coin]).toNumber(), native_currency_value: nativeCurrencyValue };
+ total = new BigNumber(total).plus(nativeCurrencyValue).toNumber();
+ }
+ if (Object.keys(history).length === 0) continue;
+ await balanceHistoryModel.create({
+ user_id: Number(userId),
+ balance: history,
+ total,
+ })
+
+ }
+ } catch (err) {
+ loggerPlugin.error(
+ '/plugin balance history job error:',
+ err.message
+ );
+ }
}, {
scheduled: true,
timezone: getTimezone()
diff --git a/server/tools/dbs/checkConfig.js b/server/tools/dbs/checkConfig.js
index aec6387c51..e6f200e661 100644
--- a/server/tools/dbs/checkConfig.js
+++ b/server/tools/dbs/checkConfig.js
@@ -61,7 +61,8 @@ Status.findOne()
quote: 'xht',
spread: 0
},
- coin_customizations: existingKitConfigurations.coin_customizations || {}
+ coin_customizations: existingKitConfigurations.coin_customizations || {},
+ balance_history_config: existingKitConfigurations.balance_history_config || {}
};
const secrets = {
diff --git a/server/tools/nginx/nginx.conf b/server/tools/nginx/nginx.conf
index 5587e19b85..fd38903bc3 100644
--- a/server/tools/nginx/nginx.conf
+++ b/server/tools/nginx/nginx.conf
@@ -51,7 +51,7 @@ server {
location /v2 {
proxy_pass http://api;
- limit_req zone=api burst=10 nodelay;
+ limit_req zone=api burst=12 nodelay;
limit_req_log_level notice;
limit_req_status 429;
diff --git a/server/utils/hollaex-network-lib/index.js b/server/utils/hollaex-network-lib/index.js
index 4668831420..627b879b28 100644
--- a/server/utils/hollaex-network-lib/index.js
+++ b/server/utils/hollaex-network-lib/index.js
@@ -3499,6 +3499,91 @@ class HollaExNetwork {
return createRequest(verb, `${this.apiUrl}${path}`, headers, { data });
}
+ /**
+ * Locks users available balance
+ * @param {number} user_id - User ID to lock the balance
+ * @param {string} currency - Currency that should be locked
+ * @param {number} amount - The amount to lock in the balance
+ * @param {object} opts - Optional parameters.
+ * @param {object} opts.additionalHeaders - Object storing addtional headers to send with request.
+ * @return {object} Object of the success message.
+ */
+ lockBalance(user_id, currency, amount, opts = {
+ additionalHeaders: null
+ }) {
+ checkKit(this.exchange_id);
+
+ if (!user_id) {
+ return reject(parameterError('user_id', 'cannot be null'));
+ }
+ if (!currency) {
+ return reject(parameterError('currency', 'cannot be null'));
+ }
+ if (!amount) {
+ return reject(parameterError('amount', 'cannot be null'));
+ }
+
+ const verb = 'POST';
+ const path = `${this.baseUrl}/network/${this.exchange_id}/balance/lock`;
+
+ const data = {
+ user_id,
+ currency,
+ amount
+ };
+
+ const headers = generateHeaders(
+ isPlainObject(opts.additionalHeaders) ? { ...this.headers, ...opts.additionalHeaders } : this.headers,
+ this.apiSecret,
+ verb,
+ path,
+ this.apiExpiresAfter,
+ data
+ );
+
+ return createRequest(verb, `${this.apiUrl}${path}`, headers, { data });
+ }
+
+ /**
+ * Unlocks users available balance
+ * @param {number} user_id - User ID to unlock the balance
+ * @param {number} lock_id - The lock ID to unlock
+ * @param {object} opts - Optional parameters.
+ * @param {object} opts.additionalHeaders - Object storing addtional headers to send with request.
+ * @return {object} Object of the success message.
+ */
+ unlockBalance(user_id, lock_id, opts = {
+ additionalHeaders: null
+ }) {
+ checkKit(this.exchange_id);
+
+ if (!user_id) {
+ return reject(parameterError('user_id', 'cannot be null'));
+ }
+ if (!lock_id) {
+ return reject(parameterError('lock_id', 'cannot be null'));
+ }
+
+ const verb = 'POST';
+ const path = `${this.baseUrl}/network/${this.exchange_id}/balance/unlock`;
+
+ const data = {
+ user_id,
+ lock_id
+ };
+
+ const headers = generateHeaders(
+ isPlainObject(opts.additionalHeaders) ? { ...this.headers, ...opts.additionalHeaders } : this.headers,
+ this.apiSecret,
+ verb,
+ path,
+ this.apiExpiresAfter,
+ data
+ );
+
+ return createRequest(verb, `${this.apiUrl}${path}`, headers, { data });
+ }
+
/**
* Connect to websocket
* @param {array} events - Array of events to connect to
diff --git a/server/utils/hollaex-tools-lib/tools/broker.js b/server/utils/hollaex-tools-lib/tools/broker.js
index 12d4013107..89d8f0961c 100644
--- a/server/utils/hollaex-tools-lib/tools/broker.js
+++ b/server/utils/hollaex-tools-lib/tools/broker.js
@@ -5,11 +5,10 @@ const math = require('mathjs');
const ccxt = require('ccxt');
const randomString = require('random-string');
const { SERVER_PATH } = require('../constants');
-const { EXCHANGE_PLAN_INTERVAL_TIME, EXCHANGE_PLAN_PRICE_SOURCE } = require(`${SERVER_PATH}/constants`)
-const { getNodeLib } = require(`${SERVER_PATH}/init`);
+const { EXCHANGE_PLAN_INTERVAL_TIME, EXCHANGE_PLAN_PRICE_SOURCE } = require(`${SERVER_PATH}/constants`);
const { client } = require('./database/redis');
const { getUserByKitId } = require('./user');
-const { validatePair, getKitTier, getKitConfig, getAssetsPrices, getQuickTrades, getKitCoin } = require('./common');
+const { validatePair, getKitConfig, getAssetsPrices, getQuickTrades, getKitCoin } = require('./common');
const { sendEmail } = require('../../../mail');
const { MAILTYPE } = require('../../../mail/strings');
const { verifyBearerTokenPromise } = require('./security');
@@ -21,20 +20,15 @@ const connectedExchanges = {};
const {
- TOKEN_EXPIRED,
- AUTH_NOT_MATCHED,
BROKER_NOT_FOUND,
- BROKER_SIZE_EXCEED,
BROKER_PAUSED,
BROKER_ERROR_DELETE_UNPAUSED,
BROKER_EXISTS,
BROKER_FORMULA_NOT_FOUND,
SPREAD_MISSING,
- MANUAL_BROKER_CREATE_ERROR,
DYNAMIC_BROKER_CREATE_ERROR,
DYNAMIC_BROKER_EXCHANGE_PLAN_ERROR,
DYNAMIC_BROKER_UNSUPPORTED,
- EXCHANGE_NOT_FOUND,
SYMBOL_NOT_FOUND,
UNISWAP_PRICE_NOT_FOUND,
FORMULA_MARKET_PAIR_ERROR,
@@ -61,26 +55,27 @@ const validateBrokerPair = (brokerPair) => {
};
const setExchange = (data) => {
- if (connectedExchanges[data.id]) {
- return connectedExchanges[data.id].exchange
- }
+ if (connectedExchanges[data.id]) {
+ return connectedExchanges[data.id].exchange;
+ }
if (data.exchange === 'bitfinex') data.exchange = 'bitfinex2';
- const exchangeClass = ccxt[data.exchange];
+ const exchangeClass = ccxt[data.exchange];
- const exchange = new exchangeClass({
- timeout: 5000,
- ...(data.api_key && { 'apiKey': data.api_key }),
- ...(data.api_secret && { 'secret': data.api_secret }),
- options: { "defaultType": "spot" }
- })
+ const exchange = new exchangeClass({
+ timeout: 5000,
+ ...(data.api_key && { 'apiKey': data.api_key }),
+ ...(data.api_secret && { 'secret': data.api_secret }),
+ ...(data.password && { 'password': data.password }),
+ options: { 'defaultType': 'spot' }
+ });
- if (data.id) {
- connectedExchanges[data.id] = { exchange, api_key: data.api_key, api_secret: data.api_secret };
- }
+ if (data.id) {
+ connectedExchanges[data.id] = { exchange, api_key: data.api_key, api_secret: data.api_secret, password: data.password };
+ }
- return exchange;
-}
+ return exchange;
+};
const getQuoteDynamicBroker = async (side, broker, user_id = null, orderData) => {
@@ -135,7 +130,7 @@ const getQuoteManualBroker = async (broker, side, user_id = null, orderData) =>
responseObject.expiry = expiryDate;
}
return responseObject;
-}
+};
const calculateSize = (orderData, side, responseObject, symbol) => {
if (orderData == null) {
@@ -167,7 +162,7 @@ const calculateSize = (orderData, side, responseObject, symbol) => {
} else if (receiving_amount != null) {
- const incrementUnit = side === 'buy' ? quoteCointInfo.increment_unit : baseCoinInfo.increment_unit
+ const incrementUnit = side === 'buy' ? quoteCointInfo.increment_unit : baseCoinInfo.increment_unit;
const targetedAmount = side === 'buy' ? receiving_amount * responseObject.price : receiving_amount / responseObject.price;
if (incrementUnit < 1) {
@@ -182,15 +177,15 @@ const calculateSize = (orderData, side, responseObject, symbol) => {
if (`${spending_currency}-${receiving_currency}` === symbol) {
size = spending_amount;
} else {
- size = receiving_amount
+ size = receiving_amount;
}
return { size, spending_amount, receiving_amount };
-}
+};
const calculateFormula = (fn) => {
- return new Function(`return ${fn}`)();
-}
+ return new Function(`return ${fn}`)();
+};
const isFairPriceForBroker = async (broker) => {
if (broker.type !== 'dynamic') return true;
@@ -207,7 +202,7 @@ const isFairPriceForBroker = async (broker) => {
const priceDifferenceTreshold = 10;
if (priceFromOracle !== -1 && percDiff > priceDifferenceTreshold) return false;
else return true;
-}
+};
const calculatePrice = async (side, spread, formula, refresh_interval, brokerId, isOracle = false) => {
const regex = /([a-zA-Z]+(?:_[a-zA-Z]+)+(?:-[a-zA-Z]+))/g;
@@ -245,11 +240,11 @@ const calculatePrice = async (side, spread, formula, refresh_interval, brokerId,
}
if (!ticker) {
- throw new Error(`${exchangePair[0].toUpperCase()} does not have market symbol ${formattedSymbol}`)
+ throw new Error(`${exchangePair[0].toUpperCase()} does not have market symbol ${formattedSymbol}`);
}
- marketPrice = ticker.last
+ marketPrice = ticker.last;
if (refresh_interval)
client.setexAsync(userCachekey, refresh_interval, JSON.stringify(tickers));
} else {
@@ -375,7 +370,7 @@ const testBroker = async (data) => {
return {
buy_price: new BigNumber(price * (1 - (spread / 100))).decimalPlaces(decimalPoint).toNumber(),
sell_price: new BigNumber(price * (1 + (spread / 100))).decimalPlaces(decimalPoint).toNumber()
- }
+ };
} catch (err) {
throw new Error(err);
}
@@ -383,7 +378,7 @@ const testBroker = async (data) => {
};
const testBrokerUniswap = async (data) => {
const { base_coin, spread, quote_coin } = data;
- const UNISWAP_COINS = {}
+ const UNISWAP_COINS = {};
try {
if (!base_coin || !quote_coin || !UNISWAP_COINS[base_coin] || !UNISWAP_COINS[quote_coin]) {
throw new Error(SYMBOL_NOT_FOUND);
@@ -403,18 +398,18 @@ const testBrokerUniswap = async (data) => {
amount: Math.pow(10, UNISWAP_COINS[base_coin].decimals),
side: SwapSide.SELL,
includeDEXS
- })
+ });
if (!priceRoute.destAmount) {
- throw new Error(UNISWAP_PRICE_NOT_FOUND)
+ throw new Error(UNISWAP_PRICE_NOT_FOUND);
}
- const price = math.divide(priceRoute.destAmount,Math.pow(10, UNISWAP_COINS[quote_coin].decimals))
+ const price = math.divide(priceRoute.destAmount,Math.pow(10, UNISWAP_COINS[quote_coin].decimals));
return {
buy_price: price * (1 - (spread / 100)),
sell_price: price * (1 + (spread / 100))
- }
+ };
} catch (err) {
throw new Error(err);
}
@@ -422,10 +417,10 @@ const testBrokerUniswap = async (data) => {
};
const testRebalance = async (data) => {
- const { exchange_id, api_key, api_secret } = data;
+ const { exchange_id, api_key, api_secret, password } = data;
try {
- const exchange = setExchange({ exchange: exchange_id, api_key, api_secret })
+ const exchange = setExchange({ exchange: exchange_id, api_key, api_secret, password });
const userBalance = await exchange.fetchBalance();
return userBalance;
} catch (err) {
@@ -435,7 +430,7 @@ const testRebalance = async (data) => {
};
const reverseTransaction = async (orderData) => {
- const { symbol, side, size, price } = orderData;
+ const { symbol, side, size } = orderData;
const notifyUser = async (data, userId) => {
const user = await getUserByKitId(userId);
sendEmail(
@@ -463,8 +458,9 @@ const reverseTransaction = async (orderData) => {
const exchange = setExchange({
exchange: exchangeKey,
api_key: broker.account[exchangeKey].apiKey,
- api_secret: broker.account[exchangeKey].apiSecret
- })
+ api_secret: broker.account[exchangeKey].apiSecret,
+ password: broker.account[exchangeKey].password
+ });
const formattedRebalancingSymbol = broker.rebalancing_symbol && broker.rebalancing_symbol.split('-').join('/').toUpperCase();
if (exchangeKey === 'bybit') {
@@ -570,12 +566,12 @@ const fetchBrokerPairs = async (attributes) => {
brokers.forEach(broker => {
for (const [key, value] of Object.entries(broker.account || [])) {
value.apiKey = '*****',
- value.apiSecret = '*********'
+ value.apiSecret = '*********';
}
- })
+ });
return brokers;
-}
+};
const updateBrokerPair = async (id, data) => {
const brokerPair = await getModel('broker').findOne({ where: { id } });
@@ -657,7 +653,7 @@ const updateBrokerPair = async (id, data) => {
const fetchTrackedExchangeMarkets = async (exchange) => {
const selectedExchage = setExchange({ id: `${exchange}-broker:fetch-markets`, exchange });
return selectedExchage.fetchMarkets();
-}
+};
const deleteBrokerPair = async (id) => {
const brokerPair = await getModel('broker').findOne({ where: { id } });
diff --git a/server/utils/hollaex-tools-lib/tools/common.js b/server/utils/hollaex-tools-lib/tools/common.js
index 426613d058..956db4f6de 100644
--- a/server/utils/hollaex-tools-lib/tools/common.js
+++ b/server/utils/hollaex-tools-lib/tools/common.js
@@ -24,7 +24,8 @@ const {
USER_META_KEYS,
VALID_USER_META_TYPES,
DOMAIN,
- DEFAULT_FEES
+ DEFAULT_FEES,
+ BALANCE_HISTORY_SUPPORTED_PLANS
} = require(`${SERVER_PATH}/constants`);
const {
COMMUNICATOR_CANNOT_UPDATE,
@@ -278,6 +279,34 @@ const joinKitConfig = (existingKitConfig = {}, newKitConfig = {}) => {
}
}
+ if (newKitConfig.balance_history_config) {
+
+ const exchangeInfo = getKitConfig().info;
+
+ if (!BALANCE_HISTORY_SUPPORTED_PLANS.includes(exchangeInfo.plan))
+ throw new Error('Exchange plan does not support this feature');
+
+ if (!newKitConfig.balance_history_config.hasOwnProperty('currency')) {
+ throw new Error('currency does not exist');
+ }
+
+ if (existingKitConfig?.balance_history_config?.currency && existingKitConfig?.balance_history_config?.currency !== newKitConfig.balance_history_config.currency) {
+ throw new Error('currency cannot be changed');
+ }
+
+ if (existingKitConfig?.balance_history_config?.date_enabled && existingKitConfig?.balance_history_config?.date_enabled !== newKitConfig.balance_history_config.date_enabled) {
+ throw new Error('date cannot be changed');
+ }
+
+ if(!newKitConfig.balance_history_config.hasOwnProperty('active')) {
+ throw new Error('active does not exist');
+ }
+
+ if(!newKitConfig.balance_history_config.hasOwnProperty('date_enabled')) {
+ throw new Error('date enabled does not exist');
+ }
+ }
+
const joinedKitConfig = {};
KIT_CONFIG_KEYS.forEach((key) => {
diff --git a/server/utils/hollaex-tools-lib/tools/order.js b/server/utils/hollaex-tools-lib/tools/order.js
index 2f5c7b58dd..3e592be12b 100644
--- a/server/utils/hollaex-tools-lib/tools/order.js
+++ b/server/utils/hollaex-tools-lib/tools/order.js
@@ -1232,6 +1232,28 @@ const generateOrderFeeData = (userTier, symbol, opts = { discount: 0 }) => {
return feeData;
};
+const createTrade = async (order, opts = { additionalHeaders: null }) => {
+ const { symbol, side, price, size, maker_id, taker_id, maker_fee, taker_fee } = order;
+
+ const maker = await getUserByKitId(maker_id);
+ const taker = await getUserByKitId(taker_id);
+
+ if (!taker || !maker) {
+ throw new Error(USER_NOT_FOUND);
+ }
+
+ return getNodeLib().createBrokerTrade(
+ symbol,
+ side,
+ price,
+ size,
+ maker.network_id,
+ taker.network_id,
+ { maker: maker_fee, taker: taker_fee },
+ opts
+ );
+};
+
module.exports = {
getAllExchangeOrders,
createUserOrderByKitId,
@@ -1260,7 +1282,8 @@ module.exports = {
dustUserBalance,
executeUserOrder,
dustPriceEstimate,
- updateQuickTradeConfig
+ updateQuickTradeConfig,
+ createTrade
// getUserTradesByKitIdStream,
// getUserTradesByNetworkIdStream,
// getAllTradesNetworkStream,
diff --git a/server/utils/hollaex-tools-lib/tools/user.js b/server/utils/hollaex-tools-lib/tools/user.js
index b59a156739..43e64257f0 100644
--- a/server/utils/hollaex-tools-lib/tools/user.js
+++ b/server/utils/hollaex-tools-lib/tools/user.js
@@ -58,7 +58,8 @@ const {
EMAIL_IS_SAME,
EMAIL_EXISTS,
CANNOT_CHANGE_DELETED_EMAIL,
- SERVICE_NOT_SUPPORTED
+ SERVICE_NOT_SUPPORTED,
+ BALANCE_HISTORY_NOT_ACTIVE
} = require(`${SERVER_PATH}/messages`);
const { publisher, client } = require('./database/redis');
const {
@@ -75,11 +76,12 @@ const {
TOKEN_TIME_LONG,
TOKEN_TIME_NORMAL,
VERIFY_STATUS,
- EVENTS_CHANNEL
+ EVENTS_CHANNEL,
+ BALANCE_HISTORY_SUPPORTED_PLANS
} = require(`${SERVER_PATH}/constants`);
const { sendEmail } = require(`${SERVER_PATH}/mail`);
const { MAILTYPE } = require(`${SERVER_PATH}/mail/strings`);
-const { getKitConfig, isValidTierLevel, getKitTier, isDatetime } = require('./common');
+const { getKitConfig, isValidTierLevel, getKitTier, isDatetime, getKitCoins } = require('./common');
const { isValidPassword, createSession } = require('./security');
const { getNodeLib } = require(`${SERVER_PATH}/init`);
const { all, reject } = require('bluebird');
@@ -91,6 +93,7 @@ const uuid = require('uuid/v4');
const { checkCaptcha, validatePassword, verifyOtpBeforeAction } = require('./security');
const geoip = require('geoip-lite');
const moment = require('moment');
+const BigNumber = require('bignumber.js');
let networkIdToKitId = {};
let kitIdToNetworkId = {};
@@ -645,7 +648,16 @@ const getAllUsersAdmin = (opts = {
const pagination = paginationQuery(opts.limit, opts.page);
const timeframe = timeframeQuery(opts.start_date, opts.end_date);
const dob_timeframe = timeframeQuery(dob_start_date, dob_end_date);
- const ordering = orderingQuery(opts.order_by, opts.order);
+
+ let orderBy = 'updated_at';
+ let order = 'desc';
+ if (opts.order_by) {
+ orderBy = opts.order_by;
+ }
+ if (opts.order) {
+ order = opts.order;
+ }
+ const ordering = orderingQuery(orderBy, order);
let query = {
where: {
created_at: timeframe,
@@ -659,12 +671,12 @@ const getAllUsersAdmin = (opts = {
order: [ordering]
};
query.attributes = {
- exclude: ['balance', 'password', 'updated_at']
+ exclude: ['balance', 'password']
};
if (opts.search) {
query.attributes = {
- exclude: ['balance', 'password', 'updated_at']
+ exclude: ['balance', 'password']
};
if (opts.id) {
query.where.id = opts.id;
@@ -1522,7 +1534,7 @@ const getUserAudits = (opts = {
}) => {
const exchangeInfo = getKitConfig().info;
- if(!['fiat', 'boost', 'enterprise'].includes(exchangeInfo.plan)) {
+ if(!BALANCE_HISTORY_SUPPORTED_PLANS.includes(exchangeInfo.plan)) {
throw new Error(SERVICE_NOT_SUPPORTED);
}
@@ -1715,6 +1727,7 @@ const inviteExchangeOperator = (invitingEmail, email, role, opts = {
return getModel('user').findOrCreate({
defaults: {
email,
+ email_verified: true,
password: tempPassword,
...roles,
settings: INITIAL_SETTINGS()
@@ -2342,6 +2355,347 @@ const changeKitUserEmail = async (userId, newEmail, auditInfo) => {
return updatedUser;
};
+const getUserBalanceHistory = (opts = {
+ user_id: null,
+ limit: null,
+ page: null,
+ orderBy: null,
+ order: null,
+ startDate: null,
+ endDate: null,
+ format: null
+}) => {
+
+ const exchangeInfo = getKitConfig().info;
+
+ if(!['fiat', 'boost', 'enterprise'].includes(exchangeInfo.plan)) {
+ throw new Error(SERVICE_NOT_SUPPORTED);
+ }
+
+
+ if(!getKitConfig()?.balance_history_config?.active) { throw new Error(BALANCE_HISTORY_NOT_ACTIVE); }
+
+ const timeframe = timeframeQuery(opts.startDate, opts.endDate);
+ const ordering = orderingQuery(opts.orderBy, opts.order);
+ let options = {
+ where: {
+ created_at: timeframe,
+ ...(opts.user_id && { user_id: opts.user_id }),
+ },
+ order: [ordering]
+ };
+
+
+ if (opts.format) {
+ return dbQuery.fetchAllRecords('balanceHistory', options)
+ .then(async (balance) => {
+ if (opts.format && opts.format === 'csv') {
+ if (balance.data.length === 0) {
+ throw new Error(NO_DATA_FOR_CSV);
+ }
+ const csv = parse(balance.data, Object.keys(balance.data[0]));
+ return csv;
+ } else {
+ return balance;
+ }
+ });
+ }
+ else {
+ return dbQuery.findAndCountAllWithRows('balanceHistory', options)
+ .then(async (balances) => {
+ if(opts.user_id && (moment(opts.startDate).format('LL') !== moment(opts.endDate).subtract(1, 'days').format('LL'))) {
+
+ const nativeCurrency = getKitConfig()?.balance_history_config?.currency || 'usdt';
+
+ const exchangeCoins = getKitCoins();
+ const conversions = await getNodeLib().getOraclePrices(exchangeCoins, {
+ quote: nativeCurrency,
+ amount: 1
+ });
+
+ let symbols = {};
+
+ const { getUserBalanceByKitId } = require('./wallet');
+
+ const balance = await getUserBalanceByKitId(opts.user_id);
+
+ for (const key of Object.keys(balance)) {
+ if (key.includes('balance') && balance[key]) {
+ let symbol = key?.split('_')?.[0];
+ symbols[symbol] = balance[key];
+ }
+ }
+
+
+ const coins = Object.keys(symbols);
+
+ let total = 0;
+ let history = {};
+ for (const coin of coins) {
+ if (!conversions[coin]) continue;
+ if (conversions[coin] === -1) continue;
+
+ const nativeCurrencyValue = new BigNumber(symbols[coin]).multipliedBy(conversions[coin]).toNumber();
+
+ history[coin] = { original_value: new BigNumber(symbols[coin]).toNumber(), native_currency_value: nativeCurrencyValue };
+ total = new BigNumber(total).plus(nativeCurrencyValue).toNumber();
+ }
+
+ balances.count += 1;
+ balances.data.unshift({
+ user_id: Number(opts.user_id),
+ balance: history,
+ total,
+ created_at: new Date()
+ });
+ }
+
+ return balances;
+ });
+ }
+};
+
+
+
+const fetchUserProfitLossInfo = async (user_id) => {
+
+ const exchangeInfo = getKitConfig().info;
+
+ if(!BALANCE_HISTORY_SUPPORTED_PLANS.includes(exchangeInfo.plan)) {
+ throw new Error(SERVICE_NOT_SUPPORTED);
+ }
+
+
+ if(!getKitConfig()?.balance_history_config?.active) { throw new Error(BALANCE_HISTORY_NOT_ACTIVE); }
+
+ const data = await client.getAsync(`${user_id}user-pl-info`);
+ if (data) return JSON.parse(data);
+
+ const { getAllUserTradesByKitId } = require('./order');
+ const { getUserWithdrawalsByKitId, getUserDepositsByKitId } = require('./wallet');
+
+ const balanceHistoryModel = getModel('balanceHistory');
+ // Set it to weeks instead of years
+ const startDate = moment().subtract(1, 'weeks').toDate();
+ const endDate = moment().toDate();
+ const timeframe = timeframeQuery(startDate, endDate);
+ const userTrades = await getAllUserTradesByKitId(user_id, null, null, null, 'timestamp', 'asc', startDate, endDate, 'all');
+
+ const userWithdrawals = await getUserWithdrawalsByKitId(user_id, null, null, null, null, null, null, null, null, 'created_at', 'asc', startDate, endDate, null, null, 'all');
+ const userDeposits = await getUserDepositsByKitId(user_id, null, null, null, null, null, null, null, null, 'created_at', 'asc', startDate, endDate, null, null, 'all');
+ const userBalanceHistory = await balanceHistoryModel.findAll({
+ where: {
+ user_id,
+ created_at: timeframe
+ }
+ });
+
+
+ const nativeCurrency = getKitConfig()?.balance_history_config?.currency || 'usdt';
+
+ const exchangeCoins = getKitCoins();
+ const conversions = await getNodeLib().getOraclePrices(exchangeCoins, {
+ quote: nativeCurrency,
+ amount: 1
+ });
+
+ let symbols = {};
+
+ const { getUserBalanceByKitId } = require('./wallet');
+
+ const balance = await getUserBalanceByKitId(user_id);
+
+ for (const key of Object.keys(balance)) {
+ if (key.includes('balance') && balance[key]) {
+ let symbol = key?.split('_')?.[0];
+ symbols[symbol] = balance[key];
+ }
+ }
+
+
+ const coins = Object.keys(symbols);
+
+ let total = 0;
+ let history = {};
+ for (const coin of coins) {
+ if (!conversions[coin]) continue;
+ if (conversions[coin] === -1) continue;
+
+ const nativeCurrencyValue = new BigNumber(symbols[coin]).multipliedBy(conversions[coin]).toNumber();
+
+ history[coin] = { original_value: new BigNumber(symbols[coin]).toNumber(), native_currency_value: nativeCurrencyValue };
+ total = new BigNumber(total).plus(nativeCurrencyValue).toNumber();
+ }
+ userBalanceHistory.push({
+ user_id: Number(user_id),
+ balance: history,
+ total,
+ created_at: new Date()
+ });
+
+
+
+ const findClosestBalanceRecord = (date) => {
+ return userBalanceHistory.reduce((closestRecord, entry) => {
+ const entryDate = new Date(entry.created_at).getTime();
+ const closestDate = new Date(closestRecord.created_at).getTime();
+ const currentDate = new Date(date).getTime();
+
+ if (Math.abs(currentDate - entryDate) < Math.abs(currentDate - closestDate)) {
+ return entry;
+ }
+
+ return closestRecord;
+ }, userBalanceHistory[0]);
+ };
+
+ const filterByInterval = (data, interval, conditionalDate) => {
+ const dateThreshold = moment();
+
+ switch (interval) {
+ case '1d':
+ dateThreshold.subtract(1, 'day');
+ break;
+ case '7d':
+ dateThreshold.subtract(7, 'day');
+ break;
+ case '1m':
+ dateThreshold.subtract(1, 'month');
+ break;
+ case '6m':
+ dateThreshold.subtract(6, 'months');
+ break;
+ case '1y':
+ dateThreshold.subtract(1, 'year');
+ break;
+ default:
+ return data;
+ }
+
+ return data.filter((entry) => (moment(entry.created_at || entry.timestamp).isSameOrAfter(dateThreshold)) && (conditionalDate ? moment(entry.created_at || entry.timestamp).isAfter(moment(conditionalDate)) : true));
+ };
+
+ const timeIntervals = ['1d', '7d', '1m', '6m', '1y'];
+
+ const results = {};
+
+ for (const interval of timeIntervals) {
+ const filteredBalanceHistory = filterByInterval(userBalanceHistory, interval, null);
+ if (!filteredBalanceHistory[0]) continue;
+ const initialBalances = filteredBalanceHistory[0]?.balance;
+ const initialBalanceDate = filteredBalanceHistory[0]?.created_at;
+ const filteredTrades = filterByInterval(userTrades.data, interval, initialBalanceDate);
+
+ const filteredDeposits = filterByInterval(userDeposits.data, interval, initialBalanceDate);
+ const filteredWithdrawals = filterByInterval(userWithdrawals.data, interval, initialBalanceDate);
+
+ if(!initialBalances) continue;
+
+ const netInflowFromDepositsPerAsset = {};
+ filteredDeposits.forEach((deposit) => {
+ const asset = deposit.currency.toLowerCase();
+ if (!netInflowFromDepositsPerAsset[asset]) {
+ netInflowFromDepositsPerAsset[asset] = 0;
+ }
+ const closestRecord = findClosestBalanceRecord(deposit.created_at);
+
+ if(closestRecord.balance[asset]) {
+ const marketPrice = closestRecord.balance[asset].native_currency_value / closestRecord.balance[asset].original_value;
+ netInflowFromDepositsPerAsset[asset] += deposit.amount * marketPrice;
+ }
+
+ });
+
+ const netInflowFromTradesPerAsset = filteredTrades.reduce((netInflow, trade) => {
+ const asset = trade.symbol.split('-')[0].toLowerCase();
+ const tradeValue = trade.size * trade.price;
+
+ if (!netInflow[asset]) {
+ netInflow[asset] = 0;
+ }
+
+ if (trade.side === 'buy') {
+ netInflow[asset] += tradeValue;
+ } else if (trade.side === 'sell') {
+ netInflow[asset] -= tradeValue;
+ }
+
+ return netInflow;
+ }, {});
+
+ const netOutflowFromWithdrawalsPerAsset = {};
+ filteredWithdrawals.forEach((withdrawal) => {
+ const asset = withdrawal.currency.toLowerCase();
+ if (!netOutflowFromWithdrawalsPerAsset[asset]) {
+ netOutflowFromWithdrawalsPerAsset[asset] = 0;
+ }
+ const closestRecord = findClosestBalanceRecord(withdrawal.created_at);
+ if(closestRecord.balance[asset]) {
+ const marketPrice = closestRecord.balance[asset].native_currency_value / closestRecord.balance[asset].original_value;
+ netOutflowFromWithdrawalsPerAsset[asset] -= withdrawal.amount * marketPrice;
+ }
+
+ });
+
+ const finalBalances = filteredBalanceHistory[filteredBalanceHistory.length - 1].balance;
+
+ results[interval] = {};
+ Object.keys(finalBalances).forEach(async (asset) => {
+ const cumulativePNL =
+ finalBalances[asset].native_currency_value -
+ initialBalances[asset].native_currency_value -
+ (netInflowFromDepositsPerAsset[asset] || 0) -
+ (netInflowFromTradesPerAsset[asset] || 0) -
+ (netOutflowFromWithdrawalsPerAsset[asset] || 0);
+
+
+ const day1Assets = initialBalances[asset].native_currency_value;
+ const inflow = netInflowFromDepositsPerAsset[asset] || 0;
+ const cumulativePNLPercentage =
+ cumulativePNL / (day1Assets + inflow) * 100;
+
+ results[interval][asset] = {
+ cumulativePNL,
+ cumulativePNLPercentage,
+ };
+ });
+ }
+
+ if (results['7d']) {
+ const weightedAverage = (prices, weights) => {
+ const [sum, weightSum] = weights.reduce(
+ (acc, w, i) => {
+ acc[0] = acc[0] + prices[i] * w;
+ acc[1] = acc[1] + w;
+ return acc;
+ },
+ [0, 0]
+ );
+ return sum / weightSum;
+ };
+
+ let total = 0;
+ let percentageValues = [];
+ let prices = [];
+ const assets = Object.keys(results['7d']);
+
+ assets?.forEach(asset => {
+ total += results['7d'][asset].cumulativePNL;
+ if (conversions[asset]) {
+ prices.push(conversions[asset]);
+ percentageValues.push(results['7d'][asset].cumulativePNLPercentage);
+ }
+ });
+ results['7d'].total = total;
+ const weightedPercentage = weightedAverage(percentageValues, prices);
+ results['7d'].totalPercentage = weightedPercentage ? weightedPercentage.toFixed(2) : null;
+ }
+
+ client.setexAsync(`${user_id}user-pl-info`, 3600, JSON.stringify(results));
+
+ return results;
+};
+
module.exports = {
loginUser,
getUserTier,
@@ -2398,6 +2752,8 @@ module.exports = {
getAllBalancesAdmin,
deleteKitUser,
restoreKitUser,
+ getUserBalanceHistory,
+ fetchUserProfitLossInfo,
revokeAllUserSessions,
changeKitUserEmail,
storeVerificationCode,
diff --git a/server/utils/hollaex-tools-lib/tools/wallet.js b/server/utils/hollaex-tools-lib/tools/wallet.js
index 63dbad1a58..07e3d4f8fa 100644
--- a/server/utils/hollaex-tools-lib/tools/wallet.js
+++ b/server/utils/hollaex-tools-lib/tools/wallet.js
@@ -82,14 +82,7 @@ const getWithdrawalFee = (currency, network, amount, level) => {
if (coinConfiguration.withdrawal_fees && coinConfiguration.withdrawal_fees[currency]) {
let value = coinConfiguration.withdrawal_fees[currency].value;
fee_coin = coinConfiguration.withdrawal_fees[currency].symbol;
- if (coinConfiguration.withdrawal_fees[currency].levels && coinConfiguration.withdrawal_fees[currency].levels[level]) {
- value = coinConfiguration.withdrawal_fees[currency].levels[level];
- }
- if (coinConfiguration.withdrawal_fees[currency].type === 'static') {
- fee = value;
- } else {
- fee = amount * value / 100;
- }
+ fee = value;
}
}
@@ -121,7 +114,7 @@ const sendRequestWithdrawalEmail = (user_id, address, amount, currency, opts = {
}) => {
let fee = opts.fee;
let fee_coin = opts.fee_coin;
- let fee_markup
+ let fee_markup;
return verifyOtpBeforeAction(user_id, opts.otpCode)
.then((validOtp) => {
@@ -174,8 +167,8 @@ const withdrawalRequestEmail = (user, data, domain, ip) => {
amount,
fee,
fee_markup,
- fee_coin: (getKitCoin(fee_coin).display_name) ? getKitCoin(fee_coin).display_name : fee_coin,
- currency: (getKitCoin(currency).display_name) ? getKitCoin(currency).display_name : currency,
+ fee_coin: fee_coin,
+ currency: currency,
transaction_id: token,
address,
ip,
@@ -1140,14 +1133,7 @@ const getDepositFee = (currency, network, amount, level) => {
if (deposit_fees && deposit_fees[currency]) {
let value = deposit_fees[currency].value;
fee_coin = deposit_fees[currency].symbol;
- if (deposit_fees[currency].levels && deposit_fees[currency].levels[level]) {
- value = deposit_fees[currency].levels[level];
- }
- if (deposit_fees[currency].type === 'static') {
- fee = value;
- } else {
- fee = amount * value / 100;
- }
+ fee = value;
}
return {
diff --git a/test/Cypress/cypress/integration/Gherkin/5timesLogin.feature b/test/Cypress/cypress/integration/Gherkin/5timesLogin.feature
new file mode 100644
index 0000000000..ce07249ec9
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/5timesLogin.feature
@@ -0,0 +1,21 @@
+Feature: Temporary Account Lockout on Multiple Failed Login Attempts
+
+ In order to secure user accounts from unauthorized access attempts,
+ As a security measure,
+ The Hollaex system should temporarily lock a user's account after multiple failed login attempts.
+
+ Scenario: User enters wrong password multiple times leading to a temporary account lock
+ Given the user keys in the wrong password for the first time
+ And the user keys in the right password for the first time
+ And the user keys in the wrong password for the second time
+ And the user keys in the right password for the second time
+ And the user keys in the wrong password for the third time
+ And the user keys in the right password for the third time
+ And the user keys in the wrong password for the fourth time
+ And the user keys in the right password for the fourth time
+ When the user keys in the wrong password for the fifth time
+ Then the user should see the message "You attempted to login too many times, please wait for a while to try again"
+ When the user attempts to log in with the wrong password after 1 minute
+ Then the user should see the message "You attempted to login too many times, please wait for a while to try again"
+ And the user should wait for 5 minutes
+ Then the user keys in the right password and log in
diff --git a/test/Cypress/cypress/integration/Gherkin/5timesLogin/5timesLogin.js b/test/Cypress/cypress/integration/Gherkin/5timesLogin/5timesLogin.js
new file mode 100644
index 0000000000..056d3f45b2
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/5timesLogin/5timesLogin.js
@@ -0,0 +1,145 @@
+Given('the user keys in the wrong password for the first time', () => {
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type("WrongPassword5?");
+ cy.get('.grecaptcha-logo').should('exist');
+ cy.wait(3000);
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('exist');
+ cy.get('.warning_text').contains('Incorrect credentials.');
+});
+
+And('the user keys in the right password for the first time', () => {
+ cy.wait(5000);
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'));
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('not.exist');
+ cy.contains('Security').click();
+ cy.contains('Login History').click();
+ cy.get(':nth-child(2) > :nth-child(3) > :nth-child(1) > .px-1')
+ .contains('Failed login: 1x');
+ cy.contains("Signout").click();
+});
+
+And('the user keys in the wrong password for the second time', () => {
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type("WrongPassword5?");
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('exist');
+ cy.get('.warning_text').contains('Incorrect credentials. You have 3 more attempts left');
+});
+
+And('the user keys in the right password for the second time', () => {
+ cy.wait(5000);
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'));
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('not.exist');
+ cy.contains('Security').click();
+ cy.contains('Login History').click();
+ cy.get(':nth-child(3) > :nth-child(3) > :nth-child(1) > .px-1')
+ .contains('Failed login: 2x');
+ cy.contains("Signout").click();
+});
+
+And('the user keys in the wrong password for the third time', () => {
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type("WrongPassword5?");
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('exist');
+ cy.get('.warning_text').contains('Incorrect credentials. You have 2 more attempts left');
+});
+
+And('the user keys in the right password for the third time', () => {
+ cy.wait(5000);
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.wait(5000);
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'));
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('not.exist');
+ cy.contains('Security').click();
+ cy.contains('Login History').click();
+ cy.get(':nth-child(4) > :nth-child(3) > :nth-child(1) > .px-1')
+ .contains('Failed login: 3x');
+ cy.contains("Signout").click();
+});
+
+And('the user keys in the wrong password for the fourth time', () => {
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type("WrongPassword5?");
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('exist');
+ cy.get('.warning_text').contains('Incorrect credentials. You have 1 more attempt left');
+});
+
+And('the user keys in the right password for the fourth time', () => {
+ cy.wait(5000);
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'));
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('not.exist');
+ cy.contains('Security').click();
+ cy.contains('Login History').click();
+ cy.get(':nth-child(5) > :nth-child(3) > :nth-child(1) > .px-1')
+ .contains('Failed login: 4x');
+ cy.contains("Signout").click();
+});
+
+When('the user keys in the wrong password for the fifth time', () => {
+ cy.wait(5000);
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type("WrongPassword5?");
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+});
+
+Then('the user should see the message {string}', (blockWarrning) => {
+ cy.get('.warning_text').should('exist');
+ cy.get('.warning_text').contains(blockWarrning);
+});
+
+When('the user attempts to log in with the wrong password after 1 minute', () => {
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type("WrongPassword5?");
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('exist');
+});
+
+And('the user should wait for 5 minutes', () => {
+ cy.wait(300000); // Wait for 5 minutes
+});
+
+Then('the user keys in the right password and log in', () => {
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ cy.get('[name="email"]').clear().type("tester+5times@hollaex.email");
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'));
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('not.exist');
+ cy.contains('Security').click();
+ cy.contains('Login History').click();
+ cy.get(':nth-child(6) > :nth-child(3) > :nth-child(1) > .px-1')
+ .contains('Failed login: 5x');
+});
+
diff --git a/test/Cypress/cypress/integration/Gherkin/accountLevel/accountLevel.js b/test/Cypress/cypress/integration/Gherkin/accountLevel/accountLevel.js
index 0896ea0ed6..b56bfdc6aa 100644
--- a/test/Cypress/cypress/integration/Gherkin/accountLevel/accountLevel.js
+++ b/test/Cypress/cypress/integration/Gherkin/accountLevel/accountLevel.js
@@ -9,12 +9,14 @@ Given ('Admin logged in and find "userlevel@testsae.com"',(userlevelATtestsaeDOT
cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('ADMIN_USER'))
cy.contains('Operator controls').click()
cy.contains('Users').click({force: true})
- cy.get('.ant-input').type(Cypress.env('LEVEL_NAME'))
- cy.get('.ant-btn').click()
+ cy.get(':nth-child(2) > .ant-input').type(Cypress.env('LEVEL_NAME'))
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(5000)
+ cy.get(':nth-child(8) > .ant-btn').click()
})
When ('Choose new level in the range of "1" to "11"',(one,eleven)=>{
@@ -51,9 +53,10 @@ Then ('The user profile page of "userlevel@testsae.com" should present the level
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
+ cy.wait(3000)
cy.get('@tier')
.then(val => {
- cy.get('.trader-account-wrapper > .w-100 > .align-items-center > :nth-child(2) > .summary-block-title')
+ cy.get('.trader-account-wrapper > .w-100 > .d-flex.mb-2 > .edit-wrapper__container > .summary-block-title')
.should('contain',val)
cy.log('second', val)
})
diff --git a/test/Cypress/cypress/integration/Gherkin/addOperator.feature b/test/Cypress/cypress/integration/Gherkin/addOperator.feature
new file mode 100644
index 0000000000..99ef2c951b
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/addOperator.feature
@@ -0,0 +1,29 @@
+Feature: Add Role Feature
+
+As an Admin
+In order to give roles to operators
+I want to assign and change a role to a user and non member user
+
+Scenario: Admin assignes, reassignes, changes and revokes a role to a user
+
+ Given Admin created a new user
+ When Admin gave the new user supprot role
+ Then The new user is in the role table with support role
+ When Admin gave to the same user the support role again
+ Then Error of User is already an operator will come up
+ When Admin changed the new user role to communicator
+ Then The new user is in the role table with communicator role
+ When Admin revoked the role from the new user
+ Then The new user is not in the role table anymore
+ When Admin gives a non-member user a Role
+ Then The new user is in the role table with supervisor role
+
+Scenario: Invtied user login in the email box
+ Given The invited user logged in
+ When The user got the temporary password
+ Then Password is saved
+
+Scenario: The user login as the supervisor
+ Given The user can loged in
+ Then The status is supervisor
+
diff --git a/test/Cypress/cypress/integration/Gherkin/addOperator/addOperator.js b/test/Cypress/cypress/integration/Gherkin/addOperator/addOperator.js
new file mode 100644
index 0000000000..fbac4a439b
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/addOperator/addOperator.js
@@ -0,0 +1,179 @@
+import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
+const randomUsername = Math.random().toString(36).substring(2,6);
+const username = "tester+"+randomUsername+Cypress.env('NEW_USER')
+const randomInvitedUsername = Math.random().toString(36).substring(2,6);
+const invitedUser = "tester+"+randomInvitedUsername+Cypress.env('NEW_USER')
+let selector;
+let selector1;
+let selector2;
+let rowKey;
+
+Given ('Admin created a new user',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.contains('Operator controls').click()
+ cy.contains('Users').click()
+ cy.wait(3000)
+ cy.get('table tbody tr:first') // get the topmost row in the table
+ .then($row => {
+ rowKey = parseInt($row.attr('data-row-key')) + 1; // get the current data-row-key attribute and add one to it
+ cy.log(rowKey)
+ selector = `[data-row-key="${rowKey}"] > :nth-child(5)`; // create a selector for the row with the updated data-row-key attribute
+ selector1 = `[data-row-key="${rowKey}"] > :nth-child(8) > .ant-btn > a`;
+ cy.contains('Add new user').click()
+ cy.get('#addUser_userEmail').clear().type(username)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+
+ cy.get('[type="submit"]').click()
+ cy.wait(4000)
+ cy.get(selector).contains(username); // get the row with the updated data-row-key attribute
+ cy.get(selector1).click()
+ cy.wait(4000)
+ cy.get(':nth-child(2) > .align-items-center > :nth-child(4)').contains(username)
+ cy.get(':nth-child(3) > .about-info-content > :nth-child(2)')
+ .contains('Verified')
+});
+
+ })
+
+When ('Admin gave the new user supprot role',()=>{
+ cy.get('a > .ant-btn').click()
+ cy.wait(4000)
+ cy.get('table tbody tr:first') // get the topmost row in the table
+ .then($row => {
+ ; // get the current data-row-key attribute and add one to it
+ selector = `[data-row-key="${rowKey}"] > :nth-child(1)`; // create a selector for the row with the updated data-row-key attribute
+ selector1 = `[data-row-key="${rowKey}"] > :nth-child(2)`;
+ selector2 = `[data-row-key="${rowKey}"] > :nth-child(3) > .admin-link`;
+ cy.contains('Add operator').click()
+ cy.get('#OperatorRoleFrom_email').type(username)
+ cy.get('#OperatorRoleFrom > :nth-child(2) > .ant-btn').click()
+ cy.get(selector).contains(username)
+})
+})
+Then ('The new user is in the role table with support role',()=>{
+ cy.get(selector1).contains('Support')
+})
+When ('Admin gave to the same user the support role again',()=>{
+ cy.contains('Add operator').click()
+ cy.get('#OperatorRoleFrom_email').type(username)
+ cy.get('#OperatorRoleFrom > :nth-child(2) > .ant-btn').click()
+ cy.get('#OperatorRoleFrom > :nth-child(2) > .ant-btn').click()
+})
+Then ('Error of User is already an operator will come up',()=>{
+cy.get('.ant-message-custom-content').contains('User is already an operator')
+cy.get('.ant-modal-close-x').click()
+})
+When ('Admin changed the new user role to communicator',()=>{
+cy.get(selector2).click()
+cy.get('.ant-select-selection-item').click().type('{downarrow}{downarrow}{enter}')
+cy.get('div.w-100 > .ant-btn').click()
+})
+Then ('The new user is in the role table with communicator role',()=>{
+cy.get(selector1).contains('Communicator')
+})
+When ('Admin revoked the role from the new user',()=>{
+cy.get(selector2).click()
+cy.get('div.mt-2 > .ant-btn').click()
+cy.get('.revoke-btn').click()
+})
+Then ('The new user is not in the role table anymore',()=>{
+cy.contains(username).should('not.exist');
+
+});
+When ('Admin gives a non-member user a Role',()=>{
+ cy.wait(4000)
+ cy.get('table tbody tr:first') // get the topmost row in the table
+ .then($row => {
+ ; // get the current data-row-key attribute and add one to it
+ selector = `[data-row-key="${rowKey+1}"] > :nth-child(1)`; // create a selector for the row with the updated data-row-key attribute
+ selector1 = `[data-row-key="${rowKey+1}"] > :nth-child(2)`;
+ selector2 = `[data-row-key="${rowKey+1}"] > :nth-child(3) > .admin-link`;
+ cy.contains('Add operator').click()
+ cy.get('#OperatorRoleFrom_email').type(invitedUser)
+ cy.get('.ant-select-selection-item').click().type('{downarrow}{downarrow}{downarrow}{downarrow}{enter}')
+ cy.get('#OperatorRoleFrom > :nth-child(2) > .ant-btn').click()
+ cy.get(selector).contains(invitedUser)
+ })
+});
+Then ('The new user is in the role table with supervisor role',()=>{
+ cy.get(selector1).contains('Supervisor')
+ cy.wrap(invitedUser).as('invitedUser');
+
+ cy.fixture('invited').then((invited) => {
+ invited.invitedUser = invitedUser;
+ cy.writeFile('cypress/fixtures/invited.json', invited);
+ });
+});
+Given ('The invited user logged in',()=>{
+ cy.visit(Cypress.env('EMAIL_PAGE'));
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox Operator Invite');
+ cy.fixture('invited').then((invited) => {
+ const newUser = invited.invitedUser;
+ cy.get('.giraffe-emailaddress-link').last().contains(newUser)
+ })
+})
+When ('The user got the temporary password',()=>{
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains("You've been invited as an operator to sandbox with the role of supervisor by user");
+
+ cy.get('@emailBody').then(($body) => {
+ const bodyText = $body.text();
+ const passwordRegex = /Password: ([\w-]+)/;
+ const passwordMatch = bodyText.match(passwordRegex);
+ const tempPassword = passwordMatch && passwordMatch[1];
+ cy.wrap(tempPassword).as('tempPassword');
+ cy.fixture('tempass').then((tempass) => {
+ tempass.password = tempPassword;
+ cy.writeFile('cypress/fixtures/tempass.json', tempass);
+ });
+ });
+})
+Then ('Password is saved',()=>{
+ cy.log('Password is saved')
+})
+
+Given ('The user can loged in',()=>{
+ cy.visit(Cypress.env("LOGIN_PAGE"))
+ cy.fixture('invited').then((invited) => {
+ const newUser = invited.invitedUser;
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(newUser)
+ })
+ cy.fixture('tempass').then((tempass) => {
+ const password = tempass.password;
+ cy.get('[name="password"]').clear().type(password)
+ });
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+})
+ Then ('The status is supervisor',()=>{
+ cy.get('a > .pl-1').click()
+ cy.get('.sub-label').contains('Supervisor')
+
+})
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/adminCreateWallet/adminCreateWallet.js b/test/Cypress/cypress/integration/Gherkin/adminCreateWallet/adminCreateWallet.js
index 3ec13bdcf4..4e7c2e1564 100644
--- a/test/Cypress/cypress/integration/Gherkin/adminCreateWallet/adminCreateWallet.js
+++ b/test/Cypress/cypress/integration/Gherkin/adminCreateWallet/adminCreateWallet.js
@@ -1,9 +1,12 @@
+import jsQR from 'jsqr';
+const chai = require('chai');
+const expect = chai.expect;
import {Given, And, When, Then} from "cypress-cucumber-preprocessor/steps"
const randomUsername = Math.random().toString(36).substring(2,6);
const username = "tester+"+randomUsername+Cypress.env('NEW_USER')
var selector
var selector1
-var XHTAdress
+var XHTAddress
Given ('an admin is logged in',()=>{
@@ -17,7 +20,7 @@ Given ('an admin is logged in',()=>{
When ('Admin create a new user',()=>{
cy.contains('Operator controls').click()
cy.contains('Users').click()
- cy.contains('All Users').click()
+ cy.wait(3000)
cy.get('table tbody tr:first') // get the topmost row in the table
.then($row => {
const rowKey = parseInt($row.attr('data-row-key')) + 1; // get the current data-row
@@ -32,7 +35,7 @@ When ('Admin create a new user',()=>{
cy.get(selector1).click()
});
cy.get(':nth-child(2) > .align-items-center > :nth-child(4)').contains(username)
- cy.get(':nth-child(2) > .about-info-content > :nth-child(2)').contains('Verified')
+ cy.get(':nth-child(3) > .about-info-content > :nth-child(2)').contains('Verified')
})
And ('Admin create a new XHT wallet address for the user',()=>{
@@ -51,7 +54,7 @@ And ('Admin create a new XHT wallet address for the user',()=>{
.invoke('text')
.then((text) => {
const trimmedText = text.trim().replace('eth: ', '');
- XHTAdress = trimmedText
+ XHTAddress = trimmedText
cy.log(trimmedText);
});
})
@@ -68,11 +71,24 @@ When ('the user logs in to Hollaex',()=>{
})
Then ('the user wallet address should be the same as the one created by the admin',()=>{
cy.wait(3000)
- cy.visit("https://sandbox.hollaex.com/wallet/xht")
+ cy.visit(Cypress.env('XHT_LINK'))
cy.get('[href="/wallet/xht/deposit"] > .holla-button').click()
- cy.get('.d-flex > .pointer')
-.should('have.text', XHTAdress);
-})
+ cy.get('.multiple-actions-wrapper > :nth-child(1)').click()
+ cy.get('.blue-link')
+ .should('have.text', XHTAddress);
+ cy.get('canvas').then($canvas => {
+ // Get the image data from the canvas
+ const imageData = $canvas[0].getContext('2d').getImageData(0, 0, $canvas[0].width, $canvas[0].height);
+ // Decode the barcode using jsQR
+ const decodedData = jsQR(imageData.data, imageData.width, imageData.height);
+ // Extract the XHT address from the decoded data
+ const Address = XHTAddress;
+ // Assert that the decoded data matches the XHT address
+ cy.log(decodedData.data)
+ expect(decodedData.data).to.equal(Address);
+ });
+});
+
diff --git a/test/Cypress/cypress/integration/Gherkin/chatbox.feature b/test/Cypress/cypress/integration/Gherkin/chatbox.feature
index 5bed541b4d..c1cdca0d6f 100644
--- a/test/Cypress/cypress/integration/Gherkin/chatbox.feature
+++ b/test/Cypress/cypress/integration/Gherkin/chatbox.feature
@@ -1,4 +1,4 @@
-Feature: Login Feature
+Feature: Chat Feature
As an Admin
In order to check the chatbox
@@ -12,4 +12,5 @@ Scenario: Successful Chatbxot feature
And I should be able to delete a message
And I should be able to ban a user
And I should be able to unban a user
- Then I should be able to make the chatbox invisible
\ No newline at end of file
+ Then I should be able to make the chatbox invisible
+ And I should be able to make the chatbox visible
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/chatbox/chatbox.js b/test/Cypress/cypress/integration/Gherkin/chatbox/chatbox.js
index ca9b966874..e6fe5fe45e 100644
--- a/test/Cypress/cypress/integration/Gherkin/chatbox/chatbox.js
+++ b/test/Cypress/cypress/integration/Gherkin/chatbox/chatbox.js
@@ -1,4 +1,4 @@
-import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
+import {Given, Then, And} from "cypress-cucumber-preprocessor/steps"
const randomTest= Math.random().toString(36).substring(2,6);
Given ('I log in the Hollaex',()=>{
diff --git a/test/Cypress/cypress/integration/Gherkin/createUserByAdmin/createUserByAdmin.js b/test/Cypress/cypress/integration/Gherkin/createUserByAdmin/createUserByAdmin.js
index 98e38abd1d..be79545b27 100644
--- a/test/Cypress/cypress/integration/Gherkin/createUserByAdmin/createUserByAdmin.js
+++ b/test/Cypress/cypress/integration/Gherkin/createUserByAdmin/createUserByAdmin.js
@@ -16,7 +16,7 @@ Given ('I am logged in as an admin',()=>{
When ('I navigate to the Create User page',()=>{
cy.contains('Operator controls').click()
cy.contains('Users').click()
- cy.contains('All Users').click()
+ //cy.contains('All Users').click()
})
Then ('I should see a form for creating a new user',()=>{
@@ -31,12 +31,13 @@ When ('I try to create a user with an existing username',()=>{
})
Then ('I should see an error message of bad',()=>{
- cy.get('.ant-message-notice-content').contains('Bad')
- cy.wait(4000)
+ cy.get('.ant-message-notice-content').contains('User already exists')
+ cy.get('.ant-modal-close-x').click()
})
When ('I try to create a user with an diffrent passwords',()=>{
- cy.get('.user-list-header-wrapper > .ant-btn').click()
+ // cy.get('.user-list-header-wrapper > .ant-btn').click()
+ cy.contains('Add new user').click()
cy.get('#addUser_userEmail').clear().type(username)
cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD')+"123")
@@ -44,8 +45,8 @@ When ('I try to create a user with an diffrent passwords',()=>{
})
Then ('I should see an error message',()=>{
- cy.get('.ant-message-notice-content').contains('Password and confirm password should be same')
- cy.wait(4000)
+ cy.get('.ant-message-notice-content').contains('Password do not match')
+ cy.get('.ant-modal-close-x').click()
})
When ('I fill out the form with valid user information',()=>{
@@ -56,6 +57,7 @@ When ('I fill out the form with valid user information',()=>{
selector = `[data-row-key="${rowKey}"] > :nth-child(5)`; // create a selector for the row with the updated data-row-key attribute
selector1 = `[data-row-key="${rowKey}"] > :nth-child(8) > .ant-btn > a`;
//cy.get('.user-list-header-wrapper > .ant-btn').click()
+ cy.contains('Add new user').click()
cy.get('#addUser_userEmail').clear().type(username)
cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
@@ -70,7 +72,7 @@ Then ('the user should be created successfully',()=>{
})
And ('I should see a success message',()=>{ cy.get(selector1).click()
cy.get(':nth-child(2) > .align-items-center > :nth-child(4)').contains(username)
- cy.get(':nth-child(2) > .about-info-content > :nth-child(2)').contains('Verified')})
+ cy.get(':nth-child(3) > .about-info-content > :nth-child(2)').contains('Verified')})
Given ('I am in the Hollaex login page',()=>{
cy.visit(Cypress.env('LOGIN_PAGE'))
@@ -106,7 +108,7 @@ When ('a non-admin user tries to create a new user',()=>{
cy.contains('Operator controls').click()
cy.contains('Users').click()
- cy.contains('All Users').click()
+ // cy.contains('All Users').click()
cy.contains('Add new user').click()
cy.get('#addUser_userEmail').clear().type(username)
cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
@@ -115,7 +117,7 @@ When ('a non-admin user tries to create a new user',()=>{
})
Then ('they should be denied access',()=>{
- cy.get('.ant-message-notice-content').contains('Forbidden')
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
})
diff --git a/test/Cypress/cypress/integration/Gherkin/emailTemplate.feature b/test/Cypress/cypress/integration/Gherkin/emailTemplate.feature
new file mode 100644
index 0000000000..ca40cf6d7d
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/emailTemplate.feature
@@ -0,0 +1,32 @@
+Feature: Edit Email template
+ As Admin
+ I want to be able to edit the content of a set of predefined emails
+ So that I can customize them for my specific needs
+
+ Scenario: Edit Email Content
+ Given I am logged in as Admin
+ When I navigate to the customize emails section
+ And I select an email type of 'login' to edit
+ Then the email content and title should be displayed, and should be stored
+ And I should be able to make changes to the email content and title
+ When I click on the save button
+ Then the changes should be saved
+ And I should see a confirmation message
+ And the email langs should be updated with my changes from 'فارسی' to 'English'
+
+Scenario: One user loging
+ Given I am on the login page
+ When I enter "tester+loginer@hollaex.email" as my email address
+ Then I should be logged in as 'tester+loginer@hollaex.email'
+
+Scenario: Cheking mailbox
+ Given I logged in my email
+ When I check my email inbox
+ Then I should see the edited email in 'tester+loginer@hollaex.email' inbox
+ And the email content should match the changes I made
+
+Scenario: Restore Email Content
+ Given I am logged in as Admin
+ When I navigate to the customize emails section
+ And I select an email type of 'login' to edit
+ Then the email content and title should be restored to its original state
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/emailTemplate/emailTemplate.js b/test/Cypress/cypress/integration/Gherkin/emailTemplate/emailTemplate.js
new file mode 100644
index 0000000000..189079ebaa
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/emailTemplate/emailTemplate.js
@@ -0,0 +1,225 @@
+
+ Given ('I am logged in as Admin',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ })
+
+ When ('I navigate to the customize emails section',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Email').click({force:true})
+
+ })
+ And ('I select an email type of {string} to edit',(emailType)=>{
+ cy.get(':nth-child(4) > .ant-col > .ant-form-item-control-input > .ant-form-item-control-input-content > .ant-select > .ant-select-selector > .ant-select-selection-item')
+ .click()
+ cy.get('.option-wrapper > .ant-input').clear().type(emailType+"{enter}")
+ cy.get('.email-option > .d-flex > :nth-child(1)').click()
+ cy.wait(2000)
+
+ })
+ Then ('the email content and title should be displayed, and should be stored',()=>{
+ cy.get('#EditEmailForm_format')
+ .invoke('text')
+ .then((text) => {
+ cy.log(text);
+ });
+ cy.get('#EditEmailForm')
+ .scrollIntoView()
+ .find('#EditEmailForm_title')
+ .invoke('val')
+ .then((text) => {
+ cy.log(text);
+ });
+
+ cy.get('#EditEmailForm_format')
+ .invoke('text')
+ .then((text) => {
+ cy.get('#EditEmailForm')
+ .scrollIntoView()
+ .find('#EditEmailForm_title')
+ .invoke('val')
+ .then((titleText) => {
+
+ cy.log(titleText)
+ cy.writeFile('cypress/fixtures/loginEmailContent.json', { content: text, title: titleText });
+ });
+ });
+
+ cy.fixture('loginEmailContent.json').then((expectedContent) => {
+
+ cy.log(expectedContent.content)
+ cy.get('#EditEmailForm_format').invoke('text').then((actualContent) => {
+ if (actualContent !== expectedContent.content) {
+ cy.fail(`Content does not match. Expected: ${expectedContent.content}. Actual: ${actualContent}.`);
+ }
+ })
+ });
+
+ })
+ And ('I should be able to make changes to the email content and title',()=>{
+ cy.get('#EditEmailForm_format').should('be.visible').should('not.be.enabled')
+ cy.get('.d-flex > .anchor').contains('EDIT EMAIL').click()
+ cy.get('#EditEmailForm_format').should('be.visible').should('be.enabled')
+ cy.get(':nth-child(1) > .anchor').contains('CANCEL').click()
+ cy.get('#EditEmailForm_format').should('be.visible').should('not.be.enabled')
+ cy.get('.d-flex > .anchor').contains('EDIT EMAIL').click()
+
+ })
+ When ('I click on the save button',()=>{
+
+ cy.get('#EditEmailForm_format').clear().type("test of body")
+ cy.get('#EditEmailForm_title').clear().type("test of title")
+
+
+ cy.get(':nth-child(10) > .ant-btn').click()
+
+ cy.get(':nth-child(3) > .ant-input').invoke('text').then((actualContent) => {
+ if (actualContent !== "test of body") {
+ cy.fail(`Content does not match. Expected: "test of body". Actual: ${actualContent}.`);
+ }
+
+ });
+ cy.get('.btn-wrapper > :nth-child(2)').click()
+
+ })
+ Then ('the changes should be saved',()=>{})
+ And ('I should see a confirmation message',()=>{
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ })
+ And ('the email langs should be updated with my changes from {string} to {string}',(str1,str2)=>{
+ cy.get(':nth-child(2) > .ant-col > .ant-form-item-control-input > .ant-form-item-control-input-content > .ant-select > .ant-select-selector > .ant-select-selection-item')
+ .click()
+ .type('{downarrow}')
+ .get('.ant-select-item-option-content')
+ .each(($option, index, $options) => {
+ const optionText = $option.text().trim()
+ if (optionText === str1) {
+ cy.wrap($option).click()
+ return false // to exit each loop
+ } else {
+ cy.get(':nth-child(2) > .ant-col > .ant-form-item-control-input > .ant-form-item-control-input-content > .ant-select > .ant-select-selector > .ant-select-selection-item')
+ .type('{downarrow}')
+ }
+ })
+
+ cy.get(':nth-child(2) > .ant-col > .ant-form-item-control-input > .ant-form-item-control-input-content > .ant-select > .ant-select-selector > .ant-select-selection-item')
+ .click()
+ .type('{downarrow}')
+ .get('.ant-select-item-option-content')
+ .each(($option, index, $options) => {
+ const optionText = $option.text().trim()
+ if (optionText === str2) {
+ cy.wrap($option).click()
+ return false // to exit each loop
+ } else {
+ cy.get(':nth-child(2) > .ant-col > .ant-form-item-control-input > .ant-form-item-control-input-content > .ant-select > .ant-select-selector > .ant-select-selection-item')
+ .type('{downarrow}')
+ }
+ })
+
+ })
+
+
+ Given ('I am on the login page',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ })
+ When ('I enter {string} as my email address',(email)=>{
+
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(email)
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ })
+ Then ('I should be logged in as {string}',(email)=>{
+ cy.url().should('contain','account')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain',email)
+ })
+
+ Given ('I logged in my email',()=>{
+ cy.visit(Cypress.env('EMAIL_PAGE'))
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+ })
+ When ('I check my email inbox',()=>{
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+ })
+ Then ('I should see the edited email in {string} inbox',(email)=>{
+ // Verify the email content
+ cy.fixture('loginEmailContent.json').then((expectedContent) => {
+ cy.log(expectedContent.title)
+ cy.get('.preview-title').contains("test of title")
+ })
+ ;
+
+ cy.get('.giraffe-emailaddress-link').last().contains(email);
+
+
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('test of body');
+
+ })
+ And ('the email content should match the changes I made',()=>{
+ //cheking email
+ })
+
+ Then ('the email content and title should be restored to its original state',()=>{
+ cy.get('.d-flex > .anchor').contains('EDIT EMAIL').click()
+
+ cy.fixture('loginEmailContent.json').then((expectedContent) => {
+ let text = expectedContent.content
+ cy.get('#EditEmailForm_format').clear().type(text,{ parseSpecialCharSequences: false })
+ cy.get('#EditEmailForm_title').clear().type(expectedContent.title)
+
+ })
+
+ cy.get(':nth-child(10) > .ant-btn').click()
+ cy.fixture('loginEmailContent.json').then((expectedContent) => {
+ cy.get(':nth-child(3) > .ant-input')
+ .invoke('text').then((actualContent) => {
+ if (actualContent !== expectedContent.content) {
+ cy.fail(`Content does not match. Expected: ${expectedContent.content}. Actual: ${actualContent}.`);
+ }
+ })
+ });
+ cy.get('.btn-wrapper > :nth-child(2)').click()
+
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ })
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test/Cypress/cypress/integration/Gherkin/feature.feature b/test/Cypress/cypress/integration/Gherkin/feature.feature
index 9b3f775723..e3fce808b4 100644
--- a/test/Cypress/cypress/integration/Gherkin/feature.feature
+++ b/test/Cypress/cypress/integration/Gherkin/feature.feature
@@ -10,7 +10,10 @@ Scenario: Successful features changes
When I check all features
And I should be able to enable or disable Pro trade
And I should be able to enable or disable Quick trade
- And I should be able to enable or disable Staking
+ And I should be able to enable or disable Defi Staking
+ And I should be able to enable or disable Cefi Staking
+ And I should be able to enable or disable Fiat Contorols
And I should be able to enable or disable Chat system
- And I should be able to enable or disabl Homepage
+ And I should be able to enable or disable Apps
+ And I should be able to enable or disable Homepage
Then I should be able to disable Chat system
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/feature/feature.js b/test/Cypress/cypress/integration/Gherkin/feature/feature.js
index d8c9c3f72f..a0f3371e17 100644
--- a/test/Cypress/cypress/integration/Gherkin/feature/feature.js
+++ b/test/Cypress/cypress/integration/Gherkin/feature/feature.js
@@ -3,14 +3,32 @@
cy.visit(Cypress.env('LOGIN_PAGE'))
cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type("tech@bitholla.com")
- cy.get('[name="password"]').clear().type("bitholla123")
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
})
- When ('I check all features',()=>{})
+ When ('I check all features',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Features').click({force:true})
+
+ cy.get('#interface-form_pro_trade').check({ force: true})
+ cy.get('#interface-form_quick_trade').check({ force: true})
+ cy.get('#interface-form_quick_trade').check({ force: true})
+ cy.get('#interface-form_ultimate_fiat').check({ force: true})
+ cy.get('#interface-form_chat').check({ force: true})
+ cy.get('#interface-form_home_page').check({ force: true})
+ cy.get('#interface-form_apps').check({ force: true})
+
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
+ .click({ force: true})
+ //cy.get('.ant-message-notice-content').contains('Updated successfully')
+ cy.get('.top-box-menu').click()
+
+ })
And ('I should be able to enable or disable Pro trade',()=>{
cy.contains('Pro trade').should('exist')
@@ -20,8 +38,8 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_pro_trade')
- .should('be.checked').should('be.checked').uncheck()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ .should('be.checked').uncheck()
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
@@ -34,7 +52,7 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_pro_trade')
.should('not.be.checked').check()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
@@ -49,7 +67,7 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_quick_trade')
.should('be.checked').should('be.checked').uncheck()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
@@ -61,25 +79,30 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_quick_trade')
.should('not.be.checked').check()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
})
- And ('I should be able to enable or disable Staking',()=>{
- cy.contains('Stake').should('exist')
+ And ('I should be able to enable or disable Defi Staking',()=>{
+ cy.contains('Stake').should('exist').click()
+ cy.contains('DeFi asset staking')
+ cy.get('.ant-switch').click()
+ cy.contains('Local CeFi Staking Pool')
+ cy.get('.ant-switch').click()
cy.contains('Operator controls').click({force:true})
cy.contains('General').click({force:true})
cy.contains('Features').click({force:true})
cy.get('#interface-form_stake_page')
.should('be.checked').should('be.checked').uncheck()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
- cy.contains('Stake').should('not.exist')
+ cy.contains('Stake').should('exist').click()
+ cy.contains('Local CeFi Staking Pool')
cy.contains('Operator controls').click({force:true})
cy.contains('General').click({force:true})
@@ -87,13 +110,40 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_stake_page')
.should('not.be.checked').check()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
})
+ And ('I should be able to enable or disable Cefi Staking',()=>{
+ cy.contains('Stake').should('exist').click()
+ cy.contains('CeFi Staking')
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+
+ cy.contains('Features').click({force:true})
+ cy.get('#interface-form_cefi_stake')
+ .should('be.checked').uncheck()
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
+ .click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ cy.get('.top-box-menu').click()
+ cy.contains('Stake').should('exist').click()
+ cy.contains('DeFi asset staking')
+ cy.contains('CeFi Staking').should('not.exist')
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+
+ cy.contains('Features').click({force:true})
+ cy.get('#interface-form_cefi_stake')
+ .should('not.be.checked').check()
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
+ .click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ cy.get('.top-box-menu').click()
+ })
And ('I should be able to enable or disable Chat system',()=>{
cy.get('.chat-header-txt').should('exist')
cy.contains('Operator controls').click({force:true})
@@ -103,7 +153,7 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_chat')
.should('be.checked').should('be.checked').uncheck()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
@@ -116,12 +166,38 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_chat')
.should('not.be.checked').check()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.get('.top-box-menu').click()
})
- And ('I should be able to enable or disabl Homepage',()=>{
+ And ('I should be able to enable or disable Apps',()=>{
+
+ cy.contains('Apps').should('exist')
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+
+ cy.contains('Features').click({force:true})
+ cy.get('#interface-form_apps')
+ .should('be.checked').should('be.checked').uncheck()
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
+ .click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ cy.get('.top-box-menu').click()
+ cy.contains('َApps').should('not.exist')
+
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+
+ cy.contains('Features').click({force:true})
+ cy.get('#interface-form_apps')
+ .should('not.be.checked').check()
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
+ .click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ cy.get('.top-box-menu').click()
+ })
+ And ('I should be able to enable or disable Homepage',()=>{
cy.contains('Operator controls').click({force:true})
cy.contains('General').click({force:true})
@@ -129,7 +205,7 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_home_page')
.should('be.checked').should('be.checked').uncheck()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.visit("https://sandbox.hollaex.com")
@@ -142,11 +218,30 @@
cy.contains('Features').click({force:true})
cy.get('#interface-form_home_page')
.should('not.be.checked').check()
- cy.get('#interface-form > :nth-child(2) > .ant-btn')
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
.click()
cy.get('.ant-message-notice-content').contains('Updated successfully')
cy.visit("https://sandbox.hollaex.com")
cy.url().should('eq','https://sandbox.hollaex.com/')
})
- Then ('I should be able to disable Chat system',()=>{})
\ No newline at end of file
+ And ('I should be able to enable or disable Fiat Contorols',()=>{})
+
+ Then ('I should be able to disable Chat system',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Features').click({force:true})
+
+ cy.get('#interface-form_pro_trade').check({ force: true})
+ cy.get('#interface-form_quick_trade').check({ force: true})
+ cy.get('#interface-form_quick_trade').check({ force: true})
+ cy.get('#interface-form_ultimate_fiat').check({ force: true})
+ cy.get('#interface-form_chat').uncheck({ force: true})
+ cy.get('#interface-form_home_page').check({ force: true})
+ cy.get('#interface-form_apps').uncheck({ force: true})
+
+ cy.get('#interface-form > :nth-child(2) > div > .ant-btn')
+ .click({ force: true})
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ cy.get('.top-box-menu').click()
+ })
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/footer.feature b/test/Cypress/cypress/integration/Gherkin/footer.feature
index 63067e4bf3..3de1401c67 100644
--- a/test/Cypress/cypress/integration/Gherkin/footer.feature
+++ b/test/Cypress/cypress/integration/Gherkin/footer.feature
@@ -1,9 +1,9 @@
Feature: Hollaex Footer
As an Admin
-In order to what???
+In order to customize landing page
I want to manipulate the footer
- @waitings
+
Scenario: Successful features changes
Given I log in the Hollaex
@@ -17,5 +17,5 @@ Scenario: Successful features changes
Then The column should be on the Footer page
When I delete a column in Footer Links
Then The column should not be on the Footer page
- When I Add the link
- Then The footer link should work
+ When I hided referral badge
+ Then Referral badge should be hidden
diff --git a/test/Cypress/cypress/integration/Gherkin/footer/footer.js b/test/Cypress/cypress/integration/Gherkin/footer/footer.js
index b89afd1518..e2e4c298db 100644
--- a/test/Cypress/cypress/integration/Gherkin/footer/footer.js
+++ b/test/Cypress/cypress/integration/Gherkin/footer/footer.js
@@ -1,83 +1,154 @@
-var def = "HollaEx Open Exchange Platform"
+var website = 'https://www.hollaex.com/'
const randomTest= Math.random().toString(36).substring(2,6);
- Given ('I log in the Hollaex',()=>{
- cy.visit(Cypress.env('LOGIN_PAGE'))
- cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type("tech@bitholla.com")
- cy.get('[name="password"]').clear().type("bitholla123")
- cy.get('.holla-button').should('be.visible').should('be.enabled').click()
- cy.get('.warning_text').should('not.exist')
+Given ('I log in the Hollaex',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+})
+When ('I change the Exchange description',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Footer').click({force:true})
+ cy.get('.input_field > div > .ant-input').clear().type(randomTest)
+ cy.get(':nth-child(3) > :nth-child(2) > div.w-100 > .ant-btn').click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
})
- When ('I change the Exchange description',()=>{
- cy.contains('Operator controls').click({force:true})
- cy.contains('General').click({force:true})
- cy.contains('Footer').click({force:true})
- cy.get('.input_field > div > .ant-input').clear().type(randomTest)
- cy.get('.description-wrapper > :nth-child(3) > .ant-btn').click()
- cy.get('.ant-message-notice-content').contains('Updated successfully')
- cy.get('.top-box-menu').click()
- cy.get('.footer-txt').contains(randomTest)
+Then ('Footer Exchange description should be changed',()=>{
+ cy.get('.top-box-menu').click()
+ cy.get('.footer-txt').contains(randomTest)
})
+When ('I change Footer small text',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Footer').click({force:true})
+ cy.get(':nth-child(7) > :nth-child(1) > :nth-child(2) > .d-flex > .ant-input').clear()
+ .type(website+"term"+randomTest)
+ cy.get(':nth-child(7) > :nth-child(2) > :nth-child(2) > .d-flex > .ant-input').clear()
+ .type(website+"privacy"+randomTest+'{enter}')
+ cy.wait(3000)
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+ cy.contains('Back to Website').click()
+})
+Then ('Footer small text should be changed',()=>{
+ cy.contains('Terms of Service',{matchCase:false})
+ .invoke('removeAttr', 'target').click({force: true})
+ cy.url().should('contain',website+"term"+randomTest)
+ cy.go('back')
+ cy.get('.app_bar-icon').click()
+ cy.wait(6000)
+ cy.get('.footer-row-bottom > .d-flex').contains('Privacy Policy',{matchCase:false})
+ .invoke('removeAttr', 'target').click({force: true})
+ cy.url().should('contain',website+"privacy"+randomTest)
+ cy.go('back')
+})
- Then ('Footer Exchange description should be changed',()=>{})
-
- When ('I change Footer small text',()=>{
- cy.contains('Operator controls').click({force:true})
- cy.contains('General').click({force:true})
- cy.contains('Footer').click({force:true})
-
- cy.get('.input_field > div > .ant-input').clear().type(def)
- cy.get('.description-wrapper > :nth-child(3) > .ant-btn').click()
-
- def = 'https://hollaex.com'
- cy.get(':nth-child(7) > :nth-child(1) > :nth-child(2) > .d-flex > .ant-input').clear()
- .type("https://www.google.com/search?q=term"+randomTest)
- cy.get(':nth-child(7) > :nth-child(2) > :nth-child(2) > .d-flex > .ant-input').clear()
- .type("https://www.google.com/search?q=privacy"+randomTest)
- cy.get('.description-wrapper > :nth-child(7) > .ant-btn').click()
- cy.get('.ant-message-notice-content').contains('Updated successfully')
- cy.get('.top-box-menu').click()
-
- cy.contains('Terms of Service',{matchCase:false})
- .invoke('removeAttr', 'target').click({force: true})
-
- cy.url().should('contain',"https://www.google.com/")
- cy.contains("term"+randomTest)
- cy.go('back')
-
- cy.contains('Privacy Policy',{matchCase:false})
- .invoke('removeAttr', 'target').click({force: true})
- cy.url().should('contain',"https://www.google.com/")
- cy.contains('privacy'+randomTest)
- cy.go('back')
- })
-
- Then ('Footer small text should be changed',()=>{})
+When ('I change Referral Badge',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Footer').click({force:true})
+ cy.get(':nth-child(11) > form > :nth-child(2) > :nth-child(2) > .d-flex > .ant-input')
+ .clear().type('For white label exchange services - Visit HollaEx.com'+ randomTest)
+ cy.get('form > :nth-child(3) > :nth-child(2) > .d-flex > .ant-input')
+ .clear().type(website+randomTest)
+ cy.get('.ant-checkbox-input')
+ .should('not.be.checked');
+ cy.get(':nth-child(4) > div.w-100 > .ant-btn').click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+})
+Then ('Referral Badge should be changed',()=>{
+ cy.contains('Back to Website').click()
+ cy.get('.app_bar-icon').click()
+ cy.get('.footer-row-bottom > :nth-child(1)')
+ .contains('For white label exchange services - Visit HollaEx.com'+randomTest)
+ .invoke('removeAttr', 'target').click({force: true})
+ cy.url().should('contain',website+randomTest)
+ cy.go('back')
+ cy.wait(3000)
+})
+When ('I add a column in Footer Links',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Footer').click({force:true})
+ cy.wait(4000)
+ cy.get(':nth-child(1) > .input_field > label').last().then((header) => {
+ cy.log(header.text())
+ const str = header.text();
+ const num = str.match(/\d+/)[0];
+ cy.log("We already have " + num +" columns" );
+ })
+ cy.get('.center-content > .ant-btn').click()
+ cy.get('.ant-modal-body > :nth-child(1) > form > .input_field > :nth-child(2) > .d-flex > .ant-input')
+ .clear().type(randomTest)
+ cy.get('.ant-modal-body > :nth-child(1) > form > :nth-child(2) > div.w-100 > .ant-btn').click()
+ cy.get(':nth-child(4) > .admin-link > :nth-child(1)').last().click()
+ cy.get('.ant-modal-body > :nth-child(1) > form > .input_field > :nth-child(2) > .d-flex > .ant-input')
+ .clear().type(randomTest)
+ cy.get('.ant-modal-body > :nth-child(1) > form > :nth-child(2) > div.w-100 > .ant-btn').click()
+ cy.get(':nth-child(3) > .input_field > label').last().contains(randomTest)
+ cy.get(':nth-child(3) > .input_field > :nth-child(2) > .d-flex > .ant-input').last()
+ .clear().type(website+randomTest)
+ cy.get(':nth-child(4) > .admin-link > :nth-child(1)').last().click()
+ cy.get('.ant-modal-body > :nth-child(1) > form > .input_field > :nth-child(2) > .d-flex > .ant-input')
+ .clear().type(randomTest)
+ cy.get('.ant-modal-body > :nth-child(1) > form > :nth-child(2) > div.w-100 > .ant-btn').click()
+ cy.get('.ant-message-custom-content').contains('Link already exist')
+ cy.get('.ant-modal-close-x').click()
+ cy.wait(3000)
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+})
+Then ('The column should be on the Footer page',()=>{
+ cy.contains('Back to Website').click()
+ cy.get('.app_bar-icon').click()
+ cy.get('.footer-links-section--title').last().contains(randomTest)
+ cy.get('.flex-column').last()
+ .contains(randomTest)
+ .invoke('removeAttr', 'target').click({force: true})
+ cy.url().should('contain',website+randomTest)
+ cy.go('back')
+})
+When ('I delete a column in Footer Links',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Footer').click({force:true})
+ cy.get(':nth-child(1) > .input_field > :nth-child(2) > .d-flex > .anticon > svg').last()
+ .click()
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+})
+Then ('The column should not be on the Footer page',()=>{
+ cy.contains('Back to Website').click()
+ cy.get('.app_bar-icon').click()
+ cy.get('.footer-links-section--title')
+ .last()
+ .should('not.contain', randomTest);
- When ('I change Referral Badge',()=>{
- cy.get('.footer-row-bottom > :nth-child(1)').contains('Powered by HollaEx')
- cy.contains('Operator controls').click({force:true})
- cy.contains('General').click({force:true})
- cy.contains('Footer').click({force:true})
- cy.get('.check_field > .ant-checkbox-wrapper > .ant-checkbox > .ant-checkbox-input')
- .should('not.be.checked').check()
- cy.get(':nth-child(11) > form > .ant-btn').click()
- cy.get('.ant-message-notice-content').contains('Updated successfully')
- cy.get('.top-box-menu').click()
- cy.contains('Powered by HollaEx').should('not.exist')
- cy.contains('Operator controls').click({force:true})
- cy.contains('General').click({force:true})
- cy.contains('Footer').click({force:true})
- cy.get('.check_field > .ant-checkbox-wrapper > .ant-checkbox > .ant-checkbox-input')
- .should('be.checked').uncheck()
- cy.get(':nth-child(11) > form > .ant-btn').click()
- cy.get('.ant-message-notice-content').contains('Updated successfully')
- })
- Then ('Referral Badge should be changed',()=>{})
+})
+When ('I hided referral badge',()=>{
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Footer').click({force:true})
+ cy.get('.ant-checkbox-input')
+ .should('not.be.checked').check()
+ cy.get(':nth-child(4) > div.w-100 > .ant-btn').click()
- When ('I add a column in Footer Links',()=>{})
- Then ('The column should be on the Footer page',()=>{})
- When ('I delete a column in Footer Links',()=>{})
- Then ('The column should not be on the Footer page',()=>{})
- When ('I Add the link',()=>{})
- Then ('The footer link should work',()=>{})
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+})
+Then ('Referral badge should be hidden',()=>{
+ cy.contains('Back to Website').click()
+ cy.get('.app_bar-icon').click()
+ cy.get('.footer-row-bottom > :nth-child(1)')
+ .should('not.contain', 'For white label exchange services - Visit HollaEx.com' + randomTest);
+ cy.contains('Operator controls').click({force:true})
+ cy.contains('General').click({force:true})
+ cy.contains('Footer').click({force:true})
+ cy.wait(3000)
+ cy.get('.ant-checkbox-input')
+ .should('be.checked')
+ .uncheck();
+ cy.get(':nth-child(4) > div.w-100 > .ant-btn').click()
+ cy.get('.ant-message-notice-content').contains('Updated successfully')
+})
diff --git a/test/Cypress/cypress/integration/Gherkin/login/login.js b/test/Cypress/cypress/integration/Gherkin/login/login.js
index ac7c10c216..86ebdfbe9e 100644
--- a/test/Cypress/cypress/integration/Gherkin/login/login.js
+++ b/test/Cypress/cypress/integration/Gherkin/login/login.js
@@ -8,23 +8,18 @@ Given ('I am in the Hollaex login page',()=>{
})
When ('I enter credentials Username,Password',()=>{
- const t0 = performance.now();
-
+
cy.get('.holla-button').should('be.visible').should('be.disabled')
cy.get('[name="email"]').clear().type(Cypress.env("USER0"))
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
- const t1 = performance.now();
- cy.log('time')
- cy.log(t1-t0)
+
})
Then ('I should be able to login successfully',()=>{
- const t0 = performance.now();
+
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- const t1 = performance.now();
- cy.log('time')
- cy.log(t1-t0)
+
})
When ('I enter credentials Wrong Username,Password',()=>{
@@ -74,8 +69,17 @@ When ('I enter credentials 2FA enabled Username,Password',()=>{
})
And ('I enter Expired,long,short,String and then true 2FA code',()=>{
+ function shuffleString(str) {
+ const arr = str.split('');
+ for (let i = arr.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [arr[i], arr[j]] = [arr[j], arr[i]];
+ }
+ return arr.join('');
+ }
cy.wait(3000)
- cy.get('.otp_form-wrapper > .icon_title-wrapper > :nth-child(2) > .icon_title-text')
+ cy.get('.otp_form-wrapper > .icon_title-wrapper > :nth-child(2) > :nth-child(1) > .icon_title-text')
+
.contains('Authenticator Code')
const totp = require("totp-generator");
let text = Cypress.env('2FACODE')
@@ -85,21 +89,17 @@ And ('I enter Expired,long,short,String and then true 2FA code',()=>{
cy.wrap(token).as('token')
cy.log(token);
cy.log('second', text)
- // cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
cy.get('.masterInput')
- .clear().type('108249')
- // cy.get('.otp_form-wrapper > form.w-100 > .holla-button').should('not.be.disabled').click()
+ .clear().type(shuffleString(token))
+ //.type('108249')
cy.get('.warning_text').should('contain','Invalid OTP Code')
- //cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
cy.get('.masterInput')
- .clear().type('108294')
+ .clear().type(shuffleString(token))
+ //.type('108294')
cy.get('.warning_text').should('contain','Invalid OTP Code')
cy.get('.masterInput')
.clear().type('ABCDEF')
- cy.get('.warning_text').should('contain','Invalid OTP Code')
- // cy.get('.otp_form-wrapper > form.w-100 > .holla-button').should('not.be.disabled')
- //cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
- cy.get('.masterInput')
+ cy.get('.warning_text').should('contain','Invalid OTP Code')
+ cy.get('.masterInput')
.clear().type(token)
- // cy.get('.otp_form-wrapper > form.w-100 > .holla-button').click()
-})
\ No newline at end of file
+ })
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/logout/logout.js b/test/Cypress/cypress/integration/Gherkin/logout/logout.js
index 67ec8944a5..c799254051 100644
--- a/test/Cypress/cypress/integration/Gherkin/logout/logout.js
+++ b/test/Cypress/cypress/integration/Gherkin/logout/logout.js
@@ -21,7 +21,7 @@ Then ('I should be able to login successfully',()=>{
And ('I should be in {string} page with {string}',(account,username)=>{
cy.url().should('contain','account')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('ADMIN_USER'))
})
diff --git a/test/Cypress/cypress/integration/Gherkin/manageRisk.feature b/test/Cypress/cypress/integration/Gherkin/manageRisk.feature
index 0150f29d7a..396cd010c0 100644
--- a/test/Cypress/cypress/integration/Gherkin/manageRisk.feature
+++ b/test/Cypress/cypress/integration/Gherkin/manageRisk.feature
@@ -3,19 +3,19 @@ Feature: Manage Risk Feature
As a user,
In order to have a threshold for the amount of trading
I want to receive a warning when I exceed the threshold.
-@waiting
-Scenario: Successful Changing the risk percentage for userlevel@testsae.com
+
+Scenario: Successful Changing the risk percentage for teste+riskmanagement@hollaex.email
- Given I log in with the account "userlevel@testsae.com"
+ Given I log in with the account "teste+riskmanagement@hollaex.email"
When I activate Risk Management
And I choose a new percentage in the range of "1" to "101"
- And I make an open order size randomly bigger than the new percentage
- Then I will get "Risky Trade Detected" message with the rate and the total amount
+ And I make an open order size randomly bigger than the new percentage
+ Then I will get "Risky Trade Detected" message with the rate and the total amount
@waiting
Scenario: unsuccessful Changing the risk percentage for userlevel@testsae.com
- Given I log in with the account "userlevel@testsae.com"
+ Given I log in with the account "teste+riskmanagement@hollaex.email"
When I deactivate Risk Management
- And I make an open order size randomly bigger than the new percentage
+ And I make an open order size randomly bigger than the new percentage
Then I will get no "Risky Trade Detected" message
- Then I reset percentage to '90'% and activate Risk Management.
\ No newline at end of file
+ Then I reset percentage to "90"% and activate Risk Management
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/manageRisk/manageRisk.js b/test/Cypress/cypress/integration/Gherkin/manageRisk/manageRisk.js
new file mode 100644
index 0000000000..cc0e271111
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/manageRisk/manageRisk.js
@@ -0,0 +1,120 @@
+Given('I log in with the account {string}', (username) => {
+ // Visiting the login page
+ cy.visit(Cypress.env('LOGIN_PAGE'));
+ // Checking if the login button is visible and disabled
+ cy.get('.holla-button').should('be.visible').should('be.disabled');
+ // Entering the email and password
+ cy.get('[name="email"]').clear().type(username);
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'));
+ // Clicking the login button and checking if no warning is present
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click();
+ cy.get('.warning_text').should('not.exist');
+});
+
+When('I activate Risk Management', () => {
+ // Navigating to Settings and then to Manage Risk
+ cy.contains('Settings').click();
+ cy.wait(5000); // Waiting for settings to load
+ cy.contains('Manage Risk').click();
+ // Turning on risk management if it's off
+ cy.get('.option-text').each((element) => {
+ cy.wrap(element).invoke('text').then((text) => {
+ if (text.toLowerCase().includes('off')) {
+ cy.wrap(element).click();
+ cy.get('.holla-button').click();
+ }
+ });
+ });
+});
+
+And('I choose a new percentage in the range of "1" to "101"', () => {
+ // Extracting the current percentage
+ cy.get('.table_body-row > :nth-child(1) > :nth-child(1)').invoke('text').then((text) => {
+ const regex = /(\d+)%\(ADJUST PERCENTAGE\)/;
+ const match = text.match(regex);
+ if (match && match.length === 2) {
+ let percentage = match[1];
+ cy.log(percentage + "%");
+ cy.wrap(percentage).as('percentage');
+ } else {
+ cy.error("Unable to extract percentage number from the text.");
+ }
+ });
+
+ // Extracting the current value amount in USD Tether
+ cy.get(':nth-child(2) > p').invoke('text').then((text) => {
+ const regex = /Total assets value in USD Tether: (\d+)/;
+ const match = text.match(regex);
+ if (match && match.length === 2) {
+ const amount = match[1];
+ cy.log(amount);
+ cy.wrap(amount).as('amount');
+ } else {
+ cy.error("Unable to extract value amount from the text.");
+ }
+ });
+
+ // Setting new percentage and validating errors
+ cy.get('.ml-2 > .edit-wrapper__container > :nth-child(1)').click();
+ cy.get('@percentage').then((percentage) => {
+ cy.get('.mt-1').contains(percentage + "%");
+ cy.get('.input_field-input').clear().type('100');
+ cy.get('.mt-3 > :nth-child(3)').click();
+ cy.get('.warning_text').contains("Request validation failed: Parameter (data) failed schema validation");
+ cy.get('.input_field-input').clear().type('101');
+ cy.get('.mt-3 > :nth-child(3)').should('be.disabled');
+ cy.get('.ReactModal__Content').click();
+ cy.get('.field-error-content').contains("Value must be 100 or lower.");
+ });
+
+ // Generating a random number between 1 and 99
+ const optedPercentage = Math.floor(Math.random() * 99) + 1;
+ cy.wrap(optedPercentage).as('optedPercentage');
+ cy.get('.input_field-input').clear().type(optedPercentage.toString());
+ cy.get('.mt-3 > :nth-child(3)').click();
+ cy.get('@optedPercentage').then((optedPercentage) => {
+ cy.get('.table_body-row > :nth-child(1)').contains(optedPercentage);
+ cy.get('@amount').then((amount) => {
+ const threshold = (optedPercentage * amount) / 100;
+ cy.log("Threshold:", threshold);
+ cy.wrap(threshold).as('threshold');
+ });
+ });
+});
+
+And('I make an open order size randomly bigger than the new percentage', () => {
+ // Extracting and logging the current open order size
+ cy.get('.table_body-row > :nth-child(2) > span').invoke('text').then((text) => {
+ const floatNumber = parseFloat(text.replace(/[^\d.]/g, ''));
+ cy.log(`Extracted Float Number: ${floatNumber}`);
+
+ // Visiting the trading page and placing an order
+ cy.visit('https://sandbox.hollaex.com/trade/xht-usdt');
+ cy.get(':nth-child(5) > .trade_input-input-wrapper > input').clear().type(0.1);
+ cy.get(':nth-child(6) > .trade_input-input-wrapper > input').clear().type((floatNumber * 10) + 0.1);
+ cy.get('.holla-button').click();
+ });
+});
+
+Then('I will get "Risky Trade Detected" message with the rate and the total amount', () => {
+ // Checking for "Risky Trade Detected" notification
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click();
+ cy.contains('Risky Trade Detected');
+ cy.get('.risky-trade-wrapper > .d-flex > :nth-child(1)').click();
+});
+
+When('I deactivate Risk Management', () => {
+ // Code to deactivate Risk Management
+});
+
+And('I make an open order size randomly bigger than the new percentage', () => {
+ // Code to make an open order size randomly bigger than the new percentage
+});
+
+Then('I will get no "Risky Trade Detected" message', () => {
+ // Code to ensure no "Risky Trade Detected" message is received
+});
+
+Then('I reset percentage to "90"% and activate Risk Management', () => {
+ // Code to reset percentage to "90%" and activate Risk Management
+});
diff --git a/test/Cypress/cypress/integration/Gherkin/notLoginView/notLoginView.js b/test/Cypress/cypress/integration/Gherkin/notLoginView/notLoginView.js
index 688325a531..2ddf201fba 100644
--- a/test/Cypress/cypress/integration/Gherkin/notLoginView/notLoginView.js
+++ b/test/Cypress/cypress/integration/Gherkin/notLoginView/notLoginView.js
@@ -1,4 +1,4 @@
-import {Given, And, When, Then} from "cypress-cucumber-preprocessor/steps"
+import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
const loginButton = '.my-2 > [href="/login"]'
Given ('I am not logged in',()=>{
@@ -51,10 +51,10 @@ When ('I traverse through pages',()=>{
cy.url().should('eq', Cypress.env('LOGIN_PAGE'))
cy.visit(Cypress.env('LANDING_PAGE')+"/trade/xht-usdt")
cy.contains('To start trading you must login')
- cy.get('[href="/login"] > .holla-button').click()
+ cy.get('[href="/login"] > .holla-button').first().click()
cy.url().should('eq', Cypress.env('LOGIN_PAGE'))
- cy.visit(Cypress.env('LANDING_PAGE')+"/quick-trade/xht-usdt")
- cy.get('.pairs').contains('XHT/USDT')
+ cy.visit(Cypress.env('LANDING_PAGE')+"/quick-trade/xrp-usdt")
+ cy.contains('XRP/USDT')
cy.get('.login-container').click()
cy.url().should('eq', Cypress.env('LOGIN_PAGE'))
})
diff --git a/test/Cypress/cypress/integration/Gherkin/onboarding.feature b/test/Cypress/cypress/integration/Gherkin/onboarding.feature
index 863da74e05..55168a0c16 100644
--- a/test/Cypress/cypress/integration/Gherkin/onboarding.feature
+++ b/test/Cypress/cypress/integration/Gherkin/onboarding.feature
@@ -4,24 +4,30 @@ As an Admin
In order to control the user registration in Hollaex
I want to active and deactivate new user registration with/without email
- @waiting
+
Scenario: Disallow a New User to register
- Given I am on admin page
+ Given I am logged in as an admin
And I disallow new sign-ups
- When user fills registration email textbox with "randomaUsername",'@testsae.com',and user clicks "sign up"
- Then "Sign up not available" Should be appeared
- And "randomaUsername" can not log in
-@waiting
+ When I am in Hollaex signup page
+ And I fill up the form
+ Then I get a unsuccess notification
+
Scenario: Allow a New User to register without confirmation
- Given I am on admin page
+ Given I am logged in as an admin
And I allow new sign-ups
And I disable Enable email verification requirement
- When user fills registration email textbox with "randomaUsername",'@testsae.com',and user clicks "sign up"
- Then "randomaUsername" can log in
-
-
+ When A non verified user tries to login
+ Then He can log in
+
+ Scenario: Disallow a New User to register without confirmation
+
+ Given I am logged in as an admin
+ And I Enable email verification requirement
+ When A non verified user tries to login
+ Then He can not log in
+
diff --git a/test/Cypress/cypress/integration/Gherkin/onboarding/onboarding.js b/test/Cypress/cypress/integration/Gherkin/onboarding/onboarding.js
new file mode 100644
index 0000000000..43823a913f
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/onboarding/onboarding.js
@@ -0,0 +1,94 @@
+import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
+const randomUsername = Math.random().toString(36).substring(2,6);
+const username = 'tester+'+randomUsername+Cypress.env('NEW_USER')
+Given ('I am logged in as an admin',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ })
+And ('I disallow new sign-ups',()=>{
+ cy.get('a > .pl-1').click()
+ cy.contains('General').click()
+ cy.contains('Onboarding').click()
+ cy.get('.label-inactive').contains('Off')
+ cy.get('.ant-switch').click()
+ cy.contains('Turn off sign ups')
+ cy.get(':nth-child(3) > .ant-btn').click()
+ cy.get('.ant-message-notice-content').should('contain','Updated successfully')
+ cy.get('.label-inactive').contains('On')
+ cy.get(':nth-child(2) > .sidebar-menu').click()
+
+})
+
+
+When ('I am in Hollaex signup page',()=>{
+
+ cy.writeFile('cypress\\fixtures\\example.json', { name: 'Newuser', email: username })
+ cy.visit(Cypress.env('SIGN_UP_PAGE'))
+})
+
+And ('I fill up the form',()=>{
+
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('.checkfield-input').should('not.be.checked')
+ cy.get('[name="email"]').clear().type(username)
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[name="password_repeat"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.checkfield-input').should('be.enabled').check();
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+
+})
+
+Then ('I get a unsuccess notification',()=>{
+
+ cy.get('.warning_text').contains('Sign up not available')
+})
+
+And ('I allow new sign-ups',()=>{
+ cy.get('a > .pl-1').click()
+ cy.contains('General').click()
+ cy.contains('Onboarding').click()
+ cy.get('.label-inactive').contains('On')
+ cy.get('.ant-switch').click()
+
+ cy.get('.ant-message-notice-content').should('contain','Updated successfully')
+ cy.get('.label-inactive').contains('Off')
+
+
+})
+And ('I disable Enable email verification requirement',()=>{
+ cy.get('#email-verification-form_email_verification_required').uncheck()
+ cy.get('#email-verification-form > :nth-child(2) > div > .ant-btn').click()
+ cy.get('.ant-message-notice-content').should('contain','Updated successfully')
+ cy.get(':nth-child(2) > .sidebar-menu').click()
+})
+
+When ('A non verified user tries to login',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type('tester+off@hollaex.email')
+ cy.get('[name="password"]').clear().type('Holla2021!')
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ //cy.get('.warning_text').should('not.exist')
+})
+Then ('He can log in',()=>{
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .contains('tester+off@hollaex.email')
+})
+
+Then ('He can not log in',()=>{
+ cy.get('.warning_text').contains('User email is not verified')
+})
+
+And ('I Enable email verification requirement',()=>{
+ cy.get('a > .pl-1').click()
+ cy.contains('General').click()
+ cy.contains('Onboarding').click()
+ cy.get('#email-verification-form_email_verification_required').check()
+ cy.get('#email-verification-form > :nth-child(2) > div > .ant-btn').click()
+ cy.get('.ant-message-notice-content').should('contain','Updated successfully')
+ cy.get(':nth-child(2) > .sidebar-menu').click()
+})
diff --git a/test/Cypress/cypress/integration/Gherkin/proTradeLoadPerf.feature b/test/Cypress/cypress/integration/Gherkin/proTradeLoadPerf.feature
new file mode 100644
index 0000000000..9b2a881c82
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/proTradeLoadPerf.feature
@@ -0,0 +1,11 @@
+Feature: Market Load Feature
+
+As a valid customer
+In order to test the performance of trade market loading
+I want to login successfully to HollaeX and check wallet
+
+Scenario: Market load time
+
+ Given I am in the Hollaex login page
+ When I enter credentials Username,Password
+ Then I should be able to view "XHT/USDT" market within 10505.3 ms
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/proTradeLoadPerf/proTradeLoadPerf.js b/test/Cypress/cypress/integration/Gherkin/proTradeLoadPerf/proTradeLoadPerf.js
new file mode 100644
index 0000000000..a35018a300
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/proTradeLoadPerf/proTradeLoadPerf.js
@@ -0,0 +1,49 @@
+import { commandTimings } from 'cypress-timings'
+commandTimings()
+import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
+
+Given ('I am in the Hollaex login page',()=>{
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+
+})
+
+When ('I enter credentials Username,Password',()=>{
+
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env("USER0"))
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+
+
+})
+
+Then ('I should be able to view {string} market within {float} ms',(market,timeRange)=>{
+
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+
+
+ cy.contains('Pro trade').click()
+ cy.contains(market).click()
+ const t0 = performance.now()
+ // Wait for the element to appear with a dynamic identifier
+ cy.get('[id^="tradingview_"]', { timeout: 5000 }) // Adjust the timeout as needed
+ .should('be.visible') // Ensure it's visible
+ .then(($element) => {
+ // Log the dynamically generated identifier
+ const dynamicId = $element.attr('id');
+ cy.log(`Found element with dynamic ID: ${dynamicId}`);
+
+ // Now you can interact with the element as needed
+
+ cy.get(`#${dynamicId}`).click()
+ const t1 = performance.now()
+ const time = t1-t0;
+ cy.log('time')
+ cy.log(t1-t0)
+ cy.log(time,timeRange)
+ expect(time).to.be.lessThan(timeRange)
+
+ });
+})
+
diff --git a/test/Cypress/cypress/integration/Gherkin/promotion/promotion.js b/test/Cypress/cypress/integration/Gherkin/promotion/promotion.js
index 12bf682451..d8d8c60e42 100644
--- a/test/Cypress/cypress/integration/Gherkin/promotion/promotion.js
+++ b/test/Cypress/cypress/integration/Gherkin/promotion/promotion.js
@@ -9,7 +9,7 @@ Given ("Admin logged in",()=>{
cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('ADMIN_USER'))
})
@@ -17,8 +17,10 @@ When ("Discount rate is in the range of {string} to {string} for {string}",(zero
cy.visit('https://sandbox.hollaex.com/admin/user')
cy.wait(3000)
- cy.get('.ant-input').type(Cypress.env('LEVEL_NAME'))
- cy.get('.ant-btn').click()
+ cy.get(':nth-child(2) > .ant-input').type(Cypress.env('LEVEL_NAME'))
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(5000)
+ cy.get(':nth-child(8) > .ant-btn').click()
cy.contains('Adjust').click()
cy.get('#user-discount-form_feeDiscount').clear().type('101')
cy.get('.ant-form-item-explain > div').should('be.exist')
@@ -43,8 +45,8 @@ Then ("The user profile page of {string} should present the discount rate",(user
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
- .should('contain',Cypress.env('LEVEL_NAME').toLowerCase())
+ //cy.get('.trader-account-wrapper > .w-100 > .align-items-center > :nth-child(2) > .summary-block-title')
+ //.should('contain',Cypress.env('LEVEL_NAME').toLowerCase())
cy.get('.trade-account-secondary-txt > .d-flex > :nth-child(2)')
.should('contain',number)
})
diff --git a/test/Cypress/cypress/integration/Gherkin/recaptcha/recaptcha.js b/test/Cypress/cypress/integration/Gherkin/recaptcha/recaptcha.js
index cb68696d04..48698136c8 100644
--- a/test/Cypress/cypress/integration/Gherkin/recaptcha/recaptcha.js
+++ b/test/Cypress/cypress/integration/Gherkin/recaptcha/recaptcha.js
@@ -11,8 +11,24 @@ When ('wait 5 second',()=>{
})
And ('reCaptcha appears',()=>{
-
- cy.get('.grecaptcha-logo > iframe').click(({ force: true })).should('be.visible')
+ cy.get('.grecaptcha-logo > iframe').then($iframe => {
+ const $body = $iframe.contents().find('body');
+ cy.wrap($body).should('be.visible').then(() => {
+ const rect = $iframe[0].getBoundingClientRect();
+ const centerX = rect.left + rect.width / 2;
+ const centerY = rect.top + rect.height / 2;
+
+ // Check if center is within the visible viewport
+ if (centerX >= 0 && centerX <= Cypress.config('viewportWidth') &&
+ centerY >= 0 && centerY <= Cypress.config('viewportHeight')) {
+ // Center is visible, attempt to trigger mouseover
+ cy.wrap($body).trigger('mouseover', { clientX: centerX, clientY: centerY });
+ } else {
+ // Center is not visible
+ cy.log('The center of the iframe is not visible in the viewport');
+ }
+ });
+ });
})
Then ('I should be able to login successfully',()=>{
@@ -21,6 +37,6 @@ Then ('I should be able to login successfully',()=>{
cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('ADMIN_USER'))
})
diff --git a/test/Cypress/cypress/integration/Gherkin/refferal/refferal.js b/test/Cypress/cypress/integration/Gherkin/refferal/refferal.js
index 56347e8529..8a3f2624de 100644
--- a/test/Cypress/cypress/integration/Gherkin/refferal/refferal.js
+++ b/test/Cypress/cypress/integration/Gherkin/refferal/refferal.js
@@ -1,4 +1,6 @@
import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
+const randomUsername = Math.random().toString(36).substring(2,6);
+const username = "tester+"+randomUsername + Cypress.env('NEW_USER')
Given ("I Log in to get the referral code as the referee",()=>{
@@ -8,9 +10,9 @@ Given ("I Log in to get the referral code as the referee",()=>{
cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('ADMIN_USER'))
- cy.get('.trade-account-secondary-txt > :nth-child(2) > .pointer').click()
+ cy.get(':nth-child(4) > .trade-account-link > .pointer').click()
})
And ("Get Referral link {string}",(referralCode)=>{
@@ -25,8 +27,8 @@ And ("Get Referral link {string}",(referralCode)=>{
And ("I have referred X users as the current user",()=>{
- cy.get('.user_refer_info > .edit-wrapper__container').click({force:true})
- cy.get('.user_refer_info > .edit-wrapper__container').invoke('text')
+ cy.get('.user_refer_info > :nth-child(1) > .edit-wrapper__container > :nth-child(1)').click({force:true})
+ cy.get('.user_refer_info > :nth-child(1) > .edit-wrapper__container > :nth-child(1)').invoke('text')
.then(text => {
var fullText = text;
var pattern = /[0-9]+/g;
@@ -39,8 +41,7 @@ And ("I have referred X users as the current user",()=>{
Then ("a new {string} user sign up with {string}",(random,ReferralCode)=>{
- const randomUsername = Math.random().toString(36).substring(2,6);
- const username = randomUsername + Cypress.env('NEW_USER')
+
cy.get('.mr-5').click()
cy.contains('Signout').click()
cy.get("@referralLink").then(val => cy.visit(val))
@@ -64,15 +65,15 @@ Then ("I log in as the referee",()=>{
cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('ADMIN_USER'))
- cy.get('.trade-account-secondary-txt > :nth-child(2) > .pointer').click()
+ cy.get(':nth-child(4) > .trade-account-link > .pointer').click()
})
And ("I have referred Y users as New user",()=>{
- cy.get('.user_refer_info > .edit-wrapper__container').click({force:true})
- cy.get('.user_refer_info > .edit-wrapper__container').invoke('text')
+ cy.get('.user_refer_info > :nth-child(1) > .edit-wrapper__container > :nth-child(1)').click({force:true})
+ cy.get('.user_refer_info > :nth-child(1) > .edit-wrapper__container > :nth-child(1)').invoke('text')
.then(text => {
var fullText = text;
var pattern = /[0-9]+/g;
@@ -90,6 +91,30 @@ When ("Y - X = 1",()=>{
const Y = val
cy.get('@Y')
.then(val1 => expect(Number(val1)).to.equal(Y))
+
+
+
+ })
+ And ("the new {string} user should be in All Successful Referrals list",(random)=>{
+ //cy.contains('view').click()
+ cy.get('.underline-text > .edit-wrapper__container > :nth-child(1)').click()
+ //cy.get(':nth-child(3) > .action_notification-wrapper > .action_notification-text')
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(1) > .d-flex')
+ .contains(username)
+
+ })
+ And ("the new {string} user should be in User profile Referral",(random)=>{
+ cy.get('.my-5 > :nth-child(2)').click()
+ cy.get('a > .pl-1').click()
+ cy.get(':nth-child(4) > .no-link').click()
+ cy.get(':nth-child(1) > .ant-input').type('1')
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(5000)
+ cy.get(':nth-child(8) > .ant-btn').click()
+ //cy.get('.ant-btn').click()
+ cy.get('#rc-tabs-0-tab-referrals').click()
+ cy.get('.ant-table-tbody > :nth-child(1) > :nth-child(2) > :nth-child(1) > .px-2')
+ .contains(username)
})
})
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail.feature b/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail.feature
index 787edde484..5b7248e531 100644
--- a/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail.feature
+++ b/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail.feature
@@ -10,3 +10,13 @@ Scenario: Successful Verification Email Request
When I ask for a resending verification email, I should be redirected to the related page
And I should be able to enter my email successfully
Then I should be redirected contact us page
+Scenario: Successful Email confirmation for new use
+
+ When I confirm the registration by Email
+ Then I am eligible to log in
+
+Scenario: Successfull Login for new user
+
+ Given I am in the Hollaex login page
+ When I enter credentials
+ Then I should be able to login successfully and Verification email should be the same
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail/resendVerificationEmail.js b/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail/resendVerificationEmail.js
index 9683e7dc16..72ae98803e 100644
--- a/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail/resendVerificationEmail.js
+++ b/test/Cypress/cypress/integration/Gherkin/resendVerificationEmail/resendVerificationEmail.js
@@ -1,6 +1,6 @@
import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
const randomUsername = Math.random().toString(36).substring(2,6);
-const username = randomUsername+Cypress.env('NEW_USER')
+const username = "tester+"+randomUsername+Cypress.env('NEW_USER')
Given ('I am on the Hollaex signup page',()=>{
@@ -14,20 +14,32 @@ Given ('I am on the Hollaex signup page',()=>{
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should("not.be.always.exist")
cy.get('.icon_title-text').should('contain', 'Email sent')
+ cy.writeFile('cypress\\fixtures\\example.json', { name: 'Newuser', email: username })
cy.wait(5000)
})
When ('I ask for a resending verification email, I should be redirected to the related page',()=>{
- cy.get('.blue-link').should('contain','Request another one here')
- .click({force:true});
- cy.url().should('include', 'verify')
+ // cy.get('.blue-link')
+ cy.get('.signup_success-wrapper > :nth-child(3) > .blue-link')
+ .should('contain','Request another one here')
+ .click()
+ cy.url().should('include', 'verify')
})
And ('I should be able to enter my email successfully',()=>{
+ cy.get('.holla-button').should('not.be.enabled')
+ cy.get('.input_field-input').clear().type('tester+iamnot@hollaex.email')
+ cy.get('.holla-button').click({force:true});
+ cy.get('.warning_text').contains('A verification code has been sent to your email with the code')
cy.get('.holla-button').should('not.be.enabled')
- cy.get('.input_field-input').type(username)
+ cy.get('.input_field-input').clear().type('tester+5xo7@hollaex.email')
+ cy.get('.holla-button').click({force:true});
+ cy.get('.warning_text').contains('A verification code has been sent to your email with the code if your email is in the system')
+
+ cy.get('.holla-button').should('not.be.enabled')
+ cy.get('.input_field-input').clear().type(username)
cy.get('.holla-button').click({force:true});
cy.contains('Resent Email').should('exist')
})
@@ -37,4 +49,98 @@ Then ('I should be redirected contact us page',()=>{
cy.get('.holla-button').as('contact us').should('be.enabled').click({force:true})
cy.get(':nth-child(3) > .holla-button')
.should('be.enabled')
+})
+
+When ('I confirm the registration by Email',()=>{
+
+ cy.visit(Cypress.env('EMAIL_PAGE'))
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ //prior signup email
+ cy.get(':nth-child(2) > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .contains('sandbox Sign Up')
+ //the last emil
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox Sign Up');
+ cy.fixture('example')
+ .then((user)=>{
+ cy.get('.giraffe-emailaddress-link').last().contains(user.email);
+ })
+
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('You need to confirm your email account by clicking the button below.');
+
+ // Get all the links with "https" protocol from the email body
+ cy.get('@emailBody')
+ .find('a')
+ .then(($links) => {
+ const httpsLinks = [];
+ $links.each((index, link) => {
+ const href = link.href;
+ if (href && href.startsWith('https')) {
+ httpsLinks.push(href);
+ }
+ });
+ cy.wrap(httpsLinks[1]).as('httpsLink');
+ });
+
+ // Log the list of https links
+ cy.get('@httpsLink')
+ .then((httpsLink) => {
+ console.log(httpsLink);
+ cy.forceVisit(httpsLink);
+ });
+ cy.contains('Confirm Sign Up').should('exist')
+ cy.contains('CONFIRM SIGN UP').click()
+ cy.contains('Success').should('exist')
+
+})
+
+Then ('I am eligible to log in',()=>{})
+
+Given ('I am in the Hollaex login page',()=>{
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+})
+
+When ('I enter credentials',()=>{
+
+ cy.fixture('example')
+ .then((user)=>{
+ cy.get('[name="email"]').clear().type(user.email)
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ })
+})
+
+Then ('I should be able to login successfully and Verification email should be the same',()=>{
+
+ cy.fixture('example')
+ .then((user)=>{
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain', user.email )
+ cy.contains('Verification').click()
+ cy.contains('Email').click()
+ cy.get('.information-content').should('contain', user.email )
+ cy.writeFile('cypress\\fixtures\\example.json', {})
+ })
})
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/resetPassword.feature b/test/Cypress/cypress/integration/Gherkin/resetPassword.feature
index 7ebbb4b314..9137000d05 100644
--- a/test/Cypress/cypress/integration/Gherkin/resetPassword.feature
+++ b/test/Cypress/cypress/integration/Gherkin/resetPassword.feature
@@ -4,9 +4,27 @@ As a valid customer
In order to change the password
I want to request to HollaeX
+Scenario: Creating a new user with the current password
+
+ Given Admin creats a user with a random username and the current password
+ Then The new username is stored
+
+Scenario: Changing password, active/deactivating 2FA, and generating API Keys
+
+ Given I log in as the new user name
+
Scenario: Successful Reset Password
Given I am on the Hollaex login page
When I ask for resetting the password, I should be redirected to the related page
And I should be able to enter my email successfully
- Then I should be redirected contact us page
\ No newline at end of file
+ Then I should be redirected contact us page
+
+Scenario: Confirm password change by email confirmation
+
+ When I confirm the transfer by Email
+ Then I receive a successful message
+
+Scenario: the last
+
+ Then I log in as the very new user name
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/resetPassword/resetPassword.js b/test/Cypress/cypress/integration/Gherkin/resetPassword/resetPassword.js
index 46d7d99ba3..4d1f75e560 100644
--- a/test/Cypress/cypress/integration/Gherkin/resetPassword/resetPassword.js
+++ b/test/Cypress/cypress/integration/Gherkin/resetPassword/resetPassword.js
@@ -1,4 +1,50 @@
import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
+const totp = require("totp-generator");
+const randomUsername = Math.random().toString(36).substring(2,6);
+var username = "tester+"+randomUsername+Cypress.env('NEW_USER')
+
+
+Given ('Admin creats a user with a random username and the current password',()=>{
+
+ cy.log(username);
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.contains('Operator controls').click()
+ cy.contains('Users').click()
+ cy.contains('Add new user').click()
+ cy.get('#addUser_userEmail').clear().type(username)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.wait(3000)
+ cy.contains(username)
+
+})
+
+Then ('The new username is stored',()=>{
+
+ cy.writeFile('cypress\\fixtures\\example.json', { name: 'Newuser', email: username })
+})
+
+Given ('I log in as the new user name',()=>{
+
+ cy.fixture('example')
+ .then((user)=>{
+ username = user.email
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(username)
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+})
+})
Given ('I am on the Hollaex login page',()=>{
@@ -17,7 +63,7 @@ When ('I ask for resetting the password, I should be redirected to the related p
And ('I should be able to enter my email successfully',()=>{
cy.get('.holla-button').should('not.be.enabled')
- cy.get('.input_field-input').type(Cypress.env('ADMIN_USER'))
+ cy.get('.input_field-input').type(username)
cy.get('.holla-button').should('be.enabled').click()
})
@@ -28,3 +74,102 @@ Then ('I should be redirected contact us page',()=>{
.should('be.enabled')
})
+When ('I confirm the transfer by Email',()=>{
+ cy.visit(Cypress.env('EMAIL_PAGE'));
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox Reset Password Request');
+ cy.fixture('example')
+ .then((user)=>{
+ cy.get('.giraffe-emailaddress-link').last().contains(user.email);
+ })
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('You have made a request to reset the password for your account.');
+
+ // Get all the links with "https" protocol from the email body
+ cy.get('@emailBody')
+ .find('a')
+ .then(($links) => {
+ const httpsLinks = [];
+ $links.each((index, link) => {
+ const href = link.href;
+ if (href && href.startsWith('https')) {
+ httpsLinks.push(href);
+ }
+ });
+ cy.wrap(httpsLinks[1]).as('httpsLink');
+ });
+
+ // Log the list of https links
+ cy.get('@httpsLink')
+ .then((httpsLink) => {
+ console.log(httpsLink);
+ cy.forceVisit(httpsLink);
+ });
+
+
+})
+Then ('I receive a successful message',()=>{
+
+ cy.contains('Set new password').should('exist')
+ cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('weakPassword')
+ cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('weakPassword')
+ cy.contains('Invalid password. It has to contain at least 8 characters, at least one digit and one character.')
+ cy.get('.holla-button').should('be.disabled')
+
+ cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('newHolla2021!')
+ cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('diffHolla2021!')
+ cy.contains("Password don't match")
+ cy.get('.holla-button').should('be.disabled')
+
+ cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('newHolla2021!')
+ cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('newHolla2021!')
+ cy.get('.holla-button').should('be.enabled').click()
+ cy.contains('Success')
+ cy.get('.holla-button').click()
+})
+
+Then ('I log in as the very new user name',()=>{
+
+ cy.fixture('example')
+ .then((user)=>{
+ username = user.email
+
+
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(username)
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').contains('Incorrect credentials.')
+ cy.wait(5000)
+ cy.get('[name="password"]').clear().type('newHolla2021!')
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .contains(username)
+})
+})
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/revokeSession.feature b/test/Cypress/cypress/integration/Gherkin/revokeSession.feature
new file mode 100644
index 0000000000..4a8700c210
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/revokeSession.feature
@@ -0,0 +1,14 @@
+Feature: Revoking session Feature
+
+As a valid customer
+In order prevent other from accessing the system without verifying their credentials
+I want to revoke its current session successfully
+
+Scenario: Successfull Revoke
+
+ Given I am in the Hollaex login page
+ When I enter credentials "Username","Password"
+ Then I should be able to login successfully
+ And I should be in "Account" page with "Username"
+ Then I should be able to revoke ny current session successfully
+
diff --git a/test/Cypress/cypress/integration/Gherkin/revokeSession/revokeSession.js b/test/Cypress/cypress/integration/Gherkin/revokeSession/revokeSession.js
new file mode 100644
index 0000000000..a977dd89cb
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/revokeSession/revokeSession.js
@@ -0,0 +1,103 @@
+import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
+
+Given ('I am in the Hollaex login page',()=>{
+
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+})
+
+When ('I enter credentials {string},{string}',(username,password)=>{
+
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+})
+
+Then ('I should be able to login successfully',()=>{
+
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+})
+
+And ('I should be in {string} page with {string}',(account,username)=>{
+ const currentDate = new Date();
+ var timestampSaved = currentDate.toLocaleString('en-US', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ })
+
+ cy.log(timestampSaved);
+
+
+
+
+ cy.url().should('contain','account')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain',Cypress.env('ADMIN_USER'))
+
+ cy.contains('Security').click()
+ cy.contains('Sessions').click()
+ //cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)')
+ // Assuming you have saved the timestamp in a variable named 'savedTimestamp'
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)').invoke('text').then((text) => {
+ var timestampSystem = new Date(text).toLocaleString('en-US',{
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit'
+ });
+ cy.log(timestampSystem)
+
+
+ const toleranceSeconds = 5; // Allow a difference of up to 5 seconds
+ cy.log(timestampSaved,timestampSystem)
+ const timestamp1 = new Date(timestampSaved);
+const timestamp2 = new Date(timestampSystem);
+
+const year1 = timestamp1.getFullYear();
+const year2 = timestamp2.getFullYear();
+
+const month1 = timestamp1.getMonth();
+const month2 = timestamp2.getMonth();
+
+const hour1 = timestamp1.getHours();
+const hour2 = timestamp2.getHours();
+
+const minute1 = timestamp1.getMinutes();
+const minute2 = timestamp2.getMinutes();
+
+expect(year1).to.equal(year2);
+expect(month1).to.equal(month2);
+expect(hour1).to.equal(hour2);
+expect(minute1).to.equal(minute2);
+
+ var diffInSeconds = Math.abs(timestamp1.getSeconds()- timestamp2.getSeconds()) / 1000; // Calculate the difference in seconds
+
+ cy.log(diffInSeconds.toString());
+ if (diffInSeconds <= toleranceSeconds) {
+ cy.log('Timestamps are close together.');
+ } else {
+ cy.log('Timestamps are not close together.');
+ }
+});
+})
+
+Then ('I should be able to revoke ny current session successfully',()=>{
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)').click()
+ cy.get(':nth-child(1) > .text-center > .d-flex > .pointer').click()
+ cy.get('.edit-wrapper__container > .font-weight-bold')
+ .contains('Are you sure you want to revoke and logout of this session?')
+ cy.get(':nth-child(3) > .holla-button').click()
+ cy.get(':nth-child(3) > .app-menu-bar-content-item > .edit-wrapper__container > :nth-child(1)').click()
+ cy.contains('XHT/USDT').click()
+ cy.get('.notification-content-information')
+ .contains('Your session is expired. Please login again.')
+ cy.get('.notification-content-wrapper > .holla-button').click()
+ cy.url().should('include', '/login');
+})
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/roles.feature b/test/Cypress/cypress/integration/Gherkin/roles.feature
index 9f1e5fa05f..86851be189 100644
--- a/test/Cypress/cypress/integration/Gherkin/roles.feature
+++ b/test/Cypress/cypress/integration/Gherkin/roles.feature
@@ -8,7 +8,7 @@ Scenario: COMMUNICATOR
Given I am in the Hollaex login page
When I enter credentials "communicator","Password"
- Then I should be able to login successfully as "communicator@testsae.com"
+ Then I should be able to login successfully as "tester+communicator@hollaex.email"
And I have title of 'Communicator'
Then I must be able to do Communicators tasks
@@ -16,23 +16,23 @@ Scenario: KYC
Given I am in the Hollaex login page
When I enter credentials "kyc","Password"
- Then I should be able to login successfully as "kyc@testsae.com"
+ Then I should be able to login successfully as "tester+kyc@hollaex.email"
And I have title of 'KYC'
Then I must be able to do KYCs tasks
-Scenario: SUPERVISOR
-
- Given I am in the Hollaex login page
- When I enter credentials "supervisor","Password"
- Then I should be able to login successfully as "supervisor@testsae.com"
- And I have title of "Supervisor"
- Then I must be able to do Supervisors tasks
Scenario: SUPPORT
Given I am in the Hollaex login page
When I enter credentials "support","Password"
- Then I should be able to login successfully as "support@testsae.com"
+ Then I should be able to login successfully as "tester+support@hollaex.email"
And I have title of "Support"
Then I must be able to do Supports tasks
-
+
+Scenario: SUPERVISOR
+
+ Given I am in the Hollaex login page
+ When I enter credentials "supervisor","Password"
+ Then I should be able to login successfully as "tester+supervisor@hollaex.email"
+ And I have title of "Supervisor"
+ Then I must be able to do Supervisors tasks
diff --git a/test/Cypress/cypress/integration/Gherkin/roles/roles.js b/test/Cypress/cypress/integration/Gherkin/roles/roles.js
index 65222cb81e..bab3e6a0a1 100644
--- a/test/Cypress/cypress/integration/Gherkin/roles/roles.js
+++ b/test/Cypress/cypress/integration/Gherkin/roles/roles.js
@@ -7,7 +7,7 @@ Given ('I am in the Hollaex login page',()=>{
When ('I enter credentials {string},{string}',(username,password)=>{
cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(username+Cypress.env('NEW_USER'))
+ cy.get('[name="email"]').clear().type('tester+'+username+Cypress.env('NEW_USER'))
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
@@ -15,7 +15,7 @@ When ('I enter credentials {string},{string}',(username,password)=>{
Then ('I should be able to login successfully as {string}',(username)=>{
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',username)
})
@@ -26,66 +26,22 @@ And ('I have title of {string}',(role)=>{
})
Then ('I must be able to do Communicators tasks',()=>{
-
cy.contains('Users').click({force: true})
- cy.contains('All Users').click({force: true})
- cy.get('#rc-tabs-0-panel-users > .app_container-content > :nth-child(1) > :nth-child(1)').
- should('contain','Forbidden')
-
- cy.contains('Assets').click({force: true})
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
-
- cy.get('.green-btn').click()
- cy.contains('Add your asset').click({force: true})
- cy.get(':nth-child(1) > .ant-radio > .ant-radio-inner').click({force: true})
- cy.contains('Next').click()
- cy.contains('Assets').click({force: true})
-
- cy.get('.ant-radio-group > :nth-child(1) > :nth-child(2)').click({force: true})
- cy.contains('Next').click()
- cy.get('#AssetConfigForm_contract').type('123')
- cy.contains('Next').click()
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
- cy.contains('Summary').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Deposits').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Withdrawals').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Markets').click({force: true})
- //cy.get('.ant-message-notice-content').should('contain','Access denied:')
-
- // cy.contains('OrderBook').click({force: true})
- // cy.contains('Create/add market').click({force: true})
- // cy.contains('Create a new market').click({force: true})
- // cy.get('.footer-info > a').click({force: true})
- // cy.get('.app_container-content > :nth-child(1) > .ant-alert-description')
- // .should('contain','Access denied:')
-
- cy.contains('Tiers').click({force: true})
- cy.contains('Adjust fees').click({force: true})
- cy.get(':nth-child(1) > :nth-child(2) > .ant-input-number > .ant-input-number-input-wrap > .ant-input-number-input')
- .type('{upArrow}{upArrow}{upArrow}')
- cy.contains('Confirm').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
- cy.contains('Roles').click({force: true})
- cy.contains('Add operator').click({force: true})
- cy.get('#OperatorRoleFrom_email').type('support#testsae.com')
- cy.contains('Save').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click().wait(5000)
-
+ cy.contains('-Access denied: User is not authorized to access this endpoint-')
+ cy.get(':nth-child(1) > .ant-input').clear().type('1')
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.get('.ant-empty-description').contains('No Data')
+ cy.contains('Add new user').click()
+ const randomUsername = Math.random().toString(36).substring(2,6);
+ const Rusername = "tester+"+randomUsername+Cypress.env('NEW_USER')
+ cy.get('#addUser_userEmail').clear().type(Rusername)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.get('.ant-message-notice-content')
+ .contains("Access denied: User is not authorized to access this endpoint")
+
cy.contains('Chat').click({force: true})
- cy.reload()
cy.get('.label-inactive').invoke('text')
.then(text => {
cy.log('second', text)
@@ -95,66 +51,80 @@ Then ('I must be able to do Communicators tasks',()=>{
cy.get('.ant-input').type(randomTest)
cy.contains('SEND').click({force: true})
cy.contains(randomTest).should('exist')
-
-// cy.contains('Cc fee settlement').click({force: true})
-// cy.get('.ant-message-notice-content')
-// .should('contain','Access denied:')
-
- cy.contains('Announcements').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
-
- cy.contains('Automatic tier upgrade').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
+
})
Then ('I must be able to do KYCs tasks',()=>{
cy.contains('Users').click({force: true})
- cy.get('.ant-input-number-input').type('191')
- cy.get('.ant-btn').click()
+ cy.contains('Add new user').click()
+ const randomUsername = Math.random().toString(36).substring(2,6);
+ const Rusername = "tester+"+randomUsername+Cypress.env('NEW_USER')
+ cy.get('#addUser_userEmail').clear().type(Rusername)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.get('.ant-message-notice-content')
+ .contains("Access denied: User is not authorized to access this endpoint")
+ cy.get('.mr-5').click()
+ cy.reload()
+ cy.get(':nth-child(1) > .ant-input').clear().type('191')
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(5000)
+ cy.get(':nth-child(8) > .ant-btn').click()
+
+ cy.get('.about-notes-content > .d-flex > .green-btn').click()
+ const randomNote= Math.random().toString(36).substring(2,6);
+ cy.get('.ant-input').clear().type(randomNote)
+ cy.get('div.w-100 > .ant-btn').click()
+ cy.get('.ant-notification-notice').contains('Success')
+ cy.get('.about-notes-text').contains(randomNote)
cy.contains('Delete').click({force: true})
cy.contains('Are you sure want to delete this?').should('exist')
cy.contains('OK').click({force: true})
- //Delete does not work
+ cy.wait(3000)
+ cy.contains(randomNote).should('not.exist');
+
cy.contains('Edit').click({force: true})
- const randomNote= Math.random().toString(36).substring(2,6);
- cy.get('.ant-input').clear().type(randomNote)
+ const randomNoteForEdit= Math.random().toString(36).substring(2,6);
+ cy.get('#number').clear().type(randomNoteForEdit)
cy.contains('Submit').click({force: true})
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
- //cy.get('.about-notes-text').should('contain',randomNote)
+ cy.get('.ant-message-notice-content').contains('Data saved successfully')
+ //cy.get('.ant-modal-close-x').click()
+
- cy.get(':nth-child(1) > .about-info-content > .info-link').as('Adjust').click({force: true})
+ cy.contains('Adjust').click({force: true})
+ // cy.wait(4000)
cy.get('#user-discount-form_feeDiscount')
- .type('{upArrow}{upArrow}{upArrow}')
+ .type('{upArrow}{upArrow}{upArrow}').wait(3000).type('9').type('{backspace}')//.type('{enter}')
cy.contains('Next').click()
- cy.get('.button-wrapper > :nth-child(2)').as('Apply').click({force: true})
+ cy.get('.button-wrapper > :nth-child(2)').as('Apply').click()
+ cy.get('.button-wrapper > :nth-child(2)').as('Apply').click()
cy.get('.ant-message-notice-content').should('contain','Access denied:')
cy.reload()
- // cy.get(':nth-child(6) > .ant-modal-root > .ant-modal-wrap > .ant-modal > .ant-modal-content > .ant-modal-close > .ant-modal-close-x')
- // .click()
+
cy.get('.user-info-container > :nth-child(2) > .ant-btn').as('userInfo').click()
- cy.get(':nth-child(2) > :nth-child(2) > .d-flex > .ant-input').as('Full Name')
- .type(Math.random().toString(36).substring(2,6))
- cy.contains('SAVE').click({force: true})
- cy.get('.user_data > :nth-child(11)')
- .should('contain','Access denied:')
- cy.reload()
- // cy.get(':nth-child(6) > .ant-modal-root > .ant-modal-wrap > .ant-modal > .ant-modal-content > .ant-modal-close > .ant-modal-close-x')
- // .click()
+ const randEmail = Math.random().toString(36).substring(2,6)
+ cy.get('.ant-input').as('FullName')
+ .type(randEmail)
+ cy.contains('Proceed').should('be.disabled');
+ cy.get('@FullName').type('@hollaex.email');
+ cy.contains('Proceed').should('be.enabled').click()
+ cy.get('.mt-3').contains(randEmail+"@hollaex.email")
+ cy.contains('Yes, Correct, Proceed').click()
+ cy.contains('Change user email').should('be.visible')
+ cy.get('.ant-modal-close-x').click()
cy.contains('Flag user').click({force: true})
cy.contains('Are you sure want to flag this user?').should('exist')
cy.contains('OK').click({force: true})
- cy.get(':nth-child(5) > .about-info-content > .info-link')
+ cy.get(':nth-child(6) > .about-info-content > .info-link')
.should('contain','Unflag user').click({force: true})
cy.contains('Are you sure want to unflag this user?').should('exist').wait(3000)
cy.get('.ant-modal-confirm-btns > .ant-btn-primary').click({ multiple: true }).wait(3000)
- cy.get(':nth-child(5) > :nth-child(1) > .info-link')
+ cy.get(':nth-child(6) > :nth-child(1) > .info-link')
.should('contain','Flag user')
cy.contains('Freeze account').click({force:true})
@@ -163,106 +133,130 @@ Then ('I must be able to do KYCs tasks',()=>{
cy.contains('Unfreeze').click({force:true})
cy.contains('Are you sure want to unfreeze this account?').should('exist')
cy.contains('OK').click({force:true}).wait(3000)
- cy.get(':nth-child(4) > :nth-child(1) > .info-link').should('contain','Freeze account')
-
- cy.contains('Assets').click({force: true})
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
-
- cy.get('.green-btn').click()
- cy.contains('Add your asset').click({force: true})
- cy.get(':nth-child(1) > .ant-radio > .ant-radio-inner').click({force: true})
- cy.contains('Next').click()
- cy.contains('Assets').click({force: true})
-
- cy.get('.ant-radio-group > :nth-child(1) > :nth-child(2)').click({force: true})
- cy.contains('Next').click()
- cy.get('#AssetConfigForm_contract').type('123')
- cy.contains('Next').click()
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
- cy.get('.ant-modal-close-x')
- //cy.get(':nth-child(12) > .ant-modal-root > .ant-modal-wrap > .ant-modal > .ant-modal-content > .ant-modal-close > .ant-modal-close-x')
- .click()
-
- cy.contains('Summary').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Deposits').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Withdrawals').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Markets').click({force: true})
- //cy.get('.ant-message-notice-content').should('contain','Access denied:')
-
- // cy.contains('Public markets').click({force: true})
- // cy.contains('OTCdesk').click({force: true})
- // cy.contains('Create/add market').click({force: true})
- // cy.contains('Create a new market').click({force: true})
- // cy.get('.footer-info > a').click({force: true})
- // cy.get('.app_container-content > :nth-child(1) > .ant-alert-description')
- // .should('contain','Access denied:')
-
- cy.contains('Tiers').click({force: true})
- cy.contains('Adjust fees').click({force: true})
- cy.get(':nth-child(1) > :nth-child(2) > .ant-input-number > .ant-input-number-input-wrap > .ant-input-number-input')
- .type('{upArrow}{upArrow}{upArrow}')
- cy.contains('Confirm').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
- cy.get('.ant-modal-close-x')
- //cy.get(':nth-child(12) > .ant-modal-root > .ant-modal-wrap > .ant-modal > .ant-modal-content > .ant-modal-close > .ant-modal-close-x')
- .click()
-
- cy.contains('Roles').click({force: true})
- cy.contains('Add operator').click({force: true})
- cy.get('#OperatorRoleFrom_email').type('support#testsae.com')
- cy.contains('Save').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
- cy.contains('Chat').click({force: true})
- const randomTest= Math.random().toString(36).substring(2,6);
- cy.get('.ant-input').type(randomTest)
- cy.contains('SEND').click({force: true})
- cy.contains(randomTest).should('exist')
- // cy.get('.ant-switch').click({force:true})
-
- // cy.contains('Cc fee settlement').click({force: true})
- // cy.get('.ant-message-notice-content')
- // .should('contain','Access denied:')
-
- cy.contains('Announcements').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
-
- cy.contains('Automatic tier upgrade').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
+ cy.get(':nth-child(5) > :nth-child(1) > .info-link').should('contain','Freeze account')
+
+ cy.get('.ml-4 > .ant-btn').click()
+ const num = Math.floor(Math.random() * 5) + 1;
+ cy.get('.ant-select-selection-item').type('{downarrow}'.repeat(num) + '{enter}');
+ cy.get('.ant-select-selection-item').invoke('text').then((text) => {
+ cy.log(text);
+ const str = text
+ const regex = /(\d+)/;
+ const match = str.match(regex);
+ const accountTier = match && match[1];
+ console.log(accountTier);
+ cy.get('div.w-100 > .ant-btn').click()
+ cy.get('.user-level-container > .user-info-label').contains(accountTier)
+
+ })
+ cy.reload()
+ cy.get('#rc-tabs-0-tab-bank').click()
+ cy.contains('Add bank').click()
+ cy.get('.ant-select-selector').click().type('{downarrow}{enter}')
+ cy.get('.ant-modal-footer > .ant-btn-primary').click()
+ cy.get(':nth-child(1) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get(':nth-child(2) > :nth-child(2) > .d-flex > .ant-input').clear().type('123456')
+ cy.get(':nth-child(3) > :nth-child(2) > .d-flex > .ant-input').clear().type('123456')
+ cy.get('.ant-modal-footer > .ant-btn-primary').click()
+ cy.get('.ant-notification-notice').contains('Success')
+ cy.contains('123456')
+ cy.get('.ant-card-extra > .anticon > svg').click()
+ cy.get('.ant-popover-buttons > .ant-btn-primary').click()
+ cy.get('.ant-message-notice-content').contains('Bank deleted')
+ cy.contains('123456').should('not.exist');
+
+ cy.get('#rc-tabs-0-tab-orders').click()
+ cy.contains('Download table').should('not.exist')
+ cy.get('#rc-tabs-0-tab-trade').click()
+ cy.contains('Download table')
+ cy.get('#rc-tabs-0-tab-deposits').click()
+ cy.contains('Download transactions')
+ cy.get('#rc-tabs-0-tab-withdrawals').click()
+ cy.contains('Download transactions')
+ cy.get('#rc-tabs-0-tab-referrals').click()
+ cy.get('.ant-spin-dot').should('exist')
+ cy.get('#rc-tabs-0-tab-meta').click()
+ //edit is not allowed
+ cy.get('.justify-content-between > .ant-btn').click()
+ cy.get(':nth-child(1) > .d-flex > .ant-btn').click()
+ cy.get('.input_field > :nth-child(1) > .ant-select > .ant-select-selector').click().type('{downarrow}{downarrow}{enter}')
+ cy.get('.ant-radio-group > :nth-child(1) > :nth-child(2)').click()
+ cy.get(':nth-child(3) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get(':nth-child(4) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(1)').click()
+
+ cy.get('.ant-table-row > :nth-child(5) > div > :nth-child(1)').click()
+ const edit= Math.random().toString(36).substring(2,6);
+ cy.get(':nth-child(4) > :nth-child(2) > .d-flex > .ant-input').clear().type(edit)
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(1)').click()
+ cy.wait(3000)
+ //remove
+ cy.get('.ant-table-row > :nth-child(5) > div > :nth-child(2)').click()
+ cy.get('.title').contains('Remove Meta')
+ cy.get('.modal-wrapper > .d-flex > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > .d-flex > :nth-child(1)').click()
+
+ cy.get(':nth-child(3) > .ant-breadcrumb-link > a').click()
+ cy.get('#rc-tabs-0-tab-about').click()
+
+ cy.get('a > .ant-btn').click()
+ cy.contains('Summary')
+ cy.get('a > .pl-1').click()
+ cy.contains('Chat').click({force: true})
+ const randomTest= Math.random().toString(36).substring(2,6);
+ cy.get('.ant-input').type(randomTest)
+ cy.contains('SEND').click({force: true})
+ cy.contains(randomTest).should('exist')
+
})
Then ('I must be able to do Supports tasks',()=>{
cy.contains('Users').click({force: true})
- cy.get('.ant-input-number-input').type('191')
- cy.get('.ant-btn').click()
+ cy.contains('Add new user').click()
+ const randomUsername = Math.random().toString(36).substring(2,6);
+ const Rusername = "tester+"+randomUsername+Cypress.env('NEW_USER')
+ cy.get('#addUser_userEmail').clear().type(Rusername)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.get('.ant-message-notice-content')
+ .contains("Access denied: User is not authorized to access this endpoint")
+ cy.get('.mr-5').click()
+ cy.reload()
+ cy.get(':nth-child(1) > .ant-input').clear().type('191')
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(5000)
+ cy.get(':nth-child(8) > .ant-btn').click()
+ cy.get('.about-notes-content > .d-flex > .green-btn').click()
+ const randomNote= Math.random().toString(36).substring(2,6);
+ cy.get('.ant-input').clear().type(randomNote)
+ cy.get('div.w-100 > .ant-btn').click()
+ cy.get('.ant-notification-notice').contains('Data')
+ cy.get('.about-notes-text').contains(randomNote)
cy.contains('Delete').click({force: true})
cy.contains('Are you sure want to delete this?').should('exist')
cy.contains('OK').click({force: true})
- //Delete does not work
+ cy.wait(3000)
+ cy.contains(randomNote).should('not.exist');
cy.contains('Edit').click({force: true})
- const randomNote= Math.random().toString(36).substring(2,6);
- cy.get('.ant-input').clear().type(randomNote)
+ const randomNoteForeEdite= Math.random().toString(36).substring(2,6);
+ cy.get('.ant-input').clear().type(randomNoteForeEdite)
cy.contains('Submit').click({force: true})
cy.get('.ant-message-notice-content').should('contain','Access denied: User is not authorized to access this endpoint')
- cy.get('.ant-modal-close-x').click()
+ // cy.get('.ant-modal-close-x').click()
//cy.get('.about-notes-text').should('contain',randomNote)
-
- cy.get(':nth-child(1) > .about-info-content > .info-link').as('Adjust').click({force: true})
- cy.get('#user-discount-form_feeDiscount')
- .type('{upArrow}{upArrow}{upArrow}')
+ cy.reload()
+
+ cy.contains('Adjust').click({force: true})
+ cy.get('#user-discount-form_feeDiscount')
+ .type('{upArrow}{upArrow}{upArrow}').wait(3000).type('9').type('{backspace}')//.type('{enter}')
cy.contains('Next').click()
cy.get('.button-wrapper > :nth-child(2)').as('Apply').click({force: true})
cy.get('.ant-message-notice-content')
@@ -273,24 +267,26 @@ Then ('I must be able to do Supports tasks',()=>{
cy.get('.user-info-container > :nth-child(2) > .ant-btn').as('userInfo').click()
- cy.get(':nth-child(2) > :nth-child(2) > .d-flex > .ant-input').as('Full Name')
- .type(Math.random().toString(36).substring(2,6))
- cy.contains('SAVE').click({force: true})
- cy.get('.user_data > :nth-child(11)')
- .should('contain','Access denied:')
- cy.reload()
- // cy.get(':nth-child(6) > .ant-modal-root > .ant-modal-wrap > .ant-modal > .ant-modal-content > .ant-modal-close > .ant-modal-close-x')
- // .click()
+ const randEmail = Math.random().toString(36).substring(2,6)
+ cy.get('.ant-input').as('FullName')
+ .type(randEmail)
+ cy.contains('Proceed').should('be.disabled');
+ cy.get('@FullName').type('@hollaex.email');
+ cy.contains('Proceed').should('be.enabled').click()
+ cy.get('.mt-3').contains(randEmail+"@hollaex.email")
+ cy.contains('Yes, Correct, Proceed').click()
+ cy.contains('Change user email').should('be.visible')
+ cy.get('.ant-modal-close-x').click()
cy.contains('Flag user').click({force: true})
cy.contains('Are you sure want to flag this user?').should('exist')
cy.contains('OK').click({force: true})
- cy.get(':nth-child(5) > .about-info-content > .info-link')
+ cy.get(':nth-child(6) > .about-info-content > .info-link')
.should('contain','Unflag user').click({force: true})
cy.contains('Are you sure want to unflag this user?').should('exist').wait(3000)
cy.get('.ant-modal-confirm-btns > .ant-btn-primary').click({ multiple: true }).wait(3000)
//cy.contains('OK').click({force: true})
- cy.get(':nth-child(5) > :nth-child(1) > .info-link')
+ cy.get(':nth-child(6) > :nth-child(1) > .info-link')
.should('contain','Flag user')
cy.contains('Freeze account').click({force:true})
@@ -299,88 +295,108 @@ Then ('I must be able to do Supports tasks',()=>{
cy.contains('Unfreeze').click({force:true})
cy.contains('Are you sure want to unfreeze this account?').should('exist')
cy.contains('OK').click({force:true}).wait(3000)
- cy.get(':nth-child(4) > :nth-child(1) > .info-link').should('contain','Freeze account')
-
- cy.contains('Assets').click({force: true})
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
-
- cy.get('.green-btn').click()
- cy.contains('Add your asset').click({force: true})
- cy.get(':nth-child(1) > .ant-radio > .ant-radio-inner').click({force: true})
- cy.contains('Next').click()
- cy.contains('Assets').click({force: true})
-
-
- cy.get('.ant-radio-group > :nth-child(1) > :nth-child(2)').click({force: true})
- cy.contains('Next').click()
- cy.get('#AssetConfigForm_contract').type('123')
- cy.contains('Next').click()
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
- cy.contains('Summary').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Deposits').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Withdrawals').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Markets').click({force: true})
- //cy.get('.ant-message-notice-content').should('contain','Access denied:')
-
- // cy.contains('OrderBook').click({force: true})
- // cy.contains('Create/add market').click({force: true})
- // cy.contains('Create a new market').click({force: true})
- // cy.get('.footer-info > a').click({force: true})
- // cy.get('.app_container-content > :nth-child(1) > .ant-alert-description')
- // .should('contain','Access denied:')
-
- cy.contains('Tiers').click({force: true})
- cy.contains('Adjust fees').click({force: true})
- cy.get(':nth-child(1) > :nth-child(2) > .ant-input-number > .ant-input-number-input-wrap > .ant-input-number-input')
- .type('{upArrow}{upArrow}{upArrow}')
- cy.contains('Confirm').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
- cy.contains('Roles').click({force: true})
- cy.contains('Add operator').click({force: true})
- cy.get('#OperatorRoleFrom_email').type('support#testsae.com')
- cy.contains('Save').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
+ cy.get(':nth-child(5) > :nth-child(1) > .info-link').should('contain','Freeze account')
+ cy.get('.ml-4 > .ant-btn').click()
+ const num = Math.floor(Math.random() * 5) + 1;
+ cy.get('.ant-select-selection-item').type('{downarrow}'.repeat(num) + '{enter}');
+ cy.get('.ant-select-selection-item').invoke('text').then((text) => {
+ cy.log(text);
+ const str = text
+ const regex = /(\d+)/;
+ const match = str.match(regex);
+ const accountTier = match && match[1];
+ console.log(accountTier);
+ cy.get('div.w-100 > .ant-btn').click()
+ //cy.get('.user-level-container > .user-info-label').contains(accountTier)
+ cy.contains('Access denied: User is not authorized to access this endpoint')
+ })
+ cy.reload()
+ cy.get('#rc-tabs-0-tab-bank').click()
+ cy.contains('Add bank').click()
+ cy.get('.ant-select-selector').click().type('{downarrow}{enter}')
+ cy.get('.ant-modal-footer > .ant-btn-primary').click()
+ cy.get(':nth-child(1) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get(':nth-child(2) > :nth-child(2) > .d-flex > .ant-input').clear().type('123456')
+ cy.get(':nth-child(3) > :nth-child(2) > .d-flex > .ant-input').clear().type('123456')
+ cy.get('.ant-modal-footer > .ant-btn-primary').click()
+ cy.get('.ant-modal-body > :nth-child(7) > strong')
+ .should('contain','Access denied: User is not authorized to access this endpoint')
cy.get('.ant-modal-close-x').click()
-
-
+ // cy.get('.ant-notification-notice').contains('Success')
+ // cy.contains('123456')
+ // cy.get('.ant-card-extra > .anticon > svg').click()
+ // cy.get('.ant-popover-buttons > .ant-btn-primary').click()
+ // cy.get('.ant-message-notice-content').contains('Bank deleted')
+ // cy.contains('123456').should('not.exist');
+
+ cy.get('#rc-tabs-0-tab-orders').click()
+ cy.contains('Download table')
+ cy.get('#rc-tabs-0-tab-trade').click()
+ cy.contains('Download table')
+ cy.get('#rc-tabs-0-tab-deposits').click()
+ cy.contains('Download transactions')
+ cy.get('#rc-tabs-0-tab-withdrawals').click()
+ cy.contains('Download transactions')
+ cy.get('#rc-tabs-0-tab-referrals').click()
+ cy.get('.ant-spin-dot').should('exist')
+ cy.get('#rc-tabs-0-tab-meta').click()
+ //edit is not allowed
+ cy.get('.justify-content-between > .ant-btn').click()
+ cy.get(':nth-child(1) > .d-flex > .ant-btn').click()
+ cy.get('.input_field > :nth-child(1) > .ant-select > .ant-select-selector').click().type('{downarrow}{downarrow}{enter}')
+ cy.get('.ant-radio-group > :nth-child(1) > :nth-child(2)').click()
+ cy.get(':nth-child(3) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get(':nth-child(4) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(1)').click()
+
+ cy.get('.ant-table-row > :nth-child(5) > div > :nth-child(1)').click()
+ cy.get(':nth-child(4) > :nth-child(2) > .d-flex > .ant-input').clear().type('edit')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(1)').click()
+ //remove
+ cy.get('.ant-table-row > :nth-child(5) > div > :nth-child(2)').click()
+ cy.get('.title').contains('Remove Meta')
+ cy.get('.modal-wrapper > .d-flex > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > .d-flex > :nth-child(1)').click()
+
+ cy.get(':nth-child(3) > .ant-breadcrumb-link > a').click()
+ cy.get('#rc-tabs-0-tab-about').click()
+
+ cy.get('a > .ant-btn').click()
+ cy.contains('Summary')
+ cy.get('a > .pl-1').click()
cy.contains('Chat').click({force: true})
const randomTest= Math.random().toString(36).substring(2,6);
cy.get('.ant-input').type(randomTest)
cy.contains('SEND').click({force: true})
cy.contains(randomTest).should('exist')
- // cy.get('.ant-switch').click({force:true})
-
- // cy.contains('Cc fee settlement').click({force: true})
- // cy.get('.ant-message-notice-content')
- // .should('contain','Access denied:')
-
- cy.contains('Announcements').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
-
- cy.contains('Automatic tier upgrade').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
+ cy.wait(10000)
+
})
Then ('I must be able to do Supervisors tasks',()=>{
cy.contains('Users').click({force: true})
- cy.get('.ant-input-number-input').type('191')
- cy.get('.ant-btn').click()
+ cy.contains('Add new user').click()
+ const randomUsername = Math.random().toString(36).substring(2,6);
+ const Rusername = "tester+"+randomUsername+Cypress.env('NEW_USER')
+ cy.get('#addUser_userEmail').clear().type(Rusername)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.get('.ant-message-notice-content')
+ .contains("Access denied: User is not authorized to access this endpoint")
+ cy.get('.mr-5').click()
+ cy.reload()
+ cy.get(':nth-child(1) > .ant-input').clear().type('191')
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(5000)
+ cy.get(':nth-child(8) > .ant-btn').click()
cy.contains('Delete').click({force: true})
cy.contains('Are you sure want to delete this?').should('exist')
cy.contains('OK').click({force: true})
@@ -397,30 +413,34 @@ Then ('I must be able to do Supervisors tasks',()=>{
cy.get('.verification_data_container > :nth-child(1) > :nth-child(1) > .d-flex > div')
.should('contain',randomNote)
- cy.get(':nth-child(1) > .about-info-content > .info-link').as('Adjust').click({force: true})
+ cy.contains('Adjust').click()
cy.get('#user-discount-form_feeDiscount')
- .type('{upArrow}{upArrow}{upArrow}')
+ .type('{upArrow}{upArrow}{upArrow}').wait(3000).type('9').type('{backspace}')//.type('{enter}')
cy.contains('Next').click()
- cy.get('.button-wrapper > :nth-child(2)').as('Apply').click({force: true})
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
+ cy.get('.button-wrapper > :nth-child(2)').as('Apply').click()
+ cy.get('.ant-message-notice-content').should('contain','Discount added successfully')
+ //cy.get('.ant-modal-close-x').click()
cy.get('.user-info-container > :nth-child(2) > .ant-btn').as('userInfo').click()
- cy.get(':nth-child(2) > :nth-child(2) > .d-flex > .ant-input').as('Full Name')
- .clear().type(Math.random().toString(36).substring(2,6))
- cy.contains('SAVE').click({force: true})
- cy.get('.user_data > :nth-child(11)')
- cy.get('.ant-notification-notice-message').should('contain','Success')
+ const randEmail = Math.random().toString(36).substring(2,6)
+ cy.get('.ant-input').as('FullName')
+ .type(randEmail)
+ cy.contains('Proceed').should('be.disabled');
+ cy.get('@FullName').type('@hollaex.email');
+ cy.contains('Proceed').should('be.enabled').click()
+ cy.get('.mt-3').contains(randEmail+"@hollaex.email")
+ cy.contains('Yes, Correct, Proceed').click()
+ cy.contains('Change user email').should('be.visible')
cy.get('.ant-modal-close-x').click()
cy.contains('Flag user').click({force: true})
cy.contains('Are you sure want to flag this user?').should('exist')
cy.contains('OK').click({force: true})
- cy.get(':nth-child(5) > .about-info-content > .info-link')
+ cy.get(':nth-child(6) > .about-info-content > .info-link')
.should('contain','Unflag user').click({force: true})
cy.contains('Are you sure want to unflag this user?').should('exist').wait(3000)
cy.get('.ant-modal-confirm-btns > .ant-btn-primary').click({ force: true }).wait(3000)
- cy.get(':nth-child(5) > :nth-child(1) > .info-link')
+ cy.get(':nth-child(6) > :nth-child(1) > .info-link')
.should('contain','Flag user')
cy.contains('Freeze account').click({force:true})
@@ -429,81 +449,354 @@ Then ('I must be able to do Supervisors tasks',()=>{
cy.contains('Unfreeze').click({force:true})
cy.contains('Are you sure want to unfreeze this account?').should('exist')
cy.contains('OK').click({force:true}).wait(3000)
- cy.get(':nth-child(4) > :nth-child(1) > .info-link').should('contain','Freeze account')
-
- cy.contains('Assets').click({force: true})
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
+ cy.get(':nth-child(5) > :nth-child(1) > .info-link').should('contain','Freeze account')
+
+
+ cy.get('.ml-4 > .ant-btn').click()
+ const num = Math.floor(Math.random() * 5) + 1;
+ cy.get('.ant-select-selection-item').type('{downarrow}'.repeat(num) + '{enter}');
+ cy.get('.ant-select-selection-item').invoke('text').then((text) => {
+ cy.log(text);
+ const str = text
+ const regex = /(\d+)/;
+ const match = str.match(regex);
+ const accountTier = match && match[1];
+ console.log(accountTier);
+ cy.get('div.w-100 > .ant-btn').click()
+ cy.get('.user-level-container > .user-info-label').contains(accountTier)
+ })
- cy.get('.green-btn').click()
- cy.contains('Add your asset').click({force: true})
- cy.get(':nth-child(1) > .ant-radio > .ant-radio-inner').click({force: true})
- cy.contains('Next').click()
- cy.contains('Assets').click({force: true})
-
- cy.get('.ant-radio-group > :nth-child(1) > :nth-child(2)').click({force: true})
- cy.contains('Next').click()
- cy.get('#AssetConfigForm_contract').type('123')
- cy.contains('Next').click()
- cy.get('.ant-message-notice-content').should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
- cy.contains('Summary').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Deposits').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
+ cy.reload()
+ cy.get('#rc-tabs-0-tab-bank').click()
+ cy.contains('Add bank').click()
+ cy.get('.ant-select-selector').click().type('{downarrow}{enter}')
+ cy.get('.ant-modal-footer > .ant-btn-primary').click()
+ cy.get(':nth-child(1) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get(':nth-child(2) > :nth-child(2) > .d-flex > .ant-input').clear().type('123456')
+ cy.get(':nth-child(3) > :nth-child(2) > .d-flex > .ant-input').clear().type('123456')
+ cy.get('.ant-modal-footer > .ant-btn-primary').click()
+ cy.get('.ant-notification-notice').contains('Success')
+ cy.contains('123456')
+ cy.get('.ant-card-extra > .anticon > svg').click()
+ cy.get('.ant-popover-buttons > .ant-btn-primary').click()
+ cy.get('.ant-message-notice-content').contains('Bank deleted')
+ cy.contains('123456').should('not.exist');
+
+ cy.get('#rc-tabs-0-tab-balance').click()
+ cy.get('[data-row-key="1"] > .ant-table-row-expand-icon-cell').click()
+ cy.get('.generate-link').should('exist')
+ cy.get('#rc-tabs-0-tab-orders').click()
+ cy.contains('Download table')
+ cy.get('#rc-tabs-0-tab-trade').click()
+ cy.contains('Download table')
+ cy.get('#rc-tabs-0-tab-deposits').click()
+ cy.contains('Download transactions')
+ cy.get('#rc-tabs-0-tab-withdrawals').click()
+ cy.contains('Download transactions')
+ cy.get('#rc-tabs-0-tab-referrals').click()
+ cy.get('.ant-spin-dot').should('exist')
+ cy.get('#rc-tabs-0-tab-meta').click()
+ //edit is not allowed
+ cy.get('.justify-content-between > .ant-btn').click()
+ cy.get(':nth-child(1) > .d-flex > .ant-btn').click()
+ cy.get('.input_field > :nth-child(1) > .ant-select > .ant-select-selector').click().type('{downarrow}{downarrow}{enter}')
+ cy.get('.ant-radio-group > :nth-child(1) > :nth-child(2)').click()
+ cy.get(':nth-child(3) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get(':nth-child(4) > :nth-child(2) > .d-flex > .ant-input').clear().type('test')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(1)').click()
+
+ cy.get('.ant-table-row > :nth-child(5) > div > :nth-child(1)').click()
+ const edit= Math.random().toString(36).substring(2,6);
+ cy.get(':nth-child(4) > :nth-child(2) > .d-flex > .ant-input').clear().type(edit)
+ cy.get('.modal-wrapper > :nth-child(3) > :nth-child(3)').click()
+ //cy.get('.ant-message-notice-content').contains('Data saved successfully')
+ cy.get('.user_meta-form > :nth-child(1) > .admin-user-container > .ant-table-wrapper > .ant-spin-nested-loading > .ant-spin-container > .ant-table > .ant-table-container > .ant-table-content > table > .ant-table-tbody > .ant-table-row > :nth-child(4)')
+ .contains(edit)
+ cy.wait(3000)
+ //remove
+ cy.get('.ant-table-row > :nth-child(5) > div > :nth-child(2)').click()
+ cy.get('.title').contains('Remove Meta')
+ cy.get('.modal-wrapper > .d-flex > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content').contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.modal-wrapper > .d-flex > :nth-child(1)').click()
+
+ cy.get(':nth-child(3) > .ant-breadcrumb-link > a').click()
+ cy.get('#rc-tabs-0-tab-about').click()
- cy.contains('Withdrawals').click()
- cy.get('.app_container-content > :nth-child(1) > .ant-alert-description').should('contain','Access denied:')
-
- cy.contains('Markets').click({force: true})
- //cy.get('.ant-message-notice-content').should('contain','Access denied:')
-
- // cy.contains('OrderBook').click({force: true})
- // cy.contains('Create/add market').click({force: true})
- // cy.contains('Create a new market').click({force: true})
- // cy.get('.footer-info > a').click({force: true})
- // cy.get('.app_container-content > :nth-child(1) > .ant-alert-description')
- // .should('contain','Access denied:')
-
- cy.contains('Tiers').click({force: true})
- cy.contains('Adjust fees').click({force: true})
- cy.get(':nth-child(1) > :nth-child(2) > .ant-input-number > .ant-input-number-input-wrap > .ant-input-number-input')
- .type('{upArrow}{upArrow}{upArrow}')
- cy.contains('Confirm').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
- cy.contains('Roles').click({force: true})
- cy.contains('Add operator').click({force: true})
- cy.get('#OperatorRoleFrom_email').type('support#testsae.com')
- cy.contains('Save').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
- cy.get('.ant-modal-close-x').click()
-
-
- cy.contains('Chat').click({force: true})
- const randomTest= Math.random().toString(36).substring(2,6);
- cy.get('.ant-input').type(randomTest)
- cy.contains('SEND').click({force: true})
- cy.contains(randomTest).should('exist')
- // cy.get('.ant-switch').click({force:true})
-
- // cy.contains('Cc fee settlement').click({force: true})
- // cy.get('.ant-message-notice-content')
- // .should('contain','Access denied:')
-
- cy.contains('Announcements').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
-
- cy.contains('Automatic tier upgrade').click({force: true})
- cy.get('.ant-message-notice-content')
- .should('contain','Access denied:')
+
+ cy.get('a > .ant-btn').click()
+ cy.contains('Summary')
+ cy.get('a > .pl-1').click()
+ cy.contains('Chat').click({force: true})
+ const randomTest= Math.random().toString(36).substring(2,6);
+ cy.get('.ant-input').type(randomTest)
+ cy.contains('SEND').click({force: true})
+ cy.contains(randomTest).should('exist')
-})
+ cy.contains('Assets').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.green-btn').click()
+ //cy.get('.add-asset > span').click()
+ //cy.get('.create-asset-btn').click()
+ cy.contains('Next').should('be.disabled')
+ cy.get('.ant-tabs > .d-flex > :nth-child(2) > :nth-child(1)').click()
+ cy.contains('Next').should('be.disabled')
+ cy.get('.d-flex > :nth-child(3) > :nth-child(1)').click()
+ cy.contains('Next').should('be.disabled')
+ cy.contains('Add your asset').click()
+ cy.get('.ant-radio-group > :nth-child(1)').click()
+ cy.get('.btn-wrapper > :nth-child(3)').click()
+ cy.get('.ant-radio-group > :nth-child(2)').click()
+ cy.get('.btn-wrapper > :nth-child(3)').click()
+ cy.get('#AssetConfigForm_contract').type('Contract')
+ cy.get('#AssetConfigForm_fullname').type('FullName')
+ cy.get('#AssetConfigForm_symbol').type('short')
+ cy.get('.md-field-wrap > .ant-input').type('SHORT')
+ cy.contains('Next').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.btn-wrapper > [type="button"]').click()
+ cy.get('.btn-wrapper > :nth-child(1)').click()
+ cy.get('.ant-radio-group > :nth-child(2) > :nth-child(2)').click()
+ cy.get('.btn-wrapper > :nth-child(3)').click()
+ cy.get('.btn-wrapper > :nth-child(3)').click()
+ cy.get('#AssetConfigForm_fullname').type('FullName')
+ cy.get('#AssetConfigForm_symbol').type('short')
+ cy.get('.md-field-wrap > .ant-input').type('SHORT')
+ cy.contains('Next').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.ant-modal-close-x').click()
+
+ //cy.get('#rc-tabs-0-tab-1').click()
+ cy.contains('Summary').click()
+ cy.get('.app_container-content > :nth-child(1) > .ant-alert-description')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.ant-card-body > .ant-alert > .ant-alert-description')
+ .contains('Access denied: User is not authorized to access this endpoint')
+
+ //cy.get('#rc-tabs-0-tab-2').click()
+ cy.contains('Wallet').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.contains('No Data')
+
+ //cy.get('#rc-tabs-0-tab-3').click()
+ cy.contains('Balances').click()
+ //need warning
+ //cy.get('#rc-tabs-0-tab-4').click()
+ cy.contains('Orders').click()
+ cy.wait(5000)
+ //// The first block logs the initial dataRowKey value.
+ // //The second block clicks on the first button in the eighth column.
+ // //The third block logs the new dataRowKeyNew value and also checks that it is not the same as the initial dataRowKey value using expect().
+ // //This code ensures that the dataRowKey' value is not the same as the previously logged dataRowKey value after performing the specified actions.
+ let initialDataRowKey;
+
+ cy.get(':nth-child(6) > time').first().then(($element) => {
+ initialDataRowKey = $element.closest('[data-row-key]').attr('data-row-key');
+ cy.log(initialDataRowKey);
+ }).then(() => {
+ cy.get(':nth-child(8) > .ant-btn').first().click();
+ cy.wait(3000);
+ }).then(() => {
+ cy.get(':nth-child(6) > time').first().then(($element) => {
+ const dataRowKeyNew = $element.closest('[data-row-key]').attr('data-row-key');
+ expect(dataRowKeyNew).not.to.equal(initialDataRowKey);
+ cy.log(dataRowKeyNew);
+ });
+});
+
+
+ //cy.get('#rc-tabs-0-tab-5').click()
+ cy.contains('Deposits').click()
+ cy.get(':nth-child(3) > .input-container > .ant-input').clear().type(1)
+ cy.get('.filters-wrapper-buttons > .ant-btn').click()
+ cy.get(':nth-child(2) > .ant-btn > a').each(($element) => {
+ cy.wrap($element).should('have.text', '1');
+ });
+
+ cy.get(':nth-child(3) > .input-container > .ant-input').clear()
+ cy.get(':nth-child(4) > .input-container > .ant-input')
+ .clear().type('d16b6401-1c83-4d0c-855c-4b0d5f5f141a')
+ cy.get('.filters-wrapper-buttons > .ant-btn').click()
+ cy.wait(3000)
+ cy.get('.ant-table-row > :nth-child(3)')
+ .contains('d16b6401-1c83-4d0c-855c-4b0d5f5f141a')
+
+ cy.get(':nth-child(3) > .input-container > .ant-input').clear()
+ cy.get(':nth-child(4) > .input-container > .ant-input').clear()
+ cy.get(':nth-child(5) > .input-container > .ant-input').clear()
+ .type('0x97b7b520e553794a610dc25d06414719ffb44a66')
+ cy.get('.filters-wrapper-buttons > .ant-btn').click()
+
+ cy.get(':nth-child(2) > .ant-btn > a').each(($element) => {
+ cy.wrap($element).should('have.text', '173');
+ });
+ cy.get('[data-row-key="11038"] > :nth-child(4)').invoke('text').then((text) => {
+ cy.log(text);
+ });
+ var firstIndex
+ cy.get('tr > :nth-child(4)').each(($element, index) => {
+ cy.wrap($element).invoke('text').then((text) => {
+ //cy.log(`Element ${index + 1}: ${text}`);
+ if (text.trim() === 'Currency') {
+ cy.log(`Element ${index + 1} has the text "Currency"`);
+ firstIndex = index + 1
+ }
+ });
+ });
+ cy.get('tr > :nth-child(4)').each(($element, index) => {
+ if (index >= firstIndex) {
+ cy.wrap($element).should('have.text', 'xht');
+ }
+ });
+
+
+ //cy.get('#rc-tabs-0-tab-6').click()
+ cy.reload()
+ cy.contains('Withdrawals').click()
+ cy.wait(5000)
+ cy.get(':nth-child(3) > .input-container > .ant-input').clear().type(1)
+ cy.get('.filters-wrapper-buttons > .ant-btn').click()
+ cy.get(':nth-child(2) > .ant-btn > a').each(($element) => {
+ cy.wrap($element).should('have.text', '1');
+ });
+
+ cy.get(':nth-child(3) > .input-container > .ant-input').clear()
+ cy.get(':nth-child(4) > .input-container > .ant-input')
+ .clear().type('d16b6401-1c83-4d0c-855c-4b0d5f5f141a')
+ cy.get('.filters-wrapper-buttons > .ant-btn').click()
+ cy.wait(3000)
+ cy.get('.ant-table-row > :nth-child(3)')
+ .contains('d16b6401-1c83-4d0c-855c-4b0d5f5f141a')
+
+ cy.get(':nth-child(3) > .input-container > .ant-input').clear()
+ cy.get(':nth-child(4) > .input-container > .ant-input').clear()
+ cy.get(':nth-child(5) > .input-container > .ant-input').clear()
+ .type('0x97b7b520e553794a610dc25d06414719ffb44a66')
+ cy.get('.filters-wrapper-buttons > .ant-btn').click()
+
+
+ //cy.get('#rc-tabs-0-tab-7').click()
+ cy.contains('Earnings').click()
+ // cy.get('.ant-message-notice-content')
+ // .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.download-btn').should('not.be', 'enabled');
+
+ //cy.get('#rc-tabs-0-tab-8').click()
+ cy.reload()
+ cy.contains('Transfers').click()
+ // cy.get('.ant-message-notice-content')
+ // .contains('Access denied: User is not authorized to access this endpoint')
+ cy.wait(3000)
+ cy.get('#rc_select_1')
+ //cy.get(':nth-child(1) > :nth-child(2) > .ant-select > .ant-select-selector')
+ .click().clear().type('tech@bitholla.com')
+ .wait(3000).type('{downarrow}{downarrow}{downarrow}{enter}')
+ cy.get('#rc_select_2')
+ //cy.get(':nth-child(2) > :nth-child(2) > .ant-select > .ant-select-selector')
+ .click().clear().type('alice@hollaex.email')
+ .wait(3000).type('{downarrow}{enter}')
+ cy.get('.ant-input-number-input').clear().type('1')
+ cy.get('.d-flex > .ant-input').clear().type('supervisor test')
+ cy.get('div.w-100 > .ant-btn').click()
+ cy.get('.d-flex > .ml-2').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Request failed with status code 403')
+
+ //cy.get('#rc-tabs-0-tab-9').click()
+ cy.contains('Duster').click()
+ var initialId;
+
+ cy.get('.w-50.my-3 > .ant-input-number > .ant-input-number-input-wrap > .ant-input-number-input')
+ .invoke('val')
+ .then((text) => {
+ initialId = text;
+ cy.log('Initial ID:', initialId);
+ cy.get('.w-50.my-3 > .ant-input-number > .ant-input-number-input-wrap > .ant-input-number-input')
+ .clear().type('174')
+ cy.get('.admin-chat-feature-wrapper > :nth-child(1) > .ant-btn').click()
+ cy.get('.btn-wrapper > :nth-child(3)').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.reload()
+ cy.get('#rc-tabs-0-tab-11').click()
+ // Perform other actions here
+ // ...
+ cy.get('.w-50.my-3 > .ant-input-number > .ant-input-number-input-wrap > .ant-input-number-input')
+ .invoke('val')
+ .should('eq', initialId);
+ });
+ // should be deleted
+ cy.contains('Limits').click()
+ //cy.get('#rc-tabs-0-tab-2').click()
+ //cy.get(':nth-child(2) > :nth-child(1) > .ant-btn').eq(0).click()
+ cy.contains('Create Independent Limit').click()
+ cy.get('#rc_select_3').click().type('{downarrow}') // Simulates pressing the down arrow key once
+ .type('{downarrow}') // Simulates pressing the down arrow key again
+ .type('{enter}');
+
+ cy.get('#rc_select_4').click().type('{downarrow}') // Simulates pressing the down arrow key once
+ .type('{downarrow}') // Simulates pressing the down arrow key again
+ .type('{enter}');
+ cy.get(':nth-child(3) > .ant-input').clear().type('3')
+ cy.get(':nth-child(4) > .ant-input').clear().type('3')
+ cy.contains('PROCEED').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.ant-modal-body').contains('Back').click()
+ //cy.get(':nth-child(2) > :nth-child(1) > .ant-btn').eq(1).click();
+ cy.contains('Create Collective Limit').click()
+ cy.get('#rc_select_5').click().type('{downarrow}') // Simulates pressing the down arrow key once
+ .type('{downarrow}') // Simulates pressing the down arrow key again
+ .type('{enter}');
+
+ cy.get('#rc_select_6').click().type('{downarrow}') // Simulates pressing the down arrow key once
+ .type('{downarrow}') // Simulates pressing the down arrow key again
+ .type('{enter}');
+
+ cy.get(':nth-child(4) > .ant-input').clear().type('3')
+ cy.get(':nth-child(5) > .ant-input').clear().type('3')
+
+ cy.contains('PROCEED').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.ant-modal-body').contains('Back').click()
+
+ cy.contains('Fee Markups').click()
+ cy.get(':nth-child(1) > :nth-child(4) > .d-flex > .ant-btn').click()
+ cy.get('.ant-input').last().clear().type('1')
+ cy.contains('PROCEED').click()
+ cy.get('.ant-message-notice-content')
+ .contains('Access denied: User is not authorized to access this endpoint')
+ cy.get('.ant-modal-close-x').click()
+
+
+ cy.contains('Sessions').click()
+ cy.get(':nth-child(2) > .d-flex > .ant-btn')
+ .contains('1438')
+ .first().then(($element) => {
+ let initialDataRowKey = $element.closest('[data-row-key]').attr('data-row-key');
+ cy.log(initialDataRowKey);
+ cy.get(`[data-row-key="${initialDataRowKey}"] > :nth-child(6) > .d-flex > div`).contains('Active');
+ cy.get(`[data-row-key="${initialDataRowKey}"] > :nth-child(6) > .d-flex > .ant-btn`).click()
+ cy.wait(3000)
+ cy.contains('Confirm').click()
+ cy.get('.ant-message-notice-content').contains('Session revoked')
+
+ cy.wait(5000)
+ cy.get('.top-box-menu').click()
+ cy.get('.notification-content-information')
+ .contains('Your session is expired. Please login again.')
+ cy.url().should('eq', 'https://sandbox.hollaex.com/login');
+ //Session revoked
+ });
+
+ })
diff --git a/test/Cypress/cypress/integration/Gherkin/security.feature b/test/Cypress/cypress/integration/Gherkin/security.feature
index 193ee11c63..f974f4a22f 100644
--- a/test/Cypress/cypress/integration/Gherkin/security.feature
+++ b/test/Cypress/cypress/integration/Gherkin/security.feature
@@ -4,13 +4,12 @@ As a user
In order to manage security
I want to change the password, active/deactivate 2FA and generate API Keys
-@waitings
+
Scenario: Creating a new user with the current password
- Given I sign up with a random username and the current password
- When Admin confirms the new user
- Then I registered with the new username
-@waitings
+ Given Admin creats a user with a random username and the current password
+ Then The new username is stored
+
Scenario: Changing password, active/deactivating 2FA, and generating API Keys
Given I log in as the new user name
@@ -18,12 +17,12 @@ Scenario: Changing password, active/deactivating 2FA, and generating API Keys
And I generate API key
And I request to change the password
Then I logout successfully
-@waitings
+
Scenario: Confirm password change by email confirmation
When I confirm the transfer by Email
Then I receive a successful message
-@waitings
+
Scenario: Successful Login with New Password
Given I am on the Hollaex login page and enter credentials
@@ -39,7 +38,7 @@ Scenario: Successful Login with New Password
When I confirm the transfer by Email
Then I receive a successful message
-
+
Scenario: Changing deactivating 2FA by Admin
Given I log in as the new user name
diff --git a/test/Cypress/cypress/integration/Gherkin/security/security.js b/test/Cypress/cypress/integration/Gherkin/security/security.js
index 19fa56495c..6eaa242809 100644
--- a/test/Cypress/cypress/integration/Gherkin/security/security.js
+++ b/test/Cypress/cypress/integration/Gherkin/security/security.js
@@ -1,45 +1,32 @@
import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
const totp = require("totp-generator");
const randomUsername = Math.random().toString(36).substring(2,6);
-var username = randomUsername+Cypress.env('NEW_USER')
+var username = "tester+"+randomUsername+Cypress.env('NEW_USER')
-Given ('I sign up with a random username and the current password',()=>{
+Given ('Admin creats a user with a random username and the current password',()=>{
cy.log(username);
- cy.visit(Cypress.env('SIGN_UP_PAGE'))
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.visit(Cypress.env('LOGIN_PAGE'))
cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('.checkfield-input').should('not.be.checked')
- cy.get('[name="email"]').clear().type(username)
- cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
- cy.get('[name="password_repeat"]').clear().type(Cypress.env('PASSWORD'))
- cy.get('.checkfield-input').should('be.enabled').check();
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
- cy.get('.warning_text').should("not.be.always.exist")
- cy.get('.icon_title-text').should('contain', 'Email sent')
-})
-
-When ('Admin confirms the new user',()=>{
+ cy.get('.warning_text').should('not.exist')
+ cy.contains('Operator controls').click()
+ cy.contains('Users').click()
+ cy.contains('Add new user').click()
+ cy.get('#addUser_userEmail').clear().type(username)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.wait(3000)
+ cy.contains(username)
- cy.visit(Cypress.env('LOGIN_PAGE'))
- cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
- cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
- cy.get('.holla-button').should('be.visible').should('be.enabled').click()
- cy.get('.warning_text').should('not.exist')
-// cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
-// .should('contain',Cypress.env('ADMIN_USER'))
- cy.contains('Operator controls').click()
- cy.contains('Users').click({force: true})
- cy.get('.ant-input').type(username)
- cy.get('.ant-btn').click()
- cy.get(':nth-child(2) > :nth-child(1) > .info-link').click()
- cy.get('.mb-4 > :nth-child(2)')
- .should('contain','Are you sure you want to mark this email as verified?')
- cy.get(':nth-child(1) > :nth-child(3) > .ant-btn').click()
})
-Then ('I registered with the new username',()=>{
+Then ('The new username is stored',()=>{
cy.writeFile('cypress\\fixtures\\example.json', { name: 'Newuser', email: username })
})
@@ -61,7 +48,7 @@ Given ('I log in as the new user name',()=>{
When ('I active 2FA',()=>{
- cy.get('.app-menu-bar-side > :nth-child(5)').as('Car Keys').click({force:true})
+ cy.contains('Security').as('Car Keys').click({force:true})
cy.get('.checkbutton-input-wrapper').as('enable').click({force:true})
cy.get(':nth-child(3) > .otp_form-section-content').invoke('text')
.then(text => {
@@ -93,8 +80,6 @@ And ('I generate API key',()=>{
})
And ('I request to change the password',()=>{
- //this is a minor bug should be considered reload should be part of activation
- cy.reload()
cy.get('.tab_controller-tabs > :nth-child(2) > div').as('password section').click()
cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
.as('current password').clear().type(Cypress.env('PASSWORD'))
@@ -108,12 +93,13 @@ And ('I request to change the password',()=>{
let token = totp(user.code);
cy.log(token)
cy.log('second', user.code)
- cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
- .clear().type(token)
- cy.get('.otp_form-wrapper > form.w-100 > .holla-button').click()
+ cy.get('.masterInput')
+ .clear().type(token)
+ cy.contains('An email is sent to you to authorize the password change.').should('exist')
+ cy.get('.success_display-wrapper > .holla-button').click()
})
- //cy.contains('An email is sent to you to authorize the password change.').should('exist')
- cy.get('.success_display-wrapper > .holla-button').click()
+
+
})
@@ -123,29 +109,56 @@ Then ('I logout successfully',()=>{
})
When ('I confirm the transfer by Email',()=>{
+ cy.visit(Cypress.env('EMAIL_PAGE'));
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox Change Password Confirmation');
cy.fixture('example')
.then((user)=>{
- username = user.email
- })
- let link;
- var text = null
- cy.visit(Cypress.env('EMAIL_PAGE'))
- cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'))
- cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'))
- cy.get('#wdc_login_button').click();
- cy.get('#ext-gen52').click()
- cy.log('created new user')
- cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
- .dblclick()
- cy.wait(5000)
- cy.then(()=>{
- text = cy.getIframe('.preview-iframe').should('not.null').toString()
- })
- .then((text)=> link= cy.trimmer(text,Cypress.env('EMAIL_CONFIRM'),username))
- .should('not.be.false').log("link is :"+link)
-
- .then((link )=>cy.forceVisit(link))
- cy.wait(3000)
+ cy.get('.giraffe-emailaddress-link').last().contains(user.email);
+ })
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('You have made a request to change the password for your account.');
+
+ // Get all the links with "https" protocol from the email body
+ cy.get('@emailBody')
+ .find('a')
+ .then(($links) => {
+ const httpsLinks = [];
+ $links.each((index, link) => {
+ const href = link.href;
+ if (href && href.startsWith('https')) {
+ httpsLinks.push(href);
+ }
+ });
+ cy.wrap(httpsLinks[1]).as('httpsLink');
+ });
+
+ // Log the list of https links
+ cy.get('@httpsLink')
+ .then((httpsLink) => {
+ console.log(httpsLink);
+ cy.forceVisit(httpsLink);
+ });
+
})
Then ('I receive a successful message',()=>{
@@ -173,43 +186,38 @@ Then ('I should be able to login successfully',()=>{
cy.wrap(token).as('token')
cy.log(token);
cy.log('second', user.code)
- cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ cy.get('.masterInput')
.clear().type(token)
- cy.get('.otp_form-wrapper > form.w-100 > .holla-button').click()
- // cy.writeFile('cypress\\fixtures\\2fa.json', {})
})
cy.fixture('example')
.then((user)=>{
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',user.email)
- // cy.writeFile('cypress\\fixtures\\example.json', {})
})
-
+ })
And ('I deactivate 2FA',()=>{
- //cy.get('.app-menu-bar-side > :nth-child(5)').click()
- cy.contains('2FA').click({force:true})
- cy.get('.checkbutton-input-wrapper').as('disable').click({force:true})
- cy.contains('Enter your authentication code to continue').should('exist')
+ cy.reload()
+ cy.contains('2FA').click()
+ cy.get('.checkbutton-input-wrapper').as('disable').click()
cy.fixture('2fa')
.then((user)=>{
const token = totp(user.code);
cy.log(token)
cy.wrap(token).as('token')
- cy.get('.input_field-input').clear().type(token)
+ cy.get('.masterInput').clear().type(token)
cy.log(token);
- cy.get('.holla-button').click()
+ cy.contains('You have successfully deactivated 2FA').should('exist')
+ cy.get('.holla-button').click()
+
})
- cy.contains('You have successfully deactivated 2FA').should('exist')
- cy.get('.holla-button').click()
cy.reload()
+
})
-})
-
And ('I enter incorrect credentials',()=>{
- cy.get('.app-menu-bar-side > :nth-child(5)').click()
+ cy.get('.app-menu-bar-side > :nth-child(5)').click()
cy.get('.tab_controller-tabs > :nth-child(2) > div').as('password section').click()
cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
.as('current password').clear().type(Cypress.env('NEWPASS')+"wrong")
@@ -225,22 +233,17 @@ And ('I enter incorrect credentials',()=>{
cy.wrap(token).as('token')
cy.log(token);
cy.log('second', user.code)
- cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ cy.get('.masterInput')
.clear().type(token)
- cy.get('.otp_form-wrapper > form.w-100 > .holla-button').click()
+
})
- //cy.contains('An email is sent to you to authorize the password change.').should('exist')
- //cy.get('.success_display-wrapper > .holla-button').should('be.disabled')
- cy.get('.warning_text').contains('Credentials incorrect')
- cy.get('.action_notification-image').click()
-
+ cy.get('.warning_text').contains('Incorrect credentials.')
+
})
And ('I enter new password as same as the previous password',()=>{
- // cy.get('.app-menu-bar-side > :nth-child(5)').click()
- //cy.get('.tab_controller-tabs > :nth-child(2) > div').as('password section').click()
cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
.as('current password').clear().type(Cypress.env('NEWPASS'))
cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
@@ -255,18 +258,15 @@ And ('I enter new password as same as the previous password',()=>{
cy.wrap(token).as('token')
cy.log(token);
cy.log('second', user.code)
- cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ cy.get('.masterInput')
.clear().type(token)
- cy.get('.otp_form-wrapper > form.w-100 > .holla-button').click()
+
})
- //cy.get('.success_display-wrapper > .holla-button').should('be.disabled')
cy.get('.warning_text').contains('New password must be different from previous password')
- cy.get('.action_notification-image').click()
+
})
And ('I enter dismatched password',()=>{
- //cy.get('.app-menu-bar-side > :nth-child(5)').click()
- //cy.get('.tab_controller-tabs > :nth-child(2) > div').as('password section').click()
cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
.as('current password').clear().type(Cypress.env('NEWPASS'))
cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
@@ -280,7 +280,7 @@ And ('I enter dismatched password',()=>{
})
Then ('I request to change the password to the previous password',()=>{
- // cy.get('.app-menu-bar-side > :nth-child(5)').click()
+
cy.get('.tab_controller-tabs > :nth-child(2) > div').as('password section').click()
cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
.as('current password').clear().type(Cypress.env('NEWPASS'))
@@ -294,7 +294,6 @@ Then ('I request to change the password to the previous password',()=>{
})
-
When ('Admin deactives the 2fa of new user',()=>{
cy.fixture('example')
.then((user)=>{
@@ -306,23 +305,22 @@ When ('Admin deactives the 2fa of new user',()=>{
cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- // cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
- // .should('contain',Cypress.env('ADMIN_USER'))
cy.contains('Operator controls').click()
cy.contains('Users').click({force: true})
- cy.get('.ant-input').type(username)
- cy.get('.ant-btn').click()
+ cy.get(':nth-child(2) > .ant-input').type(username)
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(3000)
+ cy.get(':nth-child(8) > .ant-btn').click()
- cy.get(':nth-child(3) > .about-info-content > :nth-child(1)').contains('2FA enabled')
- cy.get(':nth-child(3) > .about-info-content > .info-link').click()
+ cy.get(':nth-child(4) > .about-info-content > :nth-child(1)').contains('2FA enabled')
+ cy.get(':nth-child(4) > .about-info-content > .info-link').click()
cy.get('.mt-3').contains('Are you sure want to disable 2FA for this account?')
cy.get('.ant-modal-confirm-btns > .ant-btn-primary').click()
- cy.get('.my-5 > :nth-child(3) > :nth-child(1) > div').contains('2FA disabled')
+ cy.get('.my-5 > :nth-child(4) > :nth-child(1) > div').contains('2FA disabled')
cy.contains('Logout').click()
})
-
Then ('The activation code is different',()=>{
cy.get('.app-menu-bar-side > :nth-child(5)').as('Car Keys').click({force:true})
diff --git a/test/Cypress/cypress/integration/Gherkin/setting/setting.js b/test/Cypress/cypress/integration/Gherkin/setting/setting.js
index 074d89f01e..6fc9e11711 100644
--- a/test/Cypress/cypress/integration/Gherkin/setting/setting.js
+++ b/test/Cypress/cypress/integration/Gherkin/setting/setting.js
@@ -2,13 +2,29 @@ import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
Given ('I logged in Hollaex',()=>{
- cy.visit(Cypress.env('LOGIN_PAGE'))
- cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
- cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
- cy.get('.holla-button').should('be.visible').should('be.enabled').click()
- cy.get('.warning_text').should('not.exist')
- cy.get('.app-menu-bar-side > :nth-child(7)').click()
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.wait(5000)
+ cy.get('.ant-select-selection-item > .language_option').invoke('text').then((languageText) => {
+ if (languageText === 'en') {
+ // Perform desired actions if language is 'en'
+ cy.log('Language is already set to English');
+ } else {
+ // Perform desired actions if language is not 'en'
+ cy.log('You should select English language first');
+ cy.log('Exiting the test...',languageText);
+ cy.get('.language_option').click()
+ cy.get('.ant-select-item-option-content > .language_option')
+ .contains('en').click()
+
+ }
+ });
+
+ cy.contains('Settings').click()
})
When ('I change language, it should change',()=>{
@@ -22,55 +38,74 @@ When ('I change language, it should change',()=>{
cy.get('.d-flex > .field-label')
.should('contain','Preferencia de Idioma (Incluye correos electrònicos)')
cy.get('@LangInput').click()
+ cy.get('.language_option').contains('es')
- cy.get('#language-pt-2').click()
+ cy.get('#language-de-2').click()
cy.get('.holla-button').click()
- cy.get('.d-flex > .field-label')
- .should('contain','Preferências de idioma (inclui e-mails)')
+ cy.contains('Spracheinstellungen (schließt E-Mails ein)')
cy.get('@LangInput').click()
- cy.get('#language-ru-3').click()
+ cy.get('.language_option').contains('de')
+
+ cy.get('#language-it-3').click()
cy.get('.holla-button').click()
- cy.get('.d-flex > .field-label').should('contain','Language preferences (Includes Emails)')
+ cy.contains('Preferenze lingua (include email)')
cy.get('@LangInput').click()
-
- cy.get('#language-ko-4').click()
+ cy.get('.language_option').contains('it')
+
+
+ cy.get('#language-pt-4').click()
cy.get('.holla-button').click()
cy.get('.d-flex > .field-label')
- .should('contain','언어설정 (이메일수신포함)')
+ .should('contain','Preferências de idioma (inclui e-mails)')
cy.get('@LangInput').click()
-
- cy.get('#language-ja-5').click()
+ cy.get('.language_option').contains('pt')
+
+ cy.get('#language-tr-5').click()
cy.get('.holla-button').click()
cy.get('.d-flex > .field-label')
- .should('contain','言語設定 (メールを含む)')
+ .should('contain','Dil seçenekleri (E-Postaları da kapsar)')
cy.get('@LangInput').click()
-
- cy.get('#language-zh-6').click()
+ cy.get('.language_option').contains('tr')
+
+ cy.get('#language-ko-6').click()
cy.get('.holla-button').click()
cy.get('.d-flex > .field-label')
- .should('contain','语言设置(包括邮件)')
+ .should('contain','언어설정 (이메일수신포함)')
+ cy.get('@LangInput').click()
+ cy.get('.language_option').contains('ko')
+
+ cy.get('#language-fa-7').click()
+ cy.get('.holla-button').click()
+ cy.get('.d-flex > .field-label').should('contain','انتخاب زبان (این گزینه شامل ایمیل های ارسالی از طرف HollaEx نیز می شود)')
cy.get('@LangInput').click()
+ cy.get('.language_option').contains('fa')
- cy.get('#language-vi-7').click()
+ cy.get(' #language-ar-8').click()
cy.get('.holla-button').click()
cy.get('.d-flex > .field-label')
- .should('contain','Cài đặt ngôn ngữ (Bao gồm email)')
+ .should('contain','تفضيلات اللغة (تشمل رسائل البريد الإلكتروني)')
cy.get('@LangInput').click()
+ cy.get('.language_option').contains('ar')
- cy.get('#language-fa-8').click()
+ cy.get('#language-mn-9').click()
cy.get('.holla-button').click()
- cy.get('.d-flex > .field-label').should('contain','انتخاب زبان (این گزینه شامل ایمیل های ارسالی از طرف HollaEx نیز می شود)')
+ cy.get('.d-flex > .field-label')
+ .should('contain','Хэлний сонголт')
cy.get('@LangInput').click()
-
- cy.get(' #language-ar-9').click()
+ cy.get('.language_option').contains('mn')
+
+ cy.get('#language-ur-10').click()
cy.get('.holla-button').click()
cy.get('.d-flex > .field-label')
- .should('contain','تفضيلات اللغة (تشمل رسائل البريد الإلكتروني)')
+ .should('contain','زبان کی ترجیحات (بشمول ای میلز)')
cy.get('@LangInput').click()
+ cy.get('.language_option').contains('ur')
+
cy.get('#language-en-0').click()
cy.get('.holla-button').click()
cy.get('.d-flex > .field-label').should('contain','Language preferences (Includes Emails)')
+ cy.get('.language_option').contains('en')
})
diff --git a/test/Cypress/cypress/integration/Gherkin/signup/signup.js b/test/Cypress/cypress/integration/Gherkin/signup/signup.js
index b7f25065eb..9a79e04b38 100644
--- a/test/Cypress/cypress/integration/Gherkin/signup/signup.js
+++ b/test/Cypress/cypress/integration/Gherkin/signup/signup.js
@@ -1,6 +1,6 @@
import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
const randomUsername = Math.random().toString(36).substring(2,6);
-const username = randomUsername+Cypress.env('NEW_USER')
+const username = "tester+"+randomUsername+Cypress.env('NEW_USER')
//Background:
Given ('user on the homepage',()=>{
@@ -49,13 +49,14 @@ When ('{string} confirms {string}',(admin, randomaUsername)=>{
cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('ADMIN_USER'))
cy.contains('Operator controls').click()
cy.contains('Users').click({force: true})
- cy.get('.ant-input').type(username)
- cy.get('.ant-btn').click().wait(4000)
- cy.get(':nth-child(2) > :nth-child(1) > .info-link').click()
+ cy.get(':nth-child(2) > .ant-input').type(username)
+ cy.get(':nth-child(2) > .ant-btn').click().wait(3000)
+ cy.get(':nth-child(8) > .ant-btn').click()
+ cy.get('.my-5 > :nth-child(3) > :nth-child(1) > .info-link').click()
cy.get('.mb-4 > :nth-child(2)').should('contain','Are you sure you want to mark this email as verified?')
cy.get(':nth-child(1) > :nth-child(3) > .ant-btn').click()
cy.contains('Logout').click()
@@ -86,7 +87,6 @@ When ("user fills registration email textbox with {string} and {string}",
And ('user enters the {string}',(weakPassword)=>{
cy.get('.holla-button').should('be.visible').should('be.disabled')
- // cy.get('.checkfield-input').should('not.be.checked')
cy.get('[name="email"]').clear().type(username)
cy.get('[name="password"]').clear().type('weakpassword')
cy.get('.field-error-text')
@@ -94,13 +94,11 @@ And ('user enters the {string}',(weakPassword)=>{
cy.get('[name="password_repeat"]').clear().type('weakpassword')
cy.get('.checkfield-input').should('be.enabled').check();
cy.get('.holla-button').should('not.be.enabled')
- // cy.get('.warning_text').should('contain','User already exists')
})
And ('user enters the {string} as well',(diffrentPassword)=>{
cy.get('.holla-button').should('be.visible').should('be.disabled')
- //cy.get('.checkfield-input').should('not.be.checked')
cy.get('[name="email"]').clear().type(username)
cy.get('[name="password"]').clear().type('diffrentPassword!1')
cy.get('[name="password_repeat"]').clear().type('diffrentPassword!2')
@@ -108,7 +106,7 @@ And ('user enters the {string} as well',(diffrentPassword)=>{
.contains("Password don't match")
cy.get('.checkfield-input').should('be.enabled').check();
cy.get('.holla-button').should('not.be.enabled')
- //cy.get('.warning_text').should('contain','User already exists')
+
})
diff --git a/test/Cypress/cypress/integration/Gherkin/signupWithEmail/signupWithEmail.js b/test/Cypress/cypress/integration/Gherkin/signupWithEmail/signupWithEmail.js
index 34a111d990..061784aa13 100644
--- a/test/Cypress/cypress/integration/Gherkin/signupWithEmail/signupWithEmail.js
+++ b/test/Cypress/cypress/integration/Gherkin/signupWithEmail/signupWithEmail.js
@@ -1,6 +1,6 @@
import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
const randomUsername = Math.random().toString(36).substring(2,6);
-const username = randomUsername+Cypress.env('NEW_USER')
+const username = 'tester+'+randomUsername+Cypress.env('NEW_USER')
Given ('I am in Hollaex signup page',()=>{
@@ -28,23 +28,60 @@ Then ('I get a success notification',()=>{
When ('I confirm the registration by Email',()=>{
- let text= null
- var link;
cy.visit(Cypress.env('EMAIL_PAGE'))
- cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'))
- cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'))
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
cy.get('#wdc_login_button').click();
- cy.get('#ext-gen52').click()
- cy.log('created new user')
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
- .dblclick()
- cy.wait(5000)
- cy.then(()=>{ text = cy.getIframe('.preview-iframe').should('not.null').toString()})
- .then((text)=> link= cy.trimmer(text,"https://sandbox.hollaex.com/verify",username))
- .should('not.be.false')
- .then((link )=>cy.forceVisit(link))
- cy.contains('Success').should('exist')
- cy.log("link is ", link )
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox Sign Up');
+ cy.fixture('example')
+ .then((user)=>{
+ cy.get('.giraffe-emailaddress-link').last().contains(user.email);
+ })
+
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('You need to confirm your email account by clicking the button below.');
+
+ // Get all the links with "https" protocol from the email body
+ cy.get('@emailBody')
+ .find('a')
+ .then(($links) => {
+ const httpsLinks = [];
+ $links.each((index, link) => {
+ const href = link.href;
+ if (href && href.startsWith('https')) {
+ httpsLinks.push(href);
+ }
+ });
+ cy.wrap(httpsLinks[1]).as('httpsLink');
+ });
+
+ // Log the list of https links
+ cy.get('@httpsLink')
+ .then((httpsLink) => {
+ console.log(httpsLink);
+ cy.forceVisit(httpsLink);
+ });
+ cy.contains('Confirm Sign Up').should('exist')
+ cy.contains('CONFIRM SIGN UP').click()
+ cy.contains('Success').should('exist')
+
})
Then ('I am eligible to log in',()=>{})
@@ -66,12 +103,15 @@ When ('I enter credentials',()=>{
})
})
-Then ('I should be able to login successfully',()=>{
+Then ('I should be able to login successfully and Verification email should be the same',()=>{
cy.fixture('example')
.then((user)=>{
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain', user.email )
+ cy.contains('Verification').click()
+ cy.contains('Email').click()
+ cy.get('.information-content').should('contain', user.email )
cy.writeFile('cypress\\fixtures\\example.json', {})
})
})
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/trade.feature b/test/Cypress/cypress/integration/Gherkin/trade.feature
index 4dcbbb0f05..75ebdd25ad 100644
--- a/test/Cypress/cypress/integration/Gherkin/trade.feature
+++ b/test/Cypress/cypress/integration/Gherkin/trade.feature
@@ -3,16 +3,16 @@ Feature: Trade Feature
As a valid customer
In order to Trade
I want to login successfully make and take orders
-@waiting
+
Scenario: Successful Log in and cancell orders
Given I am in the Hollaex login page
When I enter credentials to log in successfully
And I should be able to redirect to "XHT/USDT" page and cancel all open orders
And I check the highest and lowest prices
- When I make buy orders "4" times
- When I make sell orders "4" times
- And I take sell orders "4" times
- And I take buy orders "4" times
+ When I make buy orders "1" times
+ When I make sell orders "1" times
+ And I take sell orders "1" times
+ And I take buy orders "1" times
When I fill "1" of "2" in an order partially
Then I will see the "1" / "2" percentage
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/trade/trade.js b/test/Cypress/cypress/integration/Gherkin/trade/trade.js
index 60bbefe202..889e0e081f 100644
--- a/test/Cypress/cypress/integration/Gherkin/trade/trade.js
+++ b/test/Cypress/cypress/integration/Gherkin/trade/trade.js
@@ -9,8 +9,8 @@ Given ('I am in the Hollaex login page',()=>{
When ('I enter credentials to log in successfully',()=>{
cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
- cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('[name="email"]').clear().type(Cypress.env("Alice"))
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
})
@@ -20,8 +20,8 @@ And ('I should be able to redirect to {string} page and cancel all open orders',
cy.contains('Pro trade').click()
cy.contains(tradePage).click()
cy.get('.app_bar-currency-txt').should('have.text', 'XHT/USDT:')
- //cy.get('[style="width: 801px; height: 390px; position: absolute; transform: translate(10px, 1210px);"] > .trade_block-wrapper > .trade_block-title > .justify-content-between > .d-flex > .trade_block-title-items > .edit-wrapper__container')
- cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-title > .justify-content-between > .d-flex > .trade_block-title-items > .edit-wrapper__container')
+ cy.get('.trade_block-wrapper > .trade_block-title > .justify-content-between > .d-flex > .trade_block-title-items > .edit-wrapper__container')
+ .contains('Open orders')
.invoke('text').then(text => {
var fullText = text;
var pattern = /[0-9]+/g;
@@ -37,6 +37,23 @@ And ('I should be able to redirect to {string} page and cancel all open orders',
})
And ('I check the highest and lowest prices',()=>{
+ function clickUntilDepthIs01() {
+ // Check if the condition is met
+ cy.get('.trade_orderbook-depth').then(($depth) => {
+ if ($depth.text().includes('0.01')) {
+ // If the condition is met, then do nothing (or perform any necessary actions)
+ return;
+ } else {
+ // If the condition is not met, click the element and call the function again
+ cy.get('.trade_orderbook-depth-selector > :nth-child(3)').click();
+ clickUntilDepthIs01(); // Recursively call the function
+ }
+ });
+ }
+
+ // Start the clicking process
+ clickUntilDepthIs01();
+
cy.get('.trade_orderbook-depth').contains('0.01')
cy.log('check lowest aks exist and click on the price will send the price')
cy.get('.trade_orderbook-asks > :nth-child(1) > .d-flex > .trade_orderbook-cell-price')
@@ -50,14 +67,15 @@ And ('I check the highest and lowest prices',()=>{
.should('be.visible').as('highestBuy')
cy.get('.form-error').should('not.be.exist')
cy.get('.trade_order_entry-form_fields-wrapper').click()
- cy.wallectCheck('sell','HollaEx',0.01,0,0.26)
- cy.wallectCheck('buy','USD Tether',0.01,0,0.26)
+ cy.wallectCheck('sell','HollaEx',0,0,0.26)
+ cy.wallectCheck('buy','USD Tether',0,0.0001,0.26)
+
})
When ('I make buy orders {string} times',(orderTime)=>{
- cy.get('@highestBuy').click()
- for (var i = 1; i < Number(orderTime)+1; i++)
+ cy.get('@highestBuy').click()
+ for (var i = 1; i < Number(orderTime)+1; i++)
{
cy.get('[name="price"]').type('{upArrow}').invoke('val')
.then(text => {
@@ -66,13 +84,13 @@ When ('I make buy orders {string} times',(orderTime)=>{
})
cy.get('[name="size"]').clear().type(i)
cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .buy')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(3) > .buy')
.contains('buy')
cy.get('@currentPrice')
.then(val=> {
- cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
-
- .should('contain', val)
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(5)')
+ .should('contain', val)
})
cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(6)')
.contains(i)
@@ -81,57 +99,80 @@ When ('I make buy orders {string} times',(orderTime)=>{
When ('I make sell orders {string} times',(orderTime)=>{
- cy.get('.trade_order_entry-action_selector > :nth-child(2)').click()
- cy.get('@lowestSell').click()
- for (var i = 1; i < Number(orderTime)+1; i++)
- {
- cy.get('[name="price"]').type('{downArrow}').invoke('val')
- .then(text => {
- cy.log(text)
- cy.wrap(text).as('currentPrice')
- })
- cy.get('[name="size"]').clear().type(i)
- cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .sell')
- .contains('sell')
- cy.get('@currentPrice')
- .then(val=> {
- cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
- .should('contain', val)
- // cy.get('.wallet-wrapper > :nth-child(2) > :nth-child(3)').click()
- // cy.contains('HOLLAEX', {matchCase: false}).click().log("wallet opened")//.pause()
- })
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(6)')
- .contains(i)
- // cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(4)')
- }
+ cy.get('.trade_order_entry-action_selector > :nth-child(2)').click()
+ cy.get('@lowestSell').click()
+ for (var i = 1; i < Number(orderTime)+1; i++)
+ {
+ cy.get('[name="price"]').type('{downArrow}').invoke('val')
+ .then(text => {
+ cy.log(text)
+ cy.wrap(text).as('currentPrice')
+ })
+ cy.get('[name="size"]').clear().type(i)
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .general-record-enter-done > :nth-child(3) > .sell')
+ .contains('sell')
+ cy.get('@currentPrice')
+ .then(val=> {
+ //cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ .should('contain', val)
+ // cy.get('.wallet-wrapper > :nth-child(2) > :nth-child(3)').click()
+ // cy.contains('HOLLAEX', {matchCase: false}).click().log("wallet opened")//.pause()
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(6)')
+ .contains(i)
+ // cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(4)')
+ }
})
And ('I take sell orders {string} times',(orderTime)=>{
- cy.get('.trade_order_entry-action_selector > :nth-child(1)').click()
- for (var i = Number(orderTime); i>0; i--)
- {
- // cy.get('@highestBuy')
- cy.get('.trade_orderbook-asks > :nth-child(1) > .d-flex > .trade_orderbook-cell-price')
- .click().invoke('text')
- .then(text => {
- cy.log(text)
- cy.wrap(text).as('currentPrice')
- })
- cy.get('[name="size"]').clear().type(i)
- cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .sell')
- .contains('sell').wait(2000)
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)').first()
- .contains(i)
- cy.get(':nth-child(1) > :nth-child(2) > .trade_history-row')
- .contains(i)
- cy.get('@currentPrice')
- .then(val=> {
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(4)')
- .should('contain', val)
- })
+
+ function clickUntilDepthIs00001() {
+ // Check if the condition is met
+ cy.get('.trade_orderbook-depth').then(($depth) => {
+ if ($depth.text().includes('0.00001')) {
+ // If the condition is met, then do nothing (or perform any necessary actions)
+ return;
+ } else {
+ // If the condition is not met, click the element and call the function again
+ cy.get('.trade_orderbook-depth-selector > :nth-child(1)').click();
+ clickUntilDepthIs00001(); // Recursively call the function
}
+ });
+ }
+
+ // Start the clicking process
+ clickUntilDepthIs00001();
+
+ cy.get('.trade_orderbook-depth').contains('0.00001')
+ cy.get('.trade_order_entry-action_selector > :nth-child(1)').click()
+ for (var i = Number(orderTime); i>0; i--)
+ {
+ // cy.get('@highestBuy')
+ cy.get('.trade_orderbook-asks > :nth-child(1) > .d-flex > .trade_orderbook-cell-price')
+ .click().invoke('text')
+ .then(text => {
+ cy.log(text)
+ cy.wrap(text).as('currentPrice')
+ })
+ cy.get('[name="size"]').clear().type(i)
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ cy.get('.notification-content-wrapper > .holla-button').click()//notification
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .sell')
+ .contains('sell').wait(2000)
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)').first()
+ .contains(i)
+ cy.get(':nth-child(1) > :nth-child(2) > .trade_history-row')
+ .contains(i)
+ cy.get('@currentPrice')
+ .then(val=> {
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(4)')
+ .should('contain', val)
+ })
+ }
})
And ('I take buy orders {string} times',(orderTime)=>{
@@ -148,6 +189,8 @@ And ('I take buy orders {string} times',(orderTime)=>{
})
cy.get('[name="size"]').clear().type(i)
cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ cy.get('.notification-content-wrapper > .holla-button').click()//notification
cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .buy')
.contains('buy').wait(2000)
cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)').first()
@@ -174,12 +217,15 @@ When ('I fill {string} of {string} in an order partially',(portion,whole)=>{
})
cy.get('[name="size"]').clear().type(size)
cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ //cy.get('.notification-content-wrapper > .holla-button').click()//notification
cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .sell')
.contains('sell')
cy.get('@currentPrice')
.then(val=> {
- cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
- .should('contain', val)
+ // cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(5)')
+ .should('contain', val)
cy.wallectCheck('sell','HollaEx',0.01,size,val)
})
cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(6)')
@@ -187,12 +233,16 @@ When ('I fill {string} of {string} in an order partially',(portion,whole)=>{
cy.get('.trade_order_entry-action_selector > :nth-child(1)').click()
cy.get('[name="size"]').clear().type((size-Number(portion)))
cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .buy')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ cy.get('.notification-content-wrapper > .holla-button').click()//notification
+ // cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .buy')
+ cy.get(':nth-child(2) > .cell_box-type > .buy')
.contains('buy')
cy.get('@currentPrice')
.then(val=> {
- cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
- .should('contain', val)
+ // cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(5)')
+ .should('contain', val)
})
cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)')
.contains((size-Number(portion)))
@@ -213,18 +263,15 @@ Then ('I will see the {string} / {string} percentage',(portion,whole)=>{
expect(Number(nmb)).to.equal((Number(portion)/Number(whole))*100)
})
- cy.wait(2000)
- cy.get('.trade__active-orders_cancel-All').click()
- cy.get(':nth-child(2) > .w-100 > :nth-child(3)').click()
- cy.wait(2000)
- cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-title > .justify-content-between > .d-flex > .trade_block-title-items > .edit-wrapper__container')
- .invoke('text').then(text => {
- var fullText = text;
- var pattern = /[0-9]+/g;
- var nmb = fullText.match(pattern);
- cy.wrap(nmb).as('openOrder')
- cy.log(nmb);
- cy.log('second', text)
- expect(Number(nmb)).to.equal(Number('0'))
- })
+
+ cy.contains('Open orders')
+ .invoke('text').then(text => {
+ var fullText = text;
+ var pattern = /[0-9]+/g;
+ var nmb = fullText.match(pattern);
+ cy.wrap(nmb).as('openOrder')
+ cy.log(nmb);
+ cy.log('second', text)
+ expect(Number(nmb)).to.equal(Number('1'))
+ })
})
diff --git a/test/Cypress/cypress/integration/Gherkin/tradePerf.feature b/test/Cypress/cypress/integration/Gherkin/tradePerf.feature
new file mode 100644
index 0000000000..a8e417f576
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/tradePerf.feature
@@ -0,0 +1,18 @@
+Feature: Trade Feature
+
+As a valid customer
+In order to Trade
+I want to login successfully make and take orders
+
+Scenario: Successful Log in and cancell orders
+
+ Given I am in the Hollaex login page as a trader
+ And I should be able to redirect to "XHT/USDT" page and cancel all open orders
+ And I check the highest and lowest prices
+ When I make buy orders "1" times less than 1.1 second
+ When I make sell orders "1" times less than 0.55 second
+ And I take sell orders "1" times less than 0.45 second
+ And I take buy orders "1" times less than 1.5 second
+ When I fill "1" of "2" in an order partially less than 0.45 second
+ Then I will cancel the order of "1" / "2" percentage less than 1.2 second
+
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/tradePerf/tradePerf.js b/test/Cypress/cypress/integration/Gherkin/tradePerf/tradePerf.js
new file mode 100644
index 0000000000..ad6b9f0667
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/tradePerf/tradePerf.js
@@ -0,0 +1,353 @@
+import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
+
+Given ('I am in the Hollaex login page as a trader',()=>{
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env("Alice"))
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+})
+
+And ('I should be able to redirect to {string} page and cancel all open orders',(tradePage)=>{
+
+ cy.contains('Pro trade').click()
+ cy.contains(tradePage).click()
+ cy.get('.app_bar-currency-txt').should('have.text', 'XHT/USDT:')
+ cy.get('.trade_block-wrapper > .trade_block-title > .justify-content-between > .d-flex > .trade_block-title-items > .edit-wrapper__container')
+ .contains('Open orders')
+ .invoke('text').then(text => {
+ var fullText = text;
+ var pattern = /[0-9]+/g;
+ var nmb = fullText.match(pattern);
+ cy.wrap(nmb).as('openOrder')
+ cy.log(nmb);
+ cy.log('second', text)
+ if (nmb > 0){
+ cy.get('.trade__active-orders_cancel-All').click()
+ cy.get(':nth-child(2) > .w-100 > :nth-child(3)').click()
+ }
+ })
+})
+
+And ('I check the highest and lowest prices',()=>{
+ function clickUntilDepthIs01() {
+ // Check if the condition is met
+ cy.get('.trade_orderbook-depth').then(($depth) => {
+ if ($depth.text().includes('0.01')) {
+ // If the condition is met, then do nothing (or perform any necessary actions)
+ return;
+ } else {
+ // If the condition is not met, click the element and call the function again
+ cy.get('.trade_orderbook-depth-selector > :nth-child(3)').click();
+ clickUntilDepthIs01(); // Recursively call the function
+ }
+ });
+ }
+
+ // Start the clicking process
+ clickUntilDepthIs01();
+
+ cy.get('.trade_orderbook-depth').contains('0.01')
+ cy.log('check lowest aks exist and click on the price will send the price')
+ cy.get('.trade_orderbook-asks > :nth-child(1) > .d-flex > .trade_orderbook-cell-price')
+ .as('lowestSell').should('be.visible')
+ .click().then(function($elem) { cy.focused().should('have.value', $elem.text())
+ })
+ cy.get('@lowestSell').should('be.visible')
+ .click().then(function($elem) { cy.focused().should('have.value', $elem.text()) })
+ cy.get('.trade_orderbook-asks')
+ cy.get('.trade_orderbook-bids > :nth-child(1) > .d-flex > .trade_orderbook-cell-price')
+ .should('be.visible').as('highestBuy')
+ cy.get('.form-error').should('not.be.exist')
+ cy.get('.trade_order_entry-form_fields-wrapper').click()
+
+})
+
+When ('I make buy orders {string} times less than {float} second',(orderTime,timeRange)=>{
+ let t0, t1, t2; // Declare t0 and t1 outside the loop
+ cy.get('@highestBuy').click()
+ for (var i = 1; i < Number(orderTime)+1; i++)
+ {
+ cy.get('[name="price"]').type('{upArrow}').invoke('val')
+ .then(text => {
+ cy.log(text)
+ cy.wrap(text).as('currentPrice')
+ })
+ cy.get('[name="size"]').clear().type(i)
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ .then(() => {
+ t0 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t0);
+ });
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(3) > .buy')
+ .contains('buy')
+ .then(() => {
+ t1 = performance.now();
+ // You can use `t0` here for further actions or logging
+ cy.log('Time recorded:', t1);
+ });
+ cy.get('@currentPrice')
+ .then(val=> {
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(5)')
+ .should('contain', val)
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(6)')
+ .contains(i)
+ }
+ cy.then(() => {
+ const time = (t1 - t0)/1000;
+ cy.log(time,timeRange)
+ expect(time).to.be.lessThan(timeRange)
+ });
+
+})
+
+
+When ('I make sell orders {string} times less than {float} second',(orderTime,timeRange)=>{
+ let t0, t1, t2; // Declare t0 and t1 outside the loop
+ cy.get('.trade_order_entry-action_selector > :nth-child(2)').click()
+ cy.get('@lowestSell').click()
+ for (var i = 1; i < Number(orderTime)+1; i++)
+ {
+ cy.get('[name="price"]').type('{downArrow}').invoke('val')
+ .then(text => {
+ cy.log(text)
+ cy.wrap(text).as('currentPrice')
+ })
+ cy.get('[name="size"]').clear().type(i)
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ .then(() => {
+ t0 = performance.now();
+ // You can use `t0` here for further actions or logging
+ cy.log('Time recorded:', t0);
+ });
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ .then(() => {
+ t1 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t1);
+ });
+ //cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .sell')
+ cy.get(':nth-child(1) > :nth-child(3) > .sell')
+ .contains('sell')
+ .then(() => {
+ t2 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t1);
+ });
+ cy.get('@currentPrice')
+ .then(val=> {
+ //cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ .should('contain', val)
+ // cy.get('.wallet-wrapper > :nth-child(2) > :nth-child(3)').click()
+ // cy.contains('HOLLAEX', {matchCase: false}).click().log("wallet opened")//.pause()
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(6)')
+ .contains(i)
+ // cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(4)')
+ }
+ cy.then(() => {
+ const time = (t2 - t1)/1000;
+ cy.log(time,timeRange)
+ expect(time).to.be.lessThan(timeRange)
+ });
+
+})
+
+And ('I take sell orders {string} times less than {float} second',(orderTime,timeRange)=>{
+ let t0, t1, t2; // Declare t0 and t1 outside the loop
+ function clickUntilDepthIs00001() {
+ // Check if the condition is met
+ cy.get('.trade_orderbook-depth').then(($depth) => {
+ if ($depth.text().includes('0.00001')) {
+ // If the condition is met, then do nothing (or perform any necessary actions)
+ return;
+ } else {
+ // If the condition is not met, click the element and call the function again
+ cy.get('.trade_orderbook-depth-selector > :nth-child(1)').click();
+ clickUntilDepthIs00001(); // Recursively call the function
+ }
+ });
+ }
+
+ // Start the clicking process
+ clickUntilDepthIs00001();
+
+ cy.get('.trade_orderbook-depth').contains('0.00001')
+ cy.get('.trade_order_entry-action_selector > :nth-child(1)').click()
+ for (var i = Number(orderTime); i>0; i--)
+ {
+ // cy.get('@highestBuy')
+ cy.get('.trade_orderbook-asks > :nth-child(1) > .d-flex > .trade_orderbook-cell-price')
+ .click().invoke('text')
+ .then(text => {
+ cy.log(text)
+ cy.wrap(text).as('currentPrice')
+ })
+ cy.get('[name="size"]').clear().type(i)
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ .then(() => {
+ t0 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t0);
+ });
+ cy.get('.notification-content-wrapper > .holla-button').click()//notification
+ .then(() => {
+ t1 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t1);
+ });
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .sell')
+ .contains('sell')//.wait(2000)
+ .then(() => {
+ t2 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t2);
+ });
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)').first()
+ .contains(i)
+ cy.get(':nth-child(1) > :nth-child(2) > .trade_history-row')
+ .contains(i)
+ cy.get('@currentPrice')
+ .then(val=> {
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(4)')
+ .should('contain', val)
+ })
+ }
+ cy.then(() => {
+ const time = (t2 - t1)/1000;
+ cy.log(time,timeRange)
+ expect(time).to.be.lessThan(timeRange)
+ });
+
+
+})
+
+And ('I take buy orders {string} times less than {float} second',(orderTime,timeRange)=>{
+ let t0, t1, t2; // Declare t0 and t1 outside the loop
+ cy.get('.trade_order_entry-action_selector > :nth-child(2)').click()
+ for (var i = Number(orderTime); i >0; i--)
+ {
+ // cy.get('@lowestSell')
+ cy.get('.trade_orderbook-bids > :nth-child(1) > .d-flex > .trade_orderbook-cell-price')
+ .click().invoke('text')
+ .then(text => {
+ cy.log(text)
+ cy.wrap(text).as('currentPrice')
+ })
+ cy.get('[name="size"]').clear().type(i)
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ .then(() => {
+ t0 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t0);
+ });
+ cy.get('.notification-content-wrapper > .holla-button')
+ .click().then(() => {
+ t1 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t1);
+ })
+
+ }
+ cy.then(() => {
+ const time = (t1 - t0)/1000;
+ cy.log(time,timeRange)
+ expect(time).to.be.lessThan(timeRange)
+ });
+
+})
+
+When ('I fill {string} of {string} in an order partially less than {float} second',(portion,whole,timeRange)=>{
+ let t0, t1, t2; // Declare t0 and t1 outside the loop
+ let size = Number(whole)
+ cy.get('.trade_order_entry-action_selector > :nth-child(2)').click()
+ cy.get('@lowestSell').click()
+ cy.get('[name="price"]').type('{downArrow}').invoke('val')
+ .then(text => {
+ cy.log(text)
+ cy.wrap(text).as('currentPrice')
+ })
+ cy.get('[name="size"]').clear().type(size)
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+
+ //cy.get('.notification-content-wrapper > .holla-button').click()//notification
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .sell')
+ .contains('sell')
+ cy.get('@currentPrice')
+ .then(val=> {
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(5)')
+ .should('contain', val)
+ cy.wallectCheck('sell','HollaEx',0.01,size,val)
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(6)')
+ .contains(size)
+ cy.get('.trade_order_entry-action_selector > :nth-child(1)').click()
+ cy.get('[name="size"]').clear().type((size-Number(portion)))
+ cy.get('.holla-button').click().get('.form-error').should('not.be.always.exist')
+ cy.get('.notification-content-wrapper > :nth-child(4) > :nth-child(3)').click()
+ .then(() => {
+ t0 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t0);
+ });
+ cy.get('.notification-content-wrapper > .holla-button').click()//notification
+ // cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3) > .buy')
+ cy.get(':nth-child(2) > .cell_box-type > .buy')
+ .contains('buy')
+ cy.get('@currentPrice')
+ .then(val=> {
+ // cy.get('[style="width: 801px; height: 430px; position: absolute; transform: translate(10px, 690px);"] > .trade_block-wrapper > .trade_block-content > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ cy.get('.open-order-wrapper > .trade_active_orders-wrapper > .table_container > .table-content > .table-wrapper > .table_body-wrapper > .table_body-row > :nth-child(5)')
+ .should('contain', val)
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(2)')
+ .contains((size-Number(portion)))
+ .then(() => {
+ t1= performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t1);
+ });
+ cy.get(':nth-child(1) > :nth-child(2) > .trade_history-row')
+ .contains((size-Number(portion)))
+ .then(() => {
+ t2 = performance.now();
+ // You can use `t1` here for further actions or logging
+ cy.log('Time recorded:', t2);
+ });
+ cy.then(() => {
+ const time = (t2 - t1)/1000;
+ cy.log(time,timeRange)
+ expect(time).to.be.lessThan(timeRange)
+ });
+
+})
+
+Then ('I will cancel the order of {string} / {string} percentage less than {float} second',(portion,whole,timeRange)=>{
+ let t0, t1, t2; // Declare t0 and t1 outside the loop
+ cy.get('.trade__active-orders_cancel-All').click()
+ .then(() => {
+ return cy.get(':nth-child(2) > .w-100 > :nth-child(3)').click();
+ })
+ .then(() => {
+ t1 = performance.now();
+ cy.log('Time recorded:', t1);
+ return cy.get('.no-data > .edit-wrapper__container > :nth-child(1)');
+ })
+ .then(() => {
+ t2 = performance.now();
+ cy.log('Time recorded:', t2);
+ const time = (t2 - t1) / 1000;
+ cy.log(time, timeRange);
+ expect(time).to.be.lessThan(timeRange);
+ });
+
+})
diff --git a/test/Cypress/cypress/integration/Gherkin/transactionFlow.feature b/test/Cypress/cypress/integration/Gherkin/transactionFlow.feature
index 8fa833726a..95cea73fab 100644
--- a/test/Cypress/cypress/integration/Gherkin/transactionFlow.feature
+++ b/test/Cypress/cypress/integration/Gherkin/transactionFlow.feature
@@ -24,9 +24,9 @@ Scenario: Alice received Mininal XHTT from Bob Successful
Scenario: UnSuccessful Mininal XHTT transfer from Bob to Alice
- When Bob logged in successfully
- And Bob transferred a minimal amount of XHTT to wrong Alice address
-
+ When Bob logged in successfully
+ And Bob transferred a minimal amount of XHTT to wrong Alice address
+
Scenario: Successful Mininal XHTT receiving from Bob
When Bob confirm the transfer by Email
diff --git a/test/Cypress/cypress/integration/Gherkin/transactionFlow/transactionFlow.js b/test/Cypress/cypress/integration/Gherkin/transactionFlow/transactionFlow.js
index 060e694d6d..d84d7cf027 100644
--- a/test/Cypress/cypress/integration/Gherkin/transactionFlow/transactionFlow.js
+++ b/test/Cypress/cypress/integration/Gherkin/transactionFlow/transactionFlow.js
@@ -4,13 +4,13 @@ const totp = require("totp-generator");
Given ('Alice logged in successfully',()=>{
cy.visit(Cypress.env('LOGIN_PAGE'))
cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(Cypress.env('ALICE'))
+ cy.get('[name="email"]').clear().type('tester+alice@hollaex.email')
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
cy.wait(5000)
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
- .should('contain',Cypress.env('ALICE'))
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain','tester+alice@hollaex.email')
})
And ('Alice has X amount of XHTT',()=>{
@@ -18,31 +18,31 @@ And ('Alice has X amount of XHTT',()=>{
cy.contains('Wallet').click()
cy.wait(3000)
cy.contains('HollaEx').click()
- cy.get('.with_price-block_amount-value').invoke('text')
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
.then(text => {
- var fullText = text;
+ const regex = /\d+\.?\d*/g;
+ var fullText = text.match(regex);
cy.writeFile('cypress\\fixtures\\AliceBalance.json', { name: 'XHTT', balance: fullText })
- cy.log('second', text)
+ cy.log('second', fullText)
})
cy.contains('Signout').click()
})
When ('Bob logged in successfully',()=>{
- //cy.visit(Cypress.env('LOGIN_PAGE'))
cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(Cypress.env('BOB'))
+ cy.get('[name="email"]').clear().type('bob@hollaex.email')
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
cy.wait(5000)
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('BOB'))
})
And ('Bob enables 2FA',()=>{
- cy.get('.app-menu-bar-side > :nth-child(5)').as('Car Keys').click()
+ cy.get('.app-menu-bar-side > :nth-child(6)').as('Car Keys').click()
cy.get('.checkbutton-input-wrapper').as('enable').click()
cy.get(':nth-child(3) > .otp_form-section-content').invoke('text')
.then(text => {
@@ -68,54 +68,60 @@ And ('Bob transferred a minimal amount of XHTT to Alice',()=>{
cy.wait(8000)
cy.contains('HollaEx').click()
cy.wait(3000)
- cy.get('.with_price-block_amount-value').invoke('text')
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
.then(text => {
- var fullText= text;
+ const regex = /\d+\.?\d*/g;
+ var fullText= text.match(regex);
cy.writeFile('cypress\\fixtures\\BobBalance.json', { name: 'XHTT', balance: fullText })
- cy.log('second', text)
+ cy.log('second', fullText)
})
cy.get('[href="/wallet/xht/withdraw"] > .holla-button').as('send Crypto')
.click()
- cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ cy.get(':nth-child(3) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear()
+ .type('0xc99fc19ebdcc683d15d116360525c09417c109df')
+ cy.get(':nth-child(4) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > [style="display: flex;"] > .input_field-input')
.clear()
- .type('0x97b7b520e553794a610dc25d06414719ffb44a66')
+ .type('0.0001')
cy.get('.holla-button').click()
cy.get('.review-crypto-amount > :nth-child(1)')
- .should('contain','0.0001 XHTT')
+ .should('contain','10.0001 XHT')//changed
cy.get('.review-wrapper > .flex-column > :nth-child(4)')
- .should('contain','0x97b7b520e553794a610dc25d06414719ffb44a66')
+ .should('contain','0xc99fc19ebdcc683d15d116360525c09417c109df')
cy.get('.button-success').click()
cy.fixture('2fa')
.then((user)=>{
let token = totp(user.code);
cy.log(token)
cy.log('second', user.code)
- cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ //cy.get('.otp_form-wrapper > form.w-100 > .w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ cy.get('.masterInput')
.clear().type(token)
- cy.get('.otp_form-wrapper > form.w-100 > .holla-button').click()
+
})
- cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > .icon_title-text')
- //.should('contain','Confirm Via Email')
+ cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > :nth-child(1)')
+ .should('contain','Confirm via Email')
+
cy.get('.d-flex > .holla-button').click()
cy.wait(10000)
})
And ('Bob disables 2FA',()=>{
- cy.get('.app-menu-bar-side > :nth-child(5)').click()
+ cy.get('.app-menu-bar-side > :nth-child(6)').click()
cy.contains('2FA').click()
cy.get('.checkbutton-input-wrapper').as('disable').click()
- cy.contains('Enter your authentication code to continue').should('exist')
+ cy.get('.ReactModal__Content')
+ cy.contains('Enter your 6-digit code to continue').should('exist')
cy.fixture('2fa')
.then((user)=>{
const token = totp(user.code);
cy.log(token)
cy.wrap(token).as('token')
- cy.get('.input_field-input').clear().type(token)
+ cy.get('.masterInput').clear().type(token)
cy.log(token);
cy.get('.holla-button').click()
})
- cy.contains('You have successfully deactivated 2FA').should('exist')
- cy.get('.holla-button').click()
+
cy.reload()
@@ -123,34 +129,67 @@ And ('Bob disables 2FA',()=>{
When ('Bob confirm the transfer by Email',()=>{
- cy.visit(Cypress.env('EMAIL_PAGE'))
- let link;
- var text = null
- cy.get('#wdc_username_div').type(Cypress.env('EMAIL_ADMIN_USERNAME'))
- cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'))
- cy.get('#wdc_login_button').click();
- cy.get('#ext-gen52').click()
- cy.log('created new user')
- cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
- .dblclick()
- cy.wait(5000)
- cy.then(()=>{
- text = cy.getIframe('.preview-iframe').should('not.null').toString()})
- .then((text)=> link= cy.trimmer(text,Cypress.env('EMAIL_WITHDREAW'),Cypress.env('BOB')))
- .should('not.be.false')
- .then((link )=>cy.forceVisit(link))
- cy.wait(3000)
- cy.contains('Success').should('exist')
- cy.log("link is ", link )
+ cy.visit(Cypress.env('EMAIL_PAGE'));
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_BOB'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox XHT Withdrawal Request');
+ cy.fixture('example')
+ cy.get('.giraffe-emailaddress-link').last().contains('bob')
+
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('You have made a XHT withdrawal request');
+
+ // Get all the links with "https" protocol from the email body
+ cy.get('@emailBody')
+ .find('a')
+ .then(($links) => {
+ const httpsLinks = [];
+ $links.each((index, link) => {
+ const href = link.href;
+ if (href && href.startsWith('https')) {
+ httpsLinks.push(href);
+ }
+ });
+ cy.wrap(httpsLinks[1]).as('httpsLink');
+ });
+
+ // Log the list of https links
+ cy.get('@httpsLink')
+ .then((httpsLink) => {
+ console.log(httpsLink);
+ cy.forceVisit(httpsLink);
+ });
+
+ cy.contains('Final Withdrawal Confirmation').should('exist')
+ cy.contains('CONFIRM WITHDRAWAL').click()
+ cy.contains('Success').should('exist')
cy.writeFile('cypress\\fixtures\\timestamp.json', { name: 'timestamp', time: Date.now() })
})
Then ('Bob cancels the transfer',()=>{
- cy.get(':nth-child(1) > :nth-child(6) > .withdrawal-cancel')
+ cy.contains('Cancel').click()
+ cy.contains('Cancel')
+ cy.get('.w-100 > :nth-child(3) > .edit-wrapper__container > :nth-child(1)')
.click()
- cy.get('.ReactModal__Content > :nth-child(1) > .icon_title-wrapper > :nth-child(2) > .icon_title-text')
- .contains('Cancel HollaEx Withdrawal')
- cy.get('.w-100 > :nth-child(3)').click()
+ cy.wait(3000)
cy.reload()
cy.contains('Withdrawals').click()
cy.get(':nth-child(1) > .transaction-status > .d-flex')
@@ -166,28 +205,35 @@ When ('Bob confirms that has not sent the minimal amount of XHTT',()=>{
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
cy.wait(3000)
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('BOB'))
cy.wait(3000)
cy.contains('Wallet').click()
cy.wait(3000)
cy.contains('HollaEx').click()
- cy.get('.with_price-block_amount-value').invoke('text')
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
.then(text => {
- var newBalance = text;
+ const regex = /\d+\.?\d*/g;
+ var newBalance = text.match(regex);
cy.fixture('BobBalance')
.then((user)=>{
var balance = user.balance;
var eq = balance-newBalance
- expect(eq).to.equal(0);
+ //expect(eq).to.equal(0);
+ const expectedValue = 1;
+ const actualValue = eq/* Get the actual value from your application */;
+ const tolerance = 0.0001; // Define an acceptable tolerance
+
+ cy.wrap(actualValue).should('be.closeTo', expectedValue, tolerance);
})
cy.contains('History').click()
cy.contains('Withdrawals').click()
+ cy.wait(5000)
// cy.contains('Signout').click()
cy.get(':nth-child(1) > .coin-cell > .d-flex').contains('HollaEx')
cy.get(':nth-child(1) > .transaction-status > .d-flex').contains('Pending')
- cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3)')
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(5)')
.invoke('text')
.then(text => {
var fullText = text;
@@ -225,15 +271,16 @@ And ('Bob confirms that has sent the minimal amount of XHTT',()=>{
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
cy.wait(3000)
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',Cypress.env('BOB'))
cy.wait(3000)
cy.contains('Wallet').click()
cy.wait(3000)
cy.contains('HollaEx').click()
- cy.get('.with_price-block_amount-value').invoke('text')
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
.then(text => {
- var newBalance = text;
+ const regex = /\d+\.?\d*/g;
+ var newBalance = text.match(regex);
cy.fixture('BobBalance')
.then((user)=>{
var balance = user.balance;
@@ -277,20 +324,22 @@ And ('Bob confirms that has sent the minimal amount of XHTT',()=>{
Then ('Alice has received the minimal amount of XHTT',()=>{
cy.visit(Cypress.env('LOGIN_PAGE'))
cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(Cypress.env('ALICE'))
+ cy.get('[name="email"]').clear().type('tester+alice@hollaex.email')
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
cy.wait(3000)
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
- .should('contain',Cypress.env('ALICE'))
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain','tester+alice@hollaex.email')
cy.wait(3000)
cy.contains('Wallet').click()
cy.wait(3000)
cy.contains('HollaEx').click()
- cy.get('.with_price-block_amount-value').invoke('text')
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
.then(text => {
- var newBalance = text;
+ const regex = /\d+\.?\d*/g;
+ var newBalance = text.match(regex);
+ cy.log(newBalance)
cy.fixture('ALiceBalance')
.then((user)=>{
var balance = user.balance;
@@ -328,9 +377,11 @@ Then ('Alice has received the minimal amount of XHTT',()=>{
})
})
+
cy.contains('Signout').click()
})
-
+
+})
And ('Bob transferred a minimal amount of XHTT to wrong Alice address',()=>{
cy.wait(3000)
@@ -338,34 +389,41 @@ And ('Bob transferred a minimal amount of XHTT to wrong Alice address',()=>{
cy.wait(8000)
cy.contains('HollaEx').click()
cy.wait(3000)
- cy.get('.with_price-block_amount-value').invoke('text')
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
.then(text => {
- var fullText= text;
+ const regex = /\d+\.?\d*/g;
+ var fullText= text.match(regex);
cy.writeFile('cypress\\fixtures\\BobBalance.json', { name: 'XHTT', balance: fullText })
cy.log('second', text)
})
cy.get('[href="/wallet/xht/withdraw"] > .holla-button').as('send Crypto')
.click()
- cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ cy.get(':nth-child(3) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('0xc99fc19ebdcc683d15d116360525c09417c109dh')
+
+ cy.contains('Invalid ETH address')
+ cy.get(':nth-child(3) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear()
+ cy.get(':nth-child(3) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
.clear()
.type('0x97b7b520e553794a610dc25d06414719ffb44a77')
+ cy.get(':nth-child(4) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > [style="display: flex;"] > .input_field-input')
+ .clear()
+ .type('0.0001')
cy.get('.holla-button').click()
cy.get('.review-crypto-amount > :nth-child(1)')
- .should('contain','0.0001 XHTT')
+ .should('contain','10.0001 XHT')//changed
cy.get('.review-wrapper > .flex-column > :nth-child(4)')
.should('contain','0x97b7b520e553794a610dc25d06414719ffb44a77')
cy.get('.button-success').click()
- cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > .icon_title-text')
- //.should('contain','Confirm Via Email')
- cy.get('.d-flex > .holla-button').click()
- cy.wait(10000)
+ cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > :nth-child(1)')
+ .should('contain','Confirm via Email')
+
+ cy.get('.d-flex > .holla-button').click()
+ cy.wait(10000)
})
-
-
-
-})
And ('Bob transferred a minimal amount of XHTT to real Alice address',()=>{
cy.wait(3000)
@@ -373,26 +431,31 @@ And ('Bob transferred a minimal amount of XHTT to real Alice address',()=>{
cy.wait(8000)
cy.contains('HollaEx').click()
cy.wait(3000)
- cy.get('.with_price-block_amount-value').invoke('text')
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
.then(text => {
- var fullText= text;
+ const regex = /\d+\.?\d*/g;
+ var fullText= text.match(regex);
cy.writeFile('cypress\\fixtures\\BobBalance.json', { name: 'XHTT', balance: fullText })
cy.log('second', text)
})
cy.get('[href="/wallet/xht/withdraw"] > .holla-button').as('send Crypto')
.click()
- cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ cy.get(':nth-child(3) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
.clear()
- .type(Cypress.env('ALICE_XHTT_ADDRESS'))
+ .type('0x97b7b520e553794a610dc25d06414719ffb44a77')
+ cy.get(':nth-child(4) > :nth-child(1) > :nth-child(1) > .field-content > .field-children > [style="display: flex;"] > .input_field-input')
+ .clear()
+ .type('0.0001')
cy.get('.holla-button').click()
cy.get('.review-crypto-amount > :nth-child(1)')
- .should('contain','0.0001 XHTT')
+ .should('contain','10.0001 XHT')//changed
cy.get('.review-wrapper > .flex-column > :nth-child(4)')
- .should('contain',Cypress.env('ALICE_XHTT_ADDRESS'))
+ .should('contain','0x97b7b520e553794a610dc25d06414719ffb44a77')
cy.get('.button-success').click()
- cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > .icon_title-text')
- //.should('contain','Confirm Via Email')
- cy.get('.d-flex > .holla-button').click()
- cy.wait(10000)
+ cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > :nth-child(1)')
+ .should('contain','Confirm via Email')
+
+ cy.get('.d-flex > .holla-button').click()
+ cy.wait(10000)
-})
\ No newline at end of file
+})
diff --git a/test/Cypress/cypress/integration/Gherkin/transactionFlowWithEmail.feature b/test/Cypress/cypress/integration/Gherkin/transactionFlowWithEmail.feature
new file mode 100644
index 0000000000..de67e30293
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/transactionFlowWithEmail.feature
@@ -0,0 +1,51 @@
+Feature: Crpto transfer Feature
+
+As a valid customer
+In order to Deposit and withdraw
+I want to send some amount of crypto
+
+Scenario: Successful Mininal XHTT transfer from Bob to Alice
+
+ Given Alice logged in successfully
+ And Alice has X amount of XHTT
+ When Bob logged in successfully
+ And Bob enables 2FA
+ And Bob transferred a minimal amount of XHTT to Alice
+ And Bob disables 2FA
+
+Scenario: Successful Mininal XHTT receiving from Bob
+
+ When Bob confirm the transfer by Email
+
+Scenario: Alice received Mininal XHTT from Bob Successful
+
+ And Bob confirms that has sent the minimal amount of XHTT
+ Then Alice has received the minimal amount of XHTT
+
+ Scenario: UnSuccessful Mininal XHTT transfer from Bob to Alice
+
+ When Bob logged in successfully
+ And Bob transferred a minimal amount of XHTT to wrong Alice address
+
+Scenario: Unsuccessful Mininal XHTT receiving from Bob
+
+ When Bob confirm the transfer by Email and gets error
+
+Scenario: Bob has not sent the Mininal XHTT
+
+ When Bob confirms that has not sent the minimal amount of XHTT
+
+
+Scenario: UnSuccessful Mininal XHTT transfer from Bob to deleted Alice address
+
+ When Bob logged in successfully
+ And Bob transferred a minimal amount of XHTT to deleted Alice address
+
+Scenario: Unsuccessful Mininal XHTT receiving from Bob
+
+ When Bob confirm the transfer by Email and gets error
+
+Scenario: Bob has not sent the Mininal XHTT
+
+ When Bob confirms that has not sent the minimal amount of XHTT
+
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/transactionFlowWithEmail/transactionFlowWithEmail.js b/test/Cypress/cypress/integration/Gherkin/transactionFlowWithEmail/transactionFlowWithEmail.js
new file mode 100644
index 0000000000..10ef768539
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/transactionFlowWithEmail/transactionFlowWithEmail.js
@@ -0,0 +1,516 @@
+import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
+const totp = require("totp-generator");
+
+Given ('Alice logged in successfully',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type('tester+alice@hollaex.email')
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.wait(5000)
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain','tester+alice@hollaex.email')
+})
+
+And ('Alice has X amount of XHTT',()=>{
+ cy.wait(3000)
+ cy.contains('Wallet').click()
+ cy.wait(3000)
+ cy.contains('HollaEx').click()
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
+ .then(text => {
+ const regex = /\d+\.?\d*/g;
+ var fullText = text.match(regex);
+ cy.writeFile('cypress\\fixtures\\AliceBalance.json', { name: 'XHTT', balance: fullText })
+ cy.log('second', text)
+ })
+ cy.contains('Signout').click()
+})
+
+When ('Bob logged in successfully',()=>{
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))//should be commented
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type('bob@hollaex.email')
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.wait(5000)
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain',Cypress.env('BOB'))
+})
+
+And ('Bob enables 2FA',()=>{
+
+ cy.get('.app-menu-bar-side > :nth-child(6)').as('Car Keys').click()
+ cy.get('.checkbutton-input-wrapper').as('enable').click()
+ cy.get(':nth-child(3) > .otp_form-section-content').invoke('text')
+ .then(text => {
+ var fullText = text;
+ cy.writeFile('cypress\\fixtures\\2fa.json', { name: 'Newuser', code: fullText })
+ const token = totp(fullText);
+ cy.log(token)
+ cy.wrap(token).as('token')
+ cy.log(token);
+ cy.log('second', text)
+ cy.get('.input_field-input').clear().type(token)
+ })
+ cy.get('.holla-button').click()
+ cy.contains('You have successfully activated 2FA').should('exist')
+ cy.get('.holla-button').click()
+ cy.reload()
+
+})
+
+And ('Bob transferred a minimal amount of XHTT to Alice',()=>{
+ cy.wait(3000)
+ cy.contains('Wallet').click()
+ cy.wait(8000)
+ cy.contains('HollaEx').click()
+ cy.wait(3000)
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
+ .then(text => {
+ const regex = /\d+\.?\d*/g;
+ var fullText= text.match(regex);
+ cy.writeFile('cypress\\fixtures\\BobBalance.json', { name: 'XHTT', balance: fullText })
+ cy.log('second', text)
+ })
+ cy.get('[href="/wallet/xht/withdraw"] > .holla-button').as('send Crypto')
+ .click()
+ cy.get('#method-address-undefined').click()
+ cy.get('#method-email-1').click()
+ cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children')
+ .type('tester+alice@hollaex.email')
+ cy.get('.with-notification > :nth-child(1) > :nth-child(1) > .field-content > .field-children > [style="display: flex;"] > .input_field-input')
+ .clear()
+ .type('0.0001')
+ cy.get('.holla-button').click()
+ cy.get('.review-crypto-amount > :nth-child(1)')
+ .should('contain','0.0001 XHT')//changed
+ cy.get('.review-wrapper > .flex-column > :nth-child(4)')
+ .should('contain','tester+alice@hollaex.email')
+ cy.get('.button-success').click()
+ cy.fixture('2fa')
+ .then((user)=>{
+ let token = totp(user.code);
+ cy.log(token)
+ cy.log('second', user.code)
+ cy.get('.masterInput')
+ .clear().type(token)
+
+ })
+ cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > :nth-child(1)')
+ .should('contain','Confirm via Email')
+
+ cy.get('.d-flex > .holla-button').click()
+ cy.wait(10000)
+})
+
+And ('Bob disables 2FA',()=>{
+ cy.get('.app-menu-bar-side > :nth-child(6)').click()
+ cy.contains('2FA').click()
+ cy.get('.checkbutton-input-wrapper').as('disable').click()
+ cy.get('.ReactModal__Content')
+ cy.contains('Enter your 6-digit code to continue').should('exist')
+ cy.fixture('2fa')
+ .then((user)=>{
+ const token = totp(user.code);
+ cy.log(token)
+ cy.wrap(token).as('token')
+ cy.get('.masterInput').clear().type(token)
+ cy.log(token);
+ cy.get('.holla-button').click()
+ })
+ // cy.contains('You have successfully deactivated 2FA').should('exist')
+ //s cy.get('.holla-button').click()
+ cy.reload()
+
+
+})
+
+When ('Bob confirm the transfer by Email',()=>{
+
+ cy.visit(Cypress.env('EMAIL_PAGE'));
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_BOB'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox XHT Withdrawal Request');
+ cy.fixture('example')
+ cy.get('.giraffe-emailaddress-link').last().contains('bob')
+
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('You have made a XHT withdrawal request');
+
+ // Get all the links with "https" protocol from the email body
+ cy.get('@emailBody')
+ .find('a')
+ .then(($links) => {
+ const httpsLinks = [];
+ $links.each((index, link) => {
+ const href = link.href;
+ if (href && href.startsWith('https')) {
+ httpsLinks.push(href);
+ }
+ });
+ cy.wrap(httpsLinks[1]).as('httpsLink');
+ });
+
+ // Log the list of https links
+ cy.get('@httpsLink')
+ .then((httpsLink) => {
+ console.log(httpsLink);
+ cy.forceVisit(httpsLink);
+ });
+
+ cy.contains('Final Withdrawal Confirmation').should('exist')
+ cy.contains('CONFIRM WITHDRAWAL').click()
+ cy.contains('Success').should('exist')
+ cy.writeFile('cypress\\fixtures\\timestamp.json', { name: 'timestamp', time: Date.now() })
+})
+
+Then ('Bob cancels the transfer',()=>{
+ cy.get(':nth-child(1) > :nth-child(6) > .withdrawal-cancel')
+ .click()
+ cy.contains('Cancel')
+ cy.get('.w-100 > :nth-child(3) > .edit-wrapper__container > :nth-child(1)')
+ .click()
+ cy.reload()
+ cy.contains('Withdrawals').click()
+ cy.get(':nth-child(1) > .transaction-status > .d-flex')
+ .contains('Rejected')
+ cy.contains('Signout').click()
+
+})
+When ('Bob confirms that has not sent the minimal amount of XHTT',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('BOB'))
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.wait(3000)
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain',Cypress.env('BOB'))
+ cy.wait(3000)
+ cy.contains('Wallet').click()
+ cy.wait(3000)
+ cy.contains('HollaEx').click()
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
+ .then(text => {
+ const regex = /\d+\.?\d*/g;
+ var newBalance = text.match(regex);
+ cy.fixture('BobBalance')
+ .then((user)=>{
+ var balance = user.balance;
+ var eq = balance-newBalance
+ //expect(eq).to.equal(0);
+ const expectedValue = 0;
+ const actualValue = eq/* Get the actual value from your application */;
+ const tolerance = 0.0001; // Define an acceptable tolerance
+
+ cy.wrap(actualValue).should('be.closeTo', expectedValue, tolerance);
+ })
+
+ cy.contains('History').click()
+ cy.contains('Withdrawals').click()
+ cy.wait(5000)
+ // cy.contains('Signout').click()
+ cy.get(':nth-child(1) > .coin-cell > .d-flex').contains('HollaEx')
+ cy.get(':nth-child(1) > .transaction-status > .d-flex').contains('Complete')
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ .invoke('text')
+ .then(text => {
+ var fullText = text;
+ fullText.replace('/,','/.')
+ var pattern = /[+-]?\d+(\.\d+)?/g;
+ var number = fullText.match(pattern);
+
+ cy.wrap(number).as('Y')
+ cy.log(parseFloat(number));
+ cy.log('second', fullText)
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ .invoke('text')
+ .then(text => {
+ cy.fixture('timestamp')
+ .then((user)=>{
+ var time = user.time;
+ var fullText = Date.parse(text);
+ cy.log('second', time)
+ cy.log(text,fullText)
+ cy.log(fullText-time)
+ })
+
+ })
+ })
+ // cy.contains('Signout').click()
+
+})
+
+And ('Bob confirms that has sent the minimal amount of XHTT',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('BOB'))
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.wait(3000)
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain',Cypress.env('BOB'))
+ cy.wait(3000)
+ cy.contains('Wallet').click()
+ cy.wait(3000)
+ cy.contains('HollaEx').click()
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
+ .then(text => {
+ const regex = /\d+\.?\d*/g;
+ var newBalance = text.match(regex);
+ cy.fixture('BobBalance')
+ .then((user)=>{
+ var balance = user.balance;
+ var eq = balance-newBalance
+ expect(eq).to.within(0.00009, 0.00010009);
+ })
+
+ cy.contains('History').click()
+ cy.contains('Withdrawals').click()
+ // cy.contains('Signout').click()
+ cy.get(':nth-child(1) > .coin-cell > .d-flex').contains('HollaEx')
+ cy.get(':nth-child(1) > .transaction-status > .d-flex').contains('Complete')
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3)')
+ .invoke('text')
+ .then(text => {
+ var fullText = text;
+ fullText.replace('/,','/.')
+ var pattern = /[+-]?\d+(\.\d+)?/g;
+ var number = fullText.match(pattern);
+
+ cy.wrap(number).as('Y')
+ cy.log(parseFloat(number));
+ cy.log('second', fullText)
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ .invoke('text')
+ .then(text => {
+ cy.fixture('timestamp')
+ .then((user)=>{
+ var time = user.time;
+ var fullText = Date.parse(text);
+ cy.log('second', time)
+ cy.log(text,fullText)
+ cy.log(fullText-time)
+ })
+
+ })
+ })
+ cy.contains('Signout').click()
+})
+Then ('Alice has received the minimal amount of XHTT',()=>{
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type('tester+alice@hollaex.email')
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.wait(3000)
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
+ .should('contain','tester+alice@hollaex.email')
+ cy.wait(3000)
+ cy.contains('Wallet').click()
+ cy.wait(3000)
+ cy.contains('HollaEx').click()
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
+ .then(text => {
+ const regex = /\d+\.?\d*/g;
+ var newBalance = text.match(regex);
+ cy.fixture('ALiceBalance')
+ .then((user)=>{
+ var balance = user.balance;
+ var eq = newBalance-balance
+ expect(eq).to.within(0.00009, 0.00010009);
+ })
+
+ cy.contains('History').click()
+ cy.contains('Deposits').click()
+ // cy.contains('Signout').click()
+ cy.get(':nth-child(1) > .coin-cell > .d-flex').contains('HollaEx')
+ cy.get(':nth-child(1) > .transaction-status > .d-flex').contains('Complete')
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(3)')
+ .invoke('text')
+ .then(text => {
+ var fullText = text;
+ fullText.replace('/,','/.')
+ var pattern = /[+-]?\d+(\.\d+)?/g;
+ var number = fullText.match(pattern);
+
+ cy.wrap(number).as('Y')
+ cy.log(parseFloat(number));
+ cy.log('second', fullText)
+ })
+ cy.get('.table_body-wrapper > :nth-child(1) > :nth-child(5)')
+ .invoke('text')
+ .then(text => {
+ cy.fixture('timestamp')
+ .then((user)=>{
+ var time = user.time;
+ var fullText = Date.parse(text);
+ cy.log('second', time)
+ cy.log(text,fullText)
+ cy.log(fullText-time)
+ })
+
+ })
+
+ cy.contains('Signout').click()
+ })
+
+})
+And ('Bob transferred a minimal amount of XHTT to wrong Alice address',()=>{
+
+ cy.wait(3000)
+ cy.contains('Wallet').click()
+ cy.wait(8000)
+ cy.contains('HollaEx').click()
+ cy.wait(3000)
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
+ .then(text => {
+ const regex = /\d+\.?\d*/g;
+ var fullText= text.match(regex);
+ cy.writeFile('cypress\\fixtures\\BobBalance.json', { name: 'XHTT', balance: fullText })
+ cy.log('second', text)
+ })
+ cy.get('[href="/wallet/xht/withdraw"] > .holla-button').as('send Crypto')
+ .click()
+ cy.get('#method-address-undefined').click()
+ cy.get('#method-email-1').click()
+ cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children')
+ .type('Iamnotalice@hollaex.email')
+ cy.get('.with-notification > :nth-child(1) > :nth-child(1) > .field-content > .field-children > [style="display: flex;"] > .input_field-input')
+ .clear()
+ .type('0.0001')
+ cy.get('.holla-button').click()
+ cy.get('.review-crypto-amount > :nth-child(1)')
+ .should('contain','0.0001 XHT')//changed
+ cy.get('.review-wrapper > .flex-column > :nth-child(4)')
+ .should('contain','Iamnotalice@hollaex.email')
+ cy.get('.button-success').click()
+
+
+ cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > :nth-child(1)')
+ .should('contain','Confirm via Email')
+
+ cy.get('.d-flex > .holla-button').click()
+ cy.wait(10000)
+
+})
+
+And ('Bob transferred a minimal amount of XHTT to deleted Alice address',()=>{
+
+ cy.wait(3000)
+ cy.contains('Wallet').click()
+ cy.wait(8000)
+ cy.contains('HollaEx').click()
+ cy.wait(3000)
+ cy.get('.available-balance-wrapper > :nth-child(1) > :nth-child(1)').invoke('text')
+ .then(text => {
+ const regex = /\d+\.?\d*/g;
+ var fullText= text.match(regex);
+ cy.writeFile('cypress\\fixtures\\BobBalance.json', { name: 'XHTT', balance: fullText })
+ cy.log('second', text)
+ })
+ cy.get('[href="/wallet/xht/withdraw"] > .holla-button').as('send Crypto')
+ .click()
+ cy.get('#method-address-undefined').click()
+ cy.get('#method-email-1').click()
+ cy.get(':nth-child(2) > :nth-child(1) > :nth-child(1) > .field-content > .field-children')
+ .type('tester+aliceDeleted@hollaex.email')
+ cy.get('.with-notification > :nth-child(1) > :nth-child(1) > .field-content > .field-children > [style="display: flex;"] > .input_field-input')
+ .clear()
+ .type('0.0001')
+ cy.get('.holla-button').click()
+ cy.get('.review-crypto-amount > :nth-child(1)')
+ .should('contain','0.0001 XHT')//changed
+ cy.get('.review-wrapper > .flex-column > :nth-child(4)')
+ .should('contain','tester+aliceDeleted@hollaex.email')
+ cy.get('.button-success').click()
+ cy.get('.d-flex > .icon_title-wrapper > :nth-child(2) > :nth-child(1)')
+ .should('contain','Confirm via Email')
+
+ cy.get('.d-flex > .holla-button').click()
+ cy.wait(10000)
+
+})
+
+When ('Bob confirm the transfer by Email and gets error',()=>{
+
+ cy.visit(Cypress.env('EMAIL_PAGE'));
+
+ // Login to the email account
+ cy.get('#wdc_username_div').type(Cypress.env('EMAIL_BOB'));
+ cy.get('#wdc_password').type(Cypress.env('EMAIL_PASS'));
+ cy.get('#wdc_login_button').click();
+
+ // Open the latest email in the inbox
+ cy.get('#ext-gen52').click();
+ cy.get('.x-grid3-row-first > .x-grid3-row-table > tbody[role="presentation"] > .x-grid3-row-body-tr > .x-grid3-body-cell > .x-grid3-row-body > .mail-body-row > tbody > tr > .subject > .grid_compact')
+ .dblclick();
+ cy.wait(5000);
+
+ // Verify the email content
+ cy.get('.preview-title').contains('sandbox XHT Withdrawal Request');
+ cy.fixture('example')
+ cy.get('.giraffe-emailaddress-link').last().contains('bob')
+
+ cy.get('iframe').then(($iframe) => {
+ const $emailBody = $iframe.contents().find('body');
+ cy.wrap($emailBody).as('emailBody');
+ });
+ cy.get('@emailBody')
+ .find('a')
+ .should('exist');
+ cy.get('@emailBody')
+ .contains('You have made a XHT withdrawal request');
+
+ // Get all the links with "https" protocol from the email body
+ cy.get('@emailBody')
+ .find('a')
+ .then(($links) => {
+ const httpsLinks = [];
+ $links.each((index, link) => {
+ const href = link.href;
+ if (href && href.startsWith('https')) {
+ httpsLinks.push(href);
+ }
+ });
+ cy.wrap(httpsLinks[1]).as('httpsLink');
+ });
+
+ // Log the list of https links
+ cy.get('@httpsLink')
+ .then((httpsLink) => {
+ console.log(httpsLink);
+ cy.forceVisit(httpsLink);
+ });
+ cy.contains('Final Withdrawal Confirmation').should('exist')
+ cy.contains('CONFIRM WITHDRAWAL').click()
+ cy.contains('Error').should('exist')
+ cy.writeFile('cypress\\fixtures\\timestamp.json', { name: 'timestamp', time: Date.now() })
+})
diff --git a/test/Cypress/cypress/integration/Gherkin/userDeleteAccount.feature b/test/Cypress/cypress/integration/Gherkin/userDeleteAccount.feature
new file mode 100644
index 0000000000..74e058e588
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/userDeleteAccount.feature
@@ -0,0 +1,22 @@
+Feature: Account deleting Feature
+
+As a user
+In order to manage security
+I want to cdelete my account
+
+Scenario: Creating a new user with the current password
+
+ Given Admin creats a user with a random username and the current password
+ Then The new username is stored
+
+Scenario: User delets his account
+
+ Given I log in as the new user name
+ And I delete my account
+ Then I will be kicked out
+
+Scenario: Deleted user can not login
+
+ Given I log in as the deleted user name
+ Then I can not log in
+ And Admin confirms the email is not valid anymore
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/userDeleteAccount/userDeleteAcount.js b/test/Cypress/cypress/integration/Gherkin/userDeleteAccount/userDeleteAcount.js
new file mode 100644
index 0000000000..53490f9cca
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/userDeleteAccount/userDeleteAcount.js
@@ -0,0 +1,142 @@
+import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
+const totp = require("totp-generator");
+const randomUsername = Math.random().toString(36).substring(2,6);
+var username = "tester+"+randomUsername+Cypress.env('NEW_USER')
+
+
+Given ('Admin creats a user with a random username and the current password',()=>{
+
+ cy.log(username);
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.contains('Operator controls').click()
+ cy.contains('Users').click()
+ cy.contains('Add new user').click()
+ cy.get('#addUser_userEmail').clear().type(username)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.wait(3000)
+ cy.contains(username)
+
+})
+
+Then ('The new username is stored',()=>{
+
+ cy.writeFile('cypress\\fixtures\\example.json', { name: 'Newuser', email: username })
+})
+
+Given ('I log in as the new user name',()=>{
+
+ cy.fixture('example')
+ .then((user)=>{
+ username = user.email
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(username)
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ })
+})
+
+And ('I delete my account',()=>{
+ cy.wait(3000)
+ cy.contains('Settings').click()
+ cy.get('.tab_controller-tabs > :nth-child(6) > .edit-wrapper__container > :nth-child(1)').click()
+ cy.get('.edit-wrapper__container > :nth-child(1) > .underline-text').click()
+ cy.contains('Deleting your account will make both your account and funds inaccessible.')
+ cy.get('.danger-zone > .d-flex > :nth-child(2) > .edit-wrapper__container > :nth-child(1)')
+ .click()
+ cy.contains('Delete Account')
+ cy.get('.w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .type('I UNDERSTANDn')
+ cy.get(':nth-child(3) > .holla-button').should('be.disabled')
+ cy.get('.w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children > div > .input_field-input')
+ .clear().type('I UNDERSTAND')
+ cy.get(':nth-child(3) > .holla-button').should('be.enabled').click()
+ cy.contains('Input you security codes').wait(5000)
+
+ cy.task('getLastEmail', {
+ user: Cypress.env('EMAIL_ADMIN'),
+ password: Cypress.env('EMAIL_PASS'),
+ host: Cypress.env('EMAIL_HOST'),
+ port: 993,
+ tls: true })
+ .then((emailContent) => {
+ // Use the custom command 'extractText'
+ // expect(emailContent).to.include('sandbox Security Verification')
+ cy.extractText(emailContent).then((extractedText) => {
+ cy.log(`Extracted Text: ${extractedText}`);
+ cy.fixture('example')
+ .then((user)=>{
+ expect(extractedText).to.include(user.email)
+ })
+
+ // Use the custom command 'findFirstWordAfterOperation' within the callback
+ cy.findFirstWordAfterMyWord(extractedText,'operation.').then((firstWord) => {
+ cy.log(`First word after "operation.": ${firstWord}`);
+ cy.get('.w-100 > :nth-child(1) > .field-wrapper > :nth-child(1) > :nth-child(1) > .field-content > .field-children')
+ .type(firstWord)
+ });
+});
+ cy.get('form.w-100 > .holla-button').click()
+});
+})
+Then ('I will be kicked out',()=>{
+ cy.wait(3000)
+ cy.reload();
+ // cy.wait(3000)
+ // cy.contains('You have been logged out')
+ // cy.get('.notification-content-wrapper > .holla-button').click()
+ //cy.get('.notification-content-wrapper > .holla-button').contains("Login")
+})
+
+Given ('I log in as the deleted user name',()=>{
+ cy.fixture('example')
+ .then((user)=>{
+ username = user.email
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(username)
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+
+ })
+})
+
+Then ('I can not log in',()=>{
+ cy.get('.warning_text').contains("User not found")
+})
+
+And ('Admin confirms the email is not valid anymore',()=>{
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.contains('Operator controls').click()
+ cy.contains('Users').click()
+ cy.fixture('example')
+ .then((user)=>{
+ username = user.email
+ cy.get(':nth-child(2) > .ant-input').clear().type(username)
+ cy.get(':nth-child(2) > .ant-btn').click()
+ cy.wait(5000)
+ cy.get(':nth-child(8) > .ant-btn') .click()
+ cy.get(':nth-child(2) > .align-items-center > :nth-child(4)')
+ .contains(username+"_deleted")
+ cy.contains('Unfreez')
+
+ })
+
+
+
+
+})
+
diff --git a/test/Cypress/cypress/integration/Gherkin/wallet/wallet.js b/test/Cypress/cypress/integration/Gherkin/wallet/wallet.js
index 96f9b3e4d8..7c6e6662b1 100644
--- a/test/Cypress/cypress/integration/Gherkin/wallet/wallet.js
+++ b/test/Cypress/cypress/integration/Gherkin/wallet/wallet.js
@@ -1,45 +1,36 @@
import {Given, When, Then, And} from "cypress-cucumber-preprocessor/steps"
+const totp = require("totp-generator");
const randomUsername = Math.random().toString(36).substring(2,6);
-const username = randomUsername+Cypress.env('NEW_USER')
-
-Given ('I sign up with a random username and the current password',()=>{
-
- cy.writeFile('cypress\\fixtures\\example.json', { name: 'Newuser', email: username })
- cy.log(username);
- cy.visit(Cypress.env('SIGN_UP_PAGE'))
- cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('.checkfield-input').should('not.be.checked')
- cy.get('[name="email"]').clear().type(username)
- cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
- cy.get('[name="password_repeat"]').clear().type(Cypress.env('PASSWORD'))
- cy.get('.checkfield-input').should('be.enabled').check();
- cy.get('.holla-button').should('be.visible').should('be.enabled').click()
- cy.get('.warning_text').should("not.be.always.exist")
- cy.get('.icon_title-text').should('contain', 'Email sent')
-
- })
-
-When ('Admin confirms the new user',()=>{
-
- cy.visit(Cypress.env('LOGIN_PAGE'))
- cy.get('.holla-button').should('be.visible').should('be.disabled')
- cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
- cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
- cy.get('.holla-button').should('be.visible').should('be.enabled').click()
- cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
- .should('contain',Cypress.env('ADMIN_USER'))
- cy.contains('Operator controls').click()
- cy.contains('Users').click({force: true})
- cy.get('.ant-input').type(username)
- cy.get('.ant-btn').click()
- cy.get(':nth-child(2) > :nth-child(1) > .info-link').click()
- cy.get('.mb-4 > :nth-child(2)').should('contain','Are you sure you want to mark this email as verified?')
- cy.get(':nth-child(1) > :nth-child(3) > .ant-btn').click()
-})
+var username = "tester+"+randomUsername+Cypress.env('NEW_USER')
+
+
+Given ('Admin creats a user with a random username and the current password',()=>{
+
+ cy.log(username);
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env('ADMIN_USER'))
+ cy.get('[name="password"]').clear().type(Cypress.env('ADMIN_PASS'))
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+ cy.contains('Operator controls').click()
+ cy.contains('Users').click()
+ cy.contains('Add new user').click()
+ cy.get('#addUser_userEmail').clear().type(username)
+ cy.get('#addUser_password').clear().type(Cypress.env('PASSWORD'))
+ cy.get('#addUser_confirmPassword').clear().type(Cypress.env('PASSWORD'))
+ cy.get('[type="submit"]').click()
+ cy.wait(3000)
+ cy.contains(username)
+
+})
+
+Then ('The new username is stored',()=>{
-Then ('I registered with the new username',()=>{})
+ cy.writeFile('cypress\\fixtures\\example.json', { name: 'Newuser', email: username })
+})
Given ("I logged in succesfully",()=>{
@@ -49,7 +40,7 @@ Given ("I logged in succesfully",()=>{
cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
cy.get('.holla-button').should('be.visible').should('be.enabled').click()
cy.get('.warning_text').should('not.exist')
- cy.get('#trade-nav-container > :nth-child(3) > :nth-child(2)')
+ cy.get('#trade-nav-container > :nth-child(4) > :nth-child(2)')
.should('contain',username.toLowerCase())
cy.visit(Cypress.env('WEBSITE')+"wallet/usdt")
cy.get('[href="/wallet/usdt/deposit"] > .holla-button').click()
@@ -90,4 +81,21 @@ Then ("I should have ERC20 wallet",()=>{
cy.contains('Your USD Tether receiving address').should('exist')
cy.get('#network-eth-undefined').should('exist')
+ })
+
+And ("I genrate BEP20 wallet",()=>{
+
+ cy.get(':nth-child(1) > :nth-child(1) > :nth-child(1) > .field-content > .field-children')
+ .click()
+ cy.get('#network-bnb-2').click({force:true})
+ cy.get('.holla-button').click({force:true})
+ cy.get('.font-weight-bold > .edit-wrapper__container').should('contain','Generate USD Tether Wallet')
+ cy.get('.mt-4 > :nth-child(3)').click({force:true})
+})
+
+Then ("I should have BEP20 wallet",()=>{
+
+ cy.contains('Your USD Tether receiving address').should('exist')
+ cy.get('#network-bnb-undefined').should('exist')
+
})
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/walletLoadPerf.feature b/test/Cypress/cypress/integration/Gherkin/walletLoadPerf.feature
new file mode 100644
index 0000000000..bfab903d48
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/walletLoadPerf.feature
@@ -0,0 +1,11 @@
+Feature: Wallet Load Balance Feature
+
+As a valid customer
+In order to test the performance of wallet loading
+I want to login successfully to HollaeX and check wallet
+
+Scenario: Wallet load time
+
+ Given I am in the Hollaex login page
+ When I enter credentials Username,Password
+ Then I should be able to view the wallet balances within 2594.199999988079 ms
\ No newline at end of file
diff --git a/test/Cypress/cypress/integration/Gherkin/walletLoadPerf/walletLoadPerf.js b/test/Cypress/cypress/integration/Gherkin/walletLoadPerf/walletLoadPerf.js
new file mode 100644
index 0000000000..eadbf0f7b0
--- /dev/null
+++ b/test/Cypress/cypress/integration/Gherkin/walletLoadPerf/walletLoadPerf.js
@@ -0,0 +1,47 @@
+import { commandTimings } from 'cypress-timings'
+commandTimings()
+import {Given, When, Then} from "cypress-cucumber-preprocessor/steps"
+
+Given ('I am in the Hollaex login page',()=>{
+ const t0 = performance.now();
+
+ cy.visit(Cypress.env('LOGIN_PAGE'))
+
+})
+
+When ('I enter credentials Username,Password',()=>{
+ const t0 = performance.now();
+
+ cy.get('.holla-button').should('be.visible').should('be.disabled')
+ cy.get('[name="email"]').clear().type(Cypress.env("USER0"))
+ cy.get('[name="password"]').clear().type(Cypress.env('PASSWORD'))
+ const t1 = performance.now();
+ cy.log('time')
+ cy.log(t1-t0)
+})
+
+Then ('I should be able to view the wallet balances within {float} ms',(timeRange)=>{
+
+ cy.get('.holla-button').should('be.visible').should('be.enabled').click()
+ cy.get('.warning_text').should('not.exist')
+
+
+ cy.contains('Wallet').click()
+ cy.get('.mb-2')
+ //.should('exist') // Assert that the element initially exists
+ .should('be.visible').then(($t0) => {; // Assert that the element is visible
+ const t0 = performance.now();
+ // After waiting for some time or performing some actions, the element should disappear
+ // After waiting or taking actions, assert that the element no longer exists
+ cy.get('.mb-2').should('not.exist')
+// cy.contains('Estimated Total Balance')
+ .then(($t0) => {;
+ const t1 = performance.now();
+ const time = t1-t0;
+ cy.log('time')
+ cy.log(t1-t0)
+ cy.log(time,timeRange)
+ expect(time).to.be.lessThan(timeRange)
+})})
+})
+
diff --git a/test/Cypress/cypress/plugins/index.js b/test/Cypress/cypress/plugins/index.js
index 7ff212d6c0..29e77517b1 100644
--- a/test/Cypress/cypress/plugins/index.js
+++ b/test/Cypress/cypress/plugins/index.js
@@ -1,36 +1,85 @@
///
-
-// ***********************************************************
-// This example plugins/index.js can be used to load plugins
-//
-// You can change the location of this file or turn off loading
-// the plugins file with the 'pluginsFile' configuration option.
-//
-// You can read more here:
-// https://on.cypress.io/plugins-guide
-// ***********************************************************
-
-// This function is called when a project is opened or re-opened (e.g. due to
-// the project's config changing)
+const { lighthouse, pa11y, prepareAudit } = require("cypress-audit");
+const cucumber = require('cypress-cucumber-preprocessor').default;
+const { simpleParser } = require('mailparser');
+const Imap = require('imap');
/**
* @type {Cypress.PluginConfig}
*/
-// eslint-disable-next-line no-unused-vars
-const cucumber = require('cypress-cucumber-preprocessor').default
module.exports = (on, config) => {
- on('file:preprocessor', cucumber())
+ // Prepare for audits
+ on("before:browser:launch", (browser = {}, launchOptions) => {prepareAudit(launchOptions); });
+// the file got updated
+
+ // Setup for cucumber preprocessor
+ on('file:preprocessor', cucumber());
+
+ // Custom task for fetching the last email
+ on('task', {
+ getLastEmail: (emailConfig) => {
+ return new Promise((resolve, reject) => {
+ const imap = new Imap(emailConfig);
+
+ function openInbox(cb) {
+ imap.openBox('INBOX', true, cb);
+ }
+
+ imap.once('ready', () => {
+ openInbox((err, box) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ imap.search(['ALL'], (err, results) => {
+ if (err) {
+ reject(err);
+ return;
+ }
+ if (results.length === 0) {
+ resolve('No messages found.');
+ imap.end();
+ return;
+ }
+ const lastMessageSeqNo = results[results.length - 1];
+ const f = imap.fetch(lastMessageSeqNo.toString(), { bodies: [''] });
+
+ f.on('message', (msg) => {
+ let emailBuffer = '';
+ msg.on('body', (stream) => {
+ stream.on('data', (chunk) => {
+ emailBuffer += chunk.toString('utf8');
+ });
+ });
+ msg.once('end', () => {
+ simpleParser(emailBuffer, (err, mail) => {
+ if (err) {
+ reject('Parsing error: ' + err);
+ } else {
+ const result = `Email Body: ${mail.text || 'No plain text body'}\nHTML Body: ${mail.html || 'No HTML body'}`;
+ resolve(result);
+ }
+ });
+ });
+ });
+
+ f.once('end', () => {
+ imap.end();
+ });
+ });
+ });
+ });
- // `on` is used to hook into various events Cypress emits
- // `config` is the resolved Cypress config
-}
+ imap.once('error', (err) => {
+ reject('IMAP error: ' + err);
+ });
-// const resizeObserverLoopErrRe = /^ResizeObserver loop limit exceeded/
+ imap.once('end', () => {
+ console.log('Connection ended');
+ });
-// Cypress.on('uncaught:exception', (err) => {
-// if (resizeObserverLoopErrRe.test(err.message)) {
-// // returning false here prevents Cypress from
-// // failing the test
-// return false
-// }
-// })
\ No newline at end of file
+ imap.connect();
+ });
+ }
+ });
+};
diff --git a/test/Cypress/cypress/support/commands.js b/test/Cypress/cypress/support/commands.js
index 11cff6395b..e82ee1caad 100644
--- a/test/Cypress/cypress/support/commands.js
+++ b/test/Cypress/cypress/support/commands.js
@@ -32,6 +32,7 @@
//var randomstring = require('randomstring');
// import 'cypress-iframe';
// or
+import 'cypress-audit/commands';
require('@4tw/cypress-drag-drop');
require('cypress-iframe');
Cypress.Commands.add('getIframe', (iframe) => {
@@ -41,31 +42,37 @@ Cypress.Commands.add('getIframe', (iframe) => {
.then(cy.wrap).invoke('text').then((text)=> {return text.toString()})
})
-// Cypress.Commands.add('defineNewUser',(User,i) =>{
-// const newUser = randomstring.generate(i)+'@'+User ;
-// //console.log(newUser);
-
-// if (typeof localStorage === 'undefined' || localStorage === null) {
-// var LocalStorage = require('node-localstorage').LocalStorage;
-// localStorage = new LocalStorage('./../Utils/scratch');
-// }
-
-// localStorage.setItem('NewUser', newUser);
-// //console.log(localStorage.getItem('NewUser'));
-// return localStorage.getItem('NewUser');
-// })
-// Cypress.Commands.add('getNewUser',()=>{
-// if (typeof localStorage === 'undefined' || localStorage === null) {
-// var LocalStorage = require('node-localstorage').LocalStorage;
-// localStorage = new LocalStorage('./../Utils/scratch');
-// }
-
-// return localStorage.getItem('NewUser');
-// })
+Cypress.Commands.add('finder',(str,obj)=>{
+ let arr = null;
+ arr = (str.replace(/(=\\r\\n|\\n|\\r|\\t)/gm,"")).split(' ');
+
+ //const arr = text.split('');
+ //var re = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%\/.\w-]*)?\??(?:[-+=&;%@.\w]*)#?\w*)?)/gm;
+ var m;
+
+ //console.log('command : '+Name)
+ // while ((m = re.exec(text)) !== null) {
+ // if (m.index === re.lastIndex) {
+ // re.lastIndex++;
+ // }
+ // arr.push(m[0]);
+ // console.log(arr)
+ // }
+
+
+ for (var i = 0; i < arr.length; i++) {
+ if ((arr[i]).startsWith(obj)) {
+ cy.log("link",arr[i].toString())
+ return (arr[i].toString());
+ }
+
+
+}
+//expect(arr[3].toString()).to.equal(Name.toString()+';')
+console.log("arr 2"+arr[3])
+})
-//var text= null;
-// var Link =null;
Cypress.Commands.add('trimmer',(str,obj,Name)=>{
let text = null;
@@ -87,7 +94,7 @@ Cypress.Commands.add('trimmer',(str,obj,Name)=>{
for (var i = 0; i < arr.length; i++) {
if ((arr[i]).startsWith(obj)) {
- console.log("finction link",arr[i].toString())
+ console.log("link",arr[i].toString())
return (arr[i].toString());
}
@@ -117,7 +124,7 @@ Cypress.Commands.add('wallectCheck',(buySell,wallet,Fee,Size,Price)=>{
if(wallet == 'USD Tether'){
cy.get('.accordion_section_content_text').contains(wallet).click()
- if(Size = 0){cy.get('.wallet_section-content > :nth-child(3) > :nth-child(2)').as('choose')}
+ if(Size == 0){cy.get('.wallet_section-content > :nth-child(3) > :nth-child(2)').as('choose')}
else{cy.get('.wallet_section-content > :nth-child(2) > :nth-child(2)').as('choose')}
//cy.get('.wallet_section-content > :nth-child(2) > :nth-child(2)')
@@ -135,16 +142,32 @@ Cypress.Commands.add('wallectCheck',(buySell,wallet,Fee,Size,Price)=>{
cy.log(nmb);
cy.log('second', text)
})
- cy.get('@currentAmount')
- .then(val => {
- var fee = Fee
- var size = Size
- var price = Price
- var eq = ((size*price)*(1+fee)).toFixed(1)//+0.0374
- cy.get('@reservedAmount')
- .then(val1 => expect(parseFloat((parseFloat(val1)-parseFloat(val)).toFixed(1)))
- .to.equal(parseFloat(eq)))
- })
+ cy.get('@currentAmount').then(currentAmount => {
+ const FEE = Fee; // Assuming Fee is a constant or predefined value
+ const SIZE = Size; // Assuming Size is a constant or predefined value
+ const PRICE = Price; // Assuming Price is a constant or predefined value
+
+ cy.log("Calculate the expected amount after applying the fee")
+ const expectedFeeAmount = ((SIZE * PRICE) * (1 + FEE)).toFixed(1);
+ cy.log(expectedFeeAmount)
+ cy.get('@reservedAmount').then(reservedAmount => {
+ cy.log(reservedAmount)
+ cy.log(currentAmount)
+ const actualDifference = parseFloat(reservedAmount) - parseFloat(currentAmount);
+
+ expect(actualDifference).to.equal(parseFloat(expectedFeeAmount));
+ });
+});
+// cy.get('@currentAmount')
+// .then(val => {
+// var fee = Fee
+// var size = Size
+// var price = Price
+// var eq = ((size*price)*(1+fee)).toFixed(1)//+0.0374
+// cy.get('@reservedAmount')
+// .then(val1 => expect(parseFloat((parseFloat(val1)-parseFloat(val)).toFixed(1)))
+// .to.equal(parseFloat(eq)))
+// })
}
else{
cy.get('.accordion_section_content_text').contains(wallet).click()
@@ -162,17 +185,51 @@ Cypress.Commands.add('wallectCheck',(buySell,wallet,Fee,Size,Price)=>{
cy.log(nmb);
cy.log('second', text)
})
- cy.get('@currentAmount')
- .then(val => {
- var fee = Fee
- var size = Size
- var price = Price
- var eq = ((size*price)*(1+fee)).toFixed(1)//+0.0374
- cy.get('@reservedAmount')
- .then(val1 => expect(parseFloat((parseFloat(val1)-parseFloat(val)).toFixed(1)))
- .to.equal(parseFloat(eq)))
- })
+ cy.get('@currentAmount').then(currentAmount => {
+ const FEE = Fee; // Assuming Fee is a constant or predefined value
+ const SIZE = Size; // Assuming Size is a constant or predefined value
+ const PRICE = Price; // Assuming Price is a constant or predefined value
+
+ // Calculate the expected amount after applying the fee
+ const expectedFeeAmount = ((SIZE * PRICE) * (1 + FEE)).toFixed(1);
+ cy.log(expectedFeeAmount)
+ cy.get('@reservedAmount').then(reservedAmount => {
+ cy.log(reservedAmount)
+ cy.log(currentAmount)
+ const actualDifference = parseFloat(reservedAmount) - parseFloat(currentAmount);
+
+ expect(actualDifference).to.equal(parseFloat(expectedFeeAmount));
+ });
+});
+
}
})
+// cypress/support/commands.js
+
+// Add a custom command for extracting text between "Dear" and "Regards"
+Cypress.Commands.add('extractText', (emailBody,firstWord,lastWord) => {
+ const START = firstWord || 'Dear';
+ const END = lastWord || 'Regards'
+ const startIndex = emailBody.indexOf(START);
+ const endIndex = emailBody.indexOf(END, startIndex);
+
+ if (startIndex !== -1 && endIndex !== -1) {
+ return emailBody.substring(startIndex, endIndex + END.length);
+ } else {
+ return "The specified text was not found.";
+ }
+});
+
+// Add a custom command for finding the first word after "operation."
+Cypress.Commands.add('findFirstWordAfterMyWord', (extractedText,myWord) => {
+ const parts = extractedText.split(myWord);
+ if (parts.length > 1) {
+ const firstWordWithPunctuation = parts[1].trim().split(/\s+|\b/)[0];
+ const firstWord = firstWordWithPunctuation.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"");
+ return firstWord;
+ } else {
+ return "The word 'operation.' was not found.";
+ }
+});
diff --git a/version b/version
index e3bed5e1c4..10c2c0c3d6 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-2.9.5
\ No newline at end of file
+2.10.0
diff --git a/web/package-lock.json b/web/package-lock.json
index 5087016493..b05c1b463f 100644
--- a/web/package-lock.json
+++ b/web/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "hollaex-kit",
- "version": "2.9.3",
+ "version": "2.10.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/web/package.json b/web/package.json
index 46fd8aa402..97a87c9eac 100644
--- a/web/package.json
+++ b/web/package.json
@@ -1,6 +1,6 @@
{
"name": "hollaex-kit",
- "version": "2.9.5",
+ "version": "2.10.0",
"private": true,
"dependencies": {
"@ant-design/compatible": "1.0.5",
diff --git a/web/public/assets/images/assets-list-bottom-nav-mobile.svg b/web/public/assets/images/assets-list-bottom-nav-mobile.svg
new file mode 100644
index 0000000000..5f7c4ddb4a
--- /dev/null
+++ b/web/public/assets/images/assets-list-bottom-nav-mobile.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/assets/images/chart-view-mobile.svg b/web/public/assets/images/chart-view-mobile.svg
new file mode 100644
index 0000000000..138268afab
--- /dev/null
+++ b/web/public/assets/images/chart-view-mobile.svg
@@ -0,0 +1,4 @@
+
+
+
\ No newline at end of file
diff --git a/web/public/assets/images/help-question-mark-icon.svg b/web/public/assets/images/help-question-mark-icon.svg
index 6b8df853d3..d06b9a9c31 100644
--- a/web/public/assets/images/help-question-mark-icon.svg
+++ b/web/public/assets/images/help-question-mark-icon.svg
@@ -3,7 +3,7 @@
\ No newline at end of file
diff --git a/web/public/assets/images/quick-trade-bottom-nav-mobile.svg b/web/public/assets/images/quick-trade-bottom-nav-mobile.svg
new file mode 100644
index 0000000000..dbb59c4768
--- /dev/null
+++ b/web/public/assets/images/quick-trade-bottom-nav-mobile.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/public/assets/images/quick-trade-tab-white.svg b/web/public/assets/images/quick-trade-tab-white.svg
new file mode 100644
index 0000000000..aa0beec939
--- /dev/null
+++ b/web/public/assets/images/quick-trade-tab-white.svg
@@ -0,0 +1,9 @@
+
+
+
diff --git a/web/public/assets/images/transaction-history-bottom-nav-mobile.svg b/web/public/assets/images/transaction-history-bottom-nav-mobile.svg
new file mode 100644
index 0000000000..4f1fba7a20
--- /dev/null
+++ b/web/public/assets/images/transaction-history-bottom-nav-mobile.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/web/src/_customVariables.css b/web/src/_customVariables.css
index 3b1420c97c..d40b58a175 100644
--- a/web/src/_customVariables.css
+++ b/web/src/_customVariables.css
@@ -24,6 +24,7 @@
--trading_buying-related-elements: #00a69c;
--specials_buttons-links-and-highlights: #0066b4;
+ --specials_buttons-links-and-highlights-2: #1E70FF;
--specials_chat-messages: #98ccb2;
--specials_my-username-in-chat: #ffff00;
--specials_checks-okay-done: #008000;
diff --git a/web/src/_variables.scss b/web/src/_variables.scss
index e776cdc8b8..a023143cc7 100644
--- a/web/src/_variables.scss
+++ b/web/src/_variables.scss
@@ -94,6 +94,7 @@ $pale-buy: var(--calculated_public-sale_buying-zeros);
$pale-sell: var(--calculated_public-sale_selling-zeros);
$link: var(--specials_buttons-links-and-highlights);
+$link_2: var(--specials_buttons-links-and-highlights-2);
$trade-fields: var(--labels_fields);
$trade-fields-border: $colors-border;
@@ -185,18 +186,18 @@ $app-content-display-width: 75rem;
$content-display-vertical-margin: 1rem;
$background-patter--size: 30rem;
-$font-size-headline: 4rem;
-$font-size-headline2: 3.5rem;
-$font-size-subhead1: 2.6rem;
-$font-size-subhead2: 1.8rem;
-$font-size-subhead3: 1.3rem;
-$font-size-subhead4: 1.2rem;
+$font-size-headline: 4.5rem;
+$font-size-headline2: 4rem;
+$font-size-subhead1: 2.9rem;
+$font-size-subhead2: 2rem;
+$font-size-subhead3: 1.4rem;
+$font-size-subhead4: 1.3rem;
$font-size-main: 1.1rem;
$font-size-subtext: 1rem;
-$font-size-mobile-title: 15px;
+$font-size-mobile-title: 17px;
$font-size-mobile-txt: 13px;
-$font-size-mobile-innertxt: 11px;
-$font-size-small-expandable: 0.84rem;
+$font-size-mobile-innertxt: 13px;
+$font-size-small: 0.9rem;
$form-width-max: $content-display-width / 2;
@@ -212,8 +213,9 @@ $dialog-min-width: 25rem;
$dialog-min-height: 30rem;
// APP BAR
-$navigation_bar-height: 36px;
-$navigation_bar-height-lg: 45px;
+$navigation_bar-height: 4rem;
+$navigation_bar-height-lg: 5rem;
+$market_bar-height: 3rem;
$navigation_bar-action_button: 7.5rem;
$navigation_bar-font_size: $font-size-subtext;
diff --git a/web/src/actions/appActions.js b/web/src/actions/appActions.js
index 510cfc9e6d..4a604b5b19 100644
--- a/web/src/actions/appActions.js
+++ b/web/src/actions/appActions.js
@@ -108,6 +108,7 @@ export const WALLET_SORT = {
export const DIGITAL_ASSETS_SORT = {
CHANGE: 'change',
+ CHANGESEVENDAY: 'changeSevenDay',
};
export const SORT_EXP = {
@@ -172,6 +173,12 @@ export const setDigitalAssetsSortModeChange = () => ({
export const toggleDigitalAssetsSort = () => ({
type: TOGGLE_DIGITAL_ASSETS_SORT,
+ payload: DIGITAL_ASSETS_SORT.CHANGE,
+});
+
+export const toggleSortSevenDay = () => ({
+ type: SET_DIGITAL_ASSETS_SORT_MODE,
+ payload: DIGITAL_ASSETS_SORT.CHANGESEVENDAY,
});
export const setAdminDigitalAssetsSortData = ({
diff --git a/web/src/components/AppBar/MarketSelector.js b/web/src/components/AppBar/MarketSelector.js
index 2482f51595..17268516f1 100644
--- a/web/src/components/AppBar/MarketSelector.js
+++ b/web/src/components/AppBar/MarketSelector.js
@@ -4,8 +4,8 @@ import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { object, string, func } from 'prop-types';
import classnames from 'classnames';
-import { StarFilled, StarOutlined } from '@ant-design/icons';
-
+import { StarFilled, StarOutlined, ThunderboltFilled } from '@ant-design/icons';
+import { browserHistory } from 'react-router';
import { Slider, PriceChange, Coin } from 'components';
import { DEFAULT_COIN_DATA } from 'config/constants';
import STRINGS from 'config/localizedStrings';
@@ -38,7 +38,7 @@ class MarketSelector extends Component {
const { selectedTabMenu, searchValue } = this.state;
this.onAddTabClick(selectedTabMenu);
- this.handleSearch(undefined, searchValue);
+ this.handleSearch(searchValue);
}
UNSAFE_componentWillReceiveProps(nextProps) {
@@ -79,47 +79,51 @@ class MarketSelector extends Component {
return unique(Object.entries(pairs).map(([_, { pair_2 }]) => pair_2));
};
- onAddTabClick = (symbol) => {
- const { pairs } = this.props;
+ filterData = (data, filterValue, key1, key2, key3) => {
+ return data.filter((item) => {
+ const value1 = item[key1] || item[key2];
+ const value2 = item[key3];
- const tabResult = [];
- if (symbol === 'all') {
- this.setState({ tabResult: Object.keys(pairs), selectedTabMenu: symbol });
- } else {
- Object.entries(pairs).forEach(([key, { pair_2 }]) => {
- if (pair_2 === symbol) {
- tabResult.push(key);
- }
- });
-
- this.setState({ tabResult, selectedTabMenu: symbol });
- }
+ return (
+ value1.toLowerCase().indexOf(filterValue) !== -1 ||
+ value2?.toLowerCase()?.indexOf(filterValue) !== -1
+ );
+ });
};
- handleSearch = (_, value = '') => {
- const { pairs, coins } = this.props;
- const result = [];
- const searchValue = value ? value.toLowerCase().trim() : '';
+ onAddTabClick = (tabSymbol) => {
+ const { quicktrade, markets } = this.props;
+ const coinsData = this.getCoinsData(quicktrade, markets);
- if (!value) {
- this.setState({ searchResult: Object.keys(pairs), searchValue: '' });
- } else {
- Object.entries(pairs).forEach(([key, pair]) => {
- const { pair_base, pair_2 } = pair;
- const { fullname = '' } = coins[pair_base] || DEFAULT_COIN_DATA;
-
- if (
- key.indexOf(searchValue) !== -1 ||
- pair_base.indexOf(searchValue) !== -1 ||
- pair_2.indexOf(searchValue) !== -1 ||
- fullname.toLowerCase().indexOf(searchValue) !== -1
- ) {
- result.push(key);
- }
- });
-
- this.setState({ searchResult: result, searchValue: value });
- }
+ const tabResult =
+ tabSymbol === 'all'
+ ? coinsData
+ : this.filterData(coinsData, tabSymbol, 'key', 'symbol', 'fullname');
+
+ this.setState({ searchResult: tabResult, selectedTabMenu: tabSymbol });
+ };
+
+ handleSearch = (value = '') => {
+ const { quicktrade, markets } = this.props;
+ const { selectedTabMenu } = this.state;
+ const coinsData = this.getCoinsData(quicktrade, markets);
+
+ const searchValue = value ? value.toLowerCase().trim() : '';
+ const tabResult =
+ selectedTabMenu === 'all'
+ ? coinsData
+ : this.filterData(
+ coinsData,
+ selectedTabMenu,
+ 'key',
+ 'symbol',
+ 'fullname'
+ );
+ const result = !value
+ ? tabResult
+ : this.filterData(tabResult, searchValue, 'key', 'symbol', 'fullname');
+
+ this.setState({ searchResult: result, searchValue: value });
};
onViewMarketsClick = () => {
@@ -127,6 +131,13 @@ class MarketSelector extends Component {
this.closeAddTabMenu();
};
+ onViewAssetsClick = () => {
+ browserHistory.push('/assets');
+ if (isMobile) {
+ window.location.reload();
+ }
+ };
+
closeAddTabMenu = () => {
const { pairs } = this.props;
this.setState(
@@ -148,6 +159,7 @@ class MarketSelector extends Component {
toggleFavourite = (pair) => {
const { addToFavourites, removeFromFavourites } = this.props;
+
if (isLoggedIn()) {
return this.isFavourite(pair)
? removeFromFavourites(pair)
@@ -155,30 +167,65 @@ class MarketSelector extends Component {
}
};
- onMarketClick = (key) => {
+ onMarketClick = (key, isQuickTrade) => {
const { addTradePairTab } = this.props;
- addTradePairTab(key);
+ addTradePairTab(key, isQuickTrade);
this.closeAddTabMenu();
};
+ getTypeSortedData = (array) => {
+ return array.sort((a, b) => {
+ // Custom sorting logic: "pro" comes first
+ if (a.type === 'pro' && b.type !== 'pro') {
+ return -1;
+ } else if (a.type !== 'pro' && b.type === 'pro') {
+ return 1;
+ } else {
+ return 0;
+ }
+ });
+ };
+
+ movePinnedItems = (array) => {
+ const pinnedMarkets = this.props.pinned_markets;
+ const sortedArray = array.sort((a, b) => {
+ // Find the first ID that differs between the two objects
+ const id = pinnedMarkets.find((i) => a?.key !== b?.key);
+
+ if (id) {
+ // If a has the ID, move it to the top
+ return a?.key === id ? -1 : 1;
+ }
+
+ return 0;
+ });
+ return sortedArray;
+ };
+
+ getCoinsData = (quicktrade, markets) =>
+ this.getTypeSortedData(quicktrade).map((data) =>
+ data.type === 'pro'
+ ? markets.find(({ key }) => key === data.symbol) || { ...data }
+ : { ...data }
+ );
+
render() {
const {
wrapperClassName,
constants,
- markets: allMarkets,
+ markets,
pair: activeMarket,
+ quicktrade,
} = this.props;
- const { searchResult, tabResult } = this.state;
- const { handleSearch } = this;
-
- const markets = allMarkets.filter(
- ({ key }) => searchResult.includes(key) && tabResult.includes(key)
- );
+ const { searchResult } = this.state;
const tabMenuLength = markets.length;
const hasTabMenu = tabMenuLength !== 0;
+ const coinsData = this.movePinnedItems(
+ searchResult || this.getCoinsData(quicktrade, markets)
+ );
return (
@@ -191,19 +238,22 @@ class MarketSelector extends Component {
name={STRINGS['SEARCH_TXT']}
placeHolder={`${STRINGS['SEARCH_TXT']}...`}
className="app-bar-search-field"
- handleSearch={handleSearch}
+ handleSearch={(e) => this.handleSearch(e.target.value)}
showCross
/>
- {hasTabMenu ? (
- markets.map((market, index) => {
+ {hasTabMenu && coinsData.length > 0 ? (
+ coinsData.map((market, index) => {
const {
key,
pair,
ticker,
increment_price,
display_name,
+ symbol,
+ icon_id,
+ type,
} = market;
return (
@@ -213,14 +263,14 @@ class MarketSelector extends Component {
'app-bar-add-tab-content-list',
'd-flex align-items-center justify-content-start',
'pointer',
- { 'active-market': pair.name === activeMarket }
+ { 'active-market': pair?.name === activeMarket }
)}
>
this.toggleFavourite(key)}
+ onClick={() => this.toggleFavourite(key || symbol)}
>
- {this.isFavourite(key) ? (
+ {this.isFavourite(key || symbol) ? (
) : (
@@ -228,39 +278,64 @@ class MarketSelector extends Component {
this.onMarketClick(key)}
+ onClick={() =>
+ this.onMarketClick(
+ key || symbol,
+ type && type !== 'pro'
+ )
+ }
>
-
{display_name}:
-
- {formatToCurrency(ticker.close, increment_price)}
-
+
{display_name}
+ {ticker && (
+ <>
+
:
+
+ {formatToCurrency(ticker?.close, increment_price)}
+
+ >
+ )}
+ {type && type !== 'pro' && (
+
+
+
+ )}
);
})
) : (
-
- No data...
-
- )}
-
-
- {constants && constants.features && constants.features.pro_trade && (
-
-
- {STRINGS['VIEW_MARKET']}
-
+
+ {STRINGS['CANT_FIND_MARKETS']}
+
+ {STRINGS.formatString(
+ STRINGS['TRY_VISITING_ASSETS'],
+ this.onViewAssetsClick()}
+ className="text-underline blue-link pointer"
+ >
+ {STRINGS['ASSETS_PAGE']}
+
+ )}
)}
+
+ {constants && constants.features && constants.features.pro_trade && (
+
+
+ {STRINGS['VIEW_MARKET']}
+
+
+ )}
+
@@ -289,7 +364,15 @@ const mapDispatchToProps = (dispatch) => ({
const mapStateToProps = (store) => {
const {
- app: { pairs, coins, favourites, constants, pair },
+ app: {
+ pairs,
+ coins,
+ favourites,
+ constants,
+ pair,
+ quicktrade,
+ pinned_markets,
+ },
} = store;
return {
@@ -299,6 +382,8 @@ const mapStateToProps = (store) => {
favourites,
constants,
markets: MarketsSelector(store),
+ quicktrade,
+ pinned_markets,
};
};
diff --git a/web/src/components/AppBar/PairTabs.js b/web/src/components/AppBar/PairTabs.js
index 3aa918667d..3f5250b944 100644
--- a/web/src/components/AppBar/PairTabs.js
+++ b/web/src/components/AppBar/PairTabs.js
@@ -4,7 +4,6 @@ import classnames from 'classnames';
import { browserHistory } from 'react-router';
import { Dropdown } from 'antd';
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
-import _get from 'lodash/get';
import TabList from './TabList';
import MarketSelector from './MarketSelector';
@@ -68,13 +67,13 @@ class PairTabs extends Component {
initTabs = (pairs, activePair) => {};
- onTabClick = (pair) => {
- const { router, constants } = this.props;
+ onTabClick = (pair, isQuickTrade) => {
+ const { router } = this.props;
if (pair) {
- if (_get(constants, 'features.pro_trade')) {
- router.push(`/trade/${pair}`);
- } else if (_get(constants, 'features.quick_trade')) {
+ if (isQuickTrade) {
router.push(`/quick-trade/${pair}`);
+ } else {
+ router.push(`/trade/${pair}`);
}
this.setState({ activePairTab: pair });
}
@@ -87,7 +86,7 @@ class PairTabs extends Component {
isToolsSelectorVisible,
} = this.state;
- const { location, favourites, markets } = this.props;
+ const { location, favourites, markets, quicktrade } = this.props;
const market = markets.find(({ key }) => key === activePairTab) || {};
const {
key,
@@ -96,6 +95,7 @@ class PairTabs extends Component {
display_name,
} = market;
+ const filterQuickTrade = quicktrade.filter(({ type }) => type !== 'pro');
return (
@@ -175,7 +175,7 @@ class PairTabs extends Component {
{favourites && favourites.length > 0 && (
@@ -226,7 +226,7 @@ class PairTabs extends Component {
const mapStateToProps = (state) => {
const {
- app: { language: activeLanguage, pairs, favourites, constants },
+ app: { language: activeLanguage, pairs, favourites, constants, quicktrade },
orderbook: { prices },
} = state;
@@ -237,6 +237,7 @@ const mapStateToProps = (state) => {
favourites,
constants,
markets: MarketsSelector(state),
+ quicktrade
};
};
diff --git a/web/src/components/AppBar/Tab.js b/web/src/components/AppBar/Tab.js
index 5ad8617048..b779eaebdc 100644
--- a/web/src/components/AppBar/Tab.js
+++ b/web/src/components/AppBar/Tab.js
@@ -3,6 +3,7 @@ import classnames from 'classnames';
import { PriceChange } from 'components';
import { formatToCurrency } from 'utils/currency';
+import { ThunderboltFilled } from '@ant-design/icons';
const Tab = ({
tab,
@@ -18,6 +19,7 @@ const Tab = ({
pair: { increment_price } = {},
ticker: { close } = {},
display_name,
+ type
} = market;
return (
@@ -37,14 +39,25 @@ const Tab = ({
>
onTabClick(tab)}
+ onClick={() => onTabClick(tab, type && type !== 'pro')}
>
-
{display_name}:
-
- {formatToCurrency(close, increment_price)}
-
-
+
{display_name}
+ {increment_price && (
+ <>
+
+ :
+ {formatToCurrency(close, increment_price)}
+
+
+ >
+ )}
+ {type && type !== 'pro' && (
+
+
+
+ )}
+
diff --git a/web/src/components/AppBar/TabList.js b/web/src/components/AppBar/TabList.js
index de3bbd36d4..308a4bb7cb 100644
--- a/web/src/components/AppBar/TabList.js
+++ b/web/src/components/AppBar/TabList.js
@@ -9,26 +9,31 @@ const TabList = ({
activePairTab,
onTabClick,
markets,
-}) => (
-
- {isLoggedIn() &&
- items.map((tab, index) => {
- const market = markets.find(({ key }) => key === tab);
- return (
-
- );
- })}
-
-);
+}) => {
+
+ return (
+
+ {isLoggedIn() &&
+ items.map((tab, index) => {
+ const market = markets.find(
+ ({ key, symbol }) => key === tab || symbol === tab
+ );
+ return (
+
+ );
+ })}
+
+ );
+};
export default TabList;
diff --git a/web/src/components/AppBar/_AppBar.scss b/web/src/components/AppBar/_AppBar.scss
index e44de09067..2f4785b49f 100644
--- a/web/src/components/AppBar/_AppBar.scss
+++ b/web/src/components/AppBar/_AppBar.scss
@@ -31,7 +31,7 @@ $app-menu-width: calc(100vw - 40rem);
.app_bar-icon-logo {
height: 4rem;
- width: 5rem;
+ width: 7rem;
background-size: contain;
background-position: left center;
background-repeat: no-repeat;
@@ -724,8 +724,8 @@ $app-menu-width: calc(100vw - 40rem);
background: $app-light-background;
display: flex;
font-size: $navigation_bar-font_size;
- height: $navigation_bar-height;
- min-height: $navigation_bar-height;
+ height: $market_bar-height;
+ min-height: $market_bar-height;
position: relative;
width: 100%;
@@ -910,7 +910,7 @@ $app-menu-width: calc(100vw - 40rem);
.app-bar-add-tab-search {
color: $trade-tabs-inactive-color;
height: 2.2rem;
- margin-bottom: 0.5rem;
+ margin-bottom: 1rem;
}
.search-field {
@@ -980,7 +980,7 @@ $app-menu-width: calc(100vw - 40rem);
.app-bar-tab-menu-list {
background-color: $app-bar-background-color;
color: $app-bar-menu-list-color;
- font-size: 0.9rem;
+ font-size: 1rem;
height: 2.5rem;
padding: 0.5rem 0.8rem;
width: 3.2rem;
@@ -995,7 +995,7 @@ $app-menu-width: calc(100vw - 40rem);
}
.app-bar-add-tab-content {
- font-size: 0.9rem;
+ font-size: 1rem;
overflow-y: scroll;
padding: 0.3rem 0;
.scroll-view {
@@ -1016,6 +1016,10 @@ $app-menu-width: calc(100vw - 40rem);
color: $app-bar-menu-list-color;
height: 2.2rem;
+ .summary-quick-icon {
+ padding: 4px;
+ }
+
&:hover {
background-color: $app-light-background;
color: $colors-black;
@@ -1026,9 +1030,27 @@ $app-menu-width: calc(100vw - 40rem);
stroke-width: 0rem;
}
}
+
+ .summary-quick-icon {
+ background-color: $tag_background;
+ border-radius: 50%;
+ }
}
}
+ .app-bar-add-tab-content-no-market {
+ display: block;
+ text-align: center;
+ width: 100%;
+ margin: auto;
+ margin-top: 50%;
+ transform: translateY(-50%);
+ }
+
+ .text-underline {
+ text-decoration: underline;
+ }
+
.app-bar-tab-setting {
margin: 0.3rem 0.4rem 0 0.4rem;
width: 1rem;
diff --git a/web/src/components/AppMenuSidebar/_AppMenuSidebar.scss b/web/src/components/AppMenuSidebar/_AppMenuSidebar.scss
index 97313d2fb8..8ec4bef9a4 100644
--- a/web/src/components/AppMenuSidebar/_AppMenuSidebar.scss
+++ b/web/src/components/AppMenuSidebar/_AppMenuSidebar.scss
@@ -24,7 +24,7 @@
}
.app-menu-bar-side_list {
transition: 0.3s;
- height: 60px;
+ height: 45px;
padding: 18px 17px;
cursor: pointer;
color: $colors-black !important;
@@ -32,7 +32,7 @@
.side-bar-txt {
height: 24px;
margin: 0 0 0 14px;
- font-size: 16px;
+ font-size: 1.2rem;
}
}
.list_active,
diff --git a/web/src/components/Button/index.js b/web/src/components/Button/index.js
index 21583df706..1794461e6f 100644
--- a/web/src/components/Button/index.js
+++ b/web/src/components/Button/index.js
@@ -59,7 +59,7 @@ const Button = ({
) : (
diff --git a/web/src/components/Chart/Charts/DonutChart.js b/web/src/components/Chart/Charts/DonutChart.js
index a3220b8cf1..d3470cc553 100644
--- a/web/src/components/Chart/Charts/DonutChart.js
+++ b/web/src/components/Chart/Charts/DonutChart.js
@@ -240,7 +240,7 @@ class DonutChart extends Component {
}
renderSlice = (value, i, width, height) => {
- const { showOpenWallet } = this.props;
+ const { showOpenWallet, centerText } = this.props;
let data = value.data;
let minViewportSize = Math.min(width, height);
let activeSlice = this.state.hoverId === data.symbol;
@@ -276,7 +276,7 @@ class DonutChart extends Component {
fill-opacity="0.2"
/>
{STRINGS['ZERO_ASSET']}
-
- {STRINGS['ZERO_ASSET_2']}
-
-
- {STRINGS['ZERO_ASSET_3']}
-
{showOpenWallet && (
this.handleHover(data.symbol)}
onMouseOut={this.handleOut}
/>
{activeSlice ? (
{data.balancePercentage}
{data.display_name === 'Others' ? 'Others' : display_name}
diff --git a/web/src/components/Chart/_Chart.scss b/web/src/components/Chart/_Chart.scss
index d71b7855f8..cf510a822d 100644
--- a/web/src/components/Chart/_Chart.scss
+++ b/web/src/components/Chart/_Chart.scss
@@ -28,7 +28,11 @@
}
.chart_slice {
- fill-opacity: 0.7;
+ fill-opacity: 0.5;
+}
+
+.others-color {
+ fill: var(--labels_inactive-button);
}
.slice_active {
diff --git a/web/src/components/CheckDeposit/index.js b/web/src/components/CheckDeposit/index.js
index 4fdeeeb0c4..744e54d25b 100644
--- a/web/src/components/CheckDeposit/index.js
+++ b/web/src/components/CheckDeposit/index.js
@@ -49,7 +49,7 @@ const CheckDeposit = ({
};
formFields.currency = {
- type: 'select',
+ type: 'autocomplete',
stringId: 'COINS,DEPOSIT_STATUS.CURRENCY_FIELD_LABEL',
label: STRINGS['COINS'],
placeholder: STRINGS['DEPOSIT_STATUS.CURRENCY_FIELD_LABEL'],
diff --git a/web/src/components/Form/FormFields/InputField.js b/web/src/components/Form/FormFields/InputField.js
index 58565f115f..cdcb461aba 100644
--- a/web/src/components/Form/FormFields/InputField.js
+++ b/web/src/components/Form/FormFields/InputField.js
@@ -45,7 +45,7 @@ const InputField = (props) => {
type={type}
{...input}
{...rest}
- // onBlur={() => {}}
+ onBlur={() => {}}
/>
diff --git a/web/src/components/Form/FormFields/_FieldWrapper.scss b/web/src/components/Form/FormFields/_FieldWrapper.scss
index f079270304..f6ce550dc8 100644
--- a/web/src/components/Form/FormFields/_FieldWrapper.scss
+++ b/web/src/components/Form/FormFields/_FieldWrapper.scss
@@ -330,3 +330,11 @@ $outline-height: 1.5px;
padding-bottom: 1rem;
}
}
+
+@media screen and (max-width: 550px) {
+ .field-children {
+ .clear-field {
+ bottom: 1.5rem;
+ }
+ }
+}
diff --git a/web/src/components/IconTitle/_IconTitle.scss b/web/src/components/IconTitle/_IconTitle.scss
index 92a6cc7dac..b24c490b9d 100644
--- a/web/src/components/IconTitle/_IconTitle.scss
+++ b/web/src/components/IconTitle/_IconTitle.scss
@@ -100,6 +100,14 @@ $title--margin: 1rem;
}
}
+.fees_limits {
+ .icon_title-text {
+ &.title {
+ font-size: 1.5rem !important;
+ }
+ }
+}
+
.layout-mobile {
.icon_title-wrapper {
// .auth_logo-wrapper {
diff --git a/web/src/components/PriceChange/index.js b/web/src/components/PriceChange/index.js
index 502c72b231..b716fe5c9e 100644
--- a/web/src/components/PriceChange/index.js
+++ b/web/src/components/PriceChange/index.js
@@ -17,7 +17,7 @@ class PriceChange extends Component {
market,
market: { ticker },
} = this.props;
- if (market && ticker && nextProp.market.ticker.close !== ticker.close) {
+ if (market && ticker && nextProp.market?.ticker && nextProp.market?.ticker.close !== ticker.close) {
const tickerDiff = nextProp.market.ticker.close - ticker.close;
this.setState((prevState) => ({
...prevState,
diff --git a/web/src/components/QuickTrade/InputGroup.js b/web/src/components/QuickTrade/InputGroup.js
index 3784826300..5150b6c640 100644
--- a/web/src/components/QuickTrade/InputGroup.js
+++ b/web/src/components/QuickTrade/InputGroup.js
@@ -8,19 +8,16 @@ import {
LoadingOutlined,
SyncOutlined,
} from '@ant-design/icons';
-import { isMobile } from 'react-device-detect';
import { DEFAULT_COIN_DATA } from 'config/constants';
import { minValue, maxValue } from 'components/Form/validations';
import { FieldError } from 'components/Form/FormFields/FieldWrapper';
import { translateError } from './utils';
import STRINGS from 'config/localizedStrings';
-import { EditWrapper, Coin } from 'components';
+import { Coin } from 'components';
import { getDecimals } from 'utils/utils';
const { Option } = Select;
-const { Group } = Input;
-// const DECIMALS = 4;
class InputGroup extends React.PureComponent {
state = {
@@ -89,14 +86,12 @@ class InputGroup extends React.PureComponent {
render() {
const { isOpen } = this.state;
const {
- name,
options,
inputValue,
selectValue,
onSelect,
limits = {},
autoFocus,
- stringId,
coins,
loading,
expired,
@@ -112,80 +107,77 @@ class InputGroup extends React.PureComponent {
const error = translateError(this.renderErrorMessage(inputValue));
return (
-
-
-
-
-