تست‌های عملکردی Checkout سبزند؛ دکمه «پرداخت» هم در DOM وجود دارد و کلیک می‌شود. بااین‌حال در عرض ۳۹۰ پیکسل و رابط فارسی RTL، برچسب «تومان» روی مبلغ افتاده و دکمه زیر پیام تخفیف پنهان شده است. این خطا را Assertion مربوط به API یا متن دکمه نمی‌بیند؛ کاربر آن را در یک نگاه می‌بیند. این‌جا تست بصری یا Visual Testing ارزش پیدا می‌کند.

اما گرفتن دو Screenshot و انتخاب یک Threshold عمومی، سامانه تست قابل‌اعتماد نمی‌سازد. Browser، سیستم‌عامل، Font، DPR، Animation، داده و زمان می‌توانند پیکسل‌ها را عوض کنند. از طرف دیگر، یک تغییر کوچک و پرریسک—مثلاً سبز شدن اشتباهی وضعیت «پرداخت ناموفق»—ممکن است در امتیاز کل تصویر ناچیز به نظر برسد. این راهنما تست رگرسیون بصری را از تعریف Baseline تا تثبیت Capture، انتخاب Comparator، Diff Triage، تأیید انسانی و Quality Gate توضیح می‌دهد و با یک آزمایش کنترل‌شده نشان می‌دهد چرا «یک عدد برای کل صفحه» کافی نیست.

پاسخ کوتاه: یک Visual Test قابل‌دفاع، فقط مقایسه دو تصویر نیست؛ قراردادی نسخه‌دار از State + Viewport + Browser/OS/Font + Capture rule + Baseline + Comparator + Region policy + Approval است. خروجی آن نیز «تفاوت پیدا شد» است، نه «محصول حتماً خراب است». تصمیم نهایی باید با هدف تست، ریسک ناحیه و شواهد مکمل گرفته شود.

تست بصری چیست؟

تست بصری (Visual Testing) بررسی می‌کند رابط کاربری در یک حالت مشخص چگونه رندر شده است. در رایج‌ترین پیاده‌سازی، تست ابتدا UI را به State هدف می‌رساند، Screenshot تازه می‌گیرد، آن را با تصویر مرجع یا Baseline مقایسه می‌کند و Diff را برای تصمیم‌گیری نگه می‌دارد.

طبق مستندات رسمی مقایسه بصری Playwright، اجرای اول تصویر مرجع را تولید می‌کند و اجراهای بعدی Screenshot تازه را با آن می‌سنجند. همین مستندات هشدار می‌دهند که OS، نسخه، تنظیمات، سخت‌افزار، منبع تغذیه و حالت Headless روی رندر اثر دارند؛ بنابراین ثبات محیط بخشی از خود تست است، نه یک جزئیات جانبی.

هدف درست Visual Testing

هدف، اثبات «زیبایی» یا درست‌بودن همه رفتارها نیست. هدف این است که تغییرات قابل‌مشاهده نسبت به یک مرجع تأییدشده، در محدوده و حالت تعریف‌شده آشکار شوند؛ سپس تیم مشخص کند تغییر عمدی، باگ محصول، ناپایداری Capture یا Baseline قدیمی است.

تست رگرسیون بصری چه تفاوتی با تست بصری دارد؟

Visual Testing اصطلاح گسترده‌تر است و هر بررسی ظاهر UI را در بر می‌گیرد؛ از بازبینی دستی طرح تا مقایسه خودکار Screenshot. Visual Regression Testing روی کشف تغییر ناخواسته نسبت به وضعیت تأییدشده قبلی تمرکز دارد. در عمل، بسیاری از تیم‌ها این دو عبارت را به‌جای هم جست‌وجو می‌کنند، اما هنگام طراحی فرایند باید هدف Regression روشن باشد.

  • تست بصری دستی: انسان صفحه یا طرح را مشاهده و با انتظار مقایسه می‌کند.
  • تست Screenshot: تصویر تازه با Golden/Baseline ذخیره‌شده مقایسه می‌شود.
  • Visual Regression: تغییر بین نسخه‌ها، Commitها یا Branchها شناسایی و بازبینی می‌شود.
  • UI Review: تیم محصول یا طراحی درباره عمدی‌بودن تغییر و کیفیت آن تصمیم می‌گیرد.

تست بصری چه خطاهایی را خوب پیدا می‌کند؟

این روش برای خطاهایی مناسب است که «ساختار یا رفتار منطقی ممکن است هنوز درست باشد، اما خروجی قابل‌مشاهده خراب شده باشد». نمونه‌های پرارزش عبارت‌اند از:

  • Overlap، Clipping، Overflow و بیرون‌زدگی متن یا تصویر؛
  • جابه‌جایی ناخواسته Component، تغییر اندازه یا شکستن Grid؛
  • Font fallback، وزن یا Line-height اشتباه و تفاوت Wrap متن؛
  • رنگ، Border، Icon، Shadow، Theme یا Token طراحی اشتباه؛
  • حالت ناقص Loading، Empty، Error، Disabled، Hover یا Focus؛
  • مشکل Responsive در Viewportهای منتخب؛
  • خرابی RTL، متن ترکیبی فارسی/لاتین، ارقام و واحد پول؛
  • تغییر ناخواسته مشترک در ده‌ها Component پس از اصلاح CSS پایه.

مستندات Visual Testing در Chromatic نیز بر همین شکاف تأکید می‌کند: تست عملکردی منطق و رفتار را می‌سنجد، اما الزاماً پیکسل‌های رندرشده را اعتبارسنجی نمی‌کند. این منبع فرایند را Capture، مقایسه با Baseline و بازبینی تغییر توصیف می‌کند؛ مرحله بازبینی همان جایی است که Intent انسانی وارد تصمیم می‌شود.

تست بصری جایگزین چه تست‌هایی نیست؟

