העולם המדהים של Chrome debugging

איך תוכנות שונות מפעילות את כרום כרצונן? איך דיבאגר בפרונט עובד? צלילה לעומק לתוך העולם המופלא של CDP

לא מזמן הייתי מעורב בפרויקט שבו הייתי צריך להשתמש באוטומציה של דפדפן. כשאנחנו מפעילים אוטומציה של דפדפן, יש שתי דרכים עיקריות להפעיל אותה. הראשונה היא באמצעות תוסף לדפדפן והשניה היא באמצעות מערכות אוטומציה כמו סלניום (שנחשבת ישנה יותר) ו puppeteer או Playwright וחבריהם שמתכנתי פרונט אנד משתמשים בה לבדיקות.

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

גם לפני כן, העולם הזה די סיקרן אותי. איך בדיוק אני יכול להפעיל מסקריפט של Node כרום שמציית לפקודות? איך זה בדיוק עובד? זה שימושי במיוחד אם אני עובד על אפליקציות מבוססות LLM שרוצות לגשת לעולם החיצוני או עם CLI או עם MCP.

אז בואו ונתחיל, כרום יכול להיות פתוח עבור שימוש לא גרפי באמצעות פרוטוקול שנקרא CDP – ראשי תיבות של Chrome DevTool Protocol. בדיוק כמו MCP ופרוטוקולים אחרים, מדובר בפרוטוקול שכרום מפעיל (גם דפדפנים אחרים מבוססי כרומיום) שמאפשר לכל אפליקציה תקשורת איתו. מי משתמש בו? כן כן, כלי המפתחים. כלי המפתחים בעצם הוא אפליקציה נפרדת שמתקשרת עם כרום. זה די מעיף את הראש כשחושבים על זה.

רוצים לראות? בטח שרוצים לראות! מי שלא רוצה את התיווך שלי יכול לרוץ ישר אל אתר ה-CDP שהוא די ברור ונאה ויש שם הסברים.

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

השלב הבא הוא ללחוץ על שלוש הנקודות בכלי המפתחים, ואז על More tools ואז על Protocol Monitor

עכשיו תראו שיש לכם לשונית נוספת של CDP. תשחקו קצת עם אתר כלשהו ותראו כמה events נשלחים. המון! ליטרלי כל דבר שהדפדפן עושה? נשלח event. מפרסר CSS, תמונה, אירועי DOM. הכל! זה די מהמם כשרואים את זה לראשונה. מומלץ לפלטר לפי סוגי אירועים. אפשר להתחיל עם Network.requestWillBeSent. זה אירוע שנורה כאשר הדפדפן מתכוון לטעון דף. שימו אותו בפילטר ותטענו דף – תראו שיש אובייקט. האובייקט גם נמצא בדוקומנטציה.

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


בואו ונראה איך אפשר להשפיע! נבחר ב-target את הלשונית האקטיבית שלנו, נכניס בפרמטר את הפקודה Emulation.setGeolocationOverride ונכניס את הפרמטרים:

"latitude": 48.8584
"longitude": 2.2945
"accuracy": 1


נכנס אל האתר https://browserleaks.com/geo

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

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

כלים חיצוניים שעובדים עם CDP

עכשיו, אחרי שהבנו איך כרום עובד, קל לנו להבין איך puppeteer, playwright וכלים אחרים עובדים איתו. הם פשוט משדרים דרך ה-CDP! בואו ונראה איך זה קורה בפועל. אני מראה על מק, זה עובד כמובן דרך חלונות ודרך דרכים אחרות.

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

/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome \
  --remote-debugging-port=9222 \
  --user-data-dir="/tmp/chrome-debug-profile-new" \
  --no-first-run

אני אומר לו פה: ״חבוב, ברכותיי, כל מי שמגיע דרך פורט 9222? אתה תקשיב מה שיש לו לומר לך.״

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

