percy-selenium 1.1.2 → 1.1.3.pre.beta.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c6479ecd72851c243d9e31660d690bb4f0a85447715418612c0af5b7c33e3a7e
4
- data.tar.gz: 172072160f5612dbf40d82c6f1703627bbf04aa7ef245b29b9aca7668875b667
3
+ metadata.gz: 685b6a737cf5d8ebe9ce59dc70736248b7d5f30a6131bcc34d98ccdb7d1b6a03
4
+ data.tar.gz: b25796e9354d99d64ce0022263e4987cae49a57bb5e0e31443afc490240d7ec2
5
5
  SHA512:
6
- metadata.gz: b5111c9ba7c89adf7e11cb58278333c3a650ffa3d3276291654d0c4fee6c6a66f9b883a84bbb9597f739bdd77155496825a0f04fd0f2bf3ff771c6bf2c200394
7
- data.tar.gz: 37d6c0a4a53942f8574dd73dbb0cc5d7043819e21983306fe6800cca355e0f0db8c2976f53935cdbef019cf2fa72b7c2794837149d1c78af342d77df9327400f
6
+ metadata.gz: 5e410bb024a3e3bac312041bd907e3a39a5f9f61f14450c0962dbb82af505ebec08319672e63d6e525da1679f14052c0c243bcfd818c65295bfb0c60baf53f4f
7
+ data.tar.gz: 46991f060f602406612f78986767bd5c5f64cfc0e21e4ef6834ee0609f113725dafd455d16d5ef3c0b3eb4b430475e0d9c4ce87a988e1328ad963914383397b5
data/.gitignore CHANGED
@@ -15,3 +15,4 @@ mkmf.log
15
15
  .DS_Store
16
16
  Gemfile.lock
17
17
  node_modules/
18
+ .venv/
data/lib/percy.rb CHANGED
@@ -85,13 +85,16 @@ module Percy
85
85
  get_serialized_dom(driver, options, percy_dom_script: percy_dom_script)
86
86
  end
87
87
 
88
+ # Strip `readiness` before POSTing -- SDK-local config that the CLI
89
+ # already has via healthcheck.
90
+ post_options = options.reject { |k, _| k.to_s == 'readiness' }
88
91
  response = fetch('percy/snapshot',
89
92
  name: name,
90
93
  url: driver.current_url,
91
94
  dom_snapshot: dom_snapshot,
92
95
  client_info: CLIENT_INFO,
93
96
  environment_info: ENV_INFO,
94
- **options,)
97
+ **post_options,)
95
98
 
96
99
  body = JSON.parse(response.body)
97
100
  unless body['success']
@@ -113,8 +116,79 @@ module Percy
113
116
  driver.manage
114
117
  end
115
118
 
119
+ # Shallow-merge of global (@cli_config.snapshot.readiness) and per-snapshot
120
+ # (options[:readiness] / options['readiness']) readiness config. Per-snapshot
121
+ # keys win, unspecified global keys (notably preset: disabled) are inherited.
122
+ def self.resolve_readiness_config(options)
123
+ global = @cli_config&.dig('snapshot', 'readiness')
124
+ global = {} unless global.is_a?(Hash)
125
+ per_snapshot = options[:readiness] || options['readiness']
126
+ per_snapshot = {} unless per_snapshot.is_a?(Hash)
127
+ # Normalise symbol keys to strings so the merge collapses :preset and 'preset'.
128
+ [global, per_snapshot].each_with_object({}) do |hash, merged|
129
+ hash.each { |k, v| merged[k.to_s] = v }
130
+ end
131
+ end
132
+
133
+ # Readiness gate: runs PercyDOM.waitForReady via
134
+ # execute_async_script BEFORE serialize. Graceful on old CLIs that lack the
135
+ # method. Returns readiness diagnostics (or nil) for attachment to domSnapshot.
136
+ def self.wait_for_ready(driver, options)
137
+ readiness_config = resolve_readiness_config(options)
138
+ return nil if readiness_config['preset'] == 'disabled'
139
+
140
+ # Match the driver's async-script timeout to readiness.timeoutMs so a
141
+ # higher user-configured timeout isn't silently capped by Selenium's
142
+ # default (~30s) firing ScriptTimeoutException before the in-page
143
+ # Promise resolves.
144
+ timeout_ms = readiness_config['timeoutMs']
145
+ previous_timeout = nil
146
+ if timeout_ms.is_a?(Numeric) && timeout_ms > 0
147
+ begin
148
+ previous_timeout = driver.manage.timeouts.script_timeout
149
+ driver.manage.timeouts.script_timeout = (timeout_ms / 1000.0) + 2
150
+ rescue StandardError
151
+ previous_timeout = nil # best-effort; older Selenium / unsupported
152
+ end
153
+ end
154
+
155
+ begin
156
+ script = <<~JS
157
+ var cfg = #{readiness_config.to_json};
158
+ var done = arguments[arguments.length - 1];
159
+ try {
160
+ if (typeof PercyDOM !== 'undefined' && typeof PercyDOM.waitForReady === 'function') {
161
+ PercyDOM.waitForReady(cfg).then(function(r){ done(r); }).catch(function(){ done(); });
162
+ } else { done(); }
163
+ } catch (e) { done(); }
164
+ JS
165
+ driver.execute_async_script(script)
166
+ rescue StandardError => e
167
+ log("waitForReady failed, proceeding to serialize: #{e}", 'debug')
168
+ nil
169
+ ensure
170
+ if previous_timeout
171
+ begin
172
+ driver.manage.timeouts.script_timeout = previous_timeout
173
+ rescue StandardError
174
+ # best-effort
175
+ end
176
+ end
177
+ end
178
+ end
179
+
116
180
  def self.get_serialized_dom(driver, options, percy_dom_script: nil)
