leap_salesforce_ui 0.1.0 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -3
  3. data/.gitlab-ci.yml +4 -0
  4. data/.leap_salesforce.yml +2 -0
  5. data/ChangeLog +25 -0
  6. data/Dockerfile +2 -1
  7. data/Gemfile +1 -0
  8. data/README.md +102 -7
  9. data/Rakefile +3 -0
  10. data/bin/console +2 -5
  11. data/bin/setup +1 -2
  12. data/exe/leap_salesforce_ui +44 -1
  13. data/leap_salesforce_ui.gemspec +3 -3
  14. data/lib/leap_salesforce_ui.rb +25 -8
  15. data/lib/leap_salesforce_ui/base_page.rb +5 -2
  16. data/lib/leap_salesforce_ui/create_page.rb +2 -49
  17. data/lib/leap_salesforce_ui/error.rb +9 -0
  18. data/lib/leap_salesforce_ui/form_filler.rb +152 -0
  19. data/lib/leap_salesforce_ui/generator/appenders.rb +24 -0
  20. data/lib/leap_salesforce_ui/generator/page_objects.rb +30 -0
  21. data/lib/leap_salesforce_ui/generator/templates/Rakefile.erb +1 -0
  22. data/lib/leap_salesforce_ui/generator/templates/create_page.rb.erb +5 -0
  23. data/lib/leap_salesforce_ui/generator/templates/spec_helper.rb.erb +21 -0
  24. data/lib/leap_salesforce_ui/generator/templates/ui_spec.rb.erb +22 -0
  25. data/lib/leap_salesforce_ui/generator/templates/update_page.rb.erb +5 -0
  26. data/lib/leap_salesforce_ui/generator/templates/view_page.rb.erb +5 -0
  27. data/lib/leap_salesforce_ui/login_page.rb +15 -6
  28. data/lib/leap_salesforce_ui/page_introspection.rb +11 -0
  29. data/lib/leap_salesforce_ui/rake.rb +4 -0
  30. data/lib/leap_salesforce_ui/rake/pom.rake +9 -0
  31. data/lib/leap_salesforce_ui/update_page.rb +16 -0
  32. data/lib/leap_salesforce_ui/version.rb +1 -1
  33. data/lib/leap_salesforce_ui/view_page.rb +6 -3
  34. data/run_tests_when_ready.sh +1 -1
  35. metadata +20 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34a3cff8f0fe0cb8b8b3c80d45a2dd9bf9a875c9f97692230b673ebbf52526ad
4
- data.tar.gz: a289dd23876c1b998a4e6665c2c6d813f0563e0030e175a08c1c001d7cd41664
3
+ metadata.gz: 526d7426b4cbc68a811661af10184c81150a07d5b9f7441909758a84a6ff6353
4
+ data.tar.gz: '0248a74aeaf95af16ac34ffecac449710687283b3b27bedef3bb0814e4d95106'
5
5
  SHA512:
6
- metadata.gz: 6f38f584f42b2c6b069d54e36e2174780dee40bc9c9b3498f6ea1eb718c46f925711a78023e2610fd57ced91711123c2ccbc20f9e163af7ee65119db6ce90e8f
7
- data.tar.gz: 248b5b4ba8d953e2fe83ad672d590394ee4686f7b258b1b9ed0936bc43ce8752152e04c0a893047c2c9a8515fff029420bab49e5d85005a64bd7ee6415b61d55
6
+ metadata.gz: 829da20fbece1a80e9e01e099d6bbdaf1d1f949670c9e91ac09d37154dd194c62fe91e9a5dd65586dc53a1597e80a3a2e0d9aa6b6fead07d61aa32cc0b051a76
7
+ data.tar.gz: 0612cb14198ae7ec1cd0200ec952661346262aefab0c746f4e4fb3ded1a4d7269bb1c925bf593ab3280dfc7c37f4ce5a261b8585f4dd11119ddc514b84602272
data/.gitignore CHANGED
@@ -14,9 +14,6 @@
14
14
  # Gem version
15
15
  Gemfile.lock
16
16
 
17
- # Generated files for now
18
- spec/support/
19
-
20
17
  # Here are folders related to testing this project in it's own CI
21
18
  config/credentials/
22
19
 
@@ -28,3 +25,6 @@ sf_log.log
28
25
 
29
26
  # Logs
30
27
  /logs/
28
+
29
+ # Generated files
30
+ spec/support/page_objects
data/.gitlab-ci.yml CHANGED
@@ -11,6 +11,7 @@ test:
11
11
  script:
12
12
  - apk add docker-compose
13
13
  - docker-compose version
14
+ - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
14
15
  - docker pull --quiet elgalu/selenium:3.141.59-p37 # Explicitly pull selenium image with working version
15
16
  - docker-compose up --abort-on-container-exit --exit-code-from test
16
17
  artifacts:
@@ -22,3 +23,6 @@ test:
22
23
  when: always
23
24
  variables:
24
25
  BROWSER: chrome
