לפני שבודקים פוקוס, צריך לצאת ממנו

איך העברת פוקוס אחת תיקנה את נקודת הייחוס בבדיקת כרטיס בפורטפוליו, ומה השוואת סגנונות מחושבים באמת יכולה להוכיח.

פורסם 2026-10-28. תגיות: Frontend, Testing, Accessibility.

בתיקון בדיקת דפדפן בפורטפוליו שלי, השורה החשובה הייתה העברת פוקוס לאזור התוכן הראשי. לא שינוי צבע, לא החלפת selector ולא הארכת timeout.

הבדיקה כבר ידעה להכניס קישור לפוקוס ולקרוא את העיצוב המחושב של הכרטיס שסביבו. מה שהיה חסר הוא לוודא שנקודת ההתחלה באמת נמצאת מחוץ למצב שרוצים לבדוק.

כרטיס יכול להיות בפוקוס דרך הקישור שבתוכו

באתר יש כרטיסי פרויקטים עם קישורים פנימיים. כשאחד הקישורים מקבל פוקוס, הכרטיס מסמן את ההקשר באמצעות גבול וצל. האחריות מתחלקת: הקישור הוא יעד הפעולה, והכרטיס עוזר להבין לאיזה פרויקט הפעולה שייכת.

העיצוב משתמש ב־`:focus-within`. בניגוד לבדיקה של האלמנט הממוקד בלבד, הכלל הזה חל גם כשהפוקוס נמצא באחד הצאצאים. זאת ההתנהגות המתועדת של [הבורר ב־MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:focus-within).

מכאן מגיעה המלכודת: אפשר להשאיר כרטיס במצב הזה בלי לקרוא ל־`focus()` על הכרטיס עצמו. מספיק למקד קישור שנמצא בתוכו.

הצעד הקודם נשאר בתוך הבדיקה

הבדיקה מתחילה במיקוד הפעולה הראשית ובבדיקת המסגרת שלה. אחר כך היא מודדת את המראה של כרטיס הפרויקט הראשון, ממקדת קישור בכרטיס ומצפה לשינוי.

אלא שהפעולה שנבדקה קודם נמצאת באותו כרטיס. אם לא מעבירים ממנה את הפוקוס, המדידה שנקראת `cardDefault` כבר יכולה לכלול את עיצוב הפוקוס.

השם של המשתנה אינו משנה את מצב הדפדפן. גם גלילה אל הכרטיס אינה מאפסת פוקוס. במקרה הזה ההשוואה עלולה להיות בין שני מצבים ממוקדים, במקום בין מצב ללא פוקוס לבין מצב ממוקד.

זאת תלות בין צעדים בתוך אותה בדיקה. אפילו דפדפן חדש לכל test לא פותר אותה, כי הצעד הראשון יוצר אותה מחדש.

התיקון: להגדיר את נקודת הייחוס

לפני מדידת הכרטיס הוספתי העברת פוקוס אל `#main-content`, שנמצא מחוץ לכרטיס ויכול לקבל פוקוס באמצעות `tabIndex={-1}`. אחר כך הבדיקה ממשיכה ברצף הקיים:

await page.locator('#main-content').focus();

const card = page.locator('.featured-case-card').first();
await card.scrollIntoViewIfNeeded();
const cardDefault = await interactionSnapshot(card);

const cardLink = card.locator('.home-action').first();
await cardLink.focus();

await expect.poll(() => interactionSnapshot(card))
  .not.toEqual(cardDefault);

`interactionSnapshot` היא פונקציית עזר של הבדיקה, שקוראת צבע גבול, צל, מסגרת ו־transform מתוך `getComputedStyle`. השינוי לא הוסיף צילום מסך ולא שינה את עיצוב המוצר. הוא הגדיר תנאי התחלה להשוואה שכבר הייתה שם.

ה־polling מאפשר לבדיקה להמתין לשינוי בסגנון המחושב. הוא אינו תחליף לתנאי ההתחלה: המתנה ארוכה יותר לא הייתה מוציאה את הקישור מפוקוס.

מה ההשוואה הזאת באמת מוכיחה

הבדיקה מאמתת שמיקוד קישור משנה לפחות אחד מהמאפיינים שנמדדים בכרטיס, בתצורה שנבחרה: אנגלית, מצב בהיר ורוחב דסקטופ. זה גבול שימושי ומצומצם.

היא אינה מאמתת את כל ערכי העיצוב, את סדר הניווט עם Tab, את חוויית קורא המסך או את כל השפות וערכות הנושא. בפרט, [מיקוד ישיר דרך Playwright](https://playwright.dev/docs/api/class-locator#locator-focus) אינו מסלול ניווט מלא במקלדת. גם שם רחב לבדיקה לא מרחיב את הכיסוי של ה־assertions שבתוכה.

מה אבדוק בפעם הבאה

כשבדיקת ממשק משווה מצב “לפני” למצב “אחרי”, אני רוצה לדעת איזו פעולה יצרה כל אחד מהם. פוקוס, hover, בחירה ופתיחת תפריט יכולים להישאר מצעד קודם ולהפוך מדידה שנראית ניטרלית למדידה של מצב פעיל.

במקרה הזה שורת הכנה אחת החזירה להשוואה את המשמעות שלה. לפני שמבקשים מהבדיקה לזהות מעבר, צריך לתת לה נקודת התחלה שבאמת שונה מנקודת הסיום.