Authentication
This guide covers authentication strategies for testing Power Platform applications with Playwright.
Important: Proper authentication setup is critical for automated testing. This guide covers multiple authentication strategies for different scenarios.
Overview
Power Platform applications use Microsoft 365 authentication. This toolkit provides helpers for authenticating with Microsoft accounts.
Authentication Helper
The toolkit uses playwright-ms-auth for Microsoft authentication.
import { MsAuthHelper } from 'playwright-power-platform-toolkit';Basic Authentication Setup
Using Environment Variables
Warning: Never commit credentials to source control. Always use environment variables or secrets management.
The most common approach is to use environment variables for credentials.
Create a .env file:
MS_USERNAME=your.email@domain.com
MS_PASSWORD=your-password
MS_TENANT_ID=your-tenant-idSecurity: Add
.envto your.gitignorefile to prevent accidental commits.
Authenticating Before Tests
import { test } from '@playwright/test';
import { MsAuthHelper, AppProvider } from 'playwright-power-platform-toolkit';
test('authenticated test', async ({ page }) => {
// Authenticate
const authHelper = new MsAuthHelper(page);
await authHelper.login({
username: process.env.MS_USERNAME!,
password: process.env.MS_PASSWORD!
});
// Launch app (already authenticated)
const appProvider = new AppProvider(page);
const app = await appProvider.launchApp({
appUrl: process.env.CANVAS_APP_URL!,
appType: 'canvas'
});
// Test code...
});Reusing Authentication State
Tip: Reusing authentication state can significantly speed up your test suite by avoiding repeated logins.
For faster test execution, reuse authentication state across tests.
Setup Authentication Once
Create a setup script tests/setup/auth.setup.ts:
import { test as setup } from '@playwright/test';
import { MsAuthHelper } from 'playwright-power-platform-toolkit';
const authFile = 'playwright/.auth/user.json';
setup('authenticate', async ({ page }) => {
const authHelper = new MsAuthHelper(page);
await authHelper.login({
username: process.env.MS_USERNAME!,
password: process.env.MS_PASSWORD!
});
// Save authentication state
await page.context().storageState({ path: authFile });
});Configure Playwright to Use Saved State
Update playwright.config.ts:
import { defineConfig } from '@playwright/test';
export default defineConfig({
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'chromium',
dependencies: ['setup'],
use: {
storageState: 'playwright/.auth/user.json',
},
},
],
});Using Saved State in Tests
import { test } from '@playwright/test';
import { AppProvider } from 'playwright-power-platform-toolkit';
test('authenticated test with saved state', async ({ page }) => {
// No need to authenticate - using saved state
const appProvider = new AppProvider(page);
const app = await appProvider.launchApp({
appUrl: process.env.CANVAS_APP_URL!,
appType: 'canvas'
});
// Test code...
});Multi-Factor Authentication (MFA)
Interactive MFA
For accounts with MFA, you may need to authenticate interactively:
test('interactive MFA authentication', async ({ page }) => {
const authHelper = new MsAuthHelper(page);
await authHelper.login({
username: process.env.MS_USERNAME!,
password: process.env.MS_PASSWORD!,
waitForManualMfa: true, // Wait for user to complete MFA
mfaTimeout: 60000 // 60 seconds to complete MFA
});
// Continue with test...
});SMS/App-based MFA
test('SMS MFA authentication', async ({ page }) => {
const authHelper = new MsAuthHelper(page);
await authHelper.login({
username: process.env.MS_USERNAME!,
password: process.env.MS_PASSWORD!,
mfaCode: process.env.MS_MFA_CODE // One-time code from SMS/app
});
// Continue with test...
});Service Principal Authentication
Recommended for CI/CD: Service principals are the recommended authentication method for automated pipelines.
For CI/CD pipelines, use service principal authentication.
Setting Up Service Principal
- Register an Azure AD application
- Grant it appropriate permissions to Power Platform
- Create a client secret
- Note the Application (Client) ID and Tenant ID
Using Service Principal
import { test } from '@playwright/test';
import { MsAuthHelper } from 'playwright-power-platform-toolkit';
test('service principal auth', async ({ page }) => {
const authHelper = new MsAuthHelper(page);
await authHelper.loginWithServicePrincipal({
tenantId: process.env.MS_TENANT_ID!,
clientId: process.env.MS_CLIENT_ID!,
clientSecret: process.env.MS_CLIENT_SECRET!
});
// Continue with test...
});Environment Variables for Service Principal
MS_TENANT_ID=your-tenant-id
MS_CLIENT_ID=your-client-id
MS_CLIENT_SECRET=your-client-secretCI/CD Integration
GitHub Actions
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run tests
env:
MS_USERNAME: ${{ secrets.MS_USERNAME }}
MS_PASSWORD: ${{ secrets.MS_PASSWORD }}
CANVAS_APP_URL: ${{ secrets.CANVAS_APP_URL }}
run: npx playwright test
- name: Upload test results
if: always()
uses: actions/upload-artifact@v3
with:
name: playwright-report
path: playwright-report/Azure DevOps
trigger:
- main
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '18.x'
displayName: 'Install Node.js'
- script: |
npm ci
npx playwright install --with-deps
displayName: 'Install dependencies'
- script: npx playwright test
displayName: 'Run Playwright tests'
env:
MS_USERNAME: $(MS_USERNAME)
MS_PASSWORD: $(MS_PASSWORD)
CANVAS_APP_URL: $(CANVAS_APP_URL)
- task: PublishTestResults@2
displayName: 'Publish test results'
condition: always()
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: '**/test-results/*.xml'Best Practices
1. Never Commit Credentials
Always use environment variables or secrets management:
// Good
const username = process.env.MS_USERNAME;
// Bad - NEVER do this
const username = 'user@example.com';
const password = 'password123';2. Use Dedicated Test Accounts
Best Practice: Always use dedicated test accounts separate from production accounts.
Create dedicated test accounts for automation:
- Separate from production accounts
- No MFA (for easier automation)
- Limited permissions (security best practice)
3. Rotate Credentials Regularly
For CI/CD:
- Rotate client secrets regularly
- Use Azure Key Vault or similar for secret management
- Monitor for unusual authentication activity
4. Handle Authentication Failures Gracefully
test('handle auth failure', async ({ page }) => {
const authHelper = new MsAuthHelper(page);
try {
await authHelper.login({
username: process.env.MS_USERNAME!,
password: process.env.MS_PASSWORD!
});
} catch (error) {
console.error('Authentication failed:', error);
// Take screenshot for debugging
await page.screenshot({ path: 'auth-failure.png' });
throw error;
}
});5. Separate Authentication from Test Logic
// Create a test fixture
import { test as base } from '@playwright/test';
import { AppProvider } from 'playwright-power-platform-toolkit';
type Fixtures = {
authenticatedPage: Page;
appProvider: AppProvider;
};
export const test = base.extend<Fixtures>({
authenticatedPage: async ({ page }, use) => {
// Authenticate once per test
const authHelper = new MsAuthHelper(page);
await authHelper.login({
username: process.env.MS_USERNAME!,
password: process.env.MS_PASSWORD!
});
await use(page);
},
appProvider: async ({ authenticatedPage }, use) => {
const provider = new AppProvider(authenticatedPage);
await use(provider);
}
});
// Use in tests
test('my test', async ({ appProvider }) => {
const app = await appProvider.launchApp({...});
// Test code...
});Troubleshooting
Authentication Timeout
Increase timeout if authentication is slow:
await authHelper.login({
username: process.env.MS_USERNAME!,
password: process.env.MS_PASSWORD!,
timeout: 60000 // 60 seconds
});Conditional Access Policies
If your organization uses conditional access:
- Whitelist CI/CD IP addresses
- Use device-based authentication
- Configure trusted locations
Session Expiration
If sessions expire during long test runs:
test('long running test', async ({ page }) => {
// Re-authenticate if session expires
const authHelper = new MsAuthHelper(page);
async function ensureAuthenticated() {
const isAuthenticated = await authHelper.isAuthenticated();
if (!isAuthenticated) {
await authHelper.login({...});
}
}
await ensureAuthenticated();
// Test code...
// Re-check authentication after long operation
await page.waitForTimeout(300000); // 5 minutes
await ensureAuthenticated();
});Next Steps
- Advanced Usage - Explore advanced patterns and optimization
- API Reference - Complete API documentation
- Canvas Apps Guide - Start testing Canvas Apps
- Model-Driven Apps Guide - Start testing Model-Driven Apps