Managing users efficiently during test automation is essential, especially when dealing with role-based access control and multiple user personas. In my custom test automation framework built on Node.js, leveraging Playwright, CucumberJS, and MongoDB, I have implemented a dynamic UserPool to streamline the handling of test users. This approach improves efficiency, scalability, and ensures smooth parallel test execution without conflicts or redundant logins.
In this post, I will explore the technologies involved and how the UserPool works to manage users dynamically in this automation framework.
Technology Stack
Here’s a quick overview of the technologies powering this framework:
1. Node.js
The framework is built with Node.js, a fast, scalable JavaScript runtime commonly used for backend services and automation frameworks. Node.js facilitates seamless interaction with Playwright and MongoDB in a highly asynchronous environment.
2. Playwright
Playwright is a robust browser automation library that supports modern browser engines such as Chromium, Firefox, and WebKit. It is ideal for automating tests involving different user interactions, and it easily handles parallel execution and session management.
3. CucumberJS
The framework uses CucumberJS for writing feature files in a Behavior-Driven Development (BDD) format. Cucumber enables tests to be written in plain English, making them more readable and maintainable, while still allowing powerful test execution.
4. MongoDB
MongoDB serves as the database for storing test user credentials. Its flexibility and document-based structure make it easy to manage various user roles and ensure that credentials can be accessed dynamically by the framework.
What is the UserPool?
The UserPool is a utility within the framework that manages test user credentials dynamically. Instead of hardcoding user credentials for every test, the UserPool retrieves user information from MongoDB and allocates them based on roles (such as “admin”, “user”, “read-only”, etc.). This approach improves test flexibility, reduces the risk of session conflicts, and supports parallel test execution without overlap.
Implementing the UserPool in Node.js
The UserPool is implemented as a Node.js class that interacts with MongoDB to fetch and manage user sessions. Below is the structure of the UserPool class, along with the newly added releaseAllUsers
method.
Storing Users in MongoDB
Users are stored in a MongoDB collection as documents. Each user has fields for their username, password, and role, along with an inUse
flag to indicate whether the user is currently engaged in a test:
{
"_id": ObjectId("617c1c8f8c7e1819dcae9b57"),
"username": "admin@example.com",
"password": "secureAdminPassword",
"role": "admin",
"inUse": false
},
{
"_id": ObjectId("617c1c8f8c7e1819dcae9b58"),
"username": "user@example.com",
"password": "secureUserPassword",
"role": "user",
"inUse": false
}
UserPool Class with releaseAllUsers
The following is the implementation of the UserPool class in Node.js, which interacts with MongoDB to retrieve users based on roles and ensure that they are not already in use.
const { MongoClient } = require('mongodb');
class UserPool {
constructor() {
this.db = null;
}
// Connect to the MongoDB database
async connect() {
if (!this.db) {
const client = new MongoClient(process.env.MONGO_URI);
await client.connect();
this.db = client.db(process.env.DB_NAME);
}
}
// Fetch a user based on role
async getUser(role) {
await this.connect();
const user = await this.db.collection('users').findOne({ role, inUse: false });
if (!user) {
throw new Error(`No available user found with role: ${role}`);
}
// Mark user as "in use" to avoid conflicts in parallel tests
await this.db.collection('users').updateOne({ _id: user._id }, { $set: { inUse: true } });
return {
username: user.username,
password: user.password
};
}
// Release all users (set inUse: false for all users)
async releaseAllUsers() {
await this.connect();
await this.db.collection('users').updateMany({}, { $set: { inUse: false } });
console.log('All users have been released and set to available.');
}
// Release a single user (after test execution)
async releaseUser(username) {
await this.connect();
await this.db.collection('users').updateOne({ username }, { $set: { inUse: false } });
console.log(`User ${username} has been released and set to available.`);
}
}
module.exports = new UserPool();
Explanation:
- Given Step: The
getUser
method fetches the user credentials based on the required role, ensuring the user is not already in use. - After Hook: Once the test scenario completes, the
releaseUser
method is called to release the user and make them available for future tests.
Benefits of Using releaseAllUsers
- Test Integrity: Ensuring that all users are released before the tests start helps maintain a clean testing environment, free from leftover sessions or locked users.
- Parallel Execution: The use of the
inUse
flag prevents session conflicts during parallel test execution by ensuring each test has access to a unique user. - Scalability: As the test suite grows, adding more users to the UserPool is simple and requires minimal code changes, allowing for more efficient and scalable test management.
Conclusion
Implementing a dynamic UserPool in a Node.js-based Playwright-Cucumber framework improves the way test users are managed. By utilizing MongoDB for storage and integrating methods like releaseAllUsers
in BeforeAll hooks, this solution ensures that tests run smoothly, even in parallel, without conflicts or redundancy.
If you’re working with multiple user roles or parallel test execution, incorporating a UserPool will significantly enhance the stability and scalability of your test framework.
Feel free to share your thoughts or reach out if you have any questions about implementing similar solutions in your projects!
Leave a Reply