capybara-experience 0.3.3 → 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: e02e512f097c443001b574db746c0d6e5922b82637a2a6c64cf2b2a995799360
4
- data.tar.gz: 6ea77b1f4fab8368b9076583e68b31e983f518ab1e756455b4085230a8adc654
3
+ metadata.gz: a53a92480b4d79fbf507661277b1c0cfa31379910365cf770ee299875141d68a
4
+ data.tar.gz: d782f04061591b1923d046fd0300a470751fa2a58b273a079b5b93dbcb9b5b33
5
5
  SHA512:
6
- metadata.gz: c6eba6b3d672f4b7d9fb8196b26c65933ad971fe627f98d5b867e512e945933bb93e0d72fd3639c562e4a12af70d20ba41968f43f4be0ef1dbe083fbc181f4c6
7
- data.tar.gz: b65f2ccf7c79ef6351298682502e8ec502d0084f6043dfe66ae0a73464ec863c647843645246b53ea6ec68a83ea03aeaad3d79c998395267b86a68e4c7b47394
6
+ metadata.gz: cd3a3f5bd81acb16ec0e449f04bb8ea0d71c66d15d382216a927823d145bd1fa8f39d55d6f868250ef65b42f32451a158e5f6c9898225ad554a7f1fd3907f610
7
+ data.tar.gz: 7ada0747083edb9c37edf820045f963f24dc39b5b769d6e5a79808baeadadcf91d4023564632e29ad72fc3d91850cb818ed60cdf1b1ec234660b37bf627f7893
data/CLAUDE.md ADDED
@@ -0,0 +1,29 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What This Is
6
+
7
+ capybara-experience is a Ruby gem that extends Capybara with session pooling, multi-user experience objects, and a behavior DSL for feature tests. It lets tests instantiate multiple `Capybara::Experience` objects that each get their own browser session from a shared pool, enabling tests that compare different user journeys side by side.
8
+
9
+ ## Commands
10
+
11
+ - **Run all tests:** `bundle exec rake` or `bundle exec rspec`
12
+ - **Run a single test:** `bundle exec rspec spec/capybara/experience_spec.rb`
13
+ - **Install deps:** `bundle install`
14
+ - **Build gem:** `bundle exec rake build`
15
+ - **Release gem:** `bundle exec rake release`
16
+
17
+ ## Architecture
18
+
19
+ Three core components, all under `Capybara::Experience`:
20
+
21
+ **`Experience`** (`lib/capybara/experience.rb`) — Includes `Capybara::DSL`. Each instance lazily acquires a session from the Pool via `page`. The `UnifySessionPool` module is prepended onto `Capybara`'s singleton class to route Capybara's global `session_pool` through `Experience::Pool`, unifying session management. Integrates with `capybara-screenshot` by setting `final_session_name`.
22
+
23
+ **`Pool`** (`lib/capybara/experience/pool.rb`) — Singleton that extends `Hash`. Manages session lifecycle: `take` returns an idle session matching the driver+app or creates a new one; `reset_idle!` marks all sessions as reusable. Sessions are keyed as `"driver:object_id:app_object_id"`. The `[]` override auto-creates sessions on cache miss, which is how Capybara's `session_pool` integration works.
24
+
25
+ **`BehaviorDSL`** (`lib/capybara/experience/rspec.rb`) — RSpec helper that provides `behavior` blocks for grouping steps within a single `it` block. Dynamically updates RSpec metadata so the test runner shows the current behavior name. Also registers an `after(:each)` hook on `type: :feature, js: true` tests to call `Pool#reset_idle!`.
26
+
27
+ ## Test Setup
28
+
29
+ Tests use Capybara's built-in `TestApp` (Sinatra) as the rack app. A custom `:javascript_test` driver is registered as a RackTest driver to test driver-switching logic without requiring a real browser. The single feature spec covers pool allocation, session reuse across reset cycles, and driver switching.
data/Gemfile CHANGED
@@ -2,3 +2,5 @@ source "https://rubygems.org"
2
2
 
3
3
  # Specify your gem's dependencies in capybara-experience.gemspec
4
4
  gemspec
5
+
6
+ gem "pry-byebug"
data/README.md CHANGED
@@ -29,9 +29,51 @@ Or install it yourself as:
29
29
 
30
30
  ## Usage
31
31
 