Screenshot به‌تنهایی نمی‌گوید دکمه واقعاً سفارش را ثبت کرده، پاسخ API درست بوده، Focus به ترتیب منطقی حرکت کرده یا Screen reader نام کنترل را خوانده است. Visual Diff باید کنار لایه‌های دیگر قرار بگیرد:

  • Functional/Interaction test: رفتار، State transition و Side effect؛
  • API و Integration test: قرارداد سرویس و داده؛
  • Accessibility test: Semantics، Keyboard، Screen reader و معیارهای WCAG؛
  • Performance test: زمان رندر، Responsiveness و مصرف منابع؛
  • Exploratory/UI review: تناسب، فهم‌پذیری و تجربه واقعی کاربر.

برای تفکیک دقیق این لایه‌ها، راهنمای تست UI در Storybook، Render، Interaction، Accessibility و Visual را انواع مکمل تست معرفی می‌کند. در Testrail نیز راهنمای تست دسترس‌پذیری بر پایه WCAG ۲.۲ مرز شواهد Accessibility را جداگانه پوشش می‌دهد.

آناتومی یک Visual Test قابل‌اعتماد

پیش از انتخاب ابزار، Test Contract را بنویسید. اگر یکی از اجزای زیر مبهم باشد، Pass و Fail قابل‌تفسیر نیست:

  1. Risk/Question: کدام خرابی را می‌خواهیم زودتر ببینیم؟
  2. Target: Component، ناحیه، صفحه یا Journey کدام است؟
  3. State: داده، Feature flag، نقش، پاسخ شبکه و مرحله تعامل چیست؟
  4. Mode: Viewport، Browser، Theme، Locale، Direction و Font چیست؟
  5. Readiness: چه علامتی ثابت می‌کند UI آماده Capture است؟
  6. Capture: Full page، Element یا Region و با چه DPR گرفته می‌شود؟
  7. Baseline: چه نسخه‌ای، در کدام Branch و با تأیید چه کسی؟
  8. Comparator: Exact، Pixel threshold، Perceptual یا ترکیبی؟
  9. Region policy: کدام ناحیه Critical، Tolerant یا Masked است؟
  10. Decision: چه کسی Diff را Triage و چه کسی Baseline را تأیید می‌کند؟
  11. Evidence: Baseline، Actual، Diff، Config، Commit و Trace کجا نگه‌داری می‌شوند؟

Baseline حقیقت مطلق نیست

Baseline فقط «آخرین ظاهر تأییدشده در یک Context مشخص» است. ممکن است خودش باگ داشته باشد، قدیمی شده باشد یا از محیط متفاوتی آمده باشد. بنابراین عبارت «Actual با Baseline برابر است» فقط می‌گوید تغییر بصری قابل‌تشخیص رخ نداده؛ نمی‌گوید UI صحیح، دسترس‌پذیر یا مطابق Design است.

Baseline خوب چه ویژگی‌هایی دارد؟

  • به Story/Scenario، Mode، Branch و Commit مشخص وصل است؛
  • پس از مشاهده Baseline و Diff، آگاهانه تأیید شده است؛
  • به‌صورت خودکار و بدون Evidence روی هر Fail بازنویسی نمی‌شود؛
  • تاریخچه مالک، زمان، دلیل و تغییر مرتبط را نگه می‌دارد؛
  • برای Font/OS/Browser ناسازگار دوباره استفاده نمی‌شود؛
  • قابل بازگشت به نسخه تأییدشده پیشین است.

راهنمای Branch و Baseline در Chromatic، Baseline را آخرین State خوبِ پذیرفته‌شده برای هر Story/Mode می‌داند و توضیح می‌دهد که تاریخ Git و Branch روی انتخاب آن اثر دارد. Baseline قدیمی می‌تواند False Positive بسازد؛ پس Update‌کردن Branch و مدیریت Lineage بخشی از کیفیت تست است.

ماتریس پوشش را قبل از Screenshot بسازید

اجرای تمام ترکیب‌های ممکن، هم کند است و هم صف بررسی را پر از Diff می‌کند. از ماتریس مبتنی بر ریسک استفاده کنید:

بُعد نمونه گزینه‌ها قاعده انتخاب
State Default، Loading، Empty، Error، Success، Disabled حالت‌های پرریسک و شاخه‌های بصری متفاوت
Viewport ۳۹۰، ۷۶۸، ۱۴۴۰ پیکسل Breakpoint واقعی محصول، نه هر عرض تصادفی
Browser Chromium، WebKit، Firefox سهم کاربر و ریسک Engine/Font
Theme Light، Dark، High contrast Token و محصول پشتیبانی‌شده
Locale fa-IR، en-US جهت، متن، رقم و Format متفاوت
Data shape کوتاه، بلند، صفر، Maximum، Unicode Boundary بصری و دامنه واقعی

مستندات Modes در Chromatic نشان می‌دهد می‌توان برای Theme، Viewport، Locale یا تنظیمات دیگر Snapshotهای مستقل داشت. اصل مهم ابزار نیست: هر Mode باید Baseline و Contract خودش را داشته باشد؛ مقایسه Dark mode فارسی موبایل با Baseline دسکتاپ انگلیسی بی‌معناست.

مقایسه پیکسلی دقیق؛ سخت‌گیر اما صریح

در Exact Pixel Diff، هر پیکسل متفاوت می‌تواند Fail بسازد. مزیتش شفافیت است: الگوریتم درباره معنای تصویر ادعا نمی‌کند. عیبش حساسیت بالا به Anti-aliasing، Font rasterization، Subpixel، Shadow، Encoding و نویز رندر است.

Pixelmatch خود را کتابخانه مقایسه تصویر در سطح پیکسل معرفی می‌کند و گزینه‌هایی مانند Threshold و تشخیص Anti-aliasing دارد. Threshold آن تحمل اختلاف رنگی را تنظیم می‌کند؛ اما هیچ مقدار آن نمی‌فهمد نقطه قرمز «پرداخت ناموفق» از نظر کسب‌وکار مهم‌تر از هزار پیکسل Background است.

