capybara-storyboard 0.3.0 → 0.4.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: ad9e5b4eaf1c9021974e3b98572e5df40de5cd7d9a76a4596d1d1498a5b1ba6e
4
- data.tar.gz: d43fd39b24c6501f2e22f89c63e83df203da15cc111b4812b520727fa26280e7
3
+ metadata.gz: 3bf6a851426d1aa00542d1d81071115072cd603519243887686156f178c62d57
4
+ data.tar.gz: 145f880b26d5d6ae6b8042f2434b398bb018f7d0b646cc5fc0922cd9ed25d509
5
5
  SHA512:
6
- metadata.gz: 83807f656992133cd59d9602f2a1574e9d018d69e63a69010b01dc28ba0bcc27de48d464fe10e36eb1e60ab914eae6b9725d226735b4b3266cf39c983c24d0ce
7
- data.tar.gz: 513c4e0147ef78eee5a5e36c720ea428b8a8ee9ebbf885c31201438d3d12b8bdbc2bc495a3e0fd6e8c58c2458ba49fc7ddd3d8350f59d5ead1b5f9e019953afc
6
+ metadata.gz: b73fed754aafe430f72452947b62f3ebb50480b3deeeea2c3dff00f6296d17297eaa96004c94d12908838dedbde58ac5ff966db331003e628fc3b639d9312129
7
+ data.tar.gz: 6f61ca195317e9c69f7cd476e158d55b48f86fc1d072a60829a93a780527176d94d0a5c1bf44cb5137e8ddbd6739cd7f4081d12f84c850e9991bcd39b6ac4a6f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2026-08-12
4
+
5
+ - Replace wholly-numeric path segments in `visit` paths with `N` in screenshot filenames
6
+ (e.g. `visit '/observations/20/edit'` now produces `visit_observations_N_edit.png`), so
7
+ varying record IDs no longer break visual-regression pairing between runs. Migration note:
8
+ filenames change for numeric-ID visits, so the first run after upgrading shows one round of
9
+ added/deleted images when compared against an older baseline.
10
+
3
11
  ## [0.3.0] - 2026-07-23
4
12
 
5
13
  - Fix `NoMethodError` / spurious "skipped after error" warnings when a page navigation happens between setup and a stability poll, by treating non-finite poll results (nil/NaN/Infinity) as "measurement lost" and re-arming the observer
data/README.md CHANGED
@@ -171,6 +171,17 @@ order in which actions occurred. For example:
171
171
  Non-ASCII descriptions and labels (e.g. Japanese) are preserved as-is in file and directory
172
172
  names; only symbols and whitespace are replaced with underscores.
173
173
 
