webaccount 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: 5f2306cf382f2ebbba8b93e9746e12c92d298ead4ba4f154fc71c223d844e2e0
4
- data.tar.gz: c1b04987b7f1812c549fd8f0203457e515aab18e716f2885c29494c07688baf8
3
+ metadata.gz: 219aecddbc5a0e05b1463300ade4350d9afd789de19f1a43fe34e492d5ad9296
4
+ data.tar.gz: 6cbc7db55da4afc34b164f3a8a163da4e4e716b313e09b266d2453c98af0c115
5
5
  SHA512:
6
- metadata.gz: 2f488ddda68db8c4807e5ad7ada9c8f85f48219a97e24be6f1ea6d1033395993a9bf6a74de615b539bdab6c749e7a11c3c4a1d04256dd94cfba743a2aadb0f8d
7
- data.tar.gz: 86b9e301db625c6e236da7290d916a1fa629d3cdabf74cc4cb8f61f0b25d837a8b6bd5e5cec6b357b5f3baea0ff6d4d71739ddc7ac463089ee1a4a06df5e73bc
6
+ metadata.gz: fdb4db1ec12daf5e307f0df3212f51f1eb4da9b1ca505a68a1008e626598d30214f7d235e330b9f856d215efb816c276eb4525a4578937eb7329dedf064d0581
7
+ data.tar.gz: b4515525fce7cb22a90b15f7a05ddc3da14cb870c02d4e95d31959a2ff3c3753d74ad96adfb0bd96deb64937f466c20a0ec7ae3395b9d4d43eb59b72b4881dff
data/CHANGELOG ADDED
@@ -0,0 +1,93 @@
1
+ # CHANGELOG
2
+
3
+ ## 20260717
4
+
5
+ 0.4.0: Selenium 4 compatibility, achieved mostly by deletion, since Selenium 4 has absorbed most of what these extensions were written for.
6
+
7
+ Breaking:
8
+ 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().
9
+ 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.
10
+ 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.
11
+ 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.
12
+
13
+ 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.
14
+ 2. - Thoran::Selenium::WebDriver::SearchContext::ElementsPresentQ: find_elements_by() returns an empty array rather than raising, so elements_present?() was unconditionally true.
15
+ 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().
16
+ 4. - Thoran::String::ToConst: Only ever used by Setup, and Object.const_get() has handled nested constants since Ruby 2.0.
17
+ 5. - Thoran::Array::AllButFirst: Only ever used by ToConst.
18
+ 6. - Thoran::Array::FirstX: Only ever used by AllButFirst.
19
+ 7. ~ Thoran::Selenium: - the three deleted requires.
20
+ 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.
21
+ 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.
22
+ 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.
23
+ 11. ~ Thoran::Selenium::WebDriver::Driver::ElementPresentQ#element_present?(): Use the public find_elements() rather than the private bridge.
24
+ 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.
25
+ 13. + WebAccount#default_browser(), #default_driver_options(): Return :chrome and nil, for over-riding in a sub-class.
26
+ 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.
27
+ 15. ~ WebAccount#driver(): Construct the driver directly, with browser() and driver_options(), rather than via the deleted Selenium::WebDriver.setup().
28
+ 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.
29
+ 17. + WebAccount#find(): Extracted from the four finder methods.
30
+ 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.
31
+ 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.
32
+ 20. + WebAccount#username_field_selector(), #password_field_selector(), #login_page_submit_button_selector(), #logout_button_selector(): Fall back to the defaults in one place.
33
+ 21. ~ WebAccount#username_field(), #password_field(), #login_page_submit_button(), #logout_button(): Use find() and the selector methods.
34
+ 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.
35
+ 23. ~ WebAccount#login(): Use logout_button_selector(), which no longer raises when no selector was supplied.
36
+ 24. ~ WebAccount#login(): - attempts = 0, which was unused.
37
+ 25. /WebAccount#default_username_field/WebAccount#default_username_field_selector/
38
+ 26. /WebAccount#default_password_field/WebAccount#default_password_field_selector/
39
+ 27. ~ lib/web_account.rb: /require_relative './WebAccount'/require_relative './webaccount'/, per d above.
40
+ 28. ~ WebAccount::VERSION: /0.3.1/0.4.0/
41
+ 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.
42
+ 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.
43
+ 31. ~ lib/webaccount.rb: /# WebAccount.rb/# webaccount.rb/ in the header, per d above.
44
+ 32. ~ webaccount.gemspec: /spec.license = 'Ruby'/spec.license = 'MIT'/, per the more recent gems.
45
+ 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.
46
+ 34. + Rakefile: rake test, per the other gems.
47
+ 35. ~ .gitignore (*.lock): Running bundle writes a Gemfile.lock, which is not committed for a gem.
48
+ 36. ~ webaccount.gemspec: + minitest, minitest-spec-context, and rake as development dependencies, and + Rakefile and test/ to spec.files.
49
+ 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.
50
+ 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.
51
+
52
+ Known: These are all one question, which is what login(), logout(), and logged_out?() should do
53
+ when the page does not do what was expected: raise, or report. They want one answer between
54
+ them rather than three, they are not compatibility problems, and 0.5's fixtures and
55
+ integration tests are what would pin them, so they are left alone here.
56
+
57
+ i. WebAccount#logged_out?() returns truthy or raises: Wait#until does not return false, so
58
+ the `if logged_out?` in logout() is dead code and logout() always either raises or logs out.
59
+ ii. WebAccount#login() raises Selenium::WebDriver::Error::TimeoutError rather than returning
60
+ false when the logout button never appears, so a wrong password is an exception rather than
61
+ a return value. As in 0.3.1.
62
+ iii. WebAccount#logged_out?() with no logged_out_xpath raises
63
+ Selenium::WebDriver::Error::InvalidSelectorError, from find_elements(:xpath, nil) reaching
64
+ the bridge, which sends nil.to_s. shutdown() calls logout(), so this is reachable from the
65
+ README's implicit-defaults usage.
66
+ iv. Thoran::Selenium::WebDriver::Driver::ElementPresentQ#element_present?() rests on
67
+ find_elements() returning an empty array rather than raising, which is what the deleted
68
+ ElementsPresentQ in 2 above got wrong. Only a driver can confirm it, so it is untested until
69
+ 0.5.
70
+
71
+
72
+ ## 20240407
73
+
74
+ 0.3.1: ./lib/README.md --> ./README.md
75
+
76
+
77
+ ## 20240407
78
+
79
+ 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.
80
+ 1. + WebAccount#default_username_field(): For use in username_field if username_field_selector is nil.
81
+ 2. ~ WebAccount#username_field(): Will optionally use the default_username_field if nothing is supplied.
82
+ 3. + WebAccount#default_password_field(): For use in password_field if username_field_selector is nil.
83
+ 4. ~ WebAccount#password_field(): Will optionally use the default_username_field if nothing is supplied.
84
+ 5. + WebAccount#default_login_page_submit_button_selector(): For use in login_page_submit_button if login_page_submit_button is nil.
85
+ 6. ~ WebAccount#login_page_submit_button(): Will optionally use the default_login_page_submit_button_selector if nothing is supplied.
86
+ 7. + WebAccount#default_logout_button_selector(): For use in logout_button if logout_button is nil.
87
+ 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.
88
+ 9. ~ WebAccount#login(): Use new generic selector for detecting logged-in state.
89
+ 10. ~ WebAccount#logout(): Use new logout_button() method instead of in-lining Selenium.
90
+ 11. ~ WebAccount::VERSION: /0.2.5/0.3.0/
91
+ 12. ~ webaccount.gemspec: Change the date, fix the summary, and the description.
92
+ 13. + CHANGELOG.txt
93
+ 14. + README.md
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://www.rubygems.org'
2
+ gemspec
data/README.md ADDED
@@ -0,0 +1,181 @@
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
+ ### Overriding the login method to customise logins
144
+ ```ruby
145
+ require 'webaccount'
146
+
147
+ class MyAccount < WebAccount
148
+ def login
149
+ # Put custom login code here.
150
+ end
151
+ end
152
+ ```
153
+
154
+ ### Create new methods for selection and navigation
155
+
156
+ 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.
157
+ ```ruby
158
+ require 'webaccount'
159
+
160
+ class MyAccount < WebAccount
161
+ def list_page_link
162
+ find(id: 'list')
163
+ end
164
+
165
+ def list_page
166
+ login unless logged_in?
167
+ list_page_link.click
168
+ end
169
+ end
170
+
171
+ my_account = MyAccount.new(username: username, password: password)
172
+ my_account.list_page
173
+ ```
174
+
175
+ ## Contributing
176
+
177
+ 1. Fork it: https://github.com/thoran/WebAccount/fork
178
+ 2. Create your feature branch: `git checkout -b my-new-feature`
179
+ 3. Commit your changes: `git commit -am 'Add some feature'`
180
+ 4. Push to the branch: `git push origin my-new-feature`
181
+ 5. Create a new pull request
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'
@@ -1,5 +1,6 @@
1
- class WebAccount
2
-
3
- VERSION = '0.3.0'
1
+ # WebAccount/VERSION.rb
2
+ # WebAccount::VERSION
4
3
 
