percy-selenium 1.1.2.pre.beta.0 → 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: aa2f41ca77fb6671ac305eb3ff0a99f61cef87b290b885ed8c16a462a865bbb9
4
- data.tar.gz: f45b3651099a2f84d098d4eb48db83b3685646ad3902b85a10b5f865a3b9fd48
3
+ metadata.gz: 685b6a737cf5d8ebe9ce59dc70736248b7d5f30a6131bcc34d98ccdb7d1b6a03
4
+ data.tar.gz: b25796e9354d99d64ce0022263e4987cae49a57bb5e0e31443afc490240d7ec2
5
5
  SHA512:
6
- metadata.gz: 821f02d3e049227bb1e294402cfb37ce097c21c775d3c8870871cc650db787784696ef55e0df593d817123a86664e8e868a9171526168a9d3d72835208d08c56
7
- data.tar.gz: 7e54763dd644d9a65489d2d9c043d0e82bee79637467b812e2ff9f80267d943c0fcf722095ca4c94b35a24258158e8af96c713edb6c27bf14aaee64dd3a86fc0
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/README.md CHANGED
@@ -62,3 +62,136 @@ $ percy exec -- [ruby test command]
62
62
  - `driver` (**required**) - A selenium-webdriver driver instance
63
63
  - `name` (**required**) - The snapshot name; must be unique to each snapshot
