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

Eric Bidelman

TL;DR

Chrome không có giao diện người dùng sẽ được vận chuyể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! Thư viện này đưa tất 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.

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 cho môi trường máy chủ và kiểm thử tự động mà bạn không cần hiển thị giao diện người dùng. Ví dụ: có thể bạn muốn chạy một số hoạt động kiểm thử trên một trang web thực tế, tạo tệp PDF cho trang đó hoặc chỉ cần kiểm tra cách trình duyệt hiển thị URL.

Bắt đầu 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 của Chrome từ dòng lệnh. Nếu bạn đã cài đặt Chrome 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ẽ thay đổi tuỳ theo nền tảng. Vì đang 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 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 lập trình 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 đến stdout:

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

Tạo một tệp PDF

Cờ --print-to-pdf tạo ra 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/

Khi chạy với --screenshot, hệ thống sẽ tạo một tệp có tên screenshot.png trong thư mục đang làm việc. Nếu bạn đang tìm ảnh chụp màn hình toàn trang, thì mọi thứ đều phức tạp hơn. Có một bài đăng rất hay trên blog của David Schnurr đã đề cập đến bạn. Hãy tham khảo 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 người dùng ở 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ần 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à điều khiển phiên bản trình duyệt không có giao diện người dùng. Đây cũng là những 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, nên hãy chuyển đến http://localhost:9222 trong một trình duyệt khác để kiểm tra xem mọi thứ có đang hoạt động không. Bạn sẽ thấy một danh sách các trang có thể kiểm tra mà bạn có thể nhấp vào để xem giao diện không có giao diện người dùng đ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à điều chỉnh trang như bình thường. Nếu bạn đang sử dụng giao diện Không có giao diện người dùng 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 Công cụ cho nhà phát triển thô và giao tiếp với trình duyệt.

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

Con rối

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 toàn bộ). Công cụ 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 được trên các phiên bản Chrome mới nhất.

Ngoài ra, bạn có thể 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 đó. Tôi đề xuất thư viện này nếu bạn muốn nhanh chóng tự động hoá việc kiểm thử trình duyệt. Công cụ này giúp ẩn đi sự phức tạp của giao thức Công cụ cho nhà phát triển và xử lý những công việc dư thừa như khởi chạy phiên bản gỡ lỗi của Chrome.

Cài đặt:

npm i --save puppeteer

Ví dụ – xuất 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à thư viện cấp thấp hơn so với API của Puppeteer. Bạn nên sử dụng phương pháp 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-interface không chạy Chrome cho bạn, nên bạn phải tự xử lý vấn đề này.

Trong phần CLI, chúng ta đã 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 hoạt động kiểm thử, có thể bạn sẽ muốn tạo Chrome từ ứng dụng của mình.

Bạn có thể 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) => {
  ...
});

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

Sử dụng ChromeTrình chạy

Lighthouse là một công cụ kỳ diệu để kiểm tra chất lượng của các ứ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 chrome-launcher SSID 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 và loại bỏ trình duyệt khi chương trình hoàn tất. Điều tuyệt vời nhất là công nghệ này hoạt động trên nhiều nền tảng nhờ có Node!

Theo mặc định, chrome-launcher sẽ cố gắng chạy Chrome Canary (nếu đã cài đặt), nhưng bạn có thể thay đổi điều đó để chọn thủ công Chrome sẽ sử dụng. Để sử dụng thư viện này, 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 giao diện Không có giao diện người dùng

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 mang lại hiệu quả 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 tôi 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ụ – xuất 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ả có dạng 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 lúc này, Selenium sẽ mở phiên bản Chrome hoàn chỉnh. 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 cần tốn nhiều công sức. Bạn nên Chạy Selenium với 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 mọi thứ. Tuy nhiên, tôi đã trình bày 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à một API cấp cao hơn so với 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 để giúp bạn bắt đầu:

Tài liệu

Công cụ

  • chrome-remote-interface – 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 để thử nghiệm chất lượng ứ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 chế độ tự động hoá

Bản thu thử

  • "Web không có giao diện người dùng" – Bài đăng tuyệt vời trên blog của Paul Kinlan về việc sử dụng giao diện người dùng không có giao diện người dùng với api.ai.

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 chỉ là 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 sau này. Hãy truy cập crbug.com/737678 để biết thêm thông tin.

Vậy thì 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 trước đây nhằm thực hiện thử nghiệm "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 khám phá địa điểm Lighthouse-ci. Tệp này có một ví dụ về tệp 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ể dùng tính năng này với Selenium / WebDriver / ChromeDriver không?

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

Yêu cầu này liên quan đến PhantomJS như thế nào?

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ể 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 2 công cụ này là Phantom sử dụng một phiên bản cũ hơn của luyện đơn và công cụ kết xuất không có giao diện người dùng, 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 so với giao thức DevTools.

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

Đối với lỗi trên Chrome không có giao diện người dùng, hãy báo cáo lỗi 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.