Use structured logging that integrates with Playwright's test reports. Support object logging, test step decoration, and multiple log levels (info, step, success, warning, error, debug).
Console.log in Playwright tests has limitations:
The log utility provides:
log.step() creates collapsible steps in UIContext: Log different types of messages throughout test execution.
Implementation:
import { log } from '@seontechnologies/playwright-utils';
test('logging demo', async ({ page }) => {
await log.step('Navigate to login page');
await page.goto('/login');
await log.info('Entering credentials');
await page.fill('#username', 'testuser');
await log.success('Login successful');
await log.warning('Rate limit approaching');
await log.debug({ userId: '123', sessionId: 'abc' });
// Errors still throw but get logged first
try {
await page.click('#nonexistent');
} catch (error) {
await log.error('Click failed', false); // false = no console output
throw error;
}
});
Key Points:
step() creates collapsible steps in Playwright UIinfo(), success(), warning() for different message typesdebug() for detailed data (objects/arrays)error() with optional console suppressionContext: Log structured data for debugging without cluttering console.
Implementation:
test('object logging', async ({ apiRequest }) => {
const { body } = await apiRequest({
method: 'GET',
path: '/api/users',
});
// Log array of objects
await log.debug(body); // Formatted as JSON in report
// Log specific object
await log.info({
totalUsers: body.length,
firstUser: body[0]?.name,
timestamp: new Date().toISOString(),
});
// Complex nested structures
await log.debug({
request: {
method: 'GET',
path: '/api/users',
timestamp: Date.now(),
},
response: {
status: 200,
body: body.slice(0, 3), // First 3 items
},
});
});
Key Points:
Context: Organize test execution into collapsible steps for better readability in reports.
Implementation:
test('organized with steps', async ({ page, apiRequest }) => {
await log.step('ARRANGE: Setup test data');
const { body: user } = await apiRequest({
method: 'POST',
path: '/api/users',
body: { name: 'Test User' },
});
await log.step('ACT: Perform user action');
await page.goto(`/users/${user.id}`);
await page.click('#edit');
await page.fill('#name', 'Updated Name');
await page.click('#save');
await log.step('ASSERT: Verify changes');
await expect(page.getByText('Updated Name')).toBeVisible();
// In Playwright UI, each step is collapsible
});
Key Points:
log.step() creates collapsible sectionsContext: Log different messages based on environment or test conditions.
Implementation:
test('conditional logging', async ({ page }) => {
const isCI = process.env.CI === 'true';
if (isCI) {
await log.info('Running in CI environment');
} else {
await log.debug('Running locally');
}
const isKafkaWorking = await checkKafkaHealth();
if (!isKafkaWorking) {
await log.warning('Kafka unavailable - skipping event checks');
} else {
await log.step('Verifying Kafka events');
// ... event verification
}
});
Key Points:
Context: Log authenticated API requests with tokens (safely).
Implementation:
import { test } from '@seontechnologies/playwright-utils/fixtures';
// Helper to create safe token preview
function createTokenPreview(token: string): string {
if (!token || token.length < 10) return '[invalid]';
return `${token.slice(0, 6)}...${token.slice(-4)}`;
}
test('should log auth flow', async ({ authToken, apiRequest }) => {
await log.info(`Using token: ${createTokenPreview(authToken)}`);
await log.step('Fetch protected resource');
const { status, body } = await apiRequest({
method: 'GET',
path: '/api/protected',
headers: { Authorization: `Bearer ${authToken}` },
});
await log.debug({
status,
bodyPreview: {
id: body.id,
recordCount: body.data?.length,
},
});
await log.success('Protected resource accessed successfully');
});
Key Points:
| Level | When to Use | Shows in Report | Shows in Console |
|---|---|---|---|
step |
Test organization, major actions | ✅ Collapsible steps | ✅ Yes |
info |
General information, state changes | ✅ Yes | ✅ Yes |
success |
Successful operations | ✅ Yes | ✅ Yes |
warning |
Non-critical issues, skipped checks | ✅ Yes | ✅ Yes |
error |
Failures, exceptions | ✅ Yes | ✅ Configurable |
debug |
Detailed data, objects | ✅ Yes (attached) | ✅ Configurable |
| console.log | log Utility |
|---|---|
| Not in reports | Appears in reports |
| No test steps | Creates collapsible steps |
| Manual JSON.stringify() | Auto-formats objects |
| No log levels | 6 log levels |
| Lost in CI output | Preserved in artifacts |
overview.md - Basic usage and importsapi-request.md - Log API requestsauth-session.md - Log auth flow (safely)recurse.md - Log polling progress❌ Logging objects in steps:
await log.step({ user: 'test', action: 'create' }); // Shows empty in UI
✅ Use strings for steps, objects for debug:
await log.step('Creating user: test'); // Readable in UI
await log.debug({ user: 'test', action: 'create' }); // Detailed data
❌ Logging sensitive data:
await log.info(`Password: ${password}`); // Security risk!
await log.info(`Token: ${authToken}`); // Full token exposed!
✅ Use previews or omit sensitive data:
await log.info('User authenticated successfully'); // No sensitive data
await log.debug({ tokenPreview: token.slice(0, 6) + '...' });
❌ Excessive logging in loops:
for (const item of items) {
await log.info(`Processing ${item.id}`); // 100 log entries!
}
✅ Log summary or use debug level:
await log.step(`Processing ${items.length} items`);
await log.debug({ itemIds: items.map((i) => i.id) }); // One log entry