תחילת העבודה עם דפדפן 'דפדפן ללא GUI'

אריק בידלמן

אמ;לק

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

למה זה שימושי?

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

הפעלה ללא ממשק גרפי (CLI)

הדרך הקלה ביותר להתחיל את מצב 'דפדפן ללא GUI' היא לפתוח את הקובץ הבינארי של 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 כאן.

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

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

הדפסת ה-DOM

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

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

יצירת קובץ PDF

הדגל --print-to-pdf יוצר קובץ PDF של הדף:

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 ללא GUI ככלי אוטומטי לצילום מסך .

מצב REPL (לולאת קריאה-eval-print)

הדגל --repl פועל ללא דפדפן GUI במצב שבו אפשר להעריך ביטויי 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, הוא מפעיל מכונה שבה פרוטוקול DevTools מופעל. הפרוטוקול משמש לתקשורת עם Chrome ולניהול מופע הדפדפן ללא דפדפן GUI. מדובר גם בכלים כמו Sublime, VS Code ו-Node לצורך ניפוי באגים מרחוק באפליקציה. #synergy

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

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

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

שימוש באופן פרוגרמטי (צומת)

בובנית

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

בין היתר, אפשר להשתמש ב-Puppeteer כדי לצלם בקלות צילומי מסך, ליצור קובצי PDF, לנווט בדפים ולאחזר מידע לגבי הדפים האלה. אני ממליץ על הספרייה אם אתם רוצים לבצע במהירות בדיקות דפדפן אוטומטיות. הוא מסתיר את המורכבות של פרוטוקול DevTools ומטפל במשימות מיותרות כמו הפעלת מכונה של 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();
})();

במסמכי התיעוד של Puppeteer תוכלו לקרוא מידע נוסף על ה-API המלא.

ספריית CRI

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

הפעלת 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 :(

שימוש ב'מרכז האפליקציות של Chrome'

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. זכרו, לא יהיה ממשק משתמש בדפדפן. אנחנו ללא GUI.

כדי לשלוט בדפדפן, אנחנו זקוקים לפרוטוקול 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> של הדף באמצעות ממשקי API של DOM.

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 ללא GUI עם מעט עבודה. מומלץ להפעיל את Selenium עם Chrome ללא ממשק גרפי אם ברצונך לקבל את ההוראות המלאות להגדרה בעצמך, אך הנה כמה דוגמאות שיעזרו לך להתחיל.

שימוש ב-ChromeDriver

ChromeDriver 2.32 משתמש ב-Chrome 61 ופועל היטב עם Chrome ללא GUI.

התקנה:

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();

})();

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

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

Docs

כלים

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

הדגמות

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

שאלות נפוצות

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

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

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

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

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

איך יוצרים קונטיינר Docker עם דפדפן Chrome ללא ממשק גרפי?

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

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

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

איך זה קשור ל-PantomJS?

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

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

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

לאיתור באגים ב-Chrome ללא ממשק גרפי, יש לדווח עליהם בכתובת crbug.com.

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