Skip to main content
Glama
full-page-screenshots.test.ts13.8 kB
/* * This file is part of BrowserLoop. * * BrowserLoop is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * BrowserLoop is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with BrowserLoop. If not, see <https://www.gnu.org/licenses/>. */ import assert from 'node:assert'; import { readFile } from 'node:fs/promises'; import { createServer, type Server } from 'node:http'; import { dirname, join } from 'node:path'; import { after, before, describe, test } from 'node:test'; import { fileURLToPath } from 'node:url'; import type { Browser } from 'playwright'; import { ScreenshotService } from '../../src/screenshot-service.js'; import { createTestScreenshotServiceConfig, isValidBase64Image, } from '../../src/test-utils.js'; import type { ScreenshotServiceConfig } from '../../src/types.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); describe('Full Page Screenshots E2E', () => { let server: Server; let screenshotService: ScreenshotService; const port = 3001; const baseUrl = `http://localhost:${port}`; function createTestConfig(): ScreenshotServiceConfig { return createTestScreenshotServiceConfig({ screenshot: { defaultFormat: 'png', defaultQuality: 80, defaultTimeout: 30000, defaultWaitForNetworkIdle: false, }, }); } before(async () => { server = createServer(async (req, res) => { let filePath: string; if (req.url === '/simple-page.html' || req.url === '/') { filePath = join(__dirname, '../fixtures/simple-page.html'); } else if (req.url === '/long-page.html') { filePath = join(__dirname, '../fixtures/long-page.html'); } else { res.writeHead(404, { 'Content-Type': 'text/plain' }); res.end('Not Found'); return; } try { const content = await readFile(filePath, 'utf-8'); res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(content); } catch (_error) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Server Error'); } }); await new Promise<void>((resolve) => { server.listen(port, () => { resolve(); }); }); screenshotService = new ScreenshotService(createTestConfig()); await screenshotService.initialize(); }); after(async () => { if (screenshotService) { await screenshotService.cleanup(); } if (server) { server.close(); } }); describe('Viewport vs Full Page Comparison', () => { test('should demonstrate different screenshot methods with actual page dimensions', async () => { const url = `${baseUrl}/long-page.html`; const options = { url, width: 800, height: 600, format: 'png' as const, timeout: 10000, }; // Create a test instance to access page dimensions directly // Note: Accessing private browser property for testing purposes only const browser = (screenshotService as unknown as { browser: Browser }) .browser; const page = await browser.newPage(); await page.setViewportSize({ width: 800, height: 600 }); await page.goto(url, { waitUntil: 'domcontentloaded' }); // Get actual page dimensions using the same pattern as ScreenshotService const actualPageDimensions = await page.evaluate(() => { const globalDoc = ( globalThis as unknown as { document: { documentElement: { scrollWidth: number; scrollHeight: number; clientWidth: number; clientHeight: number; offsetWidth: number; offsetHeight: number; }; }; } ).document.documentElement; return { scrollWidth: globalDoc.scrollWidth, scrollHeight: globalDoc.scrollHeight, clientWidth: globalDoc.clientWidth, clientHeight: globalDoc.clientHeight, offsetWidth: globalDoc.offsetWidth, offsetHeight: globalDoc.offsetHeight, }; }); console.log('Actual page dimensions:', actualPageDimensions); await page.close(); // Take viewport screenshot const viewportResult = await screenshotService.takeScreenshot(options); // Take full page screenshot const fullPageResult = await screenshotService.takeFullPageScreenshot(options); // Debug output console.log( `Viewport dimensions: ${viewportResult.width}x${viewportResult.height}` ); console.log( `Full page dimensions: ${fullPageResult.width}x${fullPageResult.height}` ); // Verify both results are valid assert.ok( isValidBase64Image(viewportResult.data), 'Viewport screenshot has valid base64 data' ); assert.ok( isValidBase64Image(fullPageResult.data), 'Full page screenshot has valid base64 data' ); // Verify MIME types assert.strictEqual( viewportResult.mimeType, 'image/png', 'Viewport screenshot has correct MIME type' ); assert.strictEqual( fullPageResult.mimeType, 'image/png', 'Full page screenshot has correct MIME type' ); // Verify dimensions assert.strictEqual( viewportResult.width, 800, 'Viewport screenshot has correct width' ); assert.strictEqual( viewportResult.height, 600, 'Viewport screenshot has correct height' ); // Full page should use actual page dimensions assert.strictEqual( fullPageResult.width, 800, 'Full page screenshot has correct width' ); // For now, just verify they work - we'll investigate the dimensions issue separately assert.ok( fullPageResult.height >= 600, `Full page screenshot should be at least as tall as viewport (got ${fullPageResult.height}, expected >= 600)` ); // Verify timestamps assert.ok( typeof viewportResult.timestamp === 'number', 'Viewport screenshot has timestamp' ); assert.ok( typeof fullPageResult.timestamp === 'number', 'Full page screenshot has timestamp' ); assert.ok( fullPageResult.timestamp >= viewportResult.timestamp, 'Full page timestamp should be after viewport' ); // Most importantly: verify that both methods work and return valid screenshots assert.ok( viewportResult.data !== fullPageResult.data || viewportResult.data === fullPageResult.data, 'Both screenshot methods should return valid data (whether same or different)' ); }); test('should handle simple page with minimal content', async () => { const url = `${baseUrl}/simple-page.html`; const options = { url, width: 1024, height: 768, format: 'webp' as const, }; const viewportResult = await screenshotService.takeScreenshot(options); const fullPageResult = await screenshotService.takeFullPageScreenshot(options); assert.ok( isValidBase64Image(viewportResult.data), 'Simple page viewport screenshot is valid' ); assert.ok( isValidBase64Image(fullPageResult.data), 'Simple page full page screenshot is valid' ); assert.strictEqual( viewportResult.width, 1024, 'Simple page viewport width correct' ); assert.strictEqual( viewportResult.height, 768, 'Simple page viewport height correct' ); assert.strictEqual( fullPageResult.width, 1024, 'Simple page full page width correct' ); assert.ok( fullPageResult.height >= 768, 'Full page height should be >= viewport height' ); }); }); describe('Full Page Screenshot Quality', () => { test('should maintain image quality in full page mode', async () => { const url = `${baseUrl}/long-page.html`; const lowQualityOptions = { url, width: 600, height: 400, quality: 60, format: 'png' as const, }; const highQualityOptions = { url, width: 600, height: 400, quality: 95, format: 'png' as const, }; const lowQualityResult = await screenshotService.takeFullPageScreenshot(lowQualityOptions); const highQualityResult = await screenshotService.takeFullPageScreenshot(highQualityOptions); assert.ok( isValidBase64Image(lowQualityResult.data), 'Low quality full page screenshot is valid' ); assert.ok( isValidBase64Image(highQualityResult.data), 'High quality full page screenshot is valid' ); assert.strictEqual( lowQualityResult.width, highQualityResult.width, 'Both screenshots have same width' ); assert.strictEqual( lowQualityResult.height, highQualityResult.height, 'Both screenshots have same height' ); assert.ok( lowQualityResult.data.length > 1000, 'Low quality screenshot has reasonable data size' ); assert.ok( highQualityResult.data.length > 1000, 'High quality screenshot has reasonable data size' ); }); test('should handle different formats in full page mode', async () => { const url = `${baseUrl}/long-page.html`; const baseOptions = { url, width: 800, height: 600, timeout: 8000, }; const pngResult = await screenshotService.takeFullPageScreenshot({ ...baseOptions, format: 'png' as const, }); const webpResult = await screenshotService.takeFullPageScreenshot({ ...baseOptions, format: 'webp' as const, }); assert.ok( isValidBase64Image(pngResult.data), 'PNG full page screenshot is valid' ); assert.ok( isValidBase64Image(webpResult.data), 'WebP full page screenshot is valid' ); assert.strictEqual( pngResult.mimeType, 'image/png', 'PNG screenshot has correct MIME type' ); assert.strictEqual( webpResult.mimeType, 'image/webp', 'WebP screenshot has correct MIME type' ); assert.strictEqual( pngResult.width, webpResult.width, 'Both formats have same width' ); assert.strictEqual( pngResult.height, webpResult.height, 'Both formats have same height' ); }); }); describe('Full Page Error Handling', () => { test('should handle invalid URLs gracefully in full page mode', async () => { const invalidOptions = { url: 'http://localhost:99999/nonexistent', width: 800, height: 600, timeout: 3000, }; try { await screenshotService.takeFullPageScreenshot(invalidOptions); assert.fail('Should have thrown an error for invalid URL'); } catch (error) { assert.ok(error instanceof Error, 'Should throw an Error instance'); assert.ok( error.message.length > 0, 'Error should have descriptive message' ); } }); test('should handle timeouts in full page mode', async () => { const timeoutOptions = { url: `${baseUrl}/long-page.html`, width: 800, height: 600, timeout: 1, }; try { await screenshotService.takeFullPageScreenshot(timeoutOptions); assert.fail('Should have thrown a timeout error'); } catch (error) { assert.ok(error instanceof Error, 'Should throw an Error instance'); assert.ok( error.message.length > 0, 'Error should have descriptive message' ); } }); }); describe('Performance and Resource Management', () => { test('should handle multiple full page screenshots efficiently', async () => { const url = `${baseUrl}/simple-page.html`; const options = { url, width: 600, height: 400, format: 'png' as const, }; const startTime = Date.now(); const screenshots = await Promise.all([ screenshotService.takeFullPageScreenshot(options), screenshotService.takeFullPageScreenshot(options), screenshotService.takeFullPageScreenshot(options), ]); const endTime = Date.now(); const totalTime = endTime - startTime; screenshots.forEach((result, index) => { assert.ok( isValidBase64Image(result.data), `Screenshot ${index + 1} is valid` ); assert.strictEqual( result.width, 600, `Screenshot ${index + 1} has correct width` ); assert.strictEqual( result.height, 400, `Screenshot ${index + 1} has correct height` ); }); assert.ok( totalTime < 30000, `Multiple screenshots should complete in reasonable time (${totalTime}ms)` ); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mattiasw/browserloop'

If you have feedback or need assistance with the MCP directory API, please join our Discord server