Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds "upload to s3" capabilities if configured #138

Merged
merged 1 commit into from
Apr 19, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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