בפרקים הקודמים של סדרת הכתבות שלי: "מלחמת העולמות" אשר עוסקת בשאלה מיהו אותו פריימוורק אוטומציה הטוב ביותר, ביצעתי סקירה על כמה פריימוורקים מובילים בתחום של אוטומציה מודרנית. ראינו כי ישנם מתמודדים חזקים שמתחילים לערער את מעמד Selenium WebDriver בשלטון, עם היציבות הבלתי מתפשרת ש-Cypress נותן לנו, המהירות המרשימה של Puppeteer או השילוב המנצח של Taiko.

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

קורס אוטומציה Full Stack Test Automation

 

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

נכון לכתיבת שורות אלו, הגרסה האחרונה הינה Selenium 4.0 Alpha Version 6 (שיצאה ב-29.5.2020), כאשר הגרסה הרישמית הינה 3.14 שמלווה אותנו כבר מאז סוף 2018…

כן, מדובר בלא מעט זמן שעבר במונחים של גרסאות תוכנה אבל צוות הפיתוח של סלניום לא נח על זרי הדפנה, הם שיכתבו המון מה-sources שלהם בשביל שאנו נוכל לקבל מוצר מודרני, יציב, בשל, כזה שימשיך לעמוד בחזית הטכנולוגית של אוטומציה על ה-web והכי חשוב, תומך לאחור. למעשה, כשאומרים גרסת 4, מדברים פה על 3 מוצרים שנכללים תחת קטגוריה זו, צוות המפתחים החליט כי הם מסתכלים על סלניום כאל משפחה אחת של מוצרים ולקחו את שלושת המוצרים הפופולריים ביותר שלהם ושידרגו אותם ביחד עם צוותי פיתוח אחרים מרחבי העולם, מדובר על Selenium WebDriver, Selenium IDE, Selenium Grid

 

Selenium IDE:

הסיפור של Selenium IDE הינו כמו סיפור של שווקי ההון לאורך ההיסטוריה, הגרף מראה על הצלחה עם עלייה יפה, ישנה ירידה זמנית ואח"כ שוב עלייה יפה וחוזר חלילה. מוצר זה יצא לאוויר העולם בשנת 2004 ולמי שאינו מכיר – מדובר כאן בכלי של הקלטות, אז בזמנו הוא נכתב כתוסף לדפדפן ה-Firefox, לחצנו על כפתור ה-Record, ביצענו פעולות על הדפדפן והכלי היה מקליט כל פעולה שאנו עשינו, היו לו מגבלות כמובן, דברים שמנעו ממנו להיות המוביל בשוק ולקחת את הבכורה מ-WinRunner שהיה בזמנו המלך הבלתי מעורער בשוק האוטומציה…

הפופולריות של Selenium IDE רק הלכה וירדה עם השנים, עד שבאוגוסט 2017, המפתח הראשי של פרוייקט זה – אדם גאוצ'ר החליט כי נשבר לו לתחזק פרוייקט עם קהילה מאוד קטנה של משתמשים וכך הפרוייקט נזנח, בזמנו כתבתי על זה כך פוסט בבלוג שלי.

כשנה אח"כ (אוגוסט 2018) המוצר קם לתחייה בזכות שני פרוייקטים מקבילים שהתאחדו, האחד של חברת SideeX אשר כתבו מוצר הקלטות מבוסס על הקוד של Selenium IDE והשני של חברת Applitoos אשר החליטה לתרום זמן פיתוח לכתיבת מוצר חדש לגמרי. המוצר בגרסתו הרישמית נקרא Selenium IDE 4.0 והוא נכלל כחלק ממארג השידרוגים של גרסת 4.0 הגלובלית שהחליטו לצאת איתה. החידושים עימם מגיע המוצר החדש באמת מרשימים וגם בזמנו כתבתי עליהם בפוסט בנפרד.

בגרסה זו תוכלו למצוא בין היתר פיצ'רים מגניבים כמו:

