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

אמ;לק

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

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

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

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

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

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

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

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

בובנאי

Puppeteer היא ספריית Node שפותחה על ידי צוות Chrome. הוא מספק ממשק API ברמה גבוהה כדי לשלוט ב-Chrome ללא GUI (או באופן מלא). היא דומה לספריות בדיקה אוטומטיות אחרות כמו 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();
})();

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

ספריית ה-CRI

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

הפעלה של Chrome

ממשק Chrome-מרחוק לא מפעיל את 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, ועכשיו הוא מחולץ לשימוש עצמאי. המודול chrome-launcher NPM יאתר את המיקום שבו מותקן Chrome, יגדיר מופע של ניפוי באגים, יפעיל את הדפדפן ויסגור אותו בסיום התוכנה. היתרון הכי חשוב הוא שהכלים האלה פועלים בפלטפורמות שונות הודות ל-Node!

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

npm i --save chrome-launcher

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

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

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

מתבצע אחזור של מידע על הדף

נתקין את הספרייה:

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. במילים אחרות, זהו פתרון אוטומטי, אבל לא לגמרי בלי GUI. עם זאת, אפשר להגדיר את Selenium להרצת Chrome ללא GUI, עם מעט עבודה. אם אתם רוצים את ההוראות המלאות להגדרה בעצמכם, אבל פירטתי למטה כמה דוגמאות שיעזרו לכם להתחיל, מומלץ להפעיל את Selenium עם Headless 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 – מודול של צומת שעוטף את פרוטוקול כלי הפיתוח
  • Lighthouse – כלי אוטומטי לבדיקת האיכות של אפליקציות אינטרנט, שמנצל הרבה שימוש בפרוטוקול
  • chrome-Launchs – מודול צמתים להפעלת 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 לביצוע בדיקות "ללא GUI".

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

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

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

כן. ראו שימוש ב-Selenium, ב-WebDriver וב-ChromeDriver.

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

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

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

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

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

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