lapis_lazuli 2.0.1 → 2.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/lapis_lazuli.gemspec +3 -2
  3. data/lib/lapis_lazuli/browser.rb +36 -60
  4. data/lib/lapis_lazuli/browser/error.rb +89 -62
  5. data/lib/lapis_lazuli/generators/cucumber/template/README.md +2 -0
  6. data/lib/lapis_lazuli/generators/cucumber/template/config/config.yml +5 -20
  7. data/lib/lapis_lazuli/generators/cucumber/template/config/cucumber.yml +42 -13
  8. data/lib/lapis_lazuli/generators/cucumber/template/config/users.yml +21 -0
  9. data/lib/lapis_lazuli/generators/cucumber/template/features/1_basic.feature +42 -0
  10. data/lib/lapis_lazuli/generators/cucumber/template/features/2_account.feature +38 -0
  11. data/lib/lapis_lazuli/generators/cucumber/template/features/3_todo_list.feature +23 -0
  12. data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/authentication_helper.rb +122 -0
  13. data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/navigation_helper.rb +64 -0
  14. data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/registration_helper.rb +93 -0
  15. data/lib/lapis_lazuli/generators/cucumber/template/features/helpers/user_helper.rb +74 -0
  16. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/account_steps.rb +60 -0
  17. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/basic_steps.rb +65 -0
  18. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/todo_steps.rb +27 -0
  19. data/lib/lapis_lazuli/generators/cucumber/template/features/support/env.rb +3 -2
  20. data/lib/lapis_lazuli/options.rb +2 -1
  21. data/lib/lapis_lazuli/version.rb +1 -1
  22. data/lib/lapis_lazuli/world/config.rb +348 -334
  23. data/lib/lapis_lazuli/world/hooks.rb +85 -84
  24. data/lib/lapis_lazuli/world/logging.rb +1 -1
  25. data/test/config/config.yml +7 -6
  26. data/test/config/cucumber.yml +6 -8
  27. data/test/features/bindings.feature +1 -1
  28. data/test/features/browser.feature +1 -1
  29. data/test/features/step_definitions/interaction_steps.rb +5 -2
  30. data/test/features/step_definitions/validation_steps.rb +2 -2
  31. data/test/features/support/env.rb +22 -1
  32. data/test/results/latest_results.json +0 -0
  33. metadata +49 -16
  34. data/lib/lapis_lazuli/generators/cucumber/template/features/account.feature +0 -26
  35. data/lib/lapis_lazuli/generators/cucumber/template/features/example.feature +0 -30
  36. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/interaction_steps.rb +0 -165
  37. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/precondition_steps.rb +0 -63
  38. data/lib/lapis_lazuli/generators/cucumber/template/features/step_definitions/validation_steps.rb +0 -67
  39. data/lib/lapis_lazuli/generators/cucumber/template/features/support/functions.rb +0 -68
