Setting Up Visual Regression Testing for 1C-Bitrix
A CSS update in the template, a component change, a jQuery update — and the "Buy" button shifts by 3 pixels, the price block overlaps the gallery on mobile, the product card font becomes 2px smaller. You cannot catch this by eye. Visual regression testing takes a screenshot of a page before and after changes and compares them pixel by pixel.
Tools
Playwright + built-in snapshot tests — the most integrated option if Playwright is already used for E2E. Stores reference screenshots in the repository and compares them on every run.
Percy (BrowserStack) — a SaaS service with a visual dashboard, convenient review system, and responsive design support. Requires a paid subscription above 5,000 snapshots/month.
Chromatic — similar to Percy, optimized for Storybook. A good choice if the project has a component library.
For most 1C-Bitrix projects, Playwright with built-in snapshot tests is sufficient.
Setting Up Snapshot Tests in Playwright
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
snapshotPathTemplate: '{testDir}/__snapshots__/{testFilePath}/{arg}{ext}',
expect: {
toHaveScreenshot: {
maxDiffPixels: 50, // tolerance: 50 pixel difference
threshold: 0.01, // 1% per-pixel difference
animations: 'disabled', // disable CSS animations
},
},
});
Basic Visual Tests for 1C-Bitrix
// tests/visual/catalog.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Catalog visual', () => {
test('catalog section page', async ({ page }) => {
await page.goto('/catalog/electronics/');
// Wait for images to load
await page.waitForLoadState('networkidle');
// Hide dynamic elements (time-based sale labels, counters)
await page.evaluate(() => {
document.querySelectorAll('.catalog-item-label-sale').forEach(el => {
(el as HTMLElement).style.visibility = 'hidden';
});
});
await expect(page).toHaveScreenshot('catalog-section.png');
});
test('product card', async ({ page }) => {
await page.goto('/catalog/electronics/headphones/model-x100/');
await page.waitForLoadState('networkidle');
// Hide sale timer if present
await page.evaluate(() => {
const timer = document.querySelector('.sale-timer');
if (timer) (timer as HTMLElement).style.display = 'none';
});
await expect(page).toHaveScreenshot('product-card.png', { fullPage: false });
});
test('cart page', async ({ page }) => {
// Log in and add a product
await page.request.post('/local/ajax/cart-add.php', {
data: { product_id: 123, quantity: 1 }
});
await page.goto('/personal/cart/');
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('cart.png');
});
});
Mobile Viewport
A separate project in the config for mobile view:
// playwright.config.ts
projects: [
{
name: 'desktop-chrome',
use: { viewport: { width: 1440, height: 900 } },
},
{
name: 'mobile-iphone',
use: {
...devices['iPhone 14'],
viewport: { width: 390, height: 844 },
},
testMatch: '**/visual/**',
},
],
Updating Reference Screenshots
When an intentional design change is made, update the baselines with:
npx playwright test --update-snapshots tests/visual/
The updated screenshots are committed to the repository as part of a PR. The reviewer sees not only code changes in the diff but also visual changes.
Masking Dynamic Elements
1C-Bitrix pages contain elements that change on every load: visitor counters, sale timers, "Viewed today" widgets, and similar. These must be masked:
await expect(page).toHaveScreenshot('homepage.png', {
mask: [
page.locator('.bx-visitor-counter'),
page.locator('.product-views-count'),
page.locator('.sale-countdown-timer'),
page.locator('.personal-greeting'), // "Good afternoon, Ivan"
],
});
CI/CD Integration
# .github/workflows/visual.yml
visual-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npx playwright install chromium
- run: npx playwright test tests/visual/
env:
TEST_BASE_URL: ${{ secrets.STAGING_URL }}
- uses: actions/upload-artifact@v4
if: failure()
with:
name: visual-diff
path: test-results/
When a test fails, test-results/ will contain three files: the baseline, the current snapshot, and a diff with highlighted differences.
Implementation Strategy
| Stage | What to do | Timeline |
|---|---|---|
| Baseline snapshots | 10–15 key pages on desktop and mobile | 1–2 days |
| CI integration | Run on PR to staging | 0.5 days |
| Expand coverage | Catalog components, cart, checkout | 2–3 days |
| Mobile profile | Separate tests for 375px, 768px | 1 day |
Start with the homepage, catalog page, product card, and cart — these account for 80% of what breaks during template updates.







