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ẽ xuất hiện 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, chạy Chrome mà không cần chrome! Công cụ này đưa 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 vào dòng lệnh.

Vì sao việc này 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 nên chạy một số quy trình kiểm thử trên một trang web thực, tạo một tệp PDF của trang web đó hoặc chỉ kiểm tra cách trình duyệt hiển thị URL.

Khởi động không có giao diện người dùng (CLI)

Cách dễ nhất để bắt đầu 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 bản cài đặt Chrome của bạn. 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 đang sử dụng kênh chính thức của Chrome và không thể tải 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 phải viết tập lệnh theo chương 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 thao tác phổ biến.

In DOM

Cờ --dump-dom in document.body.innerHTML sang stdout:

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

Tạo một 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 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. Có một bài đăng tuyệt vời trên blog của David Schnurr đã giúp bạn giải đáp. 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 Không có giao diện ở chế độ mà bạn có thể đánh giá các 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 của 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ể có 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. Các công cụ như Sublime, VS Code và Node cũng dùng để gỡ lỗi từ xa cho ứ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 Giao thức không có giao diện người dùng theo phương thức lập trình, thì 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 thô trong Công cụ cho nhà phát triển đang đi qua đường truyền, giao tiếp với trình duyệt.

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

Người đẩy

Puppeteer là một thư viện Nút 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ự các thư viện kiểm thử tự động khác như Phantom và NightmareJS, nhưng chỉ hoạt động với 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, điều hướng 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ếu muốn nhanh chóng tự động hoá việc kiểm thử trình duyệt. Công cụ này giúp loại bỏ sự phức tạp của giao thức Công cụ cho nhà phát triển và xử lý các tác vụ thừa như khởi chạy một thực thể 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-remote là thư viện cấp thấp hơn API của Puppeteer. Bạn nên dùng tuỳ chọn này nếu muốn ở gần kim loại và sử dụng trực tiếp giao thức DevTools.

Đang khởi chạy Chrome

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

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 quy trình kiểm thử, bạn có thể cần 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. Bạn chỉ cần nhìn vào đường dẫn được cố định giá trị trong mã vào Chrome :(

Sử dụng ChromeLaunch

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ẽ để 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 TLD chrome-launcher sẽ tìm vị trí 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 được trên nhiều nền tảng nhờ có Nút!

Theo mặc định, chrome-launcher sẽ cố chạy Chrome Canary (nếu đã cài đặt Chrome Canary), nhưng bạn có thể thay đổi điều đó để 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 để 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 phiên bản 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 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ả tương tự như: 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 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

Ngay bây giờ, Selenium sẽ mở một phiên bản Chrome đầy đủ. 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, Selenium có thể được định cấu hình để chạy Chrome không có giao diện người dùng mà chỉ cần thực hiện một vài thao tác. Bạn nên Chạy Selenium với Chrome không có giao diện người dùng nếu bạn muốn hướng dẫn đầy đủ về cách tự thiết lập mọi thứ, nhưng tôi đã đưa vào một số ví dụ bên dưới để giúp bạn 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à API cấp cao hơn ngoài Selenium WebDriver.

Cài đặt:

npm i --save-dev webdriverio chromedriver

Ví dụ: lọc các tính năng của 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 để giúp bạn bắt đầu:

Tài liệu

Công cụ

  • giao diện từ xa chrome – mô-đun nút bao bọc giao thức Công cụ cho nhà phát triển
  • Lighthouse – công cụ tự động để kiểm tra chất lượng của ứng dụng web; sử dụng nhiều giao thức
  • chrome-launcher – mô-đun nút để 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 mã này nữa. Cờ --disable-gpu là giải pháp tạm thời đối với một số lỗi. Bạn sẽ không cần cờ này trong các phiên bản Chrome sau này. 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 máy chủ hiển thị như Xvfb không còn cần thiết nữa. Bạn có thể thoải mái chạy kiểm thử tự động mà không cần nó.

Xvfb là gì? Xvfb là một máy chủ hiển thị trong bộ nhớ dà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 đi kèm. Nhiều người sử dụng Xvfb để chạy các phiên bản Chrome cũ hơn nhằm thực hiện kiểm thử "không có giao diện người dùng".

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

Hãy xem poster-ci. SDK 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 trình điều khiển này với Selenium / WebDriver / ChromeDriver không?

Có. Hãy xem phần Sử dụng Selenium, WebDriver và ChromeDriver.

Trò này có liên quan gì đến PhantomJS?

Chrome không có giao diện người dùng tương tự như các công cụ như PhantomJS. Cả hai đều có thể dùng để 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 cũ hơn của WebKit làm công cụ kết xuất, còn Headless Chrome 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 so với giao thức DevTools.

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

Đối với các lỗi xảy ra với Headless Chrome, hãy gửi báo cáo trên crbug.com.

Đối với các lỗi trong giao thức Công cụ cho nhà phát triển, hãy gửi các lỗi đó tại github.com/ChromeDevTools/devtools-protocol.