– אפשרות לקרוא לטסט מתוך טסט על ידי הפקודה run ובכך לעשות Reuse לקוד שלנו – שזהו פיצ'ר מאוד משמעותי שמתגבר למעשה על החיסרון המאוד גדול שאיפיין בעבר כלי הקלטות – עניין תחזוקת הקוד

– הגרסה החדשה תומכת בפרוקוטול ה- CDP שזהו ה-Chromium Developer Protocol, מה שנותן יכולות מאוד גבוהות לעבודת שטח (ארחיב על זה בהמשך)

– ישנו פיצ'ר מגניב שנקרא Backup Element Selector שמזהה באופן אוטומטי את האלמנטים בשיטות שונות, כך שבמידה והזיהוי הקודם נשבר הוא יוחלף על פי שיטת זיהוי אחרת, פיצ'ר זה מאחורי הקלעים יכול להתבצע מכיוון שבזמן ההקלטה הוא לא מקליט רק זיהוי אחד של האלמנט, אלא את כלל האפשרויות לזיהוי ושומר אותם מאחורי הקלעים, מה שנקרא: "ליום סגריר"

– תמיכה בדפדפנים שונים, את הטסטים ניתן להקליט כתוסף לדפדפן ה-Firefox וכן לדפדפן ה-Chrome, את הריצה ניתן להריץ על כמעט כל דפדפן אחר.

– הרצה דרך ה-Command Line, למעשה, בגרסה זו ניתן להריץ דרך ה-CMD (עם התקנת Selenium SIDE Runner), דבר שנותן לנו את האפשרות להריץ את הטסטים שלנו על מכונות מרוחקות ולהתממשק בקלות עם כלי CI / CD (לדוגמת Jenkins)

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

 

– אפשרות להכניס גם לוגיקה לתוך הטסטים שלנו, כמו if / else , for loops, while loops וכו'…

– ניתן לייצא כעת את הקוד המוקלט לשופת Java / Python

– המוצר עצמו שוכתב מחדש וכעת עובד מעל טכנולוגיית ה-Electron

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

 

Selenium Grid:

ה-Selenium Grid יצא כ-3 שנים אחרי Selenium WebDriver, אז נשאלת השאלה, למה ?

למה מוצר כל כך טוב כמו Selenium WebDriver שתפס מאוד חזק את השוק והפך להיות הפופולרי ביותר, למה לכתוב לו מוצר חדש שיחליף אותו ? התשובה היא ש-Selenium Grid אינו בא להחליף את ה-WebDriver, הוא בא להרחיב את היכולות של האוטומציה שלנו. תראו, Selenium WebDriver הוא מוצר מצויין אבל מוגבל, אלו הן בסה"כ ספריות קוד שיודעות לבצע פעולות אוטומטיות על דפדפנים, אבל מה קורה לגביי הרצה ?

ע"י Selenium Grid אנו יכולים לקחת את מקרי הבדיקה האוטומטיים שכתבנו עם Selenium WebDriver ולהריץ אותן בצורה מבוזרת, על סביבות שונות (מרוחקות) בין אם אלו סביבות פיזיות או סביבות ווירטואליות.

יופי, אז מה התחדש בגזרה זו עם גרסת 4.0 החדשה ?

קודם כל חשוב לציין כי מאז 2012 (אז יצאה גרסת 2.0) לא התחדש הרבה ב-Selenium Grid , ולמעשה הגרסה החדשה שיוצאת כעת מדלגת על 3.0 וישר קופצת ל-4.0 ליישר קו עם משפחת המוצרים. מבין השידרוגים שצוות הפיתוח מביא לנו עם גרסה זו ניתן למצוא:

– תיקוני באגים רבים שהתווספו לאורך השנים, כמו בכל פרוייקט קוד פתוח אחר, ניתן לעקוב אחריהן בקלות בריפוזיטורי שלהם בגיטהאב: https://github.com/SeleniumHQ/selenium/issues

– כעת ההתקנה וההטמעה של מוצר זה קלה יותר מבעבר

– ישנה תמיכה מובנית ב- Docker

