foot_traffic 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +243 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/docs/in_action.gif +0 -0
- data/examples/clones.rb +27 -0
- data/examples/cookies.rb +38 -0
- data/examples/multi_threaded.rb +13 -0
- data/examples/multi_threaded_pool.rb +14 -0
- data/examples/simple.rb +8 -0
- data/examples/single_threaded.rb +13 -0
- data/foot_traffic.gemspec +30 -0
- data/lib/foot_traffic.rb +6 -0
- data/lib/foot_traffic/refinements.rb +20 -0
- data/lib/foot_traffic/session.rb +55 -0
- data/lib/foot_traffic/version.rb +3 -0
- metadata +96 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f953b3b31b7d172bed8c19c4516f568969fca74c4677ad58f11f73200d11ef7b
|
4
|
+
data.tar.gz: bc6bd3f2bff7b92f7edc61aebf616c832cb49581f8197b50d9361552f277c4a5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5dc2c473dcbee1f5d4978f3b34d2bb087f3f5ed4cb7fe8e31fd9e98d5f976aa6429b3aa3c7337917bf3135df0e7cf4a9614288d6d8976d8e37a4ccead172d354
|
7
|
+
data.tar.gz: 5c039f20cf7d9ee37da0a70a958d4b78f7f154a7334f9455c96745017399d35ca4aba379e114625ab67a2fbd559750ae151a1b0d668281fd1d7c5140a5672497
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
foot_traffic (0.1.0)
|
5
|
+
ferrum (~> 0.8)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
addressable (2.7.0)
|
11
|
+
public_suffix (>= 2.0.2, < 5.0)
|
12
|
+
cliver (0.3.2)
|
13
|
+
concurrent-ruby (1.1.6)
|
14
|
+
diff-lcs (1.3)
|
15
|
+
ferrum (0.8)
|
16
|
+
addressable (~> 2.6)
|
17
|
+
cliver (~> 0.3)
|
18
|
+
concurrent-ruby (~> 1.1)
|
19
|
+
websocket-driver (>= 0.6, < 0.8)
|
20
|
+
public_suffix (4.0.5)
|
21
|
+
rake (12.3.3)
|
22
|
+
rspec (3.9.0)
|
23
|
+
rspec-core (~> 3.9.0)
|
24
|
+
rspec-expectations (~> 3.9.0)
|
25
|
+
rspec-mocks (~> 3.9.0)
|
26
|
+
rspec-core (3.9.2)
|
27
|
+
rspec-support (~> 3.9.3)
|
28
|
+
rspec-expectations (3.9.2)
|
29
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
30
|
+
rspec-support (~> 3.9.0)
|
31
|
+
rspec-mocks (3.9.1)
|
32
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
33
|
+
rspec-support (~> 3.9.0)
|
34
|
+
rspec-support (3.9.3)
|
35
|
+
websocket-driver (0.7.1)
|
36
|
+
websocket-extensions (>= 0.1.0)
|
37
|
+
websocket-extensions (0.1.4)
|
38
|
+
|
39
|
+
PLATFORMS
|
40
|
+
ruby
|
41
|
+
|
42
|
+
DEPENDENCIES
|
43
|
+
foot_traffic!
|
44
|
+
rake (~> 12.0)
|
45
|
+
rspec (~> 3.0)
|
46
|
+
|
47
|
+
BUNDLED WITH
|
48
|
+
2.1.4
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2020 Andy B
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,243 @@
|
|
1
|
+
# Foot Traffic :dancers: :dancing_men:
|
2
|
+
|
3
|
+
A natural companion to to an amazing [Ferrum](https://github.com/rubycdp/ferrum) gem that controls a fleet of Chrome windows and tabs and simulates real user interaction with your web applications from any Ruby scripts. Works naturally with your system Chrome or Chromium, no extra magic like Selenium or WebDrivers needed.
|
4
|
+
|
5
|
+
```rb
|
6
|
+
require "foot_traffic"
|
7
|
+
using FootTraffic
|
8
|
+
|
9
|
+
FootTraffic::Session.start do |window|
|
10
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
11
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
12
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com/paris" }
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
![FootTraffic in action](docs/in_action.gif)
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
Add this line to your application's Gemfile:
|
21
|
+
|
22
|
+
```ruby
|
23
|
+
gem 'foot_traffic'
|
24
|
+
```
|
25
|
+
|
26
|
+
And then execute:
|
27
|
+
|
28
|
+
$ bundle install
|
29
|
+
|
30
|
+
Or install it yourself as:
|
31
|
+
|
32
|
+
$ gem install foot_traffic
|
33
|
+
|
34
|
+
## Powered by Ferrum :heart:
|
35
|
+
|
36
|
+
At [Le Wagon](https://www.lewagon.com) we love Ferrum and use it as a Selenium replacement to run system tests for our learning platforms. We highly recommend you do the same, and here's why
|
37
|
+
|
38
|
+
> Ferrum connects to the browser by CDP protocol and there's no Selenium/WebDriver/ChromeDriver dependency. The emphasis was made on a raw CDP protocol because Chrome allows you to do so many things that are barely supported by WebDriver because it should have consistent design with other browsers.—[Ferrum on GitHub](https://github.com/rubycdp/ferrum)
|
39
|
+
|
40
|
+
Pure Ruby + pure Chrome—what's not to like?
|
41
|
+
|
42
|
+
## Tutorial
|
43
|
+
|
44
|
+
In a simplest case, all you need is to `require "foot_traffic"`, put in the `using FootTraffic` to enable Ferrum [refinements](https://docs.ruby-lang.org/en/master/syntax/refinements_rdoc.html) and proceed with opening a session block. It yields a `window` object that is an instance of `Ferrum::Context` ([source](https://github.com/rubycdp/ferrum/blob/master/lib/ferrum/context.rb)). You can create a "tab" instance with a `new_tab` method and control it through Ferrum [methods](https://github.com/rubycdp/ferrum#examples) that are designed to be close to [Puppeteer](https://github.com/puppeteer/puppeteer/).
|
45
|
+
|
46
|
+
```rb
|
47
|
+
require "foot_traffic"
|
48
|
+
using FootTraffic
|
49
|
+
|
50
|
+
FootTraffic::Session.start do |window|
|
51
|
+
window.new_tab.goto "https://www.lewagon.com"
|
52
|
+
window.new_tab.goto "https://www.lewagon.com/berlin"
|
53
|
+
|
54
|
+
paris = window.new_tab
|
55
|
+
paris.goto "https://www.lewagon.com/paris"
|
56
|
+
paris.at_css('[href="/paris/apply"]').click
|
57
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
58
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
59
|
+
end
|
60
|
+
```
|
61
|
+
|
62
|
+
In this case, all the instructions to the browser will run in a _single thread_ of execution, so you will see a browser performing actions consequetively.
|
63
|
+
|
64
|
+
As Ferrum is thread-safe by design, you can execute the same scenario in parallel. `tab_thread` method opens a block that yields the instance of `Ferrum::Page` ([source](https://github.com/rubycdp/ferrum/blob/master/lib/ferrum/page.rb)).
|
65
|
+
|
66
|
+
```rb
|
67
|
+
require "foot_traffic"
|
68
|
+
using FootTraffic
|
69
|
+
|
70
|
+
FootTraffic::Session.start do |window|
|
71
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
72
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
73
|
+
window.tab_thread do |paris|
|
74
|
+
paris.goto "https://www.lewagon.com/paris"
|
75
|
+
paris.at_css('[href="/paris/apply"]').click
|
76
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
77
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Now, all the tabs will run **in parallel** (with limits to Ruby concurrency model, of course). After the session block finishes execution, browser will stay open indefinitely—until you `Ctrl-C` the original script. That might be useful if you want to use Chrome Developer Tools on open pages.
|
83
|
+
|
84
|
+
If your script does not end with a session block and you want to continue running your code—set the duration time for the session.
|
85
|
+
|
86
|
+
```rb
|
87
|
+
require "foot_traffic"
|
88
|
+
using FootTraffic
|
89
|
+
|
90
|
+
FootTraffic::Session.start(duration: 10) do |window|
|
91
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
92
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
93
|
+
window.tab_thread do |paris|
|
94
|
+
paris.goto "https://www.lewagon.com/paris"
|
95
|
+
paris.at_css('[href="/paris/apply"]').click
|
96
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
97
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
Now the block will exit after 10 seconds.
|
103
|
+
|
104
|
+
If you don't want to guess the time for pages to stay open—you can use the `quit` parameter of session. Note that in that case, you need to wait for all your threads to exit. As a convenience, `session` block yields a second argument that is a primitive implementation of a thread pool.
|
105
|
+
|
106
|
+
```rb
|
107
|
+
require "foot_traffic"
|
108
|
+
using FootTraffic
|
109
|
+
|
110
|
+
FootTraffic::Session.start(quit: true) do |window, pool|
|
111
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
112
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
113
|
+
pool << window.tab_thread do |paris|
|
114
|
+
paris.goto "https://www.lewagon.com/paris"
|
115
|
+
paris.at_css('[href="/paris/apply"]').click
|
116
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
117
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
118
|
+
end
|
119
|
+
pool.wait
|
120
|
+
end
|
121
|
+
```
|
122
|
+
|
123
|
+
Now, the session block will exit as soon as the last action in the last tab completes—your script can run further!
|
124
|
+
|
125
|
+
If you want to see how your website handles multiple concurrent visits—you can use the `clone` parameter that will open as many Chrome windows as you want and run the tab scenario in each of them.
|
126
|
+
|
127
|
+
As this puts the strain on your system's resources, it makes sense to also use some of the Ferrum's [options](https://github.com/rubycdp/ferrum#customization) to set higher timeouts for Chrome startup and page loads. `slowmo` parameter might be particularly useful for simulating real user behavior, as it will add a small wait before executing each action, including sending keyboard keys.
|
128
|
+
|
129
|
+
```rb
|
130
|
+
require "foot_traffic"
|
131
|
+
using FootTraffic
|
132
|
+
|
133
|
+
opts = {
|
134
|
+
process_timeout: 10,
|
135
|
+
timeout: 100,
|
136
|
+
slowmo: 0.1,
|
137
|
+
window_size: [1024, 768]
|
138
|
+
}
|
139
|
+
|
140
|
+
FootTraffic::Session.start(options: opts, quit: true, clones: 10) do |window, pool|
|
141
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
142
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
143
|
+
pool << window.tab_thread { |paris|
|
144
|
+
paris.goto "https://www.lewagon.com/paris"
|
145
|
+
paris.at_css('[href="/paris/apply"]').click
|
146
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
147
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
148
|
+
}
|
149
|
+
pool.wait
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
You can also set the `headless` option to `true` to perform script in headless mode. If you open too many concurrent tabs, or the number of clones too high—your system will run out of memory. To prevent that, Foot Traffic will raise the
|
154
|
+
`FootTraffic::ResourceOverloadError` once `ThreadError`, `RuntimeError`, `Errno::EMFILE`, or `Errno::ECONNRESET` start propagating.
|
155
|
+
|
156
|
+
```rb
|
157
|
+
require "foot_traffic"
|
158
|
+
using FootTraffic
|
159
|
+
|
160
|
+
opts = {
|
161
|
+
headless: true,
|
162
|
+
process_timeout: 10,
|
163
|
+
timeout: 100,
|
164
|
+
slowmo: 0.1,
|
165
|
+
window_size: [1024, 768]
|
166
|
+
}
|
167
|
+
|
168
|
+
begin
|
169
|
+
FootTraffic::Session.start(options: opts, quit: true, clones: 10) do |window, pool|
|
170
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
171
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
172
|
+
pool << window.tab_thread { |paris|
|
173
|
+
paris.goto "https://www.lewagon.com/paris"
|
174
|
+
paris.at_css('[href="/paris/apply"]').click
|
175
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
176
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
177
|
+
}
|
178
|
+
pool.wait
|
179
|
+
end
|
180
|
+
rescue FootTraffic::ResourceOverloadError
|
181
|
+
puts "Oops..."
|
182
|
+
exit(1)
|
183
|
+
end
|
184
|
+
```
|
185
|
+
|
186
|
+
You can also control cookies for each tab. Keep in mind that in this case you don't want all your actions to run entirely concurrently, as the values of cookies may leak between tabs. Here's an example on how to avoid it:
|
187
|
+
|
188
|
+
```rb
|
189
|
+
require "concurrent" # concurrent-ruby
|
190
|
+
|
191
|
+
tokens = [] # imaginary array of auth tokens
|
192
|
+
|
193
|
+
cookies = Concurrent::Hash.new
|
194
|
+
|
195
|
+
opts = {
|
196
|
+
headless: false, # Headless or not
|
197
|
+
timeout: 300, # How long to wait for new tab to open, set for high value
|
198
|
+
slowmo: 0.1, # How fast do you want bots to type
|
199
|
+
window_size: [1200, 800]
|
200
|
+
}
|
201
|
+
|
202
|
+
FootTraffic::Session.start(options: opts, quit: true) do |window, pool|
|
203
|
+
tokens.each do |token|
|
204
|
+
sleep(1) # Need to sleep so we can propely save cookies
|
205
|
+
pool << window.with_tab { |tab|
|
206
|
+
tab.goto("https://example.com/sign_in/#{token}")
|
207
|
+
cookies[token] = tab.cookies["_example_session"].value
|
208
|
+
}
|
209
|
+
end
|
210
|
+
pool.wait
|
211
|
+
end
|
212
|
+
|
213
|
+
FootTraffic::Session.start(options: opts) do |window|
|
214
|
+
tokens.each do |token|
|
215
|
+
sleep(1) # Wait to properly load cookies
|
216
|
+
window.with_tab do |tab|
|
217
|
+
tab.cookies.clear
|
218
|
+
tab.cookies.set(
|
219
|
+
name: "_example_session",
|
220
|
+
domain: "example.lewagon.co",
|
221
|
+
value: cookies[token]
|
222
|
+
)
|
223
|
+
tab.goto("https://example.com/protected_route")
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
```
|
228
|
+
|
229
|
+
Check out the [examples](https://github.com/lewagon/foot_traffic/tree/master/examples) folder to study some of the scripts above.
|
230
|
+
|
231
|
+
## Development
|
232
|
+
|
233
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
234
|
+
|
235
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
236
|
+
|
237
|
+
## Contributing
|
238
|
+
|
239
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/lewagon/foot_traffic.
|
240
|
+
|
241
|
+
## License
|
242
|
+
|
243
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "foot_traffic"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/docs/in_action.gif
ADDED
Binary file
|
data/examples/clones.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require "foot_traffic"
|
2
|
+
using FootTraffic
|
3
|
+
|
4
|
+
opts = {
|
5
|
+
headless: true,
|
6
|
+
process_timeout: 10,
|
7
|
+
timeout: 100,
|
8
|
+
slowmo: 0.1,
|
9
|
+
window_size: [1024, 768]
|
10
|
+
}
|
11
|
+
|
12
|
+
begin
|
13
|
+
FootTraffic::Session.start(options: opts, quit: true, clones: 100) do |window, pool|
|
14
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
15
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
16
|
+
pool << window.tab_thread { |paris|
|
17
|
+
paris.goto "https://www.lewagon.com/paris"
|
18
|
+
paris.at_css('[href="/paris/apply"]').click
|
19
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
20
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
21
|
+
}
|
22
|
+
pool.wait
|
23
|
+
end
|
24
|
+
rescue FootTraffic::ResourceOverloadError
|
25
|
+
puts "Oops..."
|
26
|
+
exit(1)
|
27
|
+
end
|
data/examples/cookies.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
require "concurrent" # concurrent-ruby
|
2
|
+
|
3
|
+
tokens = [] # imaginary array of auth tokens
|
4
|
+
|
5
|
+
cookies = Concurrent::Hash.new
|
6
|
+
|
7
|
+
opts = {
|
8
|
+
headless: false, # Headless or not
|
9
|
+
timeout: 300, # How long to wait for new tab to open, set for high value
|
10
|
+
slowmo: 0.1, # How fast do you want bots to type
|
11
|
+
window_size: [1200, 800]
|
12
|
+
}
|
13
|
+
|
14
|
+
FootTraffic::Session.start(options: opts, quit: true) do |window, pool|
|
15
|
+
tokens.each do |token|
|
16
|
+
sleep(1) # Need to sleep so we can propely save cookies
|
17
|
+
pool << window.with_tab { |tab|
|
18
|
+
tab.goto("https://example.com/sign_in/#{token}")
|
19
|
+
cookies[token] = tab.cookies["_example_session"].value
|
20
|
+
}
|
21
|
+
end
|
22
|
+
pool.wait
|
23
|
+
end
|
24
|
+
|
25
|
+
FootTraffic::Session.start(options: opts) do |window|
|
26
|
+
tokens.each do |token|
|
27
|
+
sleep(1) # Wait to properly load cookies
|
28
|
+
window.with_tab do |tab|
|
29
|
+
tab.cookies.clear
|
30
|
+
tab.cookies.set(
|
31
|
+
name: "_example_session",
|
32
|
+
domain: "example.lewagon.co",
|
33
|
+
value: cookies[token]
|
34
|
+
)
|
35
|
+
tab.goto("https://example.com/protected_route")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "foot_traffic"
|
2
|
+
using FootTraffic
|
3
|
+
|
4
|
+
FootTraffic::Session.start do |window|
|
5
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
6
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
7
|
+
window.tab_thread do |paris|
|
8
|
+
paris.goto "https://www.lewagon.com/paris"
|
9
|
+
paris.at_css('[href="/paris/apply"]').click
|
10
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
11
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "foot_traffic"
|
2
|
+
using FootTraffic
|
3
|
+
|
4
|
+
FootTraffic::Session.start(quit: true) do |window, pool|
|
5
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
6
|
+
pool << window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
7
|
+
pool << window.tab_thread { |paris|
|
8
|
+
paris.goto "https://www.lewagon.com/paris"
|
9
|
+
paris.at_css('[href="/paris/apply"]').click
|
10
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
11
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
12
|
+
}
|
13
|
+
pool.wait
|
14
|
+
end
|
data/examples/simple.rb
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require "foot_traffic"
|
2
|
+
using FootTraffic
|
3
|
+
|
4
|
+
FootTraffic::Session.start do |window|
|
5
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com" }
|
6
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com/berlin" }
|
7
|
+
window.tab_thread { |tab| tab.goto "https://www.lewagon.com/paris" }
|
8
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "foot_traffic"
|
2
|
+
using FootTraffic
|
3
|
+
|
4
|
+
FootTraffic::Session.start do |window|
|
5
|
+
window.new_tab.goto "https://www.lewagon.com"
|
6
|
+
window.new_tab.goto "https://www.lewagon.com/berlin"
|
7
|
+
|
8
|
+
paris = window.new_tab
|
9
|
+
paris.goto "https://www.lewagon.com/paris"
|
10
|
+
paris.at_css('[href="/paris/apply"]').click
|
11
|
+
paris.at_css("#apply_first_name").focus.type("Alan")
|
12
|
+
paris.at_css("#apply_last_name").focus.type("Turing", :Tab)
|
13
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require_relative "lib/foot_traffic/version"
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = "foot_traffic"
|
5
|
+
spec.version = FootTraffic::VERSION
|
6
|
+
spec.authors = ["Andy B"]
|
7
|
+
spec.email = ["andrey@lewagon.org"]
|
8
|
+
|
9
|
+
spec.summary = "Control a fleet of Chromes from a Ruby script. Built on Ferrum."
|
10
|
+
spec.description = "Foot Traffic allows to simulate real web users for load testing, debugging, or feature discovery"
|
11
|
+
spec.homepage = "https://github.com/lewagon/foot_traffic"
|
12
|
+
spec.license = "MIT"
|
13
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
|
14
|
+
|
15
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
16
|
+
spec.metadata["source_code_uri"] = "https://github.com/lewagon/foot_traffic"
|
17
|
+
spec.metadata["changelog_uri"] = "https://github.com/lewagon/foot_traffic/CHANGELOG"
|
18
|
+
|
19
|
+
# Specify which files should be added to the gem when it is released.
|
20
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
21
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
22
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
|
+
end
|
24
|
+
spec.bindir = "exe"
|
25
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
26
|
+
spec.require_paths = ["lib"]
|
27
|
+
|
28
|
+
spec.add_dependency "ferrum", "~> 0.8"
|
29
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
30
|
+
end
|
data/lib/foot_traffic.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require "ferrum"
|
2
|
+
|
3
|
+
module FootTraffic
|
4
|
+
refine Ferrum::Context do
|
5
|
+
def new_tab
|
6
|
+
create_page
|
7
|
+
end
|
8
|
+
|
9
|
+
def in_thread(&block)
|
10
|
+
Thread.new do
|
11
|
+
block.call(create_page)
|
12
|
+
rescue ThreadError, RuntimeError, Errno::EMFILE, Errno::ECONNRESET
|
13
|
+
raise ResourceOverloadError
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :with_tab, :in_thread
|
18
|
+
alias_method :tab_thread, :in_thread
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_relative "refinements"
|
2
|
+
require "ferrum"
|
3
|
+
|
4
|
+
module FootTraffic
|
5
|
+
class ThreadPool
|
6
|
+
def initialize
|
7
|
+
@threads = []
|
8
|
+
end
|
9
|
+
|
10
|
+
def <<(thread)
|
11
|
+
@threads << thread
|
12
|
+
end
|
13
|
+
|
14
|
+
def wait
|
15
|
+
@threads.map(&:join)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Session
|
20
|
+
def self.start(options: {}, duration: nil, clones: 1, quit: false, &block)
|
21
|
+
new(options).start(duration: duration, clones: clones, quit: quit, &block)
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(opts)
|
25
|
+
opts[:headless] ||= false
|
26
|
+
@browser ||= Ferrum::Browser.new(opts)
|
27
|
+
end
|
28
|
+
|
29
|
+
def start(duration: nil, clones: 1, quit: false, &block)
|
30
|
+
main = Thread.new {
|
31
|
+
threads = []
|
32
|
+
clones.times do
|
33
|
+
threads << Thread.new {
|
34
|
+
window = @browser.contexts.create
|
35
|
+
block.call(window, ThreadPool.new)
|
36
|
+
}
|
37
|
+
end
|
38
|
+
threads.map(&:join)
|
39
|
+
}
|
40
|
+
|
41
|
+
# A sleeping thread to keep Ferrum open for a given period of time
|
42
|
+
unless quit
|
43
|
+
wait = Thread.new {
|
44
|
+
duration.nil? ? sleep : sleep(duration)
|
45
|
+
}
|
46
|
+
wait.join
|
47
|
+
end
|
48
|
+
|
49
|
+
main.join
|
50
|
+
@browser.quit
|
51
|
+
rescue ThreadError, RuntimeError, Errno::EMFILE, Errno::ECONNRESET
|
52
|
+
raise ResourceOverloadError
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
metadata
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: foot_traffic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Andy B
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-05-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ferrum
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.8'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
41
|
+
description: Foot Traffic allows to simulate real web users for load testing, debugging,
|
42
|
+
or feature discovery
|
43
|
+
email:
|
44
|
+
- andrey@lewagon.org
|
45
|
+
executables: []
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".gitignore"
|
50
|
+
- ".rspec"
|
51
|
+
- Gemfile
|
52
|
+
- Gemfile.lock
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- bin/console
|
57
|
+
- bin/setup
|
58
|
+
- docs/in_action.gif
|
59
|
+
- examples/clones.rb
|
60
|
+
- examples/cookies.rb
|
61
|
+
- examples/multi_threaded.rb
|
62
|
+
- examples/multi_threaded_pool.rb
|
63
|
+
- examples/simple.rb
|
64
|
+
- examples/single_threaded.rb
|
65
|
+
- foot_traffic.gemspec
|
66
|
+
- lib/foot_traffic.rb
|
67
|
+
- lib/foot_traffic/refinements.rb
|
68
|
+
- lib/foot_traffic/session.rb
|
69
|
+
- lib/foot_traffic/version.rb
|
70
|
+
homepage: https://github.com/lewagon/foot_traffic
|
71
|
+
licenses:
|
72
|
+
- MIT
|
73
|
+
metadata:
|
74
|
+
homepage_uri: https://github.com/lewagon/foot_traffic
|
75
|
+
source_code_uri: https://github.com/lewagon/foot_traffic
|
76
|
+
changelog_uri: https://github.com/lewagon/foot_traffic/CHANGELOG
|
77
|
+
post_install_message:
|
78
|
+
rdoc_options: []
|
79
|
+
require_paths:
|
80
|
+
- lib
|
81
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: 2.3.0
|
86
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
requirements: []
|
92
|
+
rubygems_version: 3.1.2
|
93
|
+
signing_key:
|
94
|
+
specification_version: 4
|
95
|
+
summary: Control a fleet of Chromes from a Ruby script. Built on Ferrum.
|
96
|
+
test_files: []
|