מעטפת Headless Chrome

Headless Chrome shell שוחרר ב-Chrome 59. זו דרך להריץ את דפדפן Chrome בסביבה ללא ממשק משתמש. בעצם, מריצים את Chrome בלי Chrome! הוא מביא לשורת הפקודה את כל התכונות המודרניות של פלטפורמת האינטרנט שמסופקות על ידי Chromium ומנוע העיבוד Blink.

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

הפעלה של דפדפן בלי GUI ‏ (CLI)

הדרך הכי קלה להתחיל עם מצב ללא ראש היא לפתוח את קובץ ה-binary של Chrome משורת הפקודה. אם מותקנת אצלכם גרסה Chrome 59 ומעלה, מפעילים את Chrome עם הדגל --headless:

chrome \
--headless \                   # Runs Chrome in headless mode.
--disable-gpu \                # Temporarily needed if running on Windows.
--remote-debugging-port=9222 \
https://www.chromestatus.com   # URL to open. Defaults to about:blank.

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

אם אתם משתמשים בערוץ היציב של Chrome ולא מצליחים להוריד את גרסת הבטא, תוכלו להשתמש ב-chrome-canary:

alias chrome="/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome"
alias chrome-canary="/Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary"
alias chromium="/Applications/Chromium.app/Contents/MacOS/Chromium"

מורידים את Chrome Canary.

תכונות של שורת הפקודה

במקרים מסוימים, לא צריך לתכנת סקריפט ל-Headless Chrome. יש כמה תכונות ניסיוניות שימושיות בשורת הפקודה לביצוע משימות נפוצות.

הדפסה של ה-DOM

הדגל --dump-dom מדפיס את document.body.innerHTML ל-stdout:

    chrome --headless --disable-gpu --dump-dom https://www.chromestatus.com/


### Create a PDF

The `--print-to-pdf` flag creates a PDF of the page:

