webaccount 0.3.1 → 0.5.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: 760ad4d11b1cfb40c9492b933d511fcd571ba2ad5a2875f6b23d14e1460e19c8
4
- data.tar.gz: 208737ad6bd03b897ecb917fdcb96898558f4f048c5a5313485a9b9842d9b79b
3
+ metadata.gz: 90b5b4da3fa0eee7ac4a4c6c85ce3b59a7640bc7aaff8065b7c8432920d8a255
4
+ data.tar.gz: 1a5efd14b2d09b0dd2d6eeaef4128272d7e34e18779e62cf968a95455858ee1a
5
5
  SHA512:
6
- metadata.gz: 8676a98a7ffe8504046408997d1f95cd6aaf908435c98ce245b03071a18af622bb15f4e359d71927af1b29ee91ae9efca65a4bf147d652eefdd71b69fcaeb7f4
7
- data.tar.gz: f691c42b066940950c48d64ad2b75959d1bdaa033f52856f4b0eca8c43e63d34126ccc77d11223c8aa63373d8eb53c5bc48a91c5e3e8487e7bbd361a5930cf75
6
+ metadata.gz: dbf5f58ce0a99f2232796010962a20f39b45a55e8ba0c5c414b2c3c50ea3c8f54fe409ab4b36878d36a5cf849664b187c3119bc3a79e483c50c2e2196f3b55a8
7
+ data.tar.gz: 935406e37e640aafd799a60a422314282874d0ffc5369fe398dde3b0948987da01583fc649ffdb47ebf58c421c0881fcf2a00dbeabeb5595ad1e92cac890b37c
data/CHANGELOG ADDED
@@ -0,0 +1,115 @@
1
+ # CHANGELOG
2
+
3
+ ## 20261011
4
+
5
+ 0.5.0: + WebAccount#driver, public, so that the logged-in browser can be driven from outside.
6
+
7
+ driver was private, so once login() had done its work the session it made could be used only by a sub-class. Something which wants the pages behind a login without being a WebAccount, a spider fetching and screenshotting them, had no way to reach the browser holding the session.
8
+
9
+ 1. ~ WebAccount#driver(): public, after shutdown(); what it does is unchanged, the browser being made once, with browser() and driver_options(), upon first being asked for.
10
+ 2. + test/webaccount_test.rb: driver public, made once, and made with the browser and driver options given, with Selenium::WebDriver.for swapped for a stand-in so that no browser is wanted. All three fail against 0.4.1.
11
+ 3. ~ README.md: + Driving the browser once logged in.
12
+ 4. ~ VERSION: /0.4.1/0.5.0/
13
+
14
+
15
+ ## 20260822
16
+
17
+ 0.4.1: + LICENSE, which the gemspec has claimed without one being present.
18
+
19
+ 1. + LICENSE: the MIT text, copyright 2020-2026 thoran. spec.license has named MIT with no licence text in the repository, so the gem shipped a licence nobody could read.
20
+ 2. ~ webaccount.gemspec: + 'LICENSE' to spec.files, without which the file sits in the repository and still does not ship.
21
+ 3. + README.md: a License section, saying MIT, last.
22
+ 4. ~ VERSION: /0.4.0/0.4.1/
23
+
24
+
25
+ ## 20260717
26
+
27
+ 0.4.0: Selenium 4 compatibility, achieved mostly by deletion, since Selenium 4 has absorbed most of what these extensions were written for.
28
+
29
+ Breaking:
30
+ a. - WebAccount#user_agent_alias, #user_agent_alias=: Replaced by the browser: argument to new(). A sub-class which set or over-rode user_agent_alias must now pass browser: or over-ride #browser().
31
+ b. ~ webaccount.gemspec: Require selenium-webdriver >= 4.0 and Ruby >= 3.3. This release depends on Selenium 4 behaviour and will not work on Selenium 3.
32
+ c. ~ Thoran::Selenium::WebDriver::Driver::ElementPresentQ#element_present?(): No longer waits. The implicit wait removed in 16 below gave it up to 5s for an element to appear; it is now an instantaneous check, so a sub-class which relied on it as a wait will now silently get false rather than true. Wrap it in a Selenium::WebDriver::Wait, as login() does.
33
+ d. /lib/WebAccount.rb/lib/webaccount.rb/: require 'webaccount', which is what the README has always documented and what the gem name implies, raised a LoadError on a case-sensitive file system. It now works, and it is require 'WebAccount' which raises instead. require 'web_account' is unaffected.
34
+
35
+ 1. - Thoran::Selenium::WebDriver::Remote::W3C::Bridge::ConvertLocators: The namespace no longer exists in Selenium 4, which raised a NameError on require, and locator conversion is now native.
36
+ 2. - Thoran::Selenium::WebDriver::SearchContext::ElementsPresentQ: find_elements_by() returns an empty array rather than raising, so elements_present?() was unconditionally true.
37
+ 3. - Thoran::Selenium::WebDriver::Setup: Firefox::Binary is gone from Selenium 4 and there was never a Chrome or Safari equivalent, so only the browser name branch remained, which is Selenium::WebDriver.for().
38
+ 4. - Thoran::String::ToConst: Only ever used by Setup, and Object.const_get() has handled nested constants since Ruby 2.0.
39
+ 5. - Thoran::Array::AllButFirst: Only ever used by ToConst.
40
+ 6. - Thoran::Array::FirstX: Only ever used by AllButFirst.
41
+ 7. ~ Thoran::Selenium: - the three deleted requires.
42
+ 8. ~ Thoran::Selenium::WebDriver::Driver::Attempt#attempt(): Yield self, so the documented block argument is the driver rather than nil; use retry rather than loop/break; re-raise rather than calling quit on @driver, which was never assigned, and exiting the process.
43
+ 9. ~ Thoran::Selenium::WebDriver::Driver::Attempt#attempt(): - Timeout::Error, which nothing in Selenium 4 raises, so the rescue was dead. Selenium's own timeouts, and those of Selenium::WebDriver::Wait, are Selenium::WebDriver::Error::TimeoutError. Timeout::Error also only resolved at all because selenium-webdriver happens to require 'timeout' for Firefox.
44
+ 10. + Thoran::Selenium::WebDriver::Driver::Attempt::TRANSIENT_ERRORS: The errors a flaky page actually raises: ElementClickInterceptedError, ElementNotInteractableError, NoSuchElementError, StaleElementReferenceError, TimeoutError, UnexpectedAlertOpenError, and UnknownError. Only the last two were caught before. InvalidSelectorError and NoSuchDriverError are deliberately excluded, being programming and environment errors respectively, so retrying only delays the report.
45
+ 11. ~ Thoran::Selenium::WebDriver::Driver::ElementPresentQ#element_present?(): Use the public find_elements() rather than the private bridge.
46
+ 12. /Thoran::Selenium::WebDriver::SearchContext::ElementPresentQ/Thoran::Selenium::WebDriver::Driver::ElementPresentQ/: It patches Driver, as Driver::Attempt does, so it now sits alongside it. Selenium::WebDriver::SearchContext is the find_element()/find_elements() contract which Driver, Element, and ShadowRoot implement, rather than a home for conveniences built on top of it, and Selenium itself has no presence predicate to follow.
47
+ 13. + WebAccount#default_browser(), #default_driver_options(): Return :chrome and nil, for over-riding in a sub-class.
48
+ 14. + WebAccount#browser(), #driver_options(): Fall back to the defaults. browser() also normalises a String or a capitalised Symbol, since Selenium::WebDriver.for() matches on a lowercase Symbol.
49
+ 15. ~ WebAccount#driver(): Construct the driver directly, with browser() and driver_options(), rather than via the deleted Selenium::WebDriver.setup().
50
+ 16. ~ WebAccount#driver(): - the implicit wait, since mixing implicit and explicit waits made driver_wait() poll every 5s rather than every 0.2s. See c above.
51
+ 17. + WebAccount#find(): Extracted from the four finder methods.
52
+ 18. + WebAccount#find_wait(), #default_find_timeout(): A Wait of its own for find(), at 5s. Sharing driver_wait() would have given a missing element 180s to not appear, where the implicit wait removed in 16 raised at 5s. Selenium::WebDriver::Wait ignores NoSuchElementError by default, so find() polls rather than raising, and 5s restores the previous contract.
53
+ 19. ~ WebAccount#driver_wait(): /180/30/, via the new #default_wait_timeout(). 180s was tolerable only because the implicit wait removed in 16 made each poll cost 5s, so it bought 36 polls; at the 0.2s interval it now buys 900 polls of the same page. 30s covers a submit, the authentication behind it, and the redirect, and is 90s worst case through attempt()'s three tries rather than 9 minutes.
54
+ 20. + WebAccount#username_field_selector(), #password_field_selector(), #login_page_submit_button_selector(), #logout_button_selector(): Fall back to the defaults in one place.
55
+ 21. ~ WebAccount#username_field(), #password_field(), #login_page_submit_button(), #logout_button(): Use find() and the selector methods.
56
+ 22. ~ WebAccount#logout_button(): Fix @logout_page_submit_button_selector, which was never assigned, so a supplied logout button selector was always ignored in favour of the default.
57
+ 23. ~ WebAccount#login(): Use logout_button_selector(), which no longer raises when no selector was supplied.
58
+ 24. ~ WebAccount#login(): - attempts = 0, which was unused.
59
+ 25. /WebAccount#default_username_field/WebAccount#default_username_field_selector/
60
+ 26. /WebAccount#default_password_field/WebAccount#default_password_field_selector/
61
+ 27. ~ lib/web_account.rb: /require_relative './WebAccount'/require_relative './webaccount'/, per d above.
62
+ 28. ~ WebAccount::VERSION: /0.3.1/0.4.0/
63
+ 29. + lib/webaccount.rb: require_relative './WebAccount/VERSION', so that WebAccount::VERSION is defined by requiring the library. It was previously loaded only by the gemspec.
64
+ 30. ~ lib/webaccount.rb: /require 'Thoran/Selenium'/require_relative './Thoran/Selenium'/. Thoran/Selenium ships in this gem, so requiring it by $LOAD_PATH meant it only loaded when lib was on the path, and left it to path order in a namespace which several other gems here also ship. require './lib/webaccount' now works.
65
+ 31. ~ lib/webaccount.rb: /# WebAccount.rb/# webaccount.rb/ in the header, per d above.
66
+ 32. ~ webaccount.gemspec: /spec.license = 'Ruby'/spec.license = 'MIT'/, per the more recent gems.
67
+ 33. + test/Thoran/Selenium/WebDriver/Driver/Attempt_test.rb: Cover attempt() against a dummy including the module: the value and receiver yielded, which errors are retried and which are not, the attempt count, and the warning. The transient and non-transient errors are listed in the test rather than read from TRANSIENT_ERRORS, so that the test states the intent rather than agreeing with the implementation. Nothing else in WebAccount is reachable to test: browser(), driver_options(), and the four selector methods are all private, and login(), find(), and logout() need a browser and a settled timeout.
68
+ 34. + Rakefile: rake test, per the other gems.
69
+ 35. ~ .gitignore (*.lock): Running bundle writes a Gemfile.lock, which is not committed for a gem.
70
+ 36. ~ webaccount.gemspec: + minitest, minitest-spec-context, and rake as development dependencies, and + Rakefile and test/ to spec.files.
71
+ 37. ~ webaccount.gemspec: Match the layout of the more recent gemspecs: + the dependencies=/development_dependencies= sugar; + spec.require_paths, which was missing; list spec.files explicitly, so that CHANGELOG, Gemfile, and README.md are packaged rather than lib alone; and - spec.date, which Rubygems sets itself and which was stale in every release so far.
72
+ 38. ~ README.md: + Choosing a browser, Driver options, and Timeouts; note that logged_out_xpath is required by logout() and shutdown(); prefer find() to driver.find_element() in the sub-classing example, since that example was relying on the implicit wait removed in 16; and fix a stray quotation mark, a missing comma, and a fork URL pointing at month.rb.
73
+
74
+ Known: These are all one question, which is what login(), logout(), and logged_out?() should do
75
+ when the page does not do what was expected: raise, or report. They want one answer between
76
+ them rather than three, they are not compatibility problems, and 0.5's fixtures and
77
+ integration tests are what would pin them, so they are left alone here.
78
+
79
+ i. WebAccount#logged_out?() returns truthy or raises: Wait#until does not return false, so
80
+ the `if logged_out?` in logout() is dead code and logout() always either raises or logs out.
81
+ ii. WebAccount#login() raises Selenium::WebDriver::Error::TimeoutError rather than returning
82
+ false when the logout button never appears, so a wrong password is an exception rather than
83
+ a return value. As in 0.3.1.
84
+ iii. WebAccount#logged_out?() with no logged_out_xpath raises
85
+ Selenium::WebDriver::Error::InvalidSelectorError, from find_elements(:xpath, nil) reaching
86
+ the bridge, which sends nil.to_s. shutdown() calls logout(), so this is reachable from the
87
+ README's implicit-defaults usage.
88
+ iv. Thoran::Selenium::WebDriver::Driver::ElementPresentQ#element_present?() rests on
89
+ find_elements() returning an empty array rather than raising, which is what the deleted
90
+ ElementsPresentQ in 2 above got wrong. Only a driver can confirm it, so it is untested until
91
+ 0.5.
92
+
93
+
94
+ ## 20240407
95
+
96
+ 0.3.1: ./lib/README.md --> ./README.md
97
+
98
+
99
+ ## 20240407
100
+
101
+ 0.3.0: Can now use all of Selenium's page selectors (:css, :id, :name:, :xpath) instead of just :id and/or overriding methods in the sub-class.
102
+ 1. + WebAccount#default_username_field(): For use in username_field if username_field_selector is nil.
103
+ 2. ~ WebAccount#username_field(): Will optionally use the default_username_field if nothing is supplied.
104
+ 3. + WebAccount#default_password_field(): For use in password_field if username_field_selector is nil.
105
+ 4. ~ WebAccount#password_field(): Will optionally use the default_username_field if nothing is supplied.
106
+ 5. + WebAccount#default_login_page_submit_button_selector(): For use in login_page_submit_button if login_page_submit_button is nil.
107
+ 6. ~ WebAccount#login_page_submit_button(): Will optionally use the default_login_page_submit_button_selector if nothing is supplied.
108
+ 7. + WebAccount#default_logout_button_selector(): For use in logout_button if logout_button is nil.
109
+ 8. + WebAccount#logout_button(): Consistent with other elements having a method and will also optionally use the default_logout_button_selector if nothing is supplied.
110
+ 9. ~ WebAccount#login(): Use new generic selector for detecting logged-in state.
111
+ 10. ~ WebAccount#logout(): Use new logout_button() method instead of in-lining Selenium.
112
+ 11. ~ WebAccount::VERSION: /0.2.5/0.3.0/
113
+ 12. ~ webaccount.gemspec: Change the date, fix the summary, and the description.
114
+ 13. + CHANGELOG.txt
115
+ 14. + README.md
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://www.rubygems.org'
2
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020-2026 thoran
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,198 @@
1
+ # WebAccount
2
+
3
+ ## Description
4
+
5
+ Easily log into web accounts by supplying just a username and password, the URL of the login page, and a few selectors for page elements to a sub-class of WebAccount.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+ ```ruby
11
+ gem 'webaccount'
12
+ ```
13
+ And then execute:
14
+ ```shell
15
+ $ bundle
16
+ ```
17
+ Or install it yourself as:
18
+ ```shell
19
+ $ gem install webaccount
20
+ ```
21
+
22
+ ## Usage
23
+
24
+ ### Instantiation
25
+
26
+ #### 1. With implicit defaults
27
+ ```ruby
28
+ require 'webaccount'
29
+
30
+ class MyAccount < WebAccount; end
31
+
32
+ MyAccount.new(username: username, password: password)
33
+ # => #<MyAccount @username=username, @password=password, @login_page_url=nil,
34
+ # @login_page_password_field_selector=nil, @login_page_username_field_selector=nil,
35
+ # @login_page_submit_button_selector=nil,
36
+ # @logout_button_selector=nil, @logged_out_xpath=nil>
37
+ ```
38
+
39
+ #### 2. With explicit defaults, then in the comment the ultimate defaults if those values remain nil
40
+ ```ruby
41
+ require 'webaccount'
42
+
43
+ class MyAccount < WebAccount
44
+ PAGE_SELECTORS = {
45
+ login_page_url: nil, # There are no defaults, so something like 'https://mysite.com/login' is required.
46
+ login_page_username_field_selector: nil, # Will default to: {name: 'username'}
47
+ login_page_password_field_selector: nil, # Will default to: {name: 'password'}
48
+ login_page_submit_button_selector: nil, # Will default to: {xpath: '//input[@type="submit" and @value="Login"]'}
49
+ logout_button_selector: nil, # Will default to: {xpath: '//input[@type="submit" and @value="Logout"]'}
50
+ logged_out_xpath: nil, # No default value for this, and logout and shutdown require it.
51
+ }
52
+ end
53
+
54
+ MyAccount.new(
55
+ **{username: username, password: password}\
56
+ .merge(MyAccount::PAGE_SELECTORS)
57
+ )
58
+ # => #<MyAccount @username=username, @password=password...>
59
+ ```
60
+
61
+ #### 3. With supplied values
62
+ ```ruby
63
+ require 'webaccount'
64
+
65
+ class MyAccount < WebAccount
66
+ PAGE_SELECTORS = {
67
+ login_page_url: 'https://mysite.com/login',
68
+ login_page_username_field_selector: {id: 'username'},
69
+ login_page_password_field_selector: {id: 'password'},
70
+ login_page_submit_button_selector: {xpath: '//button[@type="submit" and @value="Login"]'},
71
+ logout_button_selector: {css: '#button-logout'},
72
+ logged_out_xpath: '//div[@class="logged-out"]',
73
+ }
74
+ end
75
+
76
+ MyAccount.new(
77
+ **{username: username, password: password}
78
+ .merge(MyAccount::PAGE_SELECTORS)
79
+ )
80
+ # => #<MyAccount @username=username, @password=password...>
81
+ ```
82
+
83
+ ### Choosing a browser
84
+
85
+ `browser` is passed to `Selenium::WebDriver.for`, so it accepts whatever Selenium does: `:chrome`, `:firefox`, `:safari`, `:edge`, `:ie`. It defaults to `:chrome`, and a String or a capitalised Symbol is fine.
86
+ ```ruby
87
+ MyAccount.new(username: username, password: password, browser: :firefox)
88
+ ```
89
+
90
+ ### Driver options
91
+
92
+ `driver_options` is passed to `Selenium::WebDriver.for` as `options:`. It defaults to nil, which is what Selenium itself defaults to, so leaving it alone gets you a stock browser.
93
+ ```ruby
94
+ options = Selenium::WebDriver::Chrome::Options.new
95
+ options.add_argument('--headless=new')
96
+
97
+ MyAccount.new(username: username, password: password, driver_options: options)
98
+ ```
99
+ Or in a sub-class, if every instance should be configured the same way:
100
+ ```ruby
101
+ class MyAccount < WebAccount
102
+ private
103
+
104
+ def default_driver_options
105
+ Selenium::WebDriver::Chrome::Options.new.tap do |options|
106
+ options.add_argument('--headless=new')
107
+ end
108
+ end
109
+ end
110
+ ```
111
+
112
+ ### Timeouts
113
+
114
+ There are two, both overridable in a sub-class:
115
+
116
+ | Method | Default | Waits for |
117
+ | --- | --- | --- |
118
+ | `default_find_timeout` | 5s | An element on a page which has already loaded. |
119
+ | `default_wait_timeout` | 30s | Navigation: a submit, the authentication behind it, and the redirect. |
120
+
121
+ ```ruby
122
+ class SlowAccount < WebAccount
123
+ private
124
+
125
+ def default_wait_timeout
126
+ 60
127
+ end
128
+ end
129
+ ```
130
+
131
+ ### Logging in, out, and shutting down
132
+ ```ruby
133
+ require 'webaccount'
134
+
135
+ class MyAccount < WebAccount; end
136
+
137
+ my_account = MyAccount.new(username: username, password: password)
138
+ my_account.login
139
+ my_account.logout
140
+ my_account.shutdown # If necessary it logs out and then kills the selenium session.
141
+ ```
142
+
143
+ ### Driving the browser once logged in
144
+
145
+ `driver` is the logged-in browser, a `Selenium::WebDriver::Driver`, for whatever WebAccount doesn't do itself: fetching pages behind the login, reading them, or taking screenshots of them.
146
+ ```ruby
147
+ my_account = MyAccount.new(username: username, password: password)
148
+ my_account.login
149
+ my_account.driver.get('https://mysite.com/members/page')
150
+ my_account.driver.page_source
151
+ my_account.driver.save_screenshot('page.png')
152
+ my_account.shutdown
153
+ ```
154
+
155
+ ### Overriding the login method to customise logins
156
+ ```ruby
157
+ require 'webaccount'
158
+
159
+ class MyAccount < WebAccount
160
+ def login
161
+ # Put custom login code here.
162
+ end
163
+ end
164
+ ```
165
+
166
+ ### Create new methods for selection and navigation
167
+
168
+ Prefer `find` over `driver.find_element`: it waits up to `default_find_timeout` for the element, whereas `driver.find_element` raises the moment the element isn't there.
169
+ ```ruby
170
+ require 'webaccount'
171
+
172
+ class MyAccount < WebAccount
173
+ def list_page_link
174
+ find(id: 'list')
175
+ end
176
+
177
+ def list_page
178
+ login unless logged_in?
179
+ list_page_link.click
180
+ end
181
+ end
182
+
183
+ my_account = MyAccount.new(username: username, password: password)
184
+ my_account.list_page
185
+ ```
186
+
187
+ ## Contributing
188
+
189
+ 1. Fork it: https://github.com/thoran/WebAccount/fork
190
+ 2. Create your feature branch: `git checkout -b my-new-feature`
191
+ 3. Commit your changes: `git commit -am 'Add some feature'`
192
+ 4. Push to the branch: `git push origin my-new-feature`
193
+ 5. Create a new pull request
194
+
195
+
196
+ ## License
197
+
198
+ MIT
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ # Rakefile
2
+
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:test) do |t|
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ end
8
+
9
+ task default: :test
@@ -1,8 +1,8 @@
1
- # Thoran/Selenium/WebDriver/Attempt/attempt.rb
2
- # Thoran::Selenium::WebDriver::Attempt.attempt
1
+ # Thoran/Selenium/WebDriver/Driver/Attempt/attempt.rb
2
+ # Thoran::Selenium::WebDriver::Driver::Attempt#attempt
3
3
 