64
64
  - `options` - [See per-snapshot configuration options](https://www.browserstack.com/docs/percy/take-percy-snapshots/overview#per-snapshot-configuration)
65
+
66
+ ## Running Percy on Automate
67
+ `Percy.percy_screenshot(driver, name, options)` [ needs @percy/cli 1.27.0-beta.0+ ]
68
+
69
+ This is an example test using the `percy_screenshot` method.
70
+
71
+ ``` ruby
72
+ require 'percy'
73
+
74
+ capabilities = {
75
+ 'browserName' => 'chrome',
76
+ 'bstack:options' => {
77
+ 'userName' => '<your-username>',
78
+ 'accessKey' => '<your-access-key>'
79
+ }
80
+ }
81
+
82
+ driver = Selenium::WebDriver.for(
83
+ :remote,
84
+ url: 'https://hub-cloud.browserstack.com/wd/hub',
85
+ capabilities: capabilities
86
+ )
87
+
88
+ driver.navigate.to "https://example.com"
89
+
90
+ # Take a Percy screenshot
91
+ Percy.percy_screenshot(driver, 'Screenshot 1')
92
+
93
+ driver.navigate.to "https://google.com"
94
+ Percy.percy_screenshot(driver, 'Screenshot 2')
95
+
96
+ driver.quit
97
+ ```
98
+
99
+ - `driver` (**required**) - A Selenium driver instance
100
+ - `name` (**required**) - The screenshot name; must be unique to each screenshot
101
+ - `options` (**optional**) - There are various options supported by `percy_screenshot` to serve further functionality.
102
+ - `sync` - Boolean value by default it falls back to `false`, Gives the processed result around screenshot [From CLI v1.28.0-beta.0+]
103
+ - `fullPage` - Boolean value by default it falls back to `false`, Takes full page screenshot [From CLI v1.27.6+]
104
+ - `freezeAnimatedImage` - Boolean value by default it falls back to `false`, you can pass `true` and Percy will freeze image based animations.
105
+ - `freezeImageBySelectors` - List of selectors. Images will be frozen which are passed using selectors. For this to work `freezeAnimatedImage` must be set to true.
106
+ - `freezeImageByXpaths` - List of xpaths. Images will be frozen which are passed using xpaths. For this to work `freezeAnimatedImage` must be set to true.
107
+ - `percyCSS` - Custom CSS to be added to DOM before the screenshot being taken. Note: This gets removed once the screenshot is taken.
108
+ - `ignoreRegionXpaths` - List of xpaths. Elements in the DOM can be ignored using xpath.
109
+ - `ignoreRegionSelectors` - List of selectors. Elements in the DOM can be ignored using selectors.
110
+ - `ignoreRegionSeleniumElements` - List of Selenium WebElement. Elements can be ignored using selenium elements.
111
+ - `customIgnoreRegions` - List of custom objects. Elements can be ignored using custom boundaries. Just passing a simple hash for it like below.
112
+ - example: `{top: 10, right: 10, bottom: 120, left: 10}`
113
+ - In the above example it will draw a rectangle of ignore region as per given coordinates.
114
+ - `top` (int): Top coordinate of the ignore region.
115
+ - `bottom` (int): Bottom coordinate of the ignore region.
116
+ - `left` (int): Left coordinate of the ignore region.
117
+ - `right` (int): Right coordinate of the ignore region.
118
+ - `considerRegionXpaths` - List of xpaths. Elements in the DOM can be considered for diffing and will be ignored by Intelli Ignore using xpaths.
119
+ - `considerRegionSelectors` - List of selectors. Elements in the DOM can be considered for diffing and will be ignored by Intelli Ignore using selectors.
120
+ - `considerRegionSeleniumElements` - List of Selenium WebElement. Elements can be considered for diffing and will be ignored by Intelli Ignore using selenium elements.
121
+ - `customConsiderRegions` - List of custom objects. Elements can be considered for diffing and will be ignored by Intelli Ignore using custom boundaries.
122
+ - example: `{top: 10, right: 10, bottom: 120, left: 10}`
123
+ - In the above example a rectangle of consider region will be drawn.
124
+ - Parameters:
125
+ - `top` (int): Top coordinate of the consider region.
126
+ - `bottom` (int): Bottom coordinate of the consider region.
127
+ - `left` (int): Left coordinate of the consider region.
128
+ - `right` (int): Right coordinate of the consider region.
129
+ - `regions` - Parameter that allows users to apply snapshot options to specific areas of the page. This parameter is an array where each object defines a custom region with configurations.
130
+ - Parameters:
131
+ - `elementSelector` (optional, only one of the following must be provided, if this is not provided then full page will be considered as region)
132
+ - `boundingBox` (hash): Defines the coordinates and size of the region.
133
+ - `x` (number): X-coordinate of the region.
134
+ - `y` (number): Y-coordinate of the region.
135
+ - `width` (number): Width of the region.
136
+ - `height` (number): Height of the region.
137
+ - `elementXpath` (string): The XPath selector for the element.
138
+ - `elementCSS` (string): The CSS selector for the element.
139
+ - `algorithm` (mandatory)
140
+ - Specifies the snapshot comparison algorithm.
141
+ - Allowed values: `standard`, `layout`, `ignore`, `intelliignore`.
142
+ - `configuration` (required for `standard` and `intelliignore` algorithms, ignored otherwise)
143
+ - `diffSensitivity` (number): Sensitivity level for detecting differences.
144
+ - `imageIgnoreThreshold` (number): Threshold for ignoring minor image differences.
145
+ - `carouselsEnabled` (boolean): Whether to enable carousel detection.
146
+ - `bannersEnabled` (boolean): Whether to enable banner detection.
147
+ - `adsEnabled` (boolean): Whether to enable ad detection.
148
+ - `assertion` (optional)
149
+ - Defines assertions to apply to the region.
150
+ - `diffIgnoreThreshold` (number): The threshold for ignoring minor differences.
151
+
152
+ ### Example Usage for regions
153
+
154
+ ``` ruby
155
+ region1 = {
156
+ elementSelector: {
157
+ elementCSS: '.ad-banner'
158
+ },
159
+ algorithm: 'intelliignore',
160
+ configuration: {
161
+ diffSensitivity: 2,
162
+ imageIgnoreThreshold: 0.2,
163
+ carouselsEnabled: true,
164
+ bannersEnabled: true,
165
+ adsEnabled: true
166
+ },
167
+ assertion: {
168
+ diffIgnoreThreshold: 0.4
169
+ }
170
+ }
171
+
172
+ # Using Percy.create_region helper
173
+ region2 = Percy.create_region(
174
+ algorithm: 'intelliignore',
175
+ diff_sensitivity: 3,
176
+ ads_enabled: true,
177
+ diff_ignore_threshold: 0.4
178
+ )
179
+
180
+ Percy.percy_screenshot(driver, 'Screenshot 1', regions: [region1, region2])
181
+ ```
182
+
183
+ ### Creating Percy on Automate build
184
+ Note: Automate Percy Token starts with `auto` keyword. The command can be triggered using `exec` keyword.
185
+
186
+ ```sh-session
187
+ $ export PERCY_TOKEN=[your-project-token]
188
+ $ percy exec -- [ruby test command]
189
+ [percy] Percy has started!
190
+ [percy] [Ruby example] : Starting automate screenshot ...
191
+ [percy] Screenshot taken "Ruby example"
192
+ [percy] Stopping percy...
193
+ [percy] Finalized build #1: https://percy.io/[your-project]
194
+ [percy] Done!
195
+ ```
196
+
197
+ Refer to docs here: [Percy on Automate](https://www.browserstack.com/docs/percy/integrate/functional-and-visual)
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-beta.0'.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.pre.beta.0
4
+ version: 1.1.3.pre.beta.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Perceptual Inc.