26
+
27
+ include:
28
+ - template: Code-Quality.gitlab-ci.yml
data/.leap_salesforce.yml CHANGED
@@ -3,3 +3,5 @@ lib_folder: spec/support
3
3
  sfdx: false
4
4
  soql_objects:
5
5
  - Contact
6
+ - Account
7
+ - Broker: Broker__c
data/ChangeLog CHANGED
@@ -1,3 +1,28 @@
1
+ Version 0.1.5
2
+ * Enhancements
3
+ * Added logging of actions
4
+ * Allow headless mode with LeapSalesforce.headless = true
5
+ * Throw custom errors when problem occurs setting fields
6
+ * Bug fix
7
+ * Didn't handle where class name and backend name differ
8
+
9
+ Version 0.1.4
10
+ * Enhancements
11
+ * Add executable to make it fast to setup testing with exe `leap_salesforce_ui init`
12
+
13
+ Version 0.1.3
14
+ * Enhancements
15
+ * Explicitly specify higher version of Watir as minimum which is compatible with Ruby 3
16
+
17
+ Version 0.1.2
18
+ * Enhancements
19
+ * Added Rake task to auto-generate page classes from soql_objects
20
+ * Added update base class to update entities in a similar way to creating them
21
+
22
+ Version 0.1.1
23
+ * Enhancements
24
+ * Add filling in of dropdown, reference field and text area to create
25
+
1
26
  Version 0.1.0
2
27
  * Enhancements
3
28
  * Initial version with example of using the UI in an integration test
data/Dockerfile CHANGED
@@ -1,4 +1,5 @@
1
- FROM ruby:2.6
1
+ FROM ruby:2.7
2
+ # Issue as of this time with Selenium webdriver remote with Ruby3
2
3
  MAINTAINER Samuel Garratt
3
4
 
4
5
  # Required ruby gems
data/Gemfile CHANGED
@@ -9,4 +9,5 @@ gem "rake", "~> 13.0"
9
9
 
10
10
  gem "rspec", "~> 3.0"
11
11
 
12
+ gem "pry"
12
13
  gem "rubocop", "~> 1.7"
data/README.md CHANGED
@@ -23,21 +23,116 @@ Or install it yourself as:
23
23
 
24
24
  ## Usage
25
25
 