– המעצבים שינו מעט את ה-UI של Grid וכעת הוא יותר User Friendly

– גרסת 4.0 תומכת ב-3 מצבים:

  1. Standalone – בו ניתן לעבוד עם השרת הן במצב של Node והן במצב של Hub על אותה מכונה ובאותו זמן
  2. Hub & Nodes – אותו מצב אותו אנו מכירים כבר מהגרסה הקודמת (הגרסה עימה אנו עובדים כיום), בו אנו מתקינים את ה-Hub על מכונה אחת ואת ה-Nodes השונים על סביבות אחרות בד"כ
  3. Fully Distributed Mode – מאפשרת לנו להריץ את Grid בצורה מקוסטמת (customize), ע"י יצירת Session, יצירת Distributor, הגדרת Router והרצת ה-Node – את כל החבר'ה האלו אנו יכולים להפעיל ביחד או לחוד ולכולם ניתן לשלוח רשימה של פרמטרים, הכל לפי הצרכים שלנו בעבודה

 

Selenium WebDriver:

טוב, זהו אינו סוד כי בד"כ כשאומרים Selenium מתכוונים למעשה ל-Selenium WebDriver , זהו המוצר העיקרי במשפחת המוצרים של פרוייקט SeleniumHQ ואם חשבת שפיצ'רים חדשים על מוצר זה יוזנחו, מצפה לכם הפתעה 🙂

– תמיכה לאחור: זה אולי נשמע טריביאלי: לתמוך לאחור בכל הפרוייקט, אבל כשמחליפים את התשתית והפרוטוקול (עוד מעט נרחיב על זה), צצות לא מעט בעיות, בטח בסדר גודל כזה של פרוייקט, בעיות שצוות המפתחים היה צריך לפתור, בשורה התחתונה הדבר החשוב ביותר שעמד לנגד עיניהם היה לתמוך לאחור בכל הפונקציונליות הקיימת של סלניום, כך שלנו המשתמשים, כשנעבור לעבוד עם Selenium 4.0 זה היה תהליך שקוף.

– השלמת הסטנדרטיזציה של פרוטוקול ה-WebDriver , למעשה החל מגרסת 3.8 (דצמבר 2017) סלניום החל לעבור מהפרוטוקול הישן – JSON Wire Protocol לחדש – WebDriver, בגרסת 4.0 התבצעה ההשלמה וכעת כל הפרוייקט תומך בפרוטוקול החדש, מה זה אומר על המוצר ? שהוא כעת יציב יותר ותואם מוצרי צד ג' Native. מה זה אומר עלינו (מפתחי האוטומציה) ? כלום. כמו שנכתב בסעיף הקודם, המעבר מבחינתינו אמור להיות שקוף, כמה ספקי סלניום (Selenium Vendors) אבל יצטרכו קצת לבצע שינויים בתשתית הקוד שלהם, ספקים כמו Perfecto , SauceLabs, BrowserStack וכו'…

– באזזורד נחמד שהתווסף לגרסה החדשה הוא Locator חדש, כן כן, אם עד עכשיו עבדנו מול 8 סוגי Locators בסלניום (id / name / className / tagName / linkText / partialLinkText / cssSelector / xpath), אז כעת התווסף אחד חדש למשפחה, חברים, הכירו את ה-Relative Locator (או איך שבעבר קראו לו – Friendly Locator). זהו למעשה לא המצאת הגלגל ה-Relative הזה, הוא קיים כבר שנים בכלי אוטומציה אחרים (אני נתקלתי בו לראשונה ב-2011 ב-QTP), הם למעשה באים אומרים: בואו תזהו את האלמנט שלכם על פי המיקום שלו ביחס לאלמנט אחר, כאשר מימשו לצורך כך 5 מתודות חדשות:

  1. above – זיהוי האלמנט כך שיהיה מעל לאלמנט אחר
  2. below – זיהוי האלמנט כך שיהיה מתחת לאלמנט אחר
  3. toLeftOf – זיהוי האלמנט כך שיהיה לצד שמאל לאלמנט אחר
  4. toRightOf – זיהוי האלמנט כך שיהיה לצד ימין לאלמנט אחר
  5. near – זיהוי האלמנט כך שיהיה בקרבת אלמנט אחר, מה זה אומר קרבה ? עד 50 פיקסלים ממנו

 

