What are the debugging options available in Puppeteer-Sharp?
Puppeteer-Sharp provides extensive debugging capabilities that help developers troubleshoot automation scripts, identify issues, and monitor browser behavior. Understanding these debugging options is crucial for building robust web scraping and automation solutions.
Console Output and Logging
Enabling DevTools Mode
The most fundamental debugging approach in Puppeteer-Sharp is running the browser in non-headless mode with DevTools enabled:
using PuppeteerSharp;
var launchOptions = new LaunchOptions
{
Headless = false,
DevTools = true,
SlowMo = 250 // Slow down operations by 250ms
};
await using var browser = await Puppeteer.LaunchAsync(launchOptions);
var page = await browser.NewPageAsync();
await page.GoToAsync("https://example.com");
This configuration opens a visible browser window with DevTools automatically opened, allowing you to inspect elements, monitor network requests, and debug JavaScript execution in real-time.
Console Message Monitoring
Puppeteer-Sharp can capture and log all console messages from the target page:
page.Console += (sender, e) =>
{
Console.WriteLine($"[{e.Message.Type}] {e.Message.Text}");
// Log arguments if any
foreach (var arg in e.Message.Args)
{
Console.WriteLine($" Arg: {arg}");
}
};
// Navigate and interact with the page
await page.GoToAsync("https://example.com");
Exception and Error Handling
Implement comprehensive error handling to catch and debug various types of exceptions:
page.PageError += (sender, e) =>
{
Console.WriteLine($"Page Error: {e.Error.Message}");
Console.WriteLine($"Stack Trace: {e.Error.StackTrace}");
};
page.RequestFailed += (sender, e) =>
{
Console.WriteLine($"Request Failed: {e.Request.Url}");
Console.WriteLine($"Failure Text: {e.Request.FailureText}");
};
try
{
await page.GoToAsync("https://example.com");
await page.ClickAsync("#submit-button");
}
catch (PuppeteerException ex)
{
Console.WriteLine($"Puppeteer Exception: {ex.Message}");
Console.WriteLine($"Stack Trace: {ex.StackTrace}");
}
Visual Debugging Options
Screenshot Debugging
Take screenshots at different stages of your automation to visually debug the process:
// Take full page screenshot
await page.ScreenshotAsync("/path/to/debug-screenshot.png", new ScreenshotOptions
{
FullPage = true,
Type = ScreenshotType.Png
});
// Take screenshot of specific element
var element = await page.QuerySelectorAsync("#specific-element");
await element.ScreenshotAsync("/path/to/element-screenshot.png");
// Conditional screenshot based on conditions
if (await page.QuerySelectorAsync(".error-message") != null)
{
await page.ScreenshotAsync("/path/to/error-state.png");
}
Video Recording
For complex debugging scenarios, enable video recording to capture the entire automation session:
var launchOptions = new LaunchOptions
{
Headless = false,
Args = new[] { "--enable-logging", "--v=1" }
};
var browser = await Puppeteer.LaunchAsync(launchOptions);
var page = await browser.NewPageAsync();
// Start recording (requires additional setup with screen recording tools)
await page.GoToAsync("https://example.com");
// Perform your automation steps
await page.ClickAsync("#button");
await page.TypeAsync("#input", "test data");
Network Request Debugging
Request and Response Monitoring
Monitor all network requests to debug API calls, resource loading, and response handling:
page.Request += async (sender, e) =>
{
Console.WriteLine($"Request: {e.Request.Method} {e.Request.Url}");
Console.WriteLine($"Headers: {string.Join(", ", e.Request.Headers)}");
if (e.Request.PostData != null)
{
Console.WriteLine($"Post Data: {e.Request.PostData}");
}
};
page.Response += async (sender, e) =>
{
Console.WriteLine($"Response: {e.Response.Status} {e.Response.Url}");
Console.WriteLine($"Headers: {string.Join(", ", e.Response.Headers)}");
// Log response body for specific requests
if (e.Response.Url.Contains("api/data"))
{
var responseText = await e.Response.TextAsync();
Console.WriteLine($"Response Body: {responseText}");
}
};
await page.GoToAsync("https://example.com");
Request Interception for Debugging
Intercept and modify requests to test different scenarios:
await page.SetRequestInterceptionAsync(true);
page.Request += async (sender, e) =>
{
var request = e.Request;
// Log and optionally modify requests
Console.WriteLine($"Intercepting: {request.Url}");
if (request.Url.Contains("debug-endpoint"))
{
// Continue with modified headers
await request.ContinueAsync(new Payload
{
Headers = new Dictionary<string, object>
{
["X-Debug"] = "true",
["Authorization"] = "Bearer debug-token"
}
});
}
else
{
await request.ContinueAsync();
}
};
Advanced Debugging Techniques
Performance Monitoring
Monitor page performance metrics to identify bottlenecks:
await page.GoToAsync("https://example.com");
// Get performance metrics
var metrics = await page.MetricsAsync();
foreach (var metric in metrics)
{
Console.WriteLine($"{metric.Key}: {metric.Value}");
}
// Monitor specific performance events
await page.EvaluateExpressionAsync(@"
performance.mark('automation-start');
// Your automation code here
performance.mark('automation-end');
performance.measure('automation-duration', 'automation-start', 'automation-end');
");
var performanceEntries = await page.EvaluateExpressionAsync<object[]>(@"
JSON.stringify(performance.getEntriesByType('measure'))
");
Memory and Resource Monitoring
Track memory usage and resource consumption during automation:
// Enable runtime domain for memory monitoring
var client = await page.Target.CreateCDPSessionAsync();
await client.SendAsync("Runtime.enable");
await client.SendAsync("HeapProfiler.enable");
// Take heap snapshot for memory analysis
await client.SendAsync("HeapProfiler.takeHeapSnapshot");
// Monitor JavaScript execution context
page.Console += (sender, e) =>
{
if (e.Message.Type == ConsoleType.Error)
{
Console.WriteLine($"JS Error: {e.Message.Text}");
}
};
Debugging Element Interactions
Debug element selection and interaction issues with detailed logging:
public async Task<ElementHandle> DebugQuerySelector(IPage page, string selector)
{
Console.WriteLine($"Attempting to find element: {selector}");
var element = await page.QuerySelectorAsync(selector);
if (element == null)
{
Console.WriteLine($"Element not found: {selector}");
// Take screenshot for debugging
await page.ScreenshotAsync($"debug-element-not-found-{DateTime.Now:yyyyMMdd-HHmmss}.png");
// List all similar elements
var allElements = await page.QuerySelectorAllAsync(selector.Split(' ')[0]);
Console.WriteLine($"Found {allElements.Length} elements with similar selector");
return null;
}
// Verify element is visible and clickable
var boundingBox = await element.BoundingBoxAsync();
var isVisible = await element.IsIntersectingViewportAsync();
Console.WriteLine($"Element found - Visible: {isVisible}, BoundingBox: {boundingBox}");
return element;
}
// Usage
var button = await DebugQuerySelector(page, "#submit-button");
if (button != null)
{
await button.ClickAsync();
}
Browser and Context Debugging
Browser Process Monitoring
Monitor the underlying Chromium process for debugging browser-level issues:
var launchOptions = new LaunchOptions
{
Headless = false,
Args = new[]
{
"--enable-logging",
"--log-level=0",
"--remote-debugging-port=9222"
}
};
var browser = await Puppeteer.LaunchAsync(launchOptions);
// Access browser process information
var process = browser.Process;
if (process != null)
{
Console.WriteLine($"Browser Process ID: {process.Id}");
Console.WriteLine($"Browser Executable: {browser.Process?.StartInfo?.FileName}");
}
// Monitor browser disconnection
browser.Disconnected += (sender, e) =>
{
Console.WriteLine("Browser disconnected");
};
browser.TargetCreated += (sender, e) =>
{
Console.WriteLine($"New target created: {e.Target.Url}");
};
Context Isolation Debugging
When working with browser contexts, implement debugging for context-specific issues:
var context = await browser.CreateIncognitoBrowserContextAsync();
context.TargetCreated += (sender, e) =>
{
Console.WriteLine($"Target created in context: {e.Target.Url}");
};
context.Page += (sender, e) =>
{
Console.WriteLine($"New page in context: {e.Page.Url}");
// Add debugging to new pages in this context
e.Page.Console += (pageSender, pageEvent) =>
{
Console.WriteLine($"[Context Page] {pageEvent.Message.Text}");
};
};
var page = await context.NewPageAsync();
await page.GoToAsync("https://example.com");
Integration with Development Tools
Debugging with Wait Strategies
Implement robust wait strategies with debugging information, similar to handling timeouts in Puppeteer:
public async Task<bool> WaitForElementWithDebug(IPage page, string selector, int timeoutMs = 30000)
{
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
try
{
Console.WriteLine($"Waiting for element: {selector}");
await page.WaitForSelectorAsync(selector, new WaitForSelectorOptions
{
Timeout = timeoutMs
});
Console.WriteLine($"Element found after {stopwatch.ElapsedMilliseconds}ms");
return true;
}
catch (WaitTaskTimeoutException)
{
Console.WriteLine($"Timeout waiting for element: {selector} after {stopwatch.ElapsedMilliseconds}ms");
// Take screenshot for debugging
await page.ScreenshotAsync($"timeout-debug-{DateTime.Now:yyyyMMdd-HHmmss}.png");
return false;
}
}
Debugging Network Issues
For debugging network-related problems, implement comprehensive network request monitoring:
var networkDebugger = new NetworkDebugger();
await networkDebugger.EnableFor(page);
public class NetworkDebugger
{
private readonly List<string> _failedRequests = new();
private readonly Dictionary<string, DateTime> _requestTiming = new();
public async Task EnableFor(IPage page)
{
page.Request += LogRequest;
page.Response += LogResponse;
page.RequestFailed += LogFailedRequest;
}
private async void LogRequest(object sender, RequestEventArgs e)
{
_requestTiming[e.Request.Url] = DateTime.Now;
Console.WriteLine($"[REQUEST] {e.Request.Method} {e.Request.Url}");
}
private async void LogResponse(object sender, ResponseCreatedEventArgs e)
{
var duration = DateTime.Now - _requestTiming.GetValueOrDefault(e.Response.Url, DateTime.Now);
Console.WriteLine($"[RESPONSE] {e.Response.Status} {e.Response.Url} ({duration.TotalMilliseconds}ms)");
}
private async void LogFailedRequest(object sender, RequestEventArgs e)
{
_failedRequests.Add(e.Request.Url);
Console.WriteLine($"[FAILED] {e.Request.Url} - {e.Request.FailureText}");
}
public void PrintSummary()
{
Console.WriteLine($"Total failed requests: {_failedRequests.Count}");
foreach (var failed in _failedRequests)
{
Console.WriteLine($" - {failed}");
}
}
}
Best Practices for Debugging
When debugging Puppeteer-Sharp applications, implement these best practices:
- Always use try-catch blocks around critical operations
- Enable comprehensive logging for network requests, console messages, and page errors
- Take screenshots at key points in your automation flow
- Use meaningful selector strategies and validate element presence before interaction
- Monitor performance metrics to identify bottlenecks
- Implement timeout handling with proper error messages
For more complex scenarios involving browser session management or handling authentication, combine these debugging techniques with your specific use case requirements.
By leveraging these comprehensive debugging options in Puppeteer-Sharp, you can efficiently identify and resolve issues in your web automation and scraping projects, ensuring robust and reliable applications.