appium_lib 0.24.1 → 1.0.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.
Files changed (132) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +17 -8
  3. data/android_tests/Gemfile +1 -0
  4. data/android_tests/LICENSE-2.0.txt +202 -0
  5. data/android_tests/Rakefile +61 -0
  6. data/android_tests/api.apk +0 -0
  7. data/android_tests/appium.txt +3 -0
  8. data/android_tests/flaky.txt +1 -0
  9. data/android_tests/lib/android/specs/android/dynamic.rb +5 -0
  10. data/android_tests/lib/android/specs/android/element/alert.rb +41 -0
  11. data/android_tests/lib/android/specs/android/element/button.rb +55 -0
  12. data/android_tests/lib/android/specs/android/element/generic.rb +48 -0
  13. data/android_tests/lib/android/specs/android/element/text.rb +39 -0
  14. data/android_tests/lib/android/specs/android/element/textfield.rb +60 -0
  15. data/android_tests/lib/android/specs/android/helper.rb +80 -0
  16. data/android_tests/lib/android/specs/android/patch.rb +14 -0
  17. data/android_tests/lib/android/specs/common/device.rb +117 -0
  18. data/android_tests/lib/android/specs/common/element/window.rb +9 -0
  19. data/android_tests/lib/android/specs/common/helper.rb +112 -0
  20. data/android_tests/lib/android/specs/common/patch.rb +69 -0
  21. data/android_tests/lib/android/specs/common/version.rb +9 -0
  22. data/android_tests/lib/android/specs/driver.rb +174 -0
  23. data/android_tests/lib/format.rb +49 -0
  24. data/android_tests/lib/run.rb +72 -0
  25. data/android_tests/readme.md +27 -0
  26. data/appium_lib.gemspec +8 -5
  27. data/docs/android_docs.md +1052 -716
  28. data/docs/ios_docs.md +657 -834
  29. data/docs_gen/make_docs.rb +1 -3
  30. data/ios_tests/Gemfile +1 -0
  31. data/ios_tests/LICENSE-2.0.txt +202 -0
  32. data/ios_tests/Rakefile +47 -0
  33. data/ios_tests/UICatalog.app.zip +0 -0
  34. data/ios_tests/UICatalog.app/12-6AM.png +0 -0
  35. data/ios_tests/UICatalog.app/12-6PM.png +0 -0
  36. data/ios_tests/UICatalog.app/6-12AM.png +0 -0
  37. data/ios_tests/UICatalog.app/6-12PM.png +0 -0
  38. data/ios_tests/UICatalog.app/Default-568h@2x.png +0 -0
  39. data/ios_tests/UICatalog.app/Default@2x.png +0 -0
  40. data/ios_tests/UICatalog.app/Info.plist +0 -0
  41. data/ios_tests/UICatalog.app/PkgInfo +1 -0
  42. data/ios_tests/UICatalog.app/UIButton_custom.png +0 -0
  43. data/ios_tests/UICatalog.app/UICatalog +0 -0
  44. data/ios_tests/UICatalog.app/blueButton.png +0 -0
  45. data/ios_tests/UICatalog.app/bookmarkImage.png +0 -0
  46. data/ios_tests/UICatalog.app/bookmarkImageHighlighted.png +0 -0
  47. data/ios_tests/UICatalog.app/divider.png +0 -0
  48. data/ios_tests/UICatalog.app/en.lproj/AlertsViewController.nib +0 -0
  49. data/ios_tests/UICatalog.app/en.lproj/ButtonsViewController.nib +0 -0
  50. data/ios_tests/UICatalog.app/en.lproj/ControlsViewController.nib +0 -0
  51. data/ios_tests/UICatalog.app/en.lproj/ImagesViewController.nib +0 -0
  52. data/ios_tests/UICatalog.app/en.lproj/Localizable.strings +0 -0
  53. data/ios_tests/UICatalog.app/en.lproj/MainWindow.nib +0 -0
  54. data/ios_tests/UICatalog.app/en.lproj/PickerViewController.nib +0 -0
  55. data/ios_tests/UICatalog.app/en.lproj/SearchBarController.nib +0 -0
  56. data/ios_tests/UICatalog.app/en.lproj/SegmentViewController.nib +0 -0
  57. data/ios_tests/UICatalog.app/en.lproj/TextFieldController.nib +0 -0
  58. data/ios_tests/UICatalog.app/en.lproj/TextViewController.nib +0 -0
  59. data/ios_tests/UICatalog.app/en.lproj/ToolbarViewController.nib +0 -0
  60. data/ios_tests/UICatalog.app/en.lproj/TransitionViewController.nib +0 -0
  61. data/ios_tests/UICatalog.app/en.lproj/WebViewController.nib +0 -0
  62. data/ios_tests/UICatalog.app/orangeslide.png +0 -0
  63. data/ios_tests/UICatalog.app/scene1.jpg +0 -0
  64. data/ios_tests/UICatalog.app/scene2.jpg +0 -0
  65. data/ios_tests/UICatalog.app/scene3.jpg +0 -0
  66. data/ios_tests/UICatalog.app/scene4.jpg +0 -0
  67. data/ios_tests/UICatalog.app/scene5.jpg +0 -0
  68. data/ios_tests/UICatalog.app/searchBarBackground.png +0 -0
  69. data/ios_tests/UICatalog.app/segment_check.png +0 -0
  70. data/ios_tests/UICatalog.app/segment_search.png +0 -0
  71. data/ios_tests/UICatalog.app/segment_tools.png +0 -0
  72. data/ios_tests/UICatalog.app/segmentedBackground.png +0 -0
  73. data/ios_tests/UICatalog.app/slider_ball.png +0 -0
  74. data/ios_tests/UICatalog.app/toolbarBackground.png +0 -0
  75. data/ios_tests/UICatalog.app/whiteButton.png +0 -0
  76. data/ios_tests/UICatalog.app/yellowslide.png +0 -0
  77. data/ios_tests/appium.txt +3 -0
  78. data/ios_tests/flaky.txt +1 -0
  79. data/ios_tests/lib/format.rb +25 -0
  80. data/ios_tests/lib/ios/specs/common/element/window.rb +15 -0
  81. data/ios_tests/lib/ios/specs/common/helper.rb +204 -0
  82. data/ios_tests/lib/ios/specs/common/patch.rb +50 -0
  83. data/ios_tests/lib/ios/specs/common/version.rb +17 -0
  84. data/ios_tests/lib/ios/specs/device/device.rb +82 -0
  85. data/ios_tests/lib/ios/specs/device/multi_touch.rb +12 -0
  86. data/ios_tests/lib/ios/specs/device/touch_actions.rb +15 -0
  87. data/ios_tests/lib/ios/specs/driver.rb +203 -0
  88. data/ios_tests/lib/ios/specs/ios/element/alert.rb +48 -0
  89. data/ios_tests/lib/ios/specs/ios/element/button.rb +58 -0
  90. data/ios_tests/lib/ios/specs/ios/element/generic.rb +35 -0
  91. data/ios_tests/lib/ios/specs/ios/element/text.rb +54 -0
  92. data/ios_tests/lib/ios/specs/ios/element/textfield.rb +123 -0
  93. data/ios_tests/lib/ios/specs/ios/helper.rb +27 -0
  94. data/ios_tests/lib/ios/specs/ios/patch.rb +30 -0
  95. data/ios_tests/lib/run.rb +106 -0
  96. data/ios_tests/readme.md +30 -0
  97. data/ios_tests/upload/sauce_storage.rb +64 -0
  98. data/ios_tests/upload/upload.rb +6 -0
  99. data/lib/appium_lib.rb +4 -14
  100. data/lib/appium_lib/android/dynamic.rb +30 -32
  101. data/lib/appium_lib/android/element/alert.rb +34 -33
  102. data/lib/appium_lib/android/element/button.rb +91 -0
  103. data/lib/appium_lib/android/element/generic.rb +51 -146
  104. data/lib/appium_lib/android/element/text.rb +54 -0
  105. data/lib/appium_lib/android/element/textfield.rb +46 -41
  106. data/lib/appium_lib/android/helper.rb +248 -417
  107. data/lib/appium_lib/android/mobile_methods.rb +17 -0
  108. data/lib/appium_lib/android/patch.rb +9 -8
  109. data/lib/appium_lib/awesome_print/ostruct.rb +33 -0
  110. data/lib/appium_lib/common/element/window.rb +9 -8
  111. data/lib/appium_lib/common/helper.rb +182 -243
  112. data/lib/appium_lib/common/patch.rb +65 -79
  113. data/lib/appium_lib/common/version.rb +2 -3
  114. data/lib/appium_lib/device/device.rb +339 -0
  115. data/lib/appium_lib/device/multi_touch.rb +94 -0
  116. data/lib/appium_lib/device/touch_actions.rb +142 -0
  117. data/lib/appium_lib/driver.rb +217 -306
  118. data/lib/appium_lib/ios/element/alert.rb +16 -92
  119. data/lib/appium_lib/ios/element/button.rb +55 -0
  120. data/lib/appium_lib/ios/element/generic.rb +27 -160
  121. data/lib/appium_lib/ios/element/text.rb +54 -0
  122. data/lib/appium_lib/ios/element/textfield.rb +78 -65
  123. data/lib/appium_lib/ios/helper.rb +300 -190
  124. data/lib/appium_lib/ios/mobile_methods.rb +17 -0
  125. data/lib/appium_lib/ios/patch.rb +55 -41
  126. data/lib/appium_lib/logger.rb +13 -0
  127. data/lib/appium_lib/rails/duplicable.rb +116 -0
  128. data/readme.md +6 -1
  129. data/release_notes.md +118 -0
  130. metadata +170 -12
  131. data/lib/appium_lib/common/element/button.rb +0 -83
  132. data/lib/appium_lib/common/element/text.rb +0 -61
