נושא ההמתנות באוטומציה הוא נושא חשוב מאין כמוהו, בפרוייקט שלנו נהיה חייבים להכניס איזשהו מנגנון של המתנות בשביל שנוכל לשמור על יציבות הבדיקות. מה הכוונה ? אנו נרצה לסנכרן בין המערכת הבודקת לבין המערכת הנבדקת שלנו, במילים אחרות אנו נרצה לסנכרן בין הקוד של הבדיקות שלנו, למשל סלניום, לבין האפליקציה אותה אנו בודקים (האתר למשל).
זה חשוב, מכיוון שלעיתים המערכת שלנו מתנהגת בצורה קצת שונה מהרגיל מבחינת זמנים, בגלל שיש עומס על התעבורה הפנימית, בגלל שאנו עובדים כשהחיבור לאינטרנט איטי או סיבות אחרות…
נציין כאן כי חשוב לעשות את ההפרדה בין מערכת איטית שבשבילה נהיה חייבים לפתוח באג, לבין מערכת איטית שלא בשליטתינו. כעת אני מדבר על האופציה השניה בלבד, הרי זה לא יהיה הגיוני לפתוח באג על המערכת שלנו שעובדת לאט בגלל שאחד משרתי התקשורת של ספק האינטרנט קרס, נכון ?
במידה ולא נדאג לסנכרן בין המערכות, הטסטים שלנו פשוט יפלו כשהחיבור לא יהיה יציב ואת זה נרצה למנוע ע"י כתיבה של מנגנוני המתנות בקוד שלנו.
אז מי שקצת התעסק עם סלניום, מי שקצת למד אוטומציה, ומי שיודע כבר לקודד יודע שישנם סוגים של המתנות טיפשות, כמו Thread.sleep והמתנות חכמות יותר כמו Page Load Timeout , Implicit Wait, Explicit Wait , את סוגיי ההמתנות הללו אינני הולך לכסות כאן.
בפוסט זה אדבר על המתנה חכמה וגנרית בשם: Fluent Wait
ה-Fluent Wait היא סוג של Explicit Wait – תחת קטגוריה זו ניתן לכלול 2 מחלקות בסלניום:
ה-WebDriverWait שבטח מוכר למפתחי אוטומציה על סלניום
וה-FluentWait שפחות מוכר כנראה, אך יותר גנרי, למעשה ניתן לומר כי הWebDriverWait יורשת מה-FluentWait
כשאנו עובדים עם FluentWait אנחנו יכולים להגדיר כמה "חוקים" בזמן פעולת ההמתנה שלנו:
1. האלמנט לו אנו רוצים להמתין
2. זמן החסם העליון – פרק הזמן המקסימלי אותו נרצה להמתין עבור האלמנט
3. התדירות אותה נרצה לדגום את ה-DOM בשביל לבדוק האם הלאמנט מופיע שם
לפני שנגיע לקוד, בואו נראה קודם כל דוגמא של מה אנחנו רוצים לבדוק, נגיד שיש לי כפתור כזה:
ניתן לזהות אותו עפ"י id=start2
כשאני לוחץ עליו, יופיע לי פקד של loading למשך כמה שניות:
ולאחריו , יופיע לי הטקסט: , אותו ניתן לזהות ע"י id=finish2:
בבדיקה שלנו, אנו נרצה ללחוץ על הכפתור ולהמתין עד אשר הטקסט: "My Rendered Element After Fact!" יופיע על המסך, מכיוון שהטקסט יופיע כעבור כמה שניות, במידה ולא נשתמש ב-wait אנחנו נעוף מהתוכנית.
הקוד של ה-fluent wait הכללי אמור להיראות כך:
Wait wait = new FluentWait(driver) .withTimeout(10000, TimeUnit.MILLISECONDS) .pollingEvery(350, TimeUnit.MILLISECONDS) .ignoring(NoSuchElementException.class); WebElement element = wait.until(new Function<WebDriver, WebElement>() { public WebElement apply(WebDriver driver) { return driver.findElement(By.id("ABC123")); } });
מה אנחנו רואים כאן?
אנו רואים כאן הגדרה של wait עם פרמטרים של withTimeout ששווה ל-10 שניות (זהו החסם העליון שאנו נגדיר להמתנה), pollingEvery ששווה ל350 מילישניות (התדירות בה ה-wait ידגום את ה-DOM) והגדרת מחלקת ה-NoSuchElementException ממנה הוא יתעלם, מה הכווונה ?
בואו נבין קודם מה קורה אם אנו ממתינים לאלמנט מסויים והוא לא מגיע תוך פרק הזמן המקסימלי שהגדרנו:
ללא שימוש ב-Wait כלל, אנו נקבל exception מסוג: NoSuchElementException
בשימוש עם implicitWait אנו נקבל exception מסוג: NoSuchElementException
בשימוש עם ExplicitWait אנו נקבל exception מסוג: TimeoutException
ב-FluentWait אנו יכולים להגדיר את המחלקות של ה-exceptions מהן אנו נרצה להתעלם בזמן ההמתנה, במקרה שלנו נרצה להתעלם ממחלקת NoSuchElementException כי במידה ולא – ההמתנה לא תתבצע ומיד נקבל את ה-exception הזה.
אז במקרה שלנו, אנו נוכל להכניס את הקוד של ה-FluentWait לתוך פונקציה משלנו, נקרא לה: waitedElement והיא תקבל locator כפרמטר – אותו locator שמזהה את האלמנט עליו נרצה לבצע את החיפוש.
מעבר לזה, נוכל להגדיר את משתני ההמתנה כמשתנים גלובליים, או לחילופין נוכל להגדיר אותם כמשתנים חיצוניים (מוגדרים בקובץ קונפיגורציה למשל)
את האלמנט שאנו ממתינים לו נוכל גם להחזיר בפונקציה, כך שאח"כ נוכל לבצע עליו פעולות
כך למשל נגדיר את הפונקציה שלנו:
long timeout = 5000; long interval = 500; public WebElement waitedElement(By locator) { Wait wait = new FluentWait(driver) .withTimeout(timeout, TimeUnit.MILLISECONDS) .pollingEvery(interval, TimeUnit.MILLISECONDS) .ignoring(NoSuchElementException.class); WebElement element = wait.until(new Function<WebDriver, WebElement>() { public WebElement apply(WebDriver driver) { return driver.findElement(locator); } }); return element; }
והקריאה לפונקציה מתוך הטסט תהיה:
By finish = By.id("finish2"); @Test public void Test3_ExplicitWait() { driver.findElement(By.id("rendered")).click(); System.out.println(waitedElement(finish).getText()); }
שימו לב כי הדפסתי את הטקסט של האלמנט, בבדיקה אמיתית ארצה להכניס את זה לתוך assert
אוכל גם לשחק עם הפונקציה ולהגדיר אותה כך שבמקום שהיא תחזיר לי אלמנט, היא תבצע עליו פעולה (ובהמשך אוכל גם לדווח בפנים ללוג או לדוח…)
long timeout = 5000; long interval = 500; public void waitForElementAndClick(By locator) { Wait wait = new FluentWait(driver) .withTimeout(timeout, TimeUnit.MILLISECONDS) .pollingEvery(interval, TimeUnit.MILLISECONDS) .ignoring(NoSuchElementException.class); WebElement element = wait.until(new Function<WebDriver, WebElement>() { public WebElement apply(WebDriver driver) { return driver.findElement(locator); } }); element.click(); }