Bắt đầu với Chrome không có giao diện người dùng

TL;DR

Chrome không có giao diện người dùng sẽ được cung cấp trong Chrome 59. Đây là cách để chạy trình duyệt Chrome trong môi trường không có giao diện người dùng. Về cơ bản, bạn sẽ chạy Chrome mà không cần Chrome! Công cụ này mang tất cả các tính năng hiện đại của nền tảng web do Chromium và công cụ kết xuất Blink cung cấp vào dòng lệnh.

Tại sao điều đó hữu ích?

Trình duyệt không có giao diện người dùng là một công cụ tuyệt vời để kiểm thử tự động và môi trường máy chủ mà bạn không cần hiển thị giao diện người dùng (shell). Ví dụ: bạn có thể muốn chạy một số thử nghiệm trên một trang web thực, tạo tệp PDF của trang web đó hoặc chỉ kiểm tra cách trình duyệt hiển thị một URL.

Bắt đầu chế độ không có giao diện người dùng (CLI)

Cách dễ nhất để bắt đầu sử dụng chế độ không có giao diện người dùng là mở tệp nhị phân Chrome từ dòng lệnh. Nếu bạn đã cài đặt Chrome phiên bản 59 trở lên, hãy khởi động Chrome bằng cờ --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 sẽ trỏ đến vị trí cài đặt Chrome. Vị trí chính xác sẽ khác nhau tuỳ theo nền tảng. Vì đang sử dụng máy Mac, tôi đã tạo các bí danh thuận tiện cho từng phiên bản Chrome mà tôi đã cài đặt.

Nếu bạn đang sử dụng kênh chính thức của Chrome và không thể tải phiên bản Beta, bạn nên sử dụng 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"

Tải Chrome Canary xuống tại đây.

Tính năng dòng lệnh

Trong một số trường hợp, bạn có thể không cần viết tập lệnh theo phương thức lập trình cho Chrome không có giao diện người dùng. Có một số cờ dòng lệnh hữu ích để thực hiện các tác vụ phổ biến.

In DOM

Cờ --dump-dom in document.body.innerHTML vào stdout:

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

Tạo tệp PDF

Cờ --print-to-pdf tạo một tệp PDF của trang:

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

Đang chụp ảnh màn hình

Để chụp ảnh màn hình của một trang, hãy sử dụng cờ --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/

Việc chạy bằng --screenshot sẽ tạo một tệp có tên screenshot.png trong thư mục đang làm việc hiện tại. Nếu bạn đang tìm ảnh chụp màn hình toàn trang, thì mọi thứ liên quan hơn một chút. David Schnurr có một bài đăng tuyệt vời trên blog về vấn đề này. Hãy xem bài viết Sử dụng Chrome không có giao diện người dùng làm công cụ chụp ảnh màn hình tự động .

Chế độ REPL (vòng lặp đọc-eval-print)

Cờ --repl chạy Headless ở chế độ mà bạn có thể đánh giá biểu thức JS trong trình duyệt, ngay từ dòng lệnh:

$ 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
$

Gỡ lỗi Chrome mà không có giao diện người dùng trình duyệt?

Khi bạn chạy Chrome bằng --remote-debugging-port=9222, Chrome sẽ khởi động một thực thể đã bật giao thức DevTools. Giao thức này dùng để giao tiếp với Chrome và thúc đẩy phiên bản trình duyệt không có giao diện người dùng. Đây cũng là những gì các công cụ như Sublime, VS Code và Node sử dụng để gỡ lỗi từ xa một ứng dụng. #synergy

Vì bạn không có giao diện người dùng của trình duyệt để xem trang, hãy chuyển đến http://localhost:9222 trong một trình duyệt khác để kiểm tra nhằm đảm bảo mọi thứ đang hoạt động. Bạn sẽ thấy danh sách các trang có thể kiểm tra để nhấp vào và xem giao diện Headless đang hiển thị:

Điều khiển từ xa cho Công cụ cho nhà phát triển
Giao diện người dùng gỡ lỗi từ xa cho Công cụ cho nhà phát triển

Từ đây, bạn có thể sử dụng các tính năng quen thuộc của Công cụ cho nhà phát triển để kiểm tra, gỡ lỗi và chỉnh sửa trang như bình thường. Nếu bạn đang sử dụng Headless theo phương thức lập trình, trang này cũng là một công cụ gỡ lỗi mạnh mẽ để xem tất cả các lệnh giao thức DevTools thô được truyền qua mạng, giao tiếp với trình duyệt.

