ferrum 0.4 → 0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +62 -2
- data/lib/ferrum.rb +2 -7
- data/lib/ferrum/browser.rb +9 -12
- data/lib/ferrum/browser/client.rb +1 -1
- data/lib/ferrum/browser/process.rb +19 -3
- data/lib/ferrum/context.rb +79 -0
- data/lib/ferrum/contexts.rb +81 -0
- data/lib/ferrum/network.rb +1 -1
- data/lib/ferrum/network/request.rb +4 -0
- data/lib/ferrum/page.rb +1 -17
- data/lib/ferrum/page/screenshot.rb +15 -2
- data/lib/ferrum/target.rb +52 -0
- data/lib/ferrum/version.rb +1 -1
- metadata +5 -17
- data/lib/ferrum/targets.rb +0 -123
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 703207ec942dcaa7a09a6a2436e9a358da43a4f44e195150814ff1d00e05e3f8
|
4
|
+
data.tar.gz: 27e7a22921a96e1fbf4d36215170fb884e4ec72e904686cb12eae014f6dfd0a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b241f4500a4d330b2da5209e789a20c0df95f37963a6031846f51e8950378885749a2c3217116ef37dc09a5efcc421eaf4b1301536a38b9eb0747f2e1037f904
|
7
|
+
data.tar.gz: 60abc95e3633224aa08923da5b78026a687d21f380b3a818b99dcc1f32950fce83a7de574276f21973360ea257aaa8560490202e735953cbd6b714466eeefee8
|
data/README.md
CHANGED
@@ -23,10 +23,12 @@ There's no official Chrome or Chromium package for Linux don't install it this
|
|
23
23
|
way because it either will be outdated or unofficial, both are bad. Download it
|
24
24
|
from official [source](https://www.chromium.org/getting-involved/download-chromium).
|
25
25
|
Chrome binary should be in the `PATH` or `BROWSER_PATH` or you can pass it as an
|
26
|
-
option
|
26
|
+
option to browser instance `:browser_path`.
|
27
|
+
|
28
|
+
Add this to your Gemfile:
|
27
29
|
|
28
30
|
``` ruby
|
29
|
-
gem
|
31
|
+
gem "ferrum"
|
30
32
|
```
|
31
33
|
|
32
34
|
Navigate to a website and save a screenshot:
|
@@ -641,3 +643,61 @@ browser.on(:dialog) do |dialog|
|
|
641
643
|
end
|
642
644
|
browser.goto("https://google.com")
|
643
645
|
```
|
646
|
+
|
647
|
+
|
648
|
+
## Thread safety ##
|
649
|
+
|
650
|
+
Ferrum is fully thread-safe. You can create one browser or a few as you wish and
|
651
|
+
start playing around using threads. Example below shows how to create a few pages
|
652
|
+
which share the same context. Context is similar to an incognito profile but you
|
653
|
+
can have more than one, think of it like it's independent browser session:
|
654
|
+
|
655
|
+
```ruby
|
656
|
+
browser = Ferrum::Browser.new
|
657
|
+
context = browser.contexts.create
|
658
|
+
|
659
|
+
t1 = Thread.new(context) do |c|
|
660
|
+
page = c.create_page
|
661
|
+
page.goto("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
|
662
|
+
page.screenshot(path: "t1.png")
|
663
|
+
end
|
664
|
+
|
665
|
+
t2 = Thread.new(context) do |c|
|
666
|
+
page = c.create_page
|
667
|
+
page.goto("https://www.google.com/search?q=Ruby+static+typing")
|
668
|
+
page.screenshot(path: "t2.png")
|
669
|
+
end
|
670
|
+
|
671
|
+
t1.join
|
672
|
+
t2.join
|
673
|
+
|
674
|
+
context.dispose
|
675
|
+
browser.quit
|
676
|
+
```
|
677
|
+
|
678
|
+
or you can create two independent contexts:
|
679
|
+
|
680
|
+
```ruby
|
681
|
+
browser = Ferrum::Browser.new
|
682
|
+
|
683
|
+
t1 = Thread.new(browser) do |b|
|
684
|
+
context = b.contexts.create
|
685
|
+
page = context.create_page
|
686
|
+
page.goto("https://www.google.com/search?q=Ruby+headless+driver+for+Capybara")
|
687
|
+
page.screenshot(path: "t1.png")
|
688
|
+
context.dispose
|
689
|
+
end
|
690
|
+
|
691
|
+
t2 = Thread.new(browser) do |b|
|
692
|
+
context = b.contexts.create
|
693
|
+
page = context.create_page
|
694
|
+
page.goto("https://www.google.com/search?q=Ruby+static+typing")
|
695
|
+
page.screenshot(path: "t2.png")
|
696
|
+
context.dispose
|
697
|
+
end
|
698
|
+
|
699
|
+
t1.join
|
700
|
+
t2.join
|
701
|
+
|
702
|
+
browser.quit
|
703
|
+
```
|
data/lib/ferrum.rb
CHANGED
@@ -5,15 +5,10 @@ require "ferrum/node"
|
|
5
5
|
|
6
6
|
module Ferrum
|
7
7
|
class Error < StandardError; end
|
8
|
-
class
|
8
|
+
class NoSuchPageError < Error; end
|
9
|
+
class NoSuchTargetError < Error; end
|
9
10
|
class NotImplementedError < Error; end
|
10
11
|
|
11
|
-
class EmptyTargetsError < Error
|
12
|
-
def initialize
|
13
|
-
super("There aren't targets available")
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
12
|
class StatusError < Error
|
18
13
|
def initialize(url)
|
19
14
|
super("Request to #{url} failed to reach server, check DNS and/or server status")
|
data/lib/ferrum/browser.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "base64"
|
4
4
|
require "forwardable"
|
5
5
|
require "ferrum/page"
|
6
|
-
require "ferrum/
|
6
|
+
require "ferrum/contexts"
|
7
7
|
require "ferrum/browser/process"
|
8
8
|
require "ferrum/browser/client"
|
9
9
|
|
@@ -14,19 +14,19 @@ module Ferrum
|
|
14
14
|
BASE_URL_SCHEMA = %w[http https].freeze
|
15
15
|
|
16
16
|
extend Forwardable
|
17
|
-
delegate %i[
|
18
|
-
|
17
|
+
delegate %i[default_context] => :contexts
|
18
|
+
delegate %i[targets create_target create_page page pages windows] => :default_context
|
19
19
|
delegate %i[goto back forward refresh
|
20
20
|
at_css at_xpath css xpath current_url title body
|
21
21
|
headers cookies network
|
22
22
|
mouse keyboard
|
23
|
-
screenshot pdf
|
23
|
+
screenshot pdf viewport_size
|
24
24
|
evaluate evaluate_on evaluate_async execute
|
25
25
|
frame_url frame_title within_frame
|
26
26
|
on] => :page
|
27
27
|
|
28
|
-
attr_reader :client, :process, :
|
29
|
-
:options, :window_size
|
28
|
+
attr_reader :client, :process, :contexts, :logger, :js_errors,
|
29
|
+
:slowmo, :base_url, :options, :window_size
|
30
30
|
attr_writer :timeout
|
31
31
|
|
32
32
|
def initialize(options = nil)
|
@@ -82,7 +82,7 @@ module Ferrum
|
|
82
82
|
|
83
83
|
def reset
|
84
84
|
@window_size = @original_window_size
|
85
|
-
|
85
|
+
contexts.reset
|
86
86
|
end
|
87
87
|
|
88
88
|
def restart
|
@@ -93,11 +93,7 @@ module Ferrum
|
|
93
93
|
def quit
|
94
94
|
@client.close
|
95
95
|
@process.stop
|
96
|
-
@client = @process = @
|
97
|
-
end
|
98
|
-
|
99
|
-
def targets
|
100
|
-
@targets ||= Targets.new(self)
|
96
|
+
@client = @process = @contexts = nil
|
101
97
|
end
|
102
98
|
|
103
99
|
def resize(**options)
|
@@ -115,6 +111,7 @@ module Ferrum
|
|
115
111
|
Ferrum.started
|
116
112
|
@process = Process.start(@options)
|
117
113
|
@client = Client.new(self, @process.ws_url, 0, false)
|
114
|
+
@contexts = Contexts.new(self)
|
118
115
|
end
|
119
116
|
end
|
120
117
|
end
|
@@ -84,6 +84,15 @@ module Ferrum
|
|
84
84
|
end
|
85
85
|
end
|
86
86
|
|
87
|
+
def self.directory_remover(path)
|
88
|
+
proc do
|
89
|
+
begin
|
90
|
+
FileUtils.remove_entry(path)
|
91
|
+
rescue Errno::ENOENT
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
87
96
|
def self.detect_browser_path
|
88
97
|
if RUBY_PLATFORM.include?("darwin")
|
89
98
|
[
|
@@ -119,7 +128,9 @@ module Ferrum
|
|
119
128
|
host = options.fetch(:host, BROWSER_HOST)
|
120
129
|
@options.merge!("remote-debugging-address" => host)
|
121
130
|
|
122
|
-
@
|
131
|
+
@temp_user_data_dir = Dir.mktmpdir
|
132
|
+
ObjectSpace.define_finalizer(self, self.class.directory_remover(@temp_user_data_dir))
|
133
|
+
@options.merge!("user-data-dir" => @temp_user_data_dir)
|
123
134
|
|
124
135
|
@options = DEFAULT_OPTIONS.merge(@options)
|
125
136
|
|
@@ -158,8 +169,8 @@ module Ferrum
|
|
158
169
|
end
|
159
170
|
|
160
171
|
def stop
|
161
|
-
|
162
|
-
|
172
|
+
kill if @pid
|
173
|
+
remove_temp_user_data_dir if @temp_user_data_dir
|
163
174
|
ObjectSpace.undefine_finalizer(self)
|
164
175
|
end
|
165
176
|
|
@@ -175,6 +186,11 @@ module Ferrum
|
|
175
186
|
@pid = nil
|
176
187
|
end
|
177
188
|
|
189
|
+
def remove_temp_user_data_dir
|
190
|
+
self.class.directory_remover(@temp_user_data_dir).call
|
191
|
+
@temp_user_data_dir = nil
|
192
|
+
end
|
193
|
+
|
178
194
|
def parse_ws_url(read_io, timeout)
|
179
195
|
output = ""
|
180
196
|
start = Ferrum.monotonic_time
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ferrum/target"
|
4
|
+
|
5
|
+
module Ferrum
|
6
|
+
class Context
|
7
|
+
POSITION = %i[first last].freeze
|
8
|
+
|
9
|
+
attr_reader :id, :targets
|
10
|
+
|
11
|
+
def initialize(browser, contexts, id)
|
12
|
+
@browser, @contexts, @id = browser, contexts, id
|
13
|
+
@targets = Concurrent::Hash.new
|
14
|
+
@pendings = Concurrent::MVar.new
|
15
|
+
end
|
16
|
+
|
17
|
+
def default_target
|
18
|
+
@default_target ||= create_target
|
19
|
+
end
|
20
|
+
|
21
|
+
def page
|
22
|
+
default_target.page
|
23
|
+
end
|
24
|
+
|
25
|
+
def pages
|
26
|
+
@targets.values.map(&:page)
|
27
|
+
end
|
28
|
+
|
29
|
+
# When we call `page` method on target it triggers ruby to connect to given
|
30
|
+
# page by WebSocket, if there are many opened windows but we need only one
|
31
|
+
# it makes more sense to get and connect to the needed one only which
|
32
|
+
# usually is the last one.
|
33
|
+
def windows(pos = nil, size = 1)
|
34
|
+
raise ArgumentError if pos && !POSITION.include?(pos)
|
35
|
+
windows = @targets.values.select(&:window?)
|
36
|
+
windows = windows.send(pos, size) if pos
|
37
|
+
windows.map(&:page)
|
38
|
+
end
|
39
|
+
|
40
|
+
def create_page
|
41
|
+
create_target.page
|
42
|
+
end
|
43
|
+
|
44
|
+
def create_target
|
45
|
+
target_id = @browser.command("Target.createTarget",
|
46
|
+
browserContextId: @id,
|
47
|
+
url: "about:blank")["targetId"]
|
48
|
+
target = @pendings.take(@browser.timeout)
|
49
|
+
raise NoSuchTargetError unless target.is_a?(Target)
|
50
|
+
@targets[target.id] = target
|
51
|
+
target
|
52
|
+
end
|
53
|
+
|
54
|
+
def add_target(params)
|
55
|
+
target = Target.new(@browser, params)
|
56
|
+
if target.window?
|
57
|
+
@targets[target.id] = target
|
58
|
+
else
|
59
|
+
@pendings.put(target, @browser.timeout)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_target(target_id, params)
|
64
|
+
@targets[target_id].update(params)
|
65
|
+
end
|
66
|
+
|
67
|
+
def delete_target(target_id)
|
68
|
+
@targets.delete(target_id)
|
69
|
+
end
|
70
|
+
|
71
|
+
def dispose
|
72
|
+
@contexts.dispose(@id)
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect
|
76
|
+
%(#<#{self.class} @id=#{@id.inspect} @targets=#{@targets.inspect} @default_target=#{@default_target.inspect}>)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "ferrum/context"
|
4
|
+
|
5
|
+
module Ferrum
|
6
|
+
class Contexts
|
7
|
+
attr_reader :contexts
|
8
|
+
|
9
|
+
def initialize(browser)
|
10
|
+
@contexts = Concurrent::Hash.new
|
11
|
+
@browser = browser
|
12
|
+
subscribe
|
13
|
+
discover
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_context
|
17
|
+
@default_context ||= create
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_by(target_id:)
|
21
|
+
@contexts.find { |_, c| c.targets.keys.include?(target_id) }&.last
|
22
|
+
end
|
23
|
+
|
24
|
+
def create
|
25
|
+
response = @browser.command("Target.createBrowserContext")
|
26
|
+
context_id = response["browserContextId"]
|
27
|
+
context = Context.new(@browser, self, context_id)
|
28
|
+
@contexts[context_id] = context
|
29
|
+
context
|
30
|
+
end
|
31
|
+
|
32
|
+
def dispose(context_id)
|
33
|
+
context = @contexts[context_id]
|
34
|
+
@browser.command("Target.disposeBrowserContext",
|
35
|
+
browserContextId: context.id)
|
36
|
+
@contexts.delete(context_id)
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
def reset
|
41
|
+
@default_context = nil
|
42
|
+
@contexts.keys.each { |id| dispose(id) }
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def subscribe
|
48
|
+
@browser.client.on("Target.targetCreated") do |params|
|
49
|
+
info = params["targetInfo"]
|
50
|
+
next unless info["type"] == "page"
|
51
|
+
|
52
|
+
context_id = info["browserContextId"]
|
53
|
+
@contexts[context_id]&.add_target(info)
|
54
|
+
end
|
55
|
+
|
56
|
+
@browser.client.on("Target.targetInfoChanged") do |params|
|
57
|
+
info = params["targetInfo"]
|
58
|
+
next unless info["type"] == "page"
|
59
|
+
|
60
|
+
context_id, target_id = info.values_at("browserContextId", "targetId")
|
61
|
+
@contexts[context_id]&.update_target(target_id, info)
|
62
|
+
end
|
63
|
+
|
64
|
+
@browser.client.on("Target.targetDestroyed") do |params|
|
65
|
+
if context = find_by(target_id: params["targetId"])
|
66
|
+
context.delete_target(params["targetId"])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@browser.client.on("Target.targetCrashed") do |params|
|
71
|
+
if context = find_by(target_id: params["targetId"])
|
72
|
+
context.delete_target(params["targetId"])
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def discover
|
78
|
+
@browser.command("Target.setDiscoverTargets", discover: true)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/ferrum/network.rb
CHANGED
@@ -73,7 +73,7 @@ module Ferrum
|
|
73
73
|
@authorized_ids[type] << request.interception_id
|
74
74
|
request.continue(authChallengeResponse: response)
|
75
75
|
elsif index + 1 < total
|
76
|
-
next # There are other callbacks that can handle this
|
76
|
+
next # There are other callbacks that can handle this
|
77
77
|
else
|
78
78
|
request.continue
|
79
79
|
end
|
data/lib/ferrum/page.rb
CHANGED
@@ -14,8 +14,6 @@ require "ferrum/browser/client"
|
|
14
14
|
|
15
15
|
module Ferrum
|
16
16
|
class Page
|
17
|
-
NEW_WINDOW_WAIT = ENV.fetch("FERRUM_NEW_WINDOW_WAIT", 0.3).to_f
|
18
|
-
|
19
17
|
class Event < Concurrent::Event
|
20
18
|
def iteration
|
21
19
|
synchronize { @iteration }
|
@@ -37,7 +35,7 @@ module Ferrum
|
|
37
35
|
:headers, :cookies, :network,
|
38
36
|
:mouse, :keyboard
|
39
37
|
|
40
|
-
def initialize(target_id, browser
|
38
|
+
def initialize(target_id, browser)
|
41
39
|
@target_id, @browser = target_id, browser
|
42
40
|
@event = Event.new.tap(&:set)
|
43
41
|
|
@@ -45,11 +43,6 @@ module Ferrum
|
|
45
43
|
@waiting_frames ||= Set.new
|
46
44
|
@frame_stack = []
|
47
45
|
|
48
|
-
# Dirty hack because new window doesn't have events at all
|
49
|
-
sleep(NEW_WINDOW_WAIT) if new_window
|
50
|
-
|
51
|
-
@session_id = @browser.command("Target.attachToTarget", targetId: @target_id)["sessionId"]
|
52
|
-
|
53
46
|
host = @browser.process.host
|
54
47
|
port = @browser.process.port
|
55
48
|
ws_url = "ws://#{host}:#{port}/devtools/page/#{@target_id}"
|
@@ -83,12 +76,7 @@ module Ferrum
|
|
83
76
|
|
84
77
|
def close
|
85
78
|
@headers.clear
|
86
|
-
@browser.command("Target.detachFromTarget", sessionId: @session_id)
|
87
79
|
@browser.command("Target.closeTarget", targetId: @target_id)
|
88
|
-
close_connection
|
89
|
-
end
|
90
|
-
|
91
|
-
def close_connection
|
92
80
|
@client.close
|
93
81
|
end
|
94
82
|
|
@@ -173,10 +161,6 @@ module Ferrum
|
|
173
161
|
end
|
174
162
|
end
|
175
163
|
|
176
|
-
on("Page.windowOpen") do
|
177
|
-
@browser.targets.refresh
|
178
|
-
end
|
179
|
-
|
180
164
|
on("Page.navigatedWithinDocument") do
|
181
165
|
@event.set if @waiting_frames.empty?
|
182
166
|
end
|
@@ -19,6 +19,19 @@ module Ferrum
|
|
19
19
|
save_file(path, data)
|
20
20
|
end
|
21
21
|
|
22
|
+
def viewport_size
|
23
|
+
evaluate <<~JS
|
24
|
+
[window.innerWidth, window.innerHeight]
|
25
|
+
JS
|
26
|
+
end
|
27
|
+
|
28
|
+
def document_size
|
29
|
+
evaluate <<~JS
|
30
|
+
[document.documentElement.offsetWidth,
|
31
|
+
document.documentElement.offsetHeight]
|
32
|
+
JS
|
33
|
+
end
|
34
|
+
|
22
35
|
private
|
23
36
|
|
24
37
|
def save_file(path, data)
|
@@ -57,7 +70,7 @@ module Ferrum
|
|
57
70
|
end
|
58
71
|
|
59
72
|
if !!opts[:full]
|
60
|
-
width, height =
|
73
|
+
width, height = document_size
|
61
74
|
options.merge!(clip: { x: 0, y: 0, width: width, height: height, scale: scale }) if width > 0 && height > 0
|
62
75
|
elsif opts[:selector]
|
63
76
|
rect = evaluate("document.querySelector('#{opts[:selector]}').getBoundingClientRect()")
|
@@ -66,7 +79,7 @@ module Ferrum
|
|
66
79
|
|
67
80
|
if scale != 1.0
|
68
81
|
if !options[:clip]
|
69
|
-
width, height =
|
82
|
+
width, height = viewport_size
|
70
83
|
options[:clip] = { x: 0, y: 0, width: width, height: height }
|
71
84
|
end
|
72
85
|
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Target
|
5
|
+
NEW_WINDOW_WAIT = ENV.fetch("FERRUM_NEW_WINDOW_WAIT", 0.3).to_f
|
6
|
+
|
7
|
+
def initialize(browser, params = nil)
|
8
|
+
@browser = browser
|
9
|
+
@params = params
|
10
|
+
end
|
11
|
+
|
12
|
+
def update(params)
|
13
|
+
@params = params
|
14
|
+
end
|
15
|
+
|
16
|
+
def page
|
17
|
+
@page ||= begin
|
18
|
+
# Dirty hack because new window doesn't have events at all
|
19
|
+
sleep(NEW_WINDOW_WAIT) if window?
|
20
|
+
Page.new(id, @browser)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def id
|
25
|
+
@params["targetId"]
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
@params["type"]
|
30
|
+
end
|
31
|
+
|
32
|
+
def title
|
33
|
+
@params["title"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def url
|
37
|
+
@params["url"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def opener_id
|
41
|
+
@params["openerId"]
|
42
|
+
end
|
43
|
+
|
44
|
+
def context_id
|
45
|
+
@params["browserContextId"]
|
46
|
+
end
|
47
|
+
|
48
|
+
def window?
|
49
|
+
!!opener_id
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/ferrum/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ferrum
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: '0.
|
4
|
+
version: '0.5'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitry Vorotilin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-09-
|
11
|
+
date: 2019-09-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: websocket-driver
|
@@ -170,20 +170,6 @@ dependencies:
|
|
170
170
|
- - "~>"
|
171
171
|
- !ruby/object:Gem::Version
|
172
172
|
version: '1.3'
|
173
|
-
- !ruby/object:Gem::Dependency
|
174
|
-
name: byebug
|
175
|
-
requirement: !ruby/object:Gem::Requirement
|
176
|
-
requirements:
|
177
|
-
- - "~>"
|
178
|
-
- !ruby/object:Gem::Version
|
179
|
-
version: '10.0'
|
180
|
-
type: :development
|
181
|
-
prerelease: false
|
182
|
-
version_requirements: !ruby/object:Gem::Requirement
|
183
|
-
requirements:
|
184
|
-
- - "~>"
|
185
|
-
- !ruby/object:Gem::Version
|
186
|
-
version: '10.0'
|
187
173
|
description: Ferrum allows you to control headless Chrome browser
|
188
174
|
email:
|
189
175
|
- d.vorotilin@gmail.com
|
@@ -199,6 +185,8 @@ files:
|
|
199
185
|
- lib/ferrum/browser/process.rb
|
200
186
|
- lib/ferrum/browser/subscriber.rb
|
201
187
|
- lib/ferrum/browser/web_socket.rb
|
188
|
+
- lib/ferrum/context.rb
|
189
|
+
- lib/ferrum/contexts.rb
|
202
190
|
- lib/ferrum/cookies.rb
|
203
191
|
- lib/ferrum/dialog.rb
|
204
192
|
- lib/ferrum/headers.rb
|
@@ -217,7 +205,7 @@ files:
|
|
217
205
|
- lib/ferrum/page/frame.rb
|
218
206
|
- lib/ferrum/page/runtime.rb
|
219
207
|
- lib/ferrum/page/screenshot.rb
|
220
|
-
- lib/ferrum/
|
208
|
+
- lib/ferrum/target.rb
|
221
209
|
- lib/ferrum/version.rb
|
222
210
|
homepage: https://github.com/route/ferrum
|
223
211
|
licenses:
|
data/lib/ferrum/targets.rb
DELETED
@@ -1,123 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Ferrum
|
4
|
-
class Targets
|
5
|
-
TARGETS_RETRY_ATTEMPTS = 3
|
6
|
-
TARGETS_RETRY_WAIT = 0.001
|
7
|
-
|
8
|
-
def initialize(browser)
|
9
|
-
@page = nil
|
10
|
-
@mutex = Mutex.new
|
11
|
-
@browser = browser
|
12
|
-
@_default = targets.first["targetId"]
|
13
|
-
|
14
|
-
@browser.client.on("Target.detachedFromTarget") do |params|
|
15
|
-
page = remove_page(params["targetId"])
|
16
|
-
page&.close_connection
|
17
|
-
end
|
18
|
-
|
19
|
-
reset
|
20
|
-
end
|
21
|
-
|
22
|
-
def push(target_id, page = nil)
|
23
|
-
@targets[target_id] = page
|
24
|
-
end
|
25
|
-
|
26
|
-
def refresh
|
27
|
-
@mutex.synchronize do
|
28
|
-
targets.each { |t| push(t["targetId"]) if !default?(t) && !has?(t) }
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
def page
|
33
|
-
raise NoSuchWindowError unless @page
|
34
|
-
@page
|
35
|
-
end
|
36
|
-
|
37
|
-
def window_handle
|
38
|
-
page.target_id
|
39
|
-
end
|
40
|
-
|
41
|
-
def window_handles
|
42
|
-
@mutex.synchronize { @targets.keys }
|
43
|
-
end
|
44
|
-
|
45
|
-
def switch_to_window(target_id)
|
46
|
-
@page = find_or_create_page(target_id)
|
47
|
-
end
|
48
|
-
|
49
|
-
def open_new_window
|
50
|
-
target_id = @browser.command("Target.createTarget", url: "about:blank", browserContextId: @_context_id)["targetId"]
|
51
|
-
page = Page.new(target_id, @browser, true)
|
52
|
-
push(target_id, page)
|
53
|
-
target_id
|
54
|
-
end
|
55
|
-
|
56
|
-
def close_window(target_id)
|
57
|
-
remove_page(target_id)&.close
|
58
|
-
end
|
59
|
-
|
60
|
-
def within_window(locator)
|
61
|
-
original = window_handle
|
62
|
-
|
63
|
-
if window_handles.include?(locator)
|
64
|
-
switch_to_window(locator)
|
65
|
-
yield
|
66
|
-
else
|
67
|
-
raise NoSuchWindowError
|
68
|
-
end
|
69
|
-
ensure
|
70
|
-
switch_to_window(original)
|
71
|
-
end
|
72
|
-
|
73
|
-
def reset
|
74
|
-
if @page
|
75
|
-
@page.close
|
76
|
-
@browser.command("Target.disposeBrowserContext", browserContextId: @_context_id)
|
77
|
-
end
|
78
|
-
|
79
|
-
@page = nil
|
80
|
-
@targets = {}
|
81
|
-
@_context_id = nil
|
82
|
-
|
83
|
-
@_context_id = @browser.command("Target.createBrowserContext")["browserContextId"]
|
84
|
-
target_id = @browser.command("Target.createTarget", url: "about:blank", browserContextId: @_context_id)["targetId"]
|
85
|
-
@page = Page.new(target_id, @browser)
|
86
|
-
push(target_id, @page)
|
87
|
-
end
|
88
|
-
|
89
|
-
def find_or_create_page(target_id)
|
90
|
-
page = @targets[target_id]
|
91
|
-
page ||= Page.new(target_id, @browser, true)
|
92
|
-
@targets[target_id] ||= page
|
93
|
-
page
|
94
|
-
end
|
95
|
-
|
96
|
-
private
|
97
|
-
|
98
|
-
def remove_page(target_id)
|
99
|
-
page = @targets.delete(target_id)
|
100
|
-
@page = nil if page && @page == page
|
101
|
-
page
|
102
|
-
end
|
103
|
-
|
104
|
-
def targets
|
105
|
-
Ferrum.with_attempts(errors: EmptyTargetsError,
|
106
|
-
max: TARGETS_RETRY_ATTEMPTS,
|
107
|
-
wait: TARGETS_RETRY_WAIT) do
|
108
|
-
# Targets cannot be empty the must be at least one default target.
|
109
|
-
targets = @browser.command("Target.getTargets")["targetInfos"]
|
110
|
-
raise EmptyTargetsError if targets.empty?
|
111
|
-
targets
|
112
|
-
end
|
113
|
-
end
|
114
|
-
|
115
|
-
def default?(target)
|
116
|
-
@_default == target["targetId"]
|
117
|
-
end
|
118
|
-
|
119
|
-
def has?(target)
|
120
|
-
@targets.key?(target["targetId"])
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|