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

implement visual regression test #203

Merged
merged 3 commits into from Mar 6, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Gemfile
Expand Up @@ -58,8 +58,8 @@ group :development, :test do
gem 'byebug', platform: :mri
gem 'factory_bot'
gem 'faker'
gem 'ferrum'
gem 'rack-lineprof'
gem 'rack-mini-profiler'
gem 'rblineprof'
gem 'rblineprof-report'
gem 'rubocop'
Expand Down
10 changes: 7 additions & 3 deletions Gemfile.lock
Expand Up @@ -71,6 +71,7 @@ GEM
bindex (0.8.1)
builder (3.2.4)
byebug (11.1.3)
cliver (0.3.2)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
Expand Down Expand Up @@ -102,6 +103,11 @@ GEM
i18n (>= 1.8.11, < 2)
faraday (0.17.4)
multipart-post (>= 1.2, < 3)
ferrum (0.11)
addressable (~> 2.5)
cliver (~> 0.3)
concurrent-ruby (~> 1.1)
websocket-driver (>= 0.6, < 0.8)
ffi (1.15.5)
globalid (0.5.1)
activesupport (>= 5.0)
Expand Down Expand Up @@ -168,8 +174,6 @@ GEM
rack (>= 1.5)
rblineprof (~> 0.3.6)
term-ansicolor (~> 1.3)
rack-mini-profiler (3.0.0)
rack (>= 1.2.0)
rack-test (1.1.0)
rack (>= 1.0, < 3)
rails (6.1.4)
Expand Down Expand Up @@ -303,6 +307,7 @@ DEPENDENCIES
email_spec
factory_bot
faker
ferrum
jbuilder (~> 2.11)
jquery-rails
listen (~> 3.7.1)
Expand All @@ -313,7 +318,6 @@ DEPENDENCIES
pry
puma (~> 5.6)
rack-lineprof
rack-mini-profiler
rails (~> 6.1)
rblineprof
rblineprof-report
Expand Down
61 changes: 61 additions & 0 deletions app/lib/visual_regression_test/browser.rb
@@ -0,0 +1,61 @@
module VisualRegressionTest
class Browser
def initialize(output_dir:, port:)
@output_dir = output_dir
@port = port
@browser = Ferrum::Browser.new(window_size: [400, 1200])
@screenshot_number = 0
@used_idents = Set.new
end

delegate(
:go_to,
:at_css,
:at_xpath,
:quit,
:mouse,
:evaluate,
to: :@browser
)

def screenshot(ident = nil)
raise "#{ident.inspect} is already used." if @used_idents.include? ident

@used_idents << ident

# input field のカーソルが点滅することによって画像に差分が出るので、カーソルを透明にして差分が出ないようにする
@browser.add_style_tag(content: 'body { caret-color: transparent; }')

sleep 0.5

filename = File.join(@output_dir, "#{format('%05d', @screenshot_number)}_#{ident}.png")
@screenshot_number += 1

@browser.screenshot(path: filename)
end

def click_rotated(x:, y:) # rubocop:disable Naming/MethodParameterName
window_x, window_y = @browser.window_size
@browser.mouse.click(
x: (window_x + x) % window_x,
y: (window_y + y) % window_y
)
end

def wait_server_up(path)
loop do
sleep 1
go_to(path)
break if @browser.network.status
end
end

def go_to(path)
@browser.go_to(url(path))
end

def url(path)
"http://localhost:#{@port}#{path}"
end
end
end
86 changes: 86 additions & 0 deletions app/lib/visual_regression_test/scenario.rb
@@ -0,0 +1,86 @@
module VisualRegressionTest
class Scenario
def initialize(output_dir:, port: 3001)
@browser = VisualRegressionTest::Browser.new(output_dir: output_dir, port: port)
@port = port
end

def execute
with_server do
login
menu
car_usage_history
fuel
Booking.new(@browser).execute
Driving.new(@browser).execute
user_list

Admin.new(@browser).execute
end

@browser.quit
end

private

def user_list
@browser.go_to('/users/new')
@browser.screenshot('users')
end

def fuel
@browser.go_to('/')

@browser.at_xpath("//i[text() = 'local_gas_station']").click

@browser.at_css("input[name='fuel[amount]']").focus.type('2000')
@browser.screenshot('fuel_input')

@browser.at_css('input[type=submit]').click
@browser.screenshot('fuel_history')
end

def car_usage_history
@browser.go_to('/')

@browser.at_xpath("//i[text() = 'history']").click
@browser.screenshot('car_usage_histories')
end

def menu
@browser.go_to('/')

@browser.at_css('a[data-activates=side-nav]').click
@browser.screenshot('menu_open')

@browser.click_rotated(x: -1, y: 1)
@browser.screenshot('menu_close')
end

def login
@browser.go_to('/login')

@browser.at_css('input[name=email]').focus.type('example@example.com')
@browser.at_css('input[name=password]').focus.type('invalid_password')
@browser.screenshot('login_input')

@browser.at_css('input[type=submit]').click
@browser.screenshot('login_failed')

@browser.at_css('input[name=email]').focus.type('example@example.com')
@browser.at_css('input[name=password]').focus.type('password')
@browser.at_css('input[type=submit]').click
@browser.screenshot('login_success')
end

def with_server
system('RAILS_ENV=test bundle exec rake db:drop', exception: true)
system('RAILS_ENV=test bundle exec rake db:setup', exception: true)
@server_pid = spawn("RAILS_ENV=test bundle exec rails s -p #{@port} --pid=tmp/visual_regression_server.pid")
@browser.wait_server_up('/')
yield
ensure
Process.kill :INT, @server_pid if @server_pid
end
end
end
49 changes: 49 additions & 0 deletions app/lib/visual_regression_test/scenario/admin.rb
@@ -0,0 +1,49 @@
module VisualRegressionTest
class Scenario
class Admin
def initialize(browser)
@browser = browser
end