@@ -0,0 +1,50 @@
1
+ =begin
2
+ Skip:
3
+ status # status patches are already tested in driver.rb
4
+ raw_execute # debug output for Pry
5
+ =end
6
+ # rake ios[common/patch]
7
+ describe 'common/patch.rb' do
8
+ def before_first
9
+ screen.must_equal catalog
10
+ end
11
+
12
+ t 'before_first' do
13
+ before_first
14
+ end
15
+
16
+ # Attributes are busted in Android.
17
+ # Blocked on https://github.com/appium/appium/issues/628
18
+ describe 'Selenium::WebDriver::Element methods' do
19
+ # todo: t 'value' do; end
20
+
21
+ t 'name' do
22
+ first_s_text.name.must_equal 'UICatalog'
23
+ end
24
+
25
+ # t 'tag_name' do; end # Doesn't work on Android
26
+
27
+ t 'location_rel' do
28
+ loc = first_s_text.location_rel
29
+ loc.x.class.must_equal String
30
+ loc.y.class.must_equal String
31
+ end
32
+ end
33
+
34
+ describe 'common patch' do
35
+ # By default, the webdriver gem will return message instead of origValue
36
+ # {"message":"An unknown server-side error occurred while processing the command.","origValue":"Strategy id is not valid."}
37
+ t 'error_message' do
38
+ value = ''
39
+ begin
40
+ set_wait 0
41
+ find_element(:css, 'ok')
42
+ rescue Exception => e;
43
+ value = e.message
44
+ ensure
45
+ set_wait
46
+ end
47
+ value.must_equal "Invalid locator strategy: css selector"
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,17 @@
1
+ describe 'version.rb' do
2
+ def before_first
3
+ screen.must_equal catalog
4
+ end
5
+
6
+ t 'before_first' do
7
+ before_first
8
+ end
9
+
10
+ t '::Appium::VERSION' do
11
+ ::Appium::VERSION.must_match /(\d+)\.(\d+).(\d+)/
12
+ end
13
+
14
+ t '::Appium::DATE' do
15
+ ::Appium::DATE.must_match /(\d+)\-(\d+)\-(\d+)/
16
+ end
17
+ end
@@ -0,0 +1,82 @@
1
+ describe 'device/device' do
2
+ def before_first
3
+ screen.must_equal catalog
4
+ end
5
+
6
+ t 'before_first' do
7
+ before_first
8
+ end
9
+
10
+ t 'lock' do
11
+ lock 5
12
+ tag('UIAButton').name.must_equal 'SlideToUnlock'
13
+
14
+ # It appears that lockForDuration doesn't.
15
+ close_app
16
+ launch
17
+ end
18
+
19
+ t 'background_app' do
20
+ background_app 5
21
+ screen.must_equal catalog
22
+ end
23
+
24
+ t 'is_installed' do
25
+ installed = is_installed? "Derrp"
26
+ installed.must_equal false
27
+ end
28
+
29
+ t 'shake' do
30
+ shake
31
+ end
32
+
33
+ t 'close and launch' do
34
+ close_app
35
+ launch
36
+ tag('UIANavigationBar').name.must_equal 'UICatalog'
37
+ end
38
+
39
+ t 'reset' do
40
+ reset
41
+ end
42
+
43
+ t 'available_contexts' do
44
+ available_contexts.must_equal ["NATIVE_APP"]
45
+ end
46
+
47
+ t 'current_context' do
48
+ current_context.must_equal nil
49
+ end
50
+
51
+ t 'switch_to_default_context' do
52
+ switch_to_default_context
53
+ current_context.must_equal nil
54
+ end
55
+
56
+ t 'app_strings' do
57
+ app_strings.must_include "SearchBarExplain"
58
+ end
59
+
60
+ t 'action_chain' do
61
+ ac = Appium::TouchAction.new
62
+ e = find_element(:name, 'Buttons, Various uses of UIButton')
63
+ ac.press element: e, x: 10, y: 10
64
+ ac.perform
65
+ back
66
+ end
67
+
68
+ t 'swipe' do
69
+ swipe start_x: 75, start_y: 500, end_x: 75, end_y: 0, duration: 0.8
70
+ end
71
+
72
+ t 'pinch & zoom' do
73
+ s_text('Images, Use of UIImageView').click
74
+ zoom 200
75
+ pinch 75
76
+ end
77
+
78
+ t 'file movement' do
79
+ read_file = pull_file 'Library/AddressBook/AddressBook.sqlitedb'
80
+ read_file.start_with?('SQLite format').must_equal true
81
+ end
82
+ end
@@ -0,0 +1,12 @@
1
+ describe 'device/multi_touch' do
2
+ t { } # place holder test
3
+ end
4
+
5
+ # todo: write tests
6
+ =begin
7
+ pinch
8
+ zoom
9
+ initialize
10
+ add
11
+ perform
12
+ =end
@@ -0,0 +1,15 @@
1
+ describe 'device/touch_actions' do
2
+ t { } # place holder test
3
+ end
4
+ # todo: write tests
5
+ =begin
6
+ move_to
7
+ press_for_duration
8
+ press
9
+ release
10
+ tap
11
+ wait
12
+ swipe
13
+ perform
14
+ cancel
15
+ =end
@@ -0,0 +1,203 @@
1
+ # rake ios[driver]
2
+ describe 'driver' do
3
+ def before_first
4
+ screen.must_equal catalog
5
+ end
6
+
7
+ t 'before_first' do
8
+ before_first
9
+ end
10
+
11
+ def is_sauce
12
+ ENV['UPLOAD_FILE'] && ENV['SAUCE_USERNAME']
13
+ end
14
+
15
+ t 'load_appium_txt' do
16
+ # skip this test if we're using Sauce
17
+ # the storage API doesn't have an on disk file
18
+ skip if is_sauce
19
+ # __FILE__ is '(eval)' so use env var set by the Rakefile
20
+ path = ENV['APPIUM_TXT']
21
+ opts = Appium.load_appium_txt file: path, verbose: true
22
+
23
+ actual = ''
24
+ actual = File.basename opts[:caps][:app] if opts && opts[:caps]
25
+ expected = 'UICatalog.app'
26
+ assert_equal expected, actual
27
+ end
28
+
29
+ describe 'Appium::Driver attributes' do
30
+ t 'verify all attributes' do
31
+ 2.times { set_wait 30 } # must set twice to validate last_waits
32
+ actual = driver_attributes
33
+ actual[:caps][:app] = File.basename actual[:caps][:app]
34
+ expected = { caps: { platformName: 'ios',
35
+ app: 'UICatalog.app' },
36
+ custom_url: false,
37
+ export_session: false,
38
+ default_wait: 30,
39
+ last_waits: [30, 30],
40
+ sauce_username: nil,
41
+ sauce_access_key: nil,
42
+ port: 4723,
43
+ device: :ios,
44
+ debug: true }
45
+
46
+ if actual != expected
47
+ diff = HashDiff.diff expected, actual
48
+ diff = "diff (expected, actual):\n#{diff}"
49
+ # example:
50
+ # change :ios in expected to match 'ios' in actual
51
+ # [["~", "caps.platformName", :ios, "ios"]]
52
+ message = "\n\nactual:\n\n: #{actual.ai}expected:\n\n#{expected.ai}\n\n#{diff}"
53
+ fail message
54
+ end
55
+ end
56
+
57
+ t 'verify attributes are immutable' do
58
+ driver_attributes[:caps][:app] = 'fake'
59
+ actual = File.basename driver_attributes[:caps][:app]
60
+ expected = 'UICatalog.app'
61
+ actual.must_equal expected
62
+ end
63
+
64
+ t 'no_wait' do
65
+ no_wait
66
+ default_wait.must_equal 0
67
+ set_wait 30
68
+ end
69
+
70
+ t 'default_wait attr' do
71
+ set_wait 31 # set wait and no_wait update default_wait
72
+ default_wait.must_equal 31
73
+ set_wait 30
74
+ end
75
+
76
+ t 'app_path attr' do
77
+ apk_name = File.basename driver_attributes[:caps][:app]
78
+
79
+ if is_sauce
80
+ apk_name.must_equal 'sauce-storage:UICatalog6.1.app.zip'
81
+ else
82
+ apk_name.must_equal 'UICatalog.app'
83
+ end
84
+ end
85
+
86
+ # Only used for Sauce Labs
87
+ t 'app_name attr' do
88
+ name_attr = driver_attributes[:caps][:name]
89
+ if is_sauce
90
+ name_attr.must_equal 'appium_lib_ios'
91
+ else
92
+ name_attr.must_be_nil
93
+ end
94
+ end
95
+
96
+ t 'sauce_username attr' do
97
+ sauce_username = driver_attributes[:sauce_username]
98
+ if is_sauce
99
+ sauce_username.must_equal 'appiumci'
100
+ else
101
+ sauce_username.must_be_nil
102
+ end
103
+ end
104
+
105
+ t 'sauce_access_key attr' do
106
+ sauce_access_key = driver_attributes[:sauce_access_key]
107
+ if is_sauce
108
+ sauce_access_key.must_match /\h{8}-\h{4}-\h{4}-\h{4}-\h{12}/
109
+ else
110
+ sauce_access_key.must_be_nil
111
+ end
112
+ end
113
+ end
114
+
115
+ describe 'Appium::Driver' do
116
+ t '@@loaded' do
117
+ loaded = $driver.class.class_variable_get :@@loaded
118
+ loaded.must_equal true
119
+ end
120
+
121
+ t '$driver.class' do
122
+ $driver.class.must_equal Appium::Driver
123
+ end
124
+ end
125
+
126
+ describe 'methods' do
127
+ t 'status' do
128
+ appium_server_version['build'].keys.sort.must_equal %w(revision version)
129
+ end
130
+
131
+ t 'server_version' do
132
+ server_version = appium_server_version['build']['version']
133
+ if is_sauce
134
+ server_version.must_match 'Sauce OnDemand'
135
+ else
136
+ server_version.must_match /(\d+)\.(\d+).(\d+)/
137
+ end
138
+ end
139
+
140
+ t 'restart' do
141
+ restart
142
+ s_text 'buttons'
143
+ end
144
+
145
+ t 'driver' do
146
+ driver.browser.must_equal :iOS
147
+ end
148
+
149
+ =begin
150
+ Skip:
151
+ screenshot # this is slow and already tested by Appium
152
+ driver_quit # tested by restart
153
+ start_driver # tested by restart
154
+ =end
155
+ t 'set_wait' do
156
+ # fill the @last_waits array with: [30, 30]
157
+ set_wait(30).must_equal(30)
158
+ set_wait(30).must_equal(30)
159
+
160
+ # verify set_wait with no args works correctly
161
+ set_wait.must_equal(30)
162
+ set_wait(30).must_equal(30)
163
+ set_wait.must_equal(30)
164
+
165
+ set_wait(2).must_equal(2)
166
+ set_wait.must_equal(30)
167
+ set_wait(3).must_equal(3)
168
+ set_wait.must_equal(2)
169
+
170
+ set_wait(2).must_equal(2)
171
+ set_wait(3).must_equal(3)
172
+ set_wait.must_equal(2)
173
+ end
174
+
175
+ t 'default_wait' do
176
+ set_wait 30
177
+ default_wait.must_equal 30 # set in run.rb
178
+ end
179
+
180
+ # returns true unless an error is raised
181
+ t 'exists' do
182
+ exists(0, 0) { true }.must_equal true
183
+ exists(0, 0) { raise 'error' }.must_equal false
184
+ end
185
+
186
+ # any script
187
+ t 'execute_script' do
188
+ execute_script %q(au.mainApp().getFirstWithPredicate("name contains[c] 'button'");)
189
+ end
190
+
191
+ # any elements
192
+ t 'find_elements' do
193
+ find_elements(:class, 'UIAStaticText').length.must_equal 13
194
+ end
195
+
196
+ # any element
197
+ t 'find_element' do
198
+ find_element(:class, 'UIAStaticText').class.must_equal Selenium::WebDriver::Element
199
+ end
200
+
201
+ # Skip: x # x is only used in Pry
202
+ end
203
+ end
@@ -0,0 +1,48 @@
1
+ # rake ios[ios/element/alert]
2
+ describe 'ios/element/alert' do
3
+ def nav_once
4
+ screen.must_equal catalog
5
+ wait_true { s_text('alerts').click; tag('UIANavigationBar').name == 'Alerts' } # wait for true
6
+ tag('UIANavigationBar').name.must_equal 'Alerts'
7
+
8
+ # redefine method as no-op after it's invoked once
9
+ self.class.send :define_method, :nav_once, proc {}
10
+ end
11
+
12
+ def after_last
13
+ alert_accept if exists { s_text('UIActionSheet <title>') }
14
+ back_click
15
+ screen.must_equal catalog
16
+ sleep 1
17
+ end
18
+
19
+ before do
20
+ nav_once
21
+ open_alert
22
+ end
23
+
24
+ def open_alert
25
+ wait_true do
26
+ return true if exists { s_text('UIActionSheet <title>') }
27
+ s_text('Show OK-Cancel').click
28
+ s_text('UIActionSheet <title>').displayed?
29
+ end
30
+ end
31
+
32
+ # iOS 7 is not using the alert methods. alert is nil.
33
+ def ios7_alert_detected
34
+ execute_script 'UIATarget.localTarget().frontMostApp().alert().isNil()'
35
+ end
36
+
37
+ t 'alert_accept' do
38
+ alert_accept
39
+ end
40
+
41
+ t 'alert_dismiss' do
42
+ alert_dismiss
43
+ end
44
+
45
+ t 'after_last' do
46
+ after_last
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ # rake ios['ios/element/button']
2
+ describe 'ios/element/button' do
3
+ def before_first
4
+ screen.must_equal catalog
5
+ # nav to buttons activity
6
+ wait { s_text('buttons').click }
7
+ end
8
+
9
+ def after_last
10
+ # nav back to start
11
+ back_click
12
+ end
13
+
14
+ t 'before_first' do
15
+ before_first
16
+ end
17
+
18
+ def gray
19
+ 'Gray'
20
+ end
21
+
22
+ t 'button' do
23
+ # by index
24
+ button(2).name.must_equal gray
25
+
26
+ # by name contains
27
+ button('ray').name.must_equal gray
28
+ end
29
+
30
+ t 'buttons' do
31
+ exp = ['Back', 'Gray', 'Right pointing arrow']
32
+ buttons('a').map { |e| e.name }.must_equal exp
33
+ end
34
+
35
+ t 'first_button' do
36
+ first_button.name.must_equal 'Back'
37
+ end
38
+
39
+ t 'last_button' do
40
+ last_button.name.must_equal 'Rounded'
41
+ end
42
+
43
+ t 'button_exact' do
44
+ button_exact(gray).name.must_equal gray
45
+ end
46
+
47
+ t 'buttons_exact' do
48
+ buttons_exact(gray).first.name.must_equal gray
49
+ end
50
+
51
+ t 'e_buttons' do
52
+ e_buttons.length.must_equal 4
53
+ end
54
+
55
+ t 'after_last' do
56
+ after_last
57
+ end
58
+ end