מפתחי פרונט אנד: אם תהיתם, איך יכול להיות שברייקפוינט שאני שם ב-Vscode משפיע על כרום ולהיפך? זה בדיוק בגלל זה. כשאנו שמים ברייקפוינט ב-vscode, אנחנו שולחים ב-CDP משהו כזה:

{
  "id": 50,
  "method": "Debugger.setBreakpointByUrl",
  "params": {
    "lineNumber": 42,
    "urlRegex": "app.js"
  }
}

כשהכרום מגיע לשורה הזו, הוא קופא ושולח אירוע

{
  "method": "Debugger.paused",
  "params": {
    "reason": "breakpoint",
    "callFrames": [ ... ] 
  }
}

ו-VScode יודע לצבוע את השורה בצהוב ולעבוד כמו שצריך.

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

curl http://localhost:9222/json

אתם תקבלו תשובה! זה לא נעשה בפרוטוקול CDP כמובן אבל זה מראה שהפורט פתוח ואפשר לעבוד. ליטרלי כל אחד שיתקשר דרך הפורט יקבל תשובה. טוב, בואו ונראה איך זה עובד! ניצור puppeteer. צרו פרויקט npm בתיקיה כלשהי עם npm init ואז נתקין את puppeteer core.

npm install puppeteer-core

עכשיו נכתוב את הקוד, כלומר לא אני חלילה אלא קורסור או גיטהאב קופיילוט או ChatGPT, ביקשתי ממנו לכתוב קוד שיתחבר לכרום בדיבאג מוד ויכנס ל-internet-israel.com. הקוד משתמש באינטרפייס של puppeteer אבל הוא בתורו מריץ CDP.

const puppeteer = require('puppeteer-core');

(async () => {
  try {
    // Connect to your existing Chrome instance
    // We use 127.0.0.1 because localhost failed for you earlier
    const browser = await puppeteer.connect({
      browserURL: 'http://127.0.0.1:9222'
    });

    console.log('✅ Connected to Chrome!');

    // Get the current active tab (page)
    const pages = await browser.pages();
    const page = pages[0]; // usually the first open tab

    // Example Action 1: Navigate
    console.log('Navigating to https://internet-israel.com...');
    await page.goto('https://internet-israel.com', { waitUntil: 'domcontentloaded' });

    // Example Action 2: Extract Data (Lightweight scraping)
    const title = await page.title();
    console.log(`Page Title: ${title}`);

    // Example Action 3: Execute Javascript in the browser console
    // This turns the background red
    await page.evaluate(() => {
      document.body.style.backgroundColor = 'red';
    });

    // Disconnect so the script stops, but Chrome stays open
    browser.disconnect();

  } catch (error) {
    console.error('Connection failed:', error);
  }
})();

הקוד הזה יעבוד יפה מאד. עכשיו אני יודע גם מה קורה מאחורי הקלעים, אבל הרבה יותר נעים וכיפי לראות את זה ממש קורה. איך עושים את זה? נפתח wireshark. למי שלא מכיר? זו תוכנה לניטור רשת שכדאי מאד להכיר, כתובה בקוד פתוח ועובדת על מק, לינוקס וחלונות. כתבתי על Wireshark פה. נכון, אנחנו רגילים בד״כ להשתמש ב-Wiresharj כדי להציץ על תנועות ברשת אבל הוא יכול בהחלט להציץ גם בתנועות שקורות בתוך המחשב. התנועה היא בפרוטוקול http ואחרי כן בסוקט אבל היא לא עוברת ברשת. המחשב מדבר עם עצמו. אז אנחנו נתפוס את lo0 ובאנגלית loopback. זה היכן שהמחשב מדבר עם עצמו.

נבקש מ-Wireshark להאזין ל lo0 ונכניס בפילטר את tcp.port == 9222. זה הפורט שבו בחרנו. אנחנו יכולים להשתמש בכל פורט אגב. אבל 9222 איט איז. נריץ את כרום בדיאבג מוד על פורט 9222 ונריץ את קליינט ה-puppeteer שלנו. נראה ברדק רציני ביותר. כרום מציף את CDP בהמון המון אירועים ורעש אדיר. בואו נסתכל אבל על ההתחלה.