26
- Setup `leap_salesforce` with `leap_salesforce init`.
26
+ This gem is an extension to [leap salesforce](https://gitlab.com/leap-dojo/leap_salesforce)
27
+ Setup `leap_salesforce` with `leap_salesforce init` and use the retrospective rake tasks to create soql objects.
27
28
 
28
- Follow examples in `spec` to build a test using base classes.
29
+ Add the line `require 'leap_salesforce_ui/rake'` to your `Rakefile`. Then run the Rake task `rake leaps:create_poms`
30
+ which will generate page objects based upon the `soql_objects`
29
31
 
30
- These tests require that you [disable 2 step authentication for users](https://gitlab.com/leap-dojo/leap_salesforce_ui/-/wikis/Disable-2-step-authentication-and-allow-IP-range)
32
+ Set the current UI user to be used with the `LeapSalesforce.ui_user` attribute.
33
+
34
+ E.g
35
+
36
+ ```ruby
37
+ LeapSalesforce.ui_user = 'test.user@salesforce.com'
38
+ ```
39
+
40
+ See [leap salesforce test users section](https://gitlab.com/leap-dojo/leap_salesforce#test-users)
41
+ for details on ways to set test users.
42
+
43
+ ### Example spec_helper
44
+
45
+ Following is example code that can be used to setup and teardown each test.
46
+
47
+ ```ruby
48
+ require 'leap_salesforce_ui'
49
+ RSpec.configure do |config|
50
+ LeapSalesforce.ui_user = LeapSalesforce::Users.list.first
51
+ config.before(:each) do |example|
52
+ # Start browser with test name as metadata
53
+ LeapSalesforce.browser example.full_description
54
+ end
55
+
56
+ config.after(:each) do |example|
57
+ # Take screenshot on failure
58
+ screenshot = "logs/#{tidy_description(example.full_description.to_s)}_failure.png"
59
+ LeapSalesforce.browser.screenshot.save screenshot if example.exception
60
+ # Use a fresh browser for each test
61
+ LeapSalesforce.close_browser
62
+ end
63
+
64
+ # Tidy RSpec example description so it's suitable for a screenshot name
65
+ # @return [String] Description of test suitable for file name
66
+ def tidy_description(rspec_description)
67
+ rspec_description.delete("#<RSpec::Core::Example").delete('>".:{}').delete("=").delete(" ")
68
+ end
69
+ end
70
+ ```
71
+
72
+ ### Populate form using label in metadata
73
+
74
+ Following is a walk through of one of the tests. If you've copied the above code into `spec/spec_helper.rb`
75
+ you should be able to copy this code into a new test file and execute it.
76
+
77
+ > It may or may not work depending on how the fields Contact has in your org
78
+
79
+ ```ruby
80
+ RSpec.describe "Creating record" do
81
+ let(:first_name) { Faker::Name.first_name }
82
+ let(:last_name) { Faker::Name.last_name }
83
+ let(:salutation) { Contact::Salutation.sample }
84
+ let(:other_street) { Faker::Address.full_address }
85
+ it "populate form and save" do
86
+ account = FactoryBot.create(:account)
87
+ CreateContactPage.visit.submit_with({ "First Name" => first_name,
88
+ last_name: last_name,
89
+ salutation: salutation,
90
+ other_street: other_street,
91
+ account_id: account.account_name })
92
+ contact_id = ViewContactPage.id
93
+ @contact = Contact.find(id: contact_id)
94
+ expect(@contact.first_name).to eq first_name
95
+ expect(@contact.last_name).to eq last_name
96
+ expect(@contact.salutation).to eq salutation
97
+ expect(@contact.other_street).to eq other_street
98
+ expect(@contact.account_id).to eq account.id
99
+ end
100
+ end
101
+ ```
102
+
103
+ One doesn’t have to identify how to recognise first_name, last_name, etc.
104
+ From the metadata it works out what kind of field (textfield, textarea, reference) and then
105
+ finds the corresponding label and uses that to fill in the field.
106
+
107
+ This test then uses the API to setup a dependent account (through FactoryBot) and then to find the contact
108
+ created to verify that what was entered on the UI was saved as expected.
109
+
110
+ ### Manually specify label
111
+
112
+ Sometimes the label on the UI does not match the metadata label. In that case, one case manually
113
+ specify the type of object, the label and what to set it to. E.g.,
114
+
115
+ ```ruby
116
+ UpdateAccountPage.visit(@account.id).set_picklist("Type", Account::AccountType.prospect).save
117
+ ```
118
+
119
+ This will set a picklist with the label `Type` to the value defined by the picklist `AccountType`, and
120
+ the value `prospect`.
121
+
122
+ Follow other examples in `spec` to build tests using the auto-generated classes.
123
+
124
+ > These tests require that you [disable 2 step authentication for users](https://gitlab.com/leap-dojo/leap_salesforce_ui/-/wikis/Disable-2-step-authentication-and-allow-IP-range)
31
125
  unless run from an IP address that your Salesforce Org trusts. Many shared CI environments have runners that would
32
126
  be hard to whitelist as the address is dynamic.
33
127
 
34
- > In the future a script will be used to scaffold base classes based on `.leap_salesforce.yml`
35
-
36
128
  ## Development
37
129
 
38
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
130
+ After checking out the repo, run `bin/setup` to install dependencies.
131
+ Then, run `rake spec` to run the tests.
132
+ You can also run `bin/console` for an interactive prompt that will allow you to experiment.
39
133
 
40
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
134
+ To install this gem onto your local machine, run `bundle exec rake install`.
135
+ To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
41
136
 
42
137
  ## Contributing
43
138
 
data/Rakefile CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rspec/core/rake_task"
5
+ require "leap_salesforce/rake"
6
+
7
+ require_relative "lib/leap_salesforce_ui/rake"
5
8
 
6
9
  RSpec::Core::RakeTask.new(:spec)
7
10
 
data/bin/console CHANGED
@@ -8,8 +8,5 @@ require "leap_salesforce_ui"
8
8
  # with your gem easier. You can also use a different console, if you like.
9
9
 
10
10
  # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
11
+ require "pry"
12
+ Pry.start
data/bin/setup CHANGED
@@ -4,5 +4,4 @@ IFS=$'\n\t'
4
4
  set -vx
5
5
 
6
6
  bundle install
7
-
8
- # Do any other automated setup that you need to do here
7
+ bundle exec rake leaps:create_poms
@@ -1,3 +1,46 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "leap_salesforce_ui"
4
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
5
+
6
+ require "leap_salesforce/error"
7
+ require "thor"
8
+
9
+ def init_setup?
10
+ File.exist?(".leap_salesforce.yml")
11
+ end
12
+
13
+ unless init_setup?
14
+ puts "Setting up leap_salesforce API first"
15
+ Process.fork { exec("leap_salesforce init") }
16
+ Process.wait
17
+
18
+ raise LeapSalesforce::SetupError, "Unable to setup leap_salesforce" unless init_setup?
19
+ end
20
+
21
+ require "leap_salesforce_ui/version"
22
+ require "leap_salesforce_ui/generator/appenders"
23
+ require "colorize"
24
+
25
+ module LeapSalesforce
26
+ # Executable for setting up Leap Salesforce UI
27
+ class UiExe < Thor
28
+ include LeapSalesforce::Generators::Appenders
29
+
30
+ desc "init", "Create leap salesforce ui configuration"
31
+ def init
32
+ puts "Initialising initial files to get started with leap_salesforce_ui"
33
+ append "Rakefile", "Rakefile.erb"
34
+ puts 'Running Rake task "leaps:create_poms"'
35
+ puts `rake leaps:create_poms`
36
+ append File.join("spec", "spec_helper.rb"), "spec_helper.rb.erb"
37
+ append File.join("spec", "ui_spec.rb"), "ui_spec.rb.erb"
38
+ puts "Note test 'ui_spec' is specific to 'Contact' and 'Account' object using fields that
39
+ may not exist but purely as a demonstration."
40
+ end
41
+ end
42
+ end
43
+
44
+ puts "Using #{LeapSalesforceUi::VERSION} of LeapSalesforceUi"
45
+
46
+ LeapSalesforce::UiExe.start(ARGV)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'lib/leap_salesforce_ui/version'
3
+ require_relative "lib/leap_salesforce_ui/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "leap_salesforce_ui"
@@ -29,8 +29,8 @@ It reads the Metadata from Salesforce and creates the foundation for UI tests.'
29
29
  spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
30
  spec.require_paths = ["lib"]
31
31
 
32
- spec.add_dependency "leap_salesforce", ">= 1.0.2"
33
- spec.add_dependency "watir"
32
+ spec.add_dependency "leap_salesforce", ">= 1.0.3"
33
+ spec.add_dependency "watir", ">= 6.17.0"
34
34
  spec.add_dependency "webdrivers"
35
35
 
36
36
  # For more information and examples about making a new gem, checkout our
@@ -4,18 +4,26 @@ require "watir"
4
4
  require "webdrivers"
5
5
  require "leap_salesforce"
6
6
  require_relative "leap_salesforce_ui/version"
7
+ require_relative "leap_salesforce_ui/error"
7
8
  require_relative "leap_salesforce_ui/base_page"
9
+ require_relative "leap_salesforce_ui/page_introspection"
10
+ require_relative "leap_salesforce_ui/form_filler"
8
11
  require_relative "leap_salesforce_ui/create_page"
9
12
  require_relative "leap_salesforce_ui/view_page"
13
+ require_relative "leap_salesforce_ui/update_page"
14
+ page_obj_folder = File.join(LeapSalesforce.lib_folder, "page_objects")
15
+ require_all page_obj_folder if Dir.exist? page_obj_folder
10
16
 
17
+ # @return [Hash]
11
18
  def zalenium_args(feature_name, scenario)
12
- args = { timeout: 120, url: ENV['WEBDRIVER_URL'], name: "Scenario: #{scenario} #{Time.now}",
19
+ args = { timeout: 120, url: ENV["WEBDRIVER_URL"], name: "Scenario: #{scenario} #{Time.now}",
13
20
  'zal:build': "Feature: #{feature_name}" }
14
- if ENV['BROWSER'] == 'chrome'
21
+ case ENV["BROWSER"]
22
+ when "chrome"
15
23
  args.merge!('goog:chromeOptions': {
16
- args: ENV['WEBDRIVER_CHROMEOPTIONS']&.split(' ') || %w[]
17
- })
18
- elsif ENV['BROWSER'] == 'firefox'
24
+ args: ENV["WEBDRIVER_CHROMEOPTIONS"]&.split(" ") || %w[]
25
+ })
26
+ when "firefox"
19
27
  args.merge!(timeouts: 120)
20
28
  end
21
29
  args
@@ -25,6 +33,8 @@ end
25
33
  module LeapSalesforce
26
34
  # @return [String] User for UI
27
35
  @ui_user = nil
36
+ # @return [Boolean] Whether to run in headless mode
37
+ @headless = false
28
38
  class << self
29
39
  def browser(test_name = nil)
30
40
  @browser ||= new_browser(test_name)
@@ -35,8 +45,12 @@ module LeapSalesforce
35
45
  @browser = nil
36
46
  end
37
47
 
48
+ # @return [String] User used for UI tests
38
49
  attr_reader :ui_user
39
50
 
51
+ # @return [Boolean] Whether to run in headless mode. Default false
52
+ attr_accessor :headless
53
+
40
54
  # @param [String, Symbol, Regexp, LeapSalesforce::User] user User or email address of user
41
55
  def ui_user=(user)
42
56
  @ui_user = if user.is_a? String
@@ -50,10 +64,13 @@ module LeapSalesforce
50
64
  private
51
65
 
52
66
  def new_browser(test_name = nil)
53
- if ENV['WEBDRIVER_URL']
54
- Watir::Browser.new :chrome, **zalenium_args('LeapSalesforce', test_name || 'Test')
67
+ if ENV["WEBDRIVER_URL"]
68
+ # TODO: Get working on Ruby 3
69
+ Watir::Browser.new :chrome, { **zalenium_args("LeapSalesforce", test_name || "Test") }
55
70
  else
56
- Watir::Browser.new
71
+ args = {}
72
+ args[:headless] = true if headless
73
+ Watir::Browser.new :chrome, args
57
74
  end
58
75
  end
59
76
  end
@@ -8,14 +8,17 @@ module BasePage
8
8
  LeapSalesforce.browser
9
9
  end
10
10
 
11
+ # Set entity this page object refers to
12
+ # @param [Class] soql_object Backend name of SoqlObject this page object refers to
11
13
  def soql_object(soql_object)
12
14
  @soql_object = soql_object
13
15
  end
14
16
 
17
+ # Visit the current page, logging in if required
15
18
  def visit
16
- LoginPage.visit
17
19
  LoginPage.login
18
- page_url = "#{SoqlHandler.instance_url}/lightning/o/#{@soql_object}/new"
20
+ page_url = "#{SoqlHandler.instance_url}/lightning/o/#{@soql_object.soql_object_name}/new"
21
+ LeapSalesforce.logger.info "Visiting #{self}"
19
22
  browser.goto page_url
20
23
  self
21
24
  end
@@ -1,54 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module NewMethods
4
- def new_panel
5
- browser.div(class: "inlinePanel")
6
- end
7
-
8
- # Use metadata to get label for field
9
- # @param [String] field
10
- def get_label_for(field)
11
- raise "SoqlObject not defined" unless @soql_object
12
-
13
- return field if @soql_object.label_names.include? field
14
-
15
- raise "Cannot find field #{field} on #{@soql_object}" unless @soql_object.accessors[field.to_sym]
16
-
17
- @soql_object.accessors[field.to_sym][:label]
18
- end
19
-
20
- # Based on data type of field passed, fill in each field
21
- # @param [Hash] data
22
- def populate_with(data)
23
- data.each do |field, value|
24
- label_name = get_label_for field
25
- # TODO: Change based on type
26
- new_panel.text_field(xpath: "//label[contains(.,'#{label_name}')]//following-sibling::input").set value
27
- end
28
- end
29
-
30
- def submit_with(data)
31
- populate_with data
32
- save
33
- end
34
-
35
- # Save the current form, creating the new object
36
- def save
37
- browser.button(title: "Save").click
38
- new_panel.wait_until do |panel|
39
- browser_list = browser.ul(class: "errorsList")
40
- if browser_list.exists?
41
- errors = browser_list.lis
42
- raise "Errors creating: #{errors.collect(&:text)}" if errors.count.positive?
43
- end
44
-
45
- !panel.present?
46
- end
47
- end
48
- end
49
-
50
- # Base class for creating entities
3
+ # Base class for creating entities in Salesforce
51
4
  class CreatePage
52
5
  extend BasePage
53
- extend NewMethods
6
+ extend LeapSalesforce::FormFiller
54
7
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeapSalesforce
4
+ # Error related to submitting a form
5
+ class SubmitError < Error; end
6
+
7
+ # Error related to setting a field
8
+ class SetFieldError < Error; end
9
+ end
@@ -0,0 +1,152 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeapSalesforce
4
+ module FormFiller
5
+ # @return [Watir::Elements::Div] Div where form entered to create/edit entity
6
+ def form
7
+ browser.div(class: "inlinePanel")
8
+ end
9
+
10
+ # Use metadata to get label for field
11
+ # @param [String] field
12
+ def get_label_for(field)
13
+ raise "SoqlObject not defined" unless @soql_object
14
+
15
+ return field if @soql_object.label_names.include? field
16
+
17
+ raise "Cannot find field #{field} on #{@soql_object}" unless @soql_object.accessors[field.to_sym]
18
+
19
+ @soql_object.accessors[field.to_sym][:label]
20
+ end
21
+
22
+ # @return [Hash] Description of field
23
+ def field_for(desc)
24
+ ruby_desc = @soql_object.accessors[desc.to_sym]
25
+ return ruby_desc if ruby_desc
26
+
27
+ @soql_object.properties_for(desc)
28
+ end
29
+
30
+ # Based on data type of field passed, fill in each field
31
+ # The type of field is attempted to be filled in based on the metadata
32
+ # @param [Hash] data Hash with label => value to set syntax.
33
+ def populate_with(data)
34
+ sleep 1 # Being too fast can cause issues with first value. TODO: Wait for a condition
35
+ data.each do |desc, value|
36
+ field = field_for(desc)
37
+ label_name = field[:label] || field["label"]
38
+
39
+ type = field[:type] || field["type"]
40
+ case type
41
+ when "string" then set_text_field(label_name, value)
42
+ when "picklist" then set_picklist(label_name, value)
43
+ when "textarea" then set_text_area(label_name, value)
44
+ when "reference" then set_reference_field(label_name, value)
45
+ else
46
+ raise NotImplementedError, "#{type} not yet defined in #{self}"
47
+ end
48
+ end
49
+ self
50
+ end
51
+
52
+ # Set input text field
53
+ # @param [String] label Label of textfield
54
+ # @param [String] value Value to set textfield to
55
+ def set_text_area(label, value)
56
+ LeapSalesforce.logger.info "Setting text area, label '#{label}' with '#{value}'"
57
+ field_transaction label, value do
58
+ form.textarea(xpath: "//label[contains(.,'#{label}')]//following-sibling::textarea").set value
59
+ end
60
+ end
61
+
62
+ # Set input text field
63
+ # @param [String] label Label of text field
64
+ # @param [String] value Value to set text field to
65
+ def set_text_field(label, value)
66
+ value = value.to_s
67
+ LeapSalesforce.logger.info "Setting text field, label '#{label}' with '#{value}'"
68
+ field_transaction label, value do
69
+ label_element = form.label(xpath: "//label[contains(.,'#{label}')]")
70
+ text_field = label_element.text_field(xpath: ".//following-sibling::input|.//following-sibling::div/input")
71
+ text_field.focus
72
+ text_field.set! value
73
+ return self if text_field.value == value
74
+
75
+ sleep 2 # Wait a bit and then set field
76
+ text_field.set! value
77
+ unless text_field.value == value
78
+ raise SetFieldError, "Unable to set text field '#{label}' with value " \
79
+ "'#{value}'. Value set is '#{text_field.value}'"
80
+ end
81
+ end
82
+ self
83
+ end
84
+
85
+ # Set value of picklist
86
+ # @param [String] label Label of picklist
87
+ # @param [String] value Value to set picklist to
88
+ def set_picklist(label, value)
89
+ LeapSalesforce.logger.info "Setting picklist, label '#{label}' with '#{value}'"
90
+ field_transaction label, value do
91
+ dropdown = form.link(xpath: "//*[./*[text()='#{label}']]//following-sibling::div//a")
92
+ dropdown.focus
93
+ sleep 0.5
94
+ dropdown.click
95
+ browser.link(xpath: ".//div[contains(@class, 'select-options')]//a[contains(.,'#{value}')]").click
96
+ end
97
+ self
98
+ end
99
+
100
+ # Set reference field where another entity is referred to by this entity
101
+ def set_reference_field(label, value)
102
+ ref_label = label.gsub("ID", "Name")
103
+ search_val = value[0..12]
104
+ LeapSalesforce.logger.info "Setting reference field, label '#{ref_label}', searching with '#{search_val}'"
105
+ field_transaction label, value do
106
+ search_field = browser.text_field(xpath: "//label[contains(.,'#{ref_label}')]//following-sibling::div[1]//input")
107
+ search_field.set search_val
108
+ browser.div(xpath: "//div[contains(@data-aura-class,'forceSearchInputLookupDesktopActionItem') and contains(., 'Search')]").click
109
+ browser.link(xpath: "//div[contains(@class,'searchScroller')]//a[contains(.,'#{search_val}')]").click
110
+ end
111
+ self
112
+ end
113
+
114
+ def submit_with(data)
115
+ populate_with data
116
+ save
117
+ end
118
+
119
+ # @return [Watir::Elements::Ul] List of errors
120
+ def error_list
121
+ browser.ul(xpath: "//ul[contains(@class, 'errorsList') and not(normalize-space()='')]")
122
+ end
123
+
124
+ # Save the current form, creating the new object
125
+ def save
126
+ LeapSalesforce.logger.info "Saving form for #{self}"
127
+ form.button(xpath: "//button[@title='Save']|//button[text()='Save']").click
128
+ form.wait_until do |panel|
129
+ sleep 1.5
130
+ if error_list.exists?
131
+ errors = error_list.lis
132
+ error_text = errors.collect(&:text)
133
+ LeapSalesforce.logger.error "Error submitting #{self} #{error_list.text}"
134
+ raise LeapSalesforce::SubmitError, "Errors creating on #{self}: #{error_text}" if errors.count.positive?
135
+ end
136
+
137
+ !panel.present?
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ # Process of setting a field within this block. Exceptions will be caught
144
+ # and thrown with context specific information
145
+ def field_transaction(label, value)
146
+ yield
147
+ rescue Watir::Exception::Error => e
148
+ raise LeapSalesforce::SetFieldError, "Unable to set label '#{label}' to '#{value}'" \
149
+ " on #{self}. Due to #{e.inspect}"
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "leap_salesforce/generator/generator"
4
+
5
+ module LeapSalesforce
6
+ module Generators
7
+ # Used for appending content onto existing files (or adding if not already present)
8
+ module Appenders
9
+ include LeapSalesforce::Generator
10
+
11
+ # Create content in a file, adding to an existing file if present
12
+ def append(filename, template_path)
13
+ FileUtils.touch filename unless File.exist? filename
14
+ content = read_template template_path, binding, folder: __dir__
15
+ if File.read(filename).include?(content)
16
+ puts "File '#{filename}' already has expected content, skipping...".colorize :red
17
+ else
18
+ puts "\u2713 Appending to #{filename}".colorize :green
19
+ open(filename, "a") { |f| f.puts content }
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "leap_salesforce/generator/generator"
4
+
5
+ module LeapSalesforce
6
+ # Generators for creating code
7
+ module Generator
8
+ # Creates SoqlObjects and related modules
9
+ class PageObjects
10
+ include Generator
11
+
12
+ POM_FOLDER = File.join(LeapSalesforce.lib_folder, "page_objects").freeze
13
+
14
+ def create
15
+ LeapSalesforce.objects_to_verify.each { |entity| create_poms_for entity }
16
+ end
17
+
18
+ # @param [LeapSalesforce::SoqlData] entity An object representing an object in Salesforce
19
+ def create_poms_for(entity)
20
+ @entity_name = entity
21
+ @soql_object = LeapSalesforce.soql_objects.find { |so| so.class_name == entity.to_s }
22
+ %w[create view update].each do |page_action|
23
+ content = read_template "#{page_action}_page.rb.erb", binding, folder: __dir__
24
+ file = File.join(POM_FOLDER, "#{@entity_name.to_s.snakecase}/#{page_action}_page.rb")
25
+ generate_file file, content, overwrite: false
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1 @@
1
+ require 'leap_salesforce_ui/rake'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Create<%= @soql_object.class_name %>Page < CreatePage
4
+ soql_object <%= @soql_object.class_name %>
5
+ end
@@ -0,0 +1,21 @@
1
+ require 'leap_salesforce_ui'
2
+ RSpec.configure do |config|
3
+ config.before(:each) do |example|
4
+ # Default to first test user defined for each test
5
+ LeapSalesforce.ui_user = LeapSalesforce::Users.list.first
6
+ # Start browser with test name as metadata
7
+ LeapSalesforce.browser example.full_description
8
+ end
9
+
10
+ config.after(:each) do |example|
11
+ screenshot = "logs/#{tidy_description(example.full_description.to_s)}_failure.png"
12
+ LeapSalesforce.browser.screenshot.save screenshot if example.exception
13
+ LeapSalesforce.close_browser # Fresh browser for each test
14
+ end
15
+
16
+ # Tidy RSpec example description so it's suitable for a screenshot name
17
+ # @return [String] Description of test suitable for file name
18
+ def tidy_description(rspec_description)
19
+ rspec_description.delete("#<RSpec::Core::Example").delete('>".:{}').delete("=").delete(" ")
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+
2
+ RSpec.describe "Creating record" do
3
+ let(:first_name) { Faker::Name.first_name }
4
+ let(:last_name) { Faker::Name.last_name }
5
+ let(:salutation) { Contact::Salutation.sample }
6
+ let(:other_street) { Faker::Address.full_address }
7
+ it "populate form and save" do
8
+ account = FactoryBot.create(:account)
9
+ CreateContactPage.visit.submit_with({ "First Name" => first_name,
10
+ last_name: last_name,
11
+ salutation: salutation,
12
+ other_street: other_street,
13
+ account_id: account.account_name })
14
+ contact_id = ViewContactPage.id
15
+ @contact = Contact.find(id: contact_id)
16
+ expect(@contact.first_name).to eq first_name
17
+ expect(@contact.last_name).to eq last_name
18
+ expect(@contact.salutation).to eq salutation
19
+ expect(@contact.other_street).to eq other_street
20
+ expect(@contact.account_id).to eq account.id
21
+ end
22
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Update<%= @soql_object.class_name %>Page < UpdatePage
4
+ soql_object <%= @soql_object.class_name %>
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ class View<%= @soql_object.class_name %>Page < ViewPage
4
+ soql_object <%= @soql_object.class_name %>
5
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  class LoginPage
4
4
  class << self
5
- DISABLE_2STEP_URL = 'https://gitlab.com/leap-dojo/leap_salesforce_ui/-/wikis/Disable-2-step-authentication-and-allow-IP-range'
5
+ DISABLE_2STEP_URL = "https://gitlab.com/leap-dojo/leap_salesforce_ui/-/wikis/Disable-2-step-authentication-and-allow-IP-range"
6
6
 
7
7
  # @return [Watir::Browser]
8
8
  def browser
@@ -21,18 +21,17 @@ class LoginPage
21
21
  error_message_element.present?
22
22
  end
23
23
 
24
- def visit
24
+ # Could be used if a user wants to login through UI for login specific tests
25
+ # which should not be necessary
26
+ def login_manually
25
27
  browser.goto LeapSalesforce.general_url
26
- end
27
-
28
- def login
29
28
  browser.text_field(id: "username").set(LeapSalesforce.ui_user)
30
29
  browser.text_field(id: "password").set(LeapSalesforce.password)
31
30
  browser.button(id: "Login").click
32
31
  Watir::Wait.until(timeout: 60, message: "Did not login within the expected time") do
33
32
  raise "Cannot Login. #{error_message}" if error_message?
34
33
 
35
- if browser.url.include? '_ui/identity/verification/'
34
+ if browser.url.include? "_ui/identity/verification/"
36
35
  raise LeapSalesforce::SetupError, "2 step verification page appears.
37
36
  Go to #{DISABLE_2STEP_URL} to learn how to disable it"
38
37
  end
@@ -40,5 +39,15 @@ Go to #{DISABLE_2STEP_URL} to learn how to disable it"
40
39
  !browser.url.include? LeapSalesforce.general_url
41
40
  end
42
41
  end
42
+
43
+ def login
44
+ LeapSalesforce.logger.info "Logging in as user '#{LeapSalesforce.ui_user}'"
45
+ browser.goto "#{LeapSalesforce.general_url}/?un=#{LeapSalesforce.ui_user}&pw=#{LeapSalesforce.password}"
46
+ continue_button = browser.button(id: "thePage:inputForm:continue")
47
+ if continue_button.exists?
48
+ browser.checkbox(id: "thePage:inputForm:remember").set
49
+ continue_button.click
50
+ end
51
+ end
43
52
  end
44
53
  end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LeapSalesforce
4
+ # Relates to a page inspecting it's own state
5
+ module PageIntrospection
6
+ # @return [String] Id of current entity
7
+ def id
8
+ LeapSalesforce.browser.url.split("/")[-2]
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../leap_salesforce_ui"
4
+ Dir.glob(File.join(__dir__, "rake", "*.rake")).each(&method(:import))
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :leaps do
4
+ desc "Create Page objects using leaps ui framework"
5
+ task :create_poms do
6
+ require_relative "../generator/page_objects"
7
+ LeapSalesforce::Generator::PageObjects.new.create
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Base class for updating an entity in Salesforce
4
+ class UpdatePage
5
+ extend BasePage
6
+ extend LeapSalesforce::PageIntrospection
7
+ extend LeapSalesforce::FormFiller
8
+
9
+ class << self
10
+ def visit(id)
11
+ LoginPage.login
12
+ browser.goto "#{SoqlHandler.instance_url}/lightning/r/#{@soql_object.soql_object_name}/#{id}/edit"
13
+ self
14
+ end
15
+ end
16
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LeapSalesforceUi
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.5"
5
5
  end
@@ -1,11 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Base class for viewing an entity in Salesforce
3
4
  class ViewPage
4
5
  extend BasePage
6
+ extend LeapSalesforce::PageIntrospection
5
7
  class << self
6
- # @return [String] Id of current entity
7
- def id
8
- LeapSalesforce.browser.url.split("/")[-2]
8
+ def visit(id)
9
+ LoginPage.login
10
+ browser.goto "#{SoqlHandler.instance_url}lightning/r/#{@soql_object.soql_object_name}/#{id}/view"
11
+ self
9
12
  end
10
13
  end
11
14
  end
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # exit if a command returns a non-zero exit code and also print the commands and their args as they are executed
3
3
  set -e -x
4
+ bin/setup # Setup POMs fresh each time testing generator
4
5
  # Wait for Zalenium to be up and running
5
6
  timeout 30 bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' zalenium:4444/wd/hub/status)" != "200" ]]; do sleep 5; done' || false
6
- bundle install
7
7
  bundle exec rake spec
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: leap_salesforce_ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - IQA
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2021-01-29 00:00:00.000000000 Z
12
+ date: 2021-02-16 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: leap_salesforce
@@ -17,28 +17,28 @@ dependencies:
17
17
  requirements:
18
18
  - - ">="
19
19
  - !ruby/object:Gem::Version
20
- version: 1.0.2
20
+ version: 1.0.3
21
21
  type: :runtime
22
22
  prerelease: false
23
23
  version_requirements: !ruby/object:Gem::Requirement
24
24
  requirements:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
- version: 1.0.2
27
+ version: 1.0.3
28
28
  - !ruby/object:Gem::Dependency
29
29
  name: watir
30
30
  requirement: !ruby/object:Gem::Requirement
31
31
  requirements:
32
32
  - - ">="
33
33
  - !ruby/object:Gem::Version
34
- version: '0'
34
+ version: 6.17.0
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
- version: '0'
41
+ version: 6.17.0
42
42
  - !ruby/object:Gem::Dependency
43
43
  name: webdrivers
44
44
  requirement: !ruby/object:Gem::Requirement
@@ -87,7 +87,21 @@ files:
87
87
  - lib/leap_salesforce_ui.rb
88
88
  - lib/leap_salesforce_ui/base_page.rb
89
89
  - lib/leap_salesforce_ui/create_page.rb
90
+ - lib/leap_salesforce_ui/error.rb
91
+ - lib/leap_salesforce_ui/form_filler.rb
92
+ - lib/leap_salesforce_ui/generator/appenders.rb
93
+ - lib/leap_salesforce_ui/generator/page_objects.rb
94
+ - lib/leap_salesforce_ui/generator/templates/Rakefile.erb
95
+ - lib/leap_salesforce_ui/generator/templates/create_page.rb.erb
96
+ - lib/leap_salesforce_ui/generator/templates/spec_helper.rb.erb
97
+ - lib/leap_salesforce_ui/generator/templates/ui_spec.rb.erb
98
+ - lib/leap_salesforce_ui/generator/templates/update_page.rb.erb
99
+ - lib/leap_salesforce_ui/generator/templates/view_page.rb.erb
90
100
  - lib/leap_salesforce_ui/login_page.rb
101
+ - lib/leap_salesforce_ui/page_introspection.rb
102
+ - lib/leap_salesforce_ui/rake.rb
103
+ - lib/leap_salesforce_ui/rake/pom.rake
104
+ - lib/leap_salesforce_ui/update_page.rb
91
105
  - lib/leap_salesforce_ui/version.rb
92
106
  - lib/leap_salesforce_ui/view_page.rb
93
107
  - run_tests_when_ready.sh