Testing applications that support multiple user roles or account types often requires handling multiple user logins in a single test suite. Playwright is robust authentication tools simplify this by enabling persistent authentication, session management, and seamless switching between different user accounts.
In this blog, we’ll explore how to handle multiple user logins effectively in Playwright, ensuring your tests remain scalable, maintainable, and efficient.
Why Test Multiple User Logins?
Applications often have varying access levels or features based on user roles, such as:
- Admin vs. Regular User: Admins access additional controls or settings.
- Paid vs. Free Users: Paid accounts unlock premium features.
- Guest vs. Authenticated Users: Registered users can perform tasks unavailable to guests.
Testing these scenarios ensures role-specific functionality works as intended and maintains security boundaries.
Approach 1: Saving Storage States for Playwright Multiple Users
Playwright allows you to save storage states for each user after login. These saved states can then be reused in tests. By capturing the authenticated state of a user session (cookies, localStorage, sessionStorage), you can reuse this state across multiple tests without repeating the login process.
Steps to Handle Multiple Users
1. Save Storage States for Each Playwright User
Create a test that logs in with the required credentials for a specific user role and saves the storage state to a file.
Save the storage states to distinct files named according to the user role (e.g., admin.json, regular.json). Store them in a dedicated folder, such as playwright/.auth/, to keep your project organized.
import { test } from '@playwright/test';
test.describe('Admin Login', () => {
test('Admin dashboard access', async ({ page }) => {
// Admin user
await page.goto('https://conduit.bondaracademy.com/login');
await page.fill('[placeholder="Email"]', 'abc@cd.com');
await page.fill('[placeholder="Password"]', 'adminuser');
await page.click('button[type="submit"]');
await page.waitForTimeout(5000);
await page.context().storageState({ path: 'playwright/.auth/admin.json' });
});
});
test.describe('Regular user Login', () => {
test('Regular user access', async ({ page }) => {
// Regular user
await page.goto('https://conduit.bondaracademy.com/login');
await page.fill('[placeholder="Email"]', 'abcd@abc.com');
await page.fill('[placeholder="Password"]', 'regularuser');
await page.click('button[type="submit"]');
await page.waitForTimeout(5000);
await page.context().storageState({ path: 'playwright/.auth/regular.json' });
});
});
This approach can also be implemented under global setup file which would run before the test suite and the login step will not be counted a separate test case.
2. Reuse Storage States in Tests
Load the appropriate storage state for each test. Set up your test to load the storage state file corresponding to the desired user role using the test.use() method. This tells Playwright to initialize the browser context with the specified state.
import { expect, test } from '@playwright/test';
test.describe('Admin tests', () => {
test.use({ storageState: 'playwright/.auth/admin.json' });
test('Admin dashboard access', async ({ page }) => {
await page.goto('https://conduit.bondaracademy.com/');
await expect (page.locator('[class="nav navbar-nav pull-xs-right"]')).toContainText(" Admin_User ")
});
});
test.describe('Regular user tests', () => {
test.use({ storageState: 'playwright/.auth/regular.json' });
test('Regular user access', async ({ page }) => {
await page.goto('https://conduit.bondaracademy.com/');
await expect (page.locator('[class="nav navbar-nav pull-xs-right"]')).toContainText(" Regular_User2 ")
});
}); Benefits
- Tests run faster as logins are avoided for every test.
- Separate storage states ensure no interference between user sessions.
- Supports parallel test execution.
Approach 2: Using Different Contexts for Each Playwright User
When testing applications with multiple user roles, such as Admins, Regular Users, or Guests, using separate contexts in Playwright ensures isolated sessions for each user. Each context represents an independent browser session, complete with its own cookies, localStorage, and sessionStorage. This approach eliminates the risk of interference between different user sessions and allows simultaneous interactions with the application in the same test.
Steps to Use Contexts for Multiple Users
1. Create Contexts for Each Playwright User
Set up multiple contexts and log in as different users in each and then store the session to the dedicated folder.
import { test, chromium } from '@playwright/test';
test('Multiple users using contexts', async () => {
const browser = await chromium.launch();
// Admin context
const adminContext = await browser.newContext();
const adminPage = await adminContext.newPage();
await adminPage.goto('https://conduit.bondaracademy.com/login');
await adminPage.fill('[placeholder="Email"]', 'abc@cd.com');
await adminPage.fill('[placeholder="Password"]', 'adminuser');
await adminPage.click('button[type="submit"]');
await adminPage.waitForTimeout(5000);
await adminContext.storageState({ path: 'playwright/.auth/admin.json' });
// Regular user context
const userContext = await browser.newContext();
const userPage = await userContext.newPage();
await userPage.goto('https://conduit.bondaracademy.com/login');
await userPage.fill('[placeholder="Email"]', 'abcd@abc.com');
await userPage.fill('[placeholder="Password"]', 'regularuser');
await userPage.click('button[type="submit"]');
await userPage.waitForTimeout(5000);
await userContext.storageState({ path: 'playwright/.auth/regular.json' });
await browser.close();
}); 2. Run Tests with Separate Contexts
If you’ve saved storage states for each role, you can load these states into new contexts to skip the login process.
import { expect, test } from '@playwright/test';
test('Simultaneous admin and regular user actions', async ({ browser }) => {
const adminContext = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
const adminPage = await adminContext.newPage();
const userContext = await browser.newContext({ storageState: 'playwright/.auth/regular.json' });
const userPage = await userContext.newPage();
// Admin actions
await adminPage.goto('https://conduit.bondaracademy.com/');
await expect (adminPage.locator('[class="nav navbar-nav pull-xs-right"]')).toContainText(" Admin_User ")
// Regular user actions
await userPage.goto('https://conduit.bondaracademy.com/');
await expect (userPage.locator('[class="nav navbar-nav pull-xs-right"]')).toContainText(" Regular_User2 ")
await adminContext.close();
await userContext.close();
});Approach 3 : Implementation of authentication in POM fixtures.
We can add the storing of session per browser context in the below manner
Below is an example that creates fixtures for two Page Object Models - admin POM and user POM. It assumes adminStorageState.json and userStorageState.json files were created in the global setup.
import { test as base, type Page } from '@playwright/test';
// Page Object Model for the "admin" page.
class AdminPage {
// Page signed in as "admin".
page: Page;
constructor(page: Page) {
this.page = page;
}
}
// Page Object Model for the "regularUser" page.
class UserPage {
// Page signed in as "user".
page: Page;
constructor(page: Page) {
this.page = page;
}
}
// Declare the types of your fixtures.
type MyFixtures = {
adminPage: AdminPage;
userPage: UserPage;
};
export * from '@playwright/test';
export const test = base.extend<MyFixtures>({
adminPage: async ({ browser }, use) => {
const context = await browser.newContext({ storageState: 'playwright/.auth/admin.json' });
const adminPage = new AdminPage(await context.newPage());
await use(adminPage);
await context.close();
},
userPage: async ({ browser }, use) => {
const context = await browser.newContext({ storageState: 'playwright/.auth/regular.json' });
const userPage = new UserPage(await context.newPage());
await use(userPage);
await context.close();
},
}); Using the page fixtures in your tests
Using this approach makes your tests more clean and you can manage as many user roles sessions in fixture files.
import { test, expect } from '../playwright/fixtures';
// Use adminPage and userPage fixtures in the test.
test('admin and user', async ({ adminPage, userPage }) => {
// ... interact with both adminPage and userPage ...
await expect (adminPage.locator('[class="nav navbar-nav pull-xs-right"]')).toContainText(" Admin_User ")
await expect (userPage.locator('[class="nav navbar-nav pull-xs-right"]')).toContainText(" Regular_User2 ")
});Best Practices for Multiple User Logins in Playwright
- Use Role-Specific Test Descriptions: Clearly define tests based on user roles (e.g., Admin, Guest, Subscriber).
- Avoid Shared States: Keep user sessions isolated using contexts or separate storage states.
- Secure Test Credentials: Use environment variables to manage user credentials securely.
- Minimize Redundancy: Automate login steps once and reuse storage states.
- Parallelize Tests: Utilize Playwright’s parallel execution to run tests for different users simultaneously.
Conclusion
Handling authentication for multiple user logins in Playwright ensures that your application’s role-based functionality is thoroughly tested and secure. Whether you use persistent storage states or isolated browser contexts, by adopting these practices, you can enhance your test efficiency, reliability, and coverage, ensuring a seamless experience for all user roles.