117
- dom_snapshot = driver.execute_script("return PercyDOM.serialize(#{options.to_json})")
181
+ # Readiness gate before serialize. Graceful on old CLI.
182
+ readiness_diagnostics = wait_for_ready(driver, options)
183
+ # Strip `readiness` from forwarded serialize args -- it's consumed by
184
+ # wait_for_ready upstream, not a PercyDOM.serialize argument.
185
+ serialize_options = options.reject { |k, _| k.to_s == 'readiness' }
186
+ dom_snapshot = driver.execute_script("return PercyDOM.serialize(#{serialize_options.to_json})")
187
+ # `!nil?` preserves legitimate falsy returns like {} ("gate ran, no
188
+ # notable diagnostics").
189
+ if !readiness_diagnostics.nil? && dom_snapshot.is_a?(Hash)
190
+ dom_snapshot['readiness_diagnostics'] = readiness_diagnostics
191
+ end
118
192
  begin
119
193
  page_origin = get_origin(driver.current_url)
120
194
  iframes = percy_dom_script ? driver.find_elements(:tag_name, 'iframe') : []
data/lib/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Percy
2
- VERSION = '1.1.2'.freeze
2
+ VERSION = '1.1.3-beta.0'.freeze
3
3
  end
data/package.json CHANGED
@@ -4,6 +4,6 @@
4
4
  "test": "percy exec --testing -- bundle exec rspec"
5
5
  },
6
6
  "devDependencies": {
7
- "@percy/cli": "1.31.10"
7
+ "@percy/cli": "^1.31.15-beta.0"
8
8
  }
9
9
  }
@@ -748,6 +748,59 @@ RSpec.describe Percy do
748
748
  expect(dom['corsIframes'].length).to eq(1)
749
749
  expect(dom['corsIframes'][0]['frameUrl']).to eq('https://other.example.com/page')
750
750
  end
751
+
752
+ # --- Readiness gate --------------------------------------
753
+
754
+ it 'runs waitForReady before serialize and attaches diagnostics' do
755
+ allow(driver).to receive(:execute_async_script).and_return(
756
+ 'ok' => true, 'timed_out' => false,
757
+ )
758
+ allow(driver).to receive(:execute_script).and_return({'html' => '<html/>'})
759
+ allow(driver).to receive(:current_url).and_return('http://main.example.com/')
760
+ allow(driver).to receive(:find_elements).and_return([])
761
+
762
+ dom = Percy.get_serialized_dom(driver, {})
763
+ expect(driver).to have_received(:execute_async_script) do |script| # rubocop:disable RSpec/MessageSpies
764
+ expect(script).to include('waitForReady')
765
+ expect(script).to include("typeof PercyDOM.waitForReady === 'function'")
766
+ end
767
+ expect(dom['readiness_diagnostics']).to eq('ok' => true, 'timed_out' => false)
768
+ end
769
+
770
+ it 'embeds per-snapshot readiness config in the script' do
771
+ allow(driver).to receive(:execute_async_script).and_return(nil)
772
+ allow(driver).to receive(:execute_script).and_return({'html' => '<html/>'})
773
+ allow(driver).to receive(:current_url).and_return('http://main.example.com/')
774
+ allow(driver).to receive(:find_elements).and_return([])
775
+
776
+ Percy.get_serialized_dom(driver, readiness: {preset: 'strict', stabilityWindowMs: 500})
777
+ expect(driver).to have_received(:execute_async_script) do |script| # rubocop:disable RSpec/MessageSpies
778
+ expect(script).to include('"preset":"strict"')
779
+ expect(script).to include('"stabilityWindowMs":500')
780
+ end
781
+ end
782
+
783
+ it 'skips execute_async_script when preset is disabled' do
784
+ allow(driver).to receive(:execute_script).and_return({'html' => '<html/>'})
785
+ allow(driver).to receive(:current_url).and_return('http://main.example.com/')
786
+ allow(driver).to receive(:find_elements).and_return([])
787
+ expect(driver).to_not receive(:execute_async_script)
788
+
789
+ dom = Percy.get_serialized_dom(driver, readiness: {preset: 'disabled'})
790
+ expect(dom).to_not have_key('readiness_diagnostics')
791
+ expect(dom['html']).to eq('<html/>')
792
+ end
793
+
794
+ it 'still serializes when execute_async_script raises' do
795
+ allow(driver).to receive(:execute_async_script).and_raise(StandardError, 'readiness boom')
796
+ allow(driver).to receive(:execute_script).and_return({'html' => '<html/>'})
797
+ allow(driver).to receive(:current_url).and_return('http://main.example.com/')
798
+ allow(driver).to receive(:find_elements).and_return([])
799
+
800
+ dom = Percy.get_serialized_dom(driver, {})
801
+ expect(dom).to_not have_key('readiness_diagnostics')
802
+ expect(dom['html']).to eq('<html/>')
803
+ end
751
804
  end
752
805
 
753
806
  describe '.change_window_dimension_and_wait' do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: percy-selenium
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.2
4
+ version: 1.1.3.pre.beta.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Perceptual Inc.