یک تست E2E میتواند سبز باشد چون دکمه «پرداخت» کلیک میشود و API پاسخ موفق میدهد؛ در همان Build ممکن است دکمه زیر نوار پایین موبایل پنهان شده، مبلغ فارسی از کارت بیرون زده یا فونت جایگزین کل چیدمان را جابهجا کرده باشد. Assertion رفتاری این تغییرها را لزوماً نمیبیند، اما یک مقایسه تصویری پایدار آنها را فوراً برجسته میکند.
تست رگرسیون بصری (Visual Regression Testing یا VRT) تصویر فعلی یک حالت مشخص از UI را با Baseline تأییدشده همان حالت و همان محیط مقایسه میکند. خروجی ابزار «تغییر» را نشان میدهد، نه اینکه بهتنهایی درباره درست یا غلط بودن طراحی تصمیم بگیرد. کیفیت VRT بیش از الگوریتم Diff به سه چیز وابسته است: محیط رندر تکرارپذیر، داده و State کنترلشده، و فرایند انسانیِ دقیق برای پذیرش Baseline.
پاسخ کوتاه: تست رگرسیون بصری چیست؟
در Visual Regression Testing معمولاً این چرخه اجرا میشود:
- یک Component، Page یا checkpoint از Journey در State مشخص Render میشود.
- Screenshot مورد انتظار پس از بازبینی بهعنوان Baseline ذخیره میشود.
- در Build بعدی همان State در محیط همسان دوباره Capture میشود.
- الگوریتم Expected و Actual را مقایسه و Diff میسازد.
- انسان Diff را به تغییر عمدی، رگرسیون، نویز یا مشکل زیرساخت طبقهبندی میکند.
- رگرسیون رفع میشود یا تغییر عمدی با Review به Baseline تازه تبدیل میشود.
Baseline یک «تصویر همیشه درست» نیست؛ قرارداد بصری نسخهدار برای یک State، Viewport، Browser/Project و محیط معین است.
VRT چه باگهایی را پیدا میکند؟
- بریدگی، همپوشانی یا Overflow متن و کنترلها؛
- تغییر ناخواسته فاصله، اندازه، Alignment یا ترتیب بصری؛
- حذف Border، Icon، حالت Disabled، Focus یا Validation؛
- Font fallback، وزن غلط، شکستن خط یا جابهجایی ناشی از Web font؛
- مشکل RTL، ترکیب فارسی/لاتین و نمایش مبلغ یا تاریخ؛
- رنگ، Theme، Dark mode یا Design token اشتباه؛
- تصویر، SVG، Asset یا Background گمشده؛
- Modal، Tooltip، Drawer، Toast و Overlay در جای نادرست؛
- Responsive layout معیوب در Breakpointهای منتخب؛
- رگرسیون سراسری ناشی از تغییر CSS یا Component مشترک.
Diff فقط یک سیگنال است
هر تفاوت پیکسلی باگ نیست. تغییر Copy، طراحی تازه یا Baseline قدیمی میتواند Diff عمدی بسازد. برعکس، Screenshot یکسان نیز ثابت نمیکند دکمه کلیکپذیر، فرم قابل ارسال یا داده مالی صحیح است. ابزار ماشین ناحیه تغییر را کوچک میکند؛ تصمیم کیفیت همچنان به Oracle و Review وابسته است.
Visual Regression با Functional، Snapshot و Usability چه فرقی دارد؟
| نوع بررسی | سؤال اصلی | چیزی که ثابت نمیکند |
|---|---|---|
| Visual regression | آیا ظاهر Renderشده نسبت به Baseline تغییر کرده است؟ | درستبودن رفتار، Semantics یا تجربه کاربر |
| Functional test | آیا Action و نتیجه دامنهای درستاند؟ | عدم بریدگی یا تغییر بصری |
| DOM/Text snapshot | آیا ساختار یا خروجی متنی تغییر کرده است؟ | چیدمان نهایی CSS و رندر واقعی |
| Accessibility test | آیا Semantics، Keyboard و معیارهای دسترسی رعایت میشوند؟ | همه کیفیت ادراکی یا UX |
| Usability study | آیا کاربر هدف میتواند وظیفه را بفهمد و انجام دهد؟ | Regression خودکار هر Build |
برای ارزیابی رفتار واقعی کاربر از راهنمای تست کاربردپذیری استفاده کنید. VRT مکمل آن است، نه جایگزین مشاهده و سنجش کاربر.
سه سطح مناسب برای تست بصری
Component-level
Component را در Isolation و با Props/Data ثابت Render میکنیم. سرعت بالا و تشخیص علت آسان است. حالتهای Default، Hover، Focus، Disabled، Loading، Empty، Error، متن بلند، آیکون مفقود و Theme را میتوان جدا Capture کرد.
Page-level
چیدمان چند Component، Grid، Header/Footer، Responsive behavior و تعامل CSS سراسری را میسنجد. هزینه و نویز بیشتر است، اما نقصهایی را میبیند که در Isolation وجود ندارند.
Journey checkpoint
پس از Action مهم—مثلاً بازشدن درگاه، خطای فرم یا نمایش رسید—از State نهایی Screenshot میگیریم. تعداد این Captureها را محدود نگه دارید؛ هدف، پوشش لحظه بصری پرریسک است، نه تبدیل تمام E2E به آلبوم تصویر.
هرم پیشنهادی
بخش عمده Suite را روی Component stateهای پایدار بسازید، تعداد کمتری Page layout داشته باشید و فقط checkpointهای حیاتی Journey را Capture کنید. این ساختار سرعت Review و Localization خطا را بهتر میکند.
چه چیزی را Screenshot بگیریم؟
از Risk، Change frequency و Reuse شروع کنید:
- Componentهای Design system که در صفحات متعدد مصرف میشوند؛
- صفحات درآمدی مانند جستوجو، سبد، پرداخت و رسید؛
- حالتهای Error/Empty/Loading که کمتر دستی دیده میشوند؛
- Layoutهای فارسی، RTL، متن طولانی و عدد ترکیبی؛
- Breakpointهایی که ساختار واقعاً تغییر میکند؛
- Theme و Brand variantهای قراردادی؛
- Componentهای دارای نمودار، جدول، Overlay یا Asset حساس؛
- Regressionهای بصری گذشته که احتمال تکرار دارند.
چه چیزی را Screenshot نگیریم؟
- صفحهای که فقط داده تصادفی و تبلیغ زنده دارد و قابل تثبیت نیست؛
- هر Viewport ممکن بدون دلیل ریسک؛
- دهها Screenshot تقریباً یکسان در هر مرحله E2E؛
- اطلاعات Production یا PII کاربر؛
- UI موقت که مالک و معیار پذیرش ندارد؛
- جزئیاتی که Assertion ساده DOM/CSS دقیقتر و ارزانتر میسنجد.
ماتریس Visual Coverage بسازید
فضای State × Viewport × Theme × Locale × Browser بهسرعت انفجاری میشود. ماتریس را ریسکمحور انتخاب کنید:
| بُعد | نمونه Value | قاعده انتخاب |
|---|---|---|
| State | Default، Loading، Empty، Error، Disabled، Success | حالتهای دامنهای و Incidentهای گذشته |
| Content | کوتاه، بلند، فارسی/لاتین، تصویر مفقود | مرز Layout و Localization |
| Viewport | موبایل باریک، Tablet، Desktop | Breakpoint ساختاری، نه هر عرض دلخواه |
| Theme | Light، Dark، Brand variant | Themeهای رسمی پشتیبانیشده |
| Locale/Direction | fa-IR/RTL و en-US/LTR | بازار و قرارداد محصول |
| Browser/Platform | Projectهای Tier ۱ | تنوع Engine و محیط پشتیبانیشده |
برای انتخاب Browser/OS/Device از راهنمای Browser و Device Matrix استفاده کنید. یک Baseline مشترک برای تمام Browserها نسازید؛ Font و Rendering بین Projectها فرق دارد.
Baseline دقیقاً چیست؟
Baseline باید این هویت را داشته باشد:
- Test/Story و State؛
- Component یا Page version؛
- Browser project و نسخه؛
- OS/Container image و معماری؛
- Viewport، DPR و Color scheme؛
- Locale، Timezone، Font package و Direction؛
- Fixture/data version؛
- Commit و شخص/نقش تأییدکننده.
چه کسی Baseline را تأیید میکند؟
برای تغییر معمولی، نویسنده PR و Reviewer فنی میتوانند Diff را بررسی کنند. تغییر Design system، Brand، Accessibility state یا Journey درآمدی بهتر است مالک Design/Product/QA مرتبط را نیز داشته باشد. Approval باید روی Diff مشخص باشد، نه روی صدها تصویر بهروزشده بهصورت دستهای.
Baseline را کنار کد نگه داریم یا در سرویس؟
هر دو مدل معتبرند:
- Repository-based: کنترل و Audit مستقیم، اما Binary growth و مدیریت Platform با تیم است؛
- Hosted service: Review UI، Branch baseline و Cross-browser rendering آمادهتر، اما هزینه، Retention، Data residency و Vendor dependency دارد؛
- CI artifacts: برای Actual/Diff موقت مناسب است، اما Baseline پایدار و تاریخچه Approval جدا لازم دارد.
PNGهای Baseline در Version control به سیاست Storage یا Git LFS نیاز دارند. Actual و Diff شکستخورده را با Retention محدود نگه دارید.
چرا Visual Testها Flaky میشوند؟
| منبع نویز | نشانه | کنترل |
|---|---|---|
| OS/Browser/Hardware | لبه حروف و سایه در کل تصویر | Container/runner و Browser pinشده |
| Font | شکستن خط و تغییر ارتفاع بلوک | Font بستهبندیشده، انتظار برای load و Fallback کنترلشده |
| زمان | تاریخ، Countdown و Greeting متفاوت | Clock ثابت |
| داده | نام، مبلغ، ترتیب یا تصویر متفاوت | Fixture مصنوعی و Seed ثابت |
| Animation/Caret | Diff در فریم یا Cursor تصادفی | Disable/fast-forward و پنهانکردن Caret |
| Network/Asset | Skeleton، تصویر نیمهلود یا Font fallback | Mock/route، انتظار معنایی و Asset محلی |
| Scrollbar | جابجایی افقی صفحه | Viewport/overflow ثابت و محتوای کنترلشده |
| Focus/Hover | Outline یا Tooltip گاهی دیده میشود | State صریح پیش از Capture |
| Randomness | ID، Avatar یا ترتیب متغیر | Seed و Stub ثابت |
از Sleep ثابت استفاده نکنید
waitForTimeout(2000) تضمین نمیکند Font، Image یا State آماده شده است و فقط Suite را کند میکند. برای Condition واقعی انتظار بکشید: Response یا Mock مشخص، Visible بودن State نهایی، نبود Skeleton، آمادهشدن Font و تثبیت Layout.
محیط رندر را تکرارپذیر کنید
مستندات رسمی Visual Comparisons در Playwright هشدار میدهد Rendering با OS میزبان، نسخه، تنظیمات، سختافزار، منبع برق و Headless mode تغییر میکند و توصیه میکند Comparison در همان محیط تولید Baseline اجرا شود.
قرارداد محیط پیشنهادی
- Runner یا Container image نسخهدار؛
- نسخه Playwright/Browser و Dependency lockشده؛
- Fontهای دقیق و License معتبر نصبشده؛
- Locale، Timezone، Color scheme و Reduced motion صریح؛
- Viewport و Scale ثابت؛
- عدم دانلود Asset حساس از CDN ناپایدار؛
- CPU/Concurrency کافی برای جلوگیری از Capture میان Transition؛
- Baseline مجزا برای Project/Platformهای عمداً متفاوت.
جزئیات نسخه، داده و Dependency در چکلیست Test Environment آمده است.
داده و زمان را کنترل کنید
داده مصنوعی نماینده
Fixture باید هم تکرارپذیر باشد و هم Layout را به چالش بکشد: نام فارسی بلند، مبلغ بزرگ، چند وضعیت Badge، تصویر موجود/مفقود و متن خطا. داده «علی، ۱۰۰ تومان» ممکن است بیشتر باگهای واقعی Layout را پنهان کند. برای چرخه ساخت، انقضا و پاکسازی Fixture به راهنمای مدیریت داده تست مراجعه کنید.
Clock ثابت
تاریخ، زمان، Relative time و Countdown را ثابت کنید. Clock API رسمی Playwright متد setFixedTime را برای ثابتکردن زمان و install را برای کنترل Timerها ارائه میکند. Clock را پیش از کدهای وابسته به زمان نصب کنید.
محتوای پویا را اول Deterministic کنید
ترتیب اولویت:
- داده و زمان را ثابت کنید؛
- Dependency را Mock یا Stub کنید؛
- State مورد انتظار را مستقیم بسازید؛
- فقط ناحیه واقعاً خارج از Scope را Mask/Hide کنید؛
- Threshold را آخر و با Evidence تنظیم کنید.
بالابردن Threshold برای پنهانکردن داده متغیر، رگرسیون واقعی را هم پنهان میکند.
آموزش VRT با Playwright
پیکربندی Project پایدار
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests/visual',
fullyParallel: true,
use: {
locale: 'fa-IR',
timezoneId: 'Asia/Tehran',
colorScheme: 'light',
reducedMotion: 'reduce',
},
projects: [
{
name: 'chromium-linux-fa-mobile',
use: {
...devices['Desktop Chrome'],
viewport: { width: 390, height: 844 },
},
},
{
name: 'chromium-linux-fa-desktop',
use: {
...devices['Desktop Chrome'],
viewport: { width: 1440, height: 1000 },
},
},
],
expect: {
toHaveScreenshot: {
animations: 'disabled',
caret: 'hide',
scale: 'css',
},
},
});
نام Project باید Environment intent را نشان دهد. Baseline موبایل و دسکتاپ جداست؛ اگر Firefox/WebKit نیز در قرارداد پشتیبانیاند، Project و Baseline مستقل اضافه کنید.
یک تست پایدار برای خلاصه سفارش فارسی
import { test, expect } from '@playwright/test';
test('order summary — paid — fa-IR RTL', async ({ page }) => {
await page.clock.setFixedTime(
new Date('2026-01-15T10:00:00+03:30')
);
await page.route('**/api/orders/VR-104', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
id: 'VR-104',
customer: 'فاطمه محمدینیا',
status: 'PAID',
totalToman: 987654321,
items: 3,
}),
});
});
await page.goto('/orders/VR-104?locale=fa-IR');
await expect(page.getByTestId('order-status')).toHaveText('پرداختشده');
await page.evaluate(() => document.fonts.ready);
await expect(page.getByTestId('order-summary')).toHaveScreenshot(
'order-summary-paid-fa-rtl.png',
{
animations: 'disabled',
maxDiffPixels: 20,
}
);
});
چرا Assertion متنی قبل از Screenshot است؟
Screenshot باید State درست را Capture کند. Assertion متنی یک Wait معنایی و یک Oracle رفتاری کوچک است. بدون آن ممکن است Baseline از Loading state یا Response اشتباه ساخته شود.
اولین Baseline
در اجرای نخست، Reference ساخته میشود. آن را با Requirement، Story/Design و داده Fixture بازبینی و سپس Commit کنید. طبق مستندات Playwright، Snapshotهای Repository باید Review شوند؛ تولیدشدن خودکار به معنی صحیحبودن آنها نیست.
Threshold را درست بفهمید
در ابزارهای Pixel diff، چند تنظیم متفاوت وجود دارد:
| تنظیم | معنا | خطر استفاده غلط |
|---|---|---|
threshold |
حساسیت اختلاف رنگ همان پیکسل در معیار ادراکی الگوریتم | عدد lax تفاوت رنگ واقعی را نادیده میگیرد |
maxDiffPixels |
حداکثر تعداد پیکسل متفاوت پذیرفتهشده | روی تصویر کوچک/بزرگ معنای یکسان ندارد |
maxDiffPixelRatio |
نسبت پیکسل متفاوت به کل تصویر | در Full page بزرگ میتواند ناحیه مهمی را پنهان کند |
روش تنظیم
- ابتدا محیط، Font، Time و Data را تثبیت کنید.
- با Diff سختگیرانه چند اجرای تکراری روی همان Commit بگیرید.
- نویز واقعی را بر اساس ناحیه و علت طبقهبندی کنید.
- کمترین تحمل لازم را برای همان Test/گروه انتخاب کنید.
- رگرسیونهای نمونه را تزریق کنید و مطمئن شوید هنوز Fail میشوند.
- Threshold را نسخهدار و در Review قابل مشاهده نگه دارید.
یک درصد جهانی برای همه Componentها تعریف نکنید. تغییر دو پیکسل در Icon پرداخت شاید مهمتر از صد پیکسل Anti-aliasing در تصویر تزئینی باشد.
Mask، Hide و stylePath؛ با احتیاط
Mask
برای ناحیهای مانند QR یکبارمصرف یا Avatar خارج از Scope مفید است. اما Mask وسیع میتواند جابهجایی، Overflow و محتوای مهم را پنهان کند. Locator آن باید پایدار و دلیل Mask مستند باشد.
stylePath
Playwright امکان اعمال Stylesheet اختصاصی هنگام Capture را دارد. میتوان Animation یا عنصر واقعاً volatile را کنترل کرد. CSS تست نباید Layout Production را بازطراحی کند؛ در غیر این صورت چیزی غیر از محصول را Baseline میکنید.
Hide با Fixture بهتر است
اگر Banner با Feature flag در Test خاموش میشود، این State را صریح نامگذاری کنید. برای State روشن نیز یک Story/Test جدا داشته باشید. حذف خاموش و بینام ناحیه از همه Snapshotها Coverage gap میسازد.
Full-page یا Component Screenshot؟
| روش | مزیت | هزینه/ریسک |
|---|---|---|
| Element/Component | Diff کوچک، سریع، علت واضح | Interaction با Layout والد را نمیبیند |
| Region | Context محدود مانند Header+Nav | مرز Crop باید پایدار باشد |
| Viewport | آنچه کاربر در یک قاب میبیند | Scroll state و Viewport باید کنترل شود |
| Full page | چیدمان کلی و Sectionهای دور | نویز، زمان، نسبت Diff و محتوای پویا بیشتر |
پیشفرض را Component/Region بگذارید و Full page را برای صفحات ثابت و پرریسک محدود کنید. Screenshot بلند با Threshold نسبی میتواند تغییر محلی مهم را کمرنگ کند.
Responsive و Cross-browser Visual Testing
Breakpoint را از طراحی و CSS واقعی انتخاب کنید، نه صرفاً نام Device. یک تست در عرض ۷۶۷ و ۷۶۸ ممکن است برای مرز Layout ارزش بیشتری از ده مدل گوشی مشابه داشته باشد. با این حال، Viewport simulation معادل دستگاه واقعی نیست؛ Safe area، Font، Scrollbar و OS integration تفاوت دارند.
Baseline هر Project
Playwright نام Browser/Platform یا Project را در مسیر Snapshot وارد میکند، زیرا رندر و Font متفاوت است. Baseline Chromium را به WebKit تحمیل نکنید. اگر هدف «رفتار معادل» است، تفاوت ظاهری قابل قبول هر Platform Baseline خودش را دارد.
Matrix را در Laneها توزیع کنید
- PR: Componentهای تغییرکرده، یک Browser ثابت و Viewportهای اصلی؛
- Merge/Nightly: Pageها، Themeها و Browser Projectهای بیشتر؛
- Release: Journeyهای حیاتی و Device/OSهای Tier ۱؛
- دورهای: Browser آینده، Font/OS update و بازبینی Baseline قدیمی.
Storybook، Cypress یا Playwright؟
| رویکرد | مناسب برای | مسئولیت تیم |
|---|---|---|
| Playwright Test | Page/Locator screenshot، E2E checkpoint و Baseline در Repo | Runner همسان، Storage و Review Diff |
| Cypress + Plugin/Service | افزودن Visual check به Suite موجود Cypress | انتخاب Integration و فهم محل Diff/Baseline |
| Storybook + Visual integration | Component stateها در Isolation | Story کامل، Mock و Review تغییر |
| Dedicated open-source runner | Screenshot workflow مستقل و کنترل داخلی | Hosting، Browser pinning، UI Review و نگهداری |
| Hosted visual service | Cross-browser rendering، Branch baseline و Review dashboard | هزینه، امنیت، Retention، Export و Vendor risk |
مستندات رسمی Visual Testing در Cypress روشن میکند که cy.screenshot() تصویر میگیرد اما خود Cypress مقایسه تصویر انجام نمیدهد و برای این کار Plugin یا Integration لازم است. مستندات تست UI در Storybook نیز Storyها را محیط ایزوله Component با Mock داده و Dependency معرفی میکند.
معیار انتخاب ابزار
- محل Render: Runner شما یا Cloud vendor؛
- Pixel/Perceptual/Layout comparison و قابلیت توضیح Diff؛
- Component، Page، Native mobile یا Desktop support؛
- Branch baseline، Approval، Comment و Audit trail؛
- Browser/OS/Viewport inventory و Pin نسخه؛
- Parallelism، Queue time و قیمت واقعی هر Build؛
- Mask/region/style و مدیریت محتوای پویا؛
- API/CLI، CI integration و Export Baseline/History؛
- SSO/RBAC، Encryption، Retention و Data residency؛
- اتصال، پرداخت و پشتیبانی قابل اتکا برای تیم ایران؛
- Migration/exit plan و نبود قفل غیرقابل خروج.
یک Component و یک Page واقعی را در PoC اجرا کنید و نرخ نویز، زمان Review، حجم Artifact و هزینه را بسنجید. فهرست Feature فروشنده جای Proof of Concept را نمیگیرد.
Workflow پذیرش و رد Diff
چهار طبقه خروجی
- رگرسیون واقعی: ظاهر ناخواسته عوض شده؛ کد یا Asset اصلاح شود.
- تغییر عمدی: Requirement/Design تغییر کرده؛ Baseline با Approval بهروز شود.
- نویز/Flake: Time، Font، Animation یا Environment؛ علت تثبیت شود.
- Baseline gap: Reference قبلی از ابتدا غلط یا State اشتباه بوده؛ اصلاح با توضیح و Review.
Update Baseline یک Fix نیست
فرمان بهروزرسانی Snapshot باید پس از Review اجرا شود، نه برای سبزکردن Pipeline. تغییر Expected، Actual و Diff را کنار هم ببینید. Bulk update بدون بررسی میتواند همان رگرسیونی را که Test برایش ساخته شده دائمی کند.
مالکیت
هر Suite یک Owner فنی و SLA Review نیاز دارد. Diffهای رهاشده یا Baselineهایی که هفتهها منتظر تأییدند، CI را بیاعتبار میکنند. Reviewer باید توان تشخیص Design intent و ریسک کسبوکار را داشته باشد.
اتصال VRT به CI/CD
- Build قابل تکرار و Dependency lockشده بسازید.
- محیط محلی/CI یکسان برای Baseline و Actual استفاده کنید.
- Fixture و Clock را قبل از Navigation/Render کنترل کنید.
- Testهای Component و PR scope را موازی اجرا کنید.
- Expected/Actual/Diff و Trace را برای Fail آپلود کنید.
- روی PR لینک Review و طبقهبندی Diff را نمایش دهید.
- Approval Baseline را با Policy و نقش مجاز کنترل کنید.
- Artifactهای Actual/Diff را پس از Retention حذف کنید.
- Nightly matrix را جدا نگه دارید تا PR feedback کند نشود.
- Flake و Environment drift را پایش و Runner image را نسخهگذاری کنید.
VRT فقط وقتی ارزش Automation دارد که Signal آن قابل اعتماد و هزینه Review قابل تحمل باشد. اصول انتخاب کاندیدا و نگهداری در راهنمای اتوماسیون تست آمده است.
حریم خصوصی و امنیت Screenshot
تصویر میتواند نام، شماره تماس، آدرس، مانده کیف پول، Token داخل QR یا داده سلامت را ثبت کند. برای کاهش ریسک:
- فقط داده مصنوعی و حساب Test استفاده کنید؛
- Screenshot از Production واقعی را ممنوع یا به فرایند کنترلشده محدود کنید؛
- Secret و PII را پیش از Capture حذف کنید، نه فقط بعداً Blur؛
- Bucket/Artifact را با RBAC، Encryption و Expiry کنترل کنید؛
- Fork PRهای غیرقابل اعتماد را به Secret/Artifact حساس وصل نکنید؛
- Retention Baseline و Diff را جدا تعریف کنید؛
- هنگام استفاده از Cloud، Data location و Subprocessor را بررسی کنید؛
- Log، Trace و DOM snapshot همراه تصویر را نیز در Threat model ببینید.
VRT برای زبان فارسی و RTL
Font و نیمفاصله
Font فارسی دقیق را در Runner نصب و Load آن را Assert کنید. Fallback ممکن است عرض حروف، ارتفاع خط و جای ارقام را تغییر دهد. متنهایی با نیمفاصله، اعراب، ک/ی فارسی و ترکیب لاتین بسازید.
عدد، قیمت و تاریخ
مبالغ کوچک و بزرگ، ریال/تومان، ارقام فارسی و لاتین، جداکننده هزارگان و علامت منفی را Capture کنید. تاریخ شمسی/میلادی و Timezone را با Clock ثابت نگه دارید؛ ظاهر سبز نباید اشتباه دامنهای تبدیل واحد را پنهان کند.
RTL واقعی
فقط dir="rtl" صفحه Home را تست نکنید. Input، Icon جهتدار، Breadcrumb، Stepper، Tooltip، Drawer، جدول و متن دوجهته را در Stateهای مستقل ببینید. Focus ring و Error association را نیز با تست دسترسپذیری تکمیل کنید.
VRT جای تست دسترسپذیری را نمیگیرد
Screenshot ممکن است Focus outline را نشان دهد، اما نام دسترسپذیر، Role، ترتیب Tab، Announcement صفحهخوان و رابطه Label را نمیسنجد. حتی Contrast را بهتر است با محاسبه استاندارد و Stateهای واقعی بررسی کرد، نه صرفاً شباهت با Baseline. فرایند کامل در راهنمای WCAG ۲.۲ و تست دسترسپذیری آمده است.
VRT جای تست عملکرد را هم نمیگیرد
تصویر نهایی یکسان ممکن است پس از پنج ثانیه Layout shift یا Font swap آزاردهنده ثبت شده باشد. Screenshot ثابت، LCP، CLS، INP، زمان Render، Memory یا رفتار زیر بار را اندازه نمیگیرد. برای SLO و مدل بار به برنامه تست عملکرد مراجعه کنید.
گزارش باگ بصری مؤثر
عنوان نمونه
«[390px / Chromium Linux / fa-IR RTL] مبلغ ۹۸۷٬۶۵۴٬۳۲۱ تومان روی دکمه پرداخت همپوشانی دارد» از «UI خراب است» قابل اقدامتر است.
Evidence لازم
- Expected، Actual و Diff؛
- Test/Story/State و Snapshot name؛
- Commit، Build و Browser project؛
- OS/Container، Browser version، Viewport و DPR؛
- Locale، Timezone، Font و Theme؛
- Fixture version و مراحل رسیدن به State؛
- درصد/تعداد Diff و تنظیم Comparator؛
- آیا در اجرای تکراری همان Commit بازتولید میشود؟
برای Severity، Actual/Expected و شواهد امن از قالب گزارش باگ حرفهای استفاده کنید.
تشخیص سریع الگوی Diff
| الگو | فرضیه اول | بررسی |
|---|---|---|
| هاله دور همه حروف | OS/Font/Anti-aliasing drift | Runner image و Font hash |
| تمام صفحه چند پیکسل جابهجا | Scrollbar، Viewport یا Font load | ابعاد، overflow و document.fonts |
| فقط تاریخ/مبلغ فرق دارد | Clock یا Fixture ناپایدار | Mock و Seed |
| یک Component مشترک در ده صفحه | Design token/CSS سراسری | Diff Component-level و تغییر Dependency |
| فقط CI Fail است | محیط محلی با Baseline متفاوت | همان Container/Project را محلی اجرا کنید |
| گاهی Pass، گاهی Fail | Animation، Network یا Capture زودهنگام | Trace و State readiness |
معیارهای سلامت برنامه Visual Testing
- Visual states پرریسک پوششدادهشده / فهرست هدف؛
- نرخ Diffهای Actionable، عمدی، نویز و زیرساخت؛
- Flake تکرارشونده روی Commit یکسان؛
- میانه و p95 زمان Review Diff؛
- Baseline update بدون Approval یا بدون Requirement؛
- رگرسیون بصری فراریافته به Production؛
- زمان PR و Nightly به تفکیک Lane؛
- حجم Baseline/Artifact و هزینه Storage/Cloud؛
- سن Baseline و Browser/Runner image؛
- Testهای Maskشده و ناحیههای خارج از Scope.
هدف «صفر Diff» نیست؛ تغییر UI طبیعی است. هدف، Diff قابل توضیح با نویز کم و تصمیم سریع است. تعداد Screenshot نیز KPI فردی مناسبی نیست.
برنامه پیادهسازی ۳۰ روزه
هفته اول: Pilot و خط پایه
- یک Component مشترک و یک Page درآمدی انتخاب کنید؛
- Runner، Browser، Font و Fixture را Pin کنید؛
- پنج State پرریسک و دو Viewport تعریف کنید؛
- سه اجرای تکراری روی Commit یکسان برای سنجش نویز بگیرید.
هفته دوم: Review workflow
- مالک، Approver و چهار طبقه Diff را تعریف کنید؛
- Expected/Actual/Diff را در PR قابل مشاهده کنید؛
- Retention و دسترسی Artifact را تنظیم کنید؛
- یک رگرسیون عمدی کوچک تزریق و Detection را تأیید کنید.
هفته سوم: CI laneها
- Component smoke را در PR فعال کنید؛
- Page/Browser matrix را Nightly ببرید؛
- Budget زمان و Flake SLA تعیین کنید؛
- Thresholdها را فقط پس از تثبیت تنظیم کنید.
هفته چهارم: گسترش ریسکمحور
- RTL، Dark mode، Error/Empty و متن بلند را اضافه کنید؛
- Regressionهای گذشته را به Suite ببرید؛
- Coverage، Review latency و Noise را گزارش کنید؛
- تصمیم ادامه، تغییر ابزار یا توقف موارد کمارزش را مستند کنید.
اشتباههای رایج
- ساخت Baseline از UI بررسینشده و نامیدن آن Golden؛
- اجرای Baseline روی لپتاپ و Actual روی CI متفاوت؛
- یک Baseline برای همه Browserها و OSها؛
- Snapshot گرفتن قبل از آمادهشدن State و Font؛
- حل Flake با Sleep و Threshold بزرگ؛
- Mask کردن ناحیههای وسیع بدون Test مکمل؛
- Full-page screenshot برای هر Route و هر Commit؛
- بهروزرسانی دستهای Snapshot برای سبزکردن Pipeline؛
- تصور اینکه Perceptual/AI comparator حقیقت طراحی را میفهمد؛
- اتکا به VRT بهجای Functional، Accessibility یا Performance test؛
- استفاده از داده واقعی مشتری در Baseline یا Cloud artifact؛
- نداشتن Owner و SLA برای Review؛
- شمردن Screenshot بهجای State و ریسک پوششدادهشده؛
- فهرستکردن ابزارها بدون PoC، Export و Exit plan؛
- نادیدهگرفتن Font فارسی، RTL و ارقام/مبلغهای بلند.
چکلیست تست رگرسیون بصری
- Component/Page/Journey و State هدف دلیل ریسک دارد.
- Baseline با Commit، Project، Environment و Fixture قابل ردگیری است.
- Runner، Browser، Font، Viewport، DPR، Locale و Timezone Pin شدهاند.
- Data، Clock، Randomness، Network و Asset کنترل شدهاند.
- پیش از Capture یک Wait/Assertion معنایی داریم.
- Animation، Caret، Focus و Hover State صریحاند.
- Thresholdها از اجرای تکراری و تست رگرسیون نمونه به دست آمدهاند.
- Mask/Hide کوچک، مستند و دارای Coverage مکمل است.
- Baseline هر Browser/Platform جداست.
- PR و Nightly matrix هدف متفاوت دارند.
- Expected/Actual/Diff با Reviewer و SLA مشخص بررسی میشوند.
- Baseline update بدون Approval ممکن نیست.
- Screenshot و DOM/Trace حاوی PII یا Secret نیستند.
- Functional، A11y، Performance و Exploratory test مکمل وجود دارد.
- Noise، Review latency، Escaped defects و هزینه پایش میشوند.
پرسشهای متداول
تفاوت Visual Testing و Visual Regression Testing چیست؟
Visual Testing مفهوم گسترده ارزیابی ظاهر UI است و میتواند Review دستی طراحی را هم شامل شود. Visual Regression Testing مشخصاً ظاهر فعلی را با Baseline قبلی مقایسه میکند تا تغییر ناخواسته را در طول زمان پیدا کند.
آیا هر Diff باید Build را Fail کند؟
در Laneهای محافظتی، Diff باید وضعیت «نیازمند Review» ایجاد کند و Merge را تا طبقهبندی متوقف کند. پس از Review، رگرسیون رفع یا تغییر عمدی تأیید میشود. برای محیط آزمایشی/اکتشافی میتوان Policy نرمتری داشت، اما Diff بیمالک نباید سبز محسوب شود.
بهترین Threshold چند درصد است؟
عدد جهانی وجود ندارد. اول محیط و داده را تثبیت کنید، سپس کمترین تحمل لازم را بر اساس چند اجرای یکسان و رگرسیونهای تزریقی تعیین کنید. Threshold رنگ، تعداد پیکسل و نسبت پیکسل سه تنظیم متفاوتاند.
آیا Playwright بهتنهایی برای VRT کافی است؟
برای بسیاری از تیمهای وب، Screenshot assertion، Baseline در Repo و CI داخلی کافی است. اگر Cross-browser rendering وسیع، Review dashboard، Branch baseline یا همکاری طراحی پیشرفته نیاز دارید، Plugin/Service یا معماری دیگر را با PoC ارزیابی کنید.
با تاریخ، تبلیغ و محتوای پویا چه کنیم؟
Clock و Fixture را ثابت، Dependency را Mock و State را مستقیم بسازید. فقط ناحیه واقعاً خارج از Scope را Mask کنید. افزایش Threshold یا پنهانکردن کل بخش آخرین گزینه است، چون میتواند Layout regression را پنهان کند.
جمعبندی
تست رگرسیون بصری زمانی قابل اعتماد است که Baseline مثل کد مدیریت شود: محیط رندر همسان، Browser و Font نسخهدار، State و داده مصنوعی پایدار، Threshold مستدل و Approval قابل Audit. از Component stateها شروع کنید، Page و Journey را محدود و ریسکمحور اضافه کنید و PR/Nightly را از هم جدا نگه دارید. ماشین تفاوت را پیدا میکند؛ تیم با Requirement و شواهد تصمیم میگیرد آن تفاوت رگرسیون، تغییر عمدی یا نویز است. VRT خوب وعده «UI بینقص» نمیدهد؛ یک سامانه بازخورد دقیق میسازد که تغییر بصری ناخواسته را پیش از کاربر قابل مشاهده و قابل اقدام میکند.