چه زمانی Exact مناسب‌تر است؟

برای Iconهای کوچک، خروجی Canvas کنترل‌شده، Barcode/QR آزمایشی، Componentهای کاملاً Deterministic و Regressionهایی که حتی یک پیکسل اهمیت دارد، Exact یا Threshold بسیار پایین قابل‌دفاع است. برای صفحه‌ای با Font و Rendering ناپایدار، بدون کنترل محیط معمولاً نویز زیادی می‌سازد.

Perceptual Diff دقیقاً چه می‌کند؟

مقایسه ادراکی تلاش می‌کند اختلافی را که از منظر مدل کیفیت تصویر کم‌اهمیت‌تر است، کمتر وزن دهد. این عبارت طیف بزرگی را شامل می‌شود: فاصله رنگی، مدل‌کردن Anti-aliasing، SSIM، Featureهای تصویر و مدل‌های یادگیری. پس «Perceptual» مترادف «AI» نیست و هیچ الگوریتمی صرفاً با این برچسب، Intent کاربر یا ریسک محصول را نمی‌فهمد.

مقاله اصلی Structural Similarity یا SSIM یک روش Full-reference برای ارزیابی کیفیت تصویر با مؤلفه‌های روشنایی، Contrast و Structure ارائه می‌کند. SSIM برای UI ساخته نشده تا بداند کدام Label وضعیت، هشدار یا مبلغ تجاری مهم است. استفاده از آن برای Screenshot ممکن است مفید باشد، اما تفسیر امتیاز همچنان به Context نیاز دارد.

سه سوءبرداشت رایج درباره «هوش بصری»

  • SSIM یعنی AI: خیر؛ SSIM یک شاخص ساختاری است، نه شاهد وجود Machine Learning.
  • امتیاز ادراکی بالا یعنی UI درست است: خیر؛ یک تغییر کوچک معنایی می‌تواند در کل تصویر گم شود.
  • ابزار AI خودکار اهمیت را یاد می‌گیرد: شاید یک محصول قابلیت خاصی داشته باشد، اما باید با Gold set محلی و خطاهای Seed‌شده اثبات شود، نه با متن تبلیغاتی.

آزمایش کنترل‌شده: نویز، تغییر معنایی و جابه‌جایی

برای بررسی ملموس، چهار SVG با ابعاد ۳۲۰×۱۸۰ و در مجموع ۵۷٬۶۰۰ پیکسل ساخته شد: Baseline، تغییر Background از #ffffff به #fefefe، تغییر چراغ وضعیت ۱۲ پیکسلی از سبز به قرمز و جابه‌جایی چهارپیکسلی CTA. همه فایل‌ها با یک نسخه و یک محیط به PNG رندر شدند و با دستور compare در ImageMagick سنجیده شدند.

magick baseline.svg baseline.png
magick compare -metric PDC baseline.png actual.png null:
magick compare -metric SSIM baseline.png actual.png null:

در این اجرای مشخص، PDC تعداد/نسبت پیکسل‌های متفاوت را گزارش کرد. خروجی نرمال‌شده SSIM در پرانتز به‌شکل Distortion بود؛ یعنی صفر برابر تصویر یکسان است. این قرارداد باید همراه نسخه ابزار ثبت شود و نباید با «Similarity که یک بهترین است» اشتباه گرفته شود.

Case تغییر کنترل‌شده PDC کل تصویر SSIM distortion کل
Render noise Background فقط یک واحد RGB تغییر کرد ۱۸٬۱۰۳ پیکسل؛ ۳۱٫۴۲۸۸٪ ۰٫۰۰۰۰۶۸۹۶۵
Semantic state چراغ ۱۲px سبز به قرمز ۱۴۵ پیکسل؛ ۰٫۲۵۱۷۳۶٪ ۰٫۰۰۱۲۸۹۵۱
Layout shift CTA چهار پیکسل جابه‌جا شد ۲۶۴ پیکسل؛ ۰٫۴۵۸۳۳۳٪ ۰٫۰۰۴۹۹۵۹۵

نتیجه‌ای که از این اعداد می‌توان گرفت

Exact global diff، Background تقریباً نامحسوس را روی ۳۱٪ صفحه گزارش کرد. در مقابل، چراغ وضعیت که می‌تواند معنای کسب‌وکاری را عوض کند فقط ۰٫۲۵٪ پیکسل‌های صفحه را تغییر داد. SSIM نویز Background را کم‌اهمیت تشخیص داد، اما همچنان «معنای پرداخت» را نمی‌داند. این آزمایش اثبات برتری یک Metric نیست؛ نشان می‌دهد Metric بدون Risk model و Region context ناقص است.

چرا Threshold عمومی به شما دروغ می‌گوید؟

فرض کنید Gate اجازه ۰٫۳٪ اختلاف بدهد. چراغ وضعیت قرمز با ۰٫۲۵٪ ممکن است Pass شود، اما برای Background کمی متفاوت Fail بزرگی دیده شود. اگر Threshold را ۳۵٪ کنید تا نویز عبور کند، تغییرهای واقعی بیشتری پنهان می‌شوند. Threshold سراسری، اهمیت را از «تعداد پیکسل» استنتاج می‌کند؛ درحالی‌که ریسک محصول به معنا، State و Journey وابسته است.

Threshold را چگونه کالیبره کنیم؟

  1. مجموعه‌ای از Screenshotهای Known-good با تغییرات محیطی مجاز بسازید.
  2. خرابی‌های واقعی یا Seed‌شده مانند Overlap، رنگ وضعیت، Font fallback و CTA shift اضافه کنید.
  3. Comparator و تنظیمات را روی هر دو گروه اجرا کنید.
  4. False positive و مهم‌تر از آن False negative را جداگانه بشمارید.
  5. برای Component/Region/Risk level آستانه مستقل انتخاب کنید.
  6. با تغییر Browser، Font یا Renderer دوباره کالیبره کنید.

