|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426 |
- # 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
- ```
|