4
+ class WebAccount
5
+ VERSION = '0.4.0'
5
6
  end
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'
@@ -1,17 +1,17 @@
1
- # WebAccount.rb
1
+ # webaccount.rb
2
2
  # WebAccount
3
3
 
4
4
  require 'selenium-webdriver'
5
- require 'Thoran/Selenium'
5
+ require_relative './Thoran/Selenium'
6
+ require_relative './WebAccount/VERSION'
6
7
 
7
8
  class WebAccount
8
-
9
9
  attr_accessor :username
10
10
  attr_accessor :password
11
- attr_writer :user_agent_alias
12
11
 
13
12
  def initialize(
14
13
  username: nil, password: nil,
14
+ browser: nil, driver_options: nil,
15
15
  login_page_url: nil,
16
16
  login_page_username_field_selector: nil, login_page_password_field_selector: nil,
17
17
  login_page_submit_button_selector: nil,
@@ -19,6 +19,7 @@ class WebAccount
19
19
  logged_out_xpath: nil
20
20
  )
21
21
  @username, @password = username, password
22
+ @browser, @driver_options = browser, driver_options
22
23
  @login_page_url = login_page_url
23
24
  @login_page_username_field_selector, @login_page_password_field_selector = login_page_username_field_selector, login_page_password_field_selector