اگر فقط تعداد Failهای مزاحم را کم کنید، می‌توانید با بالا بردن Threshold داشبورد سبزی بسازید که باگ را نمی‌بیند. Gold set باید هم نمونه مجاز و هم Defect حساس داشته باشد.

Region-aware و Risk-aware Comparison

در همان آزمایش، ناحیه ۲۰×۲۰ دور چراغ وضعیت جداگانه مقایسه شد: ۱۴۵ پیکسل یا ۳۶٫۲۵٪ ناحیه متفاوت و SSIM distortion ناحیه ۰٫۱۷۷۷۸ بود. برای ناحیه ۸۵×۴۰ دور CTA، جابه‌جایی ۲۶۴ پیکسل یا ۷٫۷۶۴۷۱٪ ناحیه و distortion برابر ۰٫۰۸۴۶۳۷۲ شد.

این اعداد نشان می‌دهند Crop/Element screenshot، نسبت سیگنال به نویز را بهتر می‌کند. بااین‌حال حتی Region score هم معنا را کامل نمی‌فهمد؛ تست باید بداند چراغ وضعیت Critical است و پس از Diff، State منطقی آن را با Assertion مستقل بررسی کند.

Region class نمونه سیاست پیشنهادی
Critical مبلغ، وضعیت پرداخت، CTA، هشدار امنیتی Element capture، تحمل پایین، Assertion معنایی مکمل
Structural Grid، Header، Modal، Form layout Pixel/Perceptual ترکیبی و بازبینی Diff
Cosmetic Shadow یا Background غیرحیاتی تحمل کالیبره‌شده، Gate نرم‌تر
Volatile Timestamp، Avatar تصادفی، تبلیغ ابتدا کنترل داده؛ Mask فقط با دلیل و مالک

State و داده را Deterministic کنید

بسیاری از Flaky Diffها مشکل Comparator نیستند؛ UI هر بار State دیگری دارد. پیش از افزودن Mask یا Delay ثابت، ورودی را مهار کنید:

  • Clock را Freeze و Timezone را صریح کنید؛
  • Random seed، UUID و ترتیب داده را ثابت کنید؛
  • API، Feature flag و نقش کاربر را Mock/Fixture یا Provision کنید؛
  • محتوای شخصی، تبلیغ، موجودی و نرخ لحظه‌ای را با سناریوی کنترل‌شده جایگزین کنید؛
  • Cache، Cookie، Service worker و Local storage را از State معلوم آغاز کنید؛
  • تصویر و Font را از Artifact نسخه‌دار و قابل‌دسترسی بارگذاری کنید.

برای طراحی داده‌های پایدار و غیرحساس، راهنمای مدیریت محیط تست، داده و Drift کمک می‌کند Contract محیط را فراتر از Screenshot ببینید.

به‌جای Sleep، برای Readiness معنی‌دار صبر کنید

waitForTimeout(3000) فقط سه ثانیه صبر می‌کند؛ تضمین نمی‌کند Font، تصویر، Transition، درخواست شبکه یا State نهایی آماده شده باشد. شرط Capture باید به رفتار محصول وصل باشد:

  • Response یا داده هدف دریافت و نمایش داده شده باشد؛
  • Loading indicator حذف و Success/Error state ظاهر شده باشد؛
  • document.fonts.ready کامل شده باشد؛
  • Imageهای ضروری decode شده باشند؛
  • Animation/Transition به State پایانی رسیده یا در محیط تست غیرفعال شده باشد؛
  • Layout برای دو Frame متوالی ثابت مانده باشد؛
  • Caret، Cursor، Hover و Focus مطابق Scenario تنظیم شده باشند.

Retry درمان ریشه‌ای نیست

دو Screenshot متوالی می‌تواند نوسان گذرا را کاهش دهد، اما Retry بی‌حد ممکن است یک Race واقعی را پنهان کند. تعداد Attempt، علت اختلاف و تصویر هر Attempt را نگه دارید. تستی که «بالاخره» سبز می‌شود هنوز ممکن است Signal ضعیفی داشته باشد.

Environment Manifest تصویری بنویسید

مرجع و Actual فقط وقتی قابل‌مقایسه‌اند که قرارداد محیط معلوم باشد. حداقل Manifest باید این فیلدها را ثبت کند:

app_build: web-2026.08.11+9f32a1
browser: chromium 140.x
os_image: ubuntu-24.04-ui-v3
viewport: 390x844
device_scale_factor: 1
locale: fa-IR
timezone: Asia/Tehran
color_scheme: light
reduced_motion: reduce
font_bundle: vazirmatn-33.0.3@sha256:...
scenario_data: checkout-failed-v4
comparator: pixelmatch@x.y / threshold=...
baseline_commit: ...

ثبت عبارت «Chrome latest» کافی نیست، چون Latest حرکت می‌کند. Container image، نسخه Browser و Font bundle را Pin کنید و ارتقا را به‌صورت تغییر کنترل‌شده با Recalibration انجام دهید.

Font، DPR و Color سه منبع پرنویز هستند

Font

اگر فونت فارسی دیر برسد یا Fallback شود، عرض حروف، Line break و ارتفاع بلوک عوض می‌شود. وجود فایل در Repository کافی نیست؛ باید بارگذاری موفق، Family/Weight مورد انتظار و Hash Artifact کنترل شود. فونت‌های ی و ک عربی/فارسی نیز ممکن است Glyph و عرض متفاوتی بسازند.

Device Pixel Ratio

Viewport CSS با Resolution تصویر یکی نیست. DPR متفاوت روی Rasterization و اندازه Screenshot اثر می‌گذارد. DPR را بخشی از Mode بدانید و Baseline را بین DPRهای ناسازگار مشترک نکنید.

Color و Rendering

Color profile، GPU، Headless mode و نسخه Engine ممکن است اختلاف بسازند. به همین دلیل اجرای Baseline روی لپ‌تاپ طراح و Actual روی Runner لینوکسی بدون Contract، معمولاً مقایسه عادلانه‌ای نیست.

