mirror of
https://github.com/teamcapybara/capybara.git
synced 2022-11-09 12:08:07 -05:00
Disable animation on pages
This commit is contained in:
parent
15313bd269
commit
e8b49b6eed
12 changed files with 191 additions and 54 deletions
|
@ -40,6 +40,9 @@ Metrics/ModuleLength:
|
|||
Metrics/PerceivedComplexity:
|
||||
Enabled: false
|
||||
|
||||
Metrics/ParameterLists:
|
||||
Enabled: false
|
||||
|
||||
Lint/UnusedMethodArgument:
|
||||
Exclude:
|
||||
- 'lib/capybara/driver/base.rb'
|
||||
|
|
|
@ -14,6 +14,8 @@ Release date: unreleased
|
|||
* `:element` selector type which will match on any attribute (other than the reserved names) passed as a filter option
|
||||
* `:class` filter option now supports preceding class names with `!` to indicate not having that class
|
||||
* `:class` and `:id` filter options now accept `XPath::Expression` objects to allow for more flexibility in matching
|
||||
* `Capybara.disable_animation` setting which triggers loading of a middleware that attempts to disable animations in pages.
|
||||
This is very much a beta feature and may change/disappear in the future. [Thomas Walpole]
|
||||
|
||||
# Version 3.1.1
|
||||
Release date: 2018-05-25
|
||||
|
|
|
@ -24,7 +24,6 @@ Capybara::Selector::FilterSet.add(:_field) do
|
|||
end
|
||||
|
||||
# rubocop:disable Metrics/BlockLength
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
|
||||
Capybara.add_selector(:xpath) do
|
||||
xpath { |xpath| xpath }
|
||||
|
@ -457,4 +456,3 @@ Capybara.add_selector(:element) do
|
|||
end
|
||||
end
|
||||
# rubocop:enable Metrics/BlockLength
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
|
|
@ -59,7 +59,7 @@ module Capybara
|
|||
options
|
||||
end
|
||||
|
||||
def add_filter(name, filter_class, *types, matcher: nil, **options, &block) # rubocop:disable Metrics/ParameterLists
|
||||
def add_filter(name, filter_class, *types, matcher: nil, **options, &block)
|
||||
types.each { |k| options[k] = true }
|
||||
raise "ArgumentError", ":default option is not supported for filters with a :matcher option" if matcher && options[:default]
|
||||
if filter_class <= Filters::ExpressionFilter
|
||||
|
|
|
@ -3,56 +3,11 @@
|
|||
require 'uri'
|
||||
require 'net/http'
|
||||
require 'rack'
|
||||
require 'capybara/server/middleware'
|
||||
require 'capybara/server/animation_disabler'
|
||||
|
||||
module Capybara
|
||||
class Server
|
||||
class Middleware
|
||||
class Counter
|
||||
attr_reader :value
|
||||
|
||||
def initialize
|
||||
@value = 0
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
def increment
|
||||
@mutex.synchronize { @value += 1 }
|
||||
end
|
||||
|
||||
def decrement
|
||||
@mutex.synchronize { @value -= 1 }
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :error
|
||||
|
||||
def initialize(app, server_errors)
|
||||
@app = app
|
||||
@counter = Counter.new
|
||||
@server_errors = server_errors
|
||||
end
|
||||
|
||||
def pending_requests?
|
||||
@counter.value.positive?
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if env["PATH_INFO"] == "/__identify__"
|
||||
[200, {}, [@app.object_id.to_s]]
|
||||
else
|
||||
@counter.increment
|
||||
begin
|
||||
@app.call(env)
|
||||
rescue *@server_errors => e
|
||||
@error ||= e
|
||||
raise e
|
||||
ensure
|
||||
@counter.decrement
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class << self
|
||||
def ports
|
||||
@ports ||= {}
|
||||
|
@ -61,9 +16,10 @@ module Capybara
|
|||
|
||||
attr_reader :app, :port, :host
|
||||
|
||||
def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors)
|
||||
def initialize(app, *deprecated_options, port: Capybara.server_port, host: Capybara.server_host, reportable_errors: Capybara.server_errors, extra_middleware: [])
|
||||
warn "Positional arguments, other than the application, to Server#new are deprecated, please use keyword arguments" unless deprecated_options.empty?
|
||||
@app = app
|
||||
@extra_middleware = extra_middleware
|
||||
@server_thread = nil # suppress warnings
|
||||
@host = deprecated_options[1] || host
|
||||
@reportable_errors = deprecated_options[2] || reportable_errors
|
||||
|
@ -147,7 +103,7 @@ module Capybara
|
|||
end
|
||||
|
||||
def middleware
|
||||
@middleware ||= Middleware.new(app, @reportable_errors)
|
||||
@middleware ||= Middleware.new(app, @reportable_errors, @extra_middleware)
|
||||
end
|
||||
|
||||
def port_key
|
||||
|
|
43
lib/capybara/server/animation_disabler.rb
Normal file
43
lib/capybara/server/animation_disabler.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Capybara
|
||||
class Server
|
||||
class AnimationDisabler
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def call(env)
|
||||
@status, @headers, @body = @app.call(env)
|
||||
return [@status, @headers, @body] unless html_content?
|
||||
response = Rack::Response.new([], @status, @headers)
|
||||
|
||||
@body.each { |html| response.write insert_disable(html) }
|
||||
@body.close if @body.respond_to?(:close)
|
||||
|
||||
response.finish
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def html_content?
|
||||
!!(@headers["Content-Type"] =~ /html/)
|
||||
end
|
||||
|
||||
def insert_disable(html)
|
||||
html.sub(%r{(</head>)}, DISABLE_MARKUP + '\\1')
|
||||
end
|
||||
|
||||
DISABLE_MARKUP = <<~HTML
|
||||
<script defer>(typeof jQuery !== 'undefined') && (jQuery.fx.off = true);</script>
|
||||
<style>
|
||||
* {
|
||||
transition: none !important;
|
||||
animation-duration: 0s !important;
|
||||
animation-delay: 0s !important;
|
||||
}
|
||||
</style>
|
||||
HTML
|
||||
end
|
||||
end
|
||||
end
|
55
lib/capybara/server/middleware.rb
Normal file
55
lib/capybara/server/middleware.rb
Normal file
|
@ -0,0 +1,55 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Capybara
|
||||
class Server
|
||||
class Middleware
|
||||
class Counter
|
||||
attr_reader :value
|
||||
|
||||
def initialize
|
||||
@value = 0
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
def increment
|
||||
@mutex.synchronize { @value += 1 }
|
||||
end
|
||||
|
||||
def decrement
|
||||
@mutex.synchronize { @value -= 1 }
|
||||
end
|
||||
end
|
||||
|
||||
attr_accessor :error
|
||||
|
||||
def initialize(app, server_errors, extra_middleware = [])
|
||||
@app = app
|
||||
@extended_app = extra_middleware.inject(@app) do |ex_app, klass|
|
||||
klass.new(ex_app)
|
||||
end
|
||||
@counter = Counter.new
|
||||
@server_errors = server_errors
|
||||
end
|
||||
|
||||
def pending_requests?
|
||||
@counter.value.positive?
|
||||
end
|
||||
|
||||
def call(env)
|
||||
if env["PATH_INFO"] == "/__identify__"
|
||||
[200, {}, [@app.object_id.to_s]]
|
||||
else
|
||||
@counter.increment
|
||||
begin
|
||||
@extended_app.call(env)
|
||||
rescue *@server_errors => e
|
||||
@error ||= e
|
||||
raise e
|
||||
ensure
|
||||
@counter.decrement
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -84,7 +84,9 @@ module Capybara
|
|||
yield config
|
||||
end
|
||||
@server = if config.run_server && @app && driver.needs_server?
|
||||
Capybara::Server.new(@app, port: config.server_port, host: config.server_host, reportable_errors: config.server_errors).boot
|
||||
server_options = { port: config.server_port, host: config.server_host, reportable_errors: config.server_errors }
|
||||
server_options[:extra_middleware] = [Capybara::Server::AnimationDisabler] if config.disable_animation
|
||||
Capybara::Server.new(@app, server_options).boot
|
||||
end
|
||||
@touched = false
|
||||
end
|
||||
|
|
|
@ -7,7 +7,7 @@ module Capybara
|
|||
OPTIONS = %i[always_include_port run_server default_selector default_max_wait_time ignore_hidden_elements
|
||||
automatic_reload match exact exact_text raise_server_errors visible_text_only
|
||||
automatic_label_click enable_aria_label save_path asset_host default_host app_host
|
||||
server_host server_port server_errors default_set_options].freeze
|
||||
server_host server_port server_errors default_set_options disable_animation].freeze
|
||||
|
||||
attr_accessor(*OPTIONS)
|
||||
|
||||
|
@ -52,6 +52,8 @@ module Capybara
|
|||
# See {Capybara.configure}
|
||||
# @!method default_set_options
|
||||
# See {Capybara.configure}
|
||||
# @!method disable_animation
|
||||
# See {Capybara.configure}
|
||||
|
||||
remove_method :server_host
|
||||
|
||||
|
@ -80,6 +82,12 @@ module Capybara
|
|||
@default_host = url
|
||||
end
|
||||
|
||||
remove_method :disable_animation=
|
||||
def disable_animation=(bool)
|
||||
warn "Capybara.disable_animation is a beta feature - it may change/disappear in a future point version" if bool
|
||||
@disable_animation = bool
|
||||
end
|
||||
|
||||
def initialize_copy(other)
|
||||
super
|
||||
@server_errors = @server_errors.dup
|
||||
|
|
|
@ -30,6 +30,7 @@ module Capybara
|
|||
Capybara.match = :smart
|
||||
Capybara.enable_aria_label = false
|
||||
Capybara.default_set_options = {}
|
||||
Capybara.disable_animation = false
|
||||
reset_threadsafe
|
||||
end
|
||||
|
||||
|
|
46
lib/capybara/spec/views/with_animation.erb
Normal file
46
lib/capybara/spec/views/with_animation.erb
Normal file
|
@ -0,0 +1,46 @@
|
|||
|
||||
<html xmlns="http://www.w3.org/1999/xhtml" lang="en">
|
||||
<head>
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/>
|
||||
<title>with_animation</title>
|
||||
<script src="/jquery.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="/jquery-ui.js" type="text/javascript" charset="utf-8"></script>
|
||||
<script src="/test.js" type="text/javascript" charset="utf-8"></script>
|
||||
<style>
|
||||
.transition.away {
|
||||
width: 0%;
|
||||
}
|
||||
|
||||
a {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
a:not(.away) {
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
a.transition {
|
||||
transition: all 3s ease-in-out;
|
||||
}
|
||||
|
||||
@keyframes animation {
|
||||
0% {height: 20px; width: 100%;}
|
||||
100% {height: 0px; width: 0%;}
|
||||
}
|
||||
|
||||
a.animation.away {
|
||||
animation-name: animation;
|
||||
animation-duration: 3s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="with_animation">
|
||||
<a href='#' class='transition' onclick='this.classList.add("away")'>transition me away</a>
|
||||
<a href='#' class='animation' onclick='this.classList.add("away")'>animate me away</a>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -256,6 +256,29 @@ RSpec.shared_examples "Capybara::Session" do |session, mode|
|
|||
end.to raise_error(ArgumentError, 'Not allowed to close the primary window')
|
||||
end
|
||||
end
|
||||
|
||||
context "AnimationDisabler" do
|
||||
before(:context) do # rubocop:disable RSpec/BeforeAfterAll
|
||||
Capybara.disable_animation = true
|
||||
@animation_session = Capybara::Session.new(session.mode, TestApp.new)
|
||||
end
|
||||
|
||||
after(:context) do # rubocop:disable RSpec/BeforeAfterAll
|
||||
Capybara.disable_animation = false
|
||||
end
|
||||
|
||||
it "should disable CSS transitions" do
|
||||
@animation_session.visit('with_animation')
|
||||
@animation_session.click_link('transition me away')
|
||||
expect(@animation_session).to have_no_link('transition me away', wait: 0.5)
|
||||
end
|
||||
|
||||
it "should disable CSS animations", :focus_ do
|
||||
@animation_session.visit('with_animation')
|
||||
@animation_session.click_link('animate me away')
|
||||
expect(@animation_session).to have_no_link('animate me away', wait: 0.5)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def headless_or_remote?
|
||||
|
|
Loading…
Add table
Reference in a new issue