4
- # 20200419
5
- # 0.0.1
4
+ # 20260717
5
+ # 0.1.0
6
6
 
7
7
  # Examples:
8
8
  # 1. driver.attempt do |driver|
@@ -13,31 +13,41 @@
13
13
  # end
14
14
 
15
15
  # Changes:
16
- # 1. + Thoran namespace.
16
+ # 1. Yield self, so that the documented block argument is the driver rather than nil.
17
+ # 2. Use retry rather than loop/break.
18
+ # 3. Re-raise rather than calling quit on @driver, which was never assigned, and exiting the process.
19
+ # 4. - Timeout::Error: Nothing in Selenium 4 raises it. Its own timeouts, and those of
20
+ # Selenium::WebDriver::Wait, are Selenium::WebDriver::Error::TimeoutError.
21
+ # 5. + The errors a flaky page actually raises, none of which were caught before.
22
+ # Deliberately excluded: Error::InvalidSelectorError and Error::NoSuchDriverError, which
23
+ # are programming and environment errors respectively, so retrying only delays the report.
17
24
 
18
25
  module Thoran
19
26
  module Selenium
20
27
  module WebDriver
21
28
  module Driver
22
29
  module Attempt
30
+ TRANSIENT_ERRORS = [
31
+ ::Selenium::WebDriver::Error::ElementClickInterceptedError,
32
+ ::Selenium::WebDriver::Error::ElementNotInteractableError,
33
+ ::Selenium::WebDriver::Error::NoSuchElementError,
34
+ ::Selenium::WebDriver::Error::StaleElementReferenceError,
35
+ ::Selenium::WebDriver::Error::TimeoutError,
36
+ ::Selenium::WebDriver::Error::UnexpectedAlertOpenError,
37
+ ::Selenium::WebDriver::Error::UnknownError,
38
+ ].freeze
23
39
 
