rubycdp / ferrum

Headless Chrome Ruby API
https://ferrum.rubycdp.com
MIT License
1.7k stars 122 forks source link

How can I connect to the existing Chrome and control it? #320

Open dunaevv opened 1 year ago

dunaevv commented 1 year ago

So, I need to control my Chrome, which I starts with terminal command: google-chrome --remote-debugging-port=9222

For example with Golang library, I can do it like this:

func main() {
    u := "ws://localhost:9222/devtools/page/D4ACC2FBE3AF8A9C8F0400C66E2094E0"
    rod.New().ControlURL(u).MustConnect().MustPage("https://example.com")
    time.Sleep(10 * time.Second)
}

And now I can control my browser. So, Im trying to do the same with Ferrum:

browser = Ferrum::Browser.new(
  url: "http://localhost:9222",
  # browser_path: "/usr/bin/google-chrome-stable",
  headless: true,
)
browser.goto("https://google.com")
browser.screenshot(path: "google.png")
sleep 100
browser.quit

and it`s creates a new Chrome instance with Incognito mode. But, I need to control already existing Chrome.

dunaevv commented 1 year ago

So, I found some library "chrome_remote" and It works well, I can connect to Chrome(google-chrome --remote-debugging-port=9222) with this code:

require 'chrome_remote'
require 'base64'

chrome = ChromeRemote.client

# Enable events
chrome.send_cmd "Network.enable"
chrome.send_cmd "Page.enable"

# Setup handler to log network requests
chrome.on "Network.requestWillBeSent" do |params|
  puts params["request"]["url"]
end

# Navigate to github.com and wait for the page to load
chrome.send_cmd "Page.navigate", url: "https://example.com"
chrome.wait_for "Page.loadEventFired"

# Take page screenshot
response = chrome.send_cmd "Page.captureScreenshot"
File.write "screenshot.png", Base64.decode64(response["data"])
route commented 1 year ago

I just checked locally, everything works just fine. Started the browser /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --headless=true started the browser browser = Ferrum::Browser.new(url: "http://localhost:9222") went to google browser.go "http://google.com".

The solution you posted is not related to Ferrum project at all honestly.

dunaevv commented 1 year ago

I just checked locally, everything works just fine. Started the browser /Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222 --headless=true started the browser browser = Ferrum::Browser.new(url: "http://localhost:9222") went to google browser.go "http://google.com".

The solution you posted is not related to Ferrum project at all honestly.

My steps =>

  1. Run Chrome with --remote-debugging-port=9222 (I want to see Chrome, so --headless=flase)
  2. Run my app.rb script: browser = Ferrum::Browser.new( url:"http://localhost:9222", headless: false, ) browser.goto("https://ya.ru") browser.quit

Now I have 2 Chrome windows, but I need only one. Screenshot from 2022-12-12 23-11-14

route commented 1 year ago

On step 2 the only option you have to pass is :url. Try it

dunaevv commented 1 year ago

On step 2 the only option you have to pass is :url. Try it

My friend, nothing has changed, I have 2 Chormes Screenshot from 2022-12-13 23-47-18

route commented 1 year ago

That's because there are 2 contexts here. When you run the browser with --remote-debugging-port=9222 it spawns your Chrome profile, with the pages you visited before, but when you want Chrome to visit "ya.ru", it creates for you an incognito profile and does so obviously in another window. Even though if you know the context id of the existing opened session Chrome does not allow you to create any pages in it, throwing an error:

context_id = "3F44386AB32348B20EC60B793358267A"
browser = Ferrum::Browser.new(url: "http://localhost:9222")
browser.command("Target.createTarget", browserContextId: context_id, url: "https://google.com")
▶ {"method":"Target.createTarget","params":{"browserContextId":"3F44386AB32348B20EC60B793358267A","url":"https://google.com"},"id":2}
◀ {"id":2,"error":{"code":-32000,"message":"Failed to find browser context with id 3F44386AB32348B20EC60B793358267A"}}

Thus you will always see 2 windows.

dunaevv commented 1 year ago

So does it mean, I can't control my Chrome profile?

По русски: Дмитрий, скажи пожалуйста, я могу подкючаться к браузеру, который запускаю с помощью терминала(--remote-debugging-port=9222). Например, в Go example, я так же запускаю браузер и могу контролировать именно эту сессию:

func main() {
    u := "ws://localhost:9222/devtools/page/D4ACC2FBE3AF8A9C8F0400C66E2094E0"
    rod.New().ControlURL(u).MustConnect().MustPage("https://example.com")
    time.Sleep(10 * time.Second)
}

Thx in advance!

route commented 1 year ago

Я вот только сегодня пробовал, запускал хром (он открывал мой профиль) с флагом remote-debugging-port=9222 и мне приходили события о всех открытых табах, в них был browserContextId который я затем пытался использовать чтобы создать новый таб командой browser.command("Target.createTarget", browserContextId: context_id, url: "https://google.com") но хром ругался и не давал создать такую страницу. Если у тебя есть полный пример кода на Go я могу скомпилить и поанализировать, но я не думаю что тут дело в языке. Я если честно немного удивлен что можно.

dunaevv commented 1 year ago

Я вот только сегодня пробовал, запускал хром (он открывал мой профиль) с флагом remote-debugging-port=9222 и мне приходили события о всех открытых табах, в них был browserContextId который я затем пытался использовать чтобы создать новый таб командой browser.command("Target.createTarget", browserContextId: context_id, url: "https://google.com") но хром ругался и не давал создать такую страницу. Если у тебя есть полный пример кода на Go я могу скомпилить и поанализировать, но я не думаю что тут дело в языке. Я если честно немного удивлен что можно.

Во первых, спасибо тебе, что идешь навстречу, это дорого стоит. Во вторых, вот пример кода на Go:

package main

import (
    "github.com/go-rod/rod"
)
func main() {
    u := "ws://127.0.0.1:9222/devtools/browser/5dc1bb0c-dae1-4d96-bfc4-9a6af8b98331"
    rod.New().ControlURL(u).MustConnect().MustPage("https://example.com")
}

Запускаем с Chrome с помощью терминала(--remote-debugging-port=9222), берем ws строку из терминала, добавляем в переменную u и запускаем код. Он подключается к текущей сессии(запущенному Chrome) и переходит на страницу: "https://example.com". Сам пример есть в док, https://go-rod.github.io/#/custom-launch

Так же есть похожий гем на ваш, chrome_remote, он тоже может подключаться к текущей сессии, которая зарущена через терминал и управлять ею, я писал выше:

require 'chrome_remote'

chrome = ChromeRemote.client(
  host: 'localhost', # optional (default: localhost). Host of the Chrome remote server
  port: 9222,        # optional (default: 9222). Port of the Chrome remote server
  new_tab: false     # optional (default: false). Whether to use the browser's current tab or a new one
)
chrome.send_cmd "Page.navigate", url: "https://github.com"

https://github.com/cavalle/chrome_remote

route commented 1 year ago

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

dunaevv commented 1 year ago

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

Спасибо большое!

kburkhardt commented 1 year ago

I needed the same functionality and have added it here: https://github.com/kburkhardt/ferrum/commit/c5373a3a50f7a7aab01a78ae189e337b5cdfe11a

This is based on the approach from the 'chrome_remote' gem.

Using the example from above, the following should work:

browser = Ferrum::Browser.new(url: "http://localhost:9222", headless: false, new_tab: false)
browser.goto("https://google.com")
browser.screenshot(path: "google.png")
sleep 100
browser.quit

Let me know if you have any questions or issues with this. I'm still learning how the ferrum gem is structured, so I might have missed something. But, great work! Looking forward to doing more with it.

dunaevv commented 1 year ago

I needed the same functionality and have added it here: kburkhardt@c5373a3

This is based on the approach from the 'chrome_remote' gem.

Using the example from above, the following should work:

browser = Ferrum::Browser.new(url: "http://localhost:9222", headless: false, new_tab: false)
browser.goto("https://google.com")
browser.screenshot(path: "google.png")
sleep 100
browser.quit

Let me know if you have any questions or issues with this. I'm still learning how the ferrum gem is structured, so I might have missed something. But, great work! Looking forward to doing more with it.

When he will pull it? Maybe I should copy your code)))

carlagomesah commented 6 months ago

Also interested in this

pusewicz commented 6 months ago

@route I tried the newest master and it's still not possible to connect to an open Chrome app with the remote port set. It always opens a new Incognito window.

options = {
  url: "http://localhost:9222",
  headless: false,
  ws_url: "ws://localhost:9222",
  ignore_default_browser_options: true,
  port: 9222,
  host: "localhost",
}
@browser = Ferrum::Browser.new(**options)
ibrahima commented 2 months ago

Hm, so is there no way to connect to an existing browser context (say one created by Ferrum, not even the existing default context) and control it from multiple Ferrum clients? I found a related comment from a few years back but the parameters have changed and I can't get this to work for me: https://github.com/rubycdp/ferrum/issues/34#issuecomment-611589596. I haven't tried the approach from the fork above though...

ibrahima commented 2 months ago

One thing I've been trying to understand is what the purpose of the discover method is in the Context class. I would have expected it to allow Ferrum to list and use existing Targets, but that does not seem to occur - when I try to list the targets on a Ferrum Browser instance, I don't get any existing ones until I create a new page. Maybe this is just legacy code?

https://github.com/rubycdp/ferrum/blob/443b268543da5347513dafb0ec7ccfe8b609b199/lib/ferrum/contexts.rb#L109

Been looking through https://github.com/aslushnikov/getting-started-with-cdp/blob/master/README.md#protocol-fundamentals to try to understand what's happening but I don't fully understand yet. Would appreciate any guidance on what Ferrum is intending to do here and if there is some way to attach to existing targets - I suspect not, but I am wondering why and if that was previously possible (I see mention of attachToTarget in the README).