Mask آخرین انتخاب است، نه اولین واکنش

Mask می‌تواند Timestamp یا محتوای واقعاً خارج از Scope را حذف کند، اما هر مستطیل سیاه یک نقطه کور می‌سازد. ترتیب درست رفع نویز چنین است:

  1. داده و Clock را کنترل کنید؛
  2. برای State واقعی صبر کنید؛
  3. Animation و Cursor را کنترل کنید؛
  4. Capture را به Element/Region محدود کنید؛
  5. Comparator و Threshold را با Gold set کالیبره کنید؛
  6. فقط بخش واقعاً نامرتبط را با دلیل، مالک و تاریخ بازبینی Mask کنید.

Mask کردن کل جدول چون یک ستون زمان دارد، می‌تواند حذف‌شدن ستون مبلغ را هم پنهان کند. بهتر است Timestamp را Fixture کنید یا فقط Cell ناپایدار را هدف بگیرید.

نمونه Playwright برای Screenshot قابل‌توضیح

نمونه زیر قصد نسخه آماده Production ندارد؛ الگوی Contract را نشان می‌دهد. Locatorها و Waitها باید با محصول واقعی هماهنگ شوند:

import { test, expect } from '@playwright/test';

test('failed payment card — fa-IR mobile', async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-08-11T08:00:00Z'));
  await page.goto('/checkout/demo?fixture=failed-payment-v4');

  const card = page.getByTestId('payment-result');
  await expect(card).toHaveAttribute('data-state', 'failed');
  await page.evaluate(() => document.fonts.ready);

  // Oracle معنایی: رنگ به‌تنهایی حامل حقیقت نیست.
  await expect(card.getByRole('status')).toContainText('پرداخت ناموفق');

  // Oracle بصری: شکل رندر ناحیه Critical تغییر نکرده باشد.
  await expect(card).toHaveScreenshot('failed-payment-fa-mobile.png', {
    animations: 'disabled',
    maxDiffPixels: 12,
  });
});

Playwright از toHaveScreenshot()، maxDiffPixels و Style اختصاصی هنگام Capture پشتیبانی می‌کند. مقدار ۱۲ در این مثال توصیه عمومی نیست؛ باید از Gold set همان Component و محیط به دست آید. برای معماری Fixture و Locator پایدار، راهنمای کد تست قابل نگهداری با Playwright را ببینید.

Baseline Governance؛ Update Snapshot مساوی Fix نیست

دستور Update Snapshot یک عملیات فنی است، نه تصمیم کیفیت. اگر توسعه‌دهنده پس از هر Fail بدون دیدن Diff مرجع را عوض کند، تست به ثبت‌کننده آخرین خروجی تبدیل می‌شود. برای هر Update حداقل این اطلاعات لازم است:

  • Story/Scenario و Mode؛
  • Baseline قبلی، Actual و Diff؛
  • تغییر عمدی یا Ticket/Design مرتبط؛
  • نام Reviewer مجاز برای ناحیه؛
  • Commit و Branch؛
  • اثر روی Theme/Locale/Viewportهای دیگر؛
  • دلیل پذیرش و امکان Rollback.

تفکیک نقش‌ها بر اساس ریسک

برای Component کم‌خطر، Peer review مهندسی ممکن است کافی باشد. برای قیمت، وضعیت مالی، Consent یا هشدار امنیتی، Product/Design/Domain owner باید تغییر را ببیند. الزام دو امضا برای همه Diffها نیز صف را فلج می‌کند؛ Approval را Risk-based کنید.

Diff Triage؛ هر Fail را طبقه‌بندی کنید

هدف Triage فقط سبزکردن Pipeline نیست؛ باید منبع Signal مشخص شود. Taxonomy پیشنهادی:

کلاس نشانه اقدام
Product defect تغییر ناخواسته و قابل‌بازتولید Bug، Fix، Retest؛ Baseline حفظ شود
Intentional change با Design/Ticket و Scope منطبق بازبینی Modes و سپس Baseline approval
Fixture/Data State یا متن هر اجرا فرق دارد Contract داده اصلاح شود
Capture/Environment Font، Animation، Browser یا Timing Manifest/Readiness تثبیت شود
Comparator policy Noise شناخته‌شده یا Defect از دست‌رفته Gold set و Region threshold بازکالیبره شود
Stale/Wrong baseline Branch یا Mode اشتباه Lineage اصلاح؛ پذیرش کور انجام نشود
Duplicate یک Root cause در ده‌ها Snapshot گروه‌بندی و یک Fix، Retest همه Impactها
Unknown Evidence کافی نیست تصمیم متوقف و Artifact کامل شود

راهنمای تست اکتشافی ساختاریافته برای Diffهای ناشناخته مفید است: یک Charter کوتاه درباره «چه چیزی تغییر کرده و چه فرضیه‌هایی آن را توضیح می‌دهند» بسازید و شواهد را هدفمند جمع کنید.

Evidence Pack یک Visual Fail

گزارش «Screenshot فرق کرده» برای Debug کافی نیست. Artifact پیشنهادی:

  • Baseline، Actual، Diff heatmap و Overlay/Slider؛
  • نام Test، Story، State و Mode؛
  • Environment Manifest و Comparator config؛
  • Build، Commit، Branch و Baseline lineage؛
  • Console، Network و Trace مرتبط؛
  • DOM/Computed style ناحیه مشکوک؛
  • ARIA snapshot یا Accessibility tree برای معنای مکمل؛
  • Triage class، Owner، Decision و دلیل Approval/Reject.

ARIA Snapshot در Playwright می‌تواند ساختار قابل‌دسترسی را به‌صورت متن/Template بررسی کند. این خروجی جایگزین تست دستی صفحه‌خوان یا همه معیارهای Accessibility نیست، اما کنار تصویر کمک می‌کند بفهمیم Component فقط ظاهرش عوض شده یا Role/Name/Structure معنایی آن نیز تغییر کرده است.

