selenium-core-runner 0.0.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 (69) hide show
  1. data/Gemfile +9 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +30 -0
  5. data/app/controllers/selenium_core_runner/suites_controller.rb +19 -0
  6. data/app/views/selenium_core_runner/suites/index.html.erb +177 -0
  7. data/app/views/selenium_core_runner/suites/show.html.erb +0 -0
  8. data/config/routes.rb +6 -0
  9. data/lib/selenium-core-runner/engine.rb +19 -0
  10. data/lib/selenium-core-runner.rb +3 -0
  11. data/public/selenium-core-runner/Blank.html +7 -0
  12. data/public/selenium-core-runner/InjectedRemoteRunner.html +8 -0
  13. data/public/selenium-core-runner/RemoteRunner.html +101 -0
  14. data/public/selenium-core-runner/SeleniumLog.html +109 -0
  15. data/public/selenium-core-runner/TestPrompt.html +145 -0
  16. data/public/selenium-core-runner/TestRunner-splash.html +55 -0
  17. data/public/selenium-core-runner/TestRunner.hta +177 -0
  18. data/public/selenium-core-runner/TestRunner.html +177 -0
  19. data/public/selenium-core-runner/icons/all.png +0 -0
  20. data/public/selenium-core-runner/icons/continue.png +0 -0
  21. data/public/selenium-core-runner/icons/continue_disabled.png +0 -0
  22. data/public/selenium-core-runner/icons/pause.png +0 -0
  23. data/public/selenium-core-runner/icons/pause_disabled.png +0 -0
  24. data/public/selenium-core-runner/icons/selected.png +0 -0
  25. data/public/selenium-core-runner/icons/step.png +0 -0
  26. data/public/selenium-core-runner/icons/step_disabled.png +0 -0
  27. data/public/selenium-core-runner/iedoc-core.xml +1789 -0
  28. data/public/selenium-core-runner/iedoc.xml +1830 -0
  29. data/public/selenium-core-runner/lib/cssQuery/cssQuery-p.js +6 -0
  30. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-level2.js +142 -0
  31. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-level3.js +150 -0
  32. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery-standard.js +53 -0
  33. data/public/selenium-core-runner/lib/cssQuery/src/cssQuery.js +356 -0
  34. data/public/selenium-core-runner/lib/prototype.js +2006 -0
  35. data/public/selenium-core-runner/lib/scriptaculous/builder.js +101 -0
  36. data/public/selenium-core-runner/lib/scriptaculous/controls.js +815 -0
  37. data/public/selenium-core-runner/lib/scriptaculous/dragdrop.js +915 -0
  38. data/public/selenium-core-runner/lib/scriptaculous/effects.js +958 -0
  39. data/public/selenium-core-runner/lib/scriptaculous/scriptaculous.js +47 -0
  40. data/public/selenium-core-runner/lib/scriptaculous/slider.js +283 -0
  41. data/public/selenium-core-runner/lib/scriptaculous/unittest.js +383 -0
  42. data/public/selenium-core-runner/lib/snapsie.js +91 -0
  43. data/public/selenium-core-runner/scripts/find_matching_child.js +69 -0
  44. data/public/selenium-core-runner/scripts/htmlutils.js +1623 -0
  45. data/public/selenium-core-runner/scripts/injection.html +72 -0
  46. data/public/selenium-core-runner/scripts/selenium-api.js +3240 -0
  47. data/public/selenium-core-runner/scripts/selenium-browserbot.js +2333 -0
  48. data/public/selenium-core-runner/scripts/selenium-browserdetect.js +153 -0
  49. data/public/selenium-core-runner/scripts/selenium-commandhandlers.js +379 -0
  50. data/public/selenium-core-runner/scripts/selenium-executionloop.js +175 -0
  51. data/public/selenium-core-runner/scripts/selenium-logging.js +148 -0
  52. data/public/selenium-core-runner/scripts/selenium-remoterunner.js +695 -0
  53. data/public/selenium-core-runner/scripts/selenium-testrunner.js +1362 -0
  54. data/public/selenium-core-runner/scripts/selenium-version.js +5 -0
  55. data/public/selenium-core-runner/scripts/ui-doc.html +803 -0
  56. data/public/selenium-core-runner/scripts/ui-element.js +1627 -0
  57. data/public/selenium-core-runner/scripts/ui-map-sample.js +979 -0
  58. data/public/selenium-core-runner/scripts/user-extensions.js +3 -0
  59. data/public/selenium-core-runner/scripts/user-extensions.js.sample +75 -0
  60. data/public/selenium-core-runner/scripts/xmlextras.js +153 -0
  61. data/public/selenium-core-runner/selenium-logo.png +0 -0
  62. data/public/selenium-core-runner/selenium-test.css +43 -0
  63. data/public/selenium-core-runner/selenium.css +316 -0
  64. data/public/selenium-core-runner/xpath/dom.js +566 -0
  65. data/public/selenium-core-runner/xpath/javascript-xpath-0.1.11.js +2816 -0
  66. data/public/selenium-core-runner/xpath/util.js +549 -0
  67. data/public/selenium-core-runner/xpath/xmltoken.js +149 -0
  68. data/public/selenium-core-runner/xpath/xpath.js +2481 -0
  69. metadata +121 -0
