panda-core 0.8.3 → 0.8.4
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 +4 -4
- data/lib/panda/core/testing/rails_helper.rb +2 -0
- data/lib/panda/core/testing/support/system/better_system_tests.rb +143 -0
- data/lib/panda/core/testing/support/system/cuprite_helpers.rb +237 -0
- data/lib/panda/core/testing/support/system/cuprite_setup.rb +2 -2
- data/lib/panda/core/testing/support/system/system_test_helpers.rb +25 -139
- data/lib/panda/core/version.rb +1 -1
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 28b16d6a5d79cf880c87149553332200a4b0c685fd1c9e654ec969475c85b1a7
|
|
4
|
+
data.tar.gz: 67a0a3ae10b209c96219e3974f8917007443cfbd1734db1943c255c99554d674
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 501c2f8db9b9a09c55d1c2c990672683a11e1fe63ff99067b2844edab3c7c31a10ef50c7f76658e0ce9862acfd0cdc5d979227f6b56ab18c797634b2a0343776
|
|
7
|
+
data.tar.gz: ce9f83f3d6619f6bf6b58d29c4b589a6bb3727120e0695391edbe8c22007c50aeb96c1a508b1c9798386db6a9c567f31644943a48dc7978700fb0a7377e0d7fb
|
|
@@ -61,6 +61,8 @@ RSpec.configure do |config|
|
|
|
61
61
|
if ENV["GITHUB_ACTIONS"] == "true"
|
|
62
62
|
require "rspec/github"
|
|
63
63
|
config.add_formatter RSpec::Github::Formatter
|
|
64
|
+
# Also add documentation formatter for colored real-time output in CI logs
|
|
65
|
+
config.add_formatter RSpec::Core::Formatters::DocumentationFormatter, $stdout
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
# Controller testing support (if rails-controller-testing is available)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Extends Rails system tests with improved screenshot and driver handling
|
|
4
|
+
#
|
|
5
|
+
# This module provides enhancements to Rails system tests:
|
|
6
|
+
# - Multi-session screenshot support
|
|
7
|
+
# - CI-specific error handling and logging
|
|
8
|
+
# - Enhanced failure screenshots with HTML debugging
|
|
9
|
+
# - Network idle waiting before screenshots
|
|
10
|
+
#
|
|
11
|
+
# @example Using in RSpec
|
|
12
|
+
# RSpec.configure do |config|
|
|
13
|
+
# config.include Panda::Core::Testing::BetterSystemTests, type: :system
|
|
14
|
+
# end
|
|
15
|
+
module Panda
|
|
16
|
+
module Core
|
|
17
|
+
module Testing
|
|
18
|
+
module BetterSystemTests
|
|
19
|
+
# Make failure screenshots compatible with multi-session setup.
|
|
20
|
+
# That's where we use Capybara.last_used_session introduced before.
|
|
21
|
+
def take_screenshot
|
|
22
|
+
return super unless Capybara.last_used_session
|
|
23
|
+
|
|
24
|
+
Capybara.using_session(Capybara.last_used_session) { super }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
module ClassMethods
|
|
28
|
+
# Configure better system tests for RSpec
|
|
29
|
+
# This sets up the necessary hooks and configuration
|
|
30
|
+
def configure_better_system_tests!
|
|
31
|
+
# Make urls in mailers contain the correct server host.
|
|
32
|
+
# This is required for testing links in emails (e.g., via capybara-email).
|
|
33
|
+
around(:each, type: :system) do |ex|
|
|
34
|
+
was_host = Rails.application.default_url_options[:host]
|
|
35
|
+
Rails.application.default_url_options[:host] = Capybara.server_host
|
|
36
|
+
ex.run
|
|
37
|
+
Rails.application.default_url_options[:host] = was_host
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Make sure this hook runs before others
|
|
41
|
+
# Means you don't have to set js: true in every system spec
|
|
42
|
+
prepend_before(:each, type: :system) do
|
|
43
|
+
driven_by :cuprite
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Enable automatic screenshots on failure
|
|
47
|
+
# Add CI-specific timeout and retry logic for form interactions
|
|
48
|
+
around(:each, type: :system) do |example|
|
|
49
|
+
if ENV["GITHUB_ACTIONS"] == "true"
|
|
50
|
+
# In CI, wrap the test execution with additional error handling
|
|
51
|
+
begin
|
|
52
|
+
example.run
|
|
53
|
+
rescue => e
|
|
54
|
+
# Log any error for debugging
|
|
55
|
+
puts "[CI] Test error detected: #{e.class} - #{e.message}"
|
|
56
|
+
puts "[CI] Current URL: #{begin
|
|
57
|
+
page.current_url
|
|
58
|
+
rescue
|
|
59
|
+
""
|
|
60
|
+
end}"
|
|
61
|
+
|
|
62
|
+
# Re-raise the original error
|
|
63
|
+
raise e
|
|
64
|
+
end
|
|
65
|
+
else
|
|
66
|
+
example.run
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
after(:each, type: :system) do |example|
|
|
71
|
+
next unless example.exception
|
|
72
|
+
|
|
73
|
+
begin
|
|
74
|
+
# Wait for any pending JavaScript to complete
|
|
75
|
+
# Cuprite has direct network idle support
|
|
76
|
+
begin
|
|
77
|
+
page.driver.wait_for_network_idle
|
|
78
|
+
rescue
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Wait for DOM to be ready
|
|
83
|
+
sleep 0.5
|
|
84
|
+
|
|
85
|
+
# Get comprehensive page info
|
|
86
|
+
page_html = begin
|
|
87
|
+
page.html
|
|
88
|
+
rescue
|
|
89
|
+
""
|
|
90
|
+
end
|
|
91
|
+
current_url = begin
|
|
92
|
+
page.current_url
|
|
93
|
+
rescue
|
|
94
|
+
""
|
|
95
|
+
end
|
|
96
|
+
current_path = begin
|
|
97
|
+
page.current_path
|
|
98
|
+
rescue
|
|
99
|
+
""
|
|
100
|
+
end
|
|
101
|
+
page_title = begin
|
|
102
|
+
page.title
|
|
103
|
+
rescue
|
|
104
|
+
""
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Check for redirect or blank page indicators
|
|
108
|
+
if page_html.length < 100
|
|
109
|
+
puts "Warning: Page content appears minimal (#{page_html.length} chars) when taking screenshot"
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Use Capybara's save_screenshot method
|
|
113
|
+
screenshot_path = Capybara.save_screenshot
|
|
114
|
+
if screenshot_path
|
|
115
|
+
puts "Screenshot saved to: #{screenshot_path}"
|
|
116
|
+
puts "Page title: #{page_title}" if page_title.present?
|
|
117
|
+
puts "Current URL: #{current_url}" if current_url.present?
|
|
118
|
+
puts "Current path: #{current_path}" if current_path.present?
|
|
119
|
+
puts "Page content length: #{page_html.length} characters"
|
|
120
|
+
|
|
121
|
+
# Save page HTML for debugging in CI
|
|
122
|
+
if ENV["GITHUB_ACTIONS"] && page_html.present?
|
|
123
|
+
html_debug_path = screenshot_path.gsub(".png", ".html")
|
|
124
|
+
File.write(html_debug_path, page_html)
|
|
125
|
+
puts "Page HTML saved to: #{html_debug_path}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
rescue => e
|
|
129
|
+
puts "Failed to capture screenshot: #{e.message}"
|
|
130
|
+
puts "Exception class: #{example.exception.class}"
|
|
131
|
+
puts "Exception message: #{example.exception.message}"
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def self.included(base)
|
|
138
|
+
base.extend(ClassMethods)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Helper methods for Cuprite-based system tests
|
|
4
|
+
#
|
|
5
|
+
# This module provides utility methods for working with Cuprite in system tests:
|
|
6
|
+
# - Page state verification helpers
|
|
7
|
+
# - Network and DOM waiting utilities
|
|
8
|
+
# - Safe form interaction methods with automatic retry in CI
|
|
9
|
+
# - Debugging helpers for headful testing
|
|
10
|
+
#
|
|
11
|
+
# @example Using in a system test
|
|
12
|
+
# RSpec.configure do |config|
|
|
13
|
+
# config.include Panda::Core::Testing::CupriteHelpers, type: :system
|
|
14
|
+
# end
|
|
15
|
+
module Panda
|
|
16
|
+
module Core
|
|
17
|
+
module Testing
|
|
18
|
+
module CupriteHelpers
|
|
19
|
+
# Ensure page is loaded and stable before interacting
|
|
20
|
+
def ensure_page_loaded
|
|
21
|
+
# Check if we're on about:blank and need to reload
|
|
22
|
+
current_url = begin
|
|
23
|
+
page.current_url
|
|
24
|
+
rescue
|
|
25
|
+
"unknown"
|
|
26
|
+
end
|
|
27
|
+
if current_url.include?("about:blank")
|
|
28
|
+
puts "[CI] Page is on about:blank, skipping recovery to avoid loops" if ENV["GITHUB_ACTIONS"]
|
|
29
|
+
# Don't try to recover - let the test handle it
|
|
30
|
+
return false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Wait for page to be ready
|
|
34
|
+
wait_for_ready_state
|
|
35
|
+
# Wait for JavaScript to load
|
|
36
|
+
wait_for_javascript
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Wait for document ready state
|
|
41
|
+
def wait_for_ready_state
|
|
42
|
+
Timeout.timeout(5) do
|
|
43
|
+
loop do
|
|
44
|
+
ready = page.evaluate_script("document.readyState")
|
|
45
|
+
break if ready == "complete"
|
|
46
|
+
|
|
47
|
+
sleep 0.1
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
rescue Timeout::Error
|
|
51
|
+
puts "[CI] Timeout waiting for document ready state" if ENV["GITHUB_ACTIONS"]
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Wait for JavaScript to load (application-specific flag)
|
|
55
|
+
# Override in your application if you have a custom loaded flag
|
|
56
|
+
def wait_for_javascript(timeout: 5)
|
|
57
|
+
Timeout.timeout(timeout) do
|
|
58
|
+
loop do
|
|
59
|
+
loaded = begin
|
|
60
|
+
# Check for common JavaScript loaded indicators
|
|
61
|
+
page.evaluate_script("document.readyState === 'complete'")
|
|
62
|
+
rescue
|
|
63
|
+
false
|
|
64
|
+
end
|
|
65
|
+
break if loaded
|
|
66
|
+
|
|
67
|
+
sleep 0.1
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
true
|
|
71
|
+
rescue Timeout::Error
|
|
72
|
+
puts "[CI] Timeout waiting for JavaScript to load" if ENV["GITHUB_ACTIONS"]
|
|
73
|
+
false
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Waits for a specific selector to be present and visible on the page
|
|
77
|
+
# @param selector [String] CSS selector to wait for
|
|
78
|
+
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
79
|
+
# @return [Boolean] true if element is found, false if timeout occurs
|
|
80
|
+
def wait_for_selector(selector, timeout: 5)
|
|
81
|
+
start_time = Time.now
|
|
82
|
+
while Time.now - start_time < timeout
|
|
83
|
+
return true if page.has_css?(selector, visible: true)
|
|
84
|
+
|
|
85
|
+
sleep 0.1
|
|
86
|
+
end
|
|
87
|
+
false
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Waits for a specific text to be present on the page
|
|
91
|
+
# @param text [String] Text to wait for
|
|
92
|
+
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
93
|
+
# @return [Boolean] true if text is found, false if timeout occurs
|
|
94
|
+
def wait_for_text(text, timeout: 5)
|
|
95
|
+
start_time = Time.now
|
|
96
|
+
while Time.now - start_time < timeout
|
|
97
|
+
return true if page.has_text?(text)
|
|
98
|
+
|
|
99
|
+
sleep 0.1
|
|
100
|
+
end
|
|
101
|
+
false
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Waits for network requests to complete
|
|
105
|
+
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
106
|
+
# @return [Boolean] true if network is idle, false if timeout occurs
|
|
107
|
+
def wait_for_network_idle(timeout: 5)
|
|
108
|
+
# Cuprite has direct network idle support
|
|
109
|
+
page.driver.wait_for_network_idle(timeout: timeout)
|
|
110
|
+
true
|
|
111
|
+
rescue => e
|
|
112
|
+
puts "[CI] Network idle timeout: #{e.message}" if ENV["GITHUB_ACTIONS"]
|
|
113
|
+
false
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Waits for JavaScript to modify the DOM
|
|
117
|
+
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
118
|
+
# @return [Boolean] true if mutation occurred, false if timeout occurs
|
|
119
|
+
def wait_for_dom_mutation(timeout: 5)
|
|
120
|
+
start_time = Time.now
|
|
121
|
+
initial_dom = page.html
|
|
122
|
+
while Time.now - start_time < timeout
|
|
123
|
+
return true if page.html != initial_dom
|
|
124
|
+
|
|
125
|
+
sleep 0.1
|
|
126
|
+
end
|
|
127
|
+
false
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Drop #pause anywhere in a test to stop the execution.
|
|
131
|
+
# Useful when you want to checkout the contents of a web page in the middle of a test
|
|
132
|
+
# running in a headful mode.
|
|
133
|
+
def pause
|
|
134
|
+
# Cuprite-specific pause method
|
|
135
|
+
page.driver.pause
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Drop #browser_debug anywhere in a test to open a Chrome inspector and pause the execution
|
|
139
|
+
# Usage: browser_debug(binding)
|
|
140
|
+
def browser_debug(*)
|
|
141
|
+
# Cuprite-specific debug method
|
|
142
|
+
page.driver.debug
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Allows sending a list of CSS selectors to be clicked on in the correct order (no delay)
|
|
146
|
+
# Useful where you need to trigger e.g. a blur event on an input field
|
|
147
|
+
def click_on_selectors(*css_selectors)
|
|
148
|
+
css_selectors.each do |selector|
|
|
149
|
+
find(selector).click
|
|
150
|
+
sleep 0.1 # Add a small delay to allow JavaScript to run
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Wait for a field to have a specific value
|
|
155
|
+
# @param field_name [String] The field name or label
|
|
156
|
+
# @param value [String] The expected value
|
|
157
|
+
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
158
|
+
def wait_for_field_value(field_name, value, timeout: 5)
|
|
159
|
+
start_time = Time.now
|
|
160
|
+
while Time.now - start_time < timeout
|
|
161
|
+
return true if page.has_field?(field_name, with: value)
|
|
162
|
+
|
|
163
|
+
sleep 0.1
|
|
164
|
+
end
|
|
165
|
+
false
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Safe methods that handle Ferrum NodeNotFoundError in CI
|
|
169
|
+
def safe_fill_in(locator, with:)
|
|
170
|
+
retries = 0
|
|
171
|
+
start_time = Time.now
|
|
172
|
+
max_duration = 5 # Maximum 5 seconds total
|
|
173
|
+
|
|
174
|
+
begin
|
|
175
|
+
fill_in locator, with: with
|
|
176
|
+
rescue Ferrum::NodeNotFoundError, Capybara::ElementNotFound => e
|
|
177
|
+
retries += 1
|
|
178
|
+
elapsed = Time.now - start_time
|
|
179
|
+
|
|
180
|
+
if retries <= 2 && elapsed < max_duration && ENV["GITHUB_ACTIONS"]
|
|
181
|
+
puts "[CI] Element not found on fill_in '#{locator}', retry #{retries}/2 (#{elapsed.round(1)}s elapsed)"
|
|
182
|
+
sleep 0.5
|
|
183
|
+
retry
|
|
184
|
+
else
|
|
185
|
+
puts "[CI] Giving up on fill_in '#{locator}' after #{retries} retries and #{elapsed.round(1)}s" if ENV["GITHUB_ACTIONS"]
|
|
186
|
+
raise e
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def safe_select(value, from:)
|
|
192
|
+
retries = 0
|
|
193
|
+
start_time = Time.now
|
|
194
|
+
max_duration = 5 # Maximum 5 seconds total
|
|
195
|
+
|
|
196
|
+
begin
|
|
197
|
+
select value, from: from
|
|
198
|
+
rescue Ferrum::NodeNotFoundError, Capybara::ElementNotFound => e
|
|
199
|
+
retries += 1
|
|
200
|
+
elapsed = Time.now - start_time
|
|
201
|
+
|
|
202
|
+
if retries <= 2 && elapsed < max_duration && ENV["GITHUB_ACTIONS"]
|
|
203
|
+
puts "[CI] Element not found on select '#{value}' from '#{from}', retry #{retries}/2 (#{elapsed.round(1)}s elapsed)"
|
|
204
|
+
sleep 0.5
|
|
205
|
+
retry
|
|
206
|
+
else
|
|
207
|
+
puts "[CI] Giving up on select '#{value}' from '#{from}' after #{retries} retries and #{elapsed.round(1)}s" if ENV["GITHUB_ACTIONS"]
|
|
208
|
+
raise e
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def safe_click_button(locator)
|
|
214
|
+
retries = 0
|
|
215
|
+
start_time = Time.now
|
|
216
|
+
max_duration = 5 # Maximum 5 seconds total
|
|
217
|
+
|
|
218
|
+
begin
|
|
219
|
+
click_button locator
|
|
220
|
+
rescue Ferrum::NodeNotFoundError, Capybara::ElementNotFound => e
|
|
221
|
+
retries += 1
|
|
222
|
+
elapsed = Time.now - start_time
|
|
223
|
+
|
|
224
|
+
if retries <= 2 && elapsed < max_duration && ENV["GITHUB_ACTIONS"]
|
|
225
|
+
puts "[CI] Element not found on click_button '#{locator}', retry #{retries}/2 (#{elapsed.round(1)}s elapsed)"
|
|
226
|
+
sleep 0.5
|
|
227
|
+
retry
|
|
228
|
+
else
|
|
229
|
+
puts "[CI] Giving up on click_button '#{locator}' after #{retries} retries and #{elapsed.round(1)}s" if ENV["GITHUB_ACTIONS"]
|
|
230
|
+
raise e
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
@@ -24,10 +24,10 @@ module Panda
|
|
|
24
24
|
inspector: ENV["INSPECTOR"].in?(%w[y 1 yes true]),
|
|
25
25
|
headless: !ENV["HEADLESS"].in?(%w[n 0 no false]),
|
|
26
26
|
slowmo: ENV["SLOWMO"]&.to_f || 0,
|
|
27
|
-
timeout:
|
|
27
|
+
timeout: ENV["CUPRITE_TIMEOUT"]&.to_i || 2,
|
|
28
28
|
js_errors: true, # IMPORTANT: Report JavaScript errors as test failures
|
|
29
29
|
ignore_default_browser_options: false,
|
|
30
|
-
process_timeout: 2,
|
|
30
|
+
process_timeout: ENV["CUPRITE_PROCESS_TIMEOUT"]&.to_i || 2,
|
|
31
31
|
wait_for_network_idle: false, # Don't wait for all network requests
|
|
32
32
|
pending_connection_errors: false, # Don't fail on pending external connections
|
|
33
33
|
browser_options: {
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "cuprite_helpers"
|
|
4
|
+
require_relative "better_system_tests"
|
|
5
|
+
|
|
3
6
|
# Generic system test helpers for Cuprite-based testing
|
|
4
7
|
# These methods work for any Rails application using Cuprite
|
|
5
8
|
|
|
@@ -7,134 +10,8 @@ module Panda
|
|
|
7
10
|
module Core
|
|
8
11
|
module Testing
|
|
9
12
|
module SystemTestHelpers
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return super unless Capybara.last_used_session
|
|
13
|
-
|
|
14
|
-
Capybara.using_session(Capybara.last_used_session) { super }
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# Ensure page is loaded and stable before interacting
|
|
18
|
-
def ensure_page_loaded
|
|
19
|
-
# Check if we're on about:blank
|
|
20
|
-
current_url = begin
|
|
21
|
-
page.current_url
|
|
22
|
-
rescue
|
|
23
|
-
"unknown"
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
if current_url.include?("about:blank")
|
|
27
|
-
puts "[CI] Page is on about:blank, skipping recovery to avoid loops" if ENV["GITHUB_ACTIONS"]
|
|
28
|
-
return false
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
# Wait for page to be ready
|
|
32
|
-
wait_for_ready_state
|
|
33
|
-
true
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Wait for document ready state
|
|
37
|
-
def wait_for_ready_state
|
|
38
|
-
Timeout.timeout(5) do
|
|
39
|
-
loop do
|
|
40
|
-
ready = page.evaluate_script("document.readyState")
|
|
41
|
-
break if ready == "complete"
|
|
42
|
-
|
|
43
|
-
sleep 0.1
|
|
44
|
-
end
|
|
45
|
-
end
|
|
46
|
-
rescue Timeout::Error
|
|
47
|
-
puts "[CI] Timeout waiting for document ready state" if ENV["GITHUB_ACTIONS"]
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
# Waits for a specific selector to be present and visible
|
|
51
|
-
# @param selector [String] CSS selector to wait for
|
|
52
|
-
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
53
|
-
# @return [Boolean] true if element is found, false if timeout occurs
|
|
54
|
-
def wait_for_selector(selector, timeout: 5)
|
|
55
|
-
start_time = Time.now
|
|
56
|
-
while Time.now - start_time < timeout
|
|
57
|
-
return true if page.has_css?(selector, visible: true)
|
|
58
|
-
|
|
59
|
-
sleep 0.1
|
|
60
|
-
end
|
|
61
|
-
false
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Waits for a specific text to be present on the page
|
|
65
|
-
# @param text [String] Text to wait for
|
|
66
|
-
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
67
|
-
# @return [Boolean] true if text is found, false if timeout occurs
|
|
68
|
-
def wait_for_text(text, timeout: 5)
|
|
69
|
-
start_time = Time.now
|
|
70
|
-
while Time.now - start_time < timeout
|
|
71
|
-
return true if page.has_text?(text)
|
|
72
|
-
|
|
73
|
-
sleep 0.1
|
|
74
|
-
end
|
|
75
|
-
false
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
# Waits for network requests to complete
|
|
79
|
-
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
80
|
-
# @return [Boolean] true if network is idle, false if timeout occurs
|
|
81
|
-
def wait_for_network_idle(timeout: 5)
|
|
82
|
-
page.driver.wait_for_network_idle(timeout: timeout)
|
|
83
|
-
true
|
|
84
|
-
rescue => e
|
|
85
|
-
puts "[CI] Network idle timeout: #{e.message}" if ENV["GITHUB_ACTIONS"]
|
|
86
|
-
false
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
# Waits for JavaScript to modify the DOM
|
|
90
|
-
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
91
|
-
# @return [Boolean] true if mutation occurred, false if timeout occurs
|
|
92
|
-
def wait_for_dom_mutation(timeout: 5)
|
|
93
|
-
start_time = Time.now
|
|
94
|
-
initial_dom = page.html
|
|
95
|
-
while Time.now - start_time < timeout
|
|
96
|
-
return true if page.html != initial_dom
|
|
97
|
-
|
|
98
|
-
sleep 0.1
|
|
99
|
-
end
|
|
100
|
-
false
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
# Drop #pause anywhere in a test to stop the execution
|
|
104
|
-
# Useful when you want to check out the contents of a web page in the middle of a test
|
|
105
|
-
# running in a headful mode
|
|
106
|
-
def pause
|
|
107
|
-
page.driver.pause
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
# Drop #browser_debug anywhere in a test to open a Chrome inspector and pause the execution
|
|
111
|
-
# Usage: browser_debug(binding)
|
|
112
|
-
def browser_debug(*)
|
|
113
|
-
page.driver.debug
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
# Allows sending a list of CSS selectors to be clicked on in the correct order (no delay)
|
|
117
|
-
# Useful where you need to trigger e.g. a blur event on an input field
|
|
118
|
-
def click_on_selectors(*css_selectors)
|
|
119
|
-
css_selectors.each do |selector|
|
|
120
|
-
find(selector).click
|
|
121
|
-
sleep 0.1 # Add a small delay to allow JavaScript to run
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
|
-
# Wait for a field to have a specific value
|
|
126
|
-
# @param field_name [String] The field name or label
|
|
127
|
-
# @param value [String] The expected value
|
|
128
|
-
# @param timeout [Integer] Maximum time to wait in seconds (default: 5)
|
|
129
|
-
def wait_for_field_value(field_name, value, timeout: 5)
|
|
130
|
-
start_time = Time.now
|
|
131
|
-
while Time.now - start_time < timeout
|
|
132
|
-
return true if page.has_field?(field_name, with: value)
|
|
133
|
-
|
|
134
|
-
sleep 0.1
|
|
135
|
-
end
|
|
136
|
-
false
|
|
137
|
-
end
|
|
13
|
+
include CupriteHelpers
|
|
14
|
+
include BetterSystemTests
|
|
138
15
|
end
|
|
139
16
|
end
|
|
140
17
|
end
|
|
@@ -144,8 +21,7 @@ end
|
|
|
144
21
|
RSpec.configure do |config|
|
|
145
22
|
config.include Panda::Core::Testing::SystemTestHelpers, type: :system
|
|
146
23
|
|
|
147
|
-
# Make
|
|
148
|
-
# This is required for testing links in emails (e.g., via capybara-email)
|
|
24
|
+
# Make urls in mailers contain the correct server host
|
|
149
25
|
config.around(:each, type: :system) do |ex|
|
|
150
26
|
was_host = Rails.application.default_url_options[:host]
|
|
151
27
|
Rails.application.default_url_options[:host] = Capybara.server_host
|
|
@@ -154,7 +30,6 @@ RSpec.configure do |config|
|
|
|
154
30
|
end
|
|
155
31
|
|
|
156
32
|
# Make sure this hook runs before others
|
|
157
|
-
# Means you don't have to set js: true in every system spec
|
|
158
33
|
config.prepend_before(:each, type: :system) do
|
|
159
34
|
driven_by :cuprite
|
|
160
35
|
end
|
|
@@ -169,7 +44,7 @@ RSpec.configure do |config|
|
|
|
169
44
|
puts "[CI] Current URL: #{begin
|
|
170
45
|
page.current_url
|
|
171
46
|
rescue
|
|
172
|
-
"
|
|
47
|
+
""
|
|
173
48
|
end}"
|
|
174
49
|
raise e
|
|
175
50
|
end
|
|
@@ -190,22 +65,31 @@ RSpec.configure do |config|
|
|
|
190
65
|
nil
|
|
191
66
|
end
|
|
192
67
|
|
|
193
|
-
sleep 0.5
|
|
68
|
+
sleep 0.5
|
|
194
69
|
|
|
195
70
|
# Get comprehensive page info
|
|
196
71
|
page_html = begin
|
|
197
72
|
page.html
|
|
198
73
|
rescue
|
|
199
|
-
"
|
|
74
|
+
""
|
|
75
|
+
end
|
|
76
|
+
current_url = begin
|
|
77
|
+
page.current_url
|
|
78
|
+
rescue
|
|
79
|
+
""
|
|
80
|
+
end
|
|
81
|
+
current_path = begin
|
|
82
|
+
page.current_path
|
|
83
|
+
rescue
|
|
84
|
+
""
|
|
200
85
|
end
|
|
201
|
-
|
|
202
86
|
page_title = begin
|
|
203
87
|
page.title
|
|
204
88
|
rescue
|
|
205
|
-
"
|
|
89
|
+
""
|
|
206
90
|
end
|
|
207
91
|
|
|
208
|
-
#
|
|
92
|
+
# Check for redirect or blank page indicators
|
|
209
93
|
if page_html.length < 100
|
|
210
94
|
puts "Warning: Page content appears minimal (#{page_html.length} chars) when taking screenshot"
|
|
211
95
|
end
|
|
@@ -214,11 +98,13 @@ RSpec.configure do |config|
|
|
|
214
98
|
screenshot_path = Capybara.save_screenshot
|
|
215
99
|
if screenshot_path
|
|
216
100
|
puts "Screenshot saved to: #{screenshot_path}"
|
|
217
|
-
puts "Page title: #{page_title}"
|
|
101
|
+
puts "Page title: #{page_title}" if page_title.present?
|
|
102
|
+
puts "Current URL: #{current_url}" if current_url.present?
|
|
103
|
+
puts "Current path: #{current_path}" if current_path.present?
|
|
218
104
|
puts "Page content length: #{page_html.length} characters"
|
|
219
105
|
|
|
220
106
|
# Save page HTML for debugging in CI
|
|
221
|
-
if ENV["GITHUB_ACTIONS"]
|
|
107
|
+
if ENV["GITHUB_ACTIONS"] && page_html.present?
|
|
222
108
|
html_debug_path = screenshot_path.gsub(".png", ".html")
|
|
223
109
|
File.write(html_debug_path, page_html)
|
|
224
110
|
puts "Page HTML saved to: #{html_debug_path}"
|
data/lib/panda/core/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: panda-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.8.
|
|
4
|
+
version: 0.8.4
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Otaina Limited
|
|
@@ -505,7 +505,9 @@ files:
|
|
|
505
505
|
- lib/panda/core/testing/support/omniauth_setup.rb
|
|
506
506
|
- lib/panda/core/testing/support/service_stubs.rb
|
|
507
507
|
- lib/panda/core/testing/support/setup.rb
|
|
508
|
+
- lib/panda/core/testing/support/system/better_system_tests.rb
|
|
508
509
|
- lib/panda/core/testing/support/system/capybara_setup.rb
|
|
510
|
+
- lib/panda/core/testing/support/system/cuprite_helpers.rb
|
|
509
511
|
- lib/panda/core/testing/support/system/cuprite_setup.rb
|
|
510
512
|
- lib/panda/core/testing/support/system/database_connection_helpers.rb
|
|
511
513
|
- lib/panda/core/testing/support/system/system_test_helpers.rb
|