web4cucumber 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/COPYING +674 -0
- data/README.md +250 -0
- data/Rakefile +1 -0
- data/examples/testproject/Gemfile +10 -0
- data/examples/testproject/Gemfile.lock +59 -0
- data/examples/testproject/features/example.feature +12 -0
- data/examples/testproject/features/step_definitions/web.rb +23 -0
- data/examples/testproject/features/support/env.rb +20 -0
- data/examples/testproject/lib/log.rb +17 -0
- data/examples/testproject/lib/web.rb +19 -0
- data/examples/testproject/lib/web/check_community_header.yaml +117 -0
- data/examples/testproject/lib/web/login.yaml +26 -0
- data/lib/web4cucumber.rb +641 -0
- data/lib/web4cucumber/version.rb +3 -0
- data/web4cucumber.gemspec +23 -0
- metadata +130 -0
data/README.md
ADDED
@@ -0,0 +1,250 @@
|
|
1
|
+
The main filosophy behind this project is:
|
2
|
+
Separate the data from the logic as much as you possibly can. Have you had to
|
3
|
+
go through a tremendous ruby module with hundreds of methods which click
|
4
|
+
buttons, input text in form fields, randomly wait (probably, till the page is
|
5
|
+
loaded) and randomly take screenshots? Have you dreamed to have a way to store
|
6
|
+
test data in a separate simple text files and feed them to the cucumber?
|
7
|
+
Or maybe you have hundreds of scenarios looking like this:
|
8
|
+
```
|
9
|
+
When I click the element ":id=>'click_me'"
|
10
|
+
And I write "hello" in the form with ":id=>'topic'"
|
11
|
+
And I write "Blahblahblah" in the form with ":id=>'body'"
|
12
|
+
And I click the element ":id=>'submit'"
|
13
|
+
Then the page shuold contain "Success"
|
14
|
+
```
|
15
|
+
And you sometimes ask yourselves "Is it a cucumber way to write scenarios like
|
16
|
+
this?" Well the answer is no. Probably, all you need is to write a couple of
|
17
|
+
steps instead:
|
18
|
+
```
|
19
|
+
When I create a blogpost with:
|
20
|
+
|title|body|
|
21
|
+
|hello|Blahblahblah|
|
22
|
+
Then the step should succeed
|
23
|
+
```
|
24
|
+
And have a simple yaml file describing the corresponding webpage, that would
|
25
|
+
look like this:
|
26
|
+
```
|
27
|
+
blogpost_create:
|
28
|
+
pages:
|
29
|
+
- 'blogpost_create_page'
|
30
|
+
blogpost_create_page:
|
31
|
+
url: '/blogposts/new'
|
32
|
+
expected_fields:
|
33
|
+
title:
|
34
|
+
type: 'textfield'
|
35
|
+
selector:
|
36
|
+
id: 'topic'
|
37
|
+
body:
|
38
|
+
type: 'textfield'
|
39
|
+
selector:
|
40
|
+
id: 'body'
|
41
|
+
commit:
|
42
|
+
selector:
|
43
|
+
id: 'commit'
|
44
|
+
```
|
45
|
+
This library implements an abstraction layer between cucumber logic and Watir
|
46
|
+
webdriver. Using this library you no longer need to write the Watir-aware Ruby
|
47
|
+
code for low-level browser interaction. Instead you describe your pages and
|
48
|
+
actions to be performed in Web UI of your product in simple yaml-formatted
|
49
|
+
files. This approach allows you to separate the application data (html
|
50
|
+
properties of page elements) from test logic.
|
51
|
+
|
52
|
+
How to use this?
|
53
|
+
|
54
|
+
- Add `gem 'Web4Cucumber'` to you Gemfile and run `bundle install`
|
55
|
+
- In your project in one of your lib/*.rb files add `require "Web4Cucumber"` and inherit your own class from Web4Cucumber one, like this:
|
56
|
+
```
|
57
|
+
class Web < Web4Cucumber
|
58
|
+
def initialize(options)
|
59
|
+
super(options)
|
60
|
+
# Add here some code specific to your project, like
|
61
|
+
# @@logged_in = false
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
- In your features/support/env.rb in Before hook instatiate your beautiful class:
|
66
|
+
```
|
67
|
+
options = {
|
68
|
+
:base_url => "http://base_url_of_your_project",
|
69
|
+
:browser => :firefox, # or :chrome. Other browsers are not supported yet
|
70
|
+
:rules_path => "path_to_the_folder_with_your_yaml_files",
|
71
|
+
:logger => @logger
|
72
|
+
# You need to pass an object that will do the logging for you. I believe you have it implemented.
|
73
|
+
# If not, please take a look in the examples/testproject folder.
|
74
|
+
}
|
75
|
+
@web = Web.new(options)
|
76
|
+
|
77
|
+
```
|
78
|
+
From now on you will have @web, an instance of Web4Cucumber class with a bunch of convenient methods for high-level interactions with the browser, and, a browser running by default in the headless mode. For development and debugging purposes, however I recommend setting DEBUG_WEB environmental variable to *true* so that you will be presented with a visible browser window.
|
79
|
+
|
80
|
+
- Once this preparation is done, let's try to understand how to write yaml files. The key concept Web4Cucumber is built around is an *action*.
|
81
|
+
Action is any set of user actions you want to automate, it could be web search, form submissions, data upload etc. An action is described in a yaml file with the following structure:
|
82
|
+
|
83
|
+
```
|
84
|
+
our_test_action:
|
85
|
+
pages:
|
86
|
+
- 'first_page'
|
87
|
+
- 'second_page'
|
88
|
+
final_checkpoints:
|
89
|
+
alert_success:
|
90
|
+
selector:
|
91
|
+
text: 'You have successfully performed whatever you intended'
|
92
|
+
|
93
|
+
first_page:
|
94
|
+
url: '/some_relative_path/testme'
|
95
|
+
expected_fields:
|
96
|
+
field_one_on_page_one:
|
97
|
+
type: textfield
|
98
|
+
selector:
|
99
|
+
id: 'i-am-field-1'
|
100
|
+
field_two_on_page_one:
|
101
|
+
type: textfield
|
102
|
+
selector:
|
103
|
+
class: 'generic-field-2-class'
|
104
|
+
checkpoints:
|
105
|
+
sometext:
|
106
|
+
selector:
|
107
|
+
text: 'I am text one on page one'
|
108
|
+
someothertext:
|
109
|
+
selector:
|
110
|
+
text: 'I am text two on page one'
|
111
|
+
commit:
|
112
|
+
selector:
|
113
|
+
text: 'Click me'
|
114
|
+
|
115
|
+
second_page:
|
116
|
+
sleep 2 # wait 2 seconds till the page is loaded
|
117
|
+
expected_fields:
|
118
|
+
field_one_on_page_two:
|
119
|
+
type: filefield
|
120
|
+
selector:
|
121
|
+
id: 'upload-something'
|
122
|
+
field_two_on_page_two:
|
123
|
+
type: textfield
|
124
|
+
selector:
|
125
|
+
xpath '//*[@id="fancy_something"]/span'
|
126
|
+
def_value: 'If you dont pass :field_two_on_page_two value, this text will go there'
|
127
|
+
checkpoints:
|
128
|
+
sometext:
|
129
|
+
selector:
|
130
|
+
text: 'I am text one on page two'
|
131
|
+
someothertext:
|
132
|
+
selector:
|
133
|
+
text: 'I am text two on page two'
|
134
|
+
negative_checkpoints:
|
135
|
+
error_message:
|
136
|
+
selector:
|
137
|
+
text: 'I am a critical error message! Wish you never see me on this page!'
|
138
|
+
commit:
|
139
|
+
scroll: true
|
140
|
+
# sometimes the element you need is outside the area of the virtual viewport
|
141
|
+
# so webdriver is unable to interact with it. Use this keyword to execute a simple
|
142
|
+
# javascript *scroll_into_view* function
|
143
|
+
selector:
|
144
|
+
text: 'Click me too'
|
145
|
+
```
|
146
|
+
If you take a closer look at this yaml structure, you'll notice that it describes describes two web pages pages. The first one is accessed through relative url "/some_relative_path/testme" that is being appended to your project's base_url (used during Web initialization).
|
147
|
+
The second page is accessed by clicking the "Click me" button on the first page. The yaml file describes 2 textfields on first page and one textfield and one filefield on the second page. It also describes checkpoints - elements whose presence will be asserted during the action execution. We can provide default values for textfields right inside the yaml with the *def_value* keyword. Now the most interesting question: how do we use that?
|
148
|
+
|
149
|
+
- The whole workflow would look like this. You would create your step:
|
150
|
+
|
151
|
+
```
|
152
|
+
When I run my beautiful action with:
|
153
|
+
|option |value |
|
154
|
+
|field_one_on_page_one |some text |
|
155
|
+
|field_two_on_page_one | some other text|
|
156
|
+
|field_one_on_page_two | lib/files/myfile|
|
157
|
+
# We remember that field_one_on_page_two is a filefield
|
158
|
+
# And we leave default value for second field on the page two.
|
159
|
+
```
|
160
|
+
Then you would write your step definition:
|
161
|
+
```
|
162
|
+
When /^I run my beautiful action with:$/ do |table|
|
163
|
+
options = {}
|
164
|
+
table.rows.each do |row|
|
165
|
+
options[row[0].to_sym] = row[1]
|
166
|
+
end
|
167
|
+
@result = @web.run_action(:our_test_action, options)
|
168
|
+
end
|
169
|
+
```
|
170
|
+
As you can see, field_names declared in the yaml files, like *field_one_on_page_one* are used as keys in the hash, passed to the *@web.run_action* method as the second parameter. First parameter being the name of the action also declared in the yaml file.
|
171
|
+
If you then inspect the @result object then ideally you would get something like this:
|
172
|
+
```
|
173
|
+
pp @result
|
174
|
+
{:result=>true,
|
175
|
+
:failed_positive_checkpoints=>[],
|
176
|
+
:failed_negative_checkpoints=>[],
|
177
|
+
:errors=>[]}
|
178
|
+
|
179
|
+
```
|
180
|
+
If something went wrong and the webdriver was unable to find some checkpoints or other fields described in the yaml file, then the @result[:result] would be changed to *false* and @result[:failed_positive_checkpoints] array will be populated with the elements that were not found, @result[:failed_negative_checkpoints] - with elements you described in *negative_checkpoints* and @result[:errors] - with exception messages. Also, a *screenshots* folder will be created in your working directory, containing browser screenshot of the page that caught an error.
|
181
|
+
|
182
|
+
Now we have to master yaml file creation in details.
|
183
|
+
The structure of yaml files supported by Web4Cucumber library is quite flexible. You could store page descriptions separately from action descriptions, or you could store all your actions in one files, only make sure each action has a precide list of corresponding pages in exactly the same order that they appear during action execution. Besides list of pages only one keyword is supported under an action:
|
184
|
+
*final_checkpoints*. This is a list of elements whose presence we expect once the action is finished.
|
185
|
+
|
186
|
+
The key part of an *action* is a *page*. The following keywords can be used under the page description:
|
187
|
+
1. expected_fields
|
188
|
+
2. checkpoints
|
189
|
+
3. negative_checkpoints
|
190
|
+
4. links
|
191
|
+
5. base_url
|
192
|
+
6. url
|
193
|
+
7. sleep
|
194
|
+
8. commit
|
195
|
+
|
196
|
+
- *expected_fields* is a section where field descriptions go. Field structure will be discussed in more details later.
|
197
|
+
- *checkpoints*, as was mentioned above, is a list of named web elements you expect to be present on a web page.
|
198
|
+
- *negative_checkpoints* similarly a list of named elements you do not expect on a page.
|
199
|
+
- *links* Is a list of links on a page that need to be checked in a web crawler kind of way. For each link you specify the selector by which the link can be accessed and a number of checkpoints (web elements) you wish to check on a page accesible via that particular link. A typycal link section of a page would look like this:
|
200
|
+
```
|
201
|
+
:links:
|
202
|
+
:getstarted:
|
203
|
+
:selector:
|
204
|
+
:text: "Get Started"
|
205
|
+
:checkpoints:
|
206
|
+
:getstarted:
|
207
|
+
:selector:
|
208
|
+
:text: "Getting Started"
|
209
|
+
:securitypolicy:
|
210
|
+
:selector:
|
211
|
+
:text: "Security Policy"
|
212
|
+
:checkpoints:
|
213
|
+
:securityinformation:
|
214
|
+
:selector:
|
215
|
+
:text: "Security Information"
|
216
|
+
```
|
217
|
+
In this example webdriver would click first on a link with test "Get Started", check that the page has an element with text "Get Started", then get back, click on the link with text "Security Policy", check that the page has an element with text "Security Information", then again get back.
|
218
|
+
- *base_url* - needed when you need to perform some action on a third-party software (for integration cases for example)
|
219
|
+
- *url* - a relative url path of the page (base_url for your project is passed to the Web4Cucumber class during instance initialization)
|
220
|
+
You can have a variable part of relative url, embraced between angle brackets. For example, if you have "/blog/<blogtype>/new" url in your yaml file and then pass {:blogtype=>'public'} in your options to the @web.run_action method, the webdriver will access the /blog/public/new relative url.
|
221
|
+
- *sleep* - a sleep interval in seconds, how long to wait before doing anything on this page: useful for slow loading pages
|
222
|
+
- *commit* - well this is a commit button - the one you normally click to submit a form. The only tricky part about commit comes when the page presents you with a javascript popup. In this case the commit section would look like this:
|
223
|
+
```
|
224
|
+
commit:
|
225
|
+
type: alert
|
226
|
+
```
|
227
|
+
that's it!
|
228
|
+
|
229
|
+
The last thing needed to be mentioned is the structure of the *field* itself. The *field* is mapped to a key in *options* passed to the @web.run_action method through it's name. That means, you pass a text to the particular textfield in the following way:
|
230
|
+
1. Describe the field in the yaml file
|
231
|
+
```
|
232
|
+
expected_fields:
|
233
|
+
login:
|
234
|
+
type: 'textfield'
|
235
|
+
selector:
|
236
|
+
id: 'user-login'
|
237
|
+
```
|
238
|
+
2. pass the value:
|
239
|
+
|
240
|
+
```@web.run_action(:some_action_name, {:login=>'username@example.com', <...other_options_go_here...>})```
|
241
|
+
That's it.
|
242
|
+
Different elemet types get treated differently by webdriver, textfields can not be clicked the way buttons do, so you need to provide element type. The following types are supported:
|
243
|
+
- select - a dropdown list. You need to pass the element value
|
244
|
+
- checkbox - self-explanatory
|
245
|
+
- radio - same here
|
246
|
+
- textfield - you can provide a text that will be inputted in this textfield
|
247
|
+
- textarea - self-explanatory
|
248
|
+
- filefield - provide a full path to the file you would upload here
|
249
|
+
- a -link
|
250
|
+
- element any element that can be simply clicked. No need to provide the type in this case, it will be implied by default.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,59 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
builder (3.2.2)
|
5
|
+
childprocess (0.5.5)
|
6
|
+
ffi (~> 1.0, >= 1.0.11)
|
7
|
+
coderay (1.1.0)
|
8
|
+
columnize (0.8.9)
|
9
|
+
cucumber (1.3.19)
|
10
|
+
builder (>= 2.1.2)
|
11
|
+
diff-lcs (>= 1.1.3)
|
12
|
+
gherkin (~> 2.12)
|
13
|
+
multi_json (>= 1.7.5, < 2.0)
|
14
|
+
multi_test (>= 0.1.2)
|
15
|
+
debugger (1.6.8)
|
16
|
+
columnize (>= 0.3.1)
|
17
|
+
debugger-linecache (~> 1.2.0)
|
18
|
+
debugger-ruby_core_source (~> 1.3.5)
|
19
|
+
debugger-linecache (1.2.0)
|
20
|
+
debugger-ruby_core_source (1.3.5)
|
21
|
+
diff-lcs (1.2.5)
|
22
|
+
ffi (1.9.6)
|
23
|
+
gherkin (2.12.2)
|
24
|
+
multi_json (~> 1.3)
|
25
|
+
headless (1.0.2)
|
26
|
+
json (1.8.2)
|
27
|
+
method_source (0.8.2)
|
28
|
+
multi_json (1.10.1)
|
29
|
+
multi_test (0.1.2)
|
30
|
+
pry (0.9.12.6)
|
31
|
+
coderay (~> 1.0)
|
32
|
+
method_source (~> 0.8)
|
33
|
+
slop (~> 3.4)
|
34
|
+
rspec-expectations (2.14.4)
|
35
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
36
|
+
rubyzip (1.1.7)
|
37
|
+
selenium-webdriver (2.44.0)
|
38
|
+
childprocess (~> 0.5)
|
39
|
+
multi_json (~> 1.0)
|
40
|
+
rubyzip (~> 1.0)
|
41
|
+
websocket (~> 1.0)
|
42
|
+
slop (3.5.0)
|
43
|
+
watir-webdriver (0.6.11)
|
44
|
+
selenium-webdriver (>= 2.18.0)
|
45
|
+
web4cuke (0.0.1)
|
46
|
+
websocket (1.2.1)
|
47
|
+
|
48
|
+
PLATFORMS
|
49
|
+
ruby
|
50
|
+
|
51
|
+
DEPENDENCIES
|
52
|
+
cucumber
|
53
|
+
debugger
|
54
|
+
headless
|
55
|
+
json
|
56
|
+
pry
|
57
|
+
rspec-expectations
|
58
|
+
watir-webdriver
|
59
|
+
web4cuke
|
@@ -0,0 +1,12 @@
|
|
1
|
+
Feature: example.feature
|
2
|
+
# We expect that you have an account at https://www.openshift.com/
|
3
|
+
# And that you have two environmental variables containing your
|
4
|
+
# Openshift login and password: $OPENSHIFT_LOGIN and $OPENSHIFT_PASSWORD
|
5
|
+
Scenario: Login to Openshift web console
|
6
|
+
Given I am logged in to OpenShift web console
|
7
|
+
Then the url should contain "app/account"
|
8
|
+
When I run my beautiful action with:
|
9
|
+
|option |value |
|
10
|
+
|field_one_on_page_one |some text |
|
11
|
+
|field_two_on_page_one | some other text|
|
12
|
+
|field_one_on_page_two| lib/files/myfile |
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Given /^I am logged in to OpenShift web console$/ do
|
2
|
+
options = {}
|
3
|
+
options[:login] = ENV["OPENSHIFT_USER"]
|
4
|
+
options[:password] = ENV["OPENSHIFT_PASSWORD"]
|
5
|
+
@result = @web.login(options)
|
6
|
+
expect(@result[:result]).to be_true, "Failed to log in"
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /^the url should contain "(.*?)"$/ do |urlpart|
|
10
|
+
url = @web.get_url
|
11
|
+
expect(url.include?(urlpart)).to be_true, "Failed to find #{urlpart} in page url"
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
When /^I run my beautiful action with:$/ do |table|
|
16
|
+
options = {}
|
17
|
+
table.rows.each do |row|
|
18
|
+
options[row[0].to_sym] = row[1]
|
19
|
+
end
|
20
|
+
@result = @web.run_action(:our_test_action, options)
|
21
|
+
end
|
22
|
+
|
23
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'rspec/expectations'
|
3
|
+
LIB_PATH = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib'))
|
4
|
+
$LOAD_PATH.unshift(LIB_PATH)
|
5
|
+
require "web"
|
6
|
+
require "log"
|
7
|
+
|
8
|
+
Before do |scenario|
|
9
|
+
options = {
|
10
|
+
:base_url => "https://openshift.com",
|
11
|
+
:browser => :firefox,
|
12
|
+
:rules_path => File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'lib', 'web')),
|
13
|
+
:logger => Log.new
|
14
|
+
}
|
15
|
+
@web = Web.new(options)
|
16
|
+
end
|
17
|
+
|
18
|
+
After do |scenario|
|
19
|
+
@web.finalize
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "web4cucumber"
|
2
|
+
|
3
|
+
class Web < Web4Cucumber
|
4
|
+
def initialize(options)
|
5
|
+
super(options)
|
6
|
+
@@logged_in = false
|
7
|
+
end
|
8
|
+
|
9
|
+
def login(options)
|
10
|
+
unless options.keys == [:login, :password]
|
11
|
+
raise "Please povide both :login and :password options"
|
12
|
+
end
|
13
|
+
result = run_action(:login, options)
|
14
|
+
if result[:result] == true
|
15
|
+
@@logged_in = true
|
16
|
+
end
|
17
|
+
return result
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
:check_community_header:
|
2
|
+
:pages:
|
3
|
+
- "check_community_header_page"
|
4
|
+
:check_community_header_page:
|
5
|
+
:url: '/community'
|
6
|
+
|
7
|
+
:checkpoints:
|
8
|
+
:products:
|
9
|
+
:selector:
|
10
|
+
:text: "Products"
|
11
|
+
:getinvolved:
|
12
|
+
:selector:
|
13
|
+
:text: "Get Involved"
|
14
|
+
:devcenter:
|
15
|
+
:selector:
|
16
|
+
:text: "Developer Center"
|
17
|
+
:support:
|
18
|
+
:selector:
|
19
|
+
:text: "Support"
|
20
|
+
:partners:
|
21
|
+
:selector:
|
22
|
+
:text: "Partners"
|
23
|
+
:community:
|
24
|
+
:selector:
|
25
|
+
:text: "Community"
|
26
|
+
:connect:
|
27
|
+
:selector:
|
28
|
+
:text: "Connect"
|
29
|
+
:about:
|
30
|
+
:selector:
|
31
|
+
:text: "About"
|
32
|
+
:getstarted:
|
33
|
+
:selector:
|
34
|
+
:text: "Get Started"
|
35
|
+
:status:
|
36
|
+
:selector:
|
37
|
+
:text: "Status"
|
38
|
+
:stackoverflow:
|
39
|
+
:selector:
|
40
|
+
:text: "Stack Overflow"
|
41
|
+
:premiumplans:
|
42
|
+
:selector:
|
43
|
+
:text: "Premium Plans"
|
44
|
+
:events:
|
45
|
+
:selector:
|
46
|
+
:text: "Events"
|
47
|
+
:blog:
|
48
|
+
:selector:
|
49
|
+
:text: "Blog"
|
50
|
+
:securitypolicy:
|
51
|
+
:selector:
|
52
|
+
:text: "Security Policy"
|
53
|
+
:openshiftonirc:
|
54
|
+
:selector:
|
55
|
+
:text: "#openshift on IRC"
|
56
|
+
:newslettersignup:
|
57
|
+
:selector:
|
58
|
+
:text: "Join our Newsletter"
|
59
|
+
:careers:
|
60
|
+
:selector:
|
61
|
+
:text: "Careers"
|
62
|
+
:aboutredhat:
|
63
|
+
:selector:
|
64
|
+
:text: "About Red Hat"
|
65
|
+
:partnerprograms:
|
66
|
+
:selector:
|
67
|
+
:text: "Partner Programs"
|
68
|
+
:contactus:
|
69
|
+
:selector:
|
70
|
+
:text: "Contact Us"
|
71
|
+
:enterprisesalesinfo:
|
72
|
+
:selector:
|
73
|
+
:text: "Enterprise Sales Info"
|
74
|
+
:links:
|
75
|
+
:getstarted:
|
76
|
+
:selector:
|
77
|
+
:text: "Get Started"
|
78
|
+
:checkpoints:
|
79
|
+
:getstartedwithopenshift:
|
80
|
+
:selector:
|
81
|
+
:text: "Getting Started with OpenShift"
|
82
|
+
:securitypolicy:
|
83
|
+
:selector:
|
84
|
+
:text: "Security Policy"
|
85
|
+
:checkpoints:
|
86
|
+
:securityinformation:
|
87
|
+
:selector:
|
88
|
+
:text: "Security Information"
|
89
|
+
:blog:
|
90
|
+
:selector:
|
91
|
+
:text: "Blog"
|
92
|
+
:checkpoints:
|
93
|
+
:blog:
|
94
|
+
:selector:
|
95
|
+
:text: "Blog"
|
96
|
+
:newslettersignup:
|
97
|
+
:selector:
|
98
|
+
:text: "Join our Newsletter"
|
99
|
+
:checkpoints:
|
100
|
+
:blogupdates:
|
101
|
+
:selector:
|
102
|
+
:text: "Blog Updates"
|
103
|
+
:enterprisesalesinfo:
|
104
|
+
:selector:
|
105
|
+
:text: "Enterprise Sales Info"
|
106
|
+
:checkpoints:
|
107
|
+
:tryopenshiftenterprise:
|
108
|
+
:selector:
|
109
|
+
:text: "Try OpenShift Enterprise"
|
110
|
+
:partnerprograms:
|
111
|
+
:selector:
|
112
|
+
:text: "Partner Programs"
|
113
|
+
:checkpoints:
|
114
|
+
:findapartner:
|
115
|
+
:selector:
|
116
|
+
:text: "Find a Partner"
|
117
|
+
|