سناریوی ایرانی: Checkout فارسی و پرداخت ناموفق

فرض کنید کارت نتیجه پرداخت باید در موبایل فارسی این اطلاعات را نمایش دهد: وضعیت ناموفق، مبلغ ۱٬۲۵۰٬۰۰۰ تومان، شناسه پیگیری، زمان و دکمه «تلاش دوباره». Riskها فقط Responsive نیستند:

  • Backend مقدار را به ریال نگه می‌دارد اما UI تومان نشان می‌دهد؛
  • ارقام فارسی، عربی و لاتین طول و Direction متفاوت می‌سازند؛
  • ی/ي و ک/ك یا ZWNJ باعث Font fallback/Wrap می‌شوند؛
  • شناسه لاتین در متن RTL ترتیب دیداری را می‌شکند؛
  • زمان UTC باید برای نمایش Asia/Tehran تبدیل شود؛ تقویم جلالی یک Concern جداست؛
  • نام پذیرنده بلند روی CTA یا مبلغ می‌افتد؛
  • وضعیت «ناموفق» به اشتباه سبز است ولی متن درست مانده؛
  • Screenshot حاوی شماره موبایل، Token یا شناسه واقعی می‌شود.

طراحی Suite این سناریو

  1. سه Story کامپوننتی برای Success/Failed/Pending با مبلغ و نام Boundary؛
  2. Modeهای fa-IR در ۳۹۰ و ۱۴۴۰ پیکسل، Light/Dark؛
  3. Assertion عملکردی برای State code، مبلغ Canonical و عمل CTA؛
  4. Assertion معنایی برای Role/Status و متن؛
  5. Element screenshot کارت با Region Critical برای مبلغ و وضعیت؛
  6. E2E محدود برای اطمینان از Integration صفحه؛
  7. Fixture مصنوعی و Redacted؛ بدون PAN، OTP، Token یا کاربر واقعی؛
  8. Baseline approval توسط مالک Checkout برای تغییرهای مالی.

برای پوشش دستگاه و Lifecycle واقعی، ماتریس را با راهنمای تست اپلیکیشن موبایل Android و iOS تکمیل کنید؛ Screenshot مرورگر دسکتاپ به‌تنهایی نماینده WebView، Safe area، Keyboard و Font دستگاه نیست.

Visual Testing و Accessibility دو Oracle متفاوت‌اند

Diff می‌تواند حذف Outline یا Contrast آشکار را ببیند، اما نمی‌فهمد ترتیب Focus منطقی است، Label به Input وصل است یا Live region اعلام می‌شود. از طرف دیگر، اسکن Accessibility ممکن است Overlap یا متن بریده را نبیند. WCAG 2.2 معیارهایی مانند Reflow، Non-text contrast، Focus appearance، Use of color و ساختار معنایی را جداگانه تعریف می‌کند؛ هر معیار به Test method و Evidence مناسب نیاز دارد.

قاعده عملی: اگر معنای وضعیت فقط با رنگ منتقل می‌شود، Visual Test شاید تغییر رنگ را ببیند، اما Oracle دسترس‌پذیری باید وجود متن/Icon/Name مناسب را نیز بررسی کند. «تصویر همان قبلی است» شاهد Accessibility کامل نیست.

حریم خصوصی و امنیت Screenshot

Screenshot یک Artifact داده است. ممکن است نام، موبایل، آدرس، موجودی کیف پول، شناسه سفارش، پیام خصوصی یا Secret را ثبت کند و در CI خارجی، Object storage یا Comment روابط‌عمومی منتشر شود. کنترل‌های ضروری:

  • Fixture مصنوعی و حداقل داده؛
  • مسدودکردن Production account و Production API؛
  • Redaction پیش از Upload، نه پس از Leak؛
  • Access control، Retention و حذف Artifact؛
  • رمزنگاری انتقال/ذخیره و منع Public link؛
  • بازبینی Vendor، Region و Contract داده؛
  • عدم ذخیره Cookie، Token، Header یا Trace حساس کنار تصویر؛
  • ثبت اینکه Mask برای Privacy است یا Noise؛ چون آثار کیفیت متفاوت‌اند.

اگر Artifact در Bug tracker قرار می‌گیرد، همان سیاست داده باید آن‌جا نیز اجرا شود. Debug value توجیهی برای نگه‌داری نامحدود اطلاعات واقعی نیست.

Visual Testing در CI/CD؛ چند Lane بسازید

قرار دادن تمام صفحات، Browserها و Viewportها روی هر Commit معمولاً Feedback را کند و Review را سطحی می‌کند. Portfolio چندلایه بهتر است:

Lane Scope زمان هدف Gate
Local/changed component Storyهای تغییرکرده و Mode اصلی چند دقیقه هشدار سریع توسعه‌دهنده
PR Componentهای Impacted + Critical pages کوتاه و قابل‌Review Diff حل‌نشده Merge را متوقف کند
Nightly Browser/Viewport/Theme/Locale گسترده‌تر ساعتی Triage تا SLA تعیین‌شده
Release Journeyهای مالی/حیاتی و Upgradeهای محیط برنامه‌ریزی‌شده Approval مبتنی بر ریسک

همه Snapshotها باید به Commit و Artifact نسخه‌دار متصل باشند. اصول Quality Gate و Feedback سریع در راهنمای Continuous Testing در CI/CD و پیاده‌سازی Artifact/Runner در تست خودکار GitLab CI با جزئیات آمده است.

Gate را بر اساس Risk و Evidence تعریف کنید

یک Gate خوب بین «Comparator تغییر دید» و «تصمیم بازبینی شد» فرق می‌گذارد:

  • Snapshot جدید بدون Owner و Reason نباید خودکار Baseline شود؛
  • Diff حل‌نشده Critical، Gate سخت است؛
  • Diff Cosmetic می‌تواند با SLA بازبینی Gate نرم باشد؛
  • Environment mismatch باید Inconclusive/Infrastructure باشد، نه Product fail؛
  • Missing baseline باید وضعیت مستقل داشته باشد، نه Pass خاموش؛
  • Comparator/config change باید Recalibration و Approval داشته باشد؛
  • Flake شناخته‌شده باید Owner و Deadline داشته باشد؛ Ignore دائمی نیست.

