وب مدرن، رابط کاربری و تجربهی وب · فصل ۴ — React، State و Visual Regression · 47
Component Catalog و Visual Regression
میتواند «Component Catalog و Visual Regression» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
خلاصهٔ درس
میتواند «Component Catalog و Visual Regression» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
خروجی این درس
میتواند «Component Catalog و Visual Regression» را در یک feature محدود طراحی/پیادهسازی کند و correctness، accessibility یا failure behavior مرتبط را اثبات کند.
سبز شدن Screenshot Test لزوماً یعنی محصول درست نیست
یک component ممکن است در یک screenshot کاملاً درست دیده شود، اما قرارداد واقعی آن ناقص باشد. ممکن است catalog فقط happy path را نشان دهد و stateهای loading، error، عنوان بلند، focus-visible یا حالتهای حساس به جهت و محتوا اصلاً story نداشته باشند. ممکن است اولین screenshot بدون review به baseline تبدیل شود و همان خطای اولیه در آینده بهعنوان «وضعیت درست» محافظت شود. از طرف دیگر ممکن است تست در هر اجرا fail شود چون time، animation، font، viewport یا random data پایدار نشدهاند. حتی zero-pixel diff هم interaction، semantics، accessibility یا business rule را بهتنهایی ثابت نمیکند.
WEB-018 Visual Regression را به یک قرارداد evidence تبدیل میکند. Fixture محدود درس یک reusable card است. باید stateهای معنادار آن را در یک Component Catalog قابل بازرسی تعریف کنید، baseline را فقط بعد از review بهعنوان expectation بپذیرید، محیط render را تا حد لازم پایدار کنید، screenshotها را مقایسه کنید، diff را به expected change، regression یا noise طبقهبندی کنید و صریح بگویید visual oracle چه چیزهایی را ثابت نمیکند. پایان درس تولید صرف چند تصویر نیست؛ خروجی اصلی Component Catalog & Visual Regression Evidence Record v1 است.
مهارت ماندگار این درس «Storybook نصب کن» یا «screenshot test بنویس» نیست. مهارت اصلی این است که state inventory را از requirement استخراج کنید، ورودیهای ناپایدار را کنترل کنید، تغییر baseline را governed کنید و درباره diff تصمیم مهندسی بگیرید بدون اینکه pixel را oracle کامل correctness فرض کنید.
محدوده، مسیر یادگیری و معیار Evidence
این درس روی پیشنیاز WEB-011 سوار است. Story در اینجا مفید است چون یک rendered state پشتیبانیشده را addressable و تکرارپذیر میکند. WEB-012 حوزه جداگانه دارد: اگر card از server data یا URL state استفاده کند، ownership آن داده مهم است، اما این درس query cache یا router را دوباره طراحی نمیکند. تمرکز اینجا catalog و visual evidence است.
اگر component explorer و baseline تصویری برایتان جدید است، مسیر foundation را طی کنید. اگر story مینویسید ولی اولین snapshot را بهراحتی قبول میکنید یا دائماً با noisy diff درگیر هستید، مسیر repair مناسب است. اگر تجربه دارید، syntax را فشرده کنید و مستقیم روی coverage، determinism، baseline governance و transfer کار کنید. هر سه مسیر باید به شش بعد evidence برسند: implementation artifact، behavior تصویری/browser مشاهدهشده، mutation، repair، transfer و residual limitation.
Lab محلی و non-destructive است. یک story inventory و harness مبتنی بر browser دارد. Public checker میتواند structure و schema را بررسی کند، اما observation نمیسازد. هر browser evidence فقط بعد از اجرای واقعی harness و مشاهده نتیجه باید وارد record شود.
Mental Model: requirement inventory → stories → stable render → reviewed baseline → diff → decision
Visual Regression زمانی قابل اعتماد است که هر مرحله مالک و boundary روشن داشته باشد.
- Requirement/State Inventory: از stateهای محصول شروع کنید، نه از storyهایی که اتفاقی از قبل وجود دارند.
- Stories: برای stateهای مهم render input تکرارپذیر بسازید؛ در fixture این درس default، loading، error، long-title و focus-visible هستند.
- Stabilized Render Environment: browser، viewport، device scale، font/data و inputهای volatile مانند زمان، random و animation را تا حد لازم pin یا freeze کنید.
- Reviewed Baseline: reference screenshot expectation تاییدشده برای یک state و environment مشخص است؛ اولین خروجی خودکار حقیقت محصول نیست.
- Screenshot Comparison: تصویر current را در همان environment qualified تولید و اختلاف visible را محاسبه کنید.
- Diff Classification: با requirement و context تصمیم بگیرید تغییر intentional است، regression است یا noise.
- Action + Non-visual Evidence: baseline را با دلیل approve کنید، regression را fix کنید یا منبع noise را حذف کنید؛ سپس evidence مربوط به interaction/accessibility/business را جدا نگه دارید.
در این مدل دو failure boundary مهم دیده میشود. نبودن story یعنی coverage قبل از مرحله screenshot ناقص است. ناپایداری environment یعنی کیفیت evidence پایین است حتی اگر component code درست باشد.
Foundation: پنج قراردادی که نباید با هم قاطی شوند
۱. Story Coverage Contract
Catalog صرفاً لیست فایلها نیست؛ inventory از stateهای معنادار و پشتیبانیشده است. مستندات فعلی Storybook، story را rendered state یک UI component معرفی میکند و برای stateهای جالب/مهم چند story را توصیه میکند. این به معنی story ساختن برای تمام permutationهای نظری نیست. Coverage باید از risk و requirement پیروی کند.
در card این درس پنج state اجباری داریم: Default، Loading، Error، LongTitle و FocusVisible. این مجموعه عمداً محدود است. Loading و Error مسیر status را میسنجند. LongTitle wrapping و spacing را تحت فشار میگذارد. FocusVisible یک state بصری مرتبط با استفاده keyboard را قابل capture میکند. اگر محصول واقعی بعداً selected، disabled، RTL، compact یا dark theme داشته باشد، فقط وقتی وارد inventory میشوند که contract محصول آنها را لازم بداند.
سؤال review مفید این است: «کدام failure پشتیبانیشده میتواند رخ دهد بدون اینکه هیچ story تغییر یا fail کند؟» اگر جواب یک state واقعی است، catalog gap دارد.
۲. Baseline Authority Contract
اولین image که tool تولید میکند فقط میگوید environment چه چیزی render کرده است؛ نمیگوید محصول باید چه چیزی render کند. Baseline وقتی authority میگیرد که یک انسان یا process governed آن را با requirement مقایسه و دلیل پذیرش را ثبت کند.
Evidence Record باید baseline status، environment signature، stateهای reviewشده و reason تغییر را نگه دارد. اگر redesign intentional spacing را عوض کرده، update baseline بعد از review درست است. اگر developer تصادفی padding را حذف کرده، زدن گزینه Accept فقط regression را به expectation جدید تبدیل میکند. تفاوت در تعداد pixel نیست؛ در authority requirement است.
Baseline update نباید عملیات پاککردن failure باشد؛ باید change قابل review باشد.
۳. Visual Determinism Contract
Screenshot comparison فقط زمانی قابل تفسیر است که runها به اندازه کافی comparable باشند. مستندات فعلی Playwright هشدار میدهد host OS، browser/version، settings، hardware، headless mode، font و عوامل دیگر میتوانند rendering را تغییر دهند. بنابراین lab environment خود را ثبت میکند و ادعای portability جهانی ندارد.
داخل همان environment، volatility قابل کنترل را حذف کنید. Fixture data ثابت باشد. اگر زمان دیده میشود clock را freeze کنید. Random را seed یا حذف کنید. Animation را غیرفعال یا به state پایدار ببرید. Viewport و device scale ثابت باشد. فرض font صریح باشد. قبل از capture منتظر steady state مورد انتظار بمانید.
Determinism یعنی «در همه کامپیوترها هر pixel یکسان است» نیست. یعنی محیط تست آنقدر qualified شده که diff برای یک نتیجه محدود مهندسی قابل استفاده باشد.
۴. Diff Judgment Contract
Visual diff signal است، verdict نیست. Pixelهای تغییرکرده میتوانند سه معنای متفاوت داشته باشند.
- Expected Change: requirement تاییدشده عوض شده، تصویر هم مطابق آن عوض شده و baseline جدید باید با دلیل approve شود.
- Regression: requirement عوض نشده ولی implementation drift کرده؛ مثل spacing غلط، clipping، رنگ اشتباه یا focus indicator حذفشده.
- Noise: contract محصول تغییر معنادار نکرده، اما clock زنده، frame animation، mismatch font/environment یا random data تصویر را عوض کرده است.
کار ما classification با context است. Reject کورکورانه زمان را روی noise تلف میکند. Accept کورکورانه regression را به baseline جدید تبدیل میکند.
۵. Oracle Boundary Contract
Pixel به سؤال visual جواب میدهد. تمام correctness را اثبات نمیکند. Screenshot میتواند focus ring را نشان دهد، اما ثابت نمیکند Tab واقعاً control را با order درست پیدا میکند. متن ممکن است شبیه heading باشد، اما semantic heading بودن از تصویر معلوم نیست. Error message ممکن است دیده شود، اما business rule ایجادکننده آن هنوز نیاز به evidence جدا دارد.
Visual evidence را کنار interaction، semantic/accessibility و business evidence قرار دهید، نه بهجای آنها. WEB-018 فقط وقتی کامل است که حداقل یک residual limitation در record نوشته شود.
توضیح عمیق: Visual Systemها چگونه بیسر و صدا خراب میشوند
بسیاری از شکستهای Visual Testing از shortcut فرایندی میآیند نه الگوریتم image diff. Shortcut اول story-by-example است: تیم فقط Default و شاید Loading را مینویسد و میگوید «component پوشش دارد». Error یا long-content نامرئی میماند چون state بدون story اصلاً قابل capture نیست.
Shortcut دوم baseline-by-generation است. Tool میگوید baseline وجود ندارد، screenshot تولید میشود و همان فایل commit میشود. این کار history ساخته، نه correctness. اگر اولین تصویر از ابتدا clipping داشته باشد، تستهای بعدی همان clipping را با دقت محافظت میکنند.
Shortcut سوم tolerance inflation است. وقتی animation یا font drift noise میسازد، ممکن است threshold را آنقدر بالا ببریم تا تست ساکت شود. Threshold برای variance شناختهشده قابل استفاده است، اما اگر مشکل input ناپایدار باشد، اول باید input را stabilize کرد. Comparison را فقط بعد از فهم variability تنظیم کنید.
Shortcut چهارم universal کردن یک environment است. «در Chromium/Linux پاس شد» دقیق است. «در همه محیطها visual درست است» بیشادعا است. Baseline به environment تولیدشده وابسته است.
Shortcut پنجم یکی دانستن visual sameness و behavioral sameness است. ممکن است refactor pixel را ثابت نگه دارد ولی keyboard activation را بشکند. برعکس، تغییر rendering engine ممکن است anti-aliasing را عوض کند در حالی که behavior و appearance intended هنوز قابل قبول است. بین raw diff و product decision، judgment مهندسی لازم است.
مثال کامل: Card قابل استفاده مجدد با پنج State
Fixture هدایتشده cardی دارد با title، body/status و action button. پنج story بسازید.
Default عنوان کوتاه و content عادی دارد و spacing/typography معمول را نشان میدهد. Loading content را با skeleton/status پایدار جایگزین میکند؛ در capture نباید shimmer متحرک آزاد باشد مگر اینکه animation عمداً freeze شده باشد. Error presentation خطا را در محدوده component نشان میدهد. LongTitle متنی طولانی دارد تا wrapping و vertical rhythm را آزمایش کند. FocusVisible action را با focus treatment مورد انتظار render میکند.
قبل از baseline، environment را ثبت کنید: Chromium، viewport ثابت 900×640، device scale برابر 1، fixture data ثابت، clock پایدار، animation-disabled policy و فرض font. تصاویر را تولید و هر پنج state را بازبینی کنید. فقط بعد از مقایسه با requirement، baseline status را reviewed/approved کنید.
حالا regression واقعی بسازید: padding card را از token تاییدشده به مقدار دیگری تغییر دهید. Screenshot جدید باید geometry component را تغییر دهد. چون requirement و baseline authority ثابت ماندهاند، diff را regression طبقهبندی کنید، token را repair کنید و دوباره اجرا کنید تا به expectation تاییدشده برگردید.
بعد noise بسازید: timestamp زنده یا random value را وارد capture کنید. دو run پیاپی میتوانند با component code یکسان متفاوت شوند. این را noise بنامید، data/time را freeze کنید و دوباره run بگیرید. برای هر frame جدید baseline نسازید.
در Record، observation و inference جداست. «بعد از padding mutation، 8412 pixel تغییر کرد» observation است. «با توجه به ثابت بودن requirement، این تغییر spacing regression است» inference است.
Failure Probeها: سیستم باید ثابت کند که Failure را میبیند
Mutation A — حذف یک Story مهم
LongTitle یا FocusVisible را از catalog حذف کنید. قبل از اجرا prediction را بنویسید: visual system دیگر از آن state پشتیبانیشده محافظت نمیکند چون case addressable برای capture ندارد. Repair یعنی state را با fixture data صریح برگردانید؛ تغییر نام Default coverage ایجاد نمیکند.
Mutation B — تغییر Spacing Token
Padding را تغییر دهید در حالی که requirement و baseline authority ثابتاند. Screenshot comparison deterministic باید diff معنادار ایجاد کند. آن را regression طبقهبندی و token را repair کنید. اگر test واکنش نشان نداد، capture surface یا comparison ضعیف است.
Mutation C — وارد کردن Time/Random/Animation Volatility
Clock زنده، random بدون seed یا animation در حال اجرا را داخل تصویر بیاورید. دو capture متوالی باید instability را نشان دهند. Repair صحیح stabilize یا mask مستندشده برای input خارج از contract است. Accept خودکار screenshotهای متفاوت یعنی تبدیل noise به baseline churn.
Mutation D — عوض کردن Baseline بدون Review
Reference image یا metadata baseline را تغییر دهید، اما requirement/change record نداشته باشید. شاید diff صفر شود، ولی baseline authority شکسته است. Checker باید replacement بدون review را reject کند حتی اگر تصویر current با reference جدید یکسان باشد.
پل برای مهندس باتجربه: Catalog بهعنوان State Model، Baseline بهعنوان Governed Data
برای مهندس باتجربه abstraction مهم «Storybook + snapshot» نیست. Catalog یک model از stateهای محصول است. Storyها fixtureهای نامدار برای stateهای پشتیبانیشدهاند. Baselineها governed data هستند که از همان fixtureها در render environment مشخص ساخته شدهاند.
این نگاه code review را بهتر میکند. سؤال کنید coverage بر اساس risk است یا convenience؟ Fixture data پایدار است؟ Environment signature قابل بازتولید است؟ Baseline change دلیل دارد؟ Diff classifier میتواند requirement change را از implementation drift و infrastructure noise جدا کند؟
همچنین روشن میشود چه چیزهایی باید با هم version شوند: story definition، render config، baseline artifact و review metadata. Baseline-only update بدون توضیح requirement نیاز به بررسی جدی دارد.
Alternativeها و Trade-offها
Storybook یک catalog surface مشخص است، نه تنها گزینه. پروژه میتواند component explorer دیگری یا fixture app کوچک اختصاصی داشته باشد. نیاز پایدار این است که stateهای پشتیبانیشده individually addressable و reproducible باشند.
Cloud visual testing میتواند browser matrix، hosted baseline، review UI و CI integration بدهد. Harness local شبیه Playwright میتواند سادهتر و کاملاً offline باشد. Trade-off عملیاتی است: local environment ownership را شفاف میکند ولی مدیریت artifact و review workflow با تیم است؛ hosted service همکاری را سادهتر میکند ولی service configuration و external execution اضافه میشود. WEB-018 vendor خاصی را الزامی نمیکند.
Pixel threshold و mask هم ابزارند، نه راه فرار. Mask کردن timestamp third-party خارج از visual contract میتواند signal را بهتر کند. Mask کردن body card چون دائماً fail میشود coverage را از بین میبرد. هر tolerance یا mask باید reason متصل به contract داشته باشد.
DOM snapshot سؤال دیگری را جواب میدهد. Guidance فعلی Storybook اشاره میکند markup snapshot میتواند noisy باشد و بسته به سؤال visual یا interaction test مناسبتر است. Oracle را با سؤال هماهنگ کنید، نه اینکه همه test modeها را از روی عادت جمع کنید.
تمرین: Guided Fixture را Repair کنید و سپس Method را Transfer کنید
پوشه lab/WEB018-LAB1/learner را باز کنید. قبل از تغییر harness، requirement و story inventory را بنویسید. Starter عمداً stateهای لازم را کامل نکرده و baseline را unreviewed گذاشته است. Storyهای missing را با fixture data پایدار اضافه کنید. Baseline metadata را فقط بعد از inspect کردن stateها review کنید. Capture policy را deterministic کنید تا runهای متوالی قابل تفسیر باشند.
Harness را در baseline mode اجرا کنید و سپس compare. Environment signature و observation واقعی را ثبت کنید. Spacing mutation را اعمال کنید، قبل از اجرا class آن را پیشبینی کنید، diff را بگیرید و repair کنید. سپس volatile-noise mutation را اجرا کنید، دو capture بگیرید و توضیح دهید چرا این تفاوت noise است نه regression محصول.
Transfer مستقل یک notification/status component دارد که content و layout متفاوت است ولی method evidence همان است. باز هم representative state، baseline qualified، fixture deterministic، diff classification و oracle-boundary statement لازم است. نام stateهای card را مکانیکی کپی نکنید؛ state inventory را از requirement جدید استخراج کنید.
Verification: Evidence Record باید چه چیزی داشته باشد
Record سطح inspection درس است. State inventory و rationale، environment pinned، baseline authority و reason review، observationهای run screenshot، تصمیمهای diff، mutation و repair، transfer و residual limitation را ثبت میکند.
Observation و inference را جدا نگه دارید. «Screenshot مربوط به FocusVisible در environment signature X تولید شد» observation است. «پس این fixture در همین environment از این class از visual regression محافظت میشود» inference است. «Keyboard navigation accessible است» از screenshot بهتنهایی نتیجه نمیشود.
Residual limitation معتبر میتواند بگوید این fixture screen-reader announcement، keyboard order، business-rule correctness یا portability به OS/browser/font stack دیگر را ثابت نمیکند. محدود کردن claim کیفیت evidence را بالا میبرد.
Assessment و پایان درس
Retrieval: پنج contract و یک non-goal را بیان کنید. Application: catalog card و Evidence Record را کامل کنید. Diagnosis: یک spacing regression و یک volatility failure را پیشبینی و detect کنید. Decision: update/reject/noise-removal را با requirement و environment record دفاع کنید. Transfer: notification/status variant را بدون hint تعیینکننده حل کنید.
Failureهای blocking شامل missing required state، approved فرض کردن first-run screenshot، accept کردن regression baseline بدون review، کنترل نکردن volatility در حالی که claim deterministic evidence دارید، یا استفاده از visual pass بهعنوان تنها proof برای interaction/accessibility/business correctness است.
پایان درس «همه screenshotها سبزند» نیست. پایان درس catalog با coverage موجه و evidence trailای است که وقتی coverage، rendering stability، baseline authority یا styling را عمداً میشکنید، واکنش درست نشان میدهد.
منابع و مرز Freshness
مستندات فعلی Storybook story را rendered state یک component تعریف میکند، Component Story Format را برای توصیف stateها بهکار میبرد و Visual Test را از DOM/HTML Snapshot جدا میکند. مستندات فعلی Playwright reference screenshot comparison را توضیح میدهد و هشدار میدهد rendering میتواند با OS، browser version/settings، hardware، headless mode، font و environment تغییر کند. Factهای tool-specific این درس در 2026-09-13 بازبینی شدهاند و فقط در محدوده همین guidance استفاده میشوند.
روش پایدار وابسته به نسخه tool نیست: stateهای معنادار را enumerate کنید، آنها را reproducible render کنید، baseline authority را review کنید، diff را با context طبقهبندی کنید و visual oracle را داخل مرز evidence خودش نگه دارید. در review_due یا با تغییر guidance/fixture، claimهای volatile باید دوباره بررسی شوند.
ارزیابی
پاسخنامهی این درس در بستهی آموزش وجود ندارد؛ خودت جواب بده و صادقانه علامت بزن. «نیاز به مرور» درس را به صف تکرار میفرستد.
WEB-018-S1 retrieval
پنج قرارداد اصلی WEB-018 و یک non-goal صریح را بیان کنید.
WEB-018-S2 application
catalog حالتهای card، metadata مربوط به baseline بازبینیشده، تنظیمات deterministic capture و evidence record را کامل کنید.
WEB-018-S3 diagnosis
یک spacing regression واقعی و یک volatile-noise mutation را پیشبینی، اجرا، طبقهبندی و repair کنید.
WEB-018-S4 decision
دفاع کنید که یک تغییر بصری باید baseline را بهروزرسانی کند، بهعنوان regression اصلاح شود، یا بهعنوان noise حذف شود.
WEB-018-S5 transfer
روش را مستقل به notification/status component در context تغییرکرده منتقل کنید و یک limitation غیر بصری را بیان کنید.
ملاکهای ارزیابی (rubric)
Critical dimensions:
- Story Coverage — Representative states are mapped to meaningful stories; story IDs alone are not accepted as proof of state coverage.
- Baseline Authority — Baselines are explicitly reviewed, tied to a qualified capture environment, and never treated as automatically correct because they exist.
- Visual Determinism — Clock, randomness, animation, viewport, browser and font assumptions are controlled or qualified before visual comparison.
- Diff Judgment — Pixel changes are interpreted against requirements/change authority and classified as expected change, regression, or noise rather than blindly accepted/rejected.
- Oracle Boundary & Transfer — Visual equivalence is not presented as proof of interaction/accessibility/business correctness; the changed-context notification/status transfer is completed independently with one residual limitation.
Blocking assessments: S2, S3, S5. S1 and S4 are non-blocking but still require scope discipline. Browser/DOM evidence must come from learner observation; machine checks and synthetic fixtures must not fabricate browser evidence.
منابع و تازگی
مطالعهٔ موارد (case studies)
فایلهای آزمایشگاه
هدف: state inventory مربوط به card را کامل کنید، baseline را فقط بعد از review معتبر کنید، محیط capture را پایدار کنید، یک spacing regression واقعی و یک volatile noise را طبقهبندی کنید و سپس روش را مستقل transfer کنید.
python run_checks.pyرا اجرا کنید تا starter عمداً حلنشده بودن خود را نشان دهد.- Storyهای اجباری missing را در
catalog/stories.jsonاضافه کنید. config/environment.jsonرا deterministic کنید؛ portability بین همه ماشینها را claim نکنید.- Captureهای اولیه را با requirement review کنید و فقط بعد از review وضعیت
baseline/baseline_review.jsonرا تغییر دهید. python visual_harness.py baselineو سپسpython visual_harness.py compareرا اجرا کنید.- Probeهای spacing و volatile-noise را طبق
mutation_probe.mdاجرا و prediction، observation، classification، repair و rerun را ثبت کنید. - Transfer زیر
transfer/را بدون کپی مکانیکی نام stateها حل کنید. evidence/learner_evidence.jsonرا کامل کنید و visual evidence را از interaction/accessibility/business claim جدا نگه دارید.
Harness از Chromium محلی استفاده میکند. در صورت نیاز CHROMIUM_BIN را تنظیم کنید. Network خارجی لازم نیست.
رسانه و اسلایدهای درس
فایلهای آموزشی (دانلود)