ferrum 0.4 → 0.5
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/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
|