moby.rb 1.0.1 → 2.0.1

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: 8a52161eceecdfce20494ae2ff6b8e8fa8aefceb1d6911db3c2fd3bac475896f
4
- data.tar.gz: 6242e8451d3d66b70b9452dc305329c121e97a8e1d868cb6744228208d2e8da6
3
+ metadata.gz: 581350e2303d9070c849e3768aa3d6bb6fb043326d41fdf9d2203f89d054029b
4
+ data.tar.gz: 32a0549736c2716df0361c51072df302a52757c51a7d1b4446beb76d421443d2
5
5
  SHA512:
6
- metadata.gz: 40ccfc56a276bb2c2ef6ad12e903dd2791f5be79f857ad4f6718c846b848ddee3ee62974f0a60e7a56e2cd8560f6761ccd53a5aecad3919fb6b4e9bc0274d752
7
- data.tar.gz: dd7eba42a50b08e714407079b8191348f8f18d0ee389881dfce367af78d185c6b0a16570fa484d57362df8a23ec1278986ebb970c3815db897432e11b51b3f7f
6
+ metadata.gz: 17f1bd8bee97352f64560d545039653cb369cd52602254bc583aa008f1876298360a6b2bda370828399f5f0697a8ef8caee4190931fcd066690c3754b73dbc68
7
+ data.tar.gz: 5aa838cc20cad5e7bf8c22ff3b93b6c69edfed285b59a2c5e8836a9c02c25bad2aa2bedf3d98c0580cfd2a2246b043ea5b0951a1879e21f44f5a65f01275c359
data/CHANGELOG CHANGED
@@ -1,5 +1,36 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 20260812
4
+
5
+ 2.0.1: Tidy the gemspec and guard it with a test.
6
+
7
+ 1. ~ moby.rb.gemspec: declare the dependencies as data — spec.dependencies = [...] and spec.development_dependencies = [...], via setters added in a local reopening of Gem::Specification — in place of the add_dependency/add_development_dependency calls; - spec.date, which was a stale literal (2.0.0 shipped the gem with it), letting RubyGems' default stand (rubygems.org uses the push time regardless).
8
+ 2. + test/gemspec_test.rb: loads moby.rb.gemspec and checks that it validates, pins no date (spec.date left at RubyGems' default), takes its version from Moby::VERSION, and declares the expected runtime and development dependencies — a guard for the declarative gemspec above, whose custom setters are where a dependency could silently go missing, and against re-pinning the stale date that 2.0.0 shipped with.
9
+ 3. ~ lib/Moby/VERSION.rb: VERSION: /2.0.0/2.0.1/
10
+ 4. ~ test/Moby/VERSION_test.rb: /2.0.0/2.0.1/
11
+
12
+
13
+ ## 20260811
14
+
15
+ 2.0.0: Move the Mechanize engine into a pluggable backend and add a Selenium backend alongside it.
16
+
17
+ 1. ~ lib/Moby.rb: the monolithic Moby was the whole Mechanize engine; it becomes a thin facade that builds a Configuration, a RandomInputGenerator and the chosen backend, and delegates #counter_phish to it. The submission loop, the page, form and field finding, and the random credential generation all move out into the classes below.
18
+ 2. + lib/Moby/Backends/MechanizeBackend.rb: the Mechanize functionality, moved out of Moby and behind a backend interface, driven by Configuration and RandomInputGenerator.
19
+ 3. + lib/Moby/Backends/SeleniumBackend.rb: a second backend driving a real headless browser (Chrome or Firefox), for forms that filter non-browser clients so the Mechanize submissions never land.
20
+ 4. + lib/Moby/Backends.rb: Moby::Backends.for, which chooses the backend from the configuration: :mechanize (the default) or :selenium.
21
+ 5. + lib/Moby/Configuration.rb: Moby::Configuration, a settings object for a run (url, backend, browser, form and field identification, username generation, iterations, delay, word list).
22
+ 6. + lib/Moby/RandomInputGenerator.rb: Moby::RandomInputGenerator, the fake-credential generation (random username and password, optionally email-shaped), moved out of Moby.
23
+ 7. + lib/Selenium/ and lib/Thoran/Selenium/: Selenium::WebDriver::Driver#attempt (retry a block) and #element_present?, used by the Selenium backend.
24
+ 8. ~ bin/moby: builds a Configuration and runs the selected backend through the facade; + --backend, + --browser, + --iterations (moby runs until interrupted when it is omitted), + --delay; and the previously inert --username-prefilled is wired through, so only the password is submitted when the username is already on the form.
25
+ 9. - lib/File/self.collect.rb, - lib/Thoran/File/SelfCollect/self.collect.rb: File.collect was used only by the old Moby; RandomInputGenerator reads the word list with File.readlines.
26
+ 10. ~ test/moby_test.rb: it exercised the removed Moby monolith; rewritten to test the Moby facade (backend selection). + test/Moby/Configuration_test.rb, RandomInputGenerator_test.rb, Backends_test.rb, Backends/MechanizeBackend_test.rb and Backends/SeleniumBackend_test.rb.
27
+ 11. ~ moby.rb.gemspec: + selenium-webdriver (~> 4).
28
+ 12. ~ moby.rb.gemspec: required_ruby_version: /2.5.0/3.3.0/, which selenium-webdriver 4 requires.
29
+ 13. ~ Gemfile: + ostruct, which switches.rb requires and which is no longer a default gem as of Ruby 4.0.
30
+ 14. ~ lib/Moby/VERSION.rb: VERSION: /1.0.1/2.0.0/
31
+ 15. ~ test/Moby/VERSION_test.rb: /1.0.1/2.0.0/
32
+
33
+
3
34
  ## 20260807
4
35
 
5
36
  1.0.1: Add a CHANGELOG in the form changelogger writes, and retire CHANGES.txt.
@@ -9,6 +40,7 @@
9
40
  3. ~ lib/Moby/VERSION.rb: VERSION: /1.0.0/1.0.1/
10
41
  4. ~ test/Moby/VERSION_test.rb: /1.0.0/1.0.1/
11
42
 
43
+
12
44
  ## 20260108
13
45
 
14
46
  1.0.0: Ensure that tests are in place and that version handling is working properly.
@@ -23,6 +55,7 @@
23
55
  8. ~ CHANGES.txt: Reorganised in descending order and added dates.
24
56
  9. ~ LICENSE.txt: Updated the years to include the year this was started: /2026/2006-2026/
25
57
 
58
+
26
59
  ## 20260105
27
60
 
28
61
  0.9.1: Fix syntax errors in README.md.
@@ -30,10 +63,12 @@
30
63
  1. ~ README.md: Fix syntax errors.
31
64
  2. ~ lib/Moby.rb: VERSION_STRING: /0.9.0/0.9.1/
32
65
 
66
+
33
67
  ## 20240605
34
68
 
35
69
  0.9.0: Merge of 2019's 0.8.0 and 2023's 0.8.1.
36
70
 
71
+
37
72
  ## 20231206
38
73
 
39
74
  0.8.1: Fix when username or password field number is specified or nothing is specified.
@@ -41,6 +76,7 @@
41
76
  1. ~ lib/Whale: ~ set_username_field(): Fix when username field number is specified or nothing is specified.
42
77
  2. ~ lib/Whale: ~ set_password_field(): Fix when password field number is specified or nothing is specified.
43
78
 
79
+
44
80
  ## 20231119
45
81
 
46
82
  0.8.0: An accidental fork of 0.7.0 in 2023.
@@ -54,18 +90,21 @@
54
90
  7. ~ lib/Whale.rb: general tidying
55
91
  8. + lib/File/self.collect.rb
56
92
 
93
+
57
94
  ## 20190820
58
95
 
59
96
  0.7.4: Rescue Net::OpenTimeout in the submission loop.
60
97
 
61
98
  1. ~ counter_phish(): + rescue Net::OpenTimeout.
62
99
 
100
+
63
101
  ## 20190819
64
102
 
65
103
  0.7.3: Rescue Net::HTTP::Persistent::Error in place of EOFError.
66
104
 
67
105
  1. ~ counter_phish(): rescue Net::HTTP::Persistent::Error in place of EOFError.
68
106
 
107
+
69
108
  ## 20190819
70
109
 
71
110
  0.7.2: Set field values on the field objects rather than through the setter methods.
@@ -75,6 +114,7 @@
75
114
  3. ~ user_agent: attr_writer in place of attr_accessor.
76
115
  4. ~ counter_phish(): verbose reports username_field.value:password_field.value.
77
116
 
117
+
78
118
  ## 20190819
79
119
 
80
120
  0.7.1: Assign to the field's value rather than replacing the field object.
@@ -82,6 +122,7 @@
82
122
  1. ~ set_username_field()/set_password_field(): set the field's value in place of replacing the field object at form.fields[n].
83
123
  2. ~ counter_phish(): + rescue EOFError; + pp page in debug.
84
124
 
125
+
85
126
  ## 20190815
86
127
 
87
128
  0.7.0: The earliest recoverable version.
data/Gemfile CHANGED
@@ -1,3 +1,7 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
3
  gemspec
4
+
5
+ # switches.rb (the CLI option parser) requires ostruct, which is no longer a
6
+ # default gem as of Ruby 4.0, so it must be provided explicitly under bundler.
7
+ gem 'ostruct'
data/bin/moby CHANGED
@@ -11,12 +11,16 @@ require 'switches.rb'
11
11
 
12
12
  def switches
13
13
  Switches.new do |s|
14
- s.banner = 'Usage: whale <url> and any of the following options...'
14
+ s.banner = 'Usage: moby <url> and any of the following options...'
15
15
  s.set(:a, :user_agent){'What webclient I should pretend to be.'}
16
+ s.set(:b, :backend){'Which backend to use: mechanize (default) or selenium.'}
17
+ s.set(:browser){'Which browser the selenium backend should drive: chrome (default) or firefox.'}
16
18
  s.set(:debug?){'Show debugging information.'}
19
+ s.set(:delay){'Seconds to wait between submissions.'}
17
20
  s.set(:e?, :username_is_email_address?){'Automatically generate a username which is in the form of an email address.'}
18
21
  s.set(:f, :form, :form_name){'If left empty, the first form on the page will be used.'}
19
- s.set(:help){'Usage: whale <url> and any of the following options...'}
22
+ s.set(:help){'Usage: moby <url> and any of the following options...'}
23
+ s.set(:iterations){'How many submissions to make. If left empty, moby runs until interrupted.'}
20
24
  s.set(:n, :form_number){'This will set the number of the form in order of appearance on the page. Use zero-based indexing.'}
21
25
  s.set(:pass_field, :password_field, :pass_field_name, :password_field_name){'The name of the password field.'}
22
26
  s.set(:pass_field_number, :password_field_number){'The position of the password field in relation to the form it is attached to. Use zero-based indexing.'}
@@ -24,7 +28,7 @@ def switches
24
28
  s.set(:user_field, :username_field, :user_field_name, :username_field_name){'The name of the username field.'}
25
29
  s.set(:user_field_number, :username_field_number){'The position of the username field in relation to the form it is attached to. Use zero-based indexing.'}
26
30
  s.set(:username_hostname){'The hostname to be used when the username is an email address.'}
27
- s.set(:username_prefilled){'No need to supply an email address in the form.'}
31
+ s.set(:username_prefilled?){'The username is already filled in on the form, so only submit the password.'}
28
32
  s.set(:v?, :verbose?){'Outputs each submission as it goes.'}
29
33
  s.perform(:version) do
30
34
  puts "moby #{Moby::VERSION}"
@@ -35,31 +39,24 @@ end
35
39
 
36
40
  def main
37
41
  args = switches.to_h
38
- debug = args[:debug?]
39
- form_name = args[:form_name]
40
- form_number = args[:form_number]
41
- password_field_name = args[:password_field_name]
42
- password_field_number = args[:password_field_number]
43
- url = args[:url]
44
- user_agent = args[:user_agent]
45
- username_field_name = args[:username_field_name]
46
- username_field_number = args[:username_field_number]
47
- username_hostname = args[:username_hostname]
48
- username_is_email_address = args[:username_is_email_address?]
49
- verbose = args[:verbose?]
50
42
  moby = Moby.new(
51
- debug: debug,
52
- form_name: form_name,
53
- form_number: form_number,
54
- password_field_name: password_field_name,
55
- password_field_number: password_field_number,
56
- url: url,
57
- user_agent: user_agent,
58
- username_field_name: username_field_name,
59
- username_field_number: username_field_number,
60
- username_hostname: username_hostname,
61
- username_is_email_address: username_is_email_address,
62
- verbose: verbose
43
+ backend: args[:backend],
44
+ browser: args[:browser],
45
+ debug: args[:debug?],
46
+ delay: args[:delay],
47
+ form_name: args[:form_name],
48
+ form_number: args[:form_number],
49
+ iterations: args[:iterations],
50
+ password_field_name: args[:password_field_name],
51
+ password_field_number: args[:password_field_number],
52
+ url: args[:url],
53
+ user_agent: args[:user_agent],
54
+ username_field_name: args[:username_field_name],
55
+ username_field_number: args[:username_field_number],
56
+ username_hostname: args[:username_hostname],
57
+ username_is_email_address: args[:username_is_email_address?],
58
+ username_prefilled: args[:username_prefilled?],
59
+ verbose: args[:verbose?]
63
60
  )
64
61
  moby.counter_phish
65
62
  end
@@ -0,0 +1,157 @@
1
+ # lib/moby/backends/mechanize_backend.rb
2
+ # Moby::Backends::MechanizeBackend
3
+
4
+ require 'mechanize'
5
+ require 'pp'
6
+
7
+ class Moby
8
+ module Backends
9
+ class MechanizeBackend
10
+ def counter_phish
11
+ set_up_agent
12
+ load_page
13
+ repeatedly_fill_and_submit_login_form
14
+ end
15
+
16
+ private
17
+
18
+ def initialize(config:, random_input_generator:)
19
+ @config = config
20
+ @random_input_generator = random_input_generator
21
+ end
22
+
23
+ def agent
24
+ @agent ||= (
25
+ puts "Setting up Mechanize agent..." if @config.verbose?
26
+ agent = Mechanize.new
27
+ puts "Agent setup complete." if @config.verbose?
28
+ agent
29
+ )
30
+ end
31
+ alias_method :set_up_agent, :agent
32
+
33
+ def page
34
+ @page ||= (
35
+ puts "Navigating to #{@config.url}..." if @config.verbose?
36
+ page = agent.get(@config.url)
37
+ puts "Page loaded successfully." if @config.verbose?
38
+ page
39
+ )
40
+ end
41
+ alias_method :load_page, :page
42
+
43
+ def form
44
+ @form ||= (
45
+ if @config.using_form_name?
46
+ find_form_by_name
47
+ else
48
+ find_form_by_number
49
+ end
50
+ )
51
+ end
52
+
53
+ def repeatedly_fill_and_submit_login_form
54
+ start_time = Time.now
55
+ puts "moby session begun #{start_time}."
56
+ submission_count = 0
57
+ begin
58
+ if @config.fixed_number_of_submissions?
59
+ @config.iterations.times do |iteration|
60
+ fill_and_submit_login_form
61
+ submission_count += 1
62
+ puts "Iteration #{submission_count} of #{@config.iterations}." if @config.verbose?
63
+ end
64
+ puts "Successfully completed #{@config.iterations} submissions." if @config.verbose?
65
+ else
66
+ loop do
67
+ fill_and_submit_login_form
68
+ submission_count += 1
69
+ puts "#{submission_count} #{username}:#{password}" if @config.verbose?
70
+ end
71
+ end
72
+ ensure
73
+ finish_time = Time.now
74
+ time_delta_in_minutes = (finish_time - start_time) / 60
75
+ submissions_per_minute = submission_count / time_delta_in_minutes
76
+ puts "moby session terminated #{finish_time} with #{submission_count} counter-phishes served in #{time_delta_in_minutes} minutes for an average of #{submissions_per_minute} submissions per minute."
77
+ end
78
+ end
79
+
80
+ def fill_and_submit_login_form
81
+ puts "Filling with: #{username}:#{password}" if @config.verbose?
82
+ agent.user_agent_alias = @config.user_agent
83
+ username_field.value = username unless @config.username_prefilled?
84
+ password_field.value = password
85
+ pp page if @config.debug?
86
+ result = agent.submit(form)
87
+ pp result if @config.debug?
88
+ sleep @config.delay if @config.delay > 0
89
+ rescue Net::HTTP::Persistent::Error
90
+ puts "\n\nNet::HTTP::Persistent::Error rescued.\n\n" if @config.verbose?
91
+ rescue Net::OpenTimeout
92
+ puts "\n\nNet::OpenTimeout rescued.\n\n" if @config.verbose?
93
+ end
94
+
95
+ def find_form_by_name
96
+ page.form_with(name: @config.form_name) ||
97
+ page.form_with(id: @config.form_name) ||
98
+ raise("Could not find a form with the name or id of #{@config.form_name}.")
99
+ end
100
+
101
+ def find_form_by_number
102
+ page.forms[@config.form_number] ||
103
+ raise("Could not find a form at index #{@config.form_number}.")
104
+ end
105
+
106
+ def username
107
+ @random_input_generator.random_username
108
+ end
109
+
110
+ def password
111
+ @random_input_generator.random_password
112
+ end
113
+
114
+ def username_field
115
+ @username_field ||=(
116
+ if @config.using_username_field_name?
117
+ find_username_field_by_name
118
+ else
119
+ find_username_field_by_number
120
+ end
121
+ )
122
+ end
123
+
124
+ def password_field
125
+ @password_field ||= (
126
+ if @config.using_password_field_name?
127
+ find_password_field_by_name
128
+ else
129
+ find_password_field_by_number
130
+ end
131
+ )
132
+ end
133
+
134
+ def find_username_field_by_name
135
+ form.field_with(name: @config.username_field_name) ||
136
+ form.field_with(id: @config.username_field_name) ||
137
+ raise("Could not find username field: #{@config.username_field_name}")
138
+ end
139
+
140
+ def find_username_field_by_number
141
+ form.fields[@config.username_field_number] ||
142
+ raise("Could not find username field at index #{@config.username_field_number}")
143
+ end
144
+
145
+ def find_password_field_by_name
146
+ form.field_with(name: @config.password_field_name) ||
147
+ form.field_with(id: @config.password_field_name) ||
148
+ raise("Could not find password field: #{@config.password_field_name}.")
149
+ end
150
+
151
+ def find_password_field_by_number
152
+ form.fields[@config.password_field_number] ||
153
+ raise("Could not find password field at index: #{@config.password_field_number}.")
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,242 @@
1
+ # lib/Moby/Backends/SeleniumBackend.rb
2
+ # Moby::Backends::SeleniumBackend
3
+
4
+ require 'selenium-webdriver'
5
+
6
+ require_relative '../../Selenium/WebDriver/Driver/Attempt/attempt'
7
+ require_relative '../../Selenium/WebDriver/SearchContext/ElementPresentQ/element_presentQ'
8
+
9
+ class Moby
10
+ module Backends
11
+ class SeleniumBackend
12
+ def counter_phish
13
+ set_up_driver
14
+ navigate_to_target
15
+ repeatedly_fill_and_submit_login_form
16
+ ensure
17
+ cleanup_driver unless @config.debug?
18
+ end
19
+
20
+ private
21
+
22
+ def initialize(config:, random_input_generator:)
23
+ @config = config
24
+ @random_input_generator = random_input_generator
25
+ @driver = nil
26
+ end
27
+
28
+ def driver
29
+ @driver ||= (
30
+ puts "Setting up #{@config.browser} driver..." if @config.verbose
31
+ driver =
32
+ case @config.browser.to_sym
33
+ when :chrome
34
+ options = Selenium::WebDriver::Chrome::Options.new
35
+ options.add_argument('--headless') unless @config.debug
36
+ options.add_argument('--disable-gpu')
37
+ options.add_argument('--no-sandbox')
38
+ options.add_argument("user-agent=#{@config.user_agent}") if @config.user_agent
39
+ Selenium::WebDriver.for(:chrome, options: options)
40
+ when :firefox
41
+ options = Selenium::WebDriver::Firefox::Options.new
42
+ options.add_argument('--headless') unless @config.debug
43
+ options.add_preference('general.useragent.override', @config.user_agent) if @config.user_agent
44
+ Selenium::WebDriver.for(:firefox, options: options)
45
+ else
46
+ Selenium::WebDriver.for(@config.browser)
47
+ end
48
+ puts "Driver setup complete." if @config.verbose?
49
+ driver
50
+ )
51
+ end
52
+ alias_method :set_up_driver, :driver
53
+
54
+ def navigate_to_target
55
+ puts "Navigating to #{@config.url}..." if @config.verbose?
56
+ @driver.attempt do
57
+ @driver.get(@config.url)
58
+ wait_for_page_load
59
+ end
60
+ puts "Page loaded successfully." if @config.verbose?
61
+ end
62
+
63
+ def wait_for_page_load
64
+ wait = Selenium::WebDriver::Wait.new(timeout: 10)
65
+ wait.until do
66
+ @driver.execute_script('return document.readyState') == 'complete'
67
+ end
68
+ end
69
+
70
+ def form
71
+ @form ||= (
72
+ if @config.using_form_name?
73
+ find_form_by_name
74
+ else
75
+ find_form_by_number
76
+ end
77
+ )
78
+ end
79
+
80
+ def repeatedly_fill_and_submit_login_form
81
+ start_time = Time.now
82
+ puts "moby session begun #{start_time}."
83
+ submission_count = 0
84
+ begin
85
+ if @config.fixed_number_of_submissions?
86
+ @config.iterations.times do |iteration|
87
+ fill_and_submit_login_form
88
+ submission_count += 1
89
+ puts "Submission #{submission_count} of #{@config.iterations}." if @config.verbose?
90
+ end
91
+ puts "Successfully completed #{@config.iterations} submissions." if @config.verbose?
92
+ else
93
+ loop do
94
+ fill_and_submit_login_form
95
+ submission_count += 1
96
+ puts "#{submission_count} #{username}:#{password}" if @config.verbose?
97
+ end
98
+ end
99
+ ensure
100
+ finish_time = Time.now
101
+ time_delta_in_minutes = (finish_time - start_time) / 60
102
+ submissions_per_minute = submission_count / time_delta_in_minutes
103
+ puts "moby session terminated #{finish_time} with #{submission_count} counter-phishes served in #{time_delta_in_minutes} minutes for an average of #{submissions_per_minute} submissions per minute."
104
+ end
105
+ end
106
+
107
+ def fill_and_submit_login_form
108
+ puts "Filling with: #{username}:#{password}" if @config.verbose?
109
+ driver.navigate.to(@config.url)
110
+ wait_for_page_load
111
+ driver.attempt do
112
+ unless @config.username_prefilled?
113
+ username_field.clear
114
+ username_field.send_keys(username)
115
+ end
116
+ password_field.clear
117
+ password_field.send_keys(password)
118
+ submit_form(form)
119
+ end
120
+ sleep @config.delay if @config.delay > 0
121
+ end
122
+
123
+ def find_form_by_name
124
+ name = @config.form_name
125
+ selectors = [
126
+ [:name, name],
127
+ [:id, name],
128
+ [:css, "form[name='#{name}']"],
129
+ [:css, "form##{name}"]
130
+ ]
131
+ selectors.each do |type, selector|
132
+ if @driver.element_present?(type, selector)
133
+ return @driver.find_element(type, selector)
134
+ end
135
+ end
136
+ raise("Could not find a form with the name or id of #{@config.form_name}.")
137
+ end
138
+
139
+ def find_form_by_number
140
+ index = @config.form_number
141
+ forms = @driver.find_elements(:tag_name, 'form')
142
+ return forms[index] if forms[index]
143
+ raise("Could not find a form at index #{@config.form_number}.")
144
+ end
145
+
146
+ def username
147
+ @random_input_generator.random_username
148
+ end
149
+
150
+ def password
151
+ @random_input_generator.random_password
152
+ end
153
+
154
+ def username_field
155
+ @username_field ||= (
156
+ if @config.using_username_field_name?
157
+ find_username_field_by_name
158
+ else
159
+ find_username_field_by_number
160
+ end
161
+ )
162
+ end
163
+
164
+ def password_field
165
+ @password_field ||= (
166
+ if @config.using_password_field_name?
167
+ find_password_field_by_name
168
+ else
169
+ find_password_field_by_number
170
+ end
171
+ )
172
+ end
173
+
174
+ def find_username_field_by_name
175
+ name = @config.username_field_name
176
+ selectors = [
177
+ [:name, name],
178
+ [:id, name],
179
+ [:css, "input[name='#{name}']"],
180
+ [:css, "input##{name}"]
181
+ ]
182
+ selectors.each do |type, selector|
183
+ begin
184
+ element = form.find_element(type, selector)
185
+ return element if element
186
+ rescue Selenium::WebDriver::Error::NoSuchElementError
187
+ next
188
+ end
189
+ end
190
+ raise("Could not find username field: #{@config.username_field_name}")
191
+ end
192
+
193
+ def find_username_field_by_number
194
+ index = @config.username_field_number
195
+ inputs = form.find_elements(:tag_name, 'input')
196
+ inputs[index] ||
197
+ raise("Could not find username field at index #{index}")
198
+ end
199
+
200
+ def find_password_field_by_name
201
+ name = @config.password_field_name
202
+ selectors = [
203
+ [:name, name],
204
+ [:id, name],
205
+ [:css, "input[name='#{name}']"],
206
+ [:css, "input##{name}"]
207
+ ]
208
+ selectors.each do |type, selector|
209
+ begin
210
+ element = form.find_element(type, selector)
211
+ return element if element
212
+ rescue Selenium::WebDriver::Error::NoSuchElementError
213
+ next
214
+ end
215
+ end
216
+ raise("Could not find password field: #{@config.password_field_name}")
217
+ end
218
+
219
+ def find_password_field_by_number
220
+ index = @config.password_field_number
221
+ inputs = form.find_elements(:tag_name, 'input')
222
+ inputs[index] ||
223
+ raise("Could not find password field at index #{index}")
224
+ end
225
+
226
+ def submit_form(form)
227
+ begin
228
+ submit_button = form.find_element(:css, "input[type='submit'], button[type='submit'], button")
229
+ submit_button.click
230
+ rescue
231
+ form.submit
232
+ end
233
+ sleep 0.5
234
+ end
235
+
236
+ def cleanup_driver
237
+ @driver.quit if @driver
238
+ puts "Driver closed." if @config.verbose?
239
+ end
240
+ end
241
+ end
242
+ end
@@ -0,0 +1,20 @@
1
+ # lib/Moby/Backends.rb
2
+ # Moby::Backends
3
+
4
+ require_relative './Backends/MechanizeBackend'
5
+ require_relative './Backends/SeleniumBackend'
6
+
7
+ class Moby
8
+ module Backends
9
+ def self.for(config:, random_input_generator:)
10
+ case config.backend
11
+ when :mechanize
12
+ MechanizeBackend.new(config: config, random_input_generator: random_input_generator)
13
+ when :selenium
14
+ SeleniumBackend.new(config: config, random_input_generator: random_input_generator)
15
+ else
16
+ raise "Unknown backend: #{config.backend.inspect}."
17
+ end
18
+ end
19
+ end
20
+ end