capybara-lightpanda 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +50 -0
- data/LICENSE.txt +27 -0
- data/NOTICE.md +101 -0
- data/README.md +215 -0
- data/lib/capybara/lightpanda/binary.rb +190 -0
- data/lib/capybara/lightpanda/browser.rb +963 -0
- data/lib/capybara/lightpanda/client/subscriber.rb +44 -0
- data/lib/capybara/lightpanda/client/web_socket.rb +160 -0
- data/lib/capybara/lightpanda/client.rb +124 -0
- data/lib/capybara/lightpanda/cookies.rb +181 -0
- data/lib/capybara/lightpanda/driver.rb +252 -0
- data/lib/capybara/lightpanda/errors.rb +76 -0
- data/lib/capybara/lightpanda/frame.rb +33 -0
- data/lib/capybara/lightpanda/javascripts/index.js +1108 -0
- data/lib/capybara/lightpanda/keyboard.rb +142 -0
- data/lib/capybara/lightpanda/logger.rb +37 -0
- data/lib/capybara/lightpanda/network.rb +92 -0
- data/lib/capybara/lightpanda/node.rb +726 -0
- data/lib/capybara/lightpanda/options.rb +63 -0
- data/lib/capybara/lightpanda/process.rb +252 -0
- data/lib/capybara/lightpanda/utils/event.rb +37 -0
- data/lib/capybara/lightpanda/version.rb +7 -0
- data/lib/capybara/lightpanda/xpath_polyfill.rb +10 -0
- data/lib/capybara-lightpanda.rb +42 -0
- metadata +119 -0
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "logger"
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
module Lightpanda
|
|
7
|
+
class Options
|
|
8
|
+
DEFAULT_TIMEOUT = ENV.fetch("LIGHTPANDA_DEFAULT_TIMEOUT", 15).to_i
|
|
9
|
+
DEFAULT_PROCESS_TIMEOUT = ENV.fetch("LIGHTPANDA_PROCESS_TIMEOUT", 10).to_i
|
|
10
|
+
DEFAULT_HOST = "127.0.0.1"
|
|
11
|
+
DEFAULT_PORT = 9222
|
|
12
|
+
DEFAULT_WINDOW_SIZE = [1024, 768].freeze
|
|
13
|
+
|
|
14
|
+
attr_accessor :host, :port, :timeout, :process_timeout, :window_size, :browser_path, :headless, :logger
|
|
15
|
+
attr_writer :ws_url
|
|
16
|
+
|
|
17
|
+
def initialize(options = {})
|
|
18
|
+
@host = options.fetch(:host, DEFAULT_HOST)
|
|
19
|
+
@port = options.fetch(:port, DEFAULT_PORT)
|
|
20
|
+
@timeout = options.fetch(:timeout, DEFAULT_TIMEOUT)
|
|
21
|
+
@process_timeout = options.fetch(:process_timeout, DEFAULT_PROCESS_TIMEOUT)
|
|
22
|
+
@window_size = options.fetch(:window_size, DEFAULT_WINDOW_SIZE)
|
|
23
|
+
@browser_path = options[:browser_path]
|
|
24
|
+
@headless = options.fetch(:headless, true)
|
|
25
|
+
@ws_url = options[:ws_url]
|
|
26
|
+
@logger = parse_logger(options[:logger])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def ws_url
|
|
30
|
+
@ws_url || "ws://#{host}:#{port}/"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def ws_url?
|
|
34
|
+
!@ws_url.nil?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def to_h
|
|
38
|
+
h = {
|
|
39
|
+
host: host,
|
|
40
|
+
port: port,
|
|
41
|
+
timeout: timeout,
|
|
42
|
+
process_timeout: process_timeout,
|
|
43
|
+
window_size: window_size,
|
|
44
|
+
browser_path: browser_path,
|
|
45
|
+
headless: headless,
|
|
46
|
+
logger: logger,
|
|
47
|
+
}
|
|
48
|
+
h[:ws_url] = @ws_url if @ws_url
|
|
49
|
+
h
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def parse_logger(logger)
|
|
55
|
+
return logger if logger.is_a?(Capybara::Lightpanda::Logger)
|
|
56
|
+
return Capybara::Lightpanda::Logger.new(logger) if logger
|
|
57
|
+
return Capybara::Lightpanda::Logger.new($stdout.tap { |s| s.sync = true }) if ENV["LIGHTPANDA_DEBUG"]
|
|
58
|
+
|
|
59
|
+
Capybara::Lightpanda::Logger.new
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Capybara
|
|
4
|
+
module Lightpanda
|
|
5
|
+
class Process
|
|
6
|
+
READY_PATTERN = /server running.*address=(\d+\.\d+\.\d+\.\d+:\d+)/
|
|
7
|
+
ADDRESS_IN_USE_PATTERN = /err=AddressInUse/
|
|
8
|
+
|
|
9
|
+
# First nightly with Page.addScriptToEvaluateOnNewDocument (PR #1993, merged 2026-03-30).
|
|
10
|
+
# The gem relies on this for XPath polyfill auto-injection.
|
|
11
|
+
MINIMUM_NIGHTLY_BUILD = 5267
|
|
12
|
+
|
|
13
|
+
attr_reader :pid, :ws_url, :version, :nightly_build
|
|
14
|
+
|
|
15
|
+
def initialize(options)
|
|
16
|
+
@options = options
|
|
17
|
+
@pid = nil
|
|
18
|
+
@ws_url = nil
|
|
19
|
+
@version = nil
|
|
20
|
+
@nightly_build = nil
|
|
21
|
+
@stdout_r = nil
|
|
22
|
+
@stdout_w = nil
|
|
23
|
+
@stderr_r = nil
|
|
24
|
+
@stderr_w = nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def start
|
|
28
|
+
binary_path = @options.browser_path || Binary.find_or_download
|
|
29
|
+
|
|
30
|
+
raise BinaryNotFoundError, "Lightpanda binary not found" unless binary_path
|
|
31
|
+
|
|
32
|
+
check_minimum_version(binary_path)
|
|
33
|
+
attempt_start(binary_path)
|
|
34
|
+
rescue ProcessTimeoutError => e
|
|
35
|
+
raise unless e.message.include?("already in use")
|
|
36
|
+
|
|
37
|
+
kill_process_on_port(@options.port)
|
|
38
|
+
attempt_start(binary_path)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def stop
|
|
42
|
+
return unless @pid
|
|
43
|
+
|
|
44
|
+
begin
|
|
45
|
+
::Process.kill("TERM", -@pid) # Kill process group
|
|
46
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
47
|
+
# Process group already dead, try direct
|
|
48
|
+
begin
|
|
49
|
+
::Process.kill("TERM", @pid)
|
|
50
|
+
rescue Errno::ESRCH
|
|
51
|
+
# Process already dead
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
begin
|
|
56
|
+
::Process.wait(@pid)
|
|
57
|
+
rescue Errno::ECHILD
|
|
58
|
+
# Already reaped
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
cleanup_pipes
|
|
62
|
+
@pid = nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def alive?
|
|
66
|
+
return false unless @pid
|
|
67
|
+
|
|
68
|
+
::Process.kill(0, @pid)
|
|
69
|
+
true
|
|
70
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def check_minimum_version(binary_path)
|
|
77
|
+
stdout, = Open3.capture3(binary_path, "version")
|
|
78
|
+
@version = stdout.strip
|
|
79
|
+
@nightly_build = @version[/nightly\.(\d+)/, 1]&.to_i
|
|
80
|
+
|
|
81
|
+
return if @nightly_build && @nightly_build >= MINIMUM_NIGHTLY_BUILD
|
|
82
|
+
|
|
83
|
+
raise BinaryError,
|
|
84
|
+
"Lightpanda #{@version} is too old. " \
|
|
85
|
+
"This gem requires nightly build >= #{MINIMUM_NIGHTLY_BUILD} " \
|
|
86
|
+
"(Page.addScriptToEvaluateOnNewDocument support). " \
|
|
87
|
+
"Update: curl -sL https://github.com/lightpanda-io/browser/releases/download/nightly/" \
|
|
88
|
+
"#{Binary.platform_binary} -o #{binary_path} && chmod +x #{binary_path}"
|
|
89
|
+
rescue Errno::ENOENT
|
|
90
|
+
# Binary not runnable — let attempt_start handle it
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def attempt_start(binary_path)
|
|
94
|
+
@stdout_r, @stdout_w = IO.pipe
|
|
95
|
+
@stderr_r, @stderr_w = IO.pipe
|
|
96
|
+
|
|
97
|
+
@pid = spawn_process(binary_path)
|
|
98
|
+
register_finalizer(@pid)
|
|
99
|
+
|
|
100
|
+
@stdout_w.close
|
|
101
|
+
@stderr_w.close
|
|
102
|
+
|
|
103
|
+
wait_for_ready
|
|
104
|
+
|
|
105
|
+
# Drain stderr/stdout to prevent pipe buffer from filling up
|
|
106
|
+
# and blocking the Lightpanda process
|
|
107
|
+
start_drain_thread
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def start_drain_thread
|
|
111
|
+
@drain_thread = Thread.new do
|
|
112
|
+
ios = [@stdout_r, @stderr_r].compact
|
|
113
|
+
loop do
|
|
114
|
+
ready = IO.select(ios, nil, nil, 0.5)
|
|
115
|
+
next unless ready
|
|
116
|
+
|
|
117
|
+
ready[0].each do |io|
|
|
118
|
+
io.read_nonblock(4096)
|
|
119
|
+
rescue IO::WaitReadable
|
|
120
|
+
# No data
|
|
121
|
+
rescue EOFError
|
|
122
|
+
ios.delete(io)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
break if ios.empty?
|
|
126
|
+
rescue IOError
|
|
127
|
+
break
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def spawn_process(binary_path)
|
|
133
|
+
args = build_args
|
|
134
|
+
|
|
135
|
+
::Process.spawn(
|
|
136
|
+
{ "LIGHTPANDA_DISABLE_TELEMETRY" => "true" },
|
|
137
|
+
binary_path, *args,
|
|
138
|
+
out: @stdout_w,
|
|
139
|
+
err: @stderr_w,
|
|
140
|
+
pgroup: true
|
|
141
|
+
)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def build_args
|
|
145
|
+
[
|
|
146
|
+
"serve",
|
|
147
|
+
"--host",
|
|
148
|
+
@options.host.to_s,
|
|
149
|
+
"--port",
|
|
150
|
+
@options.port.to_s,
|
|
151
|
+
"--log_level",
|
|
152
|
+
"info",
|
|
153
|
+
]
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def wait_for_ready
|
|
157
|
+
started_at = Time.now
|
|
158
|
+
output = +""
|
|
159
|
+
|
|
160
|
+
catch(:ready) do
|
|
161
|
+
while Time.now - started_at < @options.process_timeout
|
|
162
|
+
ready = IO.select([@stdout_r, @stderr_r], nil, nil, 0.1)
|
|
163
|
+
|
|
164
|
+
next unless ready
|
|
165
|
+
|
|
166
|
+
ready[0].each do |io|
|
|
167
|
+
chunk = io.read_nonblock(1024)
|
|
168
|
+
output << chunk
|
|
169
|
+
|
|
170
|
+
if (match = output.match(READY_PATTERN))
|
|
171
|
+
@ws_url = "ws://#{match[1]}/"
|
|
172
|
+
throw(:ready)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
if output.match?(ADDRESS_IN_USE_PATTERN)
|
|
176
|
+
cleanup_failed_process
|
|
177
|
+
raise ProcessTimeoutError,
|
|
178
|
+
"Lightpanda failed to start: port #{@options.port} is already in use"
|
|
179
|
+
end
|
|
180
|
+
rescue IO::WaitReadable
|
|
181
|
+
# No data available yet
|
|
182
|
+
rescue EOFError
|
|
183
|
+
# Pipe closed
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
stop
|
|
188
|
+
|
|
189
|
+
raise ProcessTimeoutError,
|
|
190
|
+
"Lightpanda failed to start within #{@options.process_timeout} seconds.\nOutput: #{output}"
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def cleanup_failed_process
|
|
195
|
+
return unless @pid
|
|
196
|
+
|
|
197
|
+
begin
|
|
198
|
+
::Process.wait(@pid, ::Process::WNOHANG)
|
|
199
|
+
rescue Errno::ECHILD
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
cleanup_pipes
|
|
204
|
+
@pid = nil
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def kill_process_on_port(port)
|
|
208
|
+
port = port.to_i
|
|
209
|
+
return if port <= 0
|
|
210
|
+
|
|
211
|
+
pids = `lsof -ti tcp:#{port} 2>/dev/null`.strip
|
|
212
|
+
return if pids.empty?
|
|
213
|
+
|
|
214
|
+
pids.split("\n").each do |pid_str|
|
|
215
|
+
pid = pid_str.strip.to_i
|
|
216
|
+
next if pid <= 0
|
|
217
|
+
|
|
218
|
+
::Process.kill("TERM", pid)
|
|
219
|
+
rescue Errno::ESRCH, Errno::EPERM
|
|
220
|
+
nil
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
sleep 0.5
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Class method so the finalizer proc doesn't capture `self` (which
|
|
227
|
+
# would prevent GC from ever running the finalizer).
|
|
228
|
+
class << self
|
|
229
|
+
private
|
|
230
|
+
|
|
231
|
+
def weak_kill(pid)
|
|
232
|
+
proc do
|
|
233
|
+
::Process.kill("TERM", -pid)
|
|
234
|
+
::Process.wait(pid)
|
|
235
|
+
rescue Errno::ESRCH, Errno::ECHILD, Errno::EPERM
|
|
236
|
+
nil
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def register_finalizer(pid)
|
|
242
|
+
ObjectSpace.define_finalizer(self, self.class.send(:weak_kill, pid))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def cleanup_pipes
|
|
246
|
+
[@stdout_r, @stdout_w, @stderr_r, @stderr_w].each do |pipe|
|
|
247
|
+
pipe&.close unless pipe&.closed?
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "concurrent-ruby"
|
|
4
|
+
|
|
5
|
+
module Capybara
|
|
6
|
+
module Lightpanda
|
|
7
|
+
module Utils
|
|
8
|
+
# Concurrent::Event with an iteration counter so callers can detect
|
|
9
|
+
# that the event was reset (e.g. a new navigation started) while they
|
|
10
|
+
# were waiting on it. Mirrors ferrum's Utils::Event.
|
|
11
|
+
#
|
|
12
|
+
# The base Concurrent::Event allows wait/set/reset cycles, but a wait
|
|
13
|
+
# that returns true after a reset → set is indistinguishable from one
|
|
14
|
+
# that returned true on the original set. The iteration counter,
|
|
15
|
+
# bumped on every reset, lets callers compare before and after to
|
|
16
|
+
# tell whether the event was raced by a reset.
|
|
17
|
+
class Event < Concurrent::Event
|
|
18
|
+
def initialize
|
|
19
|
+
super
|
|
20
|
+
@iteration = 0
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def iteration
|
|
24
|
+
synchronize { @iteration }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset
|
|
28
|
+
synchronize do
|
|
29
|
+
@iteration += 1
|
|
30
|
+
@set = false if @set
|
|
31
|
+
@iteration
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "capybara"
|
|
4
|
+
|
|
5
|
+
require_relative "capybara/lightpanda/version"
|
|
6
|
+
require_relative "capybara/lightpanda/errors"
|
|
7
|
+
require_relative "capybara/lightpanda/logger"
|
|
8
|
+
require_relative "capybara/lightpanda/options"
|
|
9
|
+
require_relative "capybara/lightpanda/binary"
|
|
10
|
+
require_relative "capybara/lightpanda/process"
|
|
11
|
+
require_relative "capybara/lightpanda/utils/event"
|
|
12
|
+
require_relative "capybara/lightpanda/client"
|
|
13
|
+
require_relative "capybara/lightpanda/network"
|
|
14
|
+
require_relative "capybara/lightpanda/cookies"
|
|
15
|
+
require_relative "capybara/lightpanda/keyboard"
|
|
16
|
+
require_relative "capybara/lightpanda/frame"
|
|
17
|
+
require_relative "capybara/lightpanda/browser"
|
|
18
|
+
require_relative "capybara/lightpanda/xpath_polyfill"
|
|
19
|
+
require_relative "capybara/lightpanda/node"
|
|
20
|
+
require_relative "capybara/lightpanda/driver"
|
|
21
|
+
|
|
22
|
+
module Capybara
|
|
23
|
+
module Lightpanda
|
|
24
|
+
class << self
|
|
25
|
+
def configure
|
|
26
|
+
yield(options) if block_given?
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def options
|
|
30
|
+
@options ||= Options.new
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def reset_options!
|
|
34
|
+
@options = nil
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
Capybara.register_driver(:lightpanda) do |app|
|
|
41
|
+
Capybara::Lightpanda::Driver.new(app, Capybara::Lightpanda.options.to_h)
|
|
42
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: capybara-lightpanda
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Navid Emad
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: capybara
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '3.0'
|
|
19
|
+
- - "<"
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: '5'
|
|
22
|
+
type: :runtime
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
25
|
+
requirements:
|
|
26
|
+
- - ">="
|
|
27
|
+
- !ruby/object:Gem::Version
|
|
28
|
+
version: '3.0'
|
|
29
|
+
- - "<"
|
|
30
|
+
- !ruby/object:Gem::Version
|
|
31
|
+
version: '5'
|
|
32
|
+
- !ruby/object:Gem::Dependency
|
|
33
|
+
name: concurrent-ruby
|
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
|
35
|
+
requirements:
|
|
36
|
+
- - "~>"
|
|
37
|
+
- !ruby/object:Gem::Version
|
|
38
|
+
version: '1.3'
|
|
39
|
+
type: :runtime
|
|
40
|
+
prerelease: false
|
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
42
|
+
requirements:
|
|
43
|
+
- - "~>"
|
|
44
|
+
- !ruby/object:Gem::Version
|
|
45
|
+
version: '1.3'
|
|
46
|
+
- !ruby/object:Gem::Dependency
|
|
47
|
+
name: websocket-driver
|
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - "~>"
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: '0.8'
|
|
53
|
+
type: :runtime
|
|
54
|
+
prerelease: false
|
|
55
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
56
|
+
requirements:
|
|
57
|
+
- - "~>"
|
|
58
|
+
- !ruby/object:Gem::Version
|
|
59
|
+
version: '0.8'
|
|
60
|
+
description: A Capybara driver for Lightpanda, the fast headless browser built in
|
|
61
|
+
Zig. Provides a production-ready driver with XPath polyfill, reliable navigation,
|
|
62
|
+
and cookie management — ready for real-world Rails test suites.
|
|
63
|
+
email:
|
|
64
|
+
- design.navid@gmail.com
|
|
65
|
+
executables: []
|
|
66
|
+
extensions: []
|
|
67
|
+
extra_rdoc_files: []
|
|
68
|
+
files:
|
|
69
|
+
- CHANGELOG.md
|
|
70
|
+
- LICENSE.txt
|
|
71
|
+
- NOTICE.md
|
|
72
|
+
- README.md
|
|
73
|
+
- lib/capybara-lightpanda.rb
|
|
74
|
+
- lib/capybara/lightpanda/binary.rb
|
|
75
|
+
- lib/capybara/lightpanda/browser.rb
|
|
76
|
+
- lib/capybara/lightpanda/client.rb
|
|
77
|
+
- lib/capybara/lightpanda/client/subscriber.rb
|
|
78
|
+
- lib/capybara/lightpanda/client/web_socket.rb
|
|
79
|
+
- lib/capybara/lightpanda/cookies.rb
|
|
80
|
+
- lib/capybara/lightpanda/driver.rb
|
|
81
|
+
- lib/capybara/lightpanda/errors.rb
|
|
82
|
+
- lib/capybara/lightpanda/frame.rb
|
|
83
|
+
- lib/capybara/lightpanda/javascripts/index.js
|
|
84
|
+
- lib/capybara/lightpanda/keyboard.rb
|
|
85
|
+
- lib/capybara/lightpanda/logger.rb
|
|
86
|
+
- lib/capybara/lightpanda/network.rb
|
|
87
|
+
- lib/capybara/lightpanda/node.rb
|
|
88
|
+
- lib/capybara/lightpanda/options.rb
|
|
89
|
+
- lib/capybara/lightpanda/process.rb
|
|
90
|
+
- lib/capybara/lightpanda/utils/event.rb
|
|
91
|
+
- lib/capybara/lightpanda/version.rb
|
|
92
|
+
- lib/capybara/lightpanda/xpath_polyfill.rb
|
|
93
|
+
homepage: https://navidemad.github.io/capybara-lightpanda
|
|
94
|
+
licenses:
|
|
95
|
+
- MIT
|
|
96
|
+
metadata:
|
|
97
|
+
source_code_uri: https://github.com/navidemad/capybara-lightpanda
|
|
98
|
+
changelog_uri: https://github.com/navidemad/capybara-lightpanda/releases
|
|
99
|
+
bug_tracker_uri: https://github.com/navidemad/capybara-lightpanda/issues
|
|
100
|
+
documentation_uri: https://navidemad.github.io/capybara-lightpanda
|
|
101
|
+
rubygems_mfa_required: 'true'
|
|
102
|
+
rdoc_options: []
|
|
103
|
+
require_paths:
|
|
104
|
+
- lib
|
|
105
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '3.3'
|
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0'
|
|
115
|
+
requirements: []
|
|
116
|
+
rubygems_version: 4.0.6
|
|
117
|
+
specification_version: 4
|
|
118
|
+
summary: Capybara driver for the Lightpanda headless browser
|
|
119
|
+
test_files: []
|