Command line interface for Percy visual testing platform that enables developers to capture, upload, and manage visual snapshots for web applications.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Direct upload of image files as visual snapshots, useful for integrating with external screenshot tools or uploading pre-captured images to Percy for visual comparison.
Upload a directory of images directly to Percy as snapshots. Perfect for integrating with external screenshot tools, mobile app screenshots, or any workflow that generates image files.
/**
* Upload directory of images as snapshots
* Takes pre-captured images and uploads them to Percy for visual testing
*
* Usage: percy upload <dirname>
*
* Arguments:
* dirname Directory containing images to upload (required)
*
* Options:
* --files, -f <pattern> Glob patterns matching image files (multiple)
* Default: **/*.{png,jpg,jpeg}
* --ignore, -i <pattern> Glob patterns matching files to ignore (multiple)
* --strip-extensions, -e Strip file extensions from snapshot names
*
* Supported formats: .png, .jpg, .jpeg
* Supported projects: web and generic token types
*/
percy upload <dirname>Upload all images in a directory using default patterns:
# Upload all PNG, JPG, JPEG files
percy upload ./screenshots
# Directory structure:
# screenshots/
# ├── homepage.png → snapshot: "homepage.png"
# ├── about-page.jpg → snapshot: "about-page.jpg"
# ├── contact-form.jpeg → snapshot: "contact-form.jpeg"
# └── dashboard.png → snapshot: "dashboard.png"Control which files are uploaded using glob patterns:
# Upload only PNG files
percy upload ./screenshots --files "**/*.png"
# Upload multiple specific patterns
percy upload ./screenshots \
--files "**/*.png" \
--files "**/mobile-*.jpg" \
--files "**/desktop-*.jpg"
# Upload from subdirectories
percy upload ./screenshots --files "**/final/*.{png,jpg}"Exclude specific files or directories:
# Ignore temporary and backup files
percy upload ./screenshots \
--ignore "**/*-temp.*" \
--ignore "**/*.bak" \
--ignore "**/drafts/**"
# Ignore test and debug images
percy upload ./screenshots \
--ignore "**/test-*" \
--ignore "**/debug/**" \
--ignore "**/*.tmp"Remove file extensions from snapshot names for cleaner URLs:
# Without strip-extensions
percy upload ./screenshots
# Creates: "homepage.png", "about-page.jpg", "contact.jpeg"
# With strip-extensions
percy upload ./screenshots --strip-extensions
# Creates: "homepage", "about-page", "contact"// playwright-screenshots.js
import { test } from '@playwright/test';
import path from 'path';
test.describe('Visual tests', () => {
test('capture screenshots', async ({ page }) => {
const screenshotDir = './percy-screenshots';
// Homepage
await page.goto('https://example.com');
await page.screenshot({
path: path.join(screenshotDir, 'homepage.png'),
fullPage: true
});
// About page
await page.goto('https://example.com/about');
await page.screenshot({
path: path.join(screenshotDir, 'about.png'),
fullPage: true
});
});
});# Run Playwright tests to generate screenshots
npx playwright test
# Upload screenshots to Percy
percy upload ./percy-screenshots --strip-extensions// puppeteer-screenshots.js
import puppeteer from 'puppeteer';
import fs from 'fs';
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Ensure screenshot directory exists
fs.mkdirSync('./screenshots', { recursive: true });
// Capture multiple viewport sizes
const viewports = [
{ width: 375, height: 667, name: 'mobile' },
{ width: 768, height: 1024, name: 'tablet' },
{ width: 1280, height: 720, name: 'desktop' }
];
const pages = ['/', '/about', '/contact'];
for (const viewport of viewports) {
await page.setViewport(viewport);
for (const pagePath of pages) {
await page.goto(`https://example.com${pagePath}`);
const filename = `${pagePath.replace('/', 'home')}-${viewport.name}.png`;
await page.screenshot({
path: `./screenshots/${filename}`,
fullPage: true
});
}
}
await browser.close();# Generate screenshots
node puppeteer-screenshots.js
# Upload to Percy
percy upload ./screenshots --strip-extensions# selenium_screenshots.py
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
import os
# Setup Chrome driver
options = Options()
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
# Create screenshots directory
os.makedirs('./screenshots', exist_ok=True)
# Test scenarios
scenarios = [
{'url': 'https://example.com', 'name': 'homepage'},
{'url': 'https://example.com/about', 'name': 'about'},
{'url': 'https://example.com/contact', 'name': 'contact'}
]
# Capture screenshots at different viewport sizes
viewports = [(375, 667), (768, 1024), (1280, 720)]
for width, height in viewports:
driver.set_window_size(width, height)
viewport_name = f"{width}x{height}"
for scenario in scenarios:
driver.get(scenario['url'])
filename = f"{scenario['name']}-{viewport_name}.png"
driver.save_screenshot(f"./screenshots/{filename}")
driver.quit()# Generate screenshots
python selenium_screenshots.py
# Upload to Percy
percy upload ./screenshots --strip-extensions# iOS Simulator screenshots (using xcrun simctl)
xcrun simctl io booted screenshot ./mobile-screenshots/ios-home.png
xcrun simctl io booted screenshot ./mobile-screenshots/ios-profile.png
# Android Emulator screenshots (using adb)
adb shell screencap -p /sdcard/android-home.png
adb pull /sdcard/android-home.png ./mobile-screenshots/
adb shell screencap -p /sdcard/android-profile.png
adb pull /sdcard/android-profile.png ./mobile-screenshots/
# Upload mobile screenshots
percy upload ./mobile-screenshots --strip-extensions# Storybook screenshot addon
npm run storybook:build
npm run storybook:screenshots # Generates screenshots
# Upload Storybook screenshots
percy upload ./storybook-screenshots \
--files "**/*.png" \
--ignore "**/temp/**" \
--strip-extensions
# BackstopJS integration
backstop test # Generates reference screenshots
# Upload BackstopJS screenshots
percy upload ./backstop_data/bitmaps_reference \
--files "**/*.png" \
--strip-extensions# Organized screenshot directory
screenshots/
├── desktop/
│ ├── homepage-1280x720.png
│ ├── about-1280x720.png
│ └── contact-1280x720.png
├── tablet/
│ ├── homepage-768x1024.png
│ ├── about-768x1024.png
│ └── contact-768x1024.png
├── mobile/
│ ├── homepage-375x667.png
│ ├── about-375x667.png
│ └── contact-375x667.png
└── temp/
└── draft-screenshot.png
# Upload with specific patterns
percy upload ./screenshots \
--files "**/desktop/*.png" \
--files "**/tablet/*.png" \
--files "**/mobile/*.png" \
--ignore "**/temp/**" \
--strip-extensions# Descriptive filenames become snapshot names
screenshots/
├── 01-homepage-desktop.png → "01-homepage-desktop"
├── 02-about-page-mobile.png → "02-about-page-mobile"
├── 03-contact-form-tablet.png → "03-contact-form-tablet"
├── 04-dashboard-logged-in.png → "04-dashboard-logged-in"
└── 05-profile-settings.png → "05-profile-settings"
percy upload ./screenshots --strip-extensions# Upload multiple screenshot directories
for dir in ./test-results/*/screenshots/; do
if [ -d "$dir" ]; then
percy upload "$dir" --strip-extensions
fi
done
# Conditional upload based on file count
screenshot_count=$(find ./screenshots -name "*.png" | wc -l)
if [ "$screenshot_count" -gt 0 ]; then
echo "Uploading $screenshot_count screenshots"
percy upload ./screenshots --strip-extensions
else
echo "No screenshots found"
fi# Good screenshot practices:
# - Use PNG for UI screenshots (better compression for flat colors)
# - Use consistent viewport sizes
# - Capture full page or specific components consistently
# - Remove dynamic content (timestamps, ads) before capturing
# - Use descriptive filenames# Handle missing directory
if [ ! -d "./screenshots" ]; then
echo "Screenshots directory not found"
exit 1
fi
# Check for supported file types
image_count=$(find ./screenshots -name "*.png" -o -name "*.jpg" -o -name "*.jpeg" | wc -l)
if [ "$image_count" -eq 0 ]; then
echo "No supported image files found"
exit 1
fi
# Upload with error handling
percy upload ./screenshots --strip-extensions || {
echo "Percy upload failed"
exit 1
}# Ensure proper permissions
chmod -R 644 ./screenshots/*.{png,jpg,jpeg}
# Check file accessibility
find ./screenshots -name "*.png" -not -readable | while read file; do
echo "Warning: Cannot read $file"
done# GitHub Actions example
- name: Generate screenshots
run: npm run screenshots
- name: Upload to Percy
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
run: percy upload ./screenshots --strip-extensions# Development script
#!/bin/bash
set -e
echo "Cleaning old screenshots..."
rm -rf ./screenshots
echo "Generating new screenshots..."
npm run test:visual
echo "Uploading to Percy..."
percy upload ./screenshots --strip-extensions
echo "Screenshots uploaded successfully!"# Upload screenshots from different environments
percy upload ./screenshots/staging --files "**/*.png" --strip-extensions
percy upload ./screenshots/production --files "**/*.png" --strip-extensions# Set Percy token
export PERCY_TOKEN=your-percy-token
# Verify token type
percy ping # Should respond if token is validInstall with Tessl CLI
npx tessl i tessl/npm-percy--cli