Automated browser testing for the modern web development stack.
—
TestCafe's Role system enables testing user authentication workflows by creating reusable user sessions that can be switched between during test execution.
Create user roles with authentication logic and session state.
/**
* Creates a user role with authentication workflow
* @param loginUrl - URL where authentication should be performed
* @param initFn - Async function containing login logic
* @param options - Role configuration options
* @returns Role object that can be used to switch user context
*/
function Role(
loginUrl: string,
initFn: (t: TestController) => Promise<void>,
options?: RoleOptions
): Role;
interface Role {
/** Anonymous role for unauthenticated users */
static anonymous: Role;
}
interface RoleOptions {
/** Preserve cookies across role switches */
preserveUrl?: boolean;
}Usage Examples:
import { Role } from 'testcafe';
// Regular user role
const regularUser = Role('https://example.com/login', async t => {
await t
.typeText('#username', 'user@example.com')
.typeText('#password', 'password123')
.click('#login-button');
});
// Admin user role
const adminUser = Role('https://example.com/admin/login', async t => {
await t
.typeText('#admin-username', 'admin@example.com')
.typeText('#admin-password', 'admin123')
.click('#admin-login-button')
.expect(Selector('.admin-dashboard').exists).ok();
});
// Guest user (anonymous)
const guestUser = Role.anonymous;
fixture('User Roles')
.page('https://example.com');
test('Regular user workflow', async t => {
await t
.useRole(regularUser)
.expect(Selector('.user-dashboard').exists).ok()
.click('#profile-link')
.expect(Selector('.profile-page').exists).ok();
});
test('Admin user workflow', async t => {
await t
.useRole(adminUser)
.expect(Selector('.admin-panel').exists).ok()
.click('#manage-users')
.expect(Selector('.user-management').exists).ok();
});Switch between different user roles during test execution.
/**
* Switches to specified user role
* @param role - Role to switch to (created with Role function)
* @returns Promise resolving to test controller for chaining
*/
useRole(role: Role): Promise<TestController>;Usage Examples:
const user1 = Role('https://example.com/login', async t => {
await t
.typeText('#username', 'user1@example.com')
.typeText('#password', 'pass1');
});
const user2 = Role('https://example.com/login', async t => {
await t
.typeText('#username', 'user2@example.com')
.typeText('#password', 'pass2');
});
test('Multi-user collaboration', async t => {
// Start as user1
await t
.useRole(user1)
.click('#create-document')
.typeText('#document-title', 'Shared Document')
.click('#share-button')
.typeText('#share-email', 'user2@example.com')
.click('#send-invite');
// Switch to user2
await t
.useRole(user2)
.navigateTo('https://example.com/documents')
.expect(Selector('.shared-document').withText('Shared Document').exists).ok()
.click('.shared-document')
.typeText('.document-content', 'User2 contribution');
// Switch back to user1
await t
.useRole(user1)
.navigateTo('https://example.com/documents/shared')
.expect(Selector('.document-content').innerText).contains('User2 contribution');
});Test unauthenticated user workflows using the anonymous role.
/**
* Anonymous role representing unauthenticated users
*/
Role.anonymous: Role;Usage Examples:
const authenticatedUser = Role('https://example.com/login', async t => {
await t
.typeText('#username', 'user@example.com')
.typeText('#password', 'password123')
.click('#login-button');
});
test('Anonymous user restrictions', async t => {
// Start as anonymous user
await t
.useRole(Role.anonymous)
.navigateTo('https://example.com/protected')
.expect(Selector('.login-required-message').exists).ok();
// Try to access restricted features
await t
.navigateTo('https://example.com/dashboard')
.expect(Selector('.redirect-to-login').exists).ok();
});
test('Login flow from anonymous', async t => {
// Start anonymous, then authenticate
await t
.useRole(Role.anonymous)
.navigateTo('https://example.com')
.click('#login-link')
.useRole(authenticatedUser)
.expect(Selector('.welcome-message').exists).ok();
});Roles automatically preserve authentication state between uses.
// Role state is automatically preserved
const userRole = Role('https://example.com/login', async t => {
await t
.typeText('#username', 'user@example.com')
.typeText('#password', 'password123')
.click('#login-button');
});Usage Examples:
const persistentUser = Role('https://example.com/login', async t => {
await t
.typeText('#username', 'persistent@example.com')
.typeText('#password', 'password123')
.click('#login-button')
.expect(Selector('.dashboard').exists).ok();
});
test('First use of role', async t => {
await t
.useRole(persistentUser)
.click('#settings')
.typeText('#theme-preference', 'dark')
.click('#save-settings');
});
test('Second use of role (state preserved)', async t => {
await t
.useRole(persistentUser) // No re-authentication needed
.navigateTo('https://example.com/settings')
.expect(Selector('#theme-preference').value).eql('dark');
});
test('Role state across different tests', async t => {
await t
.useRole(persistentUser)
.expect(Selector('.user-name').innerText).eql('persistent@example.com')
.navigateTo('https://example.com/profile')
.expect(Selector('.profile-data').exists).ok();
});Handle complex authentication scenarios including multi-step login and OAuth.
// Multi-step authentication
const mfaUser = Role('https://example.com/login', async t => {
// Step 1: Username and password
await t
.typeText('#username', 'mfa-user@example.com')
.typeText('#password', 'password123')
.click('#login-button');
// Step 2: MFA code
await t
.expect(Selector('#mfa-code-input').exists).ok()
.typeText('#mfa-code-input', '123456')
.click('#verify-mfa');
// Step 3: Verify login success
await t
.expect(Selector('.dashboard').exists).ok();
});
// OAuth simulation
const oauthUser = Role('https://example.com/oauth/login', async t => {
await t
.click('#google-login-button')
.switchToWindow(w => w.url.includes('accounts.google.com'))
.typeText('#email', 'oauth-user@gmail.com')
.click('#next')
.typeText('#password', 'oauth-password')
.click('#signin')
.switchToWindow(w => w.url.includes('example.com'))
.expect(Selector('.oauth-success').exists).ok();
});Usage Examples:
test('Multi-factor authentication', async t => {
await t
.useRole(mfaUser)
.expect(Selector('.security-dashboard').exists).ok()
.click('#security-settings')
.expect(Selector('.mfa-enabled').exists).ok();
});
test('OAuth user permissions', async t => {
await t
.useRole(oauthUser)
.expect(Selector('.google-connected').exists).ok()
.click('#sync-google-data')
.expect(Selector('.sync-successful').exists).ok();
});
// Role with custom validation
const validatedUser = Role('https://example.com/login', async t => {
await t
.typeText('#username', 'validated@example.com')
.typeText('#password', 'password123')
.click('#login-button');
// Custom validation
const welcomeMessage = await Selector('.welcome').innerText;
if (!welcomeMessage.includes('validated@example.com')) {
throw new Error('Login validation failed');
}
}, { preserveUrl: true });Handle authentication failures and role switching errors.
// Roles with error handling
const fragileUser = Role('https://example.com/login', async t => {
try {
await t
.typeText('#username', 'fragile@example.com')
.typeText('#password', 'password123')
.click('#login-button');
// Wait for login to complete or fail
await t.expect(Selector('.dashboard, .login-error').exists).ok();
// Check if login failed
const hasError = await Selector('.login-error').exists;
if (hasError) {
throw new Error('Authentication failed');
}
} catch (error) {
console.log('Role initialization failed:', error.message);
throw error;
}
});Usage Examples:
test('Handle role authentication failure', async t => {
try {
await t.useRole(fragileUser);
// This will only execute if role authentication succeeded
await t.expect(Selector('.dashboard').exists).ok();
} catch (error) {
// Handle authentication failure
console.log('Authentication failed, testing anonymous flow');
await t
.useRole(Role.anonymous)
.navigateTo('https://example.com/public')
.expect(Selector('.public-page').exists).ok();
}
});
// Conditional role usage
test('Conditional authentication', async t => {
const shouldAuthenticate = process.env.TEST_AUTH === 'true';
if (shouldAuthenticate) {
await t.useRole(authenticatedUser);
await t.expect(Selector('.private-content').exists).ok();
} else {
await t.useRole(Role.anonymous);
await t.expect(Selector('.public-content').exists).ok();
}
});Recommended patterns for role usage and organization.
// Organize roles in separate file
// roles.js
export const users = {
admin: Role('https://example.com/admin/login', async t => {
await t
.typeText('#username', 'admin@example.com')
.typeText('#password', 'admin123')
.click('#login-button');
}),
regular: Role('https://example.com/login', async t => {
await t
.typeText('#username', 'user@example.com')
.typeText('#password', 'user123')
.click('#login-button');
}),
guest: Role.anonymous
};
// test file
import { users } from './roles';
fixture('User Workflows')
.page('https://example.com');
test('Admin workflow', async t => {
await t.useRole(users.admin);
// Admin-specific tests
});
test('Regular user workflow', async t => {
await t.useRole(users.regular);
// Regular user tests
});Install with Tessl CLI
npx tessl i tessl/npm-testcafe