24
- def attempt(max_attempts = 3, &block)
40
+ def attempt(max_attempts = 3)
25
41
  attempts = 0
26
- loop do
27
- begin
28
- yield
29
- break
30
- rescue Timeout::Error, ::Selenium::WebDriver::Error::UnknownError, ::Selenium::WebDriver::Error::NoSuchElementError => e
31
- attempts += 1
32
- if attempts >= max_attempts
33
- @driver.quit
34
- puts "Giving up after #{max_attempts} attempts to #{__method__} because #{e}."
35
- exit
36
- end
37
- end
42
+ begin
43
+ yield self
44
+ rescue *TRANSIENT_ERRORS => e
45
+ attempts += 1
46
+ warn "Attempt #{attempts} of #{max_attempts} failed: #{e.class}"
47
+ retry if attempts < max_attempts
48
+ raise
38
49
  end
39
50
  end
40
-
41
51
  end
42
52
  end
43
53
  end
@@ -0,0 +1,37 @@
1
+ # Thoran/Selenium/WebDriver/Driver/ElementPresentQ/element_presentQ.rb
2
+ # Thoran::Selenium::WebDriver::Driver::ElementPresentQ#element_present?
3
+
4
+ # 20260717
5
+ # 0.1.0
6
+
7
+ # Examples:
8
+ # 1. driver.element_present?(:xpath, '//a[@id="submit"]')
9
+ # 2. driver.element_present?(:css, '#button-logout')
10
+
11
+ # Changes:
12
+ # 1. Use the public find_elements() rather than the private bridge.
13
+ # 2. ^Driver, alongside Driver::Attempt, since this patches Driver too. Selenium's
14
+ # SearchContext is the find_element()/find_elements() contract, which Driver, Element,
15
+ # and ShadowRoot implement, rather than a home for conveniences built on top of it.
16
+
17
+ module Thoran
18
+ module Selenium
19
+ module WebDriver
20
+ module Driver
21
+ module ElementPresentQ
22
+ def element_present?(selector_type, selector)
23
+ !find_elements(selector_type, selector).empty?
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module Selenium
32
+ module WebDriver
33
+ class Driver
34
+ include Thoran::Selenium::WebDriver::Driver::ElementPresentQ
35
+ end
36
+ end
37
+ end
@@ -1,11 +1,14 @@
1
1
  # Thoran/Selenium.rb