@@ -0,0 +1,21 @@
1
+ ---
2
+ users:
3
+ default-user: # See ./features/helpers/user_helper.rb
4
+ username: ta_RAND-ALPHA_
5
+ password: Ab!2_RAND_
6
+ gender: male
7
+ experience: Ruby,Gherkin
8
+ biography: This is the default test user
9
+ complete_all: 1
10
+ production:
11
+ test-user:
12
+ username: test
13
+ password: test
14
+ test:
15
+ test-user:
16
+ username: ta-test-user
17
+ password: ^%8jah7hH
18
+ uat:
19
+ test-user:
20
+ username: ta-uat-user
21
+ password: (87hsjaJH#
@@ -0,0 +1,42 @@
1
+ @basic @all_env
2
+ Feature: Example Feature
3
+ When I want to learn how to make test cases
4
+ As a user of the test automation tool
5
+ I want to run and adjust the tests below
6
+
7
+ @basic_01
8
+ Scenario: example01 - Spritecloud search
9
+ Given the user navigates to "blog"
10
+ When the user searches for "lapis lazuli"
11
+ Then text "Open Source" should display somewhere on the page
12
+
13
+ @basic_02
14
+ Scenario: example02 - Going to a search result
15
+ Given the user navigates to "https://spritecloud.com/?s=lapis+lazuli"
16
+ When the user clicks on link "/announcing-lapislazuli/"
17
+ Then text "Let's talk about testing" should display somewhere on the page
18
+
19
+ @basic_03
20
+ Scenario Outline: example03 - checking multiple pages for the logo
21
+ Given the user navigates to "<page>"
22
+ When the user clicks on the spritecloud logo
23
+ Then the user should be on page "home"
24
+ Scenarios:
25
+ | page |
26
+ | blog |
27
+ | home |
28
+ | about-us |
29
+ | testing |
30
+ | functional-testing |
31
+
32
+ ### LEARNING TO DEBUG ###
33
+ # Scenario' or Feature's including the tag @dev will be ingored when running a regular profile. To run this do:
34
+ # bundle exec cucumber -t @basic_04 -p debug
35
+ # or, if you want to test it on a specific environment:
36
+ # bundle exec cucumber -p production -p debug -t @basic_04
37
+ # Good luck fixing the problems!
38
+ @basic_04 @dev
39
+ Scenario: example_04 - confirming there is a no results page
40
+ Given the user navigates to "blog"
41
+ When the user searches for "no_results_expected"
42
+ Then the text "Nothing Found" should display on the blog page
@@ -0,0 +1,38 @@
1
+ @account @all_env
2
+ Feature: User accounts
3
+ This feature will make sure the user account functionality is working as expected
4
+ By checking registration, login and logout functionality
5
+
6
+ # This is a best practise example. Please note the following
7
+ # By defining a register, log-in and log-out state, we can easily re-use all these preconditions
8
+ # All the scenario's aren't completed, but it should be easy to implement it into your own project.
9
+
10
+ @account_01 @log_in #@pause # You can add @pause to have a break between every step.
11
+ Scenario: account_01 - Logging in
12
+ Given the user is logged out
13
+ When "test-user" logs in
14
+ Then the page should display as logged in state
15
+
16
+ @account_02 @log_out
17
+ Scenario: account_02 - Logging out
18
+ Given "test-user" is logged in
19
+ When the user clicks on the logout button
20
+ Then the page should display as logged out state
21
+
22
+ @account_03
23
+ Scenario: account_03 - Opening the registration form
24
+ Given the user is logged out
25
+ When the user clicks on the registration button
26
+ Then the registration form should display
27
+
28
+ @account_04
29
+ Scenario: account_04 - Successful registration
30
+ Given "default-user" has the registration form opened
31
+ When the user completes registration
32
+ Then the successful registration message should display
33
+
34
+ @account_05
35
+ Scenario: account_05 - Logging in a new registration
36
+ Given "default-user" has registered a new account
37
+ When the user logs in
38
+ Then the page should display as logged in state
@@ -0,0 +1,23 @@
1
+ @todo @all_env
2
+ Feature: Todo list
3
+ In this feature we will test the todo functionality
4
+ We do this by adding, completing and deleting the todo lists'
5
+
6
+ @todo_01
7
+ Scenario: todo_01 - adding a todo item
8
+ Given "test-user" is logged in
9
+ When a todo item with text "Hello world" is added
10
+ Then a todo item with text "Hello world" should be present
11
+
12
+ @todo_02
13
+ Scenario: todo_02 - removing all todo items
14
+ Given "test-user" has at least 1 todo item
15
+ When the user marks all todo items as completed
16
+ And the clear completed button is pressed
17
+ Then no todo items should display
18
+
19
+ @todo_03
20
+ Scenario: todo_03 - confirming the progress bar
21
+ Given "test-user" has exactly 8 todo items
22
+ When the user marks 4 todo items as completed
23
+ Then the progress bar should display at 50 percent
@@ -0,0 +1,122 @@
1
+ module Auth
2
+ # This is the Authentication helper, it will have all functions to log in, log out or ensure one of these statusses.
3
+ # For every part of functionality of a project, you can create a new helper, to keep your TA organised.
4
+ extend LapisLazuli
5
+ class << self
6
+
7
+ @@user = ''
8
+ @login_page = 'training-page'
9
+
10
+ # This is a list of elements relevant for this helper.
11
+ # The following is short notation, *only* use this if the element selector can be done in 1 line.
12
+ # @formatter:off
13
+ def form_container; browser.wait(:like => [:form, :id, 'form-login']); end
14
+ def username_field; form_container.input(:xpath => '//*[@id="login-username"]'); end
15
+ def password_field; form_container.input(:id => 'login-password'); end
16
+ def login_button; browser.button(:id => 'button-login'); end
17
+ # @formatter:on
18
+
19
+ # Following elements that need more advanced options/search patterns
20
+ def logged_in_element(timeout=10, throw=true)
21
+ browser.wait(
22
+ :like => [:a, :id, 'user_dropdown'],
23
+ :timeout => timeout,
24
+ :throw => throw
25
+ )
26
+ end
27
+
28
+ def logged_out_element(timeout=10, throw=true)
29
+ browser.wait(
30
+ :like => [:form, :id, 'form-login'],
31
+ :timeout => timeout,
32
+ :throw => throw
33
+ )
34
+ end
35
+
36
+ # Next are the functions called from the step definitions
37
+ # `ensure_something` is best practise to be used for functions that should get the test to a certain state. For example:
38
+ # `ensure_log_out` only logs out if you're logged in
39
+ # `log_out` will blindly try to log out and fail if you're already logged out
40
+ def ensure_log_out
41
+ Nav.to('training-page')
42
+ if Auth.is_logged_in?
43
+ Auth.log_out
44
+ if Auth.is_logged_in?
45
+ error 'Page did not display in logged out state after logging out'
46
+ end
47
+ end
48
+ end
49
+
50
+ # Makes sure that a specific user is logged in, if it's not already.
51
+ def ensure_log_in(user='default-user')
52
+ Nav.to('training-page')
53
+ unless Auth.is_logged_in?(user)
54
+ # If the wrong user is logged in, we should ensure a log out action and then log in again
55
+ Auth.ensure_log_out
56
+ Auth.log_in(user)
57
+ # Double check if the login was successful, if not, throw an error.
58
+ unless Auth.is_logged_in?(user)
59
+ error "Failed to log in `#{user}`."
60
+ end
61
+ end
62
+ end
63
+
64
+ # If user=nil, any logged in user is acceptable, else we want to make sure the username matches with the logged in user.
65
+ def is_logged_in?(user=nil)
66
+ # For performance, we do a 0 second wait for the logged_out_element
67
+ if Auth.logged_out_element(0, false)
68
+ return false
69
+ end
70
+ login_elm = Auth.logged_in_element(5, false)
71
+ if login_elm.nil?
72
+ # Logged in element not found, check if the logged out element is present
73
+ logout_elm = Auth.logged_out_element(0, false)
74
+ if logout_elm.nil?
75
+ # Neither of the elements were present, this should not be possible.
76
+ error 'Failed to find the logged_out element and the logged_in element. The user is not logged in, nor logged out.'
77
+ else
78
+ # Logged out element was found the second time.
79
+ return false
80
+ end
81
+ else
82
+ # The logged in element was found, should we match the username?
83
+ if user.nil?
84
+ # No, any user is fine
85
+ return true
86
+ else
87
+ # Yes, load the user data and match the username
88
+ User.load_user_data(user)
89
+ return login_elm.span(:class => ['username', 'ng-binding']).text == User.get('username')
90
+ end
91
+ end
92
+ end
93
+
94
+ def log_out
95
+ Auth.logged_in_element.click
96
+ dropdown = browser.wait(:like => [:ul, :class, 'dropdown-menu'])
97
+ browser.find(
98
+ :like => [:a, :id, 'link-logout'],
99
+ :context => dropdown
100
+ ).click
101
+ end
102
+
103
+ def log_in(user=nil, renew_session=false)
104
+ # If user=nil, we expect that there already is user data loaded in a previous step.
105
+ User.load_user_data(user) unless user.nil?
106
+
107
+ Auth.username_field.to_subtype.set(User.get('username'))
108
+ Auth.password_field.to_subtype.set(User.get('password'))
109
+ Auth.login_button.click
110
+
111
+ unless Auth.is_logged_in? user
112
+ alert = browser.find(:like => [:div, :class, 'alert'], :throw => false)
113
+ if alert.nil?
114
+ error "Failed to log in user #{user}"
115
+ else
116
+ alert.flash
117
+ error "Found error while logging in #{user}: `#{alert.text}`"
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,64 @@
1
+ # Simple helper that makes navigating using the config file easier
2
+ # It will check if a given string is a URL or a config value and goto that page accordingly
3
+ module Nav
4
+ extend LapisLazuli
5
+ class << self
6
+
7
+ # Navigates to a given URL or page.url configuration if the current URL is not the same
8
+ # Then confirms that the new URL is loaded.
9
+ def to(config_page_or_url, force_refresh = false)
10
+ url = self.set_url(config_page_or_url)
11
+ browser.goto url unless url == browser.url and !force_refresh
12
+ Nav.wait_for_url url
13
+ end
14
+
15
+ # Waits until the browser URL is the same as the given URL
16
+ def wait_for_url(url)
17
+ browser.wait_until(timeout: 5, message: "URL did not become `#{url}`") {
18
+ browser.url.include? url
19
+ }
20
+ end
21
+
22
+ # Loads the URL from the config, prioritized from top to bottom:
23
+ # production.pages.home
24
+ # production.pages.home.path
25
+ # pages.home
26
+ # pages.home.path
27
+ def get_url page
28
+ begin
29
+ return env_or_config("pages.#{page}")
30
+ rescue RuntimeError
31
+ return env_or_config("pages.#{page}.path")
32
+ end
33
+ end
34
+
35
+ # Confirms if the given URL is a valid URL
36
+ def is_url? string
37
+ uri = URI.parse(string)
38
+ %w( http https ).include?(uri.scheme)
39
+ rescue URI::BadURIError
40
+ false
41
+ rescue URI::InvalidURIError
42
+ false
43
+ end
44
+
45
+ # returns the expected URL
46
+ def set_url(config_page_or_url)
47
+ if Nav.is_url? config_page_or_url
48
+ # Return the given URL if it alreadt is a valid URL
49
+ return config_page_or_url
50
+ else
51
+ # Look for the URL in the config files
52
+ path_or_url = get_url config_page_or_url
53
+ if Nav.is_url? path_or_url
54
+ # If it is a URL now, then return it
55
+ return path_or_url
56
+ else
57
+ # Else add an expected 'root' to the path.
58
+ return env('root') + path_or_url
59
+ end
60
+ end
61
+ end
62
+
63
+ end
64
+ end
@@ -0,0 +1,93 @@
1
+ module Register
2
+
3
+ extend LapisLazuli
4
+ class << self
5
+
6
+ # This is a list of elements relevant for this helper.
7
+ # The following is short notation, *only* use this if the element selector can be done in 1 line.
8
+ # @formatter:off
9
+ def form; browser.wait(:like => [:form, :id, 'form-register']); end
10
+ def open_register_button; browser.find(:like => [:button, :id, 'button-register']); end
11
+ def username_field; browser.find(:element => {:name => 'username'}, :context => Register.form); end
12
+ def password_field; browser.find(:element => {:name => 'password'}, :context => Register.form); end
13
+ def experience_field; browser.find(:like => [:select, :id, "register-experience"], :context => form); end
14
+ def biography_field; browser.find(:like => [:textarea, :id, 'register-bio']); end
15
+ def policy_checkbox; browser.find(:like => [:input, :id, 'register-complete-all']) end
16
+ def submit_button; browser.find(:button => {:id => 'button-save'}, :context => Register.form); end
17
+ # @formatter:on
18
+
19
+ def gender_radio(gender)
20
+ browser.find(
21
+ :label => {:text => /#{gender}/i},
22
+ :context => Register.form,
23
+ :message => "Unable to find gender `#{gender}`, are you sure it's an option to select?"
24
+ )
25
+ end
26
+
27
+ def select_experiences(*experience_list)
28
+ experience_list.each do |exp|
29
+ option = browser.find(
30
+ :option => {:value => /#{exp}/i},
31
+ :context => Register.experience_field
32
+ )
33
+ option.click(:control)
34
+ end
35
+ end
36
+
37
+ # The following 3 functions are a typical example of something to use.
38
+ # First a function in which you perform an action (open_something, click_something, press_something)
39
+ def open_registration
40
+ Register.open_register_button.click
41
+ end
42
+
43
+ # Second, a function that confirms that the action was successful
44
+ def is_registration_open?
45
+ return Register.form rescue false
46
+ end
47
+
48
+ # And finally as function that ensures an action was successfully completed.
49
+ def ensure_open_registrarion
50
+ Auth.ensure_log_out
51
+ Register.open_registration unless Register.is_registration_open?
52
+ end
53
+
54
+ def fill_form
55
+ Register.username_field.set(User.get('username'))
56
+ Register.password_field.set(User.get('password'))
57
+ Register.gender_radio(User.get('gender')).click
58
+ Register.select_experiences(User.get('experience').split(','))
59
+ Register.biography_field.set(User.get('biography'))
60
+ Register.policy_checkbox.set((User.get('complete_all').to_i == 1))
61
+ end
62
+
63
+ def submit_form
64
+ Register.submit_button.click
65
+ end
66
+
67
+ def register_user
68
+ Register.fill_form
69
+ Register.submit_form
70
+ end
71
+
72
+ def registration_result
73
+ alert = browser.wait(like: [:div, :class, 'alert'], timeout: 2, throw: false)
74
+ if alert.nil?
75
+ return false, 'No message was displayed after registering'
76
+ elsif !alert.text.include? User.get('username')
77
+ return false, "An error message did display, but didn't contain the expected text: `#{alert.text}`"
78
+ end
79
+ return true, 'Successfully found the success message'
80
+ end
81
+
82
+ def ensure_registered(user)
83
+ begin
84
+ Auth.ensure_log_in(user)
85
+ Auth.log_out
86
+ rescue Exception => e
87
+ Register.ensure_open_registrarion
88
+ Register.register_user
89
+ end
90
+ end
91
+
92
+ end
93
+ end
@@ -0,0 +1,74 @@
1
+ # This helper loads user data from the config files.
2
+ # After loading the data, it will overwrite certain strings, like __TIMESTAMP__ to randomize information
3
+ module User
4
+ extend LapisLazuli
5
+
6
+ class << self
7
+ @@data = nil
8
+
9
+ def load_user_data(user)
10
+ data = config('users.default-user')
11
+ begin
12
+ specific_data = config("users.#{user}")
13
+ rescue Exception => err1
14
+ begin
15
+ specific_data = config("users.#{ENV['TEST_ENV']}.#{user}")
16
+ rescue Exception => err2
17
+ error "The given user `#{user}` was not found in any of the config files:\n- #{err1.message}\n- #{err2.message}"
18
+ end
19
+ end
20
+ new_data = data.merge specific_data
21
+ @@data = replace_hash_constants(new_data)
22
+ end
23
+
24
+ def get(field)
25
+ return @@data[field]
26
+ end
27
+
28
+ def set(field, value)
29
+ @@data[field] = User.replace_constants(value)
30
+ end
31
+
32
+ # Replace random or time values of a complete hash
33
+ def replace_hash_constants(hash)
34
+ if hash.respond_to? :each
35
+ new_hash = {}
36
+ hash.each do |key, value|
37
+ new_hash[key] = replace_constants(value)
38
+ end
39
+ else
40
+ new_hash = replace_constants(hash)
41
+ end
42
+ return new_hash
43
+ end
44
+
45
+ # replace certain constants in a string, for example '_TIMESTAMP_' becomes '154875631'
46
+ def replace_constants(value)
47
+ if value.to_s == value
48
+ epoch = Time.now.to_i
49
+ alpha = number_to_letter(epoch)
50
+ timestamp = Time.now.strftime("D%Y-%M-%d-T%H-%M-%S")
51
+
52
+ old_val = value.to_s
53
+ value = value.sub('_RAND_', epoch.to_s)
54
+ value = value.sub('_TIMESTAMP_', timestamp)
55
+ value = value.sub('_RAND-ALPHA_', alpha)
56
+ unless value == old_val
57
+ log.debug "#{old_val} > #{value}"
58
+ end
59
+ end
60
+ return value
61
+ end
62
+
63
+ def number_to_letter(numbers)
64
+ num_string = numbers.to_s
65
+ alpha26 = ("a".."j").to_a
66
+ letters = ''
67
+ num_string.scan(/./).each do |number|
68
+ letters += alpha26[number.to_i]
69
+ end
70
+ return letters
71
+ end
72
+
73
+ end
74
+ end