לצורך כך, מתודת ה-findElement כעת מקבלת מתודה חדשה – withTagName אשר מחזירה RelativeLocator. זוהי דוגמא למשל למציאת אלמנט – emailAddressField שממוקם באופן יחסי לאלמנט אחר: emailAddressLabel:

//import static org.openqa.selenium.support.locators.RelativeLocator.withTagName;

WebElement emailAddressLabel= driver.findElement(By.id("lbl-email"));
WebElement emailAddressField = driver.findElement(withTagName("input").near(emailAddressLabel));

איך זה עובד מאחורי הקלעים ? סלניום משתמש בפונקציית JS שנקראת: getBoundingClientRect בכדיי למצוא את אותם אלמנטים יחסיים (רלטיביים).

דוגמא נוספת (אמיתית) – התחברות לשירות IMDB:

public class NewLocator
{
    WebDriver driver;

    @BeforeClass
    public void startSession() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.manage().timeouts().implicitlyWait(5, TimeUnit.SECONDS);
        driver.get("https://www.imdb.com/registration/signin?ref=nv_generic_lgin&u=%2F");
    }

    @Test
    public void test1() {
        driver.findElement(By.xpath("//div[@class='list-group']/a[1]")).click();
        driver.findElement(By.id("ap_email")).sendKeys("test123");
        driver.findElement(By.id("ap_password")).sendKeys("12345");
        // Locating Element with Relative Locator - Below
        driver.findElement(RelativeLocator.withTagName("input").below(By.id("ap_password"))).click();
    }

    @AfterClass
    public void closeSession() {
        driver.quit();
    }
}

 

– תמיכה ב-CDP:

זהו הפיצ'ר המשמעותי והתותח הכבד ביותר שיש ל-Selenium 4.0 להציע. תראו, מי שקרא את הסקירה על Cypress ועל Puppeteer בפרק הראשון של מלחמת העולמות, הבין כי יש להם איזשהו Selenium Kill Feature בדמות CDP, אוקיי, מה זה בכלל ?

ה-CDP הינו קיצור של Chromium Developers Protocol, ולמה הוא כזה חשוב לנו באוטוממציה ? אוקיי אז ככה:

אתם כבודקי תוכנה (Web), לבטח מכירים את ה-Inspect Element של הדפדפן, סביר להניח שאתם מכירים באותו איזור גם את ה-Sources ולידו נמצא שם גם ה-Network, ה-Console ועוד, אני כמובן מתכוון לממשק הפיתוח של הדפדפן, ה-DevTools:

 

עם אותו ממשק פיתוח (DevTool) שאנו רואים כאן, אנו כבודקי תוכנה (גם כמובן אנשי פיתוח – אבל נעזוב אותם כעת…), יכולים לגשת לכל אחד מן הפיצ'רים הללו ובעזרתם לבדוק את המוצר שלנו, למשל, תחת טאב ה-Network אנו יכולים לאסוף מידע תעבורתי שמתחולל בין הקליינט לסרבר, תחת ה-Console אנו יכולים לקרוא את כל ה-Errors שנזרקים מהדפדפן ועוד… עכשיו, כמה נחמד ומגניב זה יהיה אם את כל הפעולות הללו בממשק הפיתוח (DevTool) שעשינו אותם עד כה באופן ידני, נוכל כעת לבצע ע"י קוד ?

