Files
Zachary D. Rowitsch 931f17b70e
All checks were successful
ci / fast (linux) (push) Successful in 6m46s
Add BMAD framework, planning artifacts, and architecture decision document
Install BMAD workflow framework with agent commands and templates.
Create product brief, PRD, project context, and architecture decision
document covering networking/persistence strategy, JS engine evolution
path, threading model, web_api scaling, system integration, and
tab/process model. Add generated project documentation (architecture
overview, component inventory, development guide, source tree analysis).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:13:06 -04:00

427 lines
11 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
## Quick Start
```typescript
import { log } from '@seontechnologies/playwright-utils';
// Basic logging
await log.info('Starting test');
await log.step('Test step shown in Playwright UI');
await log.success('Operation completed');
await log.warning('Something to note');
await log.error('Something went wrong');
await log.debug('Debug information');
```
## 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: Test Step Decorators
**Context**: Create collapsible test steps in Playwright UI using decorators.
**Page Object Methods with @methodTestStep:**
```typescript
import { methodTestStep } from '@seontechnologies/playwright-utils';
class TodoPage {
constructor(private page: Page) {
this.name = 'TodoPage';
}
readonly name: string;
@methodTestStep('Add todo item')
async addTodo(text: string) {
await log.info(`Adding todo: ${text}`);
const newTodo = this.page.getByPlaceholder('What needs to be done?');
await newTodo.fill(text);
await newTodo.press('Enter');
await log.step('step within a decorator');
await log.success(`Added todo: ${text}`);
}
@methodTestStep('Get all todos')
async getTodos() {
await log.info('Getting all todos');
return this.page.getByTestId('todo-title');
}
}
```
**Function Helpers with functionTestStep:**
```typescript
import { functionTestStep } from '@seontechnologies/playwright-utils';
// Define todo items for the test
const TODO_ITEMS = ['buy groceries', 'pay bills', 'schedule meeting'];
const createDefaultTodos = functionTestStep('Create default todos', async (page: Page) => {
await log.info('Creating default todos');
await log.step('step within a functionWrapper');
const todoPage = new TodoPage(page);
for (const item of TODO_ITEMS) {
await todoPage.addTodo(item);
}
await log.success('Created all default todos');
});
const checkNumberOfTodosInLocalStorage = functionTestStep('Check total todos count fn-step', async (page: Page, expected: number) => {
await log.info(`Verifying todo count: ${expected}`);
const result = await page.waitForFunction((e) => JSON.parse(localStorage['react-todos']).length === e, expected);
await log.success(`Verified todo count: ${expected}`);
return result;
});
```
### Example 5: File Logging
**Context**: Enable file logging for persistent logs.
**Implementation**:
```typescript
// playwright/support/fixtures.ts
import { test as base } from '@playwright/test';
import { log, captureTestContext } from '@seontechnologies/playwright-utils';
// Configure file logging globally
log.configure({
fileLogging: {
enabled: true,
outputDir: 'playwright-logs/organized-logs',
forceConsolidated: false, // One file per test
},
});
// Extend base test with file logging context capture
export const test = base.extend({
// Auto-capture test context for file logging
autoTestContext: [
async ({}, use, testInfo) => {
captureTestContext(testInfo);
await use(undefined);
},
{ auto: true },
],
});
```
### Example 6: 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
## Configuration
**Defaults:** console logging enabled, file logging disabled.
```typescript
// Enable file logging in config
log.configure({
console: true, // default
fileLogging: {
enabled: true,
outputDir: 'playwright-logs',
forceConsolidated: false, // One file per test
},
});
// Per-test override
await log.info('Message', {
console: { enabled: false },
fileLogging: { enabled: true },
});
```
### Environment Variables
```bash
# Disable all logging
SILENT=true
# Disable only file logging
DISABLE_FILE_LOGS=true
# Disable only console logging
DISABLE_CONSOLE_LOGS=true
```
### Level Filtering
```typescript
log.configure({
level: 'warning', // Only warning, error levels will show
});
// Available levels (in priority order):
// debug < info < step < success < warning < error
```
### Sync Methods
For non-test contexts (global setup, utility functions):
```typescript
// Use sync methods when async/await isn't available
log.infoSync('Initializing configuration');
log.successSync('Environment configured');
log.errorSync('Setup failed');
```
## 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
**DON'T log objects in steps:**
```typescript
await log.step({ user: 'test', action: 'create' }); // Shows empty in UI
```
**DO 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
```
**DON'T log sensitive data:**
```typescript
await log.info(`Password: ${password}`); // Security risk!
await log.info(`Token: ${authToken}`); // Full token exposed!
```
**DO use previews or omit sensitive data:**
```typescript
await log.info('User authenticated successfully'); // No sensitive data
await log.debug({ tokenPreview: token.slice(0, 6) + '...' });
```
**DON'T log excessively in loops:**
```typescript
for (const item of items) {
await log.info(`Processing ${item.id}`); // 100 log entries!
}
```
**DO 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
```