chromate-rb 0.0.1.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +39 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +124 -0
- data/Rakefile +71 -0
- data/dockerfiles/Dockerfile +85 -0
- data/dockerfiles/docker-entrypoint.sh +15 -0
- data/docs/browser.md +163 -0
- data/lib/chromate/actions/dom.rb +39 -0
- data/lib/chromate/actions/navigate.rb +71 -0
- data/lib/chromate/actions/screenshot.rb +56 -0
- data/lib/chromate/browser.rb +113 -0
- data/lib/chromate/c_logger.rb +22 -0
- data/lib/chromate/client.rb +120 -0
- data/lib/chromate/configuration.rb +119 -0
- data/lib/chromate/element.rb +194 -0
- data/lib/chromate/elements/select.rb +19 -0
- data/lib/chromate/exceptions.rb +9 -0
- data/lib/chromate/hardwares/mouse_controller.rb +67 -0
- data/lib/chromate/hardwares/mouses/linux_controller.rb +79 -0
- data/lib/chromate/hardwares/mouses/mac_os_controller.rb +133 -0
- data/lib/chromate/hardwares/mouses/virtual_controller.rb +81 -0
- data/lib/chromate/hardwares.rb +33 -0
- data/lib/chromate/helpers.rb +23 -0
- data/lib/chromate/user_agent.rb +34 -0
- data/lib/chromate/version.rb +5 -0
- data/lib/chromate.rb +17 -0
- data/results/brotector.png +0 -0
- data/results/cloudflare.png +0 -0
- data/sig/chromate.rbs +4 -0
- metadata +111 -0
@@ -0,0 +1,194 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
class Element
|
5
|
+
class NotFoundError < StandardError
|
6
|
+
def initialize(selector, root_id)
|
7
|
+
super("Element not found with selector: #{selector} under root_id: #{root_id}")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class InvalidSelectorError < StandardError
|
12
|
+
def initialize(selector)
|
13
|
+
super("Unable to resolve element with selector: #{selector}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
attr_reader :selector, :client, :mouse
|
17
|
+
|
18
|
+
# @param [String] selector
|
19
|
+
# @param [Chromate::Client] client
|
20
|
+
# @option [Integer] node_id
|
21
|
+
# @option [String] object_id
|
22
|
+
# @option [Integer] root_id
|
23
|
+
def initialize(selector, client, node_id: nil, object_id: nil, root_id: nil)
|
24
|
+
@selector = selector
|
25
|
+
@client = client
|
26
|
+
@object_id = object_id
|
27
|
+
@node_id = node_id
|
28
|
+
@object_id, @node_id = find(selector, root_id) unless @object_id && @node_id
|
29
|
+
@root_id = root_id || document['root']['nodeId']
|
30
|
+
@mouse = Hardwares.mouse(client: client, element: self)
|
31
|
+
end
|
32
|
+
|
33
|
+
def inspect
|
34
|
+
value = selector.length > 20 ? "#{selector[0..20]}..." : selector
|
35
|
+
"#<Chromate::Element:#{value}>"
|
36
|
+
end
|
37
|
+
|
38
|
+
def text
|
39
|
+
return @text if @text
|
40
|
+
|
41
|
+
result = client.send_message('Runtime.callFunctionOn', functionDeclaration: 'function() { return this.innerText; }', objectId: @object_id)
|
42
|
+
@text = result['result']['value']
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
def html
|
47
|
+
return @html if @html
|
48
|
+
|
49
|
+
@html = client.send_message('DOM.getOuterHTML', objectId: @object_id)
|
50
|
+
@html = @html['outerHTML']
|
51
|
+
end
|
52
|
+
|
53
|
+
# @return [Hash]
|
54
|
+
def attributes
|
55
|
+
return @attributes if @attributes
|
56
|
+
|
57
|
+
result = client.send_message('DOM.getAttributes', nodeId: @node_id)
|
58
|
+
@attributes = Hash[*result['attributes']]
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [String] name
|
62
|
+
# @param [String] value
|
63
|
+
# @return [self]
|
64
|
+
def set_attribute(name, value)
|
65
|
+
client.send_message('DOM.setAttributeValue', nodeId: @node_id, name: name, value: value)
|
66
|
+
dispatch_event('change')
|
67
|
+
end
|
68
|
+
|
69
|
+
# @return [Hash]
|
70
|
+
def bounding_box
|
71
|
+
return @bounding_box if @bounding_box
|
72
|
+
|
73
|
+
result = client.send_message('DOM.getBoxModel', objectId: @object_id)
|
74
|
+
@bounding_box = result['model']
|
75
|
+
end
|
76
|
+
|
77
|
+
# @return [Integer]
|
78
|
+
def x
|
79
|
+
bounding_box['content'][0]
|
80
|
+
end
|
81
|
+
|
82
|
+
# @return [Integer]
|
83
|
+
def y
|
84
|
+
bounding_box['content'][1]
|
85
|
+
end
|
86
|
+
|
87
|
+
# @return [Integer]
|
88
|
+
def width
|
89
|
+
bounding_box['width']
|
90
|
+
end
|
91
|
+
|
92
|
+
# @return [Integer]
|
93
|
+
def height
|
94
|
+
bounding_box['height']
|
95
|
+
end
|
96
|
+
|
97
|
+
# @return [self]
|
98
|
+
def click
|
99
|
+
mouse.click
|
100
|
+
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
# @return [self]
|
105
|
+
def hover
|
106
|
+
mouse.hover
|
107
|
+
|
108
|
+
self
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param [String] text
|
112
|
+
def type(text)
|
113
|
+
client.send_message('Runtime.callFunctionOn', functionDeclaration: "function(value) { this.value = value; this.dispatchEvent(new Event('input')); }",
|
114
|
+
objectId: @object_id, arguments: [{ value: text }])
|
115
|
+
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
# @param [String] selector
|
120
|
+
# @return [Chromate::Element]
|
121
|
+
def find_element(selector)
|
122
|
+
find_elements(selector, max: 1).first
|
123
|
+
end
|
124
|
+
|
125
|
+
# @param [String] selector
|
126
|
+
# @option [Integer] max
|
127
|
+
# @return [Array<Chromate::Element>]
|
128
|
+
def find_elements(selector, max: 0)
|
129
|
+
results = client.send_message('DOM.querySelectorAll', nodeId: @node_id, selector: selector)
|
130
|
+
results['nodeIds'].each_with_index.filter_map do |node_id, idx|
|
131
|
+
node_info = client.send_message('DOM.resolveNode', nodeId: node_id)
|
132
|
+
next unless node_info['object']
|
133
|
+
break if max.positive? && idx >= max
|
134
|
+
|
135
|
+
Element.new(selector, client, node_id: node_id, object_id: node_info['object']['objectId'], root_id: @node_id)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# @return [Integer]
|
140
|
+
def shadow_root_id
|
141
|
+
return @shadow_root_id if @shadow_root_id
|
142
|
+
|
143
|
+
node_info = client.send_message('DOM.describeNode', nodeId: @node_id)
|
144
|
+
@shadow_root_id = node_info.dig('node', 'shadowRoots', 0, 'nodeId')
|
145
|
+
end
|
146
|
+
|
147
|
+
# @return [Boolean]
|
148
|
+
def shadow_root?
|
149
|
+
!!shadow_root_id
|
150
|
+
end
|
151
|
+
|
152
|
+
# @param [String] selector
|
153
|
+
# @return [Chromate::Element|NilClass]
|
154
|
+
def find_shadow_child(selector)
|
155
|
+
find_shadow_children(selector).first
|
156
|
+
end
|
157
|
+
|
158
|
+
# @param [String] selector
|
159
|
+
# @return [Array<Chromate::Element>]
|
160
|
+
def find_shadow_children(selector)
|
161
|
+
return [] unless shadow_root?
|
162
|
+
|
163
|
+
results = client.send_message('DOM.querySelectorAll', nodeId: shadow_root_id, selector: selector)
|
164
|
+
(results&.dig('nodeIds') || []).map do |node_id|
|
165
|
+
node_info = client.send_message('DOM.resolveNode', nodeId: node_id)
|
166
|
+
next unless node_info['object']
|
167
|
+
|
168
|
+
Element.new(selector, client, node_id: node_id, object_id: node_info['object']['objectId'], root_id: shadow_root_id)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
|
174
|
+
def dispatch_event(event)
|
175
|
+
client.send_message('DOM.dispatchEvent', nodeId: @node_id, type: event)
|
176
|
+
end
|
177
|
+
|
178
|
+
# @return [Array] [object_id, node_id]
|
179
|
+
def find(selector, root_id = nil)
|
180
|
+
@root_id = root_id || document['root']['nodeId']
|
181
|
+
result = client.send_message('DOM.querySelector', nodeId: @root_id, selector: selector)
|
182
|
+
raise NotFoundError.new(selector, @root_id) unless result['nodeId']
|
183
|
+
|
184
|
+
node_info = client.send_message('DOM.resolveNode', nodeId: result['nodeId'])
|
185
|
+
raise InvalidSelectorError, selector unless node_info['object']
|
186
|
+
|
187
|
+
[node_info['object']['objectId'], result['nodeId']]
|
188
|
+
end
|
189
|
+
|
190
|
+
def document
|
191
|
+
@document ||= client.send_message('DOM.getDocument')
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/element'
|
4
|
+
|
5
|
+
module Chromate
|
6
|
+
module Elements
|
7
|
+
class Select < Element
|
8
|
+
# @param [String] selector
|
9
|
+
def select_option(value)
|
10
|
+
click
|
11
|
+
opt = find_elements('option').find do |option|
|
12
|
+
option.attributes['value'] == value
|
13
|
+
end
|
14
|
+
opt.set_attribute('selected', 'true')
|
15
|
+
click
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
module Hardwares
|
5
|
+
class MouseController
|
6
|
+
CLICK_DURATION_RANGE = (0.01..0.1)
|
7
|
+
DOUBLE_CLICK_DURATION_RANGE = (0.1..0.5)
|
8
|
+
|
9
|
+
attr_accessor :element, :client, :mouse_position
|
10
|
+
|
11
|
+
# @param [Chromate::Element] element
|
12
|
+
# @param [Chromate::Client] client
|
13
|
+
def initialize(element: nil, client: nil)
|
14
|
+
@element = element
|
15
|
+
@client = client
|
16
|
+
@mouse_position = { x: 0, y: 0 }
|
17
|
+
end
|
18
|
+
|
19
|
+
def hover
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
|
23
|
+
def click
|
24
|
+
raise NotImplementedError
|
25
|
+
end
|
26
|
+
|
27
|
+
def double_click
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
30
|
+
|
31
|
+
def right_click
|
32
|
+
raise NotImplementedError
|
33
|
+
end
|
34
|
+
|
35
|
+
def position_x
|
36
|
+
mouse_position[:x]
|
37
|
+
end
|
38
|
+
|
39
|
+
def position_y
|
40
|
+
mouse_position[:y]
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def target_x
|
46
|
+
element.x + (element.width / 2)
|
47
|
+
end
|
48
|
+
|
49
|
+
def target_y
|
50
|
+
element.y + (element.height / 2)
|
51
|
+
end
|
52
|
+
|
53
|
+
def bezier_curve(steps: 50) # rubocop:disable Metrics/AbcSize
|
54
|
+
control_x = (target_x / 2)
|
55
|
+
control_y = (target_y / 2)
|
56
|
+
|
57
|
+
(0..steps).map do |t|
|
58
|
+
t /= steps.to_f
|
59
|
+
# Compute the position on the quadratic Bezier curve
|
60
|
+
new_x = (((1 - t)**2) * position_x) + (2 * (1 - t) * t * control_x) + ((t**2) * target_x)
|
61
|
+
new_y = (((1 - t)**2) * position_y) + (2 * (1 - t) * t * control_y) + ((t**2) * target_y)
|
62
|
+
{ x: new_x, y: new_y }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/helpers'
|
4
|
+
require 'open3'
|
5
|
+
|
6
|
+
module Chromate
|
7
|
+
module Hardwares
|
8
|
+
module Mouses
|
9
|
+
class LinuxController < MouseController
|
10
|
+
class InvalidPlatformError < StandardError; end
|
11
|
+
include Helpers
|
12
|
+
|
13
|
+
LEFT_BUTTON = 1
|
14
|
+
RIGHT_BUTTON = 3
|
15
|
+
|
16
|
+
def initialize(element: nil, client: nil)
|
17
|
+
raise InvalidPlatformError, 'MouseController is only supported on Linux' unless linux?
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
|
22
|
+
def hover
|
23
|
+
focus_chrome_window
|
24
|
+
# system("xdotool mousemove_relative --sync -- #{x} #{y}")
|
25
|
+
system("xdotool mousemove #{target_x} #{target_y}")
|
26
|
+
current_mouse_position
|
27
|
+
end
|
28
|
+
|
29
|
+
def click
|
30
|
+
hover
|
31
|
+
simulate_button_event(LEFT_BUTTON, true)
|
32
|
+
simulate_button_event(LEFT_BUTTON, false)
|
33
|
+
end
|
34
|
+
|
35
|
+
def right_click
|
36
|
+
simulate_button_event(RIGHT_BUTTON, true)
|
37
|
+
simulate_button_event(RIGHT_BUTTON, false)
|
38
|
+
end
|
39
|
+
|
40
|
+
def double_click
|
41
|
+
click
|
42
|
+
sleep(rand(DOUBLE_CLICK_DURATION_RANGE))
|
43
|
+
click
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def focus_chrome_window
|
49
|
+
# Recherche de la fenêtre Chrome avec xdotool
|
50
|
+
chrome_window_id = `xdotool search --onlyvisible --name "Chrome"`.strip
|
51
|
+
|
52
|
+
if chrome_window_id.empty?
|
53
|
+
puts 'Aucune fenêtre Chrome trouvée'
|
54
|
+
else
|
55
|
+
# Active la fenêtre Chrome avec xdotool
|
56
|
+
system("xdotool windowactivate #{chrome_window_id}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def current_mouse_position
|
61
|
+
x = nil
|
62
|
+
y = nil
|
63
|
+
Open3.popen3('xdotool getmouselocation --shell') do |_, stdout, _, _|
|
64
|
+
output = stdout.read
|
65
|
+
x = output.match(/X=(\d+)/)[1].to_i
|
66
|
+
y = output.match(/Y=(\d+)/)[1].to_i
|
67
|
+
end
|
68
|
+
|
69
|
+
{ x: x, y: y }
|
70
|
+
end
|
71
|
+
|
72
|
+
def simulate_button_event(button, press)
|
73
|
+
action = press ? 'mousedown' : 'mouseup'
|
74
|
+
system("xdotool #{action} #{button}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ffi'
|
4
|
+
require 'chromate/helpers'
|
5
|
+
module Chromate
|
6
|
+
module Hardwares
|
7
|
+
module Mouses
|
8
|
+
class MacOsController < MouseController
|
9
|
+
class InvalidPlatformError < StandardError; end
|
10
|
+
include Helpers
|
11
|
+
extend FFI::Library
|
12
|
+
|
13
|
+
ffi_lib '/System/Library/Frameworks/ApplicationServices.framework/ApplicationServices'
|
14
|
+
|
15
|
+
class CGPoint < FFI::Struct
|
16
|
+
layout :x, :float,
|
17
|
+
:y, :float
|
18
|
+
end
|
19
|
+
|
20
|
+
attach_function :CGEventCreateMouseEvent, [:pointer, :uint32, CGPoint.by_value, :uint32], :pointer
|
21
|
+
attach_function :CGEventPost, %i[uint32 pointer], :void
|
22
|
+
attach_function :CGEventSetType, %i[pointer uint32], :void
|
23
|
+
attach_function :CFRelease, [:pointer], :void
|
24
|
+
attach_function :CGMainDisplayID, [], :uint32
|
25
|
+
attach_function :CGEventCreate, [:pointer], :pointer
|
26
|
+
attach_function :CGEventGetLocation, [:pointer], CGPoint.by_value
|
27
|
+
attach_function :CGDisplayPixelsHigh, [:uint32], :size_t
|
28
|
+
|
29
|
+
class CGSize < FFI::Struct
|
30
|
+
layout :width, :float,
|
31
|
+
:height, :float
|
32
|
+
end
|
33
|
+
|
34
|
+
LEFT_DOWN = 1
|
35
|
+
LEFT_UP = 2
|
36
|
+
RIGHT_DOWN = 3
|
37
|
+
RIGHT_UP = 4
|
38
|
+
MOUSE_MOVED = 5
|
39
|
+
|
40
|
+
def initialize(element: nil, client: nil)
|
41
|
+
raise InvalidPlatformError, 'MouseController is only supported on macOS' unless mac?
|
42
|
+
|
43
|
+
super
|
44
|
+
@main_display = CGMainDisplayID()
|
45
|
+
@display_height = CGDisplayPixelsHigh(@main_display).to_f
|
46
|
+
@scale_factor = determine_scale_factor
|
47
|
+
end
|
48
|
+
|
49
|
+
def hover
|
50
|
+
point = convert_coordinates(target_x, target_y)
|
51
|
+
create_and_post_event(MOUSE_MOVED, point)
|
52
|
+
current_mouse_position
|
53
|
+
end
|
54
|
+
|
55
|
+
def click
|
56
|
+
current_pos = current_mouse_position
|
57
|
+
create_and_post_event(LEFT_DOWN, current_pos)
|
58
|
+
create_and_post_event(LEFT_UP, current_pos)
|
59
|
+
end
|
60
|
+
|
61
|
+
def right_click
|
62
|
+
current_pos = current_mouse_position
|
63
|
+
create_and_post_event(RIGHT_DOWN, current_pos)
|
64
|
+
create_and_post_event(RIGHT_UP, current_pos)
|
65
|
+
end
|
66
|
+
|
67
|
+
def double_click
|
68
|
+
click
|
69
|
+
sleep(rand(DOUBLE_CLICK_DURATION_RANGE))
|
70
|
+
click
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def create_and_post_event(event_type, point)
|
76
|
+
event = CGEventCreateMouseEvent(nil, event_type, point, 0)
|
77
|
+
CGEventPost(0, event)
|
78
|
+
CFRelease(event)
|
79
|
+
end
|
80
|
+
|
81
|
+
def current_mouse_position
|
82
|
+
event = CGEventCreate(nil)
|
83
|
+
return CGPoint.new if event.null?
|
84
|
+
|
85
|
+
system_point = CGEventGetLocation(event)
|
86
|
+
CFRelease(event)
|
87
|
+
|
88
|
+
# Convertir les coordonnées système en coordonnées navigateur
|
89
|
+
browser_x = system_point[:x] / @scale_factor
|
90
|
+
browser_y = (@display_height - system_point[:y]) / @scale_factor
|
91
|
+
|
92
|
+
@mouse_position = {
|
93
|
+
x: browser_x,
|
94
|
+
y: browser_y
|
95
|
+
}
|
96
|
+
|
97
|
+
# Retourner un nouveau CGPoint avec les coordonnées système
|
98
|
+
CGPoint.new.tap do |p|
|
99
|
+
p[:x] = system_point[:x]
|
100
|
+
p[:y] = system_point[:y]
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def convert_coordinates(browser_x, browser_y)
|
105
|
+
# Convertir les coordonnées du navigateur en coordonnées système
|
106
|
+
system_x = browser_x * @scale_factor
|
107
|
+
system_y = @display_height - (browser_y * @scale_factor)
|
108
|
+
|
109
|
+
CGPoint.new.tap do |p|
|
110
|
+
p[:x] = system_x
|
111
|
+
p[:y] = system_y
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def determine_scale_factor
|
116
|
+
# Obtenir le facteur d'échelle en comparant les dimensions logiques et physiques
|
117
|
+
# Par défaut, utiliser 2.0 pour les écrans Retina
|
118
|
+
|
119
|
+
`system_profiler SPDisplaysDataType | grep -i "retina"`.empty? ? 1.0 : 2.0
|
120
|
+
rescue StandardError
|
121
|
+
2.0 # Par défaut pour les écrans Retina modernes
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Test
|
129
|
+
# require 'chromate/hardwares/mouses/mac_os_controller'
|
130
|
+
# require 'ostruct'
|
131
|
+
# element = OpenStruct.new(x: 500, y: 300, width: 100, height: 100)
|
132
|
+
# mouse = Chromate::Hardwares::Mouse::MacOsController.new(element: element)
|
133
|
+
# mouse.hover
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
module Hardwares
|
5
|
+
module Mouses
|
6
|
+
class VirtualController < Chromate::Hardwares::MouseController
|
7
|
+
def hover
|
8
|
+
steps = rand(25..50)
|
9
|
+
points = bezier_curve(steps: steps)
|
10
|
+
duration = rand(0.1..0.3)
|
11
|
+
|
12
|
+
points.each do |point|
|
13
|
+
dispatch_mouse_event('mouseMoved', point[:x], point[:y])
|
14
|
+
sleep(duration / steps)
|
15
|
+
end
|
16
|
+
|
17
|
+
update_mouse_position(points.last[:x], points.last[:y])
|
18
|
+
end
|
19
|
+
|
20
|
+
def click
|
21
|
+
hover
|
22
|
+
click!
|
23
|
+
end
|
24
|
+
|
25
|
+
def double_click
|
26
|
+
click
|
27
|
+
sleep(rand(DOUBLE_CLICK_DURATION_RANGE))
|
28
|
+
click
|
29
|
+
end
|
30
|
+
|
31
|
+
def right_click
|
32
|
+
hover
|
33
|
+
dispatch_mouse_event('mousePressed', target_x, target_y, button: 'right', click_count: 1)
|
34
|
+
sleep(rand(CLICK_DURATION_RANGE))
|
35
|
+
dispatch_mouse_event('mouseReleased', target_x, target_y, button: 'right', click_count: 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def click!
|
41
|
+
dispatch_mouse_event('mousePressed', target_x, target_y, button: 'left', click_count: 1)
|
42
|
+
sleep(rand(CLICK_DURATION_RANGE))
|
43
|
+
dispatch_mouse_event('mouseReleased', target_x, target_y, button: 'left', click_count: 1)
|
44
|
+
end
|
45
|
+
|
46
|
+
# @param [String] type mouseMoved, mousePressed, mouseReleased
|
47
|
+
# @param [Integer] target_x
|
48
|
+
# @param [Integer] target_y
|
49
|
+
# @option [String] button
|
50
|
+
# @option [Integer] click_count
|
51
|
+
def dispatch_mouse_event(type, target_x, target_y, button: 'none', click_count: 0)
|
52
|
+
params = {
|
53
|
+
type: type,
|
54
|
+
x: target_x,
|
55
|
+
y: target_y,
|
56
|
+
button: button,
|
57
|
+
clickCount: click_count,
|
58
|
+
deltaX: 0,
|
59
|
+
deltaY: 0,
|
60
|
+
modifiers: 0,
|
61
|
+
timestamp: (Time.now.to_f * 1000).to_i
|
62
|
+
}
|
63
|
+
|
64
|
+
client.send_message('Input.dispatchMouseEvent', params)
|
65
|
+
end
|
66
|
+
|
67
|
+
def update_mouse_position(target_x, target_y)
|
68
|
+
@mouse_position[:x] = target_x
|
69
|
+
@mouse_position[:y] = target_y
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Test
|
77
|
+
# require 'chromate/hardwares/mouses/virtual_controller'
|
78
|
+
# require 'ostruct'
|
79
|
+
# element = OpenStruct.new(x: 500, y: 300, width: 100, height: 100)
|
80
|
+
# mouse = Chromate::Hardwares::Mouse::VirtualController.new(element: element)
|
81
|
+
# mouse.hover
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/c_logger'
|
4
|
+
require 'chromate/hardwares/mouse_controller'
|
5
|
+
require 'chromate/hardwares/mouses/virtual_controller'
|
6
|
+
require 'chromate/helpers'
|
7
|
+
|
8
|
+
module Chromate
|
9
|
+
module Hardwares
|
10
|
+
extend Helpers
|
11
|
+
|
12
|
+
def mouse(**args)
|
13
|
+
browser = args[:client].browser
|
14
|
+
if browser.options[:native_control]
|
15
|
+
if mac?
|
16
|
+
Chromate::CLogger.log('🐁 Loading MacOs mouse controller')
|
17
|
+
require 'chromate/hardwares/mouses/mac_os_controller'
|
18
|
+
return Mouses::MacOsController.new(**args)
|
19
|
+
end
|
20
|
+
if linux?
|
21
|
+
Chromate::CLogger.log('🐁 Loading Linux mouse controller')
|
22
|
+
require 'chromate/hardwares/mouses/linux_controller'
|
23
|
+
return Mouses::LinuxController.new(**args)
|
24
|
+
end
|
25
|
+
raise 'Native mouse controller is not supported on Windows' if windows?
|
26
|
+
else
|
27
|
+
Chromate::CLogger.log('🐁 Loading Virtual mouse controller')
|
28
|
+
Mouses::VirtualController.new(**args)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
module_function :mouse
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rbconfig'
|
4
|
+
module Helpers
|
5
|
+
def linux?
|
6
|
+
RbConfig::CONFIG['host_os'] =~ /linux|bsd/i
|
7
|
+
end
|
8
|
+
|
9
|
+
def mac?
|
10
|
+
RbConfig::CONFIG['host_os'] =~ /darwin/i
|
11
|
+
end
|
12
|
+
|
13
|
+
def windows?
|
14
|
+
RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/i
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_available_port
|
18
|
+
server = TCPServer.new('127.0.0.1', 0)
|
19
|
+
port = server.addr[1]
|
20
|
+
server.close
|
21
|
+
port
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Chromate
|
4
|
+
class UserAgent
|
5
|
+
def self.call
|
6
|
+
new.call
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :os
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@os = find_os
|
13
|
+
end
|
14
|
+
|
15
|
+
def call
|
16
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36'
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def find_os
|
22
|
+
case RUBY_PLATFORM
|
23
|
+
when /darwin/
|
24
|
+
'Macintosh'
|
25
|
+
when /linux/
|
26
|
+
'Linux'
|
27
|
+
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
|
28
|
+
'Windows'
|
29
|
+
else
|
30
|
+
'Unknown'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/chromate.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'chromate/version'
|
4
|
+
require_relative 'chromate/browser'
|
5
|
+
require_relative 'chromate/configuration'
|
6
|
+
|
7
|
+
module Chromate
|
8
|
+
class << self
|
9
|
+
def configure
|
10
|
+
yield configuration
|
11
|
+
end
|
12
|
+
|
13
|
+
def configuration
|
14
|
+
@configuration ||= Configuration.new
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|