2
2
  # Thoran::Selenium
3
3
 
4
- # 20200418
5
- # 0.0.0
4
+ # 20260717
5
+ # 0.1.0
6
+
7
+ # Changes:
8
+ # 1. - Selenium::WebDriver::Remote::W3C::Bridge::ConvertLocators
9
+ # 2. - Selenium::WebDriver::SearchContext::ElementsPresentQ
10
+ # 3. - Selenium::WebDriver::Setup
11
+ # 4. /Selenium::WebDriver::SearchContext::ElementPresentQ/Selenium::WebDriver::Driver::ElementPresentQ/
6
12
 
7
13
  require_relative './Selenium/WebDriver/Driver/Attempt/attempt'
8
- require_relative './Selenium/WebDriver/Remote/W3C/Bridge/ConvertLocators/convert_locators'
9
- require_relative './Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ'
10
- require_relative './Selenium/WebDriver/SearchContext/ElementsPresentQ/elements_presentQ'
11
- require_relative './Selenium/WebDriver/Setup/setup'
14
+ require_relative './Selenium/WebDriver/Driver/ElementPresentQ/element_presentQ'
data/lib/web_account.rb CHANGED
@@ -3,4 +3,4 @@
3
3
  # Notes:
4
4
  # 1. This is to enable an alternate means of loading: require 'web_account'.
5
5
 
6
- require_relative 'WebAccount'
6
+ require_relative './webaccount'
@@ -0,0 +1,6 @@
1
+ # WebAccount/VERSION.rb
2
+ # WebAccount::VERSION
3
+
4
+ class WebAccount
5
+ VERSION = '0.5.0'
6
+ end