Skip to content

Commit d61cecb

Browse files
committed
Prepare infrastructure for CDP in Ruby
Adds WebSocket support to allow using CDP from Ruby bindings. The implementation is naive: it simply processes handshake and then writes to the connection socket and reads from it. driver = Selenium::WebDriver.for(:chrome) driver.devtools.send('Page.navigate', {url: 'https://google.com'}) There is no built-in CDP commands/types/events support. These will be added later. The code relies on websocket gem as it's the only dependency-free implementation.
1 parent 7ab5f1b commit d61cecb

File tree

7 files changed

+113
-0
lines changed

7 files changed

+113
-0
lines changed

rb/lib/selenium/webdriver.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
require 'date'
2424
require 'json'
2525
require 'set'
26+
require 'websocket'
2627

2728
require 'selenium/webdriver/common'
2829
require 'selenium/webdriver/atoms'
@@ -36,6 +37,7 @@ module WebDriver
3637
Location = Struct.new(:latitude, :longitude, :altitude)
3738

3839
autoload :Chrome, 'selenium/webdriver/chrome'
40+
autoload :DevTools, 'selenium/webdriver/devtools'
3941
autoload :Edge, 'selenium/webdriver/edge'
4042
autoload :EdgeHtml, 'selenium/webdriver/edge'
4143
autoload :EdgeChrome, 'selenium/webdriver/edge'

rb/lib/selenium/webdriver/chrome/driver.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Driver < WebDriver::Driver
3232
include DriverExtensions::HasLocation
3333
include DriverExtensions::TakesScreenshot
3434
include DriverExtensions::DownloadsFiles
35+
include DriverExtensions::HasDevTools
3536

3637
def browser
3738
:chrome

rb/lib/selenium/webdriver/common.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
require 'selenium/webdriver/common/driver_extensions/has_debugger'
6363
require 'selenium/webdriver/common/driver_extensions/uploads_files'
6464
require 'selenium/webdriver/common/driver_extensions/has_addons'
65+
require 'selenium/webdriver/common/driver_extensions/has_devtools'
6566
require 'selenium/webdriver/common/keys'
6667
require 'selenium/webdriver/common/profile_helper'
6768
require 'selenium/webdriver/common/options'
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# frozen_string_literal: true
2+
3+
# Licensed to the Software Freedom Conservancy (SFC) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The SFC licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
20+
module Selenium
21+
module WebDriver
22+
module DriverExtensions
23+
module HasDevTools
24+
25+
#
26+
# Retrieves connection to DevTools.
27+
#
28+
# @return [DevTools]
29+
#
30+
31+
def devtools
32+
@devtools ||= DevTools.new(capabilities['goog:chromeOptions']['debuggerAddress'])
33+
end
34+
35+
end # HasDevTools
36+
end # DriverExtensions
37+
end # WebDriver
38+
end # Selenium

rb/lib/selenium/webdriver/devtools.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# frozen_string_literal: true
2+
3+
# Licensed to the Software Freedom Conservancy (SFC) under one
4+
# or more contributor license agreements. See the NOTICE file
5+
# distributed with this work for additional information
6+
# regarding copyright ownership. The SFC licenses this file
7+
# to you under the Apache License, Version 2.0 (the
8+
# "License"); you may not use this file except in compliance
9+
# with the License. You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing,
14+
# software distributed under the License is distributed on an
15+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
# KIND, either express or implied. See the License for the
17+
# specific language governing permissions and limitations
18+
# under the License.
19+
20+
module Selenium
21+
module WebDriver
22+
class DevTools
23+
24+
def initialize(url)
25+
@uri = URI("http://#{url}")
26+
process_handshake
27+
end
28+
29+
def send(method, **params)
30+
data = JSON.generate(id: next_id, method: method, params: params)
31+
32+
out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
33+
socket.write(out_frame.to_s)
34+
35+
in_frame = WebSocket::Frame::Incoming::Client.new(version: ws.version)
36+
in_frame << socket.readpartial(4096)
37+
JSON.parse(in_frame.next.to_s)
38+
end
39+
40+
private
41+
42+
def next_id
43+
@id ||= 0
44+
@id += 1
45+
end
46+
47+
def process_handshake
48+
socket.write(ws.to_s)
49+
ws << socket.readpartial(1024)
50+
end
51+
52+
def socket
53+
@socket ||= TCPSocket.new(ws.host, ws.port)
54+
end
55+
56+
def ws
57+
@ws ||= WebSocket::Handshake::Client.new(url: ws_url)
58+
end
59+
60+
def ws_url
61+
@ws_url ||= begin
62+
urls = JSON.parse(Net::HTTP.get(@uri.hostname, '/json', @uri.port))
63+
page = urls.find { |u| u['type'] == 'page' }
64+
page['webSocketDebuggerUrl']
65+
end
66+
end
67+
68+
end # DevTools
69+
end # WebDriver
70+
end # Selenium

rb/selenium-webdriver.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Gem::Specification.new do |s|
3333

3434
s.add_runtime_dependency 'childprocess', ['>= 0.5', '< 4.0']
3535
s.add_runtime_dependency 'rubyzip', ['>= 1.2.2']
36+
s.add_runtime_dependency 'websocket', ['~> 1.0']
3637

3738
# childprocess requires ffi on windows but doesn't declare it in its dependencies
3839
s.add_development_dependency 'ffi'
27.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)