```shell
chrome --headless --disable-gpu --print-to-pdf https://www.chromestatus.com/

צילום מסך

כדי לצלם את המסך של דף, משתמשים בדגל --screenshot:

chrome --headless --disable-gpu --screenshot https://www.chromestatus.com/

# Size of a standard letterhead.
chrome --headless --disable-gpu --screenshot --window-size=1280,1696 https://www.chromestatus.com/

# Nexus 5x
chrome --headless --disable-gpu --screenshot --window-size=412,732 https://www.chromestatus.com/

הפעלה עם --screenshot תיצור קובץ בשם screenshot.png בספריית העבודה הנוכחית. אם אתם מחפשים צילומי מסך של דף מלא, התהליך קצת יותר מורכב. יש פוסט מצוין בבלוג של David Schnurr שכולל את כל המידע שצריך. מומלץ לעיין במאמר בנושא שימוש ב-Chrome ללא ממשק משתמש ככלי אוטומטי לצילום מסך .

מצב REPL (לולאת קריאה-הערכה-הדפסה)

הדגל --repl מריץ את Headless במצב שבו אפשר להעריך ביטויי JS בדפדפן, ישירות משורת הפקודה:

$ chrome --headless --disable-gpu --repl --crash-dumps-dir=./tmp https://www.chromestatus.com/
[0608/112805.245285:INFO:headless_shell.cc(278)] Type a Javascript expression to evaluate or "quit" to exit.
>>> location.href
{"result":{"type":"string","value":"https://www.chromestatus.com/features"}}
>>> quit
$

איך מנפים באגים ב-Chrome בלי ממשק משתמש של דפדפן?

כשמריצים את Chrome עם --remote-debugging-port=9222, מופעל מופע עם פרוטוקול כלי הפיתוח. הפרוטוקול משמש לתקשורת עם Chrome ולהפעלת מופע של דפדפן ללא ממשק משתמש. הוא משמש גם כלים כמו Sublime,‏ VS Code ו-Node לניפוי באגים מרחוק באפליקציה. #synergy

מכיוון שאין לכם ממשק משתמש של דפדפן כדי לראות את הדף, אתם צריכים לנווט אל http://localhost:9222 בדפדפן אחר כדי לבדוק שהכול פועל. תוצג רשימה של דפים שאפשר לבדוק. תוכלו ללחוץ על הדפים ולראות מה מוצג ב-Headless:

כלי פיתוח מרוחקים
ממשק משתמש לניפוי באגים מרחוק בכלי הפיתוח

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

שימוש פרוגרמטי (Node)

Puppeteer

Puppeteer היא ספריית Node שפותחה על ידי צוות Chrome. הוא מספק API ברמה גבוהה לשליטה ב-Chrome ללא ראש (או מלא). הוא דומה לספריות אחרות של בדיקות אוטומטיות כמו Phantom ו-NightmareJS, אבל הוא פועל רק עם הגרסאות העדכניות של Chrome.

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

מתקינים אותו:

npm i --save puppeteer

דוגמה – הדפסת סוכן המשתמש

const puppeteer = require('puppeteer');

(async() => {
  const browser = await puppeteer.launch();
  console.log(await browser.version());
  await browser.close();
})();

דוגמה – צילום מסך של הדף

const puppeteer = require('puppeteer');

(async() => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto('https://www.chromestatus.com', {waitUntil: 'networkidle2'});
  await page.pdf({path: 'page.pdf', format: 'A4'});

  await browser.close();
})();

מידע נוסף על ה-API המלא מופיע במאמרי העזרה של Puppeteer.

ספריית ה-CRI

chrome-remote-interface היא ספרייה ברמה נמוכה יותר מה-API של Puppeteer. מומלץ להשתמש בו אם רוצים להיות קרובים ל-metal ולהשתמש ישירות בפרוטוקול של כלי הפיתוח.

הפעלת Chrome

‫chrome-remote-interface לא מפעיל את Chrome בשבילכם, אז תצטרכו לעשות את זה בעצמכם.

בקטע CLI, הפעלנו את Chrome באופן ידני באמצעות --headless --remote-debugging-port=9222. עם זאת, כדי להפוך את הבדיקות לאוטומטיות באופן מלא, כנראה שתצטרכו להפעיל את Chrome מתוך האפליקציה.

אחת הדרכים היא להשתמש בchild_process:

const execFile = require('child_process').execFile;

function launchHeadlessChrome(url, callback) {
  // Assuming MacOSx.
  const CHROME = '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome';
  execFile(CHROME, ['--headless', '--disable-gpu', '--remote-debugging-port=9222', url], callback);
}

launchHeadlessChrome('https://www.chromestatus.com', (err, stdout, stderr) => {
  ...
});

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

שימוש ב-ChromeLauncher

Lighthouse הוא כלי מצוין לבדיקת האיכות של אפליקציות האינטרנט שלכם. מודול חזק להפעלת Chrome פותח ב-Lighthouse, ועכשיו הוא מופרד לשימוש עצמאי. מודול NPM‏ chrome-launcher ימצא את המיקום שבו Chrome מותקן, יגדיר מופע לניפוי באגים, יפעיל את הדפדפן ויסגור אותו כשהתוכנית תסתיים. החלק הכי טוב הוא שהיא פועלת בפלטפורמות שונות בזכות Node!

כברירת מחדל, chrome-launcher ינסה להפעיל את Chrome Canary (אם הוא מותקן), אבל אפשר לשנות את זה כדי לבחור ידנית באיזו גרסה של Chrome להשתמש. כדי להשתמש בו, קודם מתקינים אותו מ-npm:‏

npm i --save chrome-launcher

דוגמה – שימוש ב-chrome-launcher להפעלת דפדפן ללא GUI

const chromeLauncher = require('chrome-launcher');

// Optional: set logging level of launcher to see its output.
// Install it using: npm i --save lighthouse-logger
// const log = require('lighthouse-logger');
// log.setLevel('info');

/**
 * Launches a debugging instance of Chrome.
 * @param {boolean=} headless True (default) launches Chrome in headless mode.
 *     False launches a full version of Chrome.
 * @return {Promise<ChromeLauncher>}
 */
function launchChrome(headless=true) {
  return chromeLauncher.launch({
    // port: 9222, // Uncomment to force a specific port of your choice.
    chromeFlags: [
      '--window-size=412,732',
      '--disable-gpu',
      headless ? '--headless' : ''
    ]
  });
}

launchChrome().then(chrome => {
  console.log(`Chrome debuggable on port: ${chrome.port}`);
  ...
  // chrome.kill();
});

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

כדי לשלוט בדפדפן, אנחנו צריכים את פרוטוקול DevTools!

שליפת מידע על הדף

כדי להתקין את הספרייה:

npm i --save chrome-remote-interface
דוגמאות

דוגמה – הדפסת סוכן המשתמש

const CDP = require('chrome-remote-interface');

...

launchChrome().then(async chrome => {
  const version = await CDP.Version({port: chrome.port});
  console.log(version['User-Agent']);
});

התוצאה תהיה משהו כזה: HeadlessChrome/60.0.3082.0

דוגמה – בדיקה אם באתר יש קובץ מניפסט של אפליקציית אינטרנט

const CDP = require('chrome-remote-interface');

...

(async function() {

const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});

// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page} = protocol;
await Page.enable();

Page.navigate({url: 'https://www.chromestatus.com/'});

// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
  const manifest = await Page.getAppManifest();

  if (manifest.url) {
    console.log('Manifest: ' + manifest.url);
    console.log(manifest.data);
  } else {
    console.log('Site has no app manifest');
  }

  protocol.close();
  chrome.kill(); // Kill Chrome.
});

})();

דוגמה – חילוץ <title> של הדף באמצעות ממשקי DOM API.

const CDP = require('chrome-remote-interface');

...

(async function() {

const chrome = await launchChrome();
const protocol = await CDP({port: chrome.port});

// Extract the DevTools protocol domains we need and enable them.
// See API docs: https://chromedevtools.github.io/devtools-protocol/
const {Page, Runtime} = protocol;
await Promise.all([Page.enable(), Runtime.enable()]);

Page.navigate({url: 'https://www.chromestatus.com/'});

// Wait for window.onload before doing stuff.
Page.loadEventFired(async () => {
  const js = "document.querySelector('title').textContent";
  // Evaluate the JS expression in the page.
  const result = await Runtime.evaluate({expression: js});

  console.log('Title of page: ' + result.result.value);

  protocol.close();
  chrome.kill(); // Kill Chrome.
});

})();

שימוש ב-Selenium, ב-WebDriver וב-ChromeDriver

בשלב הזה, Selenium פותח מופע מלא של Chrome. במילים אחרות, זהו פתרון אוטומטי אבל לא חסר ראש לחלוטין. עם זאת, אפשר להגדיר את Selenium להפעלה של Chrome ללא ראש, אבל זה דורש קצת עבודה. מומלץ להריץ את Selenium עם Headless Chrome אם רוצים לקבל את ההוראות המלאות להגדרה עצמית, אבל הוספתי כמה דוגמאות בהמשך כדי לעזור לכם להתחיל.

שימוש ב-ChromeDriver

ChromeDriver 2.32 משתמש ב-Chrome 61 ועובד היטב עם Headless Chrome.

התקנה:

npm i --save-dev selenium-webdriver chromedriver

דוגמה:

const fs = require('fs');
const webdriver = require('selenium-webdriver');
const chromedriver = require('chromedriver');

const chromeCapabilities = webdriver.Capabilities.chrome();
chromeCapabilities.set('chromeOptions', {args: ['--headless']});

const driver = new webdriver.Builder()
  .forBrowser('chrome')
  .withCapabilities(chromeCapabilities)
  .build();

// Navigate to google.com, enter a search.
driver.get('https://www.google.com/');
driver.findElement({name: 'q'}).sendKeys('webdriver');
driver.findElement({name: 'btnG'}).click();
driver.wait(webdriver.until.titleIs('webdriver - Google Search'), 1000);

// Take screenshot of results page. Save to disk.
driver.takeScreenshot().then(base64png => {
  fs.writeFileSync('screenshot.png', new Buffer(base64png, 'base64'));
});

driver.quit();

שימוש ב-WebDriverIO

WebDriverIO הוא API ברמה גבוהה יותר מעל Selenium WebDriver.

התקנה:

npm i --save-dev webdriverio chromedriver

דוגמה: סינון תכונות CSS ב-chromestatus.com

const webdriverio = require('webdriverio');
const chromedriver = require('chromedriver');

const PORT = 9515;

chromedriver.start([
  '--url-base=wd/hub',
  `--port=${PORT}`,
  '--verbose'
]);

(async () => {

const opts = {
  port: PORT,
  desiredCapabilities: {
    browserName: 'chrome',
    chromeOptions: {args: ['--headless']}
  }
};

const browser = webdriverio.remote(opts).init();

await browser.url('https://www.chromestatus.com/features');

const title = await browser.getTitle();
console.log(`Title: ${title}`);

await browser.waitForText('.num-features', 3000);
let numFeatures = await browser.getText('.num-features');
console.log(`Chrome has ${numFeatures} total features`);

await browser.setValue('input[type="search"]', 'CSS');
console.log('Filtering features...');
await browser.pause(1000);

numFeatures = await browser.getText('.num-features');
console.log(`Chrome has ${numFeatures} CSS features`);

const buffer = await browser.saveScreenshot('screenshot.png');
console.log('Saved screenshot...');

chromedriver.stop();
browser.end();

})();

הורדה של Headless Chrome ישן כ-chrome-headless-shell

החל מגרסה 112 של Chrome, אפשר להשתמש במצב Headless החדש של Chrome‏ (--headless=new). במצב הזה, מפתחים יכולים להריץ את Chrome עצמו ולא קובץ בינארי נפרד בסביבה לא מאוישת בלי ממשק משתמש גלוי. זה שימושי לבדיקות ולתרחישי שימוש באוטומציה.

יש תרחישי שימוש שונים לגרסה הישנה של Headless Shell ולמצב החדש 'דפדפן ללא GUI':

  1. ה-Headless shell הישן הוא עטיפה קלה סביב מודול //content של Chromium, ולכן יש לו הרבה פחות תלות. בפרט, הוא לא דורש X11/Wayland, ‏ D-Bus, ובמובנים מסוימים הביצועים שלו טובים יותר מאלה של דפדפן Chrome המלא. לכן הוא מתאים לתרחישי שימוש כמו צילומי מסך אוטומטיים או גירוד נתונים מהאינטרנט.
  2. לעומת זאת, מצב Headless החדש הוא דפדפן Chrome אמיתי, ולכן הוא אותנטי ואמין יותר, ומציע יותר תכונות. לכן הוא מתאים יותר לבדיקות מדויקות של אפליקציות אינטרנט מקצה לקצה או של תוספים לדפדפן.

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

דיאגרמה שממחישה את המידע שמופיע ברשימה הקודמת.

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

כדי להבטיח שמפתחים ובודקים יוכלו להמשיך לבחור בין שתי האפשרויות האלה, אנחנו שמחים להודיע שההטמעה הישנה של Headless זמינה עכשיו כקובץ בינארי עצמאי chrome-headless-shell. קובצי ה-chrome-headless-shell הבינאריים החדשים האלה נוצרים לכל מהדורה של Chrome שפונה למשתמשים, וזמינים להורדה דרך התשתית של Chrome for Testing החל מ-Chrome 120.

איך אפשר לקבל קובצי chrome-headless-shell בינאריים?

בדומה לקבצים בינאריים אחרים של Chrome for Testing, הדרך הכי קלה להוריד את chrome-headless-shell לפלטפורמה שלכם היא באמצעות כלי שורת הפקודה @puppeteer/browsers שלנו, שזמין באמצעות npm. הנה כמה דוגמאות:

# Download the latest available `chrome-headless-shell` binary corresponding to the Stable channel.
npx @puppeteer/browsers install chrome-headless-shell@stable

# Download a specific `chrome-headless-shell` version.
npx @puppeteer/browsers install chrome-headless-shell@120.0.6098.0

אם אתם מעדיפים ליצור סקריפטים אוטומטיים משלכם להורדת קבצים בינאריים של chrome-headless-shell, אנחנו יכולים לעזור לכם. ‫Chrome for Testing מציע נקודות קצה של API בפורמט JSON עם הגרסאות הזמינות האחרונות לכל ערוץ הפצה של Chrome (יציב, בטא, Dev ו-Canary). כדי לקבל סקירה מהירה של הסטטוס העדכני, אפשר לעיין בלוח הבקרה של הזמינות של Chrome for Testing.

משוב

נשמח לקבל ממך משוב על chrome-headless-shell. אם נתקלתם בבעיות, אתם יכולים לדווח עליהן.

מקורות מידע נוספים

ריכזנו כאן כמה מקורות מידע שימושיים שיעזרו לכם להתחיל:

Docs

כלים

  • chrome-remote-interface – מודול node שעוטף את פרוטוקול כלי הפיתוח
  • Lighthouse – כלי אוטומטי לבדיקת האיכות של אפליקציות אינטרנט. נעשה בו שימוש נרחב בפרוטוקול
  • chrome-launcher – מודול node להפעלת Chrome, מוכן לאוטומציה

הדגמות

  • ‫"The Headless Web" (האינטרנט ללא ממשק) – פוסט מצוין בבלוג של פול קינלן על שימוש ב-Headless עם api.ai.

שאלות נפוצות

האם צריך את הדגל --disable-gpu?

רק ב-Windows. פלטפורמות אחרות כבר לא דורשות את זה. הדגל --disable-gpu הוא פתרון זמני לכמה באגים. לא יהיה צורך בדגל הזה בגרסאות עתידיות של Chrome. מידע נוסף זמין בכתובת crbug.com/737678

אז עדיין צריך Xvfb?

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

מה זה Xvfb? ‫Xvfb הוא שרת תצוגה בזיכרון למערכות דמויות Unix, שמאפשר להריץ אפליקציות גרפיות (כמו Chrome) בלי צג פיזי מחובר. אנשים רבים משתמשים ב-Xvfb כדי להריץ גרסאות קודמות של Chrome לצורך בדיקות בדפדפן בלי GUI.

איך יוצרים קונטיינר Docker שמריץ Headless Chrome?

אתם יכולים לעיין ב-lighthouse-ci. יש בו קובץ Docker לדוגמה שמשתמש ב-node:8-slim כתמונת בסיס, מתקין את Lighthouse ומריץ אותו ב-App Engine Flex.

האם אפשר להשתמש בזה עם Selenium / WebDriver / ChromeDriver?

כן. מידע נוסף על שימוש ב-Selenium, ב-WebDriver וב-ChromeDriver

מה הקשר בין זה לבין PhantomJS?

‫Headless Chrome דומה לכלים כמו PhantomJS. אפשר להשתמש בשניהם לבדיקות אוטומטיות בסביבה ללא דפדפן GUI. ההבדל העיקרי בין שתי השיטות הוא ש-Phantom משתמש בגרסה ישנה של WebKit כמנוע הרינדור שלו, בעוד ש-Headless Chrome משתמש בגרסה העדכנית של Blink.

בשלב הזה, Phantom מספק גם API ברמה גבוהה יותר מפרוטוקול כלי הפיתוח.

איפה מדווחים על באגים?

אם מצאתם באגים ב-Headless Chrome, אתם יכולים לדווח עליהם בכתובת crbug.com.

אם אתם מוצאים באגים בפרוטוקול כלי הפיתוח, אתם יכולים לדווח עליהם בכתובת github.com/ChromeDevTools/devtools-protocol.