متریک‌هایی که واقعاً سلامت Suite را نشان می‌دهند

تعداد Screenshot یا درصد Pass به‌تنهایی معیار خوبی نیست. مجموعه متعادل‌تری بسازید:

  • Seeded-defect recall: چند خرابی Gold set واقعاً شناسایی شد؟
  • Known-good false-positive rate: چند اجرای مجاز بی‌دلیل Fail شد؟
  • Median time to triage: از Diff تا Classification چقدر طول کشید؟
  • Unknown rate: چند Fail Evidence ناکافی داشت؟
  • Blind baseline update rate: چند Update بدون Reviewer/Reason انجام شد؟
  • Repeat disagreement: اجرای تکراری یک Commit چند بار نتیجه متفاوت داد؟
  • Critical-mode coverage: Riskهای مهم چند State/Mode معتبر دارند؟
  • Escaped visual defects: باگ Production از کدام Gap عبور کرد؟
  • Review load: Diffهای بازبینی‌شده به ازای تغییر معنادار؛
  • Baseline age/lineage health: مرجع‌های قدیمی یا بی‌مالک.

این متریک‌ها را برای تنبیه فرد یا تیم استفاده نکنید؛ در غیر این صورت افراد Threshold را بالا می‌برند، Snapshot را حذف یا سریع Accept می‌کنند تا عدد سبز شود.

ابزار تست بصری را چگونه انتخاب کنیم؟

فهرست بلند Vendorها جای Requirement را نمی‌گیرد. ابتدا این پرسش‌ها را پاسخ دهید:

  1. Component، E2E، Mobile native یا چند مورد را پوشش می‌دهیم؟
  2. Capture در Runner خودمان است یا Cloud؟ داده کجا می‌رود؟
  3. Browser/OS/Font محیط چقدر قابل‌Pin است؟
  4. Branch-aware baseline و تاریخچه Approval لازم است؟
  5. Diff inspector، Overlay، Comment و Assignment داریم؟
  6. Region/Mask/Threshold/Perceptual تنظیم‌پذیر است؟
  7. Storybook، Playwright و CI فعلی چگونه یکپارچه می‌شوند؟
  8. قیمت بر اساس Snapshot، Parallelism یا User چگونه رشد می‌کند؟
  9. Export، Retention و Vendor lock-in چه وضعی دارد؟
  10. PoC روی Gold set محلی چه Recall/Noise و زمان Triage دارد؟

نقش ابزارها را مخلوط نکنید

  • Test runner/Capture: رسیدن به State و گرفتن Screenshot؛
  • Comparator: تولید Metric و Diff؛
  • Baseline store: نسخه، Branch و Mode؛
  • Review workflow: نمایش، Comment، Owner و Approval؛
  • CI orchestrator: Trigger، Artifact، Gate و Retention؛
  • Observability/trace: Evidence برای Root cause.

یک SaaS ممکن است چند نقش را یکجا ارائه کند؛ باز هم باید بدانید هر تصمیم در کدام لایه گرفته می‌شود و آیا می‌توان Evidence را خارج کرد.

آیا AI در تست بصری مفید است؟

مدل‌های ML می‌توانند برای گروه‌بندی Diffهای مشابه، اولویت‌بندی، تشخیص ناحیه، حذف نویز یا پیشنهاد علت مفید باشند. اما خروجی آن‌ها Candidate است. پیش از اعتماد باید روی داده محصول خودتان ارزیابی شوند:

  • Gold set شامل تغییرهای مجاز و Defectهای کوچک/معنایی؛
  • Recall جداگانه برای Critical regions؛
  • عملکرد روی فارسی، RTL، Dark mode و Font واقعی؛
  • Drift پس از Upgrade Browser/Design system؛
  • توضیح/Artifact قابل بازبینی و امکان Override؛
  • حریم خصوصی Screenshot و محل پردازش؛
  • عدم Auto-approval برای تغییر پرریسک.

برای سیاست استفاده، ارزیابی و کنترل خروجی AI در کل فرایند QA، مقاله هوش مصنوعی در تست نرم‌افزار را ببینید. این مقاله Visual Testing را به‌عنوان مسئله Baseline/Comparator/Triage نگه می‌دارد و ادعا نمی‌کند هر Perceptual Diff هوش مصنوعی است.

برنامه ۳۰روزه پیاده‌سازی Visual Regression

هفته اول: Scope و Baseline کوچک

  • سه Journey پرریسک و ۱۰ تا ۲۰ Component پرتغییر انتخاب کنید؛
  • Risk، State و Mode را ثبت کنید؛
  • Runner و Environment Manifest اولیه را Pin کنید؛
  • Baselineها را با بازبینی مشترک QA/Frontend/Design بسازید.

هفته دوم: ثبات و Gold set

  • Clock، Font، API Fixture، Animation و Readiness را کنترل کنید؛
  • Known-good variationها و ۱۰ Defect Seed‌شده بسازید؛
  • Threshold/Comparator را برای Regionهای مختلف کالیبره کنید؛
  • Flake را Root-cause کنید؛ با Retry پنهان نکنید.

هفته سوم: Review و CI

  • Triage taxonomy، Owner و SLA تعریف کنید؛
  • PR lane سریع و Nightly matrix راه بیندازید؛
  • Artifact و Baseline lineage را به Commit وصل کنید؛
  • Critical diff را Gate و Cosmetic diff را Risk-based کنید.

