The Tale of 50 Tickets, 600 Tests, and the Logger That Changed Everything

How drowning in test logs led to building a smarter way to debug end-to-end tests with Playwright Smart Logger.

Harry Tran
8 min read · 1511 words

Picture this: It's Monday morning, and you're staring at your screen with a steaming cup of coffee, ready to tackle the week. Then you open your test dashboard and see it—50+ tickets waiting for review. Each ticket represents a development environment, each environment runs nightly tests across desktop web, mobile web on Chrome and Safari. That's 600 end-to-end tests × 3 platforms = 1,800 tests per environment, per night.

Some passed. Some failed. And for every failure, you need to figure out why.

#The Debug Detective Story

This was my reality as a QA automation engineer. Every morning felt like becoming a detective, sifting through mountains of evidence to solve the mystery of why test_user_login_flow_should_work.spec.ts decided to break at 3:47 AM.

The evidence? Log files. Thousands of lines of them.

[2026-02-27 03:47:12] INFO: Starting test execution
[2026-02-27 03:47:12] DEBUG: Initializing page object
[2026-02-27 03:47:12] DEBUG: Loading configuration
[2026-02-27 03:47:12] DEBUG: Setting up test data
[2026-02-27 03:47:13] INFO: Navigating to login page
[2026-02-27 03:47:13] DEBUG: Page loaded
[2026-02-27 03:47:13] DEBUG: Checking page elements
[2026-02-27 03:47:13] DEBUG: Element #username found
[2026-02-27 03:47:13] DEBUG: Element #password found
[2026-02-27 03:47:13] DEBUG: Element #submit found
[2026-02-27 03:47:13] INFO: Filling username field
[2026-02-27 03:47:13] DEBUG: Typing into username field
[2026-02-27 03:47:13] DEBUG: Username field value updated
[2026-02-27 03:47:14] INFO: Filling password field
... 1000 more lines of this ...
[2026-02-27 03:47:45] ERROR: Test failed - Button not found

Where exactly did it fail? Hidden somewhere in those thousands of lines of "helpful" debug information.

#The Problem Gets Worse

As our team grew and our test suite expanded, this became a company-wide issue:

For QA Engineers: Every test failure meant playing detective in an ocean of logs. Simple UI changes would cascade into dozens of failed tests, each with its own novel-length log file. With API calls, JSON responses, and debugging data, our test reports were ballooning to 10MB+ per run.

For Software Engineers: When your feature broke 15 tests across 3 environments, you'd spend more time reading logs than writing code. The signal-to-noise ratio was devastating, and waiting for Jenkins to load these massive HTML reports felt like watching paint dry.

But here's the paradox: We needed those logs. They were essential for debugging flaky tests, understanding timing issues, tracking down backend changes that broke the UI, and most importantly—we wanted to feed these JSON reports to AI models to automatically detect failure patterns. But when each report was 10MB of noise, even our AI analysis pipelines were choking.

The problem wasn't too much logging—it was too much logging all the time.

#The Eureka Moment

One particularly frustrating Thursday, after spending 2 hours debugging a test that failed because someone changed a CSS class name, I had an epiphany:

What if logs were smart enough to only show themselves when something actually went wrong?

We needed smart logging for three critical reasons:

  1. Avoid noisy logs that hide actual failures
  2. Make reports lighter so Jenkins could load them instantly
  3. Make JSON reports smaller so our AI failure detection could process them efficiently

Think about it: when you're driving, you don't need your car to announce every successful gear shift, every proper signal use, every correctly applied brake. You only need to hear from it when the check engine light comes on.

Our test logs should work the same way.

#Building the Solution

I spent the next few days building what would become Playwright Smart Logger. The concept was simple but powerful:

  1. Buffer everything during test execution
  2. Only flush logs when tests fail, timeout, or need retry
  3. Keep the full console API so adoption requires minimal changes
  4. Make it work everywhere—tests, page objects, helper functions

Here's how it transformed our testing experience:

#Before Smart Logger:

bash
PASS login_test_1.spec.ts [47 lines of logs]
PASS login_test_2.spec.ts [52 lines of logs]
PASS login_test_3.spec.ts [38 lines of logs]
FAIL login_test_4.spec.ts [64 lines of logs buried in noise]
PASS login_test_5.spec.ts [41 lines of logs]

#After Smart Logger:

bash
PASS login_test_1.spec.ts
PASS login_test_2.spec.ts
PASS login_test_3.spec.ts
FAIL login_test_4.spec.ts
=== Smart Logger Output ===
10:30:01.123 [INFO] Starting login flow test
10:30:01.200 [LOG] Navigating to login page
10:30:01.456 [LOG] Filling credentials
10:30:01.789 [ERROR] Element not found: #submit-button
10:30:01.790 [LOG] Page HTML: <button id="submit-btn">...
=== End Smart Logger Output ===
PASS login_test_5.spec.ts