24
25
  @login_page_submit_button_selector = login_page_submit_button_selector
@@ -30,7 +31,6 @@ class WebAccount
30
31
  @logged_in = false
31
32
  username ||= self.username
32
33
  password ||= self.password
33
- attempts = 0
34
34
  driver.attempt do
35
35
  get_login_page
36
36
  enter_username(username)
@@ -38,7 +38,7 @@ class WebAccount
38
38
  login_page_submit_button.click
39
39
  end
40
40
  @logged_in = driver_wait.until do
41
- driver.element_present?(@logout_button_selector.keys.first, @logout_button_selector.values.first)
41
+ driver.element_present?(logout_button_selector.keys.first, logout_button_selector.values.first)
42
42
  end
43
43
  end
44
44
  alias_method :logon, :login
@@ -70,20 +70,50 @@ class WebAccount
70
70
 
71
71
  private
72
72
 
73
- def user_agent_alias
74
- @user_agent_alias ||= @user_agent_alias || :chrome
73
+ # driver construction
74
+
75
+ def default_browser
76
+ :chrome
77
+ end
78
+
79
+ def browser
80
+ (@browser || default_browser).to_sym.downcase
81
+ end
82
+
83
+ def default_driver_options
84
+ nil
85
+ end
86
+
87
+ def driver_options
88
+ @driver_options || default_driver_options
75
89
  end
76
90
 
77
91
  def driver
78
- @driver ||= (
79
- driver = Selenium::WebDriver.setup(user_agent_alias)
80
- driver.manage.timeouts.implicit_wait = 5
81
- driver
82
- )
92
+ @driver ||= Selenium::WebDriver.for(browser, options: driver_options)
93
+ end
94
+
95
+ # waiting
96
+
97
+ # For navigation: a submit, the authentication behind it, and the redirect.
98
+ def default_wait_timeout
99
+ 30
83
100
  end
84
101
 
85
102
  def driver_wait
86
- @driver_wait ||= Selenium::WebDriver::Wait.new(timeout: 180)
103
+ @driver_wait ||= Selenium::WebDriver::Wait.new(timeout: default_wait_timeout)
104
+ end
105
+
106
+ # For lookups: the element should already be on a page which has loaded.
107
+ def default_find_timeout
108
+ 5
109
+ end
110
+
111
+ def find_wait
112
+ @find_wait ||= Selenium::WebDriver::Wait.new(timeout: default_find_timeout)
113
+ end
114
+
115
+ def find(selector)
116
+ find_wait.until{driver.find_element(selector.keys.first, selector.values.first)}
87
117
  end
88
118
 
89
119
  # logging in
@@ -98,16 +128,16 @@ class WebAccount
98
128
  username_field.send_keys(username)
99
129
  end
100
130
 
101
- def default_username_field
102
- {name: "username"}
131
+ def default_username_field_selector
132
+ {name: 'username'}
133
+ end
134
+
135
+ def username_field_selector
136
+ @login_page_username_field_selector || default_username_field_selector
103
137
  end
104
138
 
105
139
  def username_field