אתם תראו קודם כל את ה-Handshake של ה-TCP (אולי זה רעיון טוב לכתוב על זה לעומק, אבל זה בפוסט אחר). בין פורט 9222 שבו הכרום שלנו משתמש לבין פורט אחר שהוא זה שהסקריפט שלי לוקח. במקרה שלי זה 52183 אבל זה באמת פורט אקראי. אחרי זה, הבקשה הראשונה בפרוטוקול HTTP היא מהכיוון של puppeteer לכרום בבקשה לקבל את הגרסאות.

GET /json/version HTTP/1.1 

אם תסתכלו על הסקריפט שלנו, לא תראו שום זכר לזה. מי עושה את זה? חבילת ה-puppeteer שלנו. הרי בסופו של דבר, הייתי יכול לממש לבד את כל בקשות ה-CDP. אבל ה-puppeteer עושה לי את זה וכחלק מהשירות, הוא גם מבצע בדיקה לראות אם בכלל הוא יכול ליצור קשר עם כרום, אם הפורט פתוח ואיזו גרסת CDP הוא משתמש ואם משהו לא בסדר הוא ידפיס שגיאה (למשל: הי, אני לא מוצא אף אחד בפורט 9222, אמרת לי שיהיה שם מישהו!). זה מה שטוב בספריות, אבל puppeteer הוא לא קסם.

כרום עונה באובייקט JSON:

{
   "Browser": "Chrome/143.0.7499.41",
   "Protocol-Version": "1.3",
   "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36",
   "V8-Version": "14.3.127.16",
   "WebKit-Version": "537.36 (@09d0e08b622603fde13600b061231d0f1e54957e)",
   "webSocketDebuggerUrl": "ws://127.0.0.1:9222/devtools/browser/eb586418-3922-4700-8e1c-973bd400e864"
}

השלב הבא הוא בקשה מהצד של ה-Puppeteer :

GET /devtools/browser/eb586418-3922-4700-8e1c-973bd400e864 HTTP/1.1 

יש גם header מעניין לבקשה הזו:

Upgrade: websocket\r\n

ה-headers הם חלק מפרוטוקול http. פה ה-Puppeteer מבקש בעצם מכרום: ״שמע נא חמוד, האם אתה יכול לעבור לפרוטוקול websocket?״

כרום עונה לו ב-WebSocket Protocol Handshake ומפה אנחנו זונחים את HTTP ועוברים לוובסוקט, שזה פרוטוקול יותר מהיר, דו כיווני ואלגנטי, ואם נבחן את הפקטות נראה שבעצם המידע שעובר שם הוא בפרוטוקול CDP:

אם נציץ במידע, נראה משהו כזה – זה בדיוק CDP

{
  "method": "Target.setDiscoverTargets",
  "params": {
    "discover": true,
    "filter": [
      {}
    ]
  },
  "id": 2
}

ה-setDiscoverTargets מופיע בתיעוד של הפרוטוקול וברור מאד מה הוא מעביר.

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

{
  "method": "Runtime.executionContextCreated",
  "params": {
    "context": {
      "id": "1",
      "origin": "blob:https://internet-israel.com/af43131a-633f-4861-a801-aba47c7e5469",
      "name": "wpTestEmojiSupports"
    }
  }
}

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

פוסטים נוספים שכדאי לקרוא

רשת האינטרנט

איך בונים custom GPT משלכם?

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

פתרונות ומאמרים על פיתוח אינטרנט

Retry decorator בפייתון

לא מזמן יצא לי לכתוב קוד בפייתון. הקוד משתמש ב-API של openAI ולפעמים ה-API הזה קצת מפשל. הוא מחזיר לי שגיאה ואומר שהגזמתי עם הקריאות,

יסודות בתכנות

backward compatibility ו forward compatibility

שלושה עקרונות חשובים בפיתוח תוכנה שכדאי להכיר במיוחד בעידן הבינה המלאכותית והקוד המהיר.

גלילה לראש העמוד