Skip to content

Commit

Permalink
Merge pull request #138 from chendrix/add_s3_upload
Browse files Browse the repository at this point in the history
Adds "upload to s3" capabilities if configured
  • Loading branch information
mattheworiordan committed Apr 19, 2016
2 parents 82c5278 + 9cbcb2b commit 14af3c1
Show file tree
Hide file tree
Showing 11 changed files with 268 additions and 7 deletions.
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,31 @@ Capybara.save_and_open_page_path = "/file/path"
```


Uploading screenshots to S3
--------------------------
You can configure capybara-screenshot to automatically save your screenshots to an AWS S3 bucket.

First, install the `aws-sdk` gem or add it to your Gemfile

```ruby
gem 'capybara-screenshot', :group => :test
gem 'aws-sdk', :group => :test
```

Next, configure capybara-screenshot with your S3 credentials, the bucket to save to, and an optional region (default: `us-east-1`).

```ruby
Capybara::Screenshot.s3_configuration = {
s3_client_credentials: {
access_key_id: "my_access_key_id",
secret_access_key: "my_secret_access_key",
region: "eu-central-1"
},
bucket_name: "my_screenshots"
}
```


Pruning old screenshots automatically
--------------------------
By default screenshots are saved indefinitely, if you want them to be automatically pruned on a new failure, then you can specify one of the following prune strategies as follows:
Expand Down
1 change: 1 addition & 0 deletions capybara-screenshot.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Gem::Specification.new do |s|
s.add_development_dependency 'test-unit'
s.add_development_dependency 'spinach'
s.add_development_dependency 'minitest'
s.add_development_dependency 'aws-sdk'

s.files = `git ls-files`.split("\n")
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
Expand Down
17 changes: 15 additions & 2 deletions lib/capybara-screenshot.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ class << self
attr_accessor :webkit_options
attr_writer :final_session_name
attr_accessor :prune_strategy
attr_accessor :s3_configuration
end

self.autosave_on_failure = true
Expand All @@ -18,6 +19,7 @@ class << self
self.append_random = false
self.webkit_options = {}
self.prune_strategy = :keep_all
self.s3_configuration = {}

def self.append_screenshot_path=(value)
$stderr.puts "WARNING: Capybara::Screenshot.append_screenshot_path is deprecated. " +
Expand All @@ -26,7 +28,7 @@ def self.append_screenshot_path=(value)
end

def self.screenshot_and_save_page
saver = Saver.new(Capybara, Capybara.page)
saver = new_saver(Capybara, Capybara.page)
if saver.save
{:html => saver.html_path, :image => saver.screenshot_path}
end
Expand All @@ -35,7 +37,7 @@ def self.screenshot_and_save_page
def self.screenshot_and_open_image
require "launchy"

saver = Saver.new(Capybara, Capybara.page, false)
saver = new_saver(Capybara, Capybara.page, false)
if saver.save
Launchy.open saver.screenshot_path
{:html => nil, :image => saver.screenshot_path}
Expand Down Expand Up @@ -90,6 +92,17 @@ def self.reset_prune_history
@pruned_previous_screenshots = nil
end

def self.new_saver(*args)
saver = Saver.new(*args)

unless s3_configuration.empty?
require 'capybara-screenshot/s3_saver'
saver = S3Saver.new_with_configuration(saver, s3_configuration)
end

return saver
end

private

# If the path isn't set, default to the current directory
Expand Down
2 changes: 1 addition & 1 deletion lib/capybara-screenshot/cucumber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
Capybara.using_session(Capybara::Screenshot.final_session_name) do
filename_prefix = Capybara::Screenshot.filename_prefix_for(:cucumber, scenario)

saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
saver = Capybara::Screenshot.new_saver(Capybara, Capybara.page, true, filename_prefix)
saver.save
saver.output_screenshot_path

Expand Down
2 changes: 1 addition & 1 deletion lib/capybara-screenshot/minitest.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def after_teardown
Capybara.using_session(Capybara::Screenshot.final_session_name) do
filename_prefix = Capybara::Screenshot.filename_prefix_for(:minitest, self)

saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
saver = Capybara::Screenshot.new_saver(Capybara, Capybara.page, true, filename_prefix)
saver.save
saver.output_screenshot_path
end
Expand Down
2 changes: 1 addition & 1 deletion lib/capybara-screenshot/rspec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def after_failed_example(example)
if Capybara.page.current_url != '' && Capybara::Screenshot.autosave_on_failure && example.exception
filename_prefix = Capybara::Screenshot.filename_prefix_for(:rspec, example)

saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
saver = Capybara::Screenshot.new_saver(Capybara, Capybara.page, true, filename_prefix)
saver.save

example.metadata[:screenshot] = {}
Expand Down
64 changes: 64 additions & 0 deletions lib/capybara-screenshot/s3_saver.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
require 'aws-sdk'

module Capybara
module Screenshot
class S3Saver
DEFAULT_REGION = 'us-east-1'

def initialize(saver, s3_client, bucket_name)
@saver = saver
@s3_client = s3_client
@bucket_name = bucket_name
end

def self.new_with_configuration(saver, configuration)
default_s3_client_credentials = {
region: DEFAULT_REGION
}

s3_client_credentials = default_s3_client_credentials.merge(
configuration.fetch(:s3_client_credentials)
)

s3_client = Aws::S3::Client.new(s3_client_credentials)
bucket_name = configuration.fetch(:bucket_name)

new(saver, s3_client, bucket_name)
rescue KeyError
raise "Invalid S3 Configuration #{configuration}. Please refer to the documentation for the necessary configurations."
end

def save_and_upload_screenshot
save_and do |local_file_path|
File.open(local_file_path) do |file|
s3_client.put_object(
bucket: bucket_name,
key: File.basename(local_file_path),
body: file
)
end
end
end
alias_method :save, :save_and_upload_screenshot

def method_missing(method, *args)
# Need to use @saver instead of S3Saver#saver attr_reader method because
# using the method goes into infinite loop. Maybe attr_reader implements
# its methods via method_missing?
@saver.send(method, *args)
end

private
attr_reader :saver,
:s3_client,
:bucket_name

def save_and
saver.save

yield(saver.html_path) if block_given? && saver.html_saved?
yield(saver.screenshot_path) if block_given? && saver.screenshot_saved?
end
end
end
end
2 changes: 1 addition & 1 deletion lib/capybara-screenshot/spinach.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def self.fail_with_screenshot(step_data, exception, location, step_definitions)
if Capybara::Screenshot.autosave_on_failure
Capybara.using_session(Capybara::Screenshot.final_session_name) do
filename_prefix = Capybara::Screenshot.filename_prefix_for(:spinach, step_data)
saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
saver = Capybara::Screenshot.new_saver(Capybara, Capybara.page, true, filename_prefix)
saver.save
saver.output_screenshot_path
end
Expand Down
2 changes: 1 addition & 1 deletion lib/capybara-screenshot/testunit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def notify_fault_with_screenshot(fault, *args)
Capybara.using_session(Capybara::Screenshot.final_session_name) do
filename_prefix = Capybara::Screenshot.filename_prefix_for(:testunit, fault)

saver = Capybara::Screenshot::Saver.new(Capybara, Capybara.page, true, filename_prefix)
saver = Capybara::Screenshot.new_saver(Capybara, Capybara.page, true, filename_prefix)
saver.save
saver.output_screenshot_path
end
Expand Down
26 changes: 26 additions & 0 deletions spec/unit/capybara-screenshot_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@
end
end

describe '.new_saver' do
it 'passes through to get a new Saver if the user has not configured s3' do
saver_double = double('saver')
args = double('args')
expect(Capybara::Screenshot::Saver).to receive(:new).with(args).and_return(saver_double)

expect(Capybara::Screenshot.new_saver(args)).to eq(saver_double)
end

it 'wraps the returned saver in an S3 saver if it has been configured' do
require 'capybara-screenshot/s3_saver'

saver_double = double('saver')
args = double('args')
s3_saver_double = double('s3_saver')
s3_configuration = { hello: 'world' }

Capybara::Screenshot.s3_configuration = s3_configuration

expect(Capybara::Screenshot::Saver).to receive(:new).with(args).and_return(saver_double)
expect(Capybara::Screenshot::S3Saver).to receive(:new_with_configuration).with(saver_double, s3_configuration).and_return(s3_saver_double)

expect(Capybara::Screenshot.new_saver(args)).to eq(s3_saver_double)
end
end

describe '#prune' do
before do
Capybara::Screenshot.reset_prune_history
Expand Down
132 changes: 132 additions & 0 deletions spec/unit/s3_saver_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
require 'spec_helper'
require 'capybara-screenshot/s3_saver'

describe Capybara::Screenshot::S3Saver do
let(:saver) { double('saver') }
let(:bucket_name) { double('bucket_name') }
let(:s3_client) { double('s3_client') }

let(:s3_saver) { Capybara::Screenshot::S3Saver.new(saver, s3_client, bucket_name) }

describe '.new_with_configuration' do
let(:access_key_id) { double('access_key_id') }
let(:secret_access_key) { double('secret_access_key') }
let(:s3_client_credentials_using_defaults) {
{
access_key_id: access_key_id,
secret_access_key: secret_access_key
}
}

let(:region) { double('region') }
let(:s3_client_credentials) {
s3_client_credentials_using_defaults.merge(region: region)
}

it 'destructures the configuration into its components' do
allow(Aws::S3::Client).to receive(:new).and_return(s3_client)
allow(Capybara::Screenshot::S3Saver).to receive(:new)

Capybara::Screenshot::S3Saver.new_with_configuration(saver, {
s3_client_credentials: s3_client_credentials,
bucket_name: bucket_name
})

expect(Aws::S3::Client).to have_received(:new).with(s3_client_credentials)
expect(Capybara::Screenshot::S3Saver).to have_received(:new).with(saver, s3_client, bucket_name)
end

it 'defaults the region to us-east-1' do
default_region = 'us-east-1'

allow(Aws::S3::Client).to receive(:new).and_return(s3_client)
allow(Capybara::Screenshot::S3Saver).to receive(:new)

Capybara::Screenshot::S3Saver.new_with_configuration(saver, {
s3_client_credentials: s3_client_credentials_using_defaults,
bucket_name: bucket_name
})

expect(Aws::S3::Client).to have_received(:new).with(
s3_client_credentials.merge(region: default_region)
)

expect(Capybara::Screenshot::S3Saver).to have_received(:new).with(saver, s3_client, bucket_name)
end
end

describe '#save' do
before do
allow(saver).to receive(:html_saved?).and_return(false)
allow(saver).to receive(:screenshot_saved?).and_return(false)
allow(saver).to receive(:save)
end

it 'calls save on the underlying saver' do
expect(saver).to receive(:save)

s3_saver.save
end

it 'uploads the html' do
html_path = '/foo/bar.html'
expect(saver).to receive(:html_path).and_return(html_path)
expect(saver).to receive(:html_saved?).and_return(true)

html_file = double('html_file')

expect(File).to receive(:open).with(html_path).and_yield(html_file)

expect(s3_client).to receive(:put_object).with(
bucket: bucket_name,
key: 'bar.html',
body: html_file
)

s3_saver.save
end

it 'uploads the screenshot' do
screenshot_path = '/baz/bim.jpg'
expect(saver).to receive(:screenshot_path).and_return(screenshot_path)
expect(saver).to receive(:screenshot_saved?).and_return(true)

screenshot_file = double('screenshot_file')

expect(File).to receive(:open).with(screenshot_path).and_yield(screenshot_file)

expect(s3_client).to receive(:put_object).with(
bucket: bucket_name,
key: 'bim.jpg',
body: screenshot_file
)

s3_saver.save
end
end

# Needed because we cannot depend on Verifying Doubles
# in older RSpec versions
describe 'an actual saver' do
it 'implements the methods needed by the s3 saver' do
instance_methods = Capybara::Screenshot::Saver.instance_methods

expect(instance_methods).to include(:save)
expect(instance_methods).to include(:html_saved?)
expect(instance_methods).to include(:html_path)
expect(instance_methods).to include(:screenshot_saved?)
expect(instance_methods).to include(:screenshot_path)
end
end

describe 'any other method' do
it 'transparently passes through to the saver' do
allow(saver).to receive(:foo_bar)

args = double('args')
s3_saver.foo_bar(*args)

expect(saver).to have_received(:foo_bar).with(*args)
end
end
end

0 comments on commit 14af3c1

Please sign in to comment.