32
- ### Basic
32
+ ### Setup
33
+
34
+ #### RSpec
35
+ ```ruby
36
+ # spec/spec_helper.rb
37
+ require "capybara/experience"
38
+ require "capybara/experience/rspec"
39
+ ```
40
+
41
+ This automatically includes `Capybara::Experience::DSL` and `BehaviorDSL` in feature specs.
42
+
43
+ #### Minitest
44
+ ```ruby
45
+ # test/test_helper.rb
46
+ require "capybara/experience"
47
+ require "capybara/experience/minitest"
48
+ ```
49
+
50
+ Include the modules in your test class:
51
+ ```ruby
52
+ class ApplicationIntegrationTest < Minitest::Test
53
+ include Capybara::DSL
54
+ include Capybara::Minitest::Assertions
55
+ include Capybara::Experience::MinitestHooks # includes DSL and resets pool after each test
56
+ end
57
+ ```
58
+
59
+ ### Test Context & DSL
60
+
61
+ Experience instances accept a test context as their first argument, enabling assertions to be called from within Experience subclasses. When you subclass `Capybara::Experience`, a factory method is automatically added to `Capybara::Experience::DSL` that passes `self` as the test context:
62
+
63
+ ```ruby
64
+ class UserExperience < Capybara::Experience
65
+ # creates UserExperience() factory method on Capybara::Experience::DSL
66
+ end
67
+
68
+ # In a test:
69
+ ux = UserExperience() # equivalent to UserExperience.new(self)
70
+ ```
71
+
72
+ ### Basic Example
73
+
33
74
  This scenario is for your standard user/admin.
34
- 1. We need to create a file for each experience
75
+
76
+ 1. Create a file for each experience:
35
77
 
36
78
  ```ruby
37
79
  # spec/support/experiences/user_experience.rb
@@ -42,7 +84,8 @@ class UserExperience < Capybara::Experience
42
84
  @user = user
43
85
  login_as user, scope: :user
44
86
  visit '/'
45
- assert_text "#{@user.name} Welcome!"
87
+ # assertions work inside Experience when test context is provided
88
+ expect(page).to have_content("#{@user.name} Welcome!")
46
89
  self
47
90
  end
48
91
 
@@ -53,17 +96,13 @@ end
53
96
  ```
54
97
 
55
98
  ```ruby
56
- # spec/support/experiences/user_experience.rb
99
+ # spec/support/experiences/admin_experience.rb
57
100
  class AdminExperience < Capybara::Experience
58
- def initialize(*args)
59
- super
60
- end
61
-
62
101
  def login(user)
63
102
  @user = user
64
103
  login_as user, scope: :admin
65
104
  visit '/admin/login'
66
- assert_text "Home"
105
+ expect(page).to have_content("Home")
67
106
  end
68
107
 
69
108
  private
@@ -72,7 +111,7 @@ class AdminExperience < Capybara::Experience
72
111
  end
73
112
  ```
74
113
 
75
- 2. now we need to create some capabilities. These capability files associate to each page, imagine each capability is an api to a page.
114
+ 2. Create some capabilities. These capability files associate to each page, imagine each capability is an api to a page.
76
115
 
77
116
  ```ruby
78
117
  # spec/support/capabilities/sign_up.rb
@@ -86,21 +125,19 @@ module Capabilities::SignUp
86
125
  fill_in "password", with: password
87
126
  fill_in "password_confirmation", with: password
88
127
  click_button "Submit"
89
- assert_text "Welcome #{email}"
128
+ expect(page).to have_content("Welcome #{email}")
90
129
  end
91
130
  end
92
131
  ```
93
132
 