106
- if @login_page_username_field_selector
107
- driver.find_element(@login_page_username_field_selector.keys.first, @login_page_username_field_selector.values.first)
108
- else
109
- driver.find_element(default_username_field.keys.first, default_username_field.values.first)
110
- end
140
+ find(username_field_selector)
111
141
  end
112
142
 
113
143
  def enter_password(password = nil)
@@ -115,28 +145,28 @@ class WebAccount
115
145
  password_field.send_keys(password)
116
146
  end
117
147
 
118
- def default_password_field
119
- {name: "password"}
148
+ def default_password_field_selector
149
+ {name: 'password'}
150
+ end
151
+
152
+ def password_field_selector
153
+ @login_page_password_field_selector || default_password_field_selector
120
154
  end
121
155
 
122
156
  def password_field
123
- if @login_page_password_field_selector
124
- driver.find_element(@login_page_password_field_selector.keys.first, @login_page_password_field_selector.values.first)
125
- else
126
- driver.find_element(default_password_field.keys.first, default_password_field.values.first)
127
- end
157
+ find(password_field_selector)
128
158
  end
129
159
 
130
160
  def default_login_page_submit_button_selector
131
161
  {xpath: '//input[@type="submit" and @value="Login"]'}
132
162
  end
133
163
 
164
+ def login_page_submit_button_selector
165
+ @login_page_submit_button_selector || default_login_page_submit_button_selector
166
+ end
167
+
134
168
  def login_page_submit_button
135
- if @login_page_submit_button_selector
136
- driver.find_element(@login_page_submit_button_selector.keys.first, @login_page_submit_button_selector.values.first)
137
- else
138
- driver.find_element(default_login_page_submit_button_selector.keys.first, default_login_page_submit_button_selector.values.first)
139
- end
169
+ find(login_page_submit_button_selector)
140
170
  end
141
171
 
142
172
  # logging out
@@ -145,11 +175,11 @@ class WebAccount
145
175
  {xpath: '//input[@type="submit" and @value="Logout"]'}
146
176
  end
147
177
 
178
+ def logout_button_selector
179
+ @logout_button_selector || default_logout_button_selector
180
+ end
181
+
148
182
  def logout_button
149
- if @logout_page_submit_button_selector
150
- driver.find_element(@logout_button_selector.keys.first, @logout_button_selector.values.first)
151
- else
152
- driver.find_element(default_logout_button_selector.keys.first, default_logout_button_selector.values.first)
153
- end
183
+ find(logout_button_selector)
154
184
  end
155
185
  end
