Can I use Headless Chromium to test web applications across different screen sizes?
Yes, Headless Chromium is an excellent tool for testing web applications across different screen sizes and viewport dimensions. It provides comprehensive capabilities for responsive web design testing, allowing developers to simulate various devices, screen resolutions, and orientations programmatically. This makes it invaluable for ensuring your web applications work consistently across desktop, tablet, and mobile devices.
Why Use Headless Chromium for Responsive Testing?
Headless Chromium offers several advantages for multi-screen testing:
- Precise viewport control: Set exact pixel dimensions for any screen size
- Device emulation: Simulate real devices with their specific characteristics
- Screenshot comparison: Capture visual differences across screen sizes
- Performance testing: Measure load times and rendering performance on different viewports
- Automated testing: Integrate responsive tests into CI/CD pipelines
- Cost-effective: No need for physical devices or cloud device farms
Setting Up Viewport Testing with Puppeteer
Puppeteer is the most popular Node.js library for controlling Headless Chromium. Here's how to set up basic viewport testing:
const puppeteer = require('puppeteer');
async function testMultipleViewports() {
const browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
// Define common viewport sizes
const viewports = [
{ width: 1920, height: 1080, name: 'Desktop Large' },
{ width: 1366, height: 768, name: 'Desktop Standard' },
{ width: 1024, height: 768, name: 'Tablet Landscape' },
{ width: 768, height: 1024, name: 'Tablet Portrait' },
{ width: 414, height: 896, name: 'iPhone XR' },
{ width: 375, height: 667, name: 'iPhone SE' },
{ width: 360, height: 640, name: 'Android Small' }
];
const page = await browser.newPage();
for (const viewport of viewports) {
console.log(`Testing ${viewport.name} (${viewport.width}x${viewport.height})`);
// Set viewport size
await page.setViewport({
width: viewport.width,
height: viewport.height,
deviceScaleFactor: 1
});
await page.goto('https://your-website.com', {
waitUntil: 'networkidle2'
});
// Take screenshot
await page.screenshot({
path: `screenshots/${viewport.name.replace(' ', '_').toLowerCase()}.png`,
fullPage: true
});
// Test specific elements visibility
const navigationVisible = await page.evaluate(() => {
const nav = document.querySelector('.main-navigation');
return nav && window.getComputedStyle(nav).display !== 'none';
});
console.log(`Navigation visible: ${navigationVisible}`);
}
await browser.close();
}
testMultipleViewports();
Advanced Device Emulation
For more realistic testing, you can emulate specific devices with their exact characteristics:
const puppeteer = require('puppeteer');
async function testWithDeviceEmulation() {
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
// Emulate iPhone 12 Pro
await page.emulate({
viewport: {
width: 390,
height: 844,
deviceScaleFactor: 3,
isMobile: true,
hasTouch: true,
isLandscape: false
},
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1'
});
await page.goto('https://your-website.com');
// Test mobile-specific interactions
await page.tap('#mobile-menu-button');
await page.waitForSelector('.mobile-menu', { visible: true });
await page.screenshot({ path: 'mobile-menu-test.png' });
await browser.close();
}
Python Implementation with Selenium
For Python developers, Selenium provides similar capabilities:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import time
def test_responsive_design():
# Configure Chrome options
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
driver = webdriver.Chrome(options=chrome_options)
# Define viewport sizes to test
screen_sizes = [
(1920, 1080, 'Desktop Large'),
(1366, 768, 'Desktop Standard'),
(768, 1024, 'Tablet Portrait'),
(375, 667, 'Mobile iPhone'),
(360, 640, 'Mobile Android')
]
try:
for width, height, device_name in screen_sizes:
print(f"Testing {device_name}: {width}x{height}")
# Set window size
driver.set_window_size(width, height)
driver.get('https://your-website.com')
# Wait for page to load
time.sleep(2)
# Take screenshot
driver.save_screenshot(f'screenshots/{device_name.lower().replace(" ", "_")}.png')
# Test responsive elements
try:
hamburger_menu = driver.find_element_by_css_selector('.hamburger-menu')
is_mobile_menu_visible = hamburger_menu.is_displayed()
print(f"Mobile menu visible: {is_mobile_menu_visible}")
except:
print("Mobile menu not found")
# Test element positioning
hero_section = driver.find_element_by_css_selector('.hero-section')
hero_rect = driver.execute_script("""
const element = arguments[0];
const rect = element.getBoundingClientRect();
return {
width: rect.width,
height: rect.height,
x: rect.x,
y: rect.y
};
""", hero_section)
print(f"Hero section dimensions: {hero_rect}")
finally:
driver.quit()
test_responsive_design()
Comprehensive Testing Strategy
Here's a more comprehensive approach that includes visual regression testing and performance monitoring:
const puppeteer = require('puppeteer');
const pixelmatch = require('pixelmatch');
const PNG = require('pngjs').PNG;
const fs = require('fs');
class ResponsiveWebTester {
constructor() {
this.browser = null;
this.baselineDir = './baseline_screenshots/';
this.currentDir = './current_screenshots/';
this.diffDir = './diff_screenshots/';
}
async initialize() {
this.browser = await puppeteer.launch({
headless: true,
args: ['--no-sandbox', '--disable-setuid-sandbox']
});
// Ensure directories exist
[this.baselineDir, this.currentDir, this.diffDir].forEach(dir => {
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
});
}
async testResponsiveDesign(url, testCases) {
const page = await this.browser.newPage();
for (const testCase of testCases) {
console.log(`Testing ${testCase.name}...`);
// Configure viewport and device settings
await page.setViewport({
width: testCase.viewport.width,
height: testCase.viewport.height,
deviceScaleFactor: testCase.deviceScaleFactor || 1,
isMobile: testCase.isMobile || false,
hasTouch: testCase.hasTouch || false
});
if (testCase.userAgent) {
await page.setUserAgent(testCase.userAgent);
}
// Navigate and wait for load
await page.goto(url, { waitUntil: 'networkidle2' });
// Run custom test functions
if (testCase.customTests) {
for (const test of testCase.customTests) {
await test(page);
}
}
// Performance metrics
const metrics = await page.metrics();
console.log(`${testCase.name} - Layout Duration: ${metrics.LayoutDuration}ms`);
// Take screenshot
const screenshotPath = `${this.currentDir}${testCase.name.replace(/\s+/g, '_').toLowerCase()}.png`;
await page.screenshot({
path: screenshotPath,
fullPage: testCase.fullPage || false
});
// Compare with baseline if it exists
await this.compareWithBaseline(testCase.name);
}
await page.close();
}
async compareWithBaseline(testName) {
const fileName = `${testName.replace(/\s+/g, '_').toLowerCase()}.png`;
const baselinePath = `${this.baselineDir}${fileName}`;
const currentPath = `${this.currentDir}${fileName}`;
const diffPath = `${this.diffDir}${fileName}`;
if (!fs.existsSync(baselinePath)) {
console.log(`No baseline found for ${testName}. Current screenshot will be used as baseline.`);
fs.copyFileSync(currentPath, baselinePath);
return;
}
const baseline = PNG.sync.read(fs.readFileSync(baselinePath));
const current = PNG.sync.read(fs.readFileSync(currentPath));
const { width, height } = baseline;
const diff = new PNG({ width, height });
const pixelDiff = pixelmatch(baseline.data, current.data, diff.data, width, height, {
threshold: 0.1
});
if (pixelDiff > 0) {
fs.writeFileSync(diffPath, PNG.sync.write(diff));
console.log(`Visual differences detected in ${testName}: ${pixelDiff} pixels`);
} else {
console.log(`${testName}: No visual differences detected`);
}
}
async close() {
if (this.browser) {
await this.browser.close();
}
}
}
// Usage example
async function runResponsiveTests() {
const tester = new ResponsiveWebTester();
await tester.initialize();
const testCases = [
{
name: 'Desktop Large',
viewport: { width: 1920, height: 1080 },
customTests: [
async (page) => {
// Test desktop-specific features
const desktopNav = await page.$('.desktop-navigation');
console.log('Desktop navigation present:', !!desktopNav);
}
]
},
{
name: 'Mobile Portrait',
viewport: { width: 375, height: 667 },
deviceScaleFactor: 2,
isMobile: true,
hasTouch: true,
userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15',
customTests: [
async (page) => {
// Test mobile hamburger menu
await page.tap('.hamburger-menu');
const mobileMenu = await page.waitForSelector('.mobile-menu', { visible: true });
console.log('Mobile menu opens correctly:', !!mobileMenu);
}
]
}
];
await tester.testResponsiveDesign('https://your-website.com', testCases);
await tester.close();
}
runResponsiveTests();
Integration with Testing Frameworks
You can integrate responsive testing into popular testing frameworks like Jest:
const puppeteer = require('puppeteer');
describe('Responsive Web Design Tests', () => {
let browser, page;
beforeAll(async () => {
browser = await puppeteer.launch({ headless: true });
page = await browser.newPage();
});
afterAll(async () => {
await browser.close();
});
test.each([
[1920, 1080, 'Desktop'],
[768, 1024, 'Tablet'],
[375, 667, 'Mobile']
])('should display correctly on %s x %s (%s)', async (width, height, device) => {
await page.setViewport({ width, height });
await page.goto('https://your-website.com');
// Test layout doesn't break
const isLayoutBroken = await page.evaluate(() => {
const elements = document.querySelectorAll('.container, .row, .col');
return Array.from(elements).some(el => {
const rect = el.getBoundingClientRect();
return rect.width > window.innerWidth || rect.height <= 0;
});
});
expect(isLayoutBroken).toBe(false);
});
});
Best Practices for Screen Size Testing
Test Critical Breakpoints: Focus on major CSS breakpoints (320px, 768px, 1024px, 1200px+)
Include Real Device Dimensions: Test actual device resolutions, not just generic sizes
Verify Interactive Elements: Ensure buttons, forms, and navigation work across all sizes
Check Text Readability: Verify font sizes and line heights are appropriate
Test Performance: Monitor load times and rendering performance on smaller screens
Automate Regular Testing: Run responsive tests as part of your CI/CD pipeline
When implementing viewport testing with Headless Chromium, consider leveraging how to set viewport in Puppeteer for more detailed viewport configuration options. For comprehensive testing workflows, you might also want to explore how to handle timeouts in Puppeteer to ensure your tests remain stable across different network conditions.
Command Line Testing
For quick manual testing, you can use Chrome's command line flags:
# Test specific viewport size
google-chrome --headless --disable-gpu --window-size=375,667 --screenshot=mobile.png https://your-website.com
# Test multiple sizes with a script
for size in "1920,1080" "768,1024" "375,667"; do
google-chrome --headless --disable-gpu --window-size=$size --screenshot=test_$size.png https://your-website.com
done
Testing with WebScraping.AI API
For developers who prefer API-based solutions, WebScraping.AI provides headless browser capabilities that can be configured for different screen sizes:
# Test mobile viewport
curl "https://api.webscraping.ai/html?url=https://your-website.com&device=mobile&js=true"
# Test desktop viewport
curl "https://api.webscraping.ai/html?url=https://your-website.com&device=desktop&js=true"
The API handles the complexity of browser management while providing the flexibility to test different viewport configurations programmatically.
Headless Chromium provides a robust, automated solution for testing web applications across different screen sizes. By implementing comprehensive viewport testing, you can ensure your applications deliver consistent user experiences across all devices and screen resolutions, catching responsive design issues before they reach production.