94
- 3. write the spec
133
+ 3. Write the spec using the DSL factory methods:
134
+
95
135
  ```ruby
96
136
  # spec/features/sign_up_flow_spec.rb
97
- RSpec.describe "sign up flow", feature: true do
98
- let(:guest_experience) { GuestExperience.new.extend(Capabilities::SignUp) }
99
- let(:admin_experience) { AdminExperience.new.extend(Capabilities::Admin::ManageUser) }
100
-
137
+ RSpec.describe "sign up flow", type: :feature do
101
138
  it "works" do
102
- behavior "user can sign up" do # behaviors are an added DSL by capybara-experiences to group interactions & assertions
103
- guest_ux = GuestExperience.new
139
+ behavior "user can sign up" do
140
+ guest_ux = GuestExperience() # factory method passes self as test context
104
141
  guest_ux.navigate_to_sign_up
105
142
 
106
143
  guest_ux.sign_up(
@@ -112,9 +149,9 @@ RSpec.describe "sign up flow", feature: true do
112
149
  end
113
150
 
114
151
  behavior "admin can see user" do
115
- admin_ux = AdminExperience.new
152
+ admin_ux = AdminExperience()
116
153
  admin_ux.login
117
- admin_ux.to have_content "user@example.com"
154
+ expect(admin_ux).to have_content "user@example.com"
118
155
  end
119
156
  end
120
157
  end
@@ -28,6 +28,8 @@ Gem::Specification.new do |spec|
28
28
  spec.add_development_dependency "bundler", "~> 2.0"
29
29
  spec.add_development_dependency "rake", "~> 10.0"
30
30
  spec.add_development_dependency "rspec", "~> 3.0"
31
+ spec.add_development_dependency "sinatra", ">= 3.0.2"
32
+ spec.add_development_dependency "minitest"
31
33
 
32
34
  spec.add_runtime_dependency "capybara", "~> 3.0"
33
35
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+ require "capybara/minitest"
3
+
4
+ module Capybara
5
+ class Experience
6
+ module MinitestContext
7
+ Capybara::Minitest::Assertions.instance_methods(false).each do |method_name|
8
+ define_method(method_name) do |*args, **kwargs, &block|
9
+ test_context.send(method_name, page, *args, **kwargs, &block)
10
+ end
11
+ end
12
+ end
13
+
14
+ module MinitestBehaviorDSL
15
+ def behavior(name)
16
+ @_behavior_stack ||= []
17
+ @_behavior_stack.push(name)
18
+ yield
19
+ ensure
20
+ @_behavior_stack.pop
21
+ end
22
+
23
+ def current_behavior
24
+ @_behavior_stack ||= []
25
+ @_behavior_stack.join(" ")
26
+ end
27
+ end
28
+
29
+ module MinitestHooks
30
+ include Capybara::Experience::DSL
31
+ include Capybara::Experience::MinitestBehaviorDSL
32
+
33
+ def after_teardown
34
+ Capybara::Experience::Pool.instance.reset_idle!
35
+ super
36
+ end
37
+ end
38
+
39
+ include MinitestContext
40
+ end
41
+ end
@@ -9,7 +9,7 @@ module Capybara
9
9
  def [](key)
10
10
  super(key) || begin
11
11
  driver, _session_name, app_object_id = key.split(":")
12
- take(driver: driver, app_object_id: app_object_id, key: key)
12
+ take(driver: driver.to_sym, app_object_id: app_object_id.to_i, key: key)
13
13
  end
14
14
  end
15
15
 
@@ -27,6 +27,8 @@ module Capybara
27
27
  session
28
28
  end
29
29
 
30
+ attr_accessor :last_session
31
+
30
32
  def idle
31
33
  @idle ||= []
32
34
  end
@@ -36,10 +38,12 @@ module Capybara
36
38
  end
37
39
 
38
40
  def reset_idle!
39
- to_h.each do |key, session|
40
- self[session_key(session)] = delete(key)
41
+ new_hash = each_with_object({}) do |(key, session), hash|
42
+ hash[session_key(session)] = delete(key)
41
43
  end
44
+ replace(new_hash)
42
45
  @idle = values
46
+ @last_session = nil
43
47
 
44
48
  nil
45
49
  end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "capybara/experience"
4
+
5
+ if defined?(RSpec)
6
+ require "capybara/experience/rspec"
7
+ elsif defined?(Minitest)
8
+ require "capybara/experience/minitest"
9
+ end
10
+
11
+ module Capybara
12
+ class Experience
13
+ module ScreenshotOverride
14
+ def page
15
+ @_capybara_experience_page_override || super
16
+ end
17
+
18
+ private
19
+
20
+ def take_failed_screenshot
21
+ last_session = Experience::Pool.instance.last_session
22
+ return super unless last_session
23
+
24
+ @_capybara_experience_page_override = last_session
25
+ super
26
+ ensure
27
+ @_capybara_experience_page_override = nil
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ if defined?(ActiveSupport)
34
+ ActiveSupport.on_load(:action_dispatch_system_test_case) do
35
+ ActionDispatch::SystemTestCase.prepend(Capybara::Experience::ScreenshotOverride)
36
+ end
37
+ end
@@ -15,7 +15,9 @@ module Capybara
15
15
 
16
16
  def refresh_description
17
17
  metadata[:description] = metadata[:description_args].join(" ")
18
- metadata[:full_description] = [metadata[:example_group][:full_description]].concat(metadata[:description_args]).join(" ")
18
+ metadata[:full_description] =
19
+ [metadata[:example_group][:full_description]]
20
+ .concat(metadata[:description_args]).join(" ")
19
21
  end
20
22
 
21
23
  def metadata
@@ -23,13 +25,28 @@ module Capybara
23
25
  end
24
26
 
25
27
  def in_continuous_integration_env?
26
- ENV["CI"].present?
28
+ ENV["CI"]&.empty?
27
29
  end
28
30
  end
29
31
 
32
+ module RSpecContext
33
+ def expect(...)
34
+ test_context.expect(...)
35
+ end
36
+
37
+ Capybara::RSpecMatchers.instance_methods(false).each do |method_name|
38
+ define_method(method_name) do |*args, **kwargs, &block|
39
+ test_context.send(method_name, *args, **kwargs, &block)
40
+ end
41
+ end
42
+ end
43
+
44
+ include RSpecContext
45
+
30
46
  RSpec.configure do |config|
31
47
  config.include BehaviorDSL
32
- config.prepend_after :each, type: :feature, js: true do
48
+ config.include Capybara::Experience::DSL, type: :feature
49
+ config.after :each, type: :feature, js: true do
33
50
  Capybara::Experience::Pool.instance.reset_idle!
34
51
  end
35
52
  end
@@ -1,5 +1,5 @@
1
1
  module Capybara
2
2
  class Experience
3
- VERSION = "0.3.3"
3
+ VERSION = "0.4.0"
4
4
  end
5
5
  end
@@ -6,12 +6,19 @@ module Capybara
6
6
  class Experience
7
7
  include Capybara::DSL
8
8
 
9
- delegate :t, to: I18n if defined?(I18n)
9
+ if defined?(I18n)
10
+ def t(*arg)
11
+ I18n.t(*args)
12
+ end
13
+ end
10
14
 
11
- def initialize(driver_name: nil)
15
+ def initialize(test_context = nil, driver_name: nil)
16
+ @test_context = test_context
12
17
  @driver_name = driver_name
13
18
  end
14
19
 
20
+ attr_reader :test_context
21
+
15
22
  def reload_page
16
23
  visit current_url
17
24
  end
@@ -20,11 +27,14 @@ module Capybara
20
27
  @driver_name ||= Capybara.current_driver
21
28
  end
22
29
 
23
- delegate :driver, to: :page
30
+ def driver
31
+ page.driver
32
+ end
24
33
 
25
34
  def page
26
35
  @page ||= Experience::Pool.instance.take(driver: driver_name)
27
36
 
37
+ Experience::Pool.instance.last_session = @page
28
38
  Capybara::Screenshot.final_session_name = @page.object_id if defined?(Capybara::Screenshot)
29
39
 
30
40
  @page
@@ -36,13 +46,27 @@ module Capybara
36
46
  end
37
47
  end
38
48
 
49
+ module DSL
50
+ end
51
+
52
+ def self.inherited(subclass)
53
+ super
54
+ return unless subclass.name
55
+
56
+ method_name = subclass.name.split("::").last
57
+ subclass_ref = subclass
58
+ DSL.define_method(method_name) do |**kwargs|
59
+ subclass_ref.new(self, **kwargs)
60
+ end
61
+ end
62
+
39
63
  class Error < StandardError; end
40
64
 
41
65
  module UnifySessionPool
42
66
  def page
43
- Capybara::Screenshot.final_session_name = Capybara.session_name if defined?(Capybara::Screenshot)
44
-
45
- super
67
+ Experience::Pool.instance.last_session = super.tap do
68
+ Capybara::Screenshot.final_session_name = Capybara.session_name if defined?(Capybara::Screenshot)
69
+ end
46
70
  end
47
71
 
48
72
  private
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: capybara-experience
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Ong
8
8
  - Britt Lewis
9
- autorequire:
10
9
  bindir: exe
11
10
  cert_chain: []
12
- date: 2022-09-01 00:00:00.000000000 Z
11
+ date: 1980-01-02 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: bundler
@@ -53,6 +52,34 @@ dependencies:
53
52
  - - "~>"
54
53
  - !ruby/object:Gem::Version
55
54
  version: '3.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sinatra
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: 3.0.2
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: 3.0.2
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
56
83
  - !ruby/object:Gem::Dependency
57
84
  name: capybara
58
85
  requirement: !ruby/object:Gem::Requirement
@@ -79,6 +106,7 @@ files:
79
106
  - ".gitignore"
80
107
  - ".rspec"
81
108
  - ".travis.yml"
109
+ - CLAUDE.md
82
110
  - Gemfile
83
111
  - README.md
84
112
  - Rakefile
@@ -87,7 +115,9 @@ files:
87
115
  - capybara-experience.gemspec
88
116
  - lib/capybara-experience.rb
89
117
  - lib/capybara/experience.rb
118
+ - lib/capybara/experience/minitest.rb
90
119
  - lib/capybara/experience/pool.rb
120
+ - lib/capybara/experience/rails.rb
91
121
  - lib/capybara/experience/rspec.rb
92
122
  - lib/capybara/experience/version.rb
93
123
  homepage: https://github.com/ryanong/capybara-experience
@@ -95,7 +125,6 @@ licenses: []
95
125
  metadata:
96
126
  homepage_uri: https://github.com/ryanong/capybara-experience
97
127
  source_code_uri: https://github.com/ryanong/capybara-experience
98
- post_install_message:
99
128
  rdoc_options: []
100
129
  require_paths:
101
130
  - lib
@@ -110,8 +139,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
110
139
  - !ruby/object:Gem::Version
111
140
  version: '0'
112
141
  requirements: []
113
- rubygems_version: 3.3.17
114
- signing_key:
142
+ rubygems_version: 4.0.6
115
143
  specification_version: 4
116
144
  summary: Simplify Capybara testing for larger applications
117
145
  test_files: []