@@ -0,0 +1,169 @@
1
+ # test/Thoran/Selenium/WebDriver/Driver/Attempt_test.rb
2
+
3
+ # Notes:
4
+ # 1. selenium-webdriver is required first because attempt.rb resolves the Selenium error
5
+ # classes at load time, in TRANSIENT_ERRORS, but does not require them itself.
6
+ # 2. The transient and non-transient errors are listed here rather than read from
7
+ # TRANSIENT_ERRORS, so that these tests state which errors are meant to be retried rather
8
+ # than agreeing with whatever the implementation happens to say.
9
+ # 3. No driver, no browser: the errors are raised directly at a dummy including the module.
10
+
11
+ require 'selenium-webdriver'
12
+ require_relative '../../../../../lib/Thoran/Selenium/WebDriver/Driver/Attempt/attempt'
13
+
14
+ require 'minitest/autorun'
15
+ require 'minitest-spec-context'
16
+
17
+ class AttemptDummy
18
+ include Thoran::Selenium::WebDriver::Driver::Attempt
19
+ end
20
+
21
+ describe Thoran::Selenium::WebDriver::Driver::Attempt do
22
+ TRANSIENT = [
23
+ Selenium::WebDriver::Error::ElementClickInterceptedError,
24
+ Selenium::WebDriver::Error::ElementNotInteractableError,
25
+ Selenium::WebDriver::Error::NoSuchElementError,
26
+ Selenium::WebDriver::Error::StaleElementReferenceError,
27
+ Selenium::WebDriver::Error::TimeoutError,
28
+ Selenium::WebDriver::Error::UnexpectedAlertOpenError,
29
+ Selenium::WebDriver::Error::UnknownError,
30
+ ]
31
+
32
+ NON_TRANSIENT = [
33
+ Selenium::WebDriver::Error::InvalidSelectorError,
34
+ Selenium::WebDriver::Error::NoSuchDriverError,
35
+ ArgumentError,
36
+ ]
37
+
38
+ before do
39
+ @dummy = AttemptDummy.new
40
+ end
41
+
42
+ context "when the block succeeds" do
43
+ it "returns the value of the block" do
44
+ expect(@dummy.attempt{42}).must_equal(42)
45
+ end
46
+
47
+ it "yields itself, so that the documented block argument is the receiver" do
48
+ yielded = nil
49
+ @dummy.attempt{|driver| yielded = driver}
50
+ expect(yielded).must_be_same_as(@dummy)
51
+ end
52
+
53
+ it "calls the block once" do
54
+ calls = 0
55
+ @dummy.attempt{calls += 1}
56
+ expect(calls).must_equal(1)
57
+ end
58
+
59
+ it "says nothing" do
60
+ _out, err = capture_io{@dummy.attempt{42}}
61
+ expect(err).must_be_empty
62
+ end
63
+ end
64
+
65
+ context "when the block raises a transient error" do
66
+ TRANSIENT.each do |error_class|
67
+ it "retries #{error_class}" do
68
+ calls = 0
69
+ capture_io do
70
+ @dummy.attempt(3) do
71
+ calls += 1
72
+ raise error_class if calls < 3
73
+ end
74
+ end
75
+ expect(calls).must_equal(3)
76
+ end
77
+ end
78
+
79
+ it "returns the value of the attempt which succeeds" do
80
+ calls = 0
81
+ value = nil
82
+ capture_io do
83
+ value = @dummy.attempt(3) do
84
+ calls += 1
85
+ raise Selenium::WebDriver::Error::StaleElementReferenceError if calls < 2
86
+ :recovered
87
+ end
88
+ end
89
+ expect(value).must_equal(:recovered)
90
+ end
91
+
92
+ it "re-raises once max_attempts is reached" do
93
+ capture_io do
94
+ expect do
95
+ @dummy.attempt(3){raise Selenium::WebDriver::Error::UnknownError}
96
+ end.must_raise(Selenium::WebDriver::Error::UnknownError)
97
+ end
98
+ end
99
+
100
+ it "makes exactly max_attempts attempts before re-raising" do
101
+ calls = 0
102
+ capture_io do
103
+ begin
104
+ @dummy.attempt(3){calls += 1; raise Selenium::WebDriver::Error::UnknownError}
105
+ rescue Selenium::WebDriver::Error::UnknownError
106
+ end
107
+ end
108
+ expect(calls).must_equal(3)
109
+ end
110
+
111
+ it "honours a supplied max_attempts" do
112
+ calls = 0
113
+ capture_io do
114
+ begin
115
+ @dummy.attempt(5){calls += 1; raise Selenium::WebDriver::Error::UnknownError}
116
+ rescue Selenium::WebDriver::Error::UnknownError
117
+ end
118
+ end
119
+ expect(calls).must_equal(5)
120
+ end
121
+
122
+ it "defaults to three attempts" do
123
+ calls = 0
124
+ capture_io do
125
+ begin
126
+ @dummy.attempt{calls += 1; raise Selenium::WebDriver::Error::UnknownError}
127
+ rescue Selenium::WebDriver::Error::UnknownError
128
+ end
129
+ end
130
+ expect(calls).must_equal(3)
131
+ end
132
+
133
+ it "warns once per failed attempt, naming the error" do
134
+ _out, err = capture_io do
135
+ begin
136
+ @dummy.attempt(2){raise Selenium::WebDriver::Error::StaleElementReferenceError}
137
+ rescue Selenium::WebDriver::Error::StaleElementReferenceError
138
+ end
139
+ end
140
+ expect(err.lines.size).must_equal(2)
141
+ expect(err).must_match(/Attempt 1 of 2 failed: Selenium::WebDriver::Error::StaleElementReferenceError/)
142
+ expect(err).must_match(/Attempt 2 of 2 failed: Selenium::WebDriver::Error::StaleElementReferenceError/)
143
+ end
144
+ end
145
+
146
+ context "when the block raises a non-transient error" do
147
+ NON_TRANSIENT.each do |error_class|
148
+ it "does not retry #{error_class}" do
149
+ calls = 0
150
+ capture_io do
151
+ expect do
152
+ @dummy.attempt(3){calls += 1; raise error_class}
153
+ end.must_raise(error_class)
154
+ end
155
+ expect(calls).must_equal(1)
156
+ end
157
+ end
158
+
159
+ it "says nothing, since it did not retry" do
160
+ _out, err = capture_io do
161
+ begin
162
+ @dummy.attempt(3){raise Selenium::WebDriver::Error::InvalidSelectorError}
163
+ rescue Selenium::WebDriver::Error::InvalidSelectorError
164
+ end
165
+ end
166
+ expect(err).must_be_empty
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,47 @@
1
+ require_relative './lib/WebAccount/VERSION'
2
+
3
+ class Gem::Specification
4
+ def dependencies=(gems)
5
+ gems.each{|gem| add_dependency(*gem)}
6
+ end
7
+
8
+ def development_dependencies=(gems)
9
+ gems.each{|gem| add_development_dependency(*gem)}
10
+ end
11
+ end
12
+
13
+ Gem::Specification.new do |spec|
14
+ spec.name = 'webaccount'
15
+ spec.version = WebAccount::VERSION
16
+
17
+ spec.summary = "An abstract super-class for navigating a web-based user account via Selenium WebDriver."
18
+ spec.description = "Easily log into web accounts by supplying just a username and password, the URL of the login page, and a few page ids to a sub-class of WebAccount."
19
+
20
+ spec.author = 'thoran'
21
+ spec.email = 'code@thoran.com'
22
+ spec.homepage = 'http://github.com/thoran/WebAccount'
23
+ spec.license = 'MIT'
24
+
25
+ spec.require_paths = ['lib']
26
+ spec.required_ruby_version = '>= 3.3'
27
+
28
+ spec.files = [
29
+ 'CHANGELOG',
30
+ 'Gemfile',
31
+ Dir['lib/**/*.rb'],
32
+ 'Rakefile',
33
+ 'README.md',
34
+ Dir['test/**/*.rb'],
35
+ 'webaccount.gemspec'
36
+ ].flatten
37
+
38
+ spec.dependencies = [
39
+ ['selenium-webdriver', '>= 4.0']
40
+ ]
41
+
42
+ spec.development_dependencies = %w{
43
+ minitest
44
+ minitest-spec-context
45
+ rake
46
+ }
47
+ end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: webaccount
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
  - thoran
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-04-07 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: selenium-webdriver
@@ -16,9 +15,51 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '0'
18
+ version: '4.0'
20
19
  type: :runtime
