/testing/selenium-core/scripts/ui-map-sample.js

http://datanucleus-appengine.googlecode.com/ · JavaScript · 979 lines · 771 code · 86 blank · 122 comment · 25 complexity · 35566840e217d4aa68436828a373e3a2 MD5 · raw file

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