295 lines
7.8 KiB
Markdown
295 lines
7.8 KiB
Markdown
# Log Utility
|
|
|
|
## Principle
|
|
|
|
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).
|
|
|
|
## Rationale
|
|
|
|
Console.log in Playwright tests has limitations:
|
|
|
|
- Not visible in HTML reports
|
|
- No test step integration
|
|
- No structured output
|
|
- Lost in terminal noise during CI
|
|
|
|
The `log` utility provides:
|
|
|
|
- **Report integration**: Logs appear in Playwright HTML reports
|
|
- **Test step decoration**: `log.step()` creates collapsible steps in UI
|
|
- **Object logging**: Automatically formats objects/arrays
|
|
- **Multiple levels**: info, step, success, warning, error, debug
|
|
- **Optional console**: Can disable console output but keep report logs
|
|
|
|
## Pattern Examples
|
|
|
|
### Example 1: Basic Logging Levels
|
|
|
|
**Context**: Log different types of messages throughout test execution.
|
|
|
|
**Implementation**:
|
|
|
|
```typescript
|
|
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 UI
|
|
- `info()`, `success()`, `warning()` for different message types
|
|
- `debug()` for detailed data (objects/arrays)
|
|
- `error()` with optional console suppression
|
|
- All logs appear in test reports
|
|
|
|
### Example 2: Object and Array Logging
|
|
|
|
**Context**: Log structured data for debugging without cluttering console.
|
|
|
|
**Implementation**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
- Objects auto-formatted as pretty JSON
|
|
- Arrays handled gracefully
|
|
- Nested structures supported
|
|
- All visible in Playwright report attachments
|
|
|
|
### Example 3: Test Step Organization
|
|
|
|
**Context**: Organize test execution into collapsible steps for better readability in reports.
|
|
|
|
**Implementation**:
|
|
|
|
```typescript
|
|
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 sections
|
|
- Organize by Arrange-Act-Assert
|
|
- Steps visible in Playwright trace viewer
|
|
- Better debugging when tests fail
|
|
|
|
### Example 4: Conditional Logging
|
|
|
|
**Context**: Log different messages based on environment or test conditions.
|
|
|
|
**Implementation**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
- Log based on environment
|
|
- Skip logging with conditionals
|
|
- Use appropriate log levels
|
|
- Debug info for local, minimal for CI
|
|
|
|
### Example 5: Integration with Auth and API
|
|
|
|
**Context**: Log authenticated API requests with tokens (safely).
|
|
|
|
**Implementation**:
|
|
|
|
```typescript
|
|
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**:
|
|
|
|
- Never log full tokens (security risk)
|
|
- Use preview functions for sensitive data
|
|
- Combine with auth and API utilities
|
|
- Log at appropriate detail level
|
|
|
|
## Log Levels Guide
|
|
|
|
| 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 |
|
|
|
|
## Comparison with console.log
|
|
|
|
| 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 |
|
|
|
|
## Related Fragments
|
|
|
|
- `overview.md` - Basic usage and imports
|
|
- `api-request.md` - Log API requests
|
|
- `auth-session.md` - Log auth flow (safely)
|
|
- `recurse.md` - Log polling progress
|
|
|
|
## Anti-Patterns
|
|
|
|
**❌ Logging objects in steps:**
|
|
|
|
```typescript
|
|
await log.step({ user: 'test', action: 'create' }); // Shows empty in UI
|
|
```
|
|
|
|
**✅ Use strings for steps, objects for debug:**
|
|
|
|
```typescript
|
|
await log.step('Creating user: test'); // Readable in UI
|
|
await log.debug({ user: 'test', action: 'create' }); // Detailed data
|
|
```
|
|
|
|
**❌ Logging sensitive data:**
|
|
|
|
```typescript
|
|
await log.info(`Password: ${password}`); // Security risk!
|
|
await log.info(`Token: ${authToken}`); // Full token exposed!
|
|
```
|
|
|
|
**✅ Use previews or omit sensitive data:**
|
|
|
|
```typescript
|
|
await log.info('User authenticated successfully'); // No sensitive data
|
|
await log.debug({ tokenPreview: token.slice(0, 6) + '...' });
|
|
```
|
|
|
|
**❌ Excessive logging in loops:**
|
|
|
|
```typescript
|
|
for (const item of items) {
|
|
await log.info(`Processing ${item.id}`); // 100 log entries!
|
|
}
|
|
```
|
|
|
|
**✅ Log summary or use debug level:**
|
|
|
|
```typescript
|
|
await log.step(`Processing ${items.length} items`);
|
|
await log.debug({ itemIds: items.map((i) => i.id) }); // One log entry
|
|
```
|