Module: SnapDiff::BrowserHelpers

Defined in:
lib/snap_diff/browser_helpers.rb

Constant Summary collapse

IMAGE_WAIT_SCRIPT =
<<~JS
  function pending_image() {
    const images = document.images
    for (var i = 0; i < images.length; i++) {
      if (!images[i].complete && images[i].loading !== "lazy") {
          return images[i].src
      }
    }
    return false
  }(window)
JS
HIDE_CARET_SCRIPT =
<<~JS
  if (!document.getElementById('csdHideCaretStyle')) {
    let style = document.createElement('style');
    style.setAttribute('id', 'csdHideCaretStyle');
    document.head.appendChild(style);
    let styleSheet = style.sheet;
    styleSheet.insertRule("* { caret-color: transparent !important; }", 0);
  }
JS
DISABLE_ANIMATIONS_SCRIPT =
<<~JS
  if (!document.getElementById('csdDisableAnimationsStyle')) {
    let style = document.createElement('style');
    style.setAttribute('id', 'csdDisableAnimationsStyle');
    style.textContent = '*, *::before, *::after { animation-duration: 0s !important; animation-delay: 0s !important; transition-duration: 0s !important; transition-delay: 0s !important; }';
    document.head.appendChild(style);
  }
JS
FIND_ACTIVE_ELEMENT_SCRIPT =
<<~JS
  function activeElement(){
    const ae = document.activeElement;
    if (ae.nodeName === "INPUT" || ae.nodeName === "TEXTAREA") {
      ae.blur();
      return ae;
    }
    return null;
  }(window);
JS
GET_BOUNDING_CLIENT_RECT_SCRIPT =
<<~JS
  [
    this.getBoundingClientRect().left,
    this.getBoundingClientRect().top,
    this.getBoundingClientRect().right,
    this.getBoundingClientRect().bottom
  ]
JS

Class Method Summary collapse

Class Method Details

.all_visible_regions_for(selector) ⇒ Object

minimum: 0 is load-bearing (issue #272). Capybara's all defaults to minimum: 1 and blocks in synchronize until the count is satisfied, so every skip_area/crop selector matching nothing burned a full Capybara.default_max_wait_time -- 5s by default, per selector, per screenshot. One project measured %w[picture img] on an image-less page at 10s per screenshot, 44% of their suite.

Waiting is the wrong semantic here regardless of the cost: these selectors describe a MASK over whatever is currently on the page. A selector that matches nothing has nothing to mask, and that answer is available immediately.



120
121
122
# File 'lib/snap_diff/browser_helpers.rb', line 120

def self.all_visible_regions_for(selector)
  BrowserHelpers.session.all(selector, visible: true, minimum: 0).map { |el| region_for(el) }
end

.blur_from_focused_elementObject



96
97
98
# File 'lib/snap_diff/browser_helpers.rb', line 96

def self.blur_from_focused_element
  session.evaluate_script(FIND_ACTIVE_ELEMENT_SCRIPT)
end

.bounds_for_css(*css_selectors) ⇒ Object

The one seam that knows, per selector, whether it found anything -- all_visible_regions_for is called once per selector and the caller gets back one flattened list. The run-level tally is fed here (#277b) rather than in AreaCalculator for exactly that reason: by the time the regions are concatenated the attribution is gone.



38
39
40
41
42
43
44
# File 'lib/snap_diff/browser_helpers.rb', line 38

def self.bounds_for_css(*css_selectors)
  css_selectors.reduce([]) do |regions, selector|
    found = all_visible_regions_for(selector)
    SnapDiff::Reporting.record_selector_use(selector, matched: !found.empty?)
    regions.concat(found)
  end
end

.current_capybara_driver_classObject



136
137
138
# File 'lib/snap_diff/browser_helpers.rb', line 136

def self.current_capybara_driver_class
  session.driver.class
end

.disable_animationsObject



81
82
83
# File 'lib/snap_diff/browser_helpers.rb', line 81

def self.disable_animations
  session.execute_script(DISABLE_ANIMATIONS_SCRIPT)
end

.hide_caretObject



68
69
70
# File 'lib/snap_diff/browser_helpers.rb', line 68

def self.hide_caret
  session.execute_script(HIDE_CARET_SCRIPT)
end

.pending_image_to_loadObject



132
133
134
# File 'lib/snap_diff/browser_helpers.rb', line 132

def self.pending_image_to_load
  BrowserHelpers.session.evaluate_script(IMAGE_WAIT_SCRIPT)
end

.region_for(element) ⇒ Object



124
125
126
# File 'lib/snap_diff/browser_helpers.rb', line 124

def self.region_for(element)
  element.evaluate_script(GET_BOUNDING_CLIENT_RECT_SCRIPT).map { |point| point.negative? ? 0 : point.ceil.to_i }
end

.resize_to(window_size) ⇒ Object



16
17
18
19
20
21
22
# File 'lib/snap_diff/browser_helpers.rb', line 16

def self.resize_to(window_size)
  if session.driver.respond_to?(:resize)
    session.driver.resize(*window_size)
  elsif BrowserHelpers.selenium?
    session.driver.browser.manage.window.resize_to(*window_size)
  end
end

.resize_window_if_neededObject



8
9
10
11
12
13
14
# File 'lib/snap_diff/browser_helpers.rb', line 8

def self.resize_window_if_needed
  # The respond_to? guard this replaced existed because the legacy
  # mattr_accessor might not be installed yet; Config always has the
  # attribute, so only the value matters now.
  window_size = SnapDiff.config.window_size
  resize_to(window_size) if window_size
end

.selenium?Boolean

Returns:

  • (Boolean)


24
25
26
# File 'lib/snap_diff/browser_helpers.rb', line 24

def self.selenium?
  current_capybara_driver_class <= Capybara::Selenium::Driver
end

.sessionObject



128
129
130
# File 'lib/snap_diff/browser_helpers.rb', line 128

def self.session
  Capybara.current_session
end

.window_size_is_wrong?(expected_window_size = nil) ⇒ Boolean

Returns:

  • (Boolean)


28
29
30
31
# File 'lib/snap_diff/browser_helpers.rb', line 28

def self.window_size_is_wrong?(expected_window_size = nil)
  selenium? && expected_window_size &&
    session.driver.browser.manage.window.size != ::Selenium::WebDriver::Dimension.new(*expected_window_size)
end