זהו אותו רעיון עליו הלכו Cypress ו-Puppeteer כבר מלפני שנתיים, ה-Chromium Developers Protocol מאפשר לנו גישה לכל אותם שירותים שיש ל-DevTools להציע בצורת API, כך שגם ב-Cypress וגם ב-Puppeteer כתבו לנו מתודות אשר מממשות פונקציונליות ב-DevTool וכעת ניתן לעשות זה גם בגרסת Selenium 4.0. מבחינת הארכיטקטורה של הקבצים, בגרסאות סלניום 3, היה לנו את ה-WebDriver (שהוא Interface), אותו מממשת מחלקה בשם: RemoteWebDriver וממנה יורשת מחלקה אחרת: ChromeDriver (במקרה שלנו – כשאנו עובדים מול דפדפן הכרום):

 

כעת, בגרסת סלניום 4, "דחפו לנו" מחלקה נוספת באמצע, שנקראת ChromiumDriver ובעזרתה אנו נוכל לבצע את כל הפעולות על ה-DevTools, עכשיו זה נראה כך:

 

הלכה ולמעשה, בגרסת 4 ניתן :

  1. לאסוף לוגים מהדפדפן דרך ה-Console Log Output (ראו דוגמת קוד בהמשך)
  2. להתחבר לאתר לא מאובטח (ראו דוגמת קוד בהמשך)
  3. להתחבר במצב Offline לאפליקציות
  4. ליירט קריאות של שליחה וקבלה (Requests / Response) בין Services שונים במערכת, מבחינת הבדיקות שלנו, אנו יכולים למשל לטעון דפים ללא CSS וללא תמונות
  5. להפיל בכוונה את האפליקציה ולראות כיצד המערכת מתנהגת, מה למשל נשלח לשרת
  6. ועוד הרבה מאוד פונקציונליות שאני מאמין שרק תלך ותגדל עם שיחרורי גרסאות עתידיות (4.1 , 4.2 וכו')

 

דוגמת קוד של איסוף לוגים מהדפדפן (על אתר Ynet):

public class CheckingConsoleLog
{
    WebDriver driver;

    @BeforeClass
    public void startSession() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        DevTools tool = ((ChromeDriver)driver).getDevTools();
        tool.createSession();
        tool.send(Log.enable());
        tool.addListener(Log.entryAdded(), entry -> System.out.println(entry.asSeleniumLogEntry()));
        driver.get("http://ynet.co.il");
    }
    
    @Test
    public void test1() {
        // Do something ...
    }

    @AfterClass
    public void closeSession() {
        driver.quit();
    }
}

 

דוגמת קוד של התחברות לאתר לא מאובטח, לדוגמא: https://expired.badssl.com

public class LoginInsecureSite
{
    WebDriver driver;
    
    @BeforeClass
    public void startSession() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        DevTools tool = ((ChromeDriver) driver).getDevTools();
        tool.createSession();
        tool.send(Security.enable());
        tool.send(Security.setIgnoreCertificateErrors(true));
        driver.get("https://expired.badssl.com");
    }
    @Test
    public void test1() {
        // Do Something ...
    }
    
    @AfterClass
    public void closeSession() {
        driver.quit();
    }
}

קורסים דיגיטליים בעתיד האוטומציה

 

היכולות של גרסת Selenium 4.0 לא מסתיימות כאן, ישנם עוד פיצ'רים נוספים (פחות קריטיים, אבל בכל זאת…) שאולי נמצא אותם מעניינים:

– צילום מסך של אלמנטים, שימו לב, לא צילום מסך של דפים – אלא של אלמנטים בודדים. בגרסאות קודמות, היינו יכולים לקחת צילום מסך של אלמנט, אבל זה היה כל כך מורכב ומעאפן, היינו לקוחים צילום מסך של כל העמוד ואח"כ גוזרים קואורדינטות של אלמנט מסוים , מפחיתים מהמיימדים של המסך… פיכס… פה, בגרסת 4, יש לנו פשוט מתודה שעושה לנו את כל ה-"פיכס" הזה מאחורי הקלעים, אנו צריכים בסה"כ לקרוא לה. בום.

public class ElementScreenShot
{
    WebDriver driver;

