click_session 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +286 -0
- data/Rakefile +1 -0
- data/click_session.gemspec +36 -0
- data/lib/click_session/async.rb +45 -0
- data/lib/click_session/click_session_processor.rb +64 -0
- data/lib/click_session/configuration.rb +142 -0
- data/lib/click_session/exceptions.rb +12 -0
- data/lib/click_session/failure_status_reporter.rb +15 -0
- data/lib/click_session/notifier.rb +23 -0
- data/lib/click_session/response_serializer.rb +34 -0
- data/lib/click_session/s3_connection.rb +34 -0
- data/lib/click_session/s3_file_uploader.rb +24 -0
- data/lib/click_session/session_state.rb +64 -0
- data/lib/click_session/status_reporter.rb +81 -0
- data/lib/click_session/successful_status_reporter.rb +15 -0
- data/lib/click_session/sync.rb +76 -0
- data/lib/click_session/version.rb +3 -0
- data/lib/click_session/web_runner.rb +60 -0
- data/lib/click_session/web_runner_processor.rb +65 -0
- data/lib/click_session/webhook.rb +24 -0
- data/lib/click_session/webhook_model_serializer.rb +7 -0
- data/lib/click_session.rb +34 -0
- data/lib/generators/click_session/db/migration/create_session_states.rb +13 -0
- data/lib/generators/click_session/initializers/click_session.rb +4 -0
- data/lib/generators/click_session/install_generator.rb +54 -0
- data/lib/tasks/click_session.rake +52 -0
- data/spec/click_session/async_spec.rb +66 -0
- data/spec/click_session/click_session_processor_spec.rb +292 -0
- data/spec/click_session/configuration_spec.rb +168 -0
- data/spec/click_session/failure_status_reporter_spec.rb +87 -0
- data/spec/click_session/notifier_spec.rb +72 -0
- data/spec/click_session/response_serializer_spec.rb +50 -0
- data/spec/click_session/s3_file_uploader_spec.rb +24 -0
- data/spec/click_session/session_state_spec.rb +54 -0
- data/spec/click_session/status_reporter_spec.rb +199 -0
- data/spec/click_session/successful_status_reporter_spec.rb +85 -0
- data/spec/click_session/sync_spec.rb +259 -0
- data/spec/click_session/web_runner_processor_spec.rb +143 -0
- data/spec/click_session/web_runner_spec.rb +77 -0
- data/spec/click_session/webhook_spec.rb +75 -0
- data/spec/factories/test_unit_model_factory.rb +5 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/click_session_runner.rb +5 -0
- data/spec/support/dummy_web_runner.rb +2 -0
- data/spec/support/schema.rb +16 -0
- data/spec/support/test_unit_model.rb +3 -0
- metadata +310 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8d180275fd38f126e826853293950674857701ee
|
4
|
+
data.tar.gz: 86ed88478e1de5c0b2fd245819617c82f56e5d4b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 481b241d4dab8796a7a9c8fcd78e535f184c396859f135369a6ac722dd768f03f3be35c2884dc7918c224ee5aec25695328756b04baf15f747707641da934856
|
7
|
+
data.tar.gz: 1abddccdb8cbbc18b1c87fb7444c836633c28835b6490d432368df4632197c61f135ee47bbd6c6f4bb64fa3e4688bb30e166a17b88d103ee8cc1a001a97d5989
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Tobias Talltorp
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,286 @@
|
|
1
|
+
# click_session
|
2
|
+
Turn any repeatable web navigation process into an api
|
3
|
+
|
4
|
+
## Why?
|
5
|
+
Modern web apps rely more and more on html to be loaded asyncronously after the page has been loaded. The current solutions for automating a series of clicks, form posts and navigation changes relies on all html being rendered at once.
|
6
|
+
|
7
|
+
The Capybara team has put a lot of thought into how these web apps can be tested and because of this, it also makes a good tool for scraping these web sites.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
## How to set up
|
12
|
+
|
13
|
+
### Generate a migration
|
14
|
+
`rails generate click_session:install`
|
15
|
+
This will create a migration and generate an initializer with configuration parameters needed for click_session
|
16
|
+
|
17
|
+
### Define the steps in a class
|
18
|
+
Name the class ```ClickSessionRunner``` and add a method called ```run```.
|
19
|
+
|
20
|
+
This class must extend the ```WebRunner``` class
|
21
|
+
|
22
|
+
The ```model``` is an ActiveRecord model which holds the data needed for the session.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
class ClickSessionRunner < ClickSession::WebRunner
|
26
|
+
|
27
|
+
# Steps to simulate
|
28
|
+
def run(model)
|
29
|
+
visit "https://www.stackoverflow.com"
|
30
|
+
fill_in "q", with: "Capybara"
|
31
|
+
press_enter_to_submit
|
32
|
+
|
33
|
+
model.name = first_search_result.text
|
34
|
+
|
35
|
+
model.save
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def press_enter_to_submit
|
41
|
+
find_field('q').native.send_key(:enter)
|
42
|
+
end
|
43
|
+
|
44
|
+
def first_search_result
|
45
|
+
page.first(".summary")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
```
|
49
|
+
|
50
|
+
### Run session syncronously
|
51
|
+
__Note:__ The response time for this type of request is totally dependant of the time it takes to visit all the pages.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
user = User.new
|
55
|
+
sync_click_session = ClickSession::Sync.new(user)
|
56
|
+
result = sync_click_session.run
|
57
|
+
# --> saves the User
|
58
|
+
# --> run the steps in the ClickSessionRunner
|
59
|
+
# --> result contains the serialized user data
|
60
|
+
```
|
61
|
+
|
62
|
+
### Run session asyncronously
|
63
|
+
```ruby
|
64
|
+
user = User.new
|
65
|
+
async_click_session = ClickSession::Async.new(user)
|
66
|
+
result = async_click_session.run
|
67
|
+
# --> saves the User
|
68
|
+
# --> saves the SessionState
|
69
|
+
# --> result contains the ID of the saved SessionState
|
70
|
+
|
71
|
+
# $ rake click_session:process_active
|
72
|
+
# --> run the steps in the ClickSessionRunner
|
73
|
+
|
74
|
+
# $ rake click_session:report_successful
|
75
|
+
# --> the request sent contains the serialized user data
|
76
|
+
```
|
77
|
+
|
78
|
+
### result hash
|
79
|
+
Example:
|
80
|
+
```
|
81
|
+
{
|
82
|
+
id: 1234,
|
83
|
+
status: {
|
84
|
+
success: true, # Boolean
|
85
|
+
},
|
86
|
+
data: { # This is the output of the Serialized model
|
87
|
+
name: "Joe",
|
88
|
+
facebook_avatar: "http://fb.com/i/joe.png"
|
89
|
+
}
|
90
|
+
}
|
91
|
+
```
|
92
|
+
The only optional part of the result is the ```data```.
|
93
|
+
|
94
|
+
### Example of how to use it in a rails controller action
|
95
|
+
```ruby
|
96
|
+
def show
|
97
|
+
user = User.new
|
98
|
+
sync_click_session = ClickSession::Sync.new(user)
|
99
|
+
|
100
|
+
result = sync_click_session.run
|
101
|
+
|
102
|
+
if result.status.success
|
103
|
+
render json: result.as_json, status: 201
|
104
|
+
else
|
105
|
+
render json: result.as_json, status: :unprocessable_entity
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
```
|
110
|
+
|
111
|
+
## Mandatory configurations
|
112
|
+
```ruby
|
113
|
+
ClickSession.configure do | config |
|
114
|
+
config.model_class = YourModel
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
## Optional configurations and extentions
|
119
|
+
|
120
|
+
```ruby
|
121
|
+
ClickSession.configure do | config |
|
122
|
+
config.processor_class = MyCustomProcessor
|
123
|
+
config.notifier_class = MyCustomNotifier
|
124
|
+
config.serializer_class = MyCustomSerializer
|
125
|
+
config.success_callback_url = "https://my.domain.com/webhook_success"
|
126
|
+
config.failure_callback_url = "https://my.domain.com/webhook_failure"
|
127
|
+
config.enable_screenshot = false # true
|
128
|
+
config.screenshot = {
|
129
|
+
s3_bucket: ENV['S3_BUCKET'],
|
130
|
+
s3_key_id: ENV['S3_KEY_ID'],
|
131
|
+
s3_access_key: ENV['S3_ACCESS_KEY']
|
132
|
+
}
|
133
|
+
config.driver_client = :poltergeist # :selenium
|
134
|
+
end
|
135
|
+
```
|
136
|
+
|
137
|
+
Option | Description
|
138
|
+
------- | ------------
|
139
|
+
```notifier_class``` | The name of the class with your [custom notifications](#define-how-you-want-to-be-notified)
|
140
|
+
```serializer_class``` | The name of the class with your [custom serializer](#define-how-you-want-to-serialize-the-result)
|
141
|
+
```success_callback_url``` | The url you want us to ```POST``` to with the successful result. Only needed when using ```AsyncClickSession```
|
142
|
+
```failure_callback_url``` | The url you want us to ```POST``` to with the error message. Only needed when using ```AsyncClickSession```
|
143
|
+
```enable_screenshot``` | Must be set to true if you want to save screenshots.
|
144
|
+
```screenshot``` | A hash containing the configuration information needed to be able to save screenshots. ```s3_bucket```, ```s3_key_id``` and ```s3_access_key``` are all required.
|
145
|
+
```driver_client``` | The driver you want to use to run the ClickSession. ```:poltergeist``` is the default, but ```:selenium``` is a good choice if you are developing in a local environment and want to see the browser appear.
|
146
|
+
|
147
|
+
|
148
|
+
### Define how you want to serialize the result
|
149
|
+
The serializer class takes the ```model``` that you accociated with the click_session and lets you transform it to whatever structure you like.
|
150
|
+
|
151
|
+
If you don't specify this class, we do a simple ```.as_json``` of the model and return that as the serialized result.
|
152
|
+
|
153
|
+
This can be good when there might be things that you save on the model that are not needed in the result, such as generated tokens or simple placeholders of data.
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
class MyUserSerializer
|
157
|
+
def serialize(model)
|
158
|
+
api_user = {
|
159
|
+
name: model.name,
|
160
|
+
facebook_avatar: model.user_image
|
161
|
+
}
|
162
|
+
|
163
|
+
api_user.as_json
|
164
|
+
end
|
165
|
+
end
|
166
|
+
```
|
167
|
+
|
168
|
+
### Define how you want to be notified
|
169
|
+
We will notify you when the following things happen
|
170
|
+
* The ClickSession was successfully completed
|
171
|
+
* The ClickSession failed because the max number of retries to run the SessionRunner has been exceeded
|
172
|
+
* The status of the ClickSession was successfully reported back to the webhook
|
173
|
+
* The max number of retries to report the asyncronous result (success or failure) back to your web hook has been exceeded.
|
174
|
+
* Every time we rescue an error
|
175
|
+
|
176
|
+
If this class is not defined, the information is logged to ```stdout```
|
177
|
+
|
178
|
+
All of these notifications are executed after the model has been successfully persisted.
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
# Override any number of methods to
|
182
|
+
# customize the behaviour of the notifications
|
183
|
+
|
184
|
+
class MyCustomNotifier < ClickSession::Notifier
|
185
|
+
def session_successful(session)
|
186
|
+
# Post to slack channel
|
187
|
+
# Send an email to the boss
|
188
|
+
super # log to stdout
|
189
|
+
end
|
190
|
+
|
191
|
+
def session_failed(session)
|
192
|
+
# Post to "alerts" channel on slack
|
193
|
+
# Send email to developers
|
194
|
+
end
|
195
|
+
|
196
|
+
def session_reported(session)
|
197
|
+
# Post to slack
|
198
|
+
end
|
199
|
+
|
200
|
+
def session_failed_to_report(session)
|
201
|
+
# Send email to developers
|
202
|
+
# Alert operations!
|
203
|
+
end
|
204
|
+
|
205
|
+
def rescued_error(e)
|
206
|
+
# Send the error to airbrake
|
207
|
+
end
|
208
|
+
end
|
209
|
+
```
|
210
|
+
|
211
|
+
All other types of possible errors must be handled by your own code.
|
212
|
+
|
213
|
+
### Save screen shots to S3
|
214
|
+
If you have enabled screenshots in your configuration, we will take a screenshot after the run has been successful or failed.
|
215
|
+
|
216
|
+
__Note:__ This requires you to add the S3 credentials and bucket name to the configuration
|
217
|
+
|
218
|
+
|
219
|
+
## Rake tasks
|
220
|
+
### click_session:process_active
|
221
|
+
Processes all the active click sessions, meaning the ones that are ready to be run.
|
222
|
+
|
223
|
+
__Note:__ Only needed for ```ClickSession::Async```
|
224
|
+
|
225
|
+
### click_session:report_succesful
|
226
|
+
Reports all click_sessions, which were successfully run, to the configured ```success_callback_url```
|
227
|
+
|
228
|
+
__Note:__ Only needed for ```ClickSession::Async```
|
229
|
+
|
230
|
+
### click_session:report_failed
|
231
|
+
Reports all click_sessions, which failed to run, to the configured ```failure_callback_url```
|
232
|
+
|
233
|
+
__Note:__ Only needed for ```ClickSession::Async```
|
234
|
+
|
235
|
+
### click_session:report_failed
|
236
|
+
Reports all click_sessions, which failed to run, to the configured ```failure_callback_url```
|
237
|
+
|
238
|
+
__Note:__ Only needed for ```ClickSession::Async```
|
239
|
+
|
240
|
+
### click_session:validate (not yet implemented)
|
241
|
+
Runs the steps you defined that validates that the steps in the session has not changed.
|
242
|
+
|
243
|
+
```ruby
|
244
|
+
class ClickSessionRunner < WebRunner
|
245
|
+
def run(search_result_model)
|
246
|
+
# ...
|
247
|
+
end
|
248
|
+
|
249
|
+
def validate(model)
|
250
|
+
visit "http://www.google.com"
|
251
|
+
|
252
|
+
unless search_field_accesible?
|
253
|
+
raise ValidateClickSessionError("There are no results!!")
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
private
|
258
|
+
def search_field_accesible?
|
259
|
+
page.find("input[name='query']") != nil
|
260
|
+
end
|
261
|
+
end
|
262
|
+
```
|
263
|
+
|
264
|
+
## Dependencies
|
265
|
+
This gem is dependant on you having a browser installed which can be run by the capybara driver.
|
266
|
+
|
267
|
+
We have tested it with
|
268
|
+
* poltergeist (PhantomJS)
|
269
|
+
* selenium (FireFox)
|
270
|
+
|
271
|
+
## Deployment
|
272
|
+
If you like to deploy your code to heroku, you need to use the ```build-pack-multi```.
|
273
|
+
|
274
|
+
Create a file in the root of your application called ```.buildpacks```with this content
|
275
|
+
```
|
276
|
+
https://github.com/stomita/heroku-buildpack-phantomjs
|
277
|
+
https://github.com/heroku/heroku-buildpack-ruby
|
278
|
+
```
|
279
|
+
|
280
|
+
## Contributing
|
281
|
+
|
282
|
+
1. Fork it ( http://github.com/<my-github-username>/click_session/fork )
|
283
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
284
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
285
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
286
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'click_session/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "click_session"
|
8
|
+
spec.version = ClickSession::VERSION
|
9
|
+
spec.authors = ["Tobias Talltorp"]
|
10
|
+
spec.email = ["tobias@talltorp.se"]
|
11
|
+
spec.summary = "Navigates the web for you"
|
12
|
+
spec.description = "Navigates the web for you"
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_dependency 'rails', '>= 4.1'
|
22
|
+
spec.add_dependency 'capybara', "~> 2.4"
|
23
|
+
spec.add_dependency 'poltergeist', "~> 1.6"
|
24
|
+
spec.add_dependency 'rest-client', "~> 1.7"
|
25
|
+
spec.add_dependency 'state_machine', "~> 1.2"
|
26
|
+
spec.add_dependency 'aws-sdk-v1', "~> 1.63"
|
27
|
+
spec.add_dependency 'selenium-webdriver', "~> 2.45"
|
28
|
+
|
29
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
30
|
+
spec.add_development_dependency "rake"
|
31
|
+
spec.add_development_dependency "rspec-rails", "~> 3.1"
|
32
|
+
spec.add_development_dependency "factory_girl_rails", "~> 4.2"
|
33
|
+
spec.add_development_dependency 'shoulda-matchers'
|
34
|
+
spec.add_development_dependency 'webmock', '~> 1.18'
|
35
|
+
spec.add_development_dependency "sqlite3"
|
36
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module ClickSession
|
2
|
+
class Async
|
3
|
+
attr_reader :model
|
4
|
+
attr_accessor :click_session
|
5
|
+
|
6
|
+
def initialize(model)
|
7
|
+
@model = model
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
validate_async_configuration
|
12
|
+
|
13
|
+
@click_session = SessionState.create(model: model)
|
14
|
+
serialize_success_response
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def validate_async_configuration
|
20
|
+
if success_callback_missing? || failure_callback_missing?
|
21
|
+
raise ConfigurationError.new("You need to configure the callback URLs in order to use the AsyncClickSession")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def success_callback_missing?
|
26
|
+
ClickSession.configuration.success_callback_url == nil
|
27
|
+
end
|
28
|
+
|
29
|
+
def failure_callback_missing?
|
30
|
+
ClickSession.configuration.failure_callback_url == nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def serialize_success_response
|
34
|
+
serializer.serialize_success(click_session)
|
35
|
+
end
|
36
|
+
|
37
|
+
def serialize_failure_response
|
38
|
+
serializer.serialize_failure(click_session)
|
39
|
+
end
|
40
|
+
|
41
|
+
def serializer
|
42
|
+
@serializer ||= ClickSession::ResponseSerializer.new
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative './exceptions'
|
2
|
+
|
3
|
+
module ClickSession
|
4
|
+
class ClickSessionProcessor
|
5
|
+
attr_accessor :click_session
|
6
|
+
attr_reader :web_runner_processor, :notifier, :screenshot_enabled, :screenshot_options
|
7
|
+
|
8
|
+
def initialize(click_session, web_runner_processor, notifier, options = {})
|
9
|
+
@click_session = click_session
|
10
|
+
@web_runner_processor = web_runner_processor
|
11
|
+
@notifier = notifier
|
12
|
+
@screenshot_enabled = options[:screenshot_enabled] || false
|
13
|
+
@screenshot_options = options[:screenshot_options] || nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def process
|
17
|
+
validate_screenshot_configuration
|
18
|
+
|
19
|
+
begin
|
20
|
+
process_provided_steps_in_session
|
21
|
+
rescue TooManyRetriesError => e
|
22
|
+
take_care_of_failed_session
|
23
|
+
raise e
|
24
|
+
end
|
25
|
+
|
26
|
+
click_session
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def validate_screenshot_configuration
|
32
|
+
if screenshot_enabled
|
33
|
+
if screenshot_options == nil
|
34
|
+
raise ConfigurationError.new(<<-ERROR.strip_heredoc)
|
35
|
+
In order to save screenshots, you need to enter s3 information
|
36
|
+
in the 'screenshot' option of the configuration
|
37
|
+
See https://github.com/talltorp/click_session for more information.
|
38
|
+
ERROR
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def process_provided_steps_in_session
|
44
|
+
web_runner_processor.process(click_session.model)
|
45
|
+
click_session.success!
|
46
|
+
notifier.session_successful(click_session)
|
47
|
+
|
48
|
+
if screenshot_enabled
|
49
|
+
click_session.screenshot_url = web_runner_processor.save_screenshot(click_session.id)
|
50
|
+
click_session.save
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def take_care_of_failed_session
|
55
|
+
click_session.failure!
|
56
|
+
notifier.session_failed(click_session)
|
57
|
+
|
58
|
+
if screenshot_enabled
|
59
|
+
click_session.screenshot_url = web_runner_processor.save_screenshot(click_session.id)
|
60
|
+
click_session.save
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module ClickSession
|
2
|
+
@@configuration = nil
|
3
|
+
|
4
|
+
def self.configure
|
5
|
+
@@configuration = Configuration.new
|
6
|
+
|
7
|
+
if block_given?
|
8
|
+
yield configuration
|
9
|
+
end
|
10
|
+
|
11
|
+
configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.configuration
|
15
|
+
@@configuration || configure
|
16
|
+
end
|
17
|
+
|
18
|
+
class Configuration
|
19
|
+
attr_accessor :processor_class,
|
20
|
+
:success_callback_url,
|
21
|
+
:failure_callback_url,
|
22
|
+
:serializer_class
|
23
|
+
|
24
|
+
def model_class
|
25
|
+
if @model_class == nil
|
26
|
+
raise NameError.new(<<-ERROR.strip_heredoc, 'model_class')
|
27
|
+
To use ClickSession, you must define the name of the active model
|
28
|
+
you want ClickSession to operate on.
|
29
|
+
See https://github.com/talltorp/click_session for more information.
|
30
|
+
ERROR
|
31
|
+
end
|
32
|
+
|
33
|
+
@model_class.constantize
|
34
|
+
end
|
35
|
+
|
36
|
+
def model_class=(klass)
|
37
|
+
@model_class = klass.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def processor_class
|
41
|
+
@processor_class ||=
|
42
|
+
begin
|
43
|
+
if Kernel.const_defined?(:ClickSessionRunner)
|
44
|
+
"ClickSessionRunner"
|
45
|
+
else
|
46
|
+
raise NameError.new(<<-ERROR.strip_heredoc, 'ClickSessionRunner')
|
47
|
+
To use ClickSession, you must either define `ClickSessionRunner` or configure a
|
48
|
+
different processor. See https://github.com/talltorp/click_session for
|
49
|
+
more information.
|
50
|
+
ERROR
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
@processor_class.constantize
|
55
|
+
end
|
56
|
+
|
57
|
+
def processor_class=(klass)
|
58
|
+
@processor_class = klass.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
def serializer_class
|
62
|
+
@serializer_class ||= "ClickSession::WebhookModelSerializer"
|
63
|
+
@serializer_class.constantize
|
64
|
+
end
|
65
|
+
|
66
|
+
def serializer_class=(klass)
|
67
|
+
@serializer_class = klass.to_s
|
68
|
+
end
|
69
|
+
|
70
|
+
def notifier_class
|
71
|
+
@notifier_class ||= "ClickSession::Notifier"
|
72
|
+
constantized_notifier = @notifier_class.constantize
|
73
|
+
|
74
|
+
if notifier_class_violates_interface(constantized_notifier)
|
75
|
+
raise ArgumentError.new(<<-ERROR.strip_heredoc)
|
76
|
+
Your custom notifier must inherit ClickSession::Notifier
|
77
|
+
See https://github.com/talltorp/click_session
|
78
|
+
ERROR
|
79
|
+
end
|
80
|
+
|
81
|
+
constantized_notifier
|
82
|
+
end
|
83
|
+
|
84
|
+
def notifier_class=(klass)
|
85
|
+
@notifier_class = klass.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def driver_client
|
89
|
+
@driver_client ||= :poltergeist
|
90
|
+
end
|
91
|
+
|
92
|
+
def driver_client=(client)
|
93
|
+
@driver_client = client
|
94
|
+
end
|
95
|
+
|
96
|
+
def screenshot_enabled?
|
97
|
+
@screenshot_enabled ||= false
|
98
|
+
end
|
99
|
+
|
100
|
+
def enable_screenshot=(enable)
|
101
|
+
@screenshot_enabled = enable
|
102
|
+
end
|
103
|
+
|
104
|
+
def screenshot
|
105
|
+
if @screenshot == nil
|
106
|
+
raise ArgumentError.new("In order to save screenshots, you need to configure \
|
107
|
+
the information.
|
108
|
+
https://github.com/talltorp/click_session#optional-configurations-and-extentions
|
109
|
+
")
|
110
|
+
end
|
111
|
+
|
112
|
+
@screenshot
|
113
|
+
end
|
114
|
+
|
115
|
+
def screenshot=(screenshot)
|
116
|
+
if screenshot[:s3_bucket] == nil
|
117
|
+
raise ArgumentError.new("The s3_bucket is required")
|
118
|
+
end
|
119
|
+
|
120
|
+
if screenshot[:s3_key_id] == nil
|
121
|
+
raise ArgumentError.new("The s3_key_id is required")
|
122
|
+
end
|
123
|
+
|
124
|
+
if screenshot[:s3_access_key] == nil
|
125
|
+
raise ArgumentError.new("The s3_access_key is required")
|
126
|
+
end
|
127
|
+
|
128
|
+
@screenshot = screenshot
|
129
|
+
end
|
130
|
+
|
131
|
+
private
|
132
|
+
|
133
|
+
def notifier_class_violates_interface(notifier_class)
|
134
|
+
required_methods = ClickSession::Notifier.instance_methods(false)
|
135
|
+
actual_methods = notifier_class.instance_methods
|
136
|
+
|
137
|
+
intersection = required_methods - actual_methods
|
138
|
+
|
139
|
+
intersection.length != 0
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ClickSession
|
2
|
+
# A general ClickSession exception
|
3
|
+
class Error < StandardError; end
|
4
|
+
|
5
|
+
# Raised when the threshold has been reached for how many retries we
|
6
|
+
# make of running the steps provided before we consider the entire
|
7
|
+
# session a failure
|
8
|
+
class TooManyRetriesError < Error; end
|
9
|
+
|
10
|
+
# Raised when the configuration is not properly initialized
|
11
|
+
class ConfigurationError < Error; end
|
12
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module ClickSession
|
2
|
+
class FailureStatusReporter < StatusReporter
|
3
|
+
def initialize(
|
4
|
+
webhook = Webhook.new(ClickSession.configuration.failure_callback_url)
|
5
|
+
)
|
6
|
+
super(webhook)
|
7
|
+
end
|
8
|
+
|
9
|
+
def report(click_session)
|
10
|
+
raise ArgumentError unless click_session.failed_to_process?
|
11
|
+
|
12
|
+
super(click_session)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ClickSession
|
2
|
+
class Notifier
|
3
|
+
def session_successful(click_session)
|
4
|
+
puts "SUCCESS: #{click_session.id} completed"
|
5
|
+
end
|
6
|
+
|
7
|
+
def session_failed(click_session)
|
8
|
+
$stderr.puts "FAILURE: #{click_session.id} failed"
|
9
|
+
end
|
10
|
+
|
11
|
+
def session_reported(click_session)
|
12
|
+
puts "REPORTED: #{click_session.id} successfully reported"
|
13
|
+
end
|
14
|
+
|
15
|
+
def session_failed_to_report(click_session)
|
16
|
+
$stderr.puts "REPORT_FAIL: #{click_session.id} failed to report"
|
17
|
+
end
|
18
|
+
|
19
|
+
def rescued_error(e)
|
20
|
+
puts "#{e.class.name}: #{e.message}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|