174
+ For `visit`, wholly-numeric path segments are additionally replaced with `N` before
175
+ sanitization, e.g. `visit '/observations/20/edit'` produces `001_visit_observations_N_edit.png`
176
+ rather than embedding the `20`. Record IDs vary between test runs and between branches, and a
177
+ filename that varies with them stops a visual-regression tool from pairing the same screen
178
+ across two runs, which reports it as added + deleted instead of comparing it. This only
179
+ replaces path segments that are *entirely* digits (`user42` and `h2` are left as-is), only
180
+ applies to `visit`, and only touches the path — the query string and fragment are untouched
181
+ (`/search?id=20` stays `search_id_20`). Locators passed to `click_on`, `fill_in`, and friends
182
+ keep their digits. If you want a specific ID to appear in a filename anyway, use
183
+ `storyboard_screenshot(label)` to build the label yourself.
184
+
174
185
  The default output root is `<Rails.root>/tmp/screenshots` (overridable, see
175
186
  [Configuration](#configuration)).
176
187
 
@@ -96,7 +96,10 @@ The compare job uploads two artifacts:
96
96
  output and would cause over-selection.
97
97
  - A large batch of "added"/"deleted" images clustered under one example usually means a
98
98
  Capybara step was inserted or removed in that example, shifting every later step's `NNN`
99
- sequence number — not many independent regressions.
99
+ sequence number — not many independent regressions. Numeric `visit` path segments are
100
+ already normalized to `N` by capybara-storyboard, so a differing record ID there is not a
101
+ cause — a shifted `NNN` is usually the remaining explanation. (An ID in a query string, as
102
+ in `/search?user_id=42`, is not normalized and can still shift a filename.)
100
103
  - Diff images are linked via artifact URLs rather than embedded in the PR comment, because
101
104
  GitHub's Markdown image embedding requires a URL its camo proxy can fetch unauthenticated,
102
105
  and artifact URLs require authentication.
@@ -12,6 +12,9 @@ module Capybara
12
12
  # of Capybara and RSpec hooks: a Session can be built with a plain example
13
13
  # double and an injected +output_root+.
14
14
  class Session
15
+ PATH_ID_PLACEHOLDER = 'N'
16
+ private_constant :PATH_ID_PLACEHOLDER
17
+
15
18
  def initialize(example:, enabled:, output_root: nil)
16
19
  @example = example
17
20
  @enabled = enabled
@@ -34,6 +37,31 @@ module Capybara
34
37
  capture_with_label(page, label)
35
38
  end
36
39
 
40
+ # Automatic screenshot for a path-shaped DSL action (currently only
41
+ # #visit). Same as #auto except wholly-numeric path segments in the detail
42
+ # are collapsed to N first, so /observations/20/edit and
43
+ # /observations/33/edit yield one stable filename. Record ids vary between
44
+ # runs and between branches; a varying filename stops a visual-regression
45
+ # tool from pairing the two sides by path, which reports the same screen
46
+ # as added + deleted instead of comparing it.
47
+ #
48
+ # Deliberately a separate entry point rather than a branch inside #auto:
49
+ # Session stays ignorant of which action names are path-shaped, and the
50
+ # caller declares it by choosing the method (as with #auto vs #manual).
51
+ # Locators passed to click_link/fill_in/... are human-written fixed
52
+ # strings, so they keep their digits and keep using #auto.
53
+ #
54
+ # The enabled/suppressed guards are repeated here rather than left to
55
+ # #auto because Ruby evaluates arguments before the call: without them,
56
+ # every visit in a disabled suite would normalize a path only for #auto to
57
+ # discard it, breaking the "disabled -> zero overhead" contract.
58
+ def auto_path(page, action, path)
59
+ return unless @enabled
60
+ return if suppressed?
61
+
62
+ auto(page, action, normalize_path_ids(path))
63
+ end
64
+
37
65
  # Manual screenshot hook. Like #auto, captured only when enabled. For an
38
66
  # unconditional screenshot, use Capybara's own save_screenshot. The page
39
67
  # is passed explicitly rather than held as state.
@@ -144,6 +172,30 @@ module Capybara
144
172
  def sanitize(text)
145
173
  text.to_s.gsub(/[^\p{Word}-]/, '_').gsub(/_+/, '_').gsub(/\A_|_\z/, '')
146
174
  end
175
+
176
+ # Replaces every wholly-numeric path segment with N (/observations/20/edit
177
+ # -> /observations/N/edit). Segment-based on purpose: only a segment that
178
+ # is *nothing but* digits is a record id, so user42, h2 and v2 are left
179
+ # alone.
180
+ #
181
+ # The query/fragment is split off and left untouched — an id there sits in
182
+ # a value (?id=20), not a segment, and rewriting it would need parameter
183
+ # parsing for no gain. Splitting on / with -1 keeps empty segments, so a
184
+ # trailing slash and a full URL's // both survive round-tripping (the host
185
+ # segment of http://example.com/x/20 is non-numeric, so a full URL needs
186
+ # no special case).
187
+ #
188
+ # Runs BEFORE #sanitize, which would otherwise collapse / into _ and make
189
+ # segment boundaries unrecoverable. \A\d+\z is ASCII-only by design: URL
190
+ # path ids are ASCII digits, and preserving Unicode is #sanitize's job.
191
+ def normalize_path_ids(path)
192
+ head, separator, tail = path.to_s.partition(/[?#]/)
193
+ normalized =
194
+ head.split('/', -1).map do |segment|
195
+ segment.match?(/\A\d+\z/) ? PATH_ID_PLACEHOLDER : segment
196
+ end
197
+ "#{normalized.join('/')}#{separator}#{tail}"
198
+ end
147
199
  end
148
200
  end
149
201
  end
@@ -19,7 +19,7 @@ module Capybara
19
19
  end
20
20
 
21
21
  def visit(path, ...)
22
- super.tap { @__storyboard.auto(page, 'visit', path) }
22
+ super.tap { @__storyboard.auto_path(page, 'visit', path) }
23
23
  end
24
24
 
25
25
  def click_on(locator = nil, ...)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Capybara
4
4
  module Storyboard
5
- VERSION = '0.3.0'
5
+ VERSION = '0.4.0'
6
6
  end
7
7
  end
@@ -233,7 +233,9 @@ npx reg-cli tmp/vrt/after tmp/vrt/before tmp/vrt/diff \
233
233
  directory). `1` means "there's a diff, go verify it," not failure.
234
234
  - Since both sets ran the same spec, filenames match across the two sets and reg-cli pairs them
235
235
  automatically by path. Images present on only one side are reported as added/deleted — this
236
- happens when the diff changed the spec's own steps.
236
+ happens when the diff changed the spec's own steps (record IDs in `visit` **path segments** are
237
+ already normalized to `N` by the gem, so a differing ID there is not a cause — but an ID in a
238
+ query string, as in `/search?user_id=42`, is not normalized and can still shift a filename).
237
239
 
238
240
  ### 5. Verify the detected diffs
239
241
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-storyboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - aki