booth 0.0.3 → 0.0.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +3 -0
  3. data/app/assets/images/booth/fido/passkey_mark_b_reverse.svg +55 -0
  4. data/config/locales/de.yml +5 -5
  5. data/lib/booth/core/sessions/to_passport.rb +11 -1
  6. data/lib/booth/core/webauth/authentication_verification.rb +5 -5
  7. data/lib/booth/models/authenticator.rb +11 -0
  8. data/lib/booth/passport.rb +15 -0
  9. data/lib/booth/requests/authentication.rb +1 -1
  10. data/lib/booth/requests/storages/login.rb +4 -1
  11. data/lib/booth/requests/sudo.rb +12 -2
  12. data/lib/booth/test.rb +10 -17
  13. data/lib/booth/testing/incorporation_test_case.rb +3 -1
  14. data/lib/booth/testing/shortcuts.rb +8 -17
  15. data/lib/booth/testing/support/assert_logged_in.rb +26 -27
  16. data/lib/booth/testing/support/assert_logged_out.rb +15 -11
  17. data/lib/booth/testing/support/assert_partial.rb +20 -29
  18. data/lib/booth/testing/support/capybara_step_logger.rb +22 -0
  19. data/lib/booth/testing/support/{soft_reset_session.rb → clear_cookies.rb} +5 -6
  20. data/lib/booth/testing/support/cookie_data_from_browser.rb +38 -0
  21. data/lib/booth/testing/support/shortcuts/create_and_onboard.rb +4 -0
  22. data/lib/booth/testing/support/shortcuts/login_with_passkey.rb +6 -4
  23. data/lib/booth/testing/support/shortcuts/register_new_passkey.rb +6 -2
  24. data/lib/booth/testing/support/virtual_authenticator.rb +196 -0
  25. data/lib/booth/testing/support/virtual_authenticators/create.rb +22 -7
  26. data/lib/booth/testing/support/virtual_authenticators/destroy.rb +14 -9
  27. data/lib/booth/testing/support/virtual_authenticators/enable.rb +14 -2
  28. data/lib/booth/testing/support/virtual_authenticators/load.rb +7 -19
  29. data/lib/booth/testing/support/virtual_authenticators.rb +106 -0
  30. data/lib/booth/testing/support/visit.rb +1 -0
  31. data/lib/booth/testing/userland/login_remotely.rb +2 -2
  32. data/lib/booth/testing/userland/onboarding_first_time.rb +5 -4
  33. data/lib/booth/testing/userland/onboarding_to_reset_passkeys.rb +3 -3
  34. data/lib/booth/testing/userland/registration_with_passkey.rb +9 -6
  35. data/lib/booth/testing/userland/registration_without_passkey.rb +11 -7
  36. data/lib/booth/testing/userland/sessions_manage_behavior.rb +14 -3
  37. data/lib/booth/userland/webauths/index.rb +9 -3
  38. data/lib/booth/userland/webauths/new.rb +10 -2
  39. data/lib/booth/userland/webauths/transitions/create/choose_nickname.rb +1 -1
  40. data/lib/booth/userland/webauths/transitions/sudo/authentication_initiation.rb +5 -0
  41. data/lib/booth/userland/webauths/transitions/sudo/authentication_verification.rb +1 -1
  42. data/lib/booth/version.rb +1 -1
  43. metadata +8 -4
  44. data/lib/booth/testing/support/get_session_value.rb +0 -37
  45. data/lib/booth/testing/support/virtual_authenticators/manager.rb +0 -124
@@ -7,6 +7,8 @@ module Booth
7
7
  class LoginWithPasskey
8
8
  include ::Calls
9
9
  include ::Capybara::DSL
10
+ include ::Booth::Testing::Support::CapybaraStepLogger
11
+ include ::Booth::Testing::Shortcuts
10
12
  include ::Booth::Logging
11
13
 
12
14
  option :routing_namespace
@@ -14,7 +16,8 @@ module Booth
14
16
  option :username
15
17
 
16
18
  def call
17
- log { 'Initiating Test Shortcut for logging in with passkey' }
19
+ log { Paint['Starting Subroutine LoginWithPasskey', '#ff9900'] }
20
+
18
21
  ::Booth::Testing::Support::Visit.call(routing_namespace:,
19
22
  controller: :logins,
20
23
  action: :new)
@@ -39,12 +42,11 @@ module Booth
39
42
  ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
