ferrum 0.10.1 → 0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +1 -1
- data/README.md +261 -28
- data/lib/ferrum/browser/binary.rb +46 -0
- data/lib/ferrum/browser/client.rb +15 -12
- data/lib/ferrum/browser/command.rb +7 -8
- data/lib/ferrum/browser/options/base.rb +1 -7
- data/lib/ferrum/browser/options/chrome.rb +17 -10
- data/lib/ferrum/browser/options/firefox.rb +11 -4
- data/lib/ferrum/browser/process.rb +41 -35
- data/lib/ferrum/browser/subscriber.rb +1 -3
- data/lib/ferrum/browser/web_socket.rb +9 -12
- data/lib/ferrum/browser/xvfb.rb +4 -8
- data/lib/ferrum/browser.rb +49 -12
- data/lib/ferrum/context.rb +12 -4
- data/lib/ferrum/contexts.rb +13 -9
- data/lib/ferrum/cookies.rb +10 -9
- data/lib/ferrum/errors.rb +115 -0
- data/lib/ferrum/frame/runtime.rb +20 -17
- data/lib/ferrum/frame.rb +32 -24
- data/lib/ferrum/headers.rb +2 -2
- data/lib/ferrum/keyboard.rb +11 -11
- data/lib/ferrum/mouse.rb +8 -7
- data/lib/ferrum/network/auth_request.rb +7 -2
- data/lib/ferrum/network/exchange.rb +14 -10
- data/lib/ferrum/network/intercepted_request.rb +10 -8
- data/lib/ferrum/network/request.rb +5 -0
- data/lib/ferrum/network/response.rb +4 -4
- data/lib/ferrum/network.rb +124 -35
- data/lib/ferrum/node.rb +98 -40
- data/lib/ferrum/page/animation.rb +15 -0
- data/lib/ferrum/page/frames.rb +46 -15
- data/lib/ferrum/page/screenshot.rb +53 -67
- data/lib/ferrum/page/stream.rb +38 -0
- data/lib/ferrum/page/tracing.rb +71 -0
- data/lib/ferrum/page.rb +88 -34
- data/lib/ferrum/proxy.rb +58 -0
- data/lib/ferrum/{rbga.rb → rgba.rb} +4 -2
- data/lib/ferrum/target.rb +1 -0
- data/lib/ferrum/utils/attempt.rb +20 -0
- data/lib/ferrum/utils/elapsed_time.rb +27 -0
- data/lib/ferrum/utils/platform.rb +28 -0
- data/lib/ferrum/version.rb +1 -1
- data/lib/ferrum.rb +4 -140
- metadata +65 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e4ca57662283c8c6b917aa3e2f1f06b154cb66ffdd8522756f55e09be84a423
|
4
|
+
data.tar.gz: 50d37d615399fe481634154d619d55872ee13b0cace511a1b147da38454247b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9cc514740ae8041b61a184bdeee0a44ce26b276f36af0358c6a00524f8e13b6db9ddda48acf5781c4122acaa4980579cf06053ffb420c2013e0b44177eab2571
|
7
|
+
data.tar.gz: cc00b6d3cb440db600241156354dcd92a6bf67c0402b9e2881f32ea507549d80f69b51011bb246baf9faf95f33e5596933ce569cd9b03f41c7cc066d215b6073
|
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
<img align="right"
|
4
4
|
width="320" height="241"
|
5
5
|
alt="Ferrum logo"
|
6
|
-
src="https://raw.githubusercontent.com/rubycdp/ferrum/
|
6
|
+
src="https://raw.githubusercontent.com/rubycdp/ferrum/main/logo.svg?sanitize=true">
|
7
7
|
|
8
8
|
#### As simple as Puppeteer, though even simpler.
|
9
9
|
|
@@ -23,12 +23,7 @@ going to crawl sites you better use Ferrum or
|
|
23
23
|
[Vessel](https://github.com/rubycdp/vessel) because you crawl, not test.
|
24
24
|
|
25
25
|
* [Vessel](https://github.com/rubycdp/vessel) high-level web crawling framework
|
26
|
-
based on Ferrum
|
27
|
-
a real browser in order to grab data.
|
28
|
-
|
29
|
-
Web design by [Evrone](https://evrone.com/), what else
|
30
|
-
[we build with Ruby on Rails](https://evrone.com/ruby), what else
|
31
|
-
[we do at Evrone](https://evrone.com/cases#case-studies).
|
26
|
+
based on Ferrum and Mechanize.
|
32
27
|
|
33
28
|
|
34
29
|
## Index
|
@@ -40,7 +35,9 @@ Web design by [Evrone](https://evrone.com/), what else
|
|
40
35
|
* [Navigation](https://github.com/rubycdp/ferrum#navigation)
|
41
36
|
* [Finders](https://github.com/rubycdp/ferrum#finders)
|
42
37
|
* [Screenshots](https://github.com/rubycdp/ferrum#screenshots)
|
38
|
+
* [Cleaning Up](https://github.com/rubycdp/ferrum#cleaning-up)
|
43
39
|
* [Network](https://github.com/rubycdp/ferrum#network)
|
40
|
+
* [Proxy](https://github.com/rubycdp/ferrum#proxy)
|
44
41
|
* [Mouse](https://github.com/rubycdp/ferrum#mouse)
|
45
42
|
* [Keyboard](https://github.com/rubycdp/ferrum#keyboard)
|
46
43
|
* [Cookies](https://github.com/rubycdp/ferrum#cookies)
|
@@ -48,7 +45,10 @@ Web design by [Evrone](https://evrone.com/), what else
|
|
48
45
|
* [JavaScript](https://github.com/rubycdp/ferrum#javascript)
|
49
46
|
* [Frames](https://github.com/rubycdp/ferrum#frames)
|
50
47
|
* [Frame](https://github.com/rubycdp/ferrum#frame)
|
51
|
-
* [
|
48
|
+
* [Dialogs](https://github.com/rubycdp/ferrum#dialogs)
|
49
|
+
* [Animation](https://github.com/rubycdp/ferrum#animation)
|
50
|
+
* [Node](https://github.com/rubycdp/ferrum#node)
|
51
|
+
* [Tracing](https://github.com/rubycdp/ferrum#tracing)
|
52
52
|
* [Thread safety](https://github.com/rubycdp/ferrum#thread-safety)
|
53
53
|
* [Development](https://github.com/rubycdp/ferrum#development)
|
54
54
|
* [Contributing](https://github.com/rubycdp/ferrum#contributing)
|
@@ -59,7 +59,8 @@ Web design by [Evrone](https://evrone.com/), what else
|
|
59
59
|
|
60
60
|
There's no official Chrome or Chromium package for Linux don't install it this
|
61
61
|
way because it's either outdated or unofficial, both are bad. Download it from
|
62
|
-
official [
|
62
|
+
official source for [Chrome](https://www.google.com/chrome/) or
|
63
|
+
[Chromium](https://www.chromium.org/getting-involved/download-chromium).
|
63
64
|
Chrome binary should be in the `PATH` or `BROWSER_PATH` or you can pass it as an
|
64
65
|
option to browser instance see `:browser_path` in
|
65
66
|
[Customization](https://github.com/rubycdp/ferrum#customization).
|
@@ -171,15 +172,18 @@ Ferrum::Browser.new(options)
|
|
171
172
|
options it passes to the browser, if you set this to `true` then only
|
172
173
|
options you put in `:browser_options` will be passed to the browser,
|
173
174
|
except required ones of course.
|
174
|
-
* `:port` (Integer) - Remote debugging port for headless Chrome
|
175
|
-
* `:host` (String) - Remote debugging address for headless Chrome
|
175
|
+
* `:port` (Integer) - Remote debugging port for headless Chrome.
|
176
|
+
* `:host` (String) - Remote debugging address for headless Chrome.
|
176
177
|
* `:url` (String) - URL for a running instance of Chrome. If this is set, a
|
177
178
|
browser process will not be spawned.
|
178
179
|
* `:process_timeout` (Integer) - How long to wait for the Chrome process to
|
179
|
-
respond on startup
|
180
|
+
respond on startup.
|
180
181
|
* `:ws_max_receive_size` (Integer) - How big messages to accept from Chrome
|
181
182
|
over the web socket, in bytes. Defaults to 64MB. Incoming messages larger
|
182
183
|
than this will cause a `Ferrum::DeadBrowserError`.
|
184
|
+
* `:proxy` (Hash) - Specify proxy settings, [read more](https://github.com/rubycdp/ferrum#proxy)
|
185
|
+
* `:save_path` (String) - Path to save attachments with [Content-Disposition](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition) header.
|
186
|
+
* `:env` (Hash) - Environment variables you'd like to pass through to the process
|
183
187
|
|
184
188
|
|
185
189
|
## Navigation
|
@@ -234,6 +238,25 @@ browser.go_to("https://github.com/")
|
|
234
238
|
browser.stop
|
235
239
|
```
|
236
240
|
|
241
|
+
#### position = \*\*options
|
242
|
+
|
243
|
+
Set the position for the browser window
|
244
|
+
|
245
|
+
* options `Hash`
|
246
|
+
* :left `Integer`
|
247
|
+
* :top `Integer`
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
browser.position = { left: 10, top: 20 }
|
251
|
+
```
|
252
|
+
|
253
|
+
#### position : `Array<Integer>`
|
254
|
+
|
255
|
+
Get the position for the browser window
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
browser.position # => [10, 20]
|
259
|
+
```
|
237
260
|
|
238
261
|
## Finders
|
239
262
|
|
@@ -386,9 +409,28 @@ browser.mhtml(path: "google.mhtml") # => 87742
|
|
386
409
|
```
|
387
410
|
|
388
411
|
|
412
|
+
## Cleaning Up
|
413
|
+
|
414
|
+
#### reset
|
415
|
+
|
416
|
+
Closes browser tabs opened by the `Browser` instance.
|
417
|
+
|
418
|
+
```ruby
|
419
|
+
# connect to a long-running Chrome process
|
420
|
+
browser = Ferrum::Browser.new(url: 'http://localhost:9222')
|
421
|
+
|
422
|
+
browser.go_to("https://github.com/")
|
423
|
+
|
424
|
+
# clean up, lest the tab stays there hanging forever
|
425
|
+
browser.reset
|
426
|
+
|
427
|
+
browser.quit
|
428
|
+
```
|
429
|
+
|
430
|
+
|
389
431
|
## Network
|
390
432
|
|
391
|
-
browser.network
|
433
|
+
`browser.network`
|
392
434
|
|
393
435
|
#### traffic `Array<Network::Exchange>`
|
394
436
|
|
@@ -521,17 +563,83 @@ end
|
|
521
563
|
browser.network.authorize(user: "login", password: "pass", type: :proxy)
|
522
564
|
|
523
565
|
browser.go_to("https://google.com")
|
524
|
-
|
525
566
|
```
|
526
567
|
|
527
568
|
You used to call `authorize` method without block, but since it's implemented using request interception there could be
|
528
569
|
a collision with another part of your code that also uses request interception, so that authorize allows the request
|
529
570
|
while your code denies but it's too late. The block is mandatory now.
|
530
571
|
|
572
|
+
#### emulate_network_conditions(\*\*options)
|
573
|
+
|
574
|
+
Activates emulation of network conditions.
|
575
|
+
|
576
|
+
* options `Hash`
|
577
|
+
* :offline `Boolean` emulate internet disconnection, `false` by default
|
578
|
+
* :latency `Integer` minimum latency from request sent to response headers received (ms), `0` by
|
579
|
+
default
|
580
|
+
* :download_throughput `Integer` maximal aggregated download throughput (bytes/sec), `-1`
|
581
|
+
by default, disables download throttling
|
582
|
+
* :upload_throughput `Integer` maximal aggregated upload throughput (bytes/sec), `-1`
|
583
|
+
by default, disables download throttling
|
584
|
+
* :connection_type `String` connection type if known, one of: none, cellular2g, cellular3g, cellular4g,
|
585
|
+
bluetooth, ethernet, wifi, wimax, other. `nil` by default
|
586
|
+
|
587
|
+
```ruby
|
588
|
+
browser.network.emulate_network_conditions(connection_type: "cellular2g")
|
589
|
+
browser.go_to("https://github.com/")
|
590
|
+
```
|
591
|
+
|
592
|
+
#### offline_mode
|
593
|
+
|
594
|
+
Activates offline mode for a page.
|
595
|
+
|
596
|
+
```ruby
|
597
|
+
browser.network.offline_mode
|
598
|
+
browser.go_to("https://github.com/") # => Ferrum::StatusError (Request to https://github.com/ failed to reach server, check DNS and server status)
|
599
|
+
```
|
600
|
+
|
601
|
+
|
602
|
+
## Proxy
|
603
|
+
|
604
|
+
You can set a proxy with the `proxy` option.
|
605
|
+
|
606
|
+
```ruby
|
607
|
+
browser = Ferrum::Browser.new(proxy: { host: "x.x.x.x", port: "8800" })
|
608
|
+
browser = Ferrum::Browser.new(proxy: { host: "x.x.x.x", port: "8800", user: "user", pasword: "pa$$" })
|
609
|
+
```
|
610
|
+
|
611
|
+
Chrome Devtools Protocol does not support changing proxies after the browser is launched. If you want to change proxies, you must restart your browser, which may not be convenient. There is a workaround. Ferrum provides a wrapper for a proxy server that can rotate proxies. We can run a proxy in the same process and rotate proxies inside this proxy server:
|
612
|
+
|
613
|
+
```ruby
|
614
|
+
browser = Ferrum::Browser.new(proxy: { server: true })
|
615
|
+
|
616
|
+
browser.proxy_server.rotate(host: "x.x.x.x", port: 31337, user: "user", password: "password")
|
617
|
+
browser.create_page(new_context: true) do |page|
|
618
|
+
page.go_to("https://api.ipify.org?format=json")
|
619
|
+
page.body # => "x.x.x.x"
|
620
|
+
end
|
621
|
+
|
622
|
+
browser.proxy_server.rotate(host: "y.y.y.y", port: 31337, user: "user", password: "password")
|
623
|
+
browser.create_page(new_context: true) do |page|
|
624
|
+
page.go_to("https://api.ipify.org?format=json")
|
625
|
+
page.body # => "y.y.y.y"
|
626
|
+
end
|
627
|
+
```
|
628
|
+
|
629
|
+
Make sure to create page in the new context, because Chrome doesn't break the connection with the proxy for `CONNECT`
|
630
|
+
requests even if you close the page.
|
631
|
+
|
632
|
+
You can specify semi-colon-separated list of hosts for which proxy shouldn't be used:
|
633
|
+
|
634
|
+
```ruby
|
635
|
+
browser = Ferrum::Browser.new(proxy: { host: "x.x.x.x", port: "8800", bypass: "*.google.com;*foo.com" })
|
636
|
+
browser = Ferrum::Browser.new(proxy: { server: true, bypass: "*.google.com;*foo.com" })
|
637
|
+
```
|
638
|
+
|
531
639
|
|
532
640
|
### Mouse
|
533
641
|
|
534
|
-
browser.mouse
|
642
|
+
`browser.mouse`
|
535
643
|
|
536
644
|
#### scroll_to(x, y)
|
537
645
|
|
@@ -618,13 +726,12 @@ Returns bitfield for a given keys
|
|
618
726
|
|
619
727
|
## Cookies
|
620
728
|
|
621
|
-
browser.cookies
|
729
|
+
`browser.cookies`
|
622
730
|
|
623
731
|
#### all : `Hash<String, Cookie>`
|
624
732
|
|
625
733
|
Returns cookies hash
|
626
734
|
|
627
|
-
|
628
735
|
```ruby
|
629
736
|
browser.cookies.all # => {"NID"=>#<Ferrum::Cookies::Cookie:0x0000558624b37a40 @attributes={"name"=>"NID", "value"=>"...", "domain"=>".google.com", "path"=>"/", "expires"=>1583211046.575681, "size"=>178, "httpOnly"=>true, "secure"=>false, "session"=>false}>}
|
630
737
|
```
|
@@ -639,11 +746,11 @@ Returns cookie
|
|
639
746
|
browser.cookies["NID"] # => <Ferrum::Cookies::Cookie:0x0000558624b67a88 @attributes={"name"=>"NID", "value"=>"...", "domain"=>".google.com", "path"=>"/", "expires"=>1583211046.575681, "size"=>178, "httpOnly"=>true, "secure"=>false, "session"=>false}>
|
640
747
|
```
|
641
748
|
|
642
|
-
#### set(
|
749
|
+
#### set(value) : `Boolean`
|
643
750
|
|
644
|
-
Sets
|
751
|
+
Sets a cookie
|
645
752
|
|
646
|
-
*
|
753
|
+
* value `Hash`
|
647
754
|
* :name `String`
|
648
755
|
* :value `String`
|
649
756
|
* :domain `String`
|
@@ -655,6 +762,13 @@ Sets given values as cookie
|
|
655
762
|
browser.cookies.set(name: "stealth", value: "omg", domain: "google.com") # => true
|
656
763
|
```
|
657
764
|
|
765
|
+
* value `Cookie`
|
766
|
+
|
767
|
+
```ruby
|
768
|
+
nid_cookie = browser.cookies["NID"] # => <Ferrum::Cookies::Cookie:0x0000558624b67a88>
|
769
|
+
browser.cookies.set(nid_cookie) # => true
|
770
|
+
```
|
771
|
+
|
658
772
|
#### remove(\*\*options) : `Boolean`
|
659
773
|
|
660
774
|
Removes given cookie
|
@@ -678,7 +792,7 @@ browser.cookies.clear # => true
|
|
678
792
|
|
679
793
|
## Headers
|
680
794
|
|
681
|
-
browser.headers
|
795
|
+
`browser.headers`
|
682
796
|
|
683
797
|
#### get : `Hash`
|
684
798
|
|
@@ -740,6 +854,20 @@ simple value.
|
|
740
854
|
browser.execute(%(1 + 1)) # => true
|
741
855
|
```
|
742
856
|
|
857
|
+
#### evaluate_on_new_document(expression)
|
858
|
+
|
859
|
+
Evaluate JavaScript to modify things before a page load
|
860
|
+
|
861
|
+
* expression `String` should be valid JavaScript
|
862
|
+
|
863
|
+
```ruby
|
864
|
+
browser.evaluate_on_new_document <<~JS
|
865
|
+
Object.defineProperty(navigator, "languages", {
|
866
|
+
get: function() { return ["tlh"]; }
|
867
|
+
});
|
868
|
+
JS
|
869
|
+
```
|
870
|
+
|
743
871
|
#### add_script_tag(\*\*options) : `Boolean`
|
744
872
|
|
745
873
|
* options `Hash`
|
@@ -763,9 +891,10 @@ browser.add_script_tag(url: "http://example.com/stylesheet.css") # => true
|
|
763
891
|
browser.add_style_tag(content: "h1 { font-size: 40px; }") # => true
|
764
892
|
|
765
893
|
```
|
766
|
-
#### bypass_csp(
|
894
|
+
#### bypass_csp(\*\*options) : `Boolean`
|
767
895
|
|
768
|
-
*
|
896
|
+
* options `Hash`
|
897
|
+
* :enabled `Boolean`, `true` by default
|
769
898
|
|
770
899
|
```ruby
|
771
900
|
browser.bypass_csp # => true
|
@@ -906,7 +1035,7 @@ browser.go_to("https://www.w3schools.com/tags/tag_frame.asp")
|
|
906
1035
|
browser.main_frame.doctype # => "<!DOCTYPE html>"
|
907
1036
|
```
|
908
1037
|
|
909
|
-
####
|
1038
|
+
#### content = html
|
910
1039
|
|
911
1040
|
Sets a content of a given frame.
|
912
1041
|
|
@@ -916,12 +1045,12 @@ Sets a content of a given frame.
|
|
916
1045
|
browser.go_to("https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe")
|
917
1046
|
frame = browser.frames[1]
|
918
1047
|
frame.body # <html lang="en"><head><style>body {transition: opacity ease-in 0.2s; }...
|
919
|
-
frame.
|
1048
|
+
frame.content = "<html><head></head><body><p>lol</p></body></html>"
|
920
1049
|
frame.body # => <html><head></head><body><p>lol</p></body></html>
|
921
1050
|
```
|
922
1051
|
|
923
1052
|
|
924
|
-
##
|
1053
|
+
## Dialogs
|
925
1054
|
|
926
1055
|
#### accept(text)
|
927
1056
|
|
@@ -946,6 +1075,107 @@ browser.go_to("https://google.com")
|
|
946
1075
|
```
|
947
1076
|
|
948
1077
|
|
1078
|
+
## Animation
|
1079
|
+
|
1080
|
+
You can slow down or speed up CSS animations.
|
1081
|
+
|
1082
|
+
#### playback_rate : `Integer`
|
1083
|
+
|
1084
|
+
Returns playback rate for CSS animations, defaults to `1`.
|
1085
|
+
|
1086
|
+
|
1087
|
+
#### playback_rate = value
|
1088
|
+
|
1089
|
+
Sets playback rate of CSS animations
|
1090
|
+
|
1091
|
+
* value `Integer`
|
1092
|
+
|
1093
|
+
```ruby
|
1094
|
+
browser = Ferrum::Browser.new
|
1095
|
+
browser.playback_rate = 2000
|
1096
|
+
browser.go_to("https://google.com")
|
1097
|
+
browser.playback_rate # => 2000
|
1098
|
+
```
|
1099
|
+
|
1100
|
+
|
1101
|
+
## Node
|
1102
|
+
|
1103
|
+
#### node? : `Boolean`
|
1104
|
+
#### frame_id
|
1105
|
+
#### frame : `Frame`
|
1106
|
+
|
1107
|
+
Returns [Frame](https://github.com/rubycdp/ferrum#frame) object for current node, you can keep using
|
1108
|
+
[Finders](https://github.com/rubycdp/ferrum#Finders) for that object:
|
1109
|
+
|
1110
|
+
```ruby
|
1111
|
+
frame = browser.at_xpath("//iframe").frame # => Frame
|
1112
|
+
frame.at_css("//a[text() = 'Log in']") # => Node
|
1113
|
+
```
|
1114
|
+
|
1115
|
+
#### focus
|
1116
|
+
#### focusable?
|
1117
|
+
#### moving? : `Boolean`
|
1118
|
+
#### wait_for_stop_moving
|
1119
|
+
#### blur
|
1120
|
+
#### type
|
1121
|
+
#### click
|
1122
|
+
#### hover
|
1123
|
+
#### select_file
|
1124
|
+
#### at_xpath
|
1125
|
+
#### at_css
|
1126
|
+
#### xpath
|
1127
|
+
#### css
|
1128
|
+
#### text
|
1129
|
+
#### inner_text
|
1130
|
+
#### value
|
1131
|
+
#### property
|
1132
|
+
#### attribute
|
1133
|
+
#### evaluate
|
1134
|
+
#### selected : `Array<Node>`
|
1135
|
+
#### select
|
1136
|
+
|
1137
|
+
(chainable) Selects options by passed attribute.
|
1138
|
+
|
1139
|
+
```ruby
|
1140
|
+
browser.at_xpath("//*[select]").select(["1"]) # => Node (select)
|
1141
|
+
browser.at_xpath("//*[select]").select(["text"], by: :text) # => Node (select)
|
1142
|
+
```
|
1143
|
+
|
1144
|
+
Accept string, array or strings:
|
1145
|
+
```ruby
|
1146
|
+
browser.at_xpath("//*[select]").select("1")
|
1147
|
+
browser.at_xpath("//*[select]").select("1", "2")
|
1148
|
+
browser.at_xpath("//*[select]").select(["1", "2"])
|
1149
|
+
```
|
1150
|
+
|
1151
|
+
|
1152
|
+
## Tracing
|
1153
|
+
|
1154
|
+
You can use `tracing.record` to create a trace file which can be opened in Chrome DevTools or
|
1155
|
+
[timeline viewer](https://chromedevtools.github.io/timeline-viewer/).
|
1156
|
+
|
1157
|
+
```ruby
|
1158
|
+
page.tracing.record(path: "trace.json") do
|
1159
|
+
page.go_to("https://www.google.com")
|
1160
|
+
end
|
1161
|
+
```
|
1162
|
+
|
1163
|
+
#### tracing.record(\*\*options) : `String`
|
1164
|
+
|
1165
|
+
Accepts block, records trace and by default returns trace data from `Tracing.tracingComplete` event as output. When
|
1166
|
+
`path` is specified returns `true` and stores trace data into file.
|
1167
|
+
|
1168
|
+
* options `Hash`
|
1169
|
+
* :path `String` save data on the disk, `nil` by default
|
1170
|
+
* :encoding `Symbol` `:base64` | `:binary` encode output as Base64 or plain text. `:binary` by default
|
1171
|
+
* :timeout `Float` wait until file streaming finishes in the specified time or raise error, defaults to `nil`
|
1172
|
+
* :screenshots `Boolean` capture screenshots in the trace, `false` by default
|
1173
|
+
* :trace_config `Hash<String, Object>` config for
|
1174
|
+
[trace](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#type-TraceConfig), for categories
|
1175
|
+
see [getCategories](https://chromedevtools.github.io/devtools-protocol/tot/Tracing/#method-getCategories),
|
1176
|
+
only one trace config can be active at a time per browser.
|
1177
|
+
|
1178
|
+
|
949
1179
|
## Thread safety ##
|
950
1180
|
|
951
1181
|
Ferrum is fully thread-safe. You can create one browser or a few as you wish and
|
@@ -1007,9 +1237,12 @@ browser.quit
|
|
1007
1237
|
|
1008
1238
|
After checking out the repo, run `bundle install` to install dependencies.
|
1009
1239
|
|
1010
|
-
Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will
|
1240
|
+
Then, run `bundle exec rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will
|
1241
|
+
allow you to experiment.
|
1011
1242
|
|
1012
|
-
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
1243
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the
|
1244
|
+
version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version,
|
1245
|
+
push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
1013
1246
|
|
1014
1247
|
|
1015
1248
|
## Contributing
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Ferrum
|
4
|
+
class Browser
|
5
|
+
module Binary
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def find(commands)
|
9
|
+
enum(commands).first
|
10
|
+
end
|
11
|
+
|
12
|
+
def all(commands)
|
13
|
+
enum(commands).force
|
14
|
+
end
|
15
|
+
|
16
|
+
def enum(commands)
|
17
|
+
paths, exts = prepare_paths
|
18
|
+
cmds = Array(commands).product(paths, exts)
|
19
|
+
lazy_find(cmds)
|
20
|
+
end
|
21
|
+
|
22
|
+
def prepare_paths
|
23
|
+
exts = (ENV.key?("PATHEXT") ? ENV.fetch("PATHEXT").split(";") : []) << ""
|
24
|
+
paths = ENV["PATH"].split(File::PATH_SEPARATOR)
|
25
|
+
raise EmptyPathError if paths.empty?
|
26
|
+
|
27
|
+
[paths, exts]
|
28
|
+
end
|
29
|
+
|
30
|
+
# rubocop:disable Style/CollectionCompact
|
31
|
+
def lazy_find(cmds)
|
32
|
+
cmds.lazy.map do |cmd, path, ext|
|
33
|
+
absolute_path = File.absolute_path(cmd)
|
34
|
+
is_absolute_path = absolute_path == cmd
|
35
|
+
cmd = File.expand_path("#{cmd}#{ext}", path) unless is_absolute_path
|
36
|
+
|
37
|
+
next unless File.executable?(cmd)
|
38
|
+
next if File.directory?(cmd)
|
39
|
+
|
40
|
+
cmd
|
41
|
+
end.reject(&:nil?) # .compact isn't defined on Enumerator::Lazy
|
42
|
+
end
|
43
|
+
# rubocop:enable Style/CollectionCompact
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "concurrent-ruby"
|
4
3
|
require "ferrum/browser/subscriber"
|
5
4
|
require "ferrum/browser/web_socket"
|
6
5
|
|
@@ -14,17 +13,18 @@ module Ferrum
|
|
14
13
|
@command_id = id_starts_with
|
15
14
|
@pendings = Concurrent::Hash.new
|
16
15
|
@ws = WebSocket.new(ws_url, @browser.ws_max_receive_size, @browser.logger)
|
17
|
-
@subscriber, @
|
16
|
+
@subscriber, @interrupter = Subscriber.build(2)
|
18
17
|
|
19
18
|
@thread = Thread.new do
|
20
19
|
Thread.current.abort_on_exception = true
|
21
|
-
if Thread.current.respond_to?(:report_on_exception=)
|
22
|
-
|
23
|
-
|
20
|
+
Thread.current.report_on_exception = true if Thread.current.respond_to?(:report_on_exception=)
|
21
|
+
|
22
|
+
loop do
|
23
|
+
message = @ws.messages.pop
|
24
|
+
break unless message
|
24
25
|
|
25
|
-
while message = @ws.messages.pop
|
26
26
|
if INTERRUPTIONS.include?(message["method"])
|
27
|
-
@
|
27
|
+
@interrupter.async.call(message)
|
28
28
|
elsif message.key?("method")
|
29
29
|
@subscriber.async.call(message)
|
30
30
|
else
|
@@ -44,6 +44,7 @@ module Ferrum
|
|
44
44
|
|
45
45
|
raise DeadBrowserError if data.nil? && @ws.messages.closed?
|
46
46
|
raise TimeoutError unless data
|
47
|
+
|
47
48
|
error, response = data.values_at("error", "result")
|
48
49
|
raise_browser_error(error) if error
|
49
50
|
response
|
@@ -52,14 +53,14 @@ module Ferrum
|
|
52
53
|
def on(event, &block)
|
53
54
|
case event
|
54
55
|
when *INTERRUPTIONS
|
55
|
-
@
|
56
|
+
@interrupter.on(event, &block)
|
56
57
|
else
|
57
58
|
@subscriber.on(event, &block)
|
58
59
|
end
|
59
60
|
end
|
60
61
|
|
61
62
|
def subscribed?(event)
|
62
|
-
[@
|
63
|
+
[@interrupter, @subscriber].any? { |s| s.subscribed?(event) }
|
63
64
|
end
|
64
65
|
|
65
66
|
def close
|
@@ -84,14 +85,16 @@ module Ferrum
|
|
84
85
|
# Node has disappeared while we were trying to get it
|
85
86
|
when "No node with given id found",
|
86
87
|
"Could not find node with given id"
|
87
|
-
raise NodeNotFoundError
|
88
|
+
raise NodeNotFoundError, error
|
88
89
|
# Context is lost, page is reloading
|
89
90
|
when "Cannot find context with specified id"
|
90
|
-
raise NoExecutionContextError
|
91
|
+
raise NoExecutionContextError, error
|
91
92
|
when "No target with given id found"
|
92
93
|
raise NoSuchPageError
|
94
|
+
when /Could not compute content quads/
|
95
|
+
raise CoordinatesNotFoundError
|
93
96
|
else
|
94
|
-
raise BrowserError
|
97
|
+
raise BrowserError, error
|
95
98
|
end
|
96
99
|
end
|
97
100
|
end
|
@@ -5,7 +5,7 @@ module Ferrum
|
|
5
5
|
class Command
|
6
6
|
NOT_FOUND = "Could not find an executable for the browser. Try to make " \
|
7
7
|
"it available on the PATH or set environment variable for " \
|
8
|
-
"example BROWSER_PATH=\"/usr/bin/chrome\""
|
8
|
+
"example BROWSER_PATH=\"/usr/bin/chrome\""
|
9
9
|
|
10
10
|
# Currently only these browsers support CDP:
|
11
11
|
# https://github.com/cyrus-and/chrome-remote-interface#implementations
|
@@ -27,9 +27,11 @@ module Ferrum
|
|
27
27
|
def initialize(defaults, options, user_data_dir)
|
28
28
|
@flags = {}
|
29
29
|
@defaults = defaults
|
30
|
-
@options
|
31
|
-
@
|
32
|
-
|
30
|
+
@options = options
|
31
|
+
@user_data_dir = user_data_dir
|
32
|
+
@path = options[:browser_path] || ENV.fetch("BROWSER_PATH", nil) || defaults.detect_path
|
33
|
+
raise BinaryNotFoundError, NOT_FOUND unless @path
|
34
|
+
|
33
35
|
merge_options
|
34
36
|
end
|
35
37
|
|
@@ -45,10 +47,7 @@ module Ferrum
|
|
45
47
|
|
46
48
|
def merge_options
|
47
49
|
@flags = defaults.merge_required(@flags, options, @user_data_dir)
|
48
|
-
|
49
|
-
unless options[:ignore_default_browser_options]
|
50
|
-
@flags = defaults.merge_default(@flags, options)
|
51
|
-
end
|
50
|
+
@flags = defaults.merge_default(@flags, options) unless options[:ignore_default_browser_options]
|
52
51
|
|
53
52
|
@flags.merge!(options.fetch(:browser_options, {}))
|
54
53
|
end
|
@@ -24,13 +24,7 @@ module Ferrum
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def detect_path
|
27
|
-
|
28
|
-
self.class::MAC_BIN_PATH.find { |n| File.exist?(n) }
|
29
|
-
else
|
30
|
-
self.class::LINUX_BIN_PATH.find do |name|
|
31
|
-
path = Cliver.detect(name) and break(path)
|
32
|
-
end
|
33
|
-
end
|
27
|
+
Binary.find(self.class::PLATFORM_PATH[Utils::Platform.name])
|
34
28
|
end
|
35
29
|
|
36
30
|
def merge_required(flags, options, user_data_dir)
|