21
20
  prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '4.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: minitest
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: minitest-spec-context
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
22
63
  version_requirements: !ruby/object:Gem::Requirement
23
64
  requirements:
24
65
  - - ">="
@@ -31,23 +72,22 @@ executables: []
31
72
  extensions: []
32
73
  extra_rdoc_files: []
33
74
  files:
34
- - lib/Thoran/Array/AllButFirst/all_but_first.rb
35
- - lib/Thoran/Array/FirstX/firstX.rb
75
+ - CHANGELOG
76
+ - Gemfile
77
+ - README.md
78
+ - Rakefile
36
79
  - lib/Thoran/Selenium.rb
37
80
  - lib/Thoran/Selenium/WebDriver/Driver/Attempt/attempt.rb
38
- - lib/Thoran/Selenium/WebDriver/Remote/W3C/Bridge/ConvertLocators/convert_locators.rb
39
- - lib/Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
40
- - lib/Thoran/Selenium/WebDriver/SearchContext/ElementsPresentQ/elements_presentQ.rb
41
- - lib/Thoran/Selenium/WebDriver/Setup/setup.rb
42
- - lib/Thoran/String/ToConst/to_const.rb
43
- - lib/WebAccount.rb
81
+ - lib/Thoran/Selenium/WebDriver/Driver/ElementPresentQ/element_presentQ.rb
44
82
  - lib/WebAccount/VERSION.rb
45
83
  - lib/web_account.rb
84
+ - lib/webaccount.rb
85
+ - test/Thoran/Selenium/WebDriver/Driver/Attempt_test.rb
86
+ - webaccount.gemspec
46
87
  homepage: http://github.com/thoran/WebAccount
47
88
  licenses:
48
- - Ruby
89
+ - MIT
49
90
  metadata: {}
50
- post_install_message:
51
91
  rdoc_options: []
52
92
  require_paths:
53
93
  - lib
@@ -55,15 +95,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
55
95
  requirements:
56
96
  - - ">="
57
97
  - !ruby/object:Gem::Version
58
- version: '2.5'
98
+ version: '3.3'
59
99
  required_rubygems_version: !ruby/object:Gem::Requirement
60
100
  requirements:
61
101
  - - ">="
62
102
  - !ruby/object:Gem::Version
63
103
  version: '0'
64
104
  requirements: []
65
- rubygems_version: 3.5.6
66
- signing_key:
105
+ rubygems_version: 4.0.16
67
106
  specification_version: 4
68
107
  summary: An abstract super-class for navigating a web-based user account via Selenium
