Связь "выход" из хрома через инструменты разработчика протокола


У меня есть страница, запущенная в безголовом экземпляре Chromium, и я управляю ею с помощью протокола DevTools, используя пакет Puppeteer NPM в Node.

Я ввожу скрипт на страницу. В какой-то момент я хочу, чтобы скрипт перезвонил мне и отправил мне некоторую информацию (через какое-то событие, выставленное протоколом DevTools или каким-то другим способом).

Как лучше всего это сделать? Было бы здорово, если бы это можно было сделать с помощью Кукольника, но я не против испачкать руки и прослушивание сообщений протокола вручную.

Я знаю, что могу сделать это, манипулируя DOM и слушая изменения DOM, но это не кажется хорошей идеей.
2 6

2 ответа:

Если скрипт отправляет все свои данные обратно в одном вызове, самым простым подходом было бы использовать page.evaluate и вернуть обещание из него:

const dataBack = page.evaluate(`new Promise((resolve, reject) => {                                                  
  setTimeout(() => resolve('some data'), 1000)                                                                      
})`)
dataBack.then(value => { console.log('got data back', value) })

Это можно обобщить на отправку данных назад дважды и т. д. Для отправки назад произвольного потока событий, возможно, console.log будет немного меньше взлома, чем события DOM? По крайней мере, это очень легко сделать с кукольником:

page.on('console', message => {
  if (message.text.startsWith('dataFromMyScript')) {
    message.args[1].jsonValue().then(value => console.log('got data back', value))
  }
})
page.evaluate(`setInterval(() => console.log('dataFromMyScript', {ts: Date.now()}), 1000)`)

(пример использует магический префикс, чтобы отличить эти сообщения журнала от всех других.)

Хорошо, я обнаружил встроенный способ сделать это в Puppeteer. Puppeteer определяет вызываемый методexposeFunction.

page.exposeFunction(name, puppeteerFunction)
Этот метод определяет функцию с заданным именем на Объекте window страницы. Функция асинхронна на стороне страницы. Когда он вызывается, puppeteerFunction, который вы определяете, выполняется как обратный вызов с теми же аргументами. Аргументы не являются JSON-сериализованными, но передаются как JSHandles, поэтому они предоставляют сами объекты. Лично я выбрал JSON-сериализацию значений перед их отправкой.

Я посмотрел на код, и он на самом деле просто работает, посылая консольные сообщения, как в ответе Pasi, который игнорируется консольными крючками Кукольника. Однако, если вы слушаете консоль непосредственно (то есть по трубе stdout). Вы все равно увидите их вместе с обычными сообщениями.

Поскольку информация о консоли фактически передается WebSocket, это довольно эффективно. Я был немного против его использования, потому что в большинстве процессов консоль передает данные через stdout, который имеет проблемы.

Пример

Узел

async function example() {
    const puppeteer = require("puppeteer");
    let browser = await puppeteer.launch({
        //arguments
    });
    let page = await browser.newPage();

    await page.exposeFunction("callPuppeteer", function(data) {
        console.log("Node receives some data!", data);
    });

    await page.goto("http://www.example.com/target");
}

Страница

Внутри javascript страницы:

window.callPuppeteer(JSON.stringify({
    thisCameFromThePage : "hello!"
}));

Обновление: поддержка протокола DevTools

Существует поддержка протокола DevTools для чего-то вроде puppeteer.exposeFunction.

Https://chromedevtools.github.io/devtools-protocol/tot/Runtime#method-addBinding

Если executionContextId пуст, добавляет привязку с заданным именем на глобальные объекты всех обследованных контексты, в том числе созданные позже привязки переживают перезагрузки. Если указан executionContextId, добавляет привязку только к глобальному объекту данного контекста выполнения. Обязательный функция принимает ровно один аргумент, этот аргумент должен быть строковым, в случае любого другого ввода функция создает исключение. Каждая привязка вызов функции производит время выполнения.bindingCalled уведомление.

.