def execute
admin_cars
admin_car_edit
admin_fuels
admin_drivings
end

private

def admin_drivings
@browser.go_to('/admin/cars/1/drives')
@browser.screenshot('admin_drivings')
end

def admin_fuels
@browser.go_to('/admin/cars/1/fuels')
@browser.screenshot('admin_fuels')

@browser.at_xpath("//i[text() = 'add']").click
@browser.screenshot('admin_fuel_add')

@browser.at_css('.select-dropdown').focus
@browser.screenshot('admin_fuel_add_select_user')
@browser.click_rotated(x: 1, y: -1)

@browser.at_css('.datepicker').focus
@browser.screenshot('admin_fuel_date_picker')
@browser.click_rotated(x: 1, y: -1)
end

def admin_car_edit
@browser.go_to('/admin/cars/1/edit')
@browser.screenshot('admin_car_edit')
end

def admin_cars
@browser.go_to('/admin/cars')
@browser.screenshot('admin_cars')
end
end
end
end
38 changes: 38 additions & 0 deletions app/lib/visual_regression_test/scenario/booking.rb
@@ -0,0 +1,38 @@
module VisualRegressionTest
class Scenario
class Booking
def initialize(browser)
@browser = browser
end

def execute
@browser.go_to('/')
@browser.at_xpath("//i[text() = 'bookmark']").click

@browser.at_css("input[name='booking_form_create[start_at_date]']").focus
@browser.screenshot('booking_start_click')

@browser.click_rotated(x: 1, y: -1)
@browser.screenshot('booking_start_focus_off')

@browser.at_css("input[name='booking_form_create[end_at_date]']").focus
@browser.screenshot('booking_end_click')

@browser.click_rotated(x: 1, y: -1)
@browser.screenshot('booking_end_focus_off')

date = '2040-03-21'
# 普通に focus type すると modal に邪魔されて値が入らないので javascript で無理やり入れる
@browser.evaluate(%{document.querySelector("input[name='booking_form_create[start_at_date]']").value = "#{date}"})
@browser.evaluate(%{document.querySelector("input[name='booking_form_create[end_at_date]']").value = "#{date}"})
# backspace と delete を2回づつ打って、確実に placeholder を消す(placeholder は二桁もしくは一桁の数字なのでこれで消える)
@browser.at_css("input[name='booking_form_create[start_at_hour]']").focus.type(:backspace, :backspace, :delete, :delete, '10')
@browser.at_css("input[name='booking_form_create[end_at_hour]']").focus.type(:backspace, :backspace, :delete, :delete, '20')
@browser.screenshot('booking_input')

@browser.at_css('input[type=submit]').click
@browser.screenshot('booking_history')
end
end
end
end
33 changes: 33 additions & 0 deletions app/lib/visual_regression_test/scenario/driving.rb
@@ -0,0 +1,33 @@
module VisualRegressionTest
class Scenario
class Driving
def initialize(browser)
@browser = browser
end

def execute
@browser.go_to('/')
@browser.at_xpath("//i[text() = 'send']").click

@browser.at_css("input[name='drive_form_create[end_at_date]']").focus
@browser.screenshot('driving_start_click')

@browser.click_rotated(x: 1, y: -1)
@browser.screenshot('driving_start_focus_off')

# 普通に focus type すると modal に邪魔されて値が入らないので javascript で無理やり入れる
@browser.evaluate(%{document.querySelector("input[name='drive_form_create[end_at_date]']").value = "2040-03-01"})
@browser.screenshot('driving_input')

@browser.at_css('input[type=submit]').click
@browser.screenshot('drive_started')

@browser.at_xpath("//i[text() = 'done']").click
@browser.screenshot('drive_end')

@browser.at_css('input[type=submit]').click
@browser.screenshot('drive_end_executed')
end
end
end
end
6 changes: 5 additions & 1 deletion db/seeds.rb
Expand Up @@ -6,6 +6,10 @@
# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
# Character.create(name: 'Luke', movie: movies.first)

# for visual regression test
Random.srand(42)
Faker::Config.random = Random.new(42)

user = User.new(
name: 'Super User',
email: 'example@example.com',
Expand Down Expand Up @@ -33,7 +37,7 @@

start_meter = 0
end_meter = rand(10..100)
start_at = Time.zone.now
start_at = Time.parse('2022-03-06 22:22:22')
end_at = start_at + rand(2..10).hours
drive_params = 30.times.map do
params = {
Expand Down
25 changes: 25 additions & 0 deletions lib/tasks/visual_regression_test.rake
@@ -0,0 +1,25 @@
namespace :visual_regression_test do
task take_screenshot: :environment do
output_dir = ENV['OUTPUT_DIR'] || 'tmp'
FileUtils.mkdir_p(output_dir)
scenario = VisualRegressionTest::Scenario.new(output_dir: output_dir)
scenario.execute
end

task compare: :environment do
before_dir = ENV.fetch('BEFORE')
after_dir = ENV.fetch('AFTER')
compare_dir = ENV.fetch('OUTPUT')
FileUtils.mkdir_p(compare_dir)

Dir.glob(File.join(after_dir, '*')).each do |entry|
filename = File.basename(entry)
before_file = File.join(before_dir, filename)
after_file = File.join(after_dir, filename)
next if File.read(before_file) == File.read(after_file)

compare_file = File.join(compare_dir, filename)
system("compare #{after_file} #{before_file} #{compare_file}")
end
end
end