The difference was night and day. 90% noise reduction on passing tests, but all the critical debugging information preserved for failures. Our 10MB report files shrunk to ~1MB, and Jenkins went from taking 30 seconds to load a test report to loading instantly.

#The Implementation Journey

The technical challenge was interesting. I needed to:

  1. Intercept all logging calls without breaking existing code
  2. Buffer logs intelligently with memory management
  3. Integrate seamlessly with Playwright's test fixtures
  4. Support complex scenarios like grouped logs, timing, and data tables

The solution uses a proxy pattern that makes Smart Logger feel like native console logging:

typescript
import { test, expect } from 'playwright-smart-logger'
test('user registration', async ({ page, smartLog }) => {
smartLog.group('Setup')
smartLog.info('Generating test user data')
const email = `test-${Date.now()}@example.com`
smartLog.groupEnd()
smartLog.group('Form Interaction')
await page.fill('#email', email)
smartLog.log('Email filled')
await page.click('#register')
smartLog.log('Form submitted')
smartLog.groupEnd()
// Only shows logs if this assertion fails
await expect(page.locator('.success')).toBeVisible()
})

#The Global Access Breakthrough

The most requested feature came from our page object models. Engineers wanted to use Smart Logger in helper functions and page classes without passing fixtures around:

typescript
// pages/login.page.ts
import { smartLog } from 'playwright-smart-logger'
export class LoginPage {
async login(username: string, password: string) {
smartLog.info('Logging in as', username)
await this.page.fill('#username', username)
await this.page.fill('#password', password)
await this.page.click('#submit')
smartLog.info('Login successful')
}
}

This made adoption seamless—no architectural changes needed, just better logging.

#Real-World Impact

After rolling out Smart Logger across our test suites, the results were dramatic:

For QA Engineers:

  • 90% reduction in log noise
  • Debug time cut from hours to minutes
  • Failed test analysis became surgical, not archaeological
  • HTML reports now load instantly instead of timing out

For Software Engineers:

  • Clear, actionable failure information
  • No more scrolling through successful test spam
  • Faster feedback loops on feature development
  • Jenkins reports that actually open without crashing

For DevOps & Infrastructure:

  • Report file sizes dropped from 10MB to ~1MB
  • JSON reports became AI-friendly for automated failure analysis
  • Build artifacts storage costs reduced significantly
  • CI/CD pipelines run faster with lighter report processing

#The Open Source Decision

After seeing the impact on our team, I realized this problem wasn't unique to us. Every team running comprehensive E2E tests faces the same logging paradox: you need the information, but you don't need it all the time.

So I open-sourced Playwright Smart Logger with:

  • Zero configuration setup (works out of the box)
  • Full console API compatibility (easy migration)
  • TypeScript-first design (because types matter)
  • Memory-safe buffering (won't crash your tests)
  • Flexible configuration (CI vs local development)

#Configuration for Different Scenarios

The beauty of Smart Logger is its adaptability:

typescript
// For CI environments - only show failures
smartLog: {
flushOn: ['fail']
}
// For local development - show failures and retries
smartLog: {
flushOn: ['fail', 'retry']
}
// For debugging everything
smartLog: {
flushOn: ['fail', 'pass', 'skip', 'retry']
}
// Always show logs locally, smart in CI
smartLog: {
alwaysFlush: process.env.CI !== 'true'
}

#The Community Response

Since going open source, the project has attracted contributors who've added features I never thought of:

  • Browser console capture for catching frontend errors
  • Test report attachments for compliance workflows
  • Custom fixture extensions for domain-specific logging
  • Advanced grouping and timing for performance analysis

The community keeps pushing the boundaries of what smart logging can do.

#Looking Forward

Today, Smart Logger processes thousands of test runs across dozens of teams. What started as a weekend project to solve my own frustration has become a tool that makes testing more humane.

The lesson? Sometimes the best solutions come from developers who are tired of their own problems. If your test logs are drowning you in noise, if your debugging sessions feel like archaeological expeditions, if your team spends more time reading logs than writing code—you're not alone.

And maybe, just maybe, there's a smarter way to handle your logs too.

#Get Started

Want to try Playwright Smart Logger? It takes less than 5 minutes:

bash
npm install playwright-smart-logger
typescript
// Replace this:
import { test, expect } from '@playwright/test'
// With this:
import { test, expect } from 'playwright-smart-logger'
test('my test', async ({ page, smartLog }) => {
smartLog.info('Starting test...')
// Your test code here
})

That's it. Your passing tests will be silent, your failing tests will be verbose, and your debugging sessions will be focused.

Because sometimes the smartest thing you can do is know when to be quiet.


Have a logging horror story of your own? Found Smart Logger useful? Share your experience or contribute to the project. Let's make testing more humane, one smart log at a time.