هفته چهارم: ارزیابی و گسترش

  • False positive، Seeded recall، Review time و Escaped defects را اندازه بگیرید؛
  • Snapshotهای کم‌ارزش و Duplicate را حذف/ادغام کنید؛
  • فارسی RTL، Mobile و Dark mode را بر اساس Risk اضافه کنید؛
  • Runbook ارتقای Browser/Font و بازکالیبراسیون را مستند کنید.

خطاهای رایج در تست رگرسیون بصری

  1. Screenshot از همه‌چیز: حجم بالا، Review سطحی و Signal پایین.
  2. یک Threshold برای کل محصول: نادیده‌گرفتن اندازه، Region و Risk.
  3. Baseline به‌عنوان حقیقت: تکثیر باگ تأییدنشده.
  4. Update خودکار روی Fail: حذف Oracle واقعی.
  5. Sleep ثابت: Capture قبل یا بعد از State هدف.
  6. Mask وسیع: ساخت نقطه کور برای Defect آینده.
  7. مقایسه محیط‌های متفاوت: نسبت‌دادن Drift محیط به Product.
  8. فقط Full-page: گم‌شدن تغییر معنایی کوچک در امتیاز کل.
  9. تصویر بدون Assertion: ناتوانی در سنجش رفتار و Semantics.
  10. AI به‌عنوان داور نهایی: پذیرش False negative بدون Gold set.
  11. Screenshot واقعی مشتری: نشت PII در Artifact.
  12. Pass rate به‌عنوان موفقیت: تشویق Threshold بالا و Approval کور.

چک‌لیست عملی تست بصری

  • □ سؤال و Risk تست نوشته شده است.
  • □ State و Test data نسخه‌دار و قابل‌بازتولیدند.
  • □ Viewport، DPR، Browser، OS، Theme، Locale و Font مشخص‌اند.
  • □ Readiness به State معنی‌دار وصل است، نه Sleep تصادفی.
  • □ Capture در کوچک‌ترین Scope مفید انجام می‌شود.
  • □ Baseline تأیید، مالک، Commit، Branch و Mode دارد.
  • □ Comparator/Threshold روی Known-good و Seeded defects کالیبره شده است.
  • □ Critical regions سیاست مستقل و Oracle مکمل دارند.
  • □ Mask حداقلی، دلیل‌دار و قابل‌بازبینی است.
  • □ Baseline، Actual، Diff و Config در Artifact می‌مانند.
  • □ Triage class، Owner و SLA تعریف شده‌اند.
  • □ Update Snapshot بدون مشاهده و دلیل ممکن نیست.
  • □ Accessibility و Functional assertions جداگانه اجرا می‌شوند.
  • □ Screenshot فاقد داده واقعی/Secret و دارای Retention است.
  • □ PR، Nightly و Release lane بر اساس Risk تقسیم شده‌اند.
  • □ False negative با Defectهای Seed‌شده اندازه‌گیری می‌شود.

سؤالات متداول تست بصری

تست بصری یا Visual Testing چیست؟

روشی برای بررسی خروجی رندرشده UI است که معمولاً Screenshot تازه را با Baseline تأییدشده مقایسه و Diff را برای بازبینی تولید می‌کند. یک تست خوب علاوه بر تصویر، State، محیط، Mode، Comparator و فرایند Approval را نیز مشخص می‌کند.

آیا Perceptual Diff همان هوش مصنوعی است؟

خیر. Perceptual Diff یک خانواده روش است و می‌تواند از فاصله رنگی، Anti-alias handling یا شاخصی مانند SSIM استفاده کند؛ این‌ها لزوماً AI/ML نیستند. هر قابلیت یادگیری نیز باید با Gold set محلی و Defectهای پرریسک ارزیابی شود.

Threshold مناسب برای مقایسه Screenshot چقدر است؟

عدد جهانی معتبری وجود ندارد. مقدار مناسب به اندازه تصویر، Font/Renderer، نوع Component، Comparator، Region و ریسک بستگی دارد. آن را با Known-good variation و Defectهای Seed‌شده کالیبره و پس از تغییر محیط دوباره بررسی کنید.

آیا هر Visual Diff باید Build را Fail کند؟

خیر؛ هر Diff باید بررسی شود، اما سیاست Gate می‌تواند مبتنی بر ریسک باشد. تغییر حل‌نشده در مبلغ، وضعیت پرداخت یا CTA باید سخت‌گیرانه‌تر از Shadow کم‌خطر باشد. Environment mismatch نیز بهتر است Inconclusive/Infrastructure طبقه‌بندی شود.

چطور Flaky Visual Test را کم کنیم؟

ابتدا Clock، داده، Font، Browser/OS، DPR، Animation و Readiness را کنترل کنید؛ سپس Scope را به Element/Region محدود و Comparator را کالیبره کنید. Mask و Retry باید آخرین ابزار و همراه دلیل/Metric باشند، نه واکنش اولیه.

جمع‌بندی

تست رگرسیون بصری زمانی قابل‌اعتماد است که Screenshot را از یک فایل تزئینی به Evidence نسخه‌دار تبدیل کنید. Baseline باید مالک و تاریخچه داشته باشد؛ Capture باید Deterministic باشد؛ Comparator باید با خطاهای واقعی کالیبره شود؛ Regionهای حیاتی باید وزن و Oracle مستقل داشته باشند؛ و هر Diff باید از مسیر Triage و Approval عبور کند.

آزمایش این راهنما نشان داد تغییر تقریباً نامحسوس Background می‌تواند ۳۱٪ پیکسل‌های صفحه را عوض کند، درحالی‌که تغییر کوچک اما معنایی وضعیت فقط ۰٫۲۵٪ کل تصویر است و در ناحیه خودش ۳۶٫۲۵٪. نتیجه عملی روشن است: نه تعداد پیکسل و نه امتیاز ادراکی به‌تنهایی معنای محصول را نمی‌فهمند. Visual Testing خوب ترکیبی از محیط کنترل‌شده، مقایسه مناسب، Context ناحیه، Assertionهای مکمل و قضاوت قابل‌ممیزی تیم است.

دیدگاهتان را بنویسید