69
108
  WebDriver.
@@ -1,28 +0,0 @@
1
- # Thoran/Array/AllButFirst/all_but_first.rb
2
- # Thoran::Array::AllButFirst#all_but_first
3
-
4
- # 20141223
5
- # 0.1.0
6
-
7
- # Description: This returns a copy of the receiving array with the first element removed.
8
-
9
- # Changes:
10
- # 1. + Thoran namespace.
11
-
12
- require 'Thoran/Array/FirstX/firstX'
13
-
14
- module Thoran
15
- module Array
16
- module AllButFirst
17
-
18
- def all_but_first
19
- d = self.dup
20
- d.first!
21
- d
22
- end
23
-
24
- end
25
- end
26
- end
27
-
28
- Array.send(:include, Thoran::Array::AllButFirst)
@@ -1,21 +0,0 @@
1
- # Thoran/Array/FirstX/firstX.rb
2
- # Thoran::Array::FirstX#first!
3
-
4
- # 20141223
5
- # 0.1.0
6
-
7
- # Description: Sometimes it makes more sense to treat arrays this way.
8
-
9
- module Thoran
10
- module Array
11
- module FirstX
12
-
13
- def first!
14
- shift
15
- end
16
-
17
- end
18
- end
19
- end
20
-
21
- Array.send(:include, Thoran::Array::FirstX)
@@ -1,51 +0,0 @@
1
- # Thoran/Selenium/WebDriver/Remote/W3C/BridgeMonkeyPatch/ConvertLocators.convert_locators.rb
2
- # Thoran::Selenium::WebDriver::Remote::W3C::BridgeMonkeyPatch::ConvertLocators.convert_locators
3
-
4
- # 20200418
5
- # 0.0.0
6
-
7
- # Changes:
8
- # 1. + Thoran namespace.
9
-
10
- module Thoran
11
- module Selenium
12
- module WebDriver
13
- module Remote
14
- module W3C
15
- module Bridge
16
- module ConvertLocators
17
-
18
- def convert_locators(how, what)
19
- case how.to_s
20
- when 'class name'
21
- how = 'css selector'
22
- what = ".#{escape_css(what)}"
23
- when 'id'
24
- how = 'css selector'
25
- what = "##{escape_css(what)}"
26
- when 'name'
27
- how = 'css selector'
28
- what = "*[name='#{escape_css(what)}']"
29
- when 'tag name'
30
- how = 'css selector'
31
- end
32
- [how, what]
33
- end
34
-
35
- end
36
- end
37
- end
38
- end
39
- end
40
- end
41
- end
42
-
43
- module Selenium
44
- module WebDriver
45
- module Remote
46
- module W3C
47
- Bridge.prepend(Thoran::Selenium::WebDriver::Remote::W3C::Bridge::ConvertLocators)
48
- end
49
- end
50
- end
51
- end
@@ -1,39 +0,0 @@
1
- # Thoran/Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ.rb
2
- # Thoran::Selenium::WebDriver::SearchContext::ElementPresentQ.element_present?
3
-
4
- # 20200418
5
- # 0.0.0
6
-
7
- # Examples:
8
- # 1. driver.element_present?(:xpath, '//a[@id="submit"]')
9
- # 2. driver.element_present?(:id, @logout_button_id)
10
-
11
- # Changes:
12
- # 1. + Thoran namespace.
13
-
14
- module Thoran
15
- module Selenium
16
- module WebDriver
17
- module SearchContext
18
- module ElementPresentQ
19
-
20
- def element_present?(selector_type, selector)
21
- bridge.find_element_by(selector_type, selector)
22
- true
23
- rescue ::Selenium::WebDriver::Error::NoSuchElementError
24
- false
25
- end
26
-
27
- end
28
- end
29
- end
30
- end
31
- end
32
-
33
- module Selenium
34
- module WebDriver
35
- class Driver
36
- include Thoran::Selenium::WebDriver::SearchContext::ElementPresentQ
37
- end
38
- end
39
- end
@@ -1,39 +0,0 @@
1
- # Thoran/Selenium/WebDriver/SearchContext/ElementsPresentQ/elements_presentQ.rb
2
- # Thoran::Selenium::WebDriver::SearchContext::ElementsPresentQ.elements_present?
3
-
4
- # 20200418
5
- # 0.0.0
6
-
7
- # Examples:
8
- # 1. driver.elements_present?(:xpath, '//li[@class="item"]')
9
- # 2. driver.elements_present?(:css, 'li.item')
10
-
11
- # Changes:
12
- # 1. + Thoran namespace.
13
-
14
- module Thoran
15
- module Selenium
16
- module WebDriver
17
- module SearchContext
18
- module ElementsPresentQ
19
-
20
- def elements_present?(selector_type, selector)
21
- bridge.find_elements_by(selector_type, selector)
22
- true
23
- rescue ::Selenium::WebDriver::Error::NoSuchElementError
24
- false
25
- end
26
-
27
- end
28
- end
29
- end
30
- end
31
- end
32
-
33
- module Selenium
34
- module WebDriver
35
- class Driver
36
- include Thoran::Selenium::WebDriver::SearchContext::ElementsPresentQ
37
- end
38
- end
39
- end
@@ -1,66 +0,0 @@
1
- # Thoran/Selenium/WebDriver/Setup/setup.rb
2
- # Thoran::Selenium::WebDriver.setup
3
-
4
- # 20200419
5
- # 0.0.1
6
-
7
- # Usage:
8
- # 1. Binary is in the path:
9
- # Selenium::WebDriver.setup('chrome') # String - lowercase
10
- # Selenium::WebDriver.setup('Chrome') # String - capitalised
11
- # Selenium::WebDriver.setup(:chrome) # Symbol - lowercase
12
- # Selenium::WebDriver.setup(:Chrome) # Symbol - capitalised
13
- # 2. Binary is not in the path:
14
- # Selenium::WebDriver.setup('~/Applications/Firefox.app/Contents/MacOS/firefox-bin')
15
-
16
- # Changes:
17
- # 1. + Thoran namespace.
18
-
19
- require 'selenium-webdriver'
20
- require 'Thoran/String/ToConst/to_const'
21
-
22
- module Thoran
23
- module Selenium
24
- module WebDriver
25
- module Setup
26
-
27
- def setup(path_or_browser)
28
- if File.exist?(File.expand_path(path_or_browser.to_s))
29
- path = path_or_browser
30
- at(path)
31
- ::Selenium::WebDriver.for(selenium_browser(path))
32
- else
33
- browser = path_or_browser.to_sym.downcase
34
- ::Selenium::WebDriver.for(browser)
35
- end
36
- end
37
-
38
- private
39
-
40
- def at(path)
41
- selenium_binary_constant(path).path = File.expand_path(path)
42
- end
43
-
44
- def selenium_browser(path)
45
- case path
46
- when /chrome/i; :chrome
47
- when /firefox/i; :firefox
48
- when /safari/i; :safari
49
- end
50
- end
51
-
52
- # This method seem to be necessary only to Firefox, since there are no equivalent constants for Safari or Chrome.
53
- def selenium_binary_constant(path)
54
- "Selenium::WebDriver::#{selenium_browser(path).to_s.capitalize}::Binary".to_const
55
- end
56
-
57
- end
58
- end
59
- end
60
- end
61
-
62
- module Selenium
63
- module WebDriver
64
- extend Thoran::Selenium::WebDriver::Setup
65
- end
66
- end
@@ -1,47 +0,0 @@
1
- # Thoran/String/ToConst/to_const.rb
2
- # Thoran::String::ToConst#to_const
3
-
4
- # 20141223
5
- # 0.2.0
6
-
7
- # Description: This takes a string and returns a constant, with unlimited namespacing.
8
-
9
- # History: Derived from Object#to_const 0.3.0, and superceding Object#to_const.
10
-
11
- # Changes:
12
- # 1. + Thoran namespace.
13
-
14
- # Todo:
15
- # 1. This only works for two levels of constants. Three and you're stuffed. So, this needs to be recursive...
16
- # Done iteratively as of 0.1.0.
17
- # 2. Make this work for symbols. However, this will only work if there's no namespacing. ie. :A is OK, but :A::B is not.
18
-
19
- # Discussion:
20
- # 1. Should this go separately into classes for which ::const_get will work and be removed from Object? Done as of 0.1.0.
21
-
22
- require 'Thoran/Array/AllButFirst/all_but_first'
23
-
24
- module Thoran
25
- module String
26
- module ToConst
27
-
28
- def to_const
29
- if self =~ /::/
30
- constants = self.split('::')
31
- constant = Object.const_get(constants.first)
32
- constants = constants.all_but_first
33
- until constants.empty? do
34
- constant = constant.const_get(constants.shift)
35
- end
36
- else
37
- constant = Object.const_get(self)
38
- end
39
- constant
40
- end
41
- alias_method :to_constant, :to_const
42
-
43
- end
44
- end
45
- end
46
-
47
- String.send(:include, Thoran::String::ToConst)