    @BeforeClass
    public void startSession() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("http://imdb.com");
    }

    @Test
    public void test1() throws IOException {
        WebElement logo = driver.findElement(By.id("home_img"));
        File source = logo.getScreenshotAs(OutputType.FILE);
        FileUtils.copyFile(source, new File("./images/logo.png"));
    }

    @AfterClass
    public void closeSession() {
        driver.quit();
    }
}

 

– פתיחת טאב חדש או חלון חדש ע"י קריאה למתודה מובנית, ראו דוגמת קוד:

public class OpenNewTabWindow
{
    WebDriver driver;

    @BeforeClass
    public void startSession() {
        WebDriverManager.chromedriver().setup();
        driver = new ChromeDriver();
        driver.manage().window().maximize();
        driver.get("http://imdb.com");
    }

    @Test
    public void test1() {

        // Opens a new tab and switches to new tab
        driver.switchTo().newWindow(WindowType.TAB);
        driver.get("http://google.com");

        // Opens a new window and switches to new window
        driver.switchTo().newWindow(WindowType.WINDOW);
        driver.get("http://bing.com");
    }

    @AfterClass
    public void closeSession() {
        driver.quit();
    }
}

 

– הוסיפו לנו מתודה חדשה שמתקינה ומסירה של Add-Ons של פיירפוקס, פשוט, קל, מהיר, בדוגמת הקוד כאן, אני מתקין ומסיר את ה-Add-on של Selenium IDE:

public class InstallUninstallFirefoxPlugins
{
    WebDriver driver;

    @BeforeClass
    public void startSession() {
        WebDriverManager.firefoxdriver().setup();
        driver = new FirefoxDriver();
        driver.manage().window().maximize();
    }

    @Test
    public void test1() {
        // Setting the Extension Path - Selenium IDE
        Path path = Paths.get("./Extensions/selenium_ide-3.17.0-fx.xpi");

        // Installing Firefox Add-on (Extension)
        String extID = ((FirefoxDriver)driver).installExtension(path);

        // UnInstalling Firefox Add-on (Extension)
        ((FirefoxDriver)driver).uninstallExtension(extID);
    }

    @AfterClass
    public void closeSession() {
        driver.quit();
    }
}

 

– שיפרו את מערכת ה-logging של סלניום, ניתן כעת לדבג את הקוד שלנו בצורה ברורה יותר

– מערך הדוקומנטציה השתפר מאוד מאז גרסת 3, עדיין הדוקומנטציה הרישמית של סלניום היא לא שיא פאר היצירה, בטח אם משווים אותה לפריימוורקים אחרים כמו WebDriverIO או Cypress , אבל בכל זאת זוהי התקדמות יפה

– בגרסה זו החליטו להפסיק לתמוך בדפדפן Opera וכן ב-PhantomJS, צוות המפתחים טוען כי לגביי שימוש ב-Headless Browsers ניתן כיום להריץ על Headless Chrome (על חשבון ה-PhantomJS) ואילו דפדפן ה-Opera ממומש מעל מנוע ה-Chromium כך שאם נבדוק את גוגל כרום למשל או את דפדפן ה-Chromium – זה למעשה כמעט כמו לבדוק את ה-Opera שכן הרבה מהקוד הוא אותו קוד.

– כל מה שהוצהר בגרסת 3 שהולך להיות Deprecated, יצא (Removed) כבר בגרסת 4, כך שמפתחי האוטומציה צריכים להיערך לכך בהתאם.

 

כאמור, הגרסה הנוכחית לזמן כתיבת שורות אלו הינה Selenium 4.0 Alpha Version 6, באופן אישי אני ממתין בקוצר רוח לגרסה הרישמית בשביל להתחיל ולעבוד עליה באופן מסיבי, כעת אני חווה בשינויי גרסאות האלפא – שינויים מהותיים בקוד עצמו ובתמיכה, כך שכעת לא מומלץ לבסס את מערך הבדיקות שלכם על גרסאות אלו.

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

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

 

מוצג כאן ה-Webinar של סלניום 4.0 אותו קיימתי לפני מספר ימים, תהנו:

השאר הערה\הודעה