40
43
  controller: :logins,
41
44
  step: :enter_webauth)
42
-
43
45
  click_on :authenticate
44
46
 
45
- AssertLoggedIn.call(scope:, username:)
47
+ ::Booth::Testing::Support::AssertLoggedIn.call(scope:, username:)
46
48
 
47
- log { 'Shortcut to login with passkey succeeded' }
49
+ log { Paint['Finished Subroutine LoginWithPasskey', '#ff9900'] }
48
50
 
49
51
  nil
50
52
  end
@@ -7,12 +7,16 @@ module Booth
7
7
  class RegisterNewPasskey
8
8
  include ::Calls
9
9
  include ::Capybara::DSL
10
+ include ::Booth::Logging
11
+ include ::Booth::Testing::Support::CapybaraStepLogger
10
12
 
11
13
  option :routing_namespace
12
14
  option :scope
13
15
  option :username
14
16
 
15
17
  def call
18
+ log { 'Starting Subroutine...' }
19
+
16
20
  ::Booth::Testing::Support::Visit.call(routing_namespace:,
17
21
  controller: :webauths,
18
22
  action: :new)
@@ -26,8 +30,8 @@ module Booth
26
30
  ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
27
31
  controller: :webauths,
28
32
  step: :choose_nickname)
29
-
30
- fill_in :nickname, with: 'Latchkey'
33
+ sleep 1
34
+ fill_in :nickname, with: 'Pristine Key'
31
35
  click_on :submit
32
36
 
