File: browser.rb

package info (click to toggle)
ruby-capybara 3.40.0%2Bds-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,368 kB
  • sloc: ruby: 23,988; javascript: 752; makefile: 11
file content (200 lines) | stat: -rw-r--r-- 4,709 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# frozen_string_literal: true

class Capybara::RackTest::Browser
  include ::Rack::Test::Methods

  attr_reader :driver
  attr_accessor :current_host

  def initialize(driver)
    @driver = driver
    @current_fragment = nil
  end

  def app
    driver.app
  end

  def options
    driver.options
  end

  def visit(path, **attributes)
    @new_visit_request = true
    reset_cache!
    reset_host!
    process_and_follow_redirects(:get, path, attributes)
  end

  def refresh
    reset_cache!
    request(last_request.fullpath, last_request.env)
  end

  def submit(method, path, attributes, content_type: nil)
    path = request_path if path.nil? || path.empty?
    uri = build_uri(path)
    uri.query = '' if method.to_s.casecmp('get').zero?
    env = { 'HTTP_REFERER' => referer_url }
    env['CONTENT_TYPE'] = content_type if content_type
    process_and_follow_redirects(
      method,
      uri.to_s,
      attributes,
      env
    )
  end

  def follow(method, path, **attributes)
    return if fragment_or_script?(path)

    process_and_follow_redirects(method, path, attributes, 'HTTP_REFERER' => referer_url)
  end

  def process_and_follow_redirects(method, path, attributes = {}, env = {})
    @current_fragment = build_uri(path).fragment
    process(method, path, attributes, env)
    return unless driver.follow_redirects?

    driver.redirect_limit.times do
      if last_response.redirect?
        if [307, 308].include? last_response.status
          process(last_request.request_method, last_response['Location'], last_request.params, env)
        else
          process(:get, last_response['Location'], {}, env)
        end
      end
    end

    if last_response.redirect? # rubocop:disable Style/GuardClause
      raise Capybara::InfiniteRedirectError, "redirected more than #{driver.redirect_limit} times, check for infinite redirects."
    end
  end

  def process(method, path, attributes = {}, env = {})
    method = method.downcase
    new_uri = build_uri(path)
    @current_scheme, @current_host, @current_port = new_uri.select(:scheme, :host, :port)
    @current_fragment = new_uri.fragment || @current_fragment
    reset_cache!
    @new_visit_request = false
    send(method, new_uri.to_s, attributes, env.merge(options[:headers] || {}))
  end

  def build_uri(path)
    uri = URI.parse(path)
    base_uri = base_relative_uri_for(uri)

    uri.path = base_uri.path + uri.path unless uri.absolute? || uri.path.start_with?('/')

    if base_uri.absolute?
      base_uri.merge(uri)
    else
      uri.scheme ||= @current_scheme
      uri.host ||= @current_host
      uri.port ||= @current_port unless uri.default_port == @current_port
      uri
    end
  end

  def current_url
    uri = build_uri(last_request.url)
    uri.fragment = @current_fragment if @current_fragment
    uri.to_s
  rescue Rack::Test::Error
    ''
  end

  def reset_host!
    uri = URI.parse(driver.session_options.app_host || driver.session_options.default_host)
    @current_scheme, @current_host, @current_port = uri.select(:scheme, :host, :port)
  end

  def reset_cache!
    @dom = nil
  end

  def dom
    @dom ||= Capybara::HTML(html)
  end

  def find(format, selector)
    if format == :css
      dom.css(selector, Capybara::RackTest::CSSHandlers.new)
    else
      dom.xpath(selector)
    end.map { |node| Capybara::RackTest::Node.new(self, node) }
  end

  def html
    last_response.body
  rescue Rack::Test::Error
    ''
  end

  def title
    dom.title
  end

  def last_request
    raise Rack::Test::Error if @new_visit_request

    super
  end

  def last_response
    raise Rack::Test::Error if @new_visit_request

    super
  end

protected

  def base_href
    find(:css, 'head > base').first&.[](:href).to_s
  end

  def base_relative_uri_for(uri)
    base_uri = URI.parse(base_href)
    current_uri = URI.parse(safe_last_request&.url.to_s).tap do |c|
      c.path.sub!(%r{/[^/]*$}, '/') unless uri.path.empty?
      c.path = '/' if c.path.empty?
    end

    if [current_uri, base_uri].any?(&:absolute?)
      current_uri.merge(base_uri)
    else
      base_uri.path = current_uri.path if base_uri.path.empty?
      base_uri
    end
  end

  def build_rack_mock_session
    reset_host! unless current_host
    Rack::MockSession.new(app, current_host)
  end

  def request_path
    last_request.path
  rescue Rack::Test::Error
    '/'
  end

  def safe_last_request
    last_request
  rescue Rack::Test::Error
    nil
  end

private

  def fragment_or_script?(path)
    path.gsub(/^#{Regexp.escape(request_path)}/, '').start_with?('#') || path.downcase.start_with?('javascript:')
  end

  def referer_url
    build_uri(last_request.url).to_s
  rescue Rack::Test::Error
    ''
  end
end