chromate-rb 0.0.2.pre → 0.0.3.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 +4 -4
- data/.rubocop.yml +1 -0
- data/CHANGELOG.md +20 -2
- data/Rakefile +2 -2
- data/docker_root/app.rb +6 -11
- data/docs/BOT_BROWSER.md +74 -0
- data/docs/browser.md +20 -55
- data/docs/client.md +126 -0
- data/docs/element.md +77 -1
- data/docs/elements/checkbox.md +69 -0
- data/docs/elements/radio.md +57 -0
- data/lib/bot_browser/downloader.rb +38 -26
- data/lib/bot_browser/installer.rb +27 -9
- data/lib/bot_browser.rb +5 -1
- data/lib/chromate/actions/dom.rb +24 -35
- data/lib/chromate/actions/navigate.rb +3 -0
- data/lib/chromate/actions/screenshot.rb +52 -14
- data/lib/chromate/actions/stealth.rb +38 -23
- data/lib/chromate/binary.rb +83 -0
- data/lib/chromate/browser.rb +70 -26
- data/lib/chromate/c_logger.rb +1 -0
- data/lib/chromate/client.rb +25 -8
- data/lib/chromate/configuration.rb +2 -2
- data/lib/chromate/element.rb +62 -9
- data/lib/chromate/elements/checkbox.rb +40 -0
- data/lib/chromate/elements/option.rb +43 -0
- data/lib/chromate/elements/radio.rb +37 -0
- data/lib/chromate/elements/select.rb +10 -18
- data/lib/chromate/elements/tags.rb +29 -0
- data/lib/chromate/exceptions.rb +2 -0
- data/lib/chromate/files/agents.json +11 -0
- data/lib/chromate/files/stealth.js +199 -0
- data/lib/chromate/hardwares/keyboard_controller.rb +11 -0
- data/lib/chromate/hardwares/mouse_controller.rb +8 -0
- data/lib/chromate/hardwares.rb +4 -4
- data/lib/chromate/user_agent.rb +14 -12
- data/lib/chromate/version.rb +1 -1
- data/results/bot.png +0 -0
- data/results/brotector.png +0 -0
- data/results/cloudflare.png +0 -0
- data/results/pixelscan.png +0 -0
- metadata +27 -2
data/lib/chromate/browser.rb
CHANGED
@@ -6,10 +6,15 @@ require 'securerandom'
|
|
6
6
|
require 'net/http'
|
7
7
|
require 'websocket-client-simple'
|
8
8
|
require_relative 'helpers'
|
9
|
+
require_relative 'binary'
|
9
10
|
require_relative 'client'
|
10
|
-
require_relative 'element'
|
11
11
|
require_relative 'hardwares'
|
12
|
+
require_relative 'element'
|
12
13
|
require_relative 'elements/select'
|
14
|
+
require_relative 'elements/option'
|
15
|
+
require_relative 'elements/tags'
|
16
|
+
require_relative 'elements/radio'
|
17
|
+
require_relative 'elements/checkbox'
|
13
18
|
require_relative 'user_agent'
|
14
19
|
require_relative 'actions/navigate'
|
15
20
|
require_relative 'actions/screenshot'
|
@@ -41,19 +46,13 @@ module Chromate
|
|
41
46
|
@xfvb = @options.fetch(:xfvb)
|
42
47
|
@native_control = @options.fetch(:native_control)
|
43
48
|
@record = @options.fetch(:record, false)
|
44
|
-
@
|
45
|
-
@xfvb_process = nil
|
49
|
+
@binary = nil
|
46
50
|
@record_process = nil
|
47
51
|
@client = nil
|
48
|
-
@args = [
|
49
|
-
@chrome_path,
|
50
|
-
"--user-data-dir=#{@user_data_dir}"
|
51
|
-
]
|
52
|
+
@args = []
|
52
53
|
|
53
54
|
trap('INT') { stop_and_exit }
|
54
55
|
trap('TERM') { stop_and_exit }
|
55
|
-
|
56
|
-
at_exit { stop }
|
57
56
|
end
|
58
57
|
|
59
58
|
# @return [self]
|
@@ -72,25 +71,34 @@ module Chromate
|
|
72
71
|
|
73
72
|
Hardwares::MouseController.reset_mouse_position
|
74
73
|
Chromate::CLogger.log("Starting browser with args: #{@args}", level: :debug)
|
75
|
-
@
|
76
|
-
sleep 2
|
74
|
+
@binary = Binary.new(@chrome_path, @args)
|
77
75
|
|
76
|
+
@binary.start
|
78
77
|
@client.start
|
79
78
|
|
80
79
|
start_video_recording if @record
|
81
80
|
|
82
81
|
patch if config.patch?
|
83
82
|
|
83
|
+
update_config!
|
84
|
+
|
84
85
|
self
|
85
86
|
end
|
86
87
|
|
88
|
+
# @return [Boolean]
|
89
|
+
def started?
|
90
|
+
@binary&.started? || false
|
91
|
+
end
|
92
|
+
|
87
93
|
# @return [self]
|
88
94
|
def stop
|
89
|
-
stop_process(@process) if @process
|
90
95
|
stop_process(@record_process) if @record_process
|
91
|
-
|
96
|
+
@binary.stop if started?
|
92
97
|
@client&.stop
|
93
98
|
|
99
|
+
@binary = nil
|
100
|
+
@record_process = nil
|
101
|
+
|
94
102
|
self
|
95
103
|
end
|
96
104
|
|
@@ -104,30 +112,67 @@ module Chromate
|
|
104
112
|
# @return [Integer]
|
105
113
|
def start_video_recording
|
106
114
|
outname = @record.is_a?(String) ? @record : "output_video_#{Time.now.to_i}.mp4"
|
107
|
-
outfile = File.join(Dir.pwd, outname)
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
115
|
+
outfile = File.join(Dir.pwd, outname).to_s
|
116
|
+
args = [
|
117
|
+
'-f',
|
118
|
+
'x11grab',
|
119
|
+
'-draw_mouse',
|
120
|
+
'1',
|
121
|
+
'-r',
|
122
|
+
'30',
|
123
|
+
'-s',
|
124
|
+
'1920x1080',
|
125
|
+
'-i',
|
126
|
+
ENV.fetch('DISPLAY'),
|
127
|
+
'-c:v',
|
128
|
+
'libx264',
|
129
|
+
'-preset',
|
130
|
+
'ultrafast',
|
131
|
+
'-pix_fmt',
|
132
|
+
'yuv420p',
|
133
|
+
'-y',
|
134
|
+
outfile
|
135
|
+
]
|
136
|
+
binary = Binary.new('ffmpeg', args)
|
137
|
+
binary.start
|
138
|
+
@record_process = binary.pid
|
112
139
|
end
|
113
140
|
|
114
141
|
# @return [Array<String>]
|
115
142
|
def build_args
|
116
143
|
exclude_switches = config.exclude_switches || []
|
117
144
|
exclude_switches += @options[:exclude_switches] if @options[:exclude_switches]
|
145
|
+
@user_agent = @options[:user_agent] || UserAgent.call
|
118
146
|
|
119
|
-
if @options.dig(:options, :args)
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@args << "--user-agent=#{@
|
147
|
+
@args = if @options.dig(:options, :args)
|
148
|
+
@options[:options][:args]
|
149
|
+
else
|
150
|
+
config.generate_arguments(**@options)
|
151
|
+
end
|
152
|
+
|
153
|
+
@args << "--user-agent=#{@user_agent}"
|
126
154
|
@args << "--exclude-switches=#{exclude_switches.join(",")}" if exclude_switches.any?
|
155
|
+
@args << "--user-data-dir=#{@user_data_dir}"
|
127
156
|
|
128
157
|
@args
|
129
158
|
end
|
130
159
|
|
160
|
+
def set_hardwares
|
161
|
+
config.mouse_controller = Hardwares.mouse(client: @client, element: nil)
|
162
|
+
config.keyboard_controller = Hardwares.keyboard(client: @client, element: nil)
|
163
|
+
end
|
164
|
+
|
165
|
+
# @return [void]
|
166
|
+
def update_config!
|
167
|
+
config.args = @args
|
168
|
+
config.user_data_dir = @user_data_dir
|
169
|
+
config.headless = @headless
|
170
|
+
config.xfvb = @xfvb
|
171
|
+
config.native_control = @native_control
|
172
|
+
|
173
|
+
set_hardwares
|
174
|
+
end
|
175
|
+
|
131
176
|
# @param pid [Integer] PID of the process to stop
|
132
177
|
# @param timeout [Integer] Timeout in seconds to wait for the process to stop
|
133
178
|
# @return [void]
|
@@ -144,7 +189,6 @@ module Chromate
|
|
144
189
|
# If the process does not stop gracefully, send SIGKILL
|
145
190
|
CLogger.log("Process #{pid} did not stop gracefully. Sending SIGKILL...", level: :debug)
|
146
191
|
Process.kill('KILL', pid)
|
147
|
-
Process.wait(pid)
|
148
192
|
end
|
149
193
|
rescue Errno::ESRCH
|
150
194
|
# The process has already stopped
|
data/lib/chromate/c_logger.rb
CHANGED
@@ -12,6 +12,7 @@ module Chromate
|
|
12
12
|
self.formatter = proc do |severity, datetime, _progname, msg|
|
13
13
|
"[Chromate] #{datetime.strftime("%Y-%m-%d %H:%M:%S")} #{severity}: #{msg}\n"
|
14
14
|
end
|
15
|
+
self.level = ENV['CHROMATE_DEBUG'] ? :debug : :info
|
15
16
|
end
|
16
17
|
|
17
18
|
# @return [Chromate::CLogger]
|
data/lib/chromate/client.rb
CHANGED
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'websocket-client-simple'
|
4
4
|
require 'chromate/helpers'
|
5
|
+
require 'chromate/exceptions'
|
5
6
|
|
6
7
|
module Chromate
|
7
8
|
class Client
|
@@ -117,16 +118,32 @@ module Chromate
|
|
117
118
|
|
118
119
|
# @return [String]
|
119
120
|
def fetch_websocket_debug_url
|
120
|
-
|
121
|
-
|
122
|
-
|
121
|
+
retries = 0
|
122
|
+
max_retries = 5
|
123
|
+
base_delay = 0.5
|
123
124
|
|
124
|
-
|
125
|
+
begin
|
126
|
+
uri = URI("http://localhost:#{@port}/json/list")
|
127
|
+
response = Net::HTTP.get(uri)
|
128
|
+
targets = JSON.parse(response)
|
129
|
+
|
130
|
+
page_target = targets.find { |target| target['type'] == 'page' }
|
131
|
+
websocket_url = if page_target
|
132
|
+
page_target['webSocketDebuggerUrl']
|
133
|
+
else
|
134
|
+
create_new_page_target
|
135
|
+
end
|
136
|
+
raise Exceptions::DebugURLError, 'Can\'t get WebSocket URL' if websocket_url.nil?
|
137
|
+
|
138
|
+
websocket_url
|
139
|
+
rescue StandardError => e
|
140
|
+
retries += 1
|
141
|
+
raise Exceptions::ConnectionTimeoutError, "Can't get WebSocket URL after #{max_retries} retries" if retries >= max_retries
|
125
142
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
143
|
+
delay = base_delay * (2**retries) # Exponential delay: 0.5s, 1s, 2s, 4s, 8s
|
144
|
+
Chromate::CLogger.log("Attempting to reconnect in #{delay} seconds, #{e.message}", level: :debug)
|
145
|
+
sleep delay
|
146
|
+
retry
|
130
147
|
end
|
131
148
|
end
|
132
149
|
|
@@ -17,7 +17,6 @@ module Chromate
|
|
17
17
|
'--no-sandbox', # Required for chrome devtools to work
|
18
18
|
'--test-type', # Remove the not allowed message for --no-sandbox flag
|
19
19
|
'--disable-dev-shm-usage', # Disable /dev/shm usage
|
20
|
-
'--disable-gpu', # Disable the GPU
|
21
20
|
'--disable-popup-blocking', # Disable popup blocking
|
22
21
|
'--ignore-certificate-errors', # Ignore certificate errors
|
23
22
|
'--window-size=1920,1080', # TODO: Make this automatic
|
@@ -48,7 +47,8 @@ module Chromate
|
|
48
47
|
].freeze
|
49
48
|
|
50
49
|
attr_accessor :user_data_dir, :headless, :xfvb, :native_control, :startup_patch,
|
51
|
-
:args, :headless_args, :xfvb_args, :exclude_switches, :proxy, :disable_features
|
50
|
+
:args, :headless_args, :xfvb_args, :exclude_switches, :proxy, :disable_features,
|
51
|
+
:mouse_controller, :keyboard_controller
|
52
52
|
|
53
53
|
def initialize
|
54
54
|
@user_data_dir = File.expand_path('~/.config/google-chrome/Default')
|
data/lib/chromate/element.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'chromate/elements/tags'
|
4
|
+
|
3
5
|
module Chromate
|
4
6
|
class Element
|
7
|
+
include Elements::Tags
|
8
|
+
|
5
9
|
class NotFoundError < StandardError
|
6
10
|
def initialize(selector, root_id)
|
7
11
|
super("Element not found with selector: #{selector} under root_id: #{root_id}")
|
@@ -13,7 +17,7 @@ module Chromate
|
|
13
17
|
super("Unable to resolve element with selector: #{selector}")
|
14
18
|
end
|
15
19
|
end
|
16
|
-
attr_reader :selector, :client
|
20
|
+
attr_reader :selector, :client, :root_id, :object_id, :node_id
|
17
21
|
|
18
22
|
# @param [String] selector
|
19
23
|
# @param [Chromate::Client] client
|
@@ -25,18 +29,18 @@ module Chromate
|
|
25
29
|
@client = client
|
26
30
|
@object_id = object_id
|
27
31
|
@node_id = node_id
|
28
|
-
@object_id, @node_id =
|
32
|
+
@object_id, @node_id = magick_find(selector, root_id) unless @object_id && @node_id
|
29
33
|
@root_id = root_id || document['root']['nodeId']
|
30
34
|
end
|
31
35
|
|
32
36
|
# @return [Chromate::Hardwares::MouseController]
|
33
37
|
def mouse
|
34
|
-
|
38
|
+
Chromate.configuration.mouse_controller.set_element(self)
|
35
39
|
end
|
36
40
|
|
37
41
|
# @return [Chromate::Hardwares::KeyboardController]
|
38
42
|
def keyboard
|
39
|
-
|
43
|
+
Chromate.configuration.keyboard_controller.set_element(self)
|
40
44
|
end
|
41
45
|
|
42
46
|
# @return [String]
|
@@ -47,8 +51,12 @@ module Chromate
|
|
47
51
|
|
48
52
|
# @return [String]
|
49
53
|
def text
|
50
|
-
|
51
|
-
|
54
|
+
evaluate_script('function() { return this.innerText; }')
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [String]
|
58
|
+
def value
|
59
|
+
evaluate_script('function() { return this.value; }')
|
52
60
|
end
|
53
61
|
|
54
62
|
# @return [String]
|
@@ -63,6 +71,11 @@ module Chromate
|
|
63
71
|
Hash[*result['attributes']]
|
64
72
|
end
|
65
73
|
|
74
|
+
# @return [String]
|
75
|
+
def tag_name
|
76
|
+
evaluate_script('function() { return this.tagName.toLowerCase(); }')
|
77
|
+
end
|
78
|
+
|
66
79
|
# @param [String] name
|
67
80
|
# @param [String] value
|
68
81
|
# @return [self]
|
@@ -196,6 +209,19 @@ module Chromate
|
|
196
209
|
end
|
197
210
|
end
|
198
211
|
|
212
|
+
# @param [String] script
|
213
|
+
# @return [String]
|
214
|
+
def evaluate_script(script, options = {})
|
215
|
+
result = client.send_message(
|
216
|
+
'Runtime.callFunctionOn',
|
217
|
+
functionDeclaration: script,
|
218
|
+
objectId: @object_id,
|
219
|
+
returnByValue: true,
|
220
|
+
**options
|
221
|
+
)
|
222
|
+
result['result']['value']
|
223
|
+
end
|
224
|
+
|
199
225
|
private
|
200
226
|
|
201
227
|
# @param [String] event
|
@@ -216,10 +242,39 @@ module Chromate
|
|
216
242
|
[node_info['object']['objectId'], result['nodeId']]
|
217
243
|
end
|
218
244
|
|
245
|
+
# @param [String] selector
|
246
|
+
# @option [Integer] root_id
|
247
|
+
# @return [Chromate::Element, nil]
|
248
|
+
def magick_find(selector, root_id = nil)
|
249
|
+
find(selector, root_id)
|
250
|
+
rescue NotFoundError, InvalidSelectorError
|
251
|
+
el = find_in_shadow_recursively(selector)
|
252
|
+
raise NotFoundError.new(selector, @root_id) unless el
|
253
|
+
|
254
|
+
el
|
255
|
+
end
|
256
|
+
|
257
|
+
# @param [String] selector
|
258
|
+
# @return [Chromate::Element, nil]
|
259
|
+
def find_in_shadow_recursively(selector)
|
260
|
+
shadow_children = find_shadow_children('*')
|
261
|
+
shadow_children.each do |child|
|
262
|
+
found_element = child.find_element(selector) || child.find_in_shadow_recursively(selector)
|
263
|
+
return found_element if found_element
|
264
|
+
end
|
265
|
+
|
266
|
+
nil
|
267
|
+
end
|
268
|
+
|
269
|
+
# @return [Hash]
|
219
270
|
def document
|
220
271
|
@document ||= client.send_message('DOM.getDocument')
|
221
272
|
end
|
222
273
|
|
274
|
+
# Allows to submit the parent form of the element
|
275
|
+
# can be used to submit a form
|
276
|
+
#
|
277
|
+
# @return [void]
|
223
278
|
def submit_parent_form
|
224
279
|
script = <<~JAVASCRIPT
|
225
280
|
function() {
|
@@ -236,9 +291,7 @@ module Chromate
|
|
236
291
|
}
|
237
292
|
JAVASCRIPT
|
238
293
|
|
239
|
-
|
240
|
-
functionDeclaration: script,
|
241
|
-
objectId: @object_id)
|
294
|
+
evaluate_script(script)
|
242
295
|
end
|
243
296
|
end
|
244
297
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/element'
|
4
|
+
|
5
|
+
module Chromate
|
6
|
+
module Elements
|
7
|
+
class Checkbox < Element
|
8
|
+
def initialize(selector, client, **options)
|
9
|
+
super
|
10
|
+
raise InvalidSelectorError, selector unless checkbox?
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [Boolean]
|
14
|
+
def checked?
|
15
|
+
attributes['checked'] == 'true'
|
16
|
+
end
|
17
|
+
|
18
|
+
# @return [self]
|
19
|
+
def check
|
20
|
+
click unless checked?
|
21
|
+
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# @return [self]
|
26
|
+
def uncheck
|
27
|
+
click if checked?
|
28
|
+
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
# @return [self]
|
33
|
+
def toggle
|
34
|
+
click
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/element'
|
4
|
+
|
5
|
+
module Chromate
|
6
|
+
module Elements
|
7
|
+
class Option < Element
|
8
|
+
attr_reader :value
|
9
|
+
|
10
|
+
# @param [String] value
|
11
|
+
def initialize(value, client, node_id: nil, object_id: nil, root_id: nil)
|
12
|
+
super("option[value='#{value}']", client, node_id: node_id, object_id: object_id, root_id: root_id)
|
13
|
+
|
14
|
+
@value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def bounding_box
|
18
|
+
script = <<~JAVASCRIPT
|
19
|
+
function() {
|
20
|
+
const select = this.closest('select');
|
21
|
+
const rect = select.getBoundingClientRect();
|
22
|
+
return {
|
23
|
+
x: rect.x,
|
24
|
+
y: rect.y,
|
25
|
+
width: rect.width,
|
26
|
+
height: rect.height
|
27
|
+
};
|
28
|
+
}
|
29
|
+
JAVASCRIPT
|
30
|
+
|
31
|
+
result = evaluate_script(script)
|
32
|
+
# TODO: fix this
|
33
|
+
# The offset is due to the fact that the option return the wrong coordinates
|
34
|
+
# can be fixed by mesuring an option and use the offset multiply by the index of the option
|
35
|
+
{
|
36
|
+
'content' => [result['x'] + 100, result['y'] + 100],
|
37
|
+
'width' => result['width'],
|
38
|
+
'height' => result['height']
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/element'
|
4
|
+
|
5
|
+
module Chromate
|
6
|
+
module Elements
|
7
|
+
class Radio < Element
|
8
|
+
def initialize(selector = nil, client = nil, **options)
|
9
|
+
if selector
|
10
|
+
super
|
11
|
+
raise InvalidSelectorError, selector unless radio?
|
12
|
+
else
|
13
|
+
super(**options)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# @return [Boolean]
|
18
|
+
def checked?
|
19
|
+
attributes['checked'] == 'true'
|
20
|
+
end
|
21
|
+
|
22
|
+
# @return [self]
|
23
|
+
def check
|
24
|
+
click unless checked?
|
25
|
+
|
26
|
+
self
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [self]
|
30
|
+
def uncheck
|
31
|
+
click if checked?
|
32
|
+
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'chromate/element'
|
4
|
+
require 'chromate/elements/option'
|
4
5
|
|
5
6
|
module Chromate
|
6
7
|
module Elements
|
@@ -8,39 +9,30 @@ module Chromate
|
|
8
9
|
# @param [String] value
|
9
10
|
# @return [self]
|
10
11
|
def select_option(value)
|
11
|
-
|
12
|
+
click
|
12
13
|
|
13
|
-
|
14
|
-
functionDeclaration: script,
|
15
|
-
objectId: @object_id,
|
16
|
-
arguments: [{ value: value }])
|
14
|
+
evaluate_script(javascript, arguments: [{ value: value }]) unless Chromate.configuration.native_control
|
17
15
|
|
18
|
-
|
19
|
-
rescue StandardError => e
|
20
|
-
raise ArgumentError, "Option '#{value}' not found in select" if e.message.include?('Option')
|
16
|
+
Option.new(value, client).click
|
21
17
|
|
22
|
-
|
18
|
+
self
|
23
19
|
end
|
24
20
|
|
25
21
|
# @return [String|nil]
|
26
22
|
def selected_value
|
27
|
-
|
28
|
-
functionDeclaration: 'function() { return this.value; }',
|
29
|
-
objectId: @object_id)
|
30
|
-
result.dig('result', 'value')
|
23
|
+
evaluate_script('function() { return this.value; }')
|
31
24
|
end
|
32
25
|
|
33
26
|
# @return [String|nil]
|
34
27
|
def selected_text
|
35
|
-
|
36
|
-
functionDeclaration: 'function() {
|
28
|
+
evaluate_script('function() {
|
37
29
|
const option = this.options[this.selectedIndex];
|
38
30
|
return option ? option.textContent.trim() : null;
|
39
|
-
}'
|
40
|
-
objectId: @object_id)
|
41
|
-
result.dig('result', 'value')
|
31
|
+
}')
|
42
32
|
end
|
43
33
|
|
34
|
+
private
|
35
|
+
|
44
36
|
# @return [String]
|
45
37
|
def javascript
|
46
38
|
<<~JAVASCRIPT
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'chromate/element'
|
4
|
+
|
5
|
+
module Chromate
|
6
|
+
module Elements
|
7
|
+
module Tags
|
8
|
+
def select?
|
9
|
+
tag_name == 'select'
|
10
|
+
end
|
11
|
+
|
12
|
+
def option?
|
13
|
+
tag_name == 'option'
|
14
|
+
end
|
15
|
+
|
16
|
+
def radio?
|
17
|
+
tag_name == 'input' && attributes['type'] == 'radio'
|
18
|
+
end
|
19
|
+
|
20
|
+
def checkbox?
|
21
|
+
tag_name == 'input' && attributes['type'] == 'checkbox'
|
22
|
+
end
|
23
|
+
|
24
|
+
def base?
|
25
|
+
!select? && !option? && !radio? && !checkbox?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/chromate/exceptions.rb
CHANGED
@@ -0,0 +1,11 @@
|
|
1
|
+
{
|
2
|
+
"windows": [
|
3
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
|
4
|
+
],
|
5
|
+
"mac": [
|
6
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
|
7
|
+
],
|
8
|
+
"linux": [
|
9
|
+
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
|
10
|
+
]
|
11
|
+
}
|