CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-testcafe

Automated browser testing for the modern web development stack.

Pending
Overview
Eval results
Files

user-roles.mddocs/

User Roles

TestCafe's Role system enables testing user authentication workflows by creating reusable user sessions that can be switched between during test execution.

Capabilities

Role Creation

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();
});

Role Switching

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');
});

Anonymous Role

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();
});

Role State Preservation

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();
});

Complex Authentication Flows

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 });

Role Error Handling

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();
    }
});

Role Best Practices

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

docs

assertions.md

browser-automation.md

client-functions.md

element-selection.md

index.md

programmatic-api.md

request-interception.md

user-roles.md

tile.json