33
37
  ::Booth::Testing::Support::AssertPartial.call(namespace: :userland,
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ # Represents a virtual WebAuthn credential that can be transferred between browser sessions.
7
+ # Stores the credential data (including private key) and tracks which authenticator ID
8
+ # exists in each browser session.
9
+ class VirtualAuthenticator
10
+ # Maps Capybara session names to their authenticator IDs
11
+ # Each browser session has its own isolated authenticator with the same credential
12
+ attr_accessor :authenticator_ids
13
+ attr_accessor :credential_data, :has_user_verification, :rp_id # Relying Party ID for this credential
14
+
15
+ def initialize(authenticator_id:, has_user_verification: true)
16
+ @authenticator_ids = {}
17
+ @has_user_verification = has_user_verification
18
+ @credential_data = nil
19
+ # Register the initial authenticator for the current session
20
+ register_authenticator_id(authenticator_id, Capybara.session_name.to_s)
21
+ end
22
+
23
+ # Registers an authenticator ID for a specific browser session
24
+ def register_authenticator_id(authenticator_id, session_name = Capybara.session_name.to_s)
25
+ authenticator_ids[session_name] = authenticator_id
26
+ end
27
+
28
+ # Gets the authenticator ID for the current browser session
29
+ def current_authenticator_id
30
+ session_key = Capybara.session_name.to_s
31
+ authenticator_ids[session_key] || raise("No authenticator registered for session #{session_key}. Call import() first if this is a new browser session.")
32
+ end
33
+
34
+ # Pulls credential data from the current browser session.
35
+ # On first pull (after WebAuthn registration), this captures the private key.
36
+ # On subsequent pulls, this updates the stored sign_count.
37
+ def pull
38
+ auth_id = current_authenticator_id
39
+ log do
40
+ "Pulling credentials from session #{Capybara.session_name} for authenticator #{auth_id}"
41
+ end
42
+
43
+ credentials = get_credentials_from_session(auth_id)
44
+
45
+ if credentials.empty?
46
+ raise "No credentials found in authenticator #{auth_id}. Ensure WebAuthn registration completed before pulling."
47
+ end
48
+
49
+ # Store the first (and typically only) credential
50
+ @credential_data = credentials.first
51
+
52
+ # rpId might not be returned by getCredentials in imported credentials
53
+ # Use stored value if available, otherwise use what we got from browser
54
+ pulled_rp_id = @credential_data['rpId']
55
+ if pulled_rp_id
56
+ @rp_id = pulled_rp_id
57
+ log do
58
+ "Pulled credential #{credential_id} with sign_count #{sign_count} and rpId #{@rp_id}"
59
+ end
60
+ elsif @rp_id
61
+ log do
62
+ "Pulled credential #{credential_id} with sign_count #{sign_count} (rpId not returned, using stored: #{@rp_id})"
63
+ end
64
+ else
65
+ raise "PULL FAILED: No rpId found in credential data and none stored! Keys: #{@credential_data.keys.inspect}"
66
+ end
67
+
68
+ log { "PUSH: Credential data keys: #{@credential_data.keys.inspect}" }
69
+
70
+ self
71
+ end
72
+
73
+ # Pushes the stored sign_count to the current browser session.
74
+ # Since WebAuthn.setCredentialProperties doesn't support signCount,
75
+ # we remove and re-add the credential with the correct sign_count.
76
+ def push
77
+ unless credential_data
78
+ raise 'No credential data to push. Call pull() first to capture credentials.'
79
+ end
80
+
81
+ auth_id = current_authenticator_id
82
+ target_sign_count = sign_count
83
+
84
+ # First, read current state from browser
85
+ current_creds = get_credentials_from_session(auth_id)
86
+ target_cred = current_creds.find { |c| c['credentialId'] == credential_id }
87
+
88
+ if target_cred.nil?
89
+ raise "Credential #{credential_id} not found in browser session #{Capybara.session_name}. Import this authenticator first."
90
+ end
91
+
92
+ current_browser_sign_count = target_cred['signCount']
93
+
94
+ log do
95
+ "PUSH: Session #{Capybara.session_name}, Auth #{auth_id}, Credential #{credential_id}, Target sign_count: #{target_sign_count}, Current browser sign_count: #{current_browser_sign_count}"
96
+ end
97
+
98
+ if current_browser_sign_count == target_sign_count
99
+ log { "PUSH: Sign count already up to date (#{target_sign_count})" }
100
+ return self
101
+ end
102
+
103
+ # Remove and re-add credential with updated sign_count
104
+ # (setCredentialProperties doesn't support signCount per CDP spec)
105
+ log { "PUSH: Removing credential #{credential_id} from authenticator #{auth_id}" }
106
+ remove_result = send_cdp_message(
107
+ 'WebAuthn.removeCredential',
108
+ authenticatorId: auth_id,
109
+ credentialId: credential_id,
110
+ )
111
+ log { "PUSH: removeCredential result: #{remove_result.inspect}" }
112
+
113
+ log { "PUSH: Re-adding credential with sign_count #{target_sign_count}" }
114
+ # Create credential data with updated sign_count
115
+ updated_credential = credential_data.dup
116
+ updated_credential['signCount'] = target_sign_count
117
+ updated_credential['rpId'] = @rp_id
118
+
119
+ # Debug: show what fields are in the credential
120
+ log { "PUSH: Credential fields: #{updated_credential.keys.inspect}" }
121
+ log { "PUSH: rpId in credential: #{updated_credential['rpId'].inspect}" }
122
+ log { "PUSH: @rp_id instance variable: #{@rp_id.inspect}" }
123
+
124
+ raise 'PUSH FAILED: @rp_id is nil!' unless @rp_id
125
+
126
+ add_result = send_cdp_message(
127
+ 'WebAuthn.addCredential',
128
+ authenticatorId: auth_id,
129
+ credential: updated_credential,
130
+ )
131
+ log { "PUSH: addCredential result: #{add_result.inspect}" }
132
+
133
+ # CRITICAL: Verify the update actually worked
134
+ sleep 0.5 # Give browser time to apply update
135
+ post_update_creds = get_credentials_from_session(auth_id)
136
+ post_update_cred = post_update_creds.find { |c| c['credentialId'] == credential_id }
137
+
138
+ if post_update_cred.nil?
139
+ raise "PUSH FAILED: Credential #{credential_id} not found after re-add!"
140
+ end
141
+
142
+ actual_sign_count = post_update_cred['signCount']
143
+ log do
144
+ "PUSH: Post-update browser sign_count: #{actual_sign_count} (expected: #{target_sign_count})"
145
+ end
146
+
147
+ unless actual_sign_count == target_sign_count
148
+ raise "PUSH FAILED: Browser sign_count is #{actual_sign_count} but we expected #{target_sign_count}"
149
+ end
150
+
151
+ log { "PUSH: Successfully updated sign_count to #{target_sign_count} in browser" }
152
+
153
+ self
154
+ end
155
+
156
+ # Returns the credential ID
157
+ def credential_id
158
+ credential_data&.dig('credentialId')
159
+ end
160
+
161
+ # Returns the current sign count
162
+ def sign_count
163
+ credential_data&.dig('signCount') || 0
164
+ end
165
+
166
+ # Returns the private key (base64 encoded)
167
+ def private_key
168
+ credential_data&.dig('privateKey')
169
+ end
170
+
171
+ private
172
+
173
+ include ::Booth::Logging
174
+ include ::Capybara::DSL
175
+
176
+ def get_credentials_from_session(authenticator_id)
177
+ result = send_cdp_message(
178
+ 'WebAuthn.getCredentials',
179
+ authenticatorId: authenticator_id,
180
+ )
181
+
182
+ result['credentials'] || []
183
+ end
184
+
185
+ def send_cdp_message(method, params)
186
+ result = nil
187
+ Capybara.current_session.driver.with_playwright_page do |playwright_page|
188
+ cdp_session = playwright_page.context.new_cdp_session(playwright_page)
189
+ result = cdp_session.send_message(method, params:)
190
+ end
191
+ result
192
+ end
193
+ end
194
+ end
195
+ end
196
+ end
@@ -13,19 +13,34 @@ module Booth
13
13
 
14
14
  def call
15
15
  log { "Adding Virtual Authenticator... #{options.as_json}" }
16
- page.driver.browser.add_virtual_authenticator(options)
16
+ result = nil
17
+ page.driver.with_playwright_page do |playwright_page|
18
+ cdp_session = playwright_page.context.new_cdp_session(playwright_page)
19
+ log { '[WebAuthn] Sending addVirtualAuthenticator command...' }
20
+ result = cdp_session.send_message('WebAuthn.addVirtualAuthenticator', params: { options: options })
21
+ log { "[WebAuthn] addVirtualAuthenticator result: #{result.inspect}" }
22
+ end
23
+ authenticator_id = result['authenticatorId']
24
+ if authenticator_id
25
+ log { "[WebAuthn] ✓ Virtual Authenticator created with ID: #{authenticator_id}" }
26
+ else
27
+ log { "[WebAuthn] ✗ FAILED to create Virtual Authenticator - no authenticatorId in response" }
28
+ end
29
+ authenticator_id
17
30
  end
18
31
 
19
32
  private
20
33
 
21
- # See `bundle open selenium-webdriver`
22
34
  # See https://chromedevtools.github.io/devtools-protocol/tot/WebAuthn/#type-VirtualAuthenticatorOptions
23
35
  def options
24
- ::Selenium::WebDriver::VirtualAuthenticatorOptions.new.tap do |instance|
25
- instance.user_verification = has_user_verification
26
- instance.user_verified = true
27
- # instance.attestation = 'direct' # or 'indirect'
28
- end
36
+ {
37
+ protocol: 'ctap2',
38
+ transport: 'internal',
39
+ hasResidentKey: true,
40
+ hasUserVerification: has_user_verification,
41
+ isUserVerified: true,
42
+ automaticPresenceSimulation: true
43
+ }
29
44
  end
30
45
  end
31
46
  end
@@ -2,17 +2,22 @@
2
2
 
3
3
  module Booth
4
4
  module Testing
5
- module VirtualAuthenticators
6
- class Destroy
7
- include ::Booth::Logging
8
- include Calls
5
+ module Support
6
+ module VirtualAuthenticators
7
+ class Destroy
8
+ include ::Booth::Logging
9
+ include ::Calls
10
+ include ::Capybara::DSL
9
11
 
10
- option :devtools
11
- option :id
12
+ option :authenticator_id
12
13
 
13
- def call
14
- log { "Removing Virtual Authenticator with ID #{id}" }
15
- devtools.send_cmd 'WebAuthn.removeVirtualAuthenticator', authenticatorId: id
14
+ def call
15
+ log { "Removing Virtual Authenticator with ID #{authenticator_id}" }
16
+ page.driver.with_playwright_page do |playwright_page|
17
+ cdp_session = playwright_page.context.new_cdp_session(playwright_page)
18
+ cdp_session.send_message('WebAuthn.removeVirtualAuthenticator', params: { authenticatorId: authenticator_id })
19
+ end
20
+ end
16
21
  end
17
22
  end
18
23
  end
@@ -13,9 +13,21 @@ module Booth
13
13
  def call
14
14
  log { 'Ensuring enabled Chrome Virtual Authenticator Environment...' }
15
15
  # The Environment *randomly* leaks from test to test, disabling it first works.
16
+ # This happens with both Selenium and Playwright.
16
17
  # All you'll see is `NotAllowedError` in the JS console if you miss this.
17
- page.driver.browser.devtools.send_cmd 'WebAuthn.disable'
18
- page.driver.browser.devtools.send_cmd 'WebAuthn.enable'
18
+ page.driver.with_playwright_page do |playwright_page|
19
+ cdp_session = playwright_page.context.new_cdp_session(playwright_page)
20
+
21
+ # log { '[WebAuthn] Disabling any existing environment...' }
22
+ # disable_result = cdp_session.send_message('WebAuthn.disable')
23
+ # log { "[Disable result: #{disable_result.inspect}" }
24
+
25
+ # log { '[WebAuthn] Enabling virtual authenticator environment...' }
26
+ cdp_session.send_message('WebAuthn.enable')
27
+ # log { "[Enable result: #{enable_result.inspect}" }
28
+
29
+ # log { '[WebAuthn] Virtual Authenticator Environment is ready' }
30
+ end
19
31
  end
20
32
  end
21
33
  end
@@ -9,27 +9,15 @@ module Booth
9
9
  include ::Capybara::DSL
10
10
  include ::Booth::Logging
11
11
 
12
- option :virtual_authenticator
12
+ option :authenticator_id
13
13
 
14
14
  def call
15
- credential = virtual_authenticator.credentials.first
16
-
17
- # p 'A' * 100
18
- # pp credential.instance_variable_get(:@sign_count)
19
- # p Booth::Models::Authenticator.sole.sign_count
20
- # p 'B' * 100
21
-
22
- instance = ::Booth::Testing::Support::VirtualAuthenticators::Create.call(
23
- has_user_verification: true,
24
- )
25
-
26
- log do
27
- "Attaching Virtual Authenticator Secrets to #{virtual_authenticator.instance_variable_get(:@id)}"
28
- end
29
- instance.add_credential(credential)
30
- # virtual_authenticator.instance_variable_set(:@id, instance.instance_variable_get(:@sign_count))
31
-
32
- instance
15
+ raise NotImplementedError, 'Load needs to be reimplemented for Playwright using CDP'
16
+ # Previously this loaded credentials from a Selenium virtual authenticator object
17
+ # and added them to a new authenticator. With CDP, we'd need to:
18
+ # 1. Get credentials from the source authenticator via WebAuthn.getCredentials
19
+ # 2. Create a new authenticator
20
+ # 3. Add the credentials via WebAuthn.addCredential
33
21
  end
34
22
  end
35
23
  end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Booth
4
+ module Testing
5
+ module Support
6
+ # Factory and operations for VirtualAuthenticator objects.
7
+ # Provides methods to create and import authenticators across browser sessions.
8
+ module VirtualAuthenticators
9
+ include ::Booth::Logging
10
+ include ::Capybara::DSL
11
+
12
+ # Creates a new virtual authenticator in the current browser session.
13
+ # Returns a VirtualAuthenticator object that can be used to pull credentials
14
+ # after WebAuthn registration.
15
+ def self.create(has_user_verification: true)
16
+ log { "Creating virtual authenticator in session #{Capybara.session_name}" }
17
+
18
+ ::Booth::Testing::Support::VirtualAuthenticators::Enable.call
19
+
20
+ result = send_cdp_message(
21
+ 'WebAuthn.addVirtualAuthenticator',
22
+ options: authenticator_options(has_user_verification:),
23
+ )
24
+
25
+ authenticator_id = result['authenticatorId']
26
+
27
+ unless authenticator_id
28
+ raise 'Failed to create virtual authenticator - no authenticatorId in response'
29
+ end
30
+
31
+ log { "Created virtual authenticator #{authenticator_id}" }
32
+
33
+ VirtualAuthenticator.new(
34
+ authenticator_id:,
35
+ has_user_verification:,
36
+ )
37
+ end
38
+
39
+ # Imports a VirtualAuthenticator into the current browser session.
40
+ # This creates a new authenticator and adds the credential with the stored private key.
41
+ def self.import(virtual_authenticator)
42
+ unless virtual_authenticator.credential_data
43
+ raise 'VirtualAuthenticator has no credential data to import. Call pull() on the source authenticator first.'
44
+ end
45
+
46
+ log { "Importing authenticator into session #{Capybara.session_name}" }
47
+
48
+ ::Booth::Testing::Support::VirtualAuthenticators::Enable.call
49
+
50
+ result = send_cdp_message(
51
+ 'WebAuthn.addVirtualAuthenticator',
52
+ options: authenticator_options(has_user_verification: virtual_authenticator.has_user_verification),
53
+ )
54
+
55
+ new_authenticator_id = result['authenticatorId']
56
+
57
+ raise 'Failed to create authenticator for import' unless new_authenticator_id
58
+
59
+ # Add the credential with stored data
60
+ unless virtual_authenticator.rp_id
61
+ raise 'IMPORT FAILED: No rp_id set on VirtualAuthenticator'
62
+ end
63
+
64
+ send_cdp_message(
65
+ 'WebAuthn.addCredential',
66
+ authenticatorId: new_authenticator_id,
67
+ credential: virtual_authenticator.credential_data,
68
+ rpId: virtual_authenticator.rp_id,
69
+ )
70
+
71
+ log do
72
+ "Imported credential #{virtual_authenticator.credential_id} into new authenticator #{new_authenticator_id}"
73
+ end
74
+
75
+ # Register the new authenticator ID for the current browser session
76
+ virtual_authenticator.register_authenticator_id(new_authenticator_id,
77
+ Capybara.session_name.to_s)
78
+
79
+ virtual_authenticator
80
+ end
81
+
82
+ private
83
+
84
+ def self.authenticator_options(has_user_verification:)
85
+ {
86
+ protocol: 'ctap2',
87
+ transport: 'internal',
88
+ hasResidentKey: true,
89
+ hasUserVerification: has_user_verification,
90
+ isUserVerified: true,
91
+ automaticPresenceSimulation: true
92
+ }
93
+ end
94
+
95
+ def self.send_cdp_message(method, params)
96
+ result = nil
97
+ Capybara.current_session.driver.with_playwright_page do |playwright_page|
98
+ cdp_session = playwright_page.context.new_cdp_session(playwright_page)
99
+ result = cdp_session.send_message(method, params:)
100
+ end
101
+ result
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -40,6 +40,7 @@ module Booth
40
40
  def options
41
41
  params.merge subdomain:,
42
42
  domain: 'localhost',
43
+ port: Capybara.server_port,
43
44
  controller: namespaced_controller,
44
45
  action:
45
46
  end
@@ -49,12 +49,12 @@ module Booth
49
49
  assert_userland_view controller: :remote_logins, step: :remote_solved
50
50
 
51
51
  using_session(:other_device) do
52
- visit_namespaced controller: :webauths, action: :index
52
+ visit current_path
53
53
 
54
54
  # ------------ SIGNIFICANT TEST --------------
55
55
  # Webauth sudo is required after remote login.
56
56
  # --------------------------------------------
57
- assert_userland_view controller: :webauths, step: :sudo
57
+ assert_logged_in username: 'alice'
58
58
  end
59
59
 
60
60
  using_session(:yet_another_device) do
@@ -45,13 +45,14 @@ module Booth
45
45
  # --------------------------------------------------------------------------
46
46
  assert_userland_view controller: :onboardings, step: :success
47
47
 
48
- visit_namespaced controller: :webauths, action: :index
48
+ # ::Booth::Testing::Support::CheckBrowserState.call(operation: 'after assert_userland_view :success')
49
+ assert_logged_in username: 'alice'
49
50
 
51
+ # ::Booth::Testing::Support::CheckBrowserState.call(operation: 'after assert_logged_in')
50
52
  # ----- SIGNIFICANT TEST -----
51
53
  # Onboarding logs the user in.
52
54
  # ----------------------------
53
- assert_userland_view controller: :webauths, step: :index
54
-
55
+ # ::Booth::Testing::Support::CheckBrowserState.call(operation: 'before second visit')
55
56
  visit_namespaced controller: :onboardings, action: :show,
56
57
  params: { id: alices_onboarding.secret_key }
57
58
 
@@ -65,7 +66,7 @@ module Booth
65
66
  # ---------------------------------------------------------
66
67
  assert_userland_view controller: :onboardings, step: :wrong_user
67
68
 
68
- soft_reset_session
69
+ clear_cookies
69
70
 
70
71
  visit_namespaced controller: :onboardings, action: :show,
71
72
  params: { id: alices_onboarding.secret_key }
@@ -64,11 +64,11 @@ module Booth
64
64
 
65
65
  assert_userland_view controller: :webauths, step: :completed
66
66
 
67
- authenticator = ::Booth::Models::Authenticator.sole
67
+ authenticator = ActiveRecord::Base.uncached { ::Booth::Models::Authenticator.sole }
68
68
 
69
69
  assert_equal 'Latchkey', authenticator.nickname
70
70
 
71
- soft_reset_session
71
+ clear_cookies
72
72
 
73
73
  # Onboard via URL
74
74
 
@@ -100,7 +100,7 @@ module Booth
100
100
 
101
101
  assert_userland_view controller: :webauths, step: :completed
102
102
 
103
- authenticator = ::Booth::Models::Authenticator.sole
103
+ authenticator = ActiveRecord::Base.uncached { ::Booth::Models::Authenticator.sole }
104
104
 
105
105
  assert_equal 'Superkey', authenticator.nickname
106
106
 
@@ -9,9 +9,14 @@ module Booth
9
9
 
10
10
  # Register
11
11
 
12
- visit_namespaced controller: :registrations, action: :new
12
+ begin
13
+ # Non-headless Chrome may cause Exception if HTTP status is 4xx
14
+ visit_namespaced controller: :registrations, action: :new
15
+ rescue Playwright::Error => e
16
+ raise unless e.message.include?('ERR_HTTP_RESPONSE_CODE_FAILURE')
17
+ end
13
18
 
14
- return 'Skipping self-registration tests' if page.has_text?('HTTP ERROR 410') # Chrome Page
19
+ return log { 'Skipping self-registration tests' } if page.status_code == 410
15
20
 
16
21
  virtual_authenticators.create
17
22
 
@@ -39,7 +44,7 @@ module Booth
39
44
  # --------------------------------------------
40
45
  assert_userland_view controller: :webauths, step: :completed
41
46
 
42
- soft_reset_session
47
+ clear_cookies
43
48
 
44
49
  # Login
45
50
 
@@ -64,9 +69,7 @@ module Booth
64
69
  # -----------------------------------------------------------------
65
70
  click_on :authenticate
66
71
 
67
- visit_namespaced controller: :webauths, action: :index
68
-
69
- assert_userland_view controller: :webauths, step: :index
72
+ assert_logged_in username: 'alice'
70
73
 
71
74
  # Try to register when already logged in
72
75
 
@@ -9,10 +9,14 @@ module Booth
9
9
 
10
10
  # Register normally
11
11
 
12
- visit_namespaced controller: :registrations, action: :new
12
+ begin
13
+ # Non-headless Chrome may cause Exception if HTTP status is 4xx
14
+ visit_namespaced controller: :registrations, action: :new
15
+ rescue Playwright::Error => e
16
+ raise unless e.message.include?('ERR_HTTP_RESPONSE_CODE_FAILURE')
17
+ end
13
18
 
14
- # Chrome Page
15
- return 'Skipping self-registration tests' if page.has_text?('HTTP ERROR 410')
19
+ return log { 'Skipping self-registration tests' } if page.status_code == 410
16
20
 
17
21
  assert_userland_view controller: :registrations, step: :choose_username
18
22
 
@@ -53,17 +57,17 @@ module Booth
53
57
  assert_userland_view controller: :remote_logins, step: :remote_solved
54
58
 
55
59
  using_session(:other_device) do
56
- visit_namespaced controller: :webauths, action: :index
60
+ visit current_path
57
61
 
58
62
  # -------------------------- SIGNIFICANT TEST ---------------------------
59
63
  # You can remote-login even though you don't have any Authenticators yet.
60
64
  # -----------------------------------------------------------------------
61
- assert_userland_view controller: :webauths, step: :index
65
+ assert_logged_in username: 'alice'
62
66
  end
63
67
 
64
68
  # Try to Login without having any Authenticators
65
69
 
66
- soft_reset_session
70
+ clear_cookies
67
71
 
68
72
  visit_namespaced controller: :logins, action: :new
69
73
 
@@ -93,7 +97,7 @@ module Booth
93
97
  # ---------------- SIGNIFICANT TEST -------------------
94
98
  # You cannot register a username that is already taken.
95
99
  # -----------------------------------------------------
96
- assert_text 'username already exists'
100
+ assert_text I18n.t('booth.username_already_exists')
97
101
  end
98
102
  end
99
103
  end