Sử dụng theo phương thức lập trình (Node)

Người đẩy

Puppeteer là một thư viện Node do nhóm Chrome phát triển. Thư viện này cung cấp API cấp cao để kiểm soát Chrome không có giao diện người dùng (hoặc phiên bản đầy đủ). Thư viện này tương tự như các thư viện kiểm thử tự động khác như Phantom và NightmareJS, nhưng chỉ hoạt động với các phiên bản Chrome mới nhất.

Ngoài ra, bạn có thể sử dụng Puppeteer để dễ dàng chụp ảnh màn hình, tạo tệp PDF, di chuyển trên các trang và tìm nạp thông tin về các trang đó. Bạn nên sử dụng thư viện này nếu muốn tự động hoá nhanh quá trình kiểm thử trình duyệt. Công cụ này ẩn đi sự phức tạp của giao thức DevTools và xử lý các tác vụ thừa như khởi chạy một phiên bản gỡ lỗi của Chrome.

Cài đặt:

npm i --save puppeteer

Ví dụ – in tác nhân người dùng

const puppeteer = require('puppeteer');

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

Ví dụ – chụp ảnh màn hình trang

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

Hãy xem tài liệu của Puppeteer để tìm hiểu thêm về API đầy đủ.

Thư viện CRI

chrome-remote-interface là một thư viện cấp thấp hơn so với API của Puppeteer. Bạn nên sử dụng tính năng này nếu muốn tiếp cận trực tiếp với mã nguồn và sử dụng giao thức DevTools.

Khởi chạy Chrome

chrome-remote-interface không chạy Chrome cho bạn, vì vậy, bạn sẽ phải tự xử lý việc đó.

Trong phần CLI, chúng tôi đã khởi động Chrome theo cách thủ công bằng --headless --remote-debugging-port=9222. Tuy nhiên, để tự động hoá hoàn toàn các chương trình kiểm thử, bạn có thể tạo Chrome từ ứng dụng của mình.

Một cách là sử dụng 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) => {
  ...
});

Tuy nhiên, mọi thứ sẽ trở nên khó khăn nếu bạn muốn có một giải pháp di động hoạt động trên nhiều nền tảng. Hãy xem đường dẫn được mã hoá cứng đó đến Chrome :(

Sử dụng Chromelauncher

Lighthouse là một công cụ tuyệt vời để kiểm tra chất lượng của ứng dụng web. Một mô-đun mạnh mẽ để khởi chạy Chrome đã được phát triển trong Lighthouse và hiện đã được trích xuất để sử dụng độc lập. mô-đun NPM chrome-launcher sẽ tìm nơi cài đặt Chrome, thiết lập một phiên bản gỡ lỗi, khởi chạy trình duyệt và tắt trình duyệt khi chương trình của bạn hoàn tất. Điều tuyệt vời nhất là ứng dụng này hoạt động trên nhiều nền tảng nhờ Node!

Theo mặc định, chrome-launcher sẽ cố gắng chạy Chrome Canary (nếu bạn đã cài đặt), nhưng bạn có thể thay đổi để chọn Chrome sẽ sử dụng theo cách thủ công. Để sử dụng, trước tiên, hãy cài đặt từ npm:

npm i --save chrome-launcher

Ví dụ – sử dụng chrome-launcher để khởi chạy 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();
});

Việc chạy tập lệnh này không ảnh hưởng nhiều, nhưng bạn sẽ thấy một thực thể của Chrome kích hoạt trong trình quản lý tác vụ đã tải about:blank. Hãy nhớ rằng sẽ không có bất kỳ giao diện người dùng trình duyệt nào. Chúng ta đang ở chế độ không có giao diện người dùng.

Để điều khiển trình duyệt, chúng ta cần có giao thức Công cụ cho nhà phát triển!

Truy xuất thông tin về trang

Hãy cài đặt thư viện:

npm i --save chrome-remote-interface
Ví dụ

Ví dụ – in tác nhân người dùng

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

...

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

Kết quả sẽ có dạng như sau: HeadlessChrome/60.0.3082.0

