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 đang được giao 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, việc chạy Chrome không có Chrome! Dịch vụ này kết hợp tất cả tính năng nền tảng web hiện đại được cung cấp bởi 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à công cụ tuyệt vời cho việc kiểm tra tự động và môi trường máy chủ mà bạn không cần giao diện người dùng hiển thị. Ví dụ: bạn nên chạy một số kiểm thử dựa trên một trang web thực, tạo tệp PDF 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 với 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ì tôi đ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 bản Beta, thì 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 ra một tệp có tên screenshot.png trong thư mục đang làm việc. Nếu bạn muốn tìm ảnh chụp màn hình toàn trang, thì liên quan hơn một chút. Có một blog tuyệt vời bài đăng của David Schnurr để giải đáp thắc mắc của bạn. Trả phòng 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ó 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ể khi bật giao thức Công cụ cho nhà phát triển. Chiến lược phát hành đĩa đơn giao thức này được dùng để giao tiếp với Chrome và thúc đẩy giao thức không có giao diện người dùng bản sao trình duyệt của bạn. Các công cụ như Sublime, VS Code và Node cũng 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ày, 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ó hoạt động không. Bạn sẽ thấy danh sách các trang có thể kiểm tra mà bạn có thể nhấp vào để xem Headless đang hiển thị nội dung gì:

Đ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à tinh chỉnh trang như bạn vẫn thường làm. Nếu bạn đang dùng giao diện người dùng không có giao diện người dùng theo phương thức lập trình, cũng là một công cụ gỡ lỗi mạnh mẽ để xem tất cả giao thức thô trong Công cụ cho nhà phát triển các lệnh truyền qua dây, 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 không có giao diện người dùng (hoặc toàn bộ) Chrome. 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 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 đó. 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. 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ý các tác vụ thừa như khởi chạy một 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();
})();

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 API của Puppeteer. Nếu muốn trở nên gần với kim loại và sử dụng trực tiếp giao thức DevTools.

Đang khởi chạy Chrome

Giao diện từ xa trên chrome không chạy Chrome cho bạn, vì vậy bạn sẽ phải thực hiện tự chăm sóc điều đó.

Trong phần CLI, chúng tôi đã bắt đầu 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ử, có thể bạn cần muố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) => {
  ...
});

Nhưng mọi thứ 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 Chromelauncher

Lighthouse là một điều kỳ diệu để kiểm tra chất lượng ứng dụng web của bạn. 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 GMS chrome-launcher sẽ tìm thấy ở đâu Chrome đã được cài đặt, thiết lập một phiên bản gỡ lỗi, chạy và tắt trình duyệt khi chương trình kết thúc. Điều tuyệt vời nhất là trò chơi này hoạt động được trên nhiều nền tảng nhờ vào Nút!

Theo mặc định, chrome-launcher sẽ cố chạy Chrome Canary (nếu có Chrome đã cài đặt), nhưng bạn có thể thay đổi cài đặt đó để chọn Chrome sẽ sử dụng theo cách thủ công nào. Người nhận hãy sử dụng nó, 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 có tác dụng gì nhiều, nhưng bạn sẽ thấy một phiên bản của Chrome khởi động trong trình quản lý tác vụ đã tải about:blank. Hãy nhớ rằng có sẽ không phải là giao diện người dùng của trình duyệt. 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, đó là 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ể đã định cấu hình để chạy Chrome không có giao diện người dùng chỉ với một vài thao tác. Tôi đề xuất 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 đã bỏ một số ví dụ dưới đây để 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ụ

  • chrome-remote-interface – nút mô-đun bao bọc giao thức Công cụ cho nhà phát triển
  • Lighthouse – công cụ kiểm thử tự động chất lượng ứng dụng web; sử dụng rất 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 mã này nữa. Cờ --disable-gpu là khắc phục một số lỗi tạm thời. Bạn sẽ không cần cờ này trong các phiên bản sau này của Chrome. Hãy truy cập vào 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 đến. 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á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 phải có 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 "không có giao diện người dùng" kiểm thử.

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. Chiến dịch này có 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 bài viết 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 có thể được 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 là Phantom sử dụng phiên bản WebKit cũ hơn để kết xuất trong khi Headless Chrome sử dụng phiên bản mới nhất của Blink.

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.