@@ -0,0 +1,979 @@
1
+ // sample UI element mapping definition. This is for http://alistapart.com/,
2
+ // a particularly well structured site on web design principles.
3
+
4
+
5
+
6
+ // in general, the map should capture structural aspects of the system, instead
7
+ // of "content". In other words, interactive elements / assertible elements
8
+ // that can be counted on to always exist should be defined here. Content -
9
+ // for example text or a link that appears in a blog entry - is always liable
10
+ // to change, and will not be fun to represent in this way. You probably don't
11
+ // want to be testing specific content anyway.
12
+
13
+ // create the UI mapping object. THIS IS THE MOST IMPORTANT PART - DON'T FORGET
14
+ // TO DO THIS! In order for it to come into play, a user extension must
15
+ // construct the map in this way.
16
+ var myMap = new UIMap();
17
+
18
+
19
+
20
+
21
+ // any values which may appear multiple times can be defined as variables here.
22
+ // For example, here we're enumerating a list of top level topics that will be
23
+ // used as default argument values for several UI elements. Check out how
24
+ // this variable is referenced further down.
25
+ var topics = [
26
+ 'Code',
27
+ 'Content',
28
+ 'Culture',
29
+ 'Design',
30
+ 'Process',
31
+ 'User Science'
32
+ ];
33
+
34
+ // map subtopics to their parent topics
35
+ var subtopics = {
36
+ 'Browsers': 'Code'
37
+ , 'CSS': 'Code'
38
+ , 'Flash': 'Code'
39
+ , 'HTML and XHTML': 'Code'
40
+ , 'Scripting': 'Code'
41
+ , 'Server Side': 'Code'
42
+ , 'XML': 'Code'
43
+ , 'Brand Arts': 'Content'
44
+ , 'Community': 'Content'
45
+ , 'Writing': 'Content'
46
+ , 'Industry': 'Culture'
47
+ , 'Politics and Money': 'Culture'
48
+ , 'State of the Web': 'Culture'
49
+ , 'Graphic Design': 'Design'
50
+ , 'User Interface Design': 'Design'
51
+ , 'Typography': 'Design'
52
+ , 'Layout': 'Design'
53
+ , 'Business': 'Process'
54
+ , 'Creativity': 'Process'
55
+ , 'Project Management and Workflow': 'Process'
56
+ , 'Accessibility': 'User Science'
57
+ , 'Information Architecture': 'User Science'
58
+ , 'Usability': 'User Science'
59
+ };
60
+
61
+
62
+
63
+ // define UI elements common for all pages. This regular expression does the
64
+ // trick. '^' is automatically prepended, and '$' is automatically postpended.
65
+ // Please note that because the regular expression is being represented as a
66
+ // string, all backslashes must be escaped with an additional backslash. Also
67
+ // note that the URL being matched will always have any trailing forward slash
68
+ // stripped.
69
+ myMap.addPageset({
70
+ name: 'allPages'
71
+ , description: 'all alistapart.com pages'
72
+ , pathRegexp: '.*'
73
+ });
74
+ myMap.addElement('allPages', {
75
+ name: 'masthead'
76
+ // the description should be short and to the point, usually no longer than
77
+ // a single line
78
+ , description: 'top level image link to site homepage'
79
+ // make sure the function returns the XPath ... it's easy to leave out the
80
+ // "return" statement by accident!
81
+ , locator: "xpath=//*[@id='masthead']/a/img"
82
+ , testcase1: {
83
+ xhtml: '<h1 id="masthead"><a><img expected-result="1" /></a></h1>'
84
+ }
85
+ });
86
+ myMap.addElement('allPages', {
87
+ // be VERY CAREFUL to include commas in the correct place. Missing commas
88
+ // and extra commas can cause lots of headaches when debugging map
89
+ // definition files!!!
90
+ name: 'current_issue'
91
+ , description: 'top level link to issue currently being browsed'
92
+ , locator: "//div[@id='ish']/a"
93
+ , testcase1: {
94
+ xhtml: '<div id="ish"><a expected-result="1"></a></div>'
95
+ }
96
+ });
97
+ myMap.addElement('allPages', {
98
+ name: 'section'
99
+ , description: 'top level link to articles section'
100
+ , args: [
101
+ {
102
+ name: 'section'
103
+ , description: 'the name of the section'
104
+ , defaultValues: [
105
+ 'articles'
106
+ , 'topics'
107
+ , 'about'
108
+ , 'contact'
109
+ , 'contribute'
110
+ , 'feed'
111
+ ]
112
+ }
113
+ ]
114
+ // getXPath has been deprecated by getLocator, but verify backward
115
+ // compatability here
116
+ , getXPath: function(args) {
117
+ return "//li[@id=" + args.section.quoteForXPath() + "]/a";
118
+ }
119
+ , testcase1: {
120
+ args: { section: 'feed' }
121
+ , xhtml: '<ul><li id="feed"><a expected-result="1" /></li></ul>'
122
+ }
123
+ });
124
+ myMap.addElement('allPages', {
125
+ name: 'search_box'
126
+ , description: 'site search input field'
127
+ // xpath has been deprecated by locator, but verify backward compatability
128
+ , xpath: "//input[@id='search']"
129
+ , testcase1: {
130
+ xhtml: '<input id="search" expected-result="1" />'
131
+ }
132
+ });
133
+ myMap.addElement('allPages', {
134
+ name: 'search_discussions'
135
+ , description: 'site search include discussions checkbox'
136
+ , locator: 'incdisc'
137
+ , testcase1: {
138
+ xhtml: '<input id="incdisc" expected-result="1" />'
139
+ }
140
+ });
141
+ myMap.addElement('allPages', {
142
+ name: 'search_submit'
143
+ , description: 'site search submission button'
144
+ , locator: 'submit'
145
+ , testcase1: {
146
+ xhtml: '<input id="submit" expected-result="1" />'
147
+ }
148
+ });
149
+ myMap.addElement('allPages', {
150
+ name: 'topics'
151
+ , description: 'sidebar links to topic categories'
152
+ , args: [
153
+ {
154
+ name: 'topic'
155
+ , description: 'the name of the topic'
156
+ , defaultValues: topics
157
+ }
158
+ ]
159
+ , getLocator: function(args) {
160
+ return "//div[@id='topiclist']/ul/li" +
161
+ "/a[text()=" + args.topic.quoteForXPath() + "]";
162
+ }
163
+ , testcase1: {
164
+ args: { topic: 'foo' }
165
+ , xhtml: '<div id="topiclist"><ul><li>'
166
+ + '<a expected-result="1">foo</a>'
167
+ + '</li></ul></div>'
168
+ }
169
+ });
170
+ myMap.addElement('allPages', {
171
+ name: 'copyright'
172
+ , description: 'footer link to copyright page'
173
+ , getLocator: function(args) { return "//span[@class='copyright']/a"; }
174
+ , testcase1: {
175
+ xhtml: '<span class="copyright"><a expected-result="1" /></span>'
176
+ }
177
+ });
178
+
179
+
180
+
181
+ // define UI elements for the homepage, i.e. "http://alistapart.com/", and
182
+ // magazine issue pages, i.e. "http://alistapart.com/issues/234".
183
+ myMap.addPageset({
184
+ name: 'issuePages'
185
+ , description: 'pages including magazine issues'
186
+ , pathRegexp: '(issues/.+)?'
187
+ });
188
+ myMap.addElement('issuePages', {
189
+ name: 'article'
190
+ , description: 'front or issue page link to article'
191
+ , args: [
192
+ {
193
+ name: 'index'
194
+ , description: 'the index of the article'
195
+ // an array of default values for the argument. A default
196
+ // value is one that is passed to the getXPath() method of
197
+ // the container UIElement object when trying to build an
198
+ // element locator.
199
+ //
200
+ // range() may be used to count easily. Remember though that
201
+ // the ending value does not include the right extreme; for
202
+ // example range(1, 5) counts from 1 to 4 only.
203
+ , defaultValues: range(1, 5)
204
+ }
205
+ ]
206
+ , getLocator: function(args) {
207
+ return "//div[@class='item'][" + args.index + "]/h4/a";
208
+ }
209
+ });
210
+ myMap.addElement('issuePages', {
211
+ name: 'author'
212
+ , description: 'article author link'
213
+ , args: [
214
+ {
215
+ name: 'index'
216
+ , description: 'the index of the author, by article'
217
+ , defaultValues: range(1, 5)
218
+ }
219
+ ]
220
+ , getLocator: function(args) {
221
+ return "//div[@class='item'][" + args.index + "]/h5/a";
222
+ }
223
+ });
224
+ myMap.addElement('issuePages', {
225
+ name: 'store'
226
+ , description: 'alistapart.com store link'
227
+ , locator: "//ul[@id='banners']/li/a[@title='ALA Store']/img"
228
+ });
229
+ myMap.addElement('issuePages', {
230
+ name: 'special_article'
231
+ , description: "editor's choice article link"
232
+ , locator: "//div[@id='choice']/h4/a"
233
+ });
234
+ myMap.addElement('issuePages', {
235
+ name: 'special_author'
236
+ , description: "author link of editor's choice article"
237
+ , locator: "//div[@id='choice']/h5/a"
238
+ });
239
+
240
+
241
+
242
+ // define UI elements for the articles page, i.e.
243
+ // "http://alistapart.com/articles"
244
+ myMap.addPageset({
245
+ name: 'articleListPages'
246
+ , description: 'page with article listings'
247
+ , paths: [ 'articles' ]
248
+ });
249
+ myMap.addElement('articleListPages', {
250
+ name: 'issue'
251
+ , description: 'link to issue'
252
+ , args: [
253
+ {
254
+ name: 'index'
255
+ , description: 'the index of the issue on the page'
256
+ , defaultValues: range(1, 10)
257
+ }
258
+ ]
259
+ , getLocator: function(args) {
260
+ return "//h2[@class='ishinfo'][" + args.index + ']/a';
261
+ }
262
+ , genericLocator: "//h2[@class='ishinfo']/a"
263
+ });
264
+ myMap.addElement('articleListPages', {
265
+ name: 'article'
266
+ , description: 'link to article, by issue and article number'
267
+ , args: [
268
+ {
269
+ name: 'issue_index'
270
+ , description: "the index of the article's issue on the page; "
271
+ + 'typically five per page'
272
+ , defaultValues: range(1, 6)
273
+ }
274
+ , {
275
+ name: 'article_index'
276
+ , description: 'the index of the article within the issue; '
277
+ + 'typically two per issue'
278
+ , defaultValues: range(1, 5)
279
+ }
280
+ ]
281
+ , getLocator: function(args) {
282
+ var xpath = "//h2[@class='ishinfo'][" + (args.issue_index || 1) + ']'
283
+ + "/following-sibling::div[@class='item']"
284
+ + '[' + (args.article_index || 1) + "]/h3[@class='title']/a";
285
+ return xpath;
286
+ }
287
+ , genericLocator: "//h2[@class='ishinfo']"
288
+ + "/following-sibling::div[@class='item']/h3[@class='title']/a"
289
+ });
290
+ myMap.addElement('articleListPages', {
291
+ name: 'author'
292
+ , description: 'article author link, by issue and article'
293
+ , args: [
294
+ {
295
+ name: 'issue_index'
296
+ , description: "the index of the article's issue on the page; \
297
+ typically five per page"
298
+ , defaultValues: range(1, 6)
299
+ }
300
+ , {
301
+ name: 'article_index'
302
+ , description: "the index of the article within the issue; \
303
+ typically two articles per issue"
304
+ , defaultValues: range(1, 3)
305
+ }
306
+ ]
307
+ // this XPath uses the "following-sibling" axis. The div elements for
308
+ // the articles in an issue are not children, but siblings of the h2
309
+ // element identifying the article.
310
+ , getLocator: function(args) {
311
+ var xpath = "//h2[@class='ishinfo'][" + (args.issue_index || 1) + ']'
312
+ + "/following-sibling::div[@class='item']"
313
+ + '[' + (args.article_index || 1) + "]/h4[@class='byline']/a";
314
+ return xpath;
315
+ }
316
+ , genericLocator: "//h2[@class='ishinfo']"
317
+ + "/following-sibling::div[@class='item']/h4[@class='byline']/a"
318
+ });
319
+ myMap.addElement('articleListPages', {
320
+ name: 'next_page'
321
+ , description: 'link to next page of articles (older)'
322
+ , locator: "//a[contains(text(),'Next page')]"
323
+ });
324
+ myMap.addElement('articleListPages', {
325
+ name: 'previous_page'
326
+ , description: 'link to previous page of articles (newer)'
327
+ , locator: "//a[contains(text(),'Previous page')]"
328
+ });
329
+
330
+
331
+
332
+ // define UI elements for specific article pages, i.e.
333
+ // "http://alistapart.com/articles/culturalprobe"
334
+ myMap.addPageset({
335
+ name: 'articlePages'
336
+ , description: 'pages for actual articles'
337
+ , pathRegexp: 'articles/.+'
338
+ });
339
+ myMap.addElement('articlePages', {
340
+ name: 'title'
341
+ , description: 'article title loop-link'
342
+ , locator: "//div[@id='content']/h1[@class='title']/a"
343
+ });
344
+ myMap.addElement('articlePages', {
345
+ name: 'author'
346
+ , description: 'article author link'
347
+ , locator: "//div[@id='content']/h3[@class='byline']/a"
348
+ });
349
+ myMap.addElement('articlePages', {
350
+ name: 'article_topics'
351
+ , description: 'links to topics under which article is published, before \
352
+ article content'
353
+ , args: [
354
+ {
355
+ name: 'topic'
356
+ , description: 'the name of the topic'
357
+ , defaultValues: keys(subtopics)
358
+ }
359
+ ]
360
+ , getLocator: function(args) {
361
+ return "//ul[@id='metastuff']/li/a"
362
+ + "[@title=" + args.topic.quoteForXPath() + "]";
363
+ }
364
+ });
365
+ myMap.addElement('articlePages', {
366
+ name: 'discuss'
367
+ , description: 'link to article discussion area, before article content'
368
+ , locator: "//ul[@id='metastuff']/li[@class='discuss']/p/a"
369
+ });
370
+ myMap.addElement('articlePages', {
371
+ name: 'related_topics'
372
+ , description: 'links to topics under which article is published, after \
373
+ article content'
374
+ , args: [
375
+ {
376
+ name: 'topic'
377
+ , description: 'the name of the topic'
378
+ , defaultValues: keys(subtopics)
379
+ }
380
+ ]
381
+ , getLocator: function(args) {
382
+ return "//div[@id='learnmore']/p/a"
383
+ + "[@title=" + args.topic.quoteForXPath() + "]";
384
+ }
385
+ });
386
+ myMap.addElement('articlePages', {
387
+ name: 'join_discussion'
388
+ , description: 'link to article discussion area, after article content'
389
+ , locator: "//div[@class='discuss']/p/a"
390
+ });
391
+
392
+
393
+
394
+ myMap.addPageset({
395
+ name: 'topicListingPages'
396
+ , description: 'top level listing of topics'
397
+ , paths: [ 'topics' ]
398
+ });
399
+ myMap.addElement('topicListingPages', {
400
+ name: 'topic'
401
+ , description: 'link to topic category'
402
+ , args: [
403
+ {
404
+ name: 'topic'
405
+ , description: 'the name of the topic'
406
+ , defaultValues: topics
407
+ }
408
+ ]
409
+ , getLocator: function(args) {
410
+ return "//div[@id='content']/h2/a"
411
+ + "[text()=" + args.topic.quoteForXPath() + "]";
412
+ }
413
+ });
414
+ myMap.addElement('topicListingPages', {
415
+ name: 'subtopic'
416
+ , description: 'link to subtopic category'
417
+ , args: [
418
+ {
419
+ name: 'subtopic'
420
+ , description: 'the name of the subtopic'
421
+ , defaultValues: keys(subtopics)
422
+ }
423
+ ]
424
+ , getLocator: function(args) {
425
+ return "//div[@id='content']" +
426
+ "/descendant::a[text()=" + args.subtopic.quoteForXPath() + "]";
427
+ }
428
+ });
429
+
430
+ // the following few subtopic page UI elements are very similar. Define UI
431
+ // elements for the code page, which is a subpage under topics, i.e.
432
+ // "http://alistapart.com/topics/code/"
433
+ myMap.addPageset({
434
+ name: 'subtopicListingPages'
435
+ , description: 'pages listing subtopics'
436
+ , pathPrefix: 'topics/'
437
+ , paths: [
438
+ 'code'
439
+ , 'content'
440
+ , 'culture'
441
+ , 'design'
442
+ , 'process'
443
+ , 'userscience'
444
+ ]
445
+ });
446
+ myMap.addElement('subtopicListingPages', {
447
+ name: 'subtopic'
448
+ , description: 'link to a subtopic category'
449
+ , args: [
450
+ {
451
+ name: 'subtopic'
452
+ , description: 'the name of the subtopic'
453
+ , defaultValues: keys(subtopics)
454
+ }
455
+ ]
456
+ , getLocator: function(args) {
457
+ return "//div[@id='content']/h2" +
458
+ "/a[text()=" + args.subtopic.quoteForXPath() + "]";
459
+ }
460
+ });
461
+
462
+
463
+
464
+ // subtopic articles page
465
+ myMap.addPageset({
466
+ name: 'subtopicArticleListingPages'
467
+ , description: 'pages listing the articles for a given subtopic'
468
+ , pathRegexp: 'topics/[^/]+/.+'
469
+ });
470
+ myMap.addElement('subtopicArticleListingPages', {
471
+ name: 'article'
472
+ , description: 'link to a subtopic article'
473
+ , args: [
474
+ {
475
+ name: 'index'
476
+ , description: 'the index of the article'
477
+ , defaultValues: range(1, 51) // the range seems unlimited ...
478
+ }
479
+ ]
480
+ , getLocator: function(args) {
481
+ return "//div[@id='content']/div[@class='item']"
482
+ + "[" + args.index + "]/h3/a";
483
+ }
484
+ , testcase1: {
485
+ args: { index: 2 }
486
+ , xhtml: '<div id="content"><div class="item" /><div class="item">'
487
+ + '<h3><a expected-result="1" /></h3></div></div>'
488
+ }
489
+ });
490
+ myMap.addElement('subtopicArticleListingPages', {
491
+ name: 'author'
492
+ , description: "link to a subtopic article author's page"
493
+ , args: [
494
+ {
495
+ name: 'article_index'
496
+ , description: 'the index of the authored article'
497
+ , defaultValues: range(1, 51)
498
+ }
499
+ , {
500
+ name: 'author_index'
501
+ , description: 'the index of the author when there are multiple'
502
+ , defaultValues: range(1, 4)
503
+ }
504
+ ]
505
+ , getLocator: function(args) {
506
+ return "//div[@id='content']/div[@class='item'][" +
507
+ args.article_index + "]/h4/a[" +
508
+ (args.author_index ? args.author_index : '1') + ']';
509
+ }
510
+ });
511
+ myMap.addElement('subtopicArticleListingPages', {
512
+ name: 'issue'
513
+ , description: 'link to issue a subtopic article appears in'
514
+ , args: [
515
+ {
516
+ name: 'index'
517
+ , description: 'the index of the subtopic article'
518
+ , defaultValues: range(1, 51)
519
+ }
520
+ ]
521
+ , getLocator: function(args) {
522
+ return "//div[@id='content']/div[@class='item']"
523
+ + "[" + args.index + "]/h5/a";
524
+ }
525
+ });
526
+
527
+
528
+
529
+ myMap.addPageset({
530
+ name: 'aboutPages'
531
+ , description: 'the website about page'
532
+ , paths: [ 'about' ]
533
+ });
534
+ myMap.addElement('aboutPages', {
535
+ name: 'crew'
536
+ , description: 'link to site crew member bio or personal website'
537
+ , args: [
538
+ {
539
+ name: 'role'
540
+ , description: 'the role of the crew member'
541
+ , defaultValues: [
542
+ 'ALA Crew'
543
+ , 'Support'
544
+ , 'Emeritus'
545
+ ]
546
+ }
547
+ , {
548
+ name: 'role_index'
549
+ , description: 'the index of the member within the role'
550
+ , defaultValues: range(1, 20)
551
+ }
552
+ , {
553
+ name: 'member_index'
554
+ , description: 'the index of the member within the role title'
555
+ , defaultValues: range(1, 5)
556
+ }
557
+ ]
558
+ , getLocator: function(args) {
559
+ // the first role is kind of funky, and requires a conditional to
560
+ // build the XPath correctly. Its header looks like this:
561
+ //
562
+ // <h3>
563
+ // <span class="caps">ALA 4</span>.0 <span class="caps">CREW</span>
564
+ // </h3>
565
+ //
566
+ // This kind of complexity is a little daunting, but you can see
567
+ // how the format can handle it relatively easily and concisely.
568
+ if (args.role == 'ALA Crew') {
569
+ var selector = "descendant::text()='CREW'";
570
+ }
571
+ else {
572
+ var selector = "text()=" + args.role.quoteForXPath();
573
+ }
574
+ var xpath =
575
+ "//div[@id='secondary']/h3[" + selector + ']' +
576
+ "/following-sibling::dl/dt[" + (args.role_index || 1) + ']' +
577
+ '/a[' + (args.member_index || '1') + ']';
578
+ return xpath;
579
+ }
580
+ });
581
+
582
+
583
+
584
+ myMap.addPageset({
585
+ name: 'searchResultsPages'
586
+ , description: 'pages listing search results'
587
+ , paths: [ 'search' ]
588
+ });
589
+ myMap.addElement('searchResultsPages', {
590
+ name: 'result_link'
591
+ , description: 'search result link'
592
+ , args: [
593
+ {
594
+ name: 'index'
595
+ , description: 'the index of the search result'
596
+ , defaultValues: range(1, 11)
597
+ }
598
+ ]
599
+ , getLocator: function(args) {
600
+ return "//div[@id='content']/ul[" + args.index + ']/li/h3/a';
601
+ }
602
+ });
603
+ myMap.addElement('searchResultsPages', {
604
+ name: 'more_results_link'
605
+ , description: 'next or previous results link at top or bottom of page'
606
+ , args: [
607
+ {
608
+ name: 'direction'
609
+ , description: 'next or previous results page'
610
+ // demonstrate a method which acquires default values from the
611
+ // document object. Such default values may contain EITHER commas
612
+ // OR equals signs, but NOT BOTH.
613
+ , getDefaultValues: function(inDocument) {
614
+ var defaultValues = [];
615
+ var divs = inDocument.getElementsByTagName('div');
616
+ for (var i = 0; i < divs.length; ++i) {
617
+ if (divs[i].className == 'pages') {
618
+ break;
619
+ }
620
+ }
621
+ var links = divs[i].getElementsByTagName('a');
622
+ for (i = 0; i < links.length; ++i) {
623
+ defaultValues.push(links[i].innerHTML
624
+ .replace(/^\xab\s*/, "")
625
+ .replace(/\s*\bb$/, "")
626
+ .replace(/\s*\d+$/, ""));
627
+ }
628
+ return defaultValues;
629
+ }
630
+ }
631
+ , {
632
+ name: 'position'
633
+ , description: 'position of the link'
634
+ , defaultValues: ['top', 'bottom']
635
+ }
636
+ ]
637
+ , getLocator: function(args) {
638
+ return "//div[@id='content']/div[@class='pages']["
639
+ + (args.position == 'top' ? '1' : '2') + ']'
640
+ + "/a[contains(text(), "
641
+ + (args.direction ? args.direction.quoteForXPath() : undefined)
642
+ + ")]";
643
+ }
644
+ });
645
+
646
+
647
+
648
+ myMap.addPageset({
649
+ name: 'commentsPages'
650
+ , description: 'pages listing comments made to an article'
651
+ , pathRegexp: 'comments/.+'
652
+ });
653
+ myMap.addElement('commentsPages', {
654
+ name: 'article_link'
655
+ , description: 'link back to the original article'
656
+ , locator: "//div[@id='content']/h1[@class='title']/a"
657
+ });
658
+ myMap.addElement('commentsPages', {
659
+ name: 'comment_link'
660
+ , description: 'same-page link to comment'
661
+ , args: [
662
+ {
663
+ name: 'index'
664
+ , description: 'the index of the comment'
665
+ , defaultValues: range(1, 11)
666
+ }
667
+ ]
668
+ , getLocator: function(args) {
669
+ return "//div[@class='content']/div[contains(@class, 'comment')]" +
670
+ '[' + args.index + ']/h4/a[2]';
671
+ }
672
+ });
673
+ myMap.addElement('commentsPages', {
674
+ name: 'paging_link'
675
+ , description: 'links to more pages of comments'
676
+ , args: [
677
+ {
678
+ name: 'dest'
679
+ , description: 'the destination page'
680
+ , defaultValues: ['next', 'prev'].concat(range(1, 16))
681
+ }
682
+ , {
683
+ name: 'position'
684
+ , description: 'position of the link'
685
+ , defaultValues: ['top', 'bottom']
686
+ }
687
+ ]
688
+ , getLocator: function(args) {
689
+ var dest = args.dest;
690
+ var xpath = "//div[@id='content']/div[@class='pages']" +
691
+ '[' + (args.position == 'top' ? '1' : '2') + ']/p';
692
+ if (dest == 'next' || dest == 'prev') {
693
+ xpath += "/a[contains(text(), " + dest.quoteForXPath() + ")]";
694
+ }
695
+ else {
696
+ xpath += "/a[text()=" + dest.quoteForXPath() + "]";
697
+ }
698
+ return xpath;
699
+ }
700
+ });
701
+
702
+
703
+
704
+ myMap.addPageset({
705
+ name: 'authorPages'
706
+ , description: 'personal pages for each author'
707
+ , pathRegexp: 'authors/[a-z]/.+'
708
+ });
709
+ myMap.addElement('authorPages', {
710
+ name: 'article'
711
+ , description: "link to article written by this author.\n"
712
+ + 'This description has a line break.'
713
+ , args: [
714
+ {
715
+ name: 'index'
716
+ , description: 'index of the article on the page'
717
+ , defaultValues: range(1, 11)
718
+ }
719
+ ]
720
+ , getLocator: function(args) {
721
+ var index = args.index;
722
+ // try out the CSS locator!
723
+ //return "//h4[@class='title'][" + index + "]/a";
724
+ return 'css=h4.title:nth-child(' + index + ') > a';
725
+ }
726
+ , testcase1: {
727
+ args: { index: '2' }
728
+ , xhtml: '<h4 class="title" /><h4 class="title">'
729
+ + '<a expected-result="1" /></h4>'
730
+ }
731
+ });
732
+
733
+
734
+
735
+ // test the offset locator. Something like the following can be recorded:
736
+ // ui=qaPages::content()//a[contains(text(),'May I quote from your articles?')]
737
+ myMap.addPageset({
738
+ name: 'qaPages'
739
+ , description: 'question and answer pages'
740
+ , pathRegexp: 'qa'
741
+ });
742
+ myMap.addElement('qaPages', {
743
+ name: 'content'
744
+ , description: 'the content pane containing the q&a entries'
745
+ , locator: "//div[@id='content' and "
746
+ + "child::h1[text()='Questions and Answers']]"
747
+ , getOffsetLocator: UIElement.defaultOffsetLocatorStrategy
748
+ });
749
+ myMap.addElement('qaPages', {
750
+ name: 'last_updated'
751
+ , description: 'displays the last update date'
752
+ // demonstrate calling getLocator() for another UI element within a
753
+ // getLocator(). The former must have already been added to the map. And
754
+ // obviously, you can't randomly combine different locator types!
755
+ , locator: myMap.getUIElement('qaPages', 'content').getLocator() + '/p/em'
756
+ });
757
+
758
+
759
+
760
+ //******************************************************************************
761
+
762
+ var myRollupManager = new RollupManager();
763
+
764
+ // though the description element is required, its content is free form. You
765
+ // might want to create a documentation policy as given below, where the pre-
766
+ // and post-conditions of the rollup are spelled out.
767
+ //
768
+ // To take advantage of a "heredoc" like syntax for longer descriptions,
769
+ // add a backslash to the end of the current line and continue the string on
770
+ // the next line.
771
+ myRollupManager.addRollupRule({
772
+ name: 'navigate_to_subtopic_article_listing'
773
+ , description: 'drill down to the listing of articles for a given subtopic \
774
+ from the section menu, then the topic itself.'
775
+ , pre: 'current page contains the section menu (most pages should)'
776
+ , post: 'navigated to the page listing all articles for a given subtopic'
777
+ , args: [
778
+ {
779
+ name: 'subtopic'
780
+ , description: 'the subtopic whose article listing to navigate to'
781
+ , exampleValues: keys(subtopics)
782
+ }
783
+ ]
784
+ , commandMatchers: [
785
+ {
786
+ command: 'clickAndWait'
787
+ , target: 'ui=allPages::section\\(section=topics\\)'
788
+ // must escape parentheses in the the above target, since the
789
+ // string is being used as a regular expression. Again, backslashes
790
+ // in strings must be escaped too.
791
+ }
792
+ , {
793
+ command: 'clickAndWait'
794
+ , target: 'ui=topicListingPages::topic\\(.+'
795
+ }
796
+ , {
797
+ command: 'clickAndWait'
798
+ , target: 'ui=subtopicListingPages::subtopic\\(.+'
799
+ , updateArgs: function(command, args) {
800
+ // don't bother stripping the "ui=" prefix from the locator
801
+ // here; we're just using UISpecifier to parse the args out
802
+ var uiSpecifier = new UISpecifier(command.target);
803
+ args.subtopic = uiSpecifier.args.subtopic;
804
+ return args;
805
+ }
806
+ }
807
+ ]
808
+ , getExpandedCommands: function(args) {
809
+ var commands = [];
810
+ var topic = subtopics[args.subtopic];
811
+ var subtopic = args.subtopic;
812
+ commands.push({
813
+ command: 'clickAndWait'
814
+ , target: 'ui=allPages::section(section=topics)'
815
+ });
816
+ commands.push({
817
+ command: 'clickAndWait'
818
+ , target: 'ui=topicListingPages::topic(topic=' + topic + ')'
819
+ });
820
+ commands.push({
821
+ command: 'clickAndWait'
822
+ , target: 'ui=subtopicListingPages::subtopic(subtopic=' + subtopic
823
+ + ')'
824
+ });
825
+ commands.push({
826
+ command: 'verifyLocation'
827
+ , target: 'regexp:.+/topics/.+/.+'
828
+ });
829
+ return commands;
830
+ }
831
+ });
832
+
833
+
834
+
835
+ myRollupManager.addRollupRule({
836
+ name: 'replace_click_with_clickAndWait'
837
+ , description: 'replaces commands where a click was detected with \
838
+ clickAndWait instead'
839
+ , alternateCommand: 'clickAndWait'
840
+ , commandMatchers: [
841
+ {
842
+ command: 'click'
843
+ , target: 'ui=subtopicArticleListingPages::article\\(.+'
844
+ }
845
+ ]
846
+ , expandedCommands: []
847
+ });
848
+
849
+
850
+
851
+ myRollupManager.addRollupRule({
852
+ name: 'navigate_to_subtopic_article'
853
+ , description: 'navigate to an article listed under a subtopic.'
854
+ , pre: 'current page contains the section menu (most pages should)'
855
+ , post: 'navigated to an article page'
856
+ , args: [
857
+ {
858
+ name: 'subtopic'
859
+ , description: 'the subtopic whose article listing to navigate to'
860
+ , exampleValues: keys(subtopics)
861
+ }
862
+ , {
863
+ name: 'index'
864
+ , description: 'the index of the article in the listing'
865
+ , exampleValues: range(1, 11)
866
+ }
867
+ ]
868
+ , commandMatchers: [
869
+ {
870
+ command: 'rollup'
871
+ , target: 'navigate_to_subtopic_article_listing'
872
+ , value: 'subtopic\\s*=.+'
873
+ , updateArgs: function(command, args) {
874
+ var args1 = parse_kwargs(command.value);
875
+ args.subtopic = args1.subtopic;
876
+ return args;
877
+ }
878
+ }
879
+ , {
880
+ command: 'clickAndWait'
881
+ , target: 'ui=subtopicArticleListingPages::article\\(.+'
882
+ , updateArgs: function(command, args) {
883
+ var uiSpecifier = new UISpecifier(command.target);
884
+ args.index = uiSpecifier.args.index;
885
+ return args;
886
+ }
887
+ }
888
+ ]
889
+ /*
890
+ // this is pretty much equivalent to the commandMatchers immediately above.
891
+ // Seems more verbose and less expressive, doesn't it? But sometimes you
892
+ // might prefer the flexibility of a function.
893
+ , getRollup: function(commands) {
894
+ if (commands.length >= 2) {
895
+ command1 = commands[0];
896
+ command2 = commands[1];
897
+ var args1 = parse_kwargs(command1.value);
898
+ try {
899
+ var uiSpecifier = new UISpecifier(command2.target
900
+ .replace(/^ui=/, ''));
901
+ }
902
+ catch (e) {
903
+ return false;
904
+ }
905
+ if (command1.command == 'rollup' &&
906
+ command1.target == 'navigate_to_subtopic_article_listing' &&
907
+ args1.subtopic &&
908
+ command2.command == 'clickAndWait' &&
909
+ uiSpecifier.pagesetName == 'subtopicArticleListingPages' &&
910
+ uiSpecifier.elementName == 'article') {
911
+ var args = {
912
+ subtopic: args1.subtopic
913
+ , index: uiSpecifier.args.index
914
+ };
915
+ return {
916
+ command: 'rollup'
917
+ , target: this.name
918
+ , value: to_kwargs(args)
919
+ , replacementIndexes: [ 0, 1 ]
920
+ };
921
+ }
922
+ }
923
+ return false;
924
+ }
925
+ */
926
+ , getExpandedCommands: function(args) {
927
+ var commands = [];
928
+ commands.push({
929
+ command: 'rollup'
930
+ , target: 'navigate_to_subtopic_article_listing'
931
+ , value: to_kwargs({ subtopic: args.subtopic })
932
+ });
933
+ var uiSpecifier = new UISpecifier(
934
+ 'subtopicArticleListingPages'
935
+ , 'article'
936
+ , { index: args.index });
937
+ commands.push({
938
+ command: 'clickAndWait'
939
+ , target: 'ui=' + uiSpecifier.toString()
940
+ });
941
+ commands.push({
942
+ command: 'verifyLocation'
943
+ , target: 'regexp:.+/articles/.+'
944
+ });
945
+ return commands;
946
+ }
947
+ });
948
+
949
+
950
+
951
+
952
+
953
+
954
+
955
+
956
+
957
+
958
+
959
+
960
+
961
+
962
+
963
+
964
+
965
+
966
+
967
+
968
+
969
+
970
+
971
+
972
+
973
+
974
+
975
+
976
+
977
+
978
+
979
+