Ví dụ – kiểm tra xem trang web có tệp kê khai ứng dụng web hay không

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.
});

})();

Ví dụ – trích xuất <title> của trang bằng các 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.
});

})();

Sử dụng Selenium, WebDriver và ChromeDriver

Hiện tại, Selenium mở một phiên bản đầy đủ của Chrome. Nói cách khác, đây là một giải pháp tự động nhưng không hoàn toàn không có giao diện người dùng. Tuy nhiên, bạn có thể định cấu hình Selenium để chạy Chrome không có giao diện người dùng mà không tốn nhiều công sức. Bạn nên Chạy Selenium bằng Chrome không có giao diện người dùng nếu muốn xem hướng dẫn đầy đủ về cách tự thiết lập. Tuy nhiên, tôi đã đưa ra một số ví dụ bên dưới để bạn có thể bắt đầu.

Sử dụng ChromeDriver

ChromeDriver 2.32 sử dụng Chrome 61 và hoạt động tốt với Chrome không có giao diện người dùng.

Cài đặt:

npm i --save-dev selenium-webdriver chromedriver

Ví dụ:

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

Sử dụng WebDriverIO

WebDriverIO là một API cấp cao hơn trên Selenium WebDriver.

Cài đặt:

npm i --save-dev webdriverio chromedriver

Ví dụ: lọc các tính năng CSS trên 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();

})();

Tài nguyên khác

Dưới đây là một số tài nguyên hữu ích để bạn bắt đầu:

Tài liệu

Công cụ

  • chrome-remote-interface – mô-đun nút đóng gói giao thức DevTools
  • Lighthouse – công cụ tự động để kiểm tra chất lượng ứng dụng web; sử dụng nhiều giao thức
  • chrome-launcher – mô-đun nút để khởi chạy Chrome, sẵn sàng cho quá trình tự động hoá

Bản thu thử

Câu hỏi thường gặp

Tôi có cần cờ --disable-gpu không?

Chỉ có trên Windows. Các nền tảng khác không còn yêu cầu điều này. Cờ --disable-gpu là một giải pháp tạm thời cho một số lỗi. Bạn sẽ không cần cờ này trong các phiên bản Chrome trong tương lai. Hãy truy cập crbug.com/737678 để biết thêm thông tin.

Vậy tôi vẫn cần Xvfb chứ?

Không. Chrome không có giao diện người dùng không sử dụng cửa sổ nên bạn không cần máy chủ hiển thị như Xvfb nữa. Bạn có thể chạy kiểm thử tự động mà không cần đến tính năng này.

Xvfb là gì? Xvfb là một máy chủ hiển thị trong bộ nhớ cho các hệ thống giống Unix, cho phép bạn chạy các ứng dụng đồ hoạ (như Chrome) mà không cần màn hình thực tế được đính kèm. Nhiều người sử dụng Xvfb để chạy các phiên bản Chrome cũ hơn nhằm kiểm thử "không có giao diện người dùng".

Làm cách nào để tạo một vùng chứa Docker chạy Chrome không có giao diện người dùng?

Hãy xem lighthouse-ci. Thư viện này có một ví dụ về Dockerfile sử dụng node:8-slim làm hình ảnh cơ sở, cài đặt + chạy Lighthouse trên App Engine Flex.

Tôi có thể sử dụng tính năng này với Selenium / WebDriver / ChromeDriver không?

Có. Xem phần Sử dụng Selenium, WebDriver và ChromeDriver.

Mối liên hệ giữa điều này với PhantomJS là gì?

Chrome không có giao diện người dùng tương tự như các công cụ như PhantomJS. Bạn có thể sử dụng cả hai để kiểm thử tự động trong môi trường không có giao diện người dùng. Điểm khác biệt chính giữa hai công cụ này là Phantom sử dụng phiên bản WebKit cũ hơn làm công cụ hiển thị, trong khi Chrome không có giao diện người dùng sử dụng phiên bản Blink mới nhất.

Hiện tại, Phantom cũng cung cấp API cấp cao hơn giao thức DevTools.

Tôi có thể báo cáo lỗi ở đâu?

Đối với các lỗi liên quan đến Headless Chrome, hãy báo cáo lỗi trên crbug.com.

Đối với các lỗi trong giao thức DevTools, hãy gửi lỗi đó tại github.com/ChromeDevTools/devtools-protocol.