sakai-oae-test-api 0.0.1
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.
- data/Gemfile +7 -0
- data/lib/sakai-oae-test-api.rb +37 -0
- data/lib/sakai-oae-test-api/cle_frame_classes.rb +2160 -0
- data/lib/sakai-oae-test-api/gem_extensions.rb +168 -0
- data/lib/sakai-oae-test-api/global_methods.rb +109 -0
- data/lib/sakai-oae-test-api/page_classes.rb +1949 -0
- data/lib/sakai-oae-test-api/pop_up_dialogs.rb +1273 -0
- data/lib/sakai-oae-test-api/toolbars_and_menus.rb +745 -0
- data/lib/sakai-oae-test-api/widgets.rb +495 -0
- data/sakai-oae-test-api.gemspec +16 -0
- metadata +121 -0
@@ -0,0 +1,168 @@
|
|
1
|
+
# This file contains custom Utility methods used throughout the Sakai-OAE test scripts,
|
2
|
+
# as well as Monkey patches to the Page Object and Watir-webdriver gems.
|
3
|
+
#
|
4
|
+
|
5
|
+
# Contains methods that extend the PageObject module to include
|
6
|
+
# methods that are custom to OAE.
|
7
|
+
module PageObject
|
8
|
+
|
9
|
+
# Monkey patch helper method for links to named objects...
|
10
|
+
def name_link(name)
|
11
|
+
self.link(:text=>/#{Regexp.escape(name)}/i)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Monkey patch helper method for li elements referring to
|
15
|
+
# named items...
|
16
|
+
def name_li(name)
|
17
|
+
self.li(:text=>/#{Regexp.escape(name)}/i)
|
18
|
+
end
|
19
|
+
|
20
|
+
def method_missing(sym, *args, &block)
|
21
|
+
@browser.send sym, *args, &block
|
22
|
+
end
|
23
|
+
|
24
|
+
module Accessors
|
25
|
+
|
26
|
+
# Use this for menus that require floating the mouse
|
27
|
+
# over the first link, before you click on the second
|
28
|
+
# link...
|
29
|
+
def float_menu(name, menu_text, link_text, target_class)
|
30
|
+
define_method(name) {
|
31
|
+
self.back_to_top
|
32
|
+
self.link(:text=>menu_text).fire_event("onmouseover")
|
33
|
+
self.link(:text=>/#{link_text}/).click
|
34
|
+
self.linger_for_ajax(10)
|
35
|
+
eval(target_class).new @browser
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def open_link(name, klass)
|
40
|
+
define_method("open_#{name}") do |value|
|
41
|
+
self.back_to_top
|
42
|
+
self.link(:text=>/#{value}/).click
|
43
|
+
sleep 2
|
44
|
+
self.linger_for_ajax(6)
|
45
|
+
# This code is necessary because of a null style tag
|
46
|
+
# that confuses Watir into thinking the buttons aren't really there.
|
47
|
+
if self.div(:id=>"joinrequestbuttons_widget").exist?
|
48
|
+
@browser.execute_script("$('#joinrequestbuttons_widget').css({display: 'block'})")
|
49
|
+
end
|
50
|
+
eval(klass).new @browser
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Use this for menu items that are accessed via clicking
|
55
|
+
# the div to get to the target menu item.
|
56
|
+
def permissions_menu(name, text) #, target_class)
|
57
|
+
define_method(name) {
|
58
|
+
self.link(:text=>text).fire_event("onmouseover")
|
59
|
+
self.link(:text=>text).parent.div(:class=>"lhnavigation_selected_submenu_image").fire_event("onclick")
|
60
|
+
self.link(:id=>"lhnavigation_submenu_user_permissions").click
|
61
|
+
self.class.class_eval { include ProfilePermissionsPopUp }
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
# Use this for button objects that cause page navigations and thus
|
66
|
+
# require a new Page Object.
|
67
|
+
def navigating_button(name, id, class_name=nil)
|
68
|
+
define_method(name) {
|
69
|
+
self.button(:id=>id).click
|
70
|
+
sleep 0.2
|
71
|
+
browser.wait_for_ajax(2)
|
72
|
+
sleep 0.2
|
73
|
+
unless class_name==nil
|
74
|
+
eval(class_name).new @browser
|
75
|
+
end
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
# Use this for links on the page that cause page navigations and
|
80
|
+
# thus require a new Page Object.
|
81
|
+
def navigating_link(name, link_text, class_name=nil)
|
82
|
+
define_method(name) {
|
83
|
+
self.link(:text=>/#{Regexp.escape(link_text)}/).click
|
84
|
+
sleep 2 # wait_for_ajax keeps throwing unknown JS errors in Selenium-webdriver
|
85
|
+
unless class_name==nil
|
86
|
+
eval(class_name).new @browser
|
87
|
+
end
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
# This method is specifically for defining the contents of
|
92
|
+
# the Insert button, found on the Document Edit page. See the module
|
93
|
+
# DocumentWidget
|
94
|
+
def insert_button(name, id, module_name=nil)
|
95
|
+
define_method("insert_#{name}") {
|
96
|
+
self.button(:id=>"sakaidocs_insert_dropdown_button").click
|
97
|
+
sleep 0.1
|
98
|
+
self.button(:id=>id).click
|
99
|
+
unless module_name==nil
|
100
|
+
self.class.class_eval { include eval(module_name) }
|
101
|
+
sleep 0.4
|
102
|
+
end
|
103
|
+
self.wait_for_ajax(2)
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Extension of the Utilities module.
|
111
|
+
# This is included here because it contains methods
|
112
|
+
# specific to OAE testing only.
|
113
|
+
module Utilities
|
114
|
+
|
115
|
+
# Returns an array containing a randomized list of the specified
|
116
|
+
# number of OAE Category strings (The array returned is one string long if no
|
117
|
+
# number is specified).
|
118
|
+
def random_categories(number=1)
|
119
|
+
categories = ["Agriculture", "Animal Sciences", "Food Sciences", "Plant Sciences", "Soil Sciences", "Architecture & Planning", "City, Urban, Community & Regional Planning", "Environmental Design", "Interior Architecture", "Landscape Architecture", "Real Estate Development", "Arts & Music", "Acting", "Art History, Criticism & Conservation", "Arts Management", "Arts, Entertainment, & Media Management", "Ballet", "Brass Instruments", "Ceramic Arts & Ceramics", "Cinematography & Film/Video Production", "Commercial & Advertising Art", "Commercial Photography", "Conducting", "Costume Design", "Crafts/Craft Design, Folk Art & Artisanry", "Dance", "Design & Applied Arts", "Digital Arts", "Directing & Theatrical Production", "Documentary Production", "Drama/Theatre Arts & Stagecraft", "Drawing", "Fashion/Apparel Design", "Fiber, Textile & Weaving Arts", "Film/Cinema/Video Studies", "Fine & Studio Arts Management", "Game & Interactive Media Design", "Graphic Design", "Illustration", "Industrial & Product Design", "Interior Design", "Intermedia/Multimedia", "Jazz/Jazz Studies", "Keyboard Instruments", "Metal & Jewelry Arts", "Music", "Music History, Literature, & Theory", "Music Management & Merchandising", "Music Pedagogy", "Music Performance", "Music Technology", "Music Theory & Composition", "Musical Theatre", "Musicology & Ethnomusicology", "Painting", "Percussion Instruments", "Photography", "Playwriting & Screenwriting", "Printmaking", "Sculpture", "Stringed Instruments", "Technical Theatre/Theatre Design & Technology", "Theatre Literature, History & Criticism", "Theatre/Theatre Arts Management", "Voice & Opera", "Woodwind Instruments", "Business", "Accounting & Related Services", "Business Administration, Management & Operations", "Business Operations Support & Assistant Services", "Busines & Corporate Communications", "Business & Managerial Economics", "Construction Management", "Entrepreneurial & Small Business Operations", "Finance & Financial Management Services", "Hospitality Administration & Management", "Human Resources Management & Services", "Insurance", "International Business", "Management Information Systems & Services", "Management Sciences & Quantitative Methods", "Marketing", "Real Estate", "Sales & Merchandising", "Taxation", "Telecommunications Management", "Communications", "Animation, Interactive Technology, Video Graphics & Special Effects", "Graphic Communications", "Journalism", "Photographic & Film/Video Technology", "Public Relations & Advertising", "Publishing", "Radio & Television Broadcasting Technology", "Recording Arts Technology", "Computers & Information", "Archives/Archival Administration", "Artificial Intelligence", "Children & Youth Library Services", "Computer Programming", "Computer Science", "Human Computer Interaction", "Informatics", "Information Science/Studies", "Information Technology", "Library & Information Science", "Library Science & Administration", "Education", "Adult & Continuing Education", "Bilingual, Multilingual, & Multicultural Education", "Curriculum & Instruction", "Developmental & Remedial English", "Developmental & Remedial Mathematics", "Early Childhood Education", "Educational Administration & Supervision", "Educational Assessment, Evaluation, & Research", "Educational & Instructional Media Design", "Elementary Education", "High School & Secondary Diploma Programs", "International & Comparative Education", "Outdoor Education", "Second Language Learning", "Secondary Education", "Special Education", "Student Counseling & Personnel Services", "Vocational Education", "Engineering & Technology", "Aerospace, Aeronautical & Astronautical Engineering", "Agricultural Engineering", "Architectural Engineering", "Biomedical & Medical Engineering", "Ceramic Sciences & Engineering", "Chemical Engineering", "Civil Engineering", "Computer Engineering", "Electrical, Electronics & Communications Engineering", "Environmental & Environmental Health Engineering", "Geological & Geophysical Engineering", "Industrial Engineering", "Materials Science", "Mechanical Engineering", "Mining & Mineral Engineering", "Nanotechnology", "Naval Architecture & Marine Engineering", "Nuclear Engineering", "Systems Engineering", "History", "American History", "Asian History", "Canadian History", "European History", "Military History", "Science & Technology History", "Languages & Literatures", "African Languages & Literatures", "Albanian Language & Literature", "American Literature", "American Sign Language", "Ancient Near Eastern & Biblical Languages & Literatures", "Ancient/Classical Greek Language & Literature", "Arabic Language & Literature", "Australian, Oceanic, & Pacific Languages & Literatures", "Baltic Languages & Literatures", "Bengali Language & Literature", "Bosnian, Serbian, & Croatian Languages & Literatures", "Bulgarian Language & Literature", "Burmese Language & Literature", "Catalan Language & Literature", "Celtic Languages & Literatures", "Children's & Adolescent Literature", "Chinese Language & Literature", "Classics & Classical Languages & Literatures", "Comparative Literature", "Creative Writing", "Czech Language & Literature", "Danish Language & Literature", "Dutch/Flemish Language & Literature", "East Asian Languages & Literatures", "English Composition", "English Literature", "Filipino/Tagalog Language & Literature", "French Language & Literature", "Germanic Languages & Literatures", "Hebrew Language & Literature", "Hindi Language & Literature", "Hungarian & Magyar Language & Literature", "Indonesian & Malay Languages & Literatures", "Iranian & Persian Languages & Literatures", "Italian Language & Literature", "Japanese Language & Literature", "Khmer & Cambodian Language & Literature", "Korean Language & Literature", "Lao Language & Literature", "Latin Language & Literature", "Linguistics", "Literature", "Middle & Near Eastern & Semitic Languages & Literatures", "Modern Greek Language & Literature", "Mongolian Language & Literature", "Native American Languages & Literatures", "Norwegian Language & Literature", "Polish Language & Literature", "Portuguese Language & Literature", "Punjabi Language & Literature", "Romance Languages & Literatures", "Romanian Language & Literature", "Russian Language & Literature", "Sanskrit & Classical Indian Languages & Literatures", "Scandinavian Languages & Literatures", "Slavic Languages & Literatures", "Slovak Language & Literature", "South Asian Languages & Literatures", "Southeast Asian, Australasian, & Pacific Languages & Literatures", "Spanish Language & Literature", "Speech & Rhetorical Studies", "Swedish Language & Literature", "Tamil Language & Literature", "Technical & Business Writing", "Thai Language & Literature", "Tibetan Language & Literature", "Turkic, Uralic-Altaic, Caucasian, & Central Asian Languages & Literatures", "Turkish Language & Literature", "Ukrainian Language & Literature", "Uralic Languages & Literatures", "Urdu Language & Literature", "Vietnamese Language & Literature", "Law", "Banking, Corporate, Finance, & Securities Law", "Comparative Law", "Energy, Environment, & Natural Resources Law", "Health Law", "Intellectual Property Law", "International Law & Legal Studies", "Law", "Legal Support Services", "Pre-Law Studies", "Tax Law & Taxation", "Lifelong Learning", "Addiction Prevention & Treatment", "Adult & Continuing Education", "Aircraft Piloting", "Art", "Basic Computer Skills", "Birthing & Parenting", "Board, Card & Role-Playing Games", "Career Exploration", "Collecting", "Computer Games & Programming Skills", "Cooking & Other Domestic Skills", "Dancing", "Handicrafts & Model-Making", "Home Maintenance & Improvement", "Music", "Nature Appreciation", "Personal Health", "Pets", "Reading", "Sports & Exercise", "Theater", "Travel", "Workforce Development & Training", "Writing", "Math", "Algebra & Number Theory", "Applied Mathematics", "Geometry & Geometric Analysis", "Statistics", "Topology & Foundations", "Medicine & Health", "Alternative & Complementary Medicine", "Bioethics & Medical Ethics", "Chiropractic", "Clinical & Medical Laboratory Science & Research", "Communication Disorders Sciences", "Dentistry", "Dietetics & Clinical Nutrition", "Energy & Biologically Based Therapies", "Health & Medical Administration", "Health & Medical Assistance", "Health & Medical Preparatory Programs", "Health Diagnostics, Intervention & Treatment", "Health, Physical Education & Fitness", "Medical Illustration & Informatics", "Medicine", "Mental & Social Health", "Movement & Mind-Body Therapies", "Nursing", "Ophthalmics & Optometrics", "Optometry", "Osteopathic Medicine", "Pharmacy & Pharmaceutical Sciences", "Podiatric Medicine", "Public Health", "Rehabilitation & Therapeutics", "Somatic Bodywork", "Veterinary Medicine", "Military & Security", "Air Force ROTC, Air Science & Operations", "Army ROTC, Military Science & Operations", "Corrections", "Criminal Justice", "Crisis, Emergency & Disaster Management", "Critical Incident Response & Special Police Operations", "Critical Infrastructure Protection", "Cyber & Computer Forensics & Counterterrorism", "Financial Forensics & Fraud Investigation", "Fire Protection", "Fire Science & Fire-fighting", "Fire Systems Technology", "Fire & Arson Investigation & Prevention", "Forensic Science & Technology", "Homeland Security", "Intelligence, Command Control & Information Operations", "Maritime Law Enforcement", "Military Economics & Management", "Military Science & Operational Studies", "Military Technologies", "Navy & Marine ROTC, Naval Science & Operations", "Protective Services Operations", "Security Policy & Strategy", "Terrorism & Counterterrorism Operations", "Wildland & Forest Firefighting & Investigation", "Multidisciplinary Studies", "African Studies", "African-American & Black Studies", "American Studies & Civilization", "Asian Studies & Civilization", "Asian-American Studies", "Balkans Studies", "Baltic Studies", "Canadian Studies", "Caribbean Studies", "Chinese Studies", "Classical & Ancient Studies", "Commonwealth Studies", "Cultural Studies, Critical Theory & Analysis", "Deaf Studies", "Disability Studies", "East Asian Studies", "European Studies & Civilization", "Folklore Studies", "French Studies", "Gay & Lesbian Studies", "General Studies", "German Studies", "Hispanic-American, Puerto Rican, & Mexican-American/Chicano Studies", "Historic Preservation & Conservation", "Holocaust & Related Studies", "Housing & Human Environments", "Humanities & Humanistic Studies", "Intercultural, Multicultural & Diversity Studies", "International & Global Studies", "Irish Studies", "Italian Studies", "Japanese Studies", "Korean Studies", "Latin American & Caribbean Studies", "Medieval & Renaissance Studies", "Museology & Museum Studies", "Native American Studies", "Near & Middle Eastern Studies", "Pacific Area & Pacific Rim Studies", "Peace Studies & Conflict Resolution", "Polish Studies", "Russian, Central European, East European & Eurasian Studies", "Scandinavian Studies", "Slavic Studies", "South Asian Studies", "Southeast Asian Studies", "Spanish & Iberian Studies", "Sustainability Studies", "Systems Science & Theory", "Tibetan Studies", "Ukraine Studies", "Ural-Altaic & Central Asian Studies", "Western European Studies", "Women's Studies", "Work & Family Studies", "Natural & Physical Sciences", "Astronomy", "Astrophysics", "Atmospheric Sciences & Meteorology", "Atomic & Molecular Physics", "Biochemistry, Biophysics & Molecular Biology", "Biology", "Biomathematics, Bioinformatics, & Computational Biology", "Biotechnology", "Botany & Plant Biology", "Cellular Biology & Anatomical Sciences", "Chemistry", "Cognitive Science", "Ecology", "Ecology, Evolution, Systematics, & Population Biology", "Genetics", "Geochemistry & Petrology", "Geological & Earth Sciences", "Geophysics & Seismology", "Hydrology & Water Resources Science", "Marine Biology & Biological Oceanography", "Materials Sciences", "Microbiological Sciences & Immunology", "Neurobiology & Neurosciences", "Neuroscience", "Nuclear Physics", "Oceanography", "Optics", "Paleontology", "Pharmacology & Toxicology", "Physics", "Physiology, Pathology & Related Sciences", "Zoology & Animal Biology", "Philosophy & Religion", "Bible & Biblical Studies", "Buddhist Studies", "Christian Studies", "Divinity & Ministry", "Ethics", "Hindu Studies", "Islamic Studies", "Jewish & Judaic Studies", "Lay Ministry", "Logic", "Missionary Studies & Missiology", "Pastoral Counseling & Specialized Ministries", "Philosophy", "Rabbinical Studies", "Religion & Religious Studies", "Religious Education", "Religious & Sacred Music", "Talmudic Studies", "Theological & Ministerial Studies", "Urban Ministry", "Women's Ministry", "Youth Ministry", "Public Policy & Administration", "Community Organization & Advocacy", "Education Policy Analysis", "Fishing & Fisheries", "Forestry", "Health Policy Analysis", "Human Services", "International Policy Analysis", "Public Administration", "Public Policy Analysis", "Social Work", "Wildlife & Wildlands", "Youth Services", "Social Sciences", "Anthropology", "Archeology", "Criminology", "Demography & Population Studies", "Economics", "Geography & Cartography", "International Relations & National Security Studies", "Political Science & Government", "Psychology", "Sociology", "Urban Studies & Affairs", "Trades & Professions", "Air Traffic Control", "Aircraft Powerplant Technology", "Airframe Mechanics & Aircraft Maintenance & Repair", "Airline Flight Attendant", "Airline Pilot & Flight Crew", "Alternative Fuel Vehicle Technology", "Apparel & Textiles", "Automotive Mechanics", "Aviation Management & Operations", "Avionics Maintenance & Repair", "Bicycle Mechanics & Repair", "Blasting & Blaster", "Boilermaking & Boilermaker", "Building & Construction Finishing, Management, & Inspection", "Building & Construction Site Management", "Building, Home & Construction Inspection", "Building & Property Maintenance", "Cabinetmaking & Millwork", "Carpenters", "Carpet, Floor & Tile Worker", "Commercial Fishing", "Computer Numerically Controlled Machinist", "Concrete Finishing", "Construction, Heavy Equipment & Earthmoving Equipment Operation", "Cosmetology", "Culinary Arts", "Diesel Mechanics", "Diver, Professional & Instructor", "Drywall Installation", "Electrical & Electronics Maintenance & Repair", "Electrician", "Engine Machinist", "Flagging & Traffic Control", "Flight Instruction", "Foods, Nutrition & Related Services", "Funeral Service & Mortuary Science", "Furniture Design & Manufacturing", "Glazing", "Gunsmithing", "Heating, Air Conditioning, Ventilation & Refrigeration Maintenance & Repair", "Heavy & Industrial Equipment Maintenance & Repair", "High Performance & Custom Engine Mechanics", "Insulator", "Ironworking", "Locksmithing & Safe Repair", "Machine Tool Technology & Machinist", "Marine Maintenance, Fitter & Ship Repair", "Marine Science & Merchant Marine Officer", "Marine Transportation", "Masonry", "Medium & Heavy Vehicle & Truck Technology", "Metal Building Assembly", "Metal Fabricator", "Mobile Crane Operation", "Motorcycle Maintenance & Repair", "Musical Instrument Fabrication & Repair", "Painting & Wall Covering", "Pipefitting & Sprinkler Fitting", "Plumbing", "Railroad & Railway Transportation", "Recreation Vehicle (RV) Service", "Roofing", "Sheet Metal Technology & Sheetworking", "Shoe, Boot & Leather Repair", "Small Engine Mechanics", "Tool & Die Technology", "Truck/Bus Driver & Commercial Vehicle Operator & Instructor", "Upholstery", "Watchmaking & Jewelrymaking", "Welding", "Well Drilling", "Woodworking"]
|
120
|
+
list = []
|
121
|
+
number.times do
|
122
|
+
categories.shuffle!
|
123
|
+
list << categories.pop
|
124
|
+
end
|
125
|
+
return list
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
130
|
+
# Need this to extend Watir to be able to attach to Sakai's non-standard tags...
|
131
|
+
module Watir
|
132
|
+
|
133
|
+
class Browser
|
134
|
+
|
135
|
+
def linger_for_ajax(timeout=5)
|
136
|
+
end_time = ::Time.now + timeout
|
137
|
+
while self.execute_script("return jQuery.active") > 0
|
138
|
+
sleep 0.2
|
139
|
+
break if ::Time.now > end_time
|
140
|
+
end
|
141
|
+
self.wait(timeout + 10)
|
142
|
+
end
|
143
|
+
|
144
|
+
def back_to_top
|
145
|
+
self.execute_script("javascript:window.scrollTo(0,0)")
|
146
|
+
end
|
147
|
+
|
148
|
+
alias return_to_top back_to_top
|
149
|
+
alias scroll_to_top back_to_top
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
class Element
|
154
|
+
# attaches to the "headers" tags inside of the assignments table.
|
155
|
+
def headers
|
156
|
+
@how = :ole_object
|
157
|
+
return @o.headers
|
158
|
+
end
|
159
|
+
|
160
|
+
# attaches to the "for" tags in "label" tags.
|
161
|
+
def for
|
162
|
+
@how = :ole_object
|
163
|
+
return @o.for
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
@@ -0,0 +1,109 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
|
3
|
+
# This module contains methods that will be almost
|
4
|
+
# universally useful, and don't pertain to any specific
|
5
|
+
# area or widget or pop up on a given page.
|
6
|
+
#
|
7
|
+
# The primary method provided in this module is of the type
|
8
|
+
# .open_<page>('target_text'). Depending on the type of <page>
|
9
|
+
# specified, the method will return the appropriate Class object.
|
10
|
+
module GlobalMethods
|
11
|
+
|
12
|
+
include PageObject
|
13
|
+
|
14
|
+
# These create methods of the form
|
15
|
+
# open_<name>('target link text')
|
16
|
+
# Example usage in a script on a page where there's a calendar link
|
17
|
+
# with the text "Moon Phases" ...
|
18
|
+
#
|
19
|
+
# calendar_page = current_page.open_calendar("Moon Phases")
|
20
|
+
#
|
21
|
+
# Once the link is clicked, the method
|
22
|
+
# returns the specified class object.
|
23
|
+
#
|
24
|
+
# It's important to note that the methods created
|
25
|
+
# assume that the target page will be opened in the
|
26
|
+
# same tab. If a new tab is opened, these methods will not work.
|
27
|
+
#
|
28
|
+
# In addition, the page classes that are instantiated for the
|
29
|
+
# target pages assume that the user has editing
|
30
|
+
# rights.
|
31
|
+
open_link(:document, "ContentDetailsPage")
|
32
|
+
open_link(:content, "ContentDetailsPage")
|
33
|
+
open_link(:remote_content, "Remote")
|
34
|
+
open_link(:library, "Library")
|
35
|
+
open_link(:participants, "Participants")
|
36
|
+
open_link(:discussions, "Discussions")
|
37
|
+
open_link(:tests_and_quizzes, "Tests")
|
38
|
+
open_link(:assessments, "Tests")
|
39
|
+
open_link(:calendar, "Calendar")
|
40
|
+
open_link(:map, "GoogleMaps")
|
41
|
+
open_link(:file, "Files")
|
42
|
+
open_link(:forum, "Forum")
|
43
|
+
open_link(:comments, "Comments")
|
44
|
+
open_link(:jisc, "JISC")
|
45
|
+
open_link(:assignments, "Assignments")
|
46
|
+
open_link(:feed, "RSS")
|
47
|
+
open_link(:rss_feed, "RSS")
|
48
|
+
open_link(:rss, "RSS")
|
49
|
+
open_link(:lti, "LTI")
|
50
|
+
open_link(:basic_lti, "LTI")
|
51
|
+
open_link(:gadget, "Gadget")
|
52
|
+
open_link(:gradebook, "Gradebook")
|
53
|
+
|
54
|
+
alias open_group open_library
|
55
|
+
alias open_course open_library
|
56
|
+
alias go_to open_library
|
57
|
+
|
58
|
+
# The "gritter" notification that appears to confirm
|
59
|
+
# when something has happened (like updating a user profile
|
60
|
+
# or sending a message).
|
61
|
+
div(:notification, :class=>"gritter-with-image")
|
62
|
+
|
63
|
+
# This method is essentially
|
64
|
+
# identical with the
|
65
|
+
# open_link methods listed above.
|
66
|
+
# It opens page/document items that are listed on the page--for example
|
67
|
+
# in the Recent activity box.
|
68
|
+
# There is an important distinction, however:
|
69
|
+
# This method should be used in cases when clicking the
|
70
|
+
# link results in a new browser tab/window being generated.
|
71
|
+
# May ultimately convert this to open_public_<object>
|
72
|
+
# if that seems the best generic way to handle things.
|
73
|
+
#
|
74
|
+
# THIS METHOD MAY SOON BE DEPRECATED (if so, it
|
75
|
+
# will be included as an alias of open_content)
|
76
|
+
def open_page(name)
|
77
|
+
name_link(name).click
|
78
|
+
self.wait_for_ajax
|
79
|
+
self.window(:title=>"rSmart | Content Profile").use
|
80
|
+
ContentDetailsPage.new @browser
|
81
|
+
end
|
82
|
+
|
83
|
+
# Clicks the link of the specified name (It will click any link on the page,
|
84
|
+
# really, but it should be used for Person links only, because it
|
85
|
+
# instantiates the ViewPerson Class)
|
86
|
+
def view_person(name)
|
87
|
+
name_link(name).click
|
88
|
+
sleep 3
|
89
|
+
self.wait_for_ajax
|
90
|
+
ViewPerson.new @browser
|
91
|
+
end
|
92
|
+
alias view_profile view_person
|
93
|
+
|
94
|
+
# Clicks the X in the Notification box, to dismiss it.
|
95
|
+
def close_notification
|
96
|
+
self.div(:class=>"gritter-item").hover
|
97
|
+
self.div(:class=>"gritter-close").fire_event "onclick"
|
98
|
+
end
|
99
|
+
|
100
|
+
# This method exposes the "draggable" menu items in the left-hand
|
101
|
+
# menus, so that you can use Watir's <X>.drag_and_drop_on <Y> method.
|
102
|
+
# Note that not all matching menu items are necessarily draggable.
|
103
|
+
# Also useful if you just want to see if the menu item is visible
|
104
|
+
# on the page
|
105
|
+
def menu_item(name)
|
106
|
+
self.div(:class=>/lhnavigation(_subnav|)_item_content/, :text=>name)
|
107
|
+
end
|
108
|
+
|
109
|
+
end # GlobalMethods
|
@@ -0,0 +1,1949 @@
|
|
1
|
+
# coding: UTF-8
|
2
|
+
require 'sakai-OAE/gem_extensions'
|
3
|
+
require 'sakai-OAE/global_methods'
|
4
|
+
require 'sakai-OAE/toolbars_and_menus'
|
5
|
+
require 'sakai-OAE/pop_up_dialogs'
|
6
|
+
require 'sakai-OAE/widgets'
|
7
|
+
require 'sakai-OAE/cle_frame_classes'
|
8
|
+
|
9
|
+
# The Login page for OAE.
|
10
|
+
class LoginPage
|
11
|
+
|
12
|
+
include PageObject
|
13
|
+
include HeaderFooterBar
|
14
|
+
include GlobalMethods
|
15
|
+
|
16
|
+
# Page Objects
|
17
|
+
div(:expand_categories, :class=>"categories_expand")
|
18
|
+
|
19
|
+
# Custom Methods...
|
20
|
+
|
21
|
+
# Returns an array containing the titles of the items
|
22
|
+
# displayed in the "Recent activity" box on the login page.
|
23
|
+
def recent_activity_list
|
24
|
+
list = []
|
25
|
+
self.div(:id=>"recentactivity_activity_container").links(:class=>"recentactivity_activity_item_title recentactivity_activity_item_text s3d-regular-links s3d-bold").each do |link|
|
26
|
+
list << link.text
|
27
|
+
end
|
28
|
+
return list.uniq!
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an array containing the titles of the items in the
|
32
|
+
# Featured Content area of the page.
|
33
|
+
def featured_content_list
|
34
|
+
list = []
|
35
|
+
self.div(:id=>"featuredcontent_content_container").links(:class=>/featuredcontent_content_title/).each do |link|
|
36
|
+
list << link.text
|
37
|
+
end
|
38
|
+
return list
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# Page Objects and Methods for the "All Categories" page.
|
44
|
+
# Note that this page is distinct from the "Search => All types"
|
45
|
+
# page.
|
46
|
+
class AllCategoriesPage
|
47
|
+
|
48
|
+
include PageObject
|
49
|
+
include HeaderFooterBar
|
50
|
+
include GlobalMethods
|
51
|
+
|
52
|
+
# Page Objects
|
53
|
+
div(:page_title, :class=>"s3d-contentpage-title")
|
54
|
+
|
55
|
+
# Custom Methods...
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
# TODO - Write a class description
|
60
|
+
class Calendar
|
61
|
+
|
62
|
+
include PageObject
|
63
|
+
include HeaderFooterBar
|
64
|
+
include LeftMenuBar
|
65
|
+
include HeaderBar
|
66
|
+
include DocButtons
|
67
|
+
include GlobalMethods
|
68
|
+
|
69
|
+
def calendar_frame
|
70
|
+
self.frame(:src=>/sakai2calendar.launch.html/)
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
# Methods related to the Content Details page.
|
76
|
+
class ContentDetailsPage
|
77
|
+
|
78
|
+
include PageObject
|
79
|
+
include GlobalMethods
|
80
|
+
include HeaderFooterBar
|
81
|
+
include DocButtons
|
82
|
+
include LeftMenuBar
|
83
|
+
|
84
|
+
# Page Objects
|
85
|
+
text_area(:comment_text_area, :id=>"contentcomments_txtMessage")
|
86
|
+
button(:comment_button, :text=>"Comment")
|
87
|
+
button(:see_more, :id=>"contentmetadata_show_more")
|
88
|
+
button(:see_less, :id=>"contentmetadata_show_more")
|
89
|
+
button(:permissions_menu_button, :id=>"entity_content_permissions")
|
90
|
+
button(:permissions_button, :text=>"Permissions")
|
91
|
+
button(:delete_button, :text=>"Delete")
|
92
|
+
button(:add_to_button, :id=>"entity_content_save")
|
93
|
+
button(:share_button, :id=>"entity_content_share")
|
94
|
+
|
95
|
+
span(:name, :id=>"entity_name")
|
96
|
+
span(:type, :id=>"entity_type")
|
97
|
+
|
98
|
+
# Custom Methods...
|
99
|
+
|
100
|
+
# Returns the text of the description display span.
|
101
|
+
def description
|
102
|
+
self.span(:id=>"contentmetadata_description_display").text
|
103
|
+
end
|
104
|
+
|
105
|
+
# Enters the specified text into the description text area box.
|
106
|
+
# Note that this method first fires off the edit_description method
|
107
|
+
# because the description text area is not present by default.
|
108
|
+
def description=(text)
|
109
|
+
edit_description
|
110
|
+
self.text_area(:id=>"contentmetadata_description_description").set text
|
111
|
+
end
|
112
|
+
|
113
|
+
# Header row items...
|
114
|
+
def update_name=(new_name)
|
115
|
+
name_element.click
|
116
|
+
self.text_field(:id=>"entity_name_text").set new_name + "\n"
|
117
|
+
end
|
118
|
+
|
119
|
+
# Visibility...
|
120
|
+
def change_visibility_private
|
121
|
+
# TODO - Write method
|
122
|
+
end
|
123
|
+
|
124
|
+
def change_visibility_logged_in
|
125
|
+
# TODO - Write method
|
126
|
+
end
|
127
|
+
|
128
|
+
def change_visibility_public
|
129
|
+
# TODO - Write method
|
130
|
+
end
|
131
|
+
|
132
|
+
# Collaboration...
|
133
|
+
|
134
|
+
#
|
135
|
+
def view_collaborators
|
136
|
+
# TODO - Write method
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
def change_collaborators
|
141
|
+
# TODO - Write method
|
142
|
+
end
|
143
|
+
|
144
|
+
# This method is currently not working TODO - Fix method
|
145
|
+
#def change_sharing
|
146
|
+
# self.div(:class=>"entity_owns_actions_share has_counts ew_permissions").hover
|
147
|
+
# self.div(:class=>"entity_owns_actions_share has_counts ew_permissions").click
|
148
|
+
# self.execute_script(%|$('.entity_owns_actions_share.has_counts.ew_permissions').trigger("mouseover");|)
|
149
|
+
# wait_for_ajax
|
150
|
+
# self.div(:class=>"entity_owns_actions_share has_counts ew_permissions").button(:class=>"s3d-link-button ew_permissions").click
|
151
|
+
# self.class.class_eval { include ContentPermissionsPopUp }
|
152
|
+
#end
|
153
|
+
|
154
|
+
def collaboration_share
|
155
|
+
self.div(:id=>"entity_actions").button(:text=>"Share").click
|
156
|
+
self.wait_until { self.text.include? "Who do you want to share with?" }
|
157
|
+
self.class.class_eval { include ShareWithPopUp }
|
158
|
+
end
|
159
|
+
|
160
|
+
# Comments count...
|
161
|
+
|
162
|
+
# Comments field...
|
163
|
+
|
164
|
+
# Clicks the Comments button
|
165
|
+
def comment
|
166
|
+
comment_button
|
167
|
+
self.wait_for_ajax(2)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Enters the specified text string into the Comment box.
|
171
|
+
def comment_text(text)
|
172
|
+
comment_text_area_element.click
|
173
|
+
comment_text_area_element.send_keys text
|
174
|
+
end
|
175
|
+
alias comment_text= comment_text
|
176
|
+
|
177
|
+
# Clicks the "Add to..." button
|
178
|
+
def add_to
|
179
|
+
add_to_button
|
180
|
+
self.wait_for_ajax(2)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Clicks "Permissions" in the menu...
|
184
|
+
def permissions
|
185
|
+
permissions_menu_button
|
186
|
+
self.wait_for_ajax(2)
|
187
|
+
permissions_button
|
188
|
+
self.wait_for_ajax(2)
|
189
|
+
self.class.class_eval { include ContentPermissionsPopUp }
|
190
|
+
end
|
191
|
+
|
192
|
+
# Returns an array object containing the items displayed in
|
193
|
+
# "Related Content" on the page.
|
194
|
+
def related_content
|
195
|
+
list = []
|
196
|
+
self.div(:class=>"relatedcontent_list").links.each do |link|
|
197
|
+
list << link.title
|
198
|
+
end
|
199
|
+
return list
|
200
|
+
end
|
201
|
+
|
202
|
+
# Clicks on the "Share Content" button.
|
203
|
+
def share_content
|
204
|
+
self.div(:id=>"entity_actions").span(:class=>"entity_share_content").click
|
205
|
+
self.wait_until { @browser.text.include? "Who do you want to share with?" }
|
206
|
+
self.class.class_eval { include ShareWithPopUp }
|
207
|
+
end
|
208
|
+
|
209
|
+
# Clicks on the "Add to library" button.
|
210
|
+
def add_to_library
|
211
|
+
self.button(:text=>"Add to library").click
|
212
|
+
self.wait_until { self.text.include? "Save to" }
|
213
|
+
self.class.class_eval { include SaveContentPopUp }
|
214
|
+
end
|
215
|
+
|
216
|
+
# Opens the description text field for editing.
|
217
|
+
def edit_description
|
218
|
+
self.div(:id=>"contentmetadata_description_container").fire_event "onmouseover"
|
219
|
+
self.div(:id=>"contentmetadata_description_container").fire_event "onclick"
|
220
|
+
end
|
221
|
+
|
222
|
+
# Opens the tag field for editing.
|
223
|
+
def edit_tags
|
224
|
+
self.div(:id=>"contentmetadata_tags_container").fire_event "onclick"
|
225
|
+
end
|
226
|
+
|
227
|
+
# Opens the Copyright field for editing.
|
228
|
+
def edit_copyright
|
229
|
+
self.div(:id=>"contentmetadata_copyright_container").fire_event "onclick"
|
230
|
+
end
|
231
|
+
|
232
|
+
# Opens the Categories field for editing.
|
233
|
+
def edit_categories
|
234
|
+
self.div(:id=>"contentmetadata_locations_container").fire_event "onclick"
|
235
|
+
self.class.class_eval { include AddRemoveCategories }
|
236
|
+
end
|
237
|
+
|
238
|
+
# Returns an array containing the tags and categories listed on the page.
|
239
|
+
def tags_and_categories_list
|
240
|
+
list =[]
|
241
|
+
self.div(:id=>"contentmetadata_tags_container").links.each do |link|
|
242
|
+
list << link.text
|
243
|
+
end
|
244
|
+
return list
|
245
|
+
end
|
246
|
+
|
247
|
+
# The "share" button next to the Download button.
|
248
|
+
def share_with_others
|
249
|
+
self.share_button
|
250
|
+
self.wait_for_ajax
|
251
|
+
self.class.class_eval { include ShareWithPopUp }
|
252
|
+
end
|
253
|
+
|
254
|
+
# Comments List Stuff
|
255
|
+
|
256
|
+
# This method grabs all of the information about the last listed comment and
|
257
|
+
# returns it in a hash object. Relevant strings are put into the following
|
258
|
+
# keys: :poster, :date, :message. The delete button element is defined in the
|
259
|
+
# :delete_button key. Note that you'll have to use Watir's .click method to click
|
260
|
+
# the button.
|
261
|
+
def last_comment
|
262
|
+
hash = {}
|
263
|
+
comments_table = self.div(:class=>"contentcommentsTable")
|
264
|
+
last_message = comments_table.div(:class=>"contentcomments_comment last")
|
265
|
+
hash.store(:poster, last_message.span(:class=>"contentcomments_posterDataName s3d-regular-links").link.text)
|
266
|
+
hash.store(:date, last_message.span(:class=>"contentcomments_dateComment").text)
|
267
|
+
hash.store(:message, last_message.div(:class=>"contentcomments_message").text)
|
268
|
+
hash.store(:delete_button, last_message.button(:id=>/contentcomments_delete_\d+/))
|
269
|
+
return hash
|
270
|
+
end
|
271
|
+
|
272
|
+
# This method grabs all of the information about the first listed comment and
|
273
|
+
# returns it in a hash object. Relevant strings are put into the following
|
274
|
+
# keys: :poster, :date, :message. The delete button element is defined in the
|
275
|
+
# :delete_button key. Note that you'll have to use Watir's .click method to click
|
276
|
+
# the button.
|
277
|
+
def first_comment
|
278
|
+
hash = {}
|
279
|
+
comments_table = self.div(:class=>"contentcommentsTable")
|
280
|
+
last_message = comments_table.div(:class=>"contentcomments_comment last")
|
281
|
+
hash.store(:poster, last_message.span(:class=>"contentcomments_posterDataName s3d-regular-links").link.text)
|
282
|
+
hash.store(:date, last_message.span(:class=>"contentcomments_dateComment").text)
|
283
|
+
hash.store(:message, last_message.div(:class=>"contentcomments_message").text)
|
284
|
+
hash.store(:delete_button, last_message.button(:id=>/contentcomments_delete_\d+/))
|
285
|
+
return hash
|
286
|
+
end
|
287
|
+
|
288
|
+
end
|
289
|
+
|
290
|
+
# Methods related to the page for creating a new user account
|
291
|
+
class CreateNewAccount
|
292
|
+
|
293
|
+
include PageObject
|
294
|
+
include HeaderFooterBar
|
295
|
+
|
296
|
+
text_field(:user_name, :id=>"username")
|
297
|
+
text_field(:institution, :id=>"institution")
|
298
|
+
# The password field's method name needs to be
|
299
|
+
# "new_password" so that it doesn't conflict with
|
300
|
+
# the "password" field in the Sign In menu...
|
301
|
+
text_field(:new_password, :id=>"password")
|
302
|
+
text_field(:retype_password, :id=>"password_repeat")
|
303
|
+
select_list(:role, :id=>"role")
|
304
|
+
select_list(:title, :id=>"title")
|
305
|
+
text_field(:first_name,:id=>"firstName")
|
306
|
+
text_field(:last_name,:id=>"lastName")
|
307
|
+
text_field(:email,:id=>"email")
|
308
|
+
text_field(:email_confirm, :id=>"emailConfirm")
|
309
|
+
text_field(:phone_number,:id=>"phone")
|
310
|
+
checkbox(:receive_tips, :id=>"emailContact")
|
311
|
+
checkbox(:contact_me_directly, :id=>"contactMe")
|
312
|
+
button(:create_account_button, :id=>"save_account")
|
313
|
+
|
314
|
+
span(:username_error, :id=>"username_error")
|
315
|
+
span(:password_error, :id=>"password_error")
|
316
|
+
span(:password_repeat_error, :id=>"password_repeat_error")
|
317
|
+
span(:title_error, :id=>"title_error")
|
318
|
+
span(:firstname_error, :id=>"firstName_error")
|
319
|
+
span(:lastname_error, :id=>"lastName_error")
|
320
|
+
span(:email_error, :id=>"email_error")
|
321
|
+
span(:email_confirm_error, :id=>"emailConfirm_error")
|
322
|
+
span(:institution_error, :id=>"institution_error")
|
323
|
+
span(:role_error, :id=>"role_error")
|
324
|
+
span(:password_strength, :class=>/strength (zero|one|two|three|four)/)
|
325
|
+
|
326
|
+
# Clicks the 'create account' button, waits for the dashboard,
|
327
|
+
# then returns the MyDashboard class object.
|
328
|
+
def create_account
|
329
|
+
self.back_to_top
|
330
|
+
self.create_account_button
|
331
|
+
sleep 4
|
332
|
+
self.wait_until { self.text.include? "My dashboard" }
|
333
|
+
MyDashboard.new @browser
|
334
|
+
#self.class.class_eval { include OurAgreementPopUp }
|
335
|
+
end
|
336
|
+
|
337
|
+
end
|
338
|
+
|
339
|
+
# Methods related to the page that appears when you are
|
340
|
+
# creating a new Course.
|
341
|
+
class CreateCourses
|
342
|
+
|
343
|
+
include PageObject
|
344
|
+
include HeaderFooterBar
|
345
|
+
include LeftMenuBarCreateWorlds
|
346
|
+
|
347
|
+
# Clicks the button for using the math template,
|
348
|
+
# then returns the MathTemplate class object.
|
349
|
+
def use_math_template
|
350
|
+
self.div(:class=>"selecttemplate_template_large").button(:text=>"Use this template").click
|
351
|
+
# TODO - Class goes here
|
352
|
+
end
|
353
|
+
|
354
|
+
# Clicks the button to use the basic template,
|
355
|
+
# waits for the new page to load, then returns
|
356
|
+
# the CreateGroups class object.
|
357
|
+
def use_basic_template
|
358
|
+
self.div(:class=>"selecttemplate_template_small selecttemplate_template_right").button(:text=>"Use this template").click
|
359
|
+
self.wait_until { self.text.include? "Suggested URL:" }
|
360
|
+
CreateGroups.new @browser
|
361
|
+
end
|
362
|
+
|
363
|
+
end
|
364
|
+
|
365
|
+
# Methods related to the page where a new Course/Group/Project
|
366
|
+
# is set up.
|
367
|
+
class CreateGroups
|
368
|
+
|
369
|
+
include PageObject
|
370
|
+
include HeaderFooterBar
|
371
|
+
include LeftMenuBarCreateWorlds
|
372
|
+
|
373
|
+
text_field(:title, :id=>"newcreategroup_title")
|
374
|
+
text_field(:suggested_url, :id=>"newcreategroup_suggested_url")
|
375
|
+
text_area(:description, :id=>"newcreategroup_description")
|
376
|
+
text_area(:tags, :name=>"newcreategroup_tags")
|
377
|
+
select_list(:can_be_discovered_by, :id=>"newcreategroup_can_be_found_in")
|
378
|
+
select_list(:membership, :id=>"newcreategroup_membership")
|
379
|
+
|
380
|
+
# Clicks the 'Add people' button and waits for the
|
381
|
+
# Manage Participants dialog to appear.
|
382
|
+
# Includes the ManageParticipants module in the class.
|
383
|
+
def add_people
|
384
|
+
self.button(:text, "Add people").click
|
385
|
+
self.wait_for_ajax(2)
|
386
|
+
self.class.class_eval { include ManageParticipants }
|
387
|
+
end
|
388
|
+
|
389
|
+
# Clicks the 'Add more people' button and waits for
|
390
|
+
# the Manage Participants dialog to appear.
|
391
|
+
# Includes the ManageParticipants module in the class.
|
392
|
+
def add_more_people
|
393
|
+
self.button(:text, "Add more people").click
|
394
|
+
self.wait_for_ajax(2)
|
395
|
+
self.class.class_eval { include ManageParticipants }
|
396
|
+
end
|
397
|
+
|
398
|
+
# Clicks the 'List categories' button, waits for
|
399
|
+
# the Categories dialog to appear, and then
|
400
|
+
# includes the AddRemoveCategories module in the class.
|
401
|
+
def list_categories
|
402
|
+
self.button(:text=>"List categories").click
|
403
|
+
self.wait_for_ajax(2)
|
404
|
+
self.class.class_eval { include AddRemoveCategories }
|
405
|
+
end
|
406
|
+
|
407
|
+
# Clicks the 'Create' button for the Course or Group, etc.,
|
408
|
+
# then waits until the Course Library page loads. Once that
|
409
|
+
# happens, returns the Library class object.
|
410
|
+
def create_basic_course
|
411
|
+
create_thing
|
412
|
+
unless url_error_element.visible?
|
413
|
+
self.wait_until(45) { self.text.include? "Add content" }
|
414
|
+
self.button(:id=>"group_create_new_area", :class=>"s3d-button s3d-header-button s3d-popout-button").wait_until_present
|
415
|
+
Library.new @browser
|
416
|
+
end
|
417
|
+
end
|
418
|
+
alias create_simple_group create_basic_course
|
419
|
+
alias create_group create_basic_course
|
420
|
+
alias create_research_support_group create_basic_course
|
421
|
+
|
422
|
+
# Clicks the 'Create' button then waits until the Research Intro page loads. Once that
|
423
|
+
# happens, returns the ResearchIntro class object.
|
424
|
+
def create_research_project
|
425
|
+
create_thing
|
426
|
+
unless url_error_element.visible?
|
427
|
+
self.button(:id=>"group_create_new_area", :class=>"s3d-button s3d-header-button s3d-popout-button").wait_until_present
|
428
|
+
ResearchIntro.new @browser
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
|
433
|
+
span(:url_error, :id=>"newcreategroup_suggested_url_error")
|
434
|
+
|
435
|
+
private
|
436
|
+
|
437
|
+
# Do not use. This method is used by the public 'create' methods in this class.
|
438
|
+
def create_thing
|
439
|
+
self.button(:class=>"s3d-button s3d-overlay-button newcreategroup_create_simple_group").click
|
440
|
+
sleep 0.3
|
441
|
+
self.div(:id=>"sakai_progressindicator").wait_while_present
|
442
|
+
sleep 2 # The poor man's wait_for_ajax, since that was failing.
|
443
|
+
end
|
444
|
+
|
445
|
+
end
|
446
|
+
|
447
|
+
# Methods related to the page for Creating a Research Project
|
448
|
+
class CreateResearch
|
449
|
+
|
450
|
+
include PageObject
|
451
|
+
include HeaderFooterBar
|
452
|
+
include LeftMenuBarCreateWorlds
|
453
|
+
|
454
|
+
# Clicks the button for using the Research Project template, waits for the
|
455
|
+
# page to load, then returns the CreateGroups class object.
|
456
|
+
def use_research_project_template
|
457
|
+
self.div(:class=>"selecttemplate_template_large").button(:text=>"Use this template").click
|
458
|
+
self.wait_until { self.text.include? "Suggested URL:" }
|
459
|
+
CreateGroups.new @browser
|
460
|
+
end
|
461
|
+
|
462
|
+
# Clicks the button for using the Research Support Group template,
|
463
|
+
# waits for the page to load, then returns the CreateGroups class object.
|
464
|
+
def use_research_support_group_template
|
465
|
+
self.div(:class=>"selecttemplate_template_small selecttemplate_template_right").button(:text=>"Use this template").click
|
466
|
+
self.wait_until { self.text.include? "Suggested URL:" }
|
467
|
+
CreateGroups.new @browser
|
468
|
+
end
|
469
|
+
|
470
|
+
end
|
471
|
+
|
472
|
+
# Page Object and methods for the SEARCH ALL TYPES page
|
473
|
+
# NOT the "Browse All Categories" page!
|
474
|
+
class ExploreAll
|
475
|
+
|
476
|
+
include PageObject
|
477
|
+
include GlobalMethods
|
478
|
+
include HeaderFooterBar
|
479
|
+
include LeftMenuBarSearch
|
480
|
+
include ListWidget
|
481
|
+
include ListCollections
|
482
|
+
include ListContent
|
483
|
+
include ListGroups
|
484
|
+
include ListPeople
|
485
|
+
include ListProjects
|
486
|
+
include SearchBar
|
487
|
+
|
488
|
+
# Returns the results header title (the text prior to the count of the results returned)
|
489
|
+
def results_header
|
490
|
+
top = self.div(:class=>"searchall_content_main")
|
491
|
+
top.div(:id=>"results_header").span.text =~ /^.+(?=.\()/
|
492
|
+
$~.to_s
|
493
|
+
end
|
494
|
+
|
495
|
+
end
|
496
|
+
|
497
|
+
# Methods related to the page for searching Content.
|
498
|
+
class ExploreContent
|
499
|
+
|
500
|
+
include PageObject
|
501
|
+
include GlobalMethods
|
502
|
+
include HeaderFooterBar
|
503
|
+
include LeftMenuBarSearch
|
504
|
+
include ListWidget
|
505
|
+
include ListContent
|
506
|
+
include SearchBar
|
507
|
+
|
508
|
+
# Returns the results header title (the text prior to the count of the results returned)
|
509
|
+
def results_header
|
510
|
+
top = self.div(:class=>"searchcontent_content_main")
|
511
|
+
top.div(:id=>"results_header").span.text =~ /^.+(?=.\()/
|
512
|
+
$~.to_s
|
513
|
+
end
|
514
|
+
|
515
|
+
end
|
516
|
+
|
517
|
+
# Methods related to the People/Users search page
|
518
|
+
class ExplorePeople
|
519
|
+
|
520
|
+
include PageObject
|
521
|
+
include GlobalMethods
|
522
|
+
include HeaderFooterBar
|
523
|
+
include LeftMenuBarSearch
|
524
|
+
include ListWidget
|
525
|
+
include ListPeople
|
526
|
+
include SearchBar
|
527
|
+
|
528
|
+
# Returns the results header title (the text prior to the count of the results returned)
|
529
|
+
def results_header
|
530
|
+
top = self.div(:class=>"searchpeople_content_main")
|
531
|
+
top.div(:id=>"results_header").span.text =~ /^.+(?=.\()/
|
532
|
+
$~.to_s
|
533
|
+
end
|
534
|
+
|
535
|
+
end
|
536
|
+
|
537
|
+
# Methods related to the Groups search page.
|
538
|
+
class ExploreGroups
|
539
|
+
|
540
|
+
include PageObject
|
541
|
+
include GlobalMethods
|
542
|
+
include HeaderFooterBar
|
543
|
+
include LeftMenuBarSearch
|
544
|
+
include ListWidget
|
545
|
+
include ListGroups
|
546
|
+
include SearchBar
|
547
|
+
|
548
|
+
# Returns the results header title (the text prior to the count of the results returned)
|
549
|
+
def results_header
|
550
|
+
top = self.div(:id=>"searchgroups_widget", :index=>0)
|
551
|
+
top.div(:id=>"results_header").span(:id=>"searchgroups_type_title").text
|
552
|
+
end
|
553
|
+
|
554
|
+
end
|
555
|
+
|
556
|
+
# Methods related to the page for searching Courses.
|
557
|
+
class ExploreCourses
|
558
|
+
|
559
|
+
include PageObject
|
560
|
+
include GlobalMethods
|
561
|
+
include HeaderFooterBar
|
562
|
+
include LeftMenuBarSearch
|
563
|
+
include ListWidget
|
564
|
+
include ListGroups
|
565
|
+
include SearchBar
|
566
|
+
|
567
|
+
# Returns the results header title (the text prior to the count of the results returned)
|
568
|
+
def results_header
|
569
|
+
top = self.div(:id=>"searchgroups_widget", :index=>1)
|
570
|
+
top.div(:id=>"results_header").span(:id=>"searchgroups_type_title").text
|
571
|
+
end
|
572
|
+
|
573
|
+
# TODO - Describe method
|
574
|
+
def courses_count
|
575
|
+
#TODO - Write method
|
576
|
+
end
|
577
|
+
|
578
|
+
# Selects the specified item in the 'Filter by' field. Waits for
|
579
|
+
# Ajax calls to drop to zero.
|
580
|
+
def filter_by=(selection)
|
581
|
+
self.select(:id=>"facted_select").select(selection)
|
582
|
+
self.wait_for_ajax(2)
|
583
|
+
end
|
584
|
+
|
585
|
+
# Selects the specified item in the 'Sort by' field. Waits for
|
586
|
+
# any Ajax calls to finish.
|
587
|
+
def sort_by=(selection)
|
588
|
+
self.div(:class=>"s3d-search-sort").select().select(selection)
|
589
|
+
self.wait_for_ajax(2)
|
590
|
+
end
|
591
|
+
|
592
|
+
end
|
593
|
+
|
594
|
+
# Methods related to the page for searching for Research Projects.
|
595
|
+
class ExploreResearch
|
596
|
+
|
597
|
+
include PageObject
|
598
|
+
include GlobalMethods
|
599
|
+
include HeaderFooterBar
|
600
|
+
include LeftMenuBarSearch
|
601
|
+
include ListWidget
|
602
|
+
include ListProjects
|
603
|
+
include SearchBar
|
604
|
+
|
605
|
+
# Returns the results header title (the text prior to the count of the results returned)
|
606
|
+
def results_header
|
607
|
+
top = self.div(:id=>"searchgroups_widget", :index=>2)
|
608
|
+
top.div(:id=>"results_header").span(:id=>"searchgroups_type_title").text
|
609
|
+
end
|
610
|
+
|
611
|
+
end
|
612
|
+
|
613
|
+
# Methods related to objects found on the Dashboard
|
614
|
+
class MyDashboard
|
615
|
+
|
616
|
+
include PageObject
|
617
|
+
include GlobalMethods
|
618
|
+
include HeaderFooterBar
|
619
|
+
include LeftMenuBarYou
|
620
|
+
include ChangePicturePopUp # TODO ... Rethink including this by default
|
621
|
+
|
622
|
+
# Page Objects
|
623
|
+
button(:edit_layout, :text=>"Edit layout")
|
624
|
+
radio_button(:one_column, :id=>"layout-picker-onecolumn")
|
625
|
+
radio_button(:two_column, :id=>"layout-picker-dev")
|
626
|
+
radio_button(:three_column, :id=>"layout-picker-threecolumn")
|
627
|
+
button(:save_layout, :id=>"select-layout-finished")
|
628
|
+
button(:add_widgets_button, :class=>/dashboard_global_add_widget/) # Do not use for clicking the button. See custom methods
|
629
|
+
image(:profile_pic, :id=>"entity_profile_picture")
|
630
|
+
div(:my_name, :class=>"s3d-bold entity_name_me")
|
631
|
+
|
632
|
+
#div(:page_title, :class=>"s3d-contentpage-title")
|
633
|
+
|
634
|
+
# Custom Methods...
|
635
|
+
|
636
|
+
# Returns the text contents of the page title div
|
637
|
+
def page_title
|
638
|
+
self.div(:id=>"s3d-page-container").div(:class=>"s3d-contentpage-title").text
|
639
|
+
end
|
640
|
+
|
641
|
+
# Clicks the 'Add widget' button, waits for the page to load,
|
642
|
+
# then includes the AddRemoveWidgets module in the class.
|
643
|
+
# Note that this method is specifically "add_widgets" because
|
644
|
+
# otherwise there would be a method collision with the "add widget"
|
645
|
+
# method in the AddRemoveWidgets module.
|
646
|
+
def add_widgets
|
647
|
+
self.add_widgets_button
|
648
|
+
self.wait_until { self.text.include? "Add widgets" }
|
649
|
+
self.class.class_eval { include AddRemoveWidgets }
|
650
|
+
end
|
651
|
+
|
652
|
+
# Returns an array object containing a list of all selected widgets.
|
653
|
+
def displayed_widgets
|
654
|
+
list = []
|
655
|
+
self.div(:class=>"fl-container-flex widget-content").divs(:class=>"s3d-contentpage-title").each do |div|
|
656
|
+
list << div.text
|
657
|
+
end
|
658
|
+
return list
|
659
|
+
end
|
660
|
+
|
661
|
+
# Returns the name of the recent membership item displayed.
|
662
|
+
def recent_membership_item
|
663
|
+
self.div(:class=>"recentmemberships_widget").link(:class=>/recentmemberships_item_link/).text
|
664
|
+
end
|
665
|
+
|
666
|
+
# Clicks on the membership item displayed in the Recent Memberships widget.
|
667
|
+
# Then returns the Library class object.
|
668
|
+
def go_to_most_recent_membership
|
669
|
+
self.link(:class=>"recentmemberships_item_link s3d-widget-links s3d-bold").click
|
670
|
+
sleep 2 # The next line sometimes throws an "unknown javascript error" without this line.
|
671
|
+
self.wait_for_ajax(2)
|
672
|
+
Library.new @browser
|
673
|
+
end
|
674
|
+
|
675
|
+
# Returns an array containing all the items listed in the "My memberships" dashboard widget.
|
676
|
+
def my_memberships_list
|
677
|
+
list = []
|
678
|
+
self.ul(:class=>"mygroup_items_list").spans(:class=>"mygroups_ellipsis_text").each { |span| list << span.text }
|
679
|
+
return list
|
680
|
+
end
|
681
|
+
|
682
|
+
end
|
683
|
+
|
684
|
+
# Methods related to the My Messages pages.
|
685
|
+
class MyMessages
|
686
|
+
|
687
|
+
include PageObject
|
688
|
+
include GlobalMethods
|
689
|
+
include HeaderFooterBar
|
690
|
+
include LeftMenuBarYou
|
691
|
+
|
692
|
+
# Page Objects
|
693
|
+
button(:accept_invitation, :text=>"Accept invitation")
|
694
|
+
button(:ignore_invitation, :text=>"Ignore invitation")
|
695
|
+
|
696
|
+
# Custom Methods...
|
697
|
+
|
698
|
+
# Returns the text of the displayed page title
|
699
|
+
def page_title
|
700
|
+
active_div.span(:id=>"inbox_box_title").text
|
701
|
+
end
|
702
|
+
|
703
|
+
# Clicks the "Compose message" button on any of the
|
704
|
+
# My messages pages.
|
705
|
+
def compose_message
|
706
|
+
active_div.link(:id=>"inbox_create_new_message").click
|
707
|
+
self.wait_for_ajax(2)
|
708
|
+
self.class.class_eval { include SendMessagePopUp }
|
709
|
+
end
|
710
|
+
|
711
|
+
# Returns an Array containing the list of Email subjects.
|
712
|
+
def message_subjects
|
713
|
+
list = []
|
714
|
+
active_div.divs(:class=>"inbox_subject").each do |div|
|
715
|
+
list << div.text
|
716
|
+
end
|
717
|
+
return list
|
718
|
+
end
|
719
|
+
|
720
|
+
# Clicks on the specified email to open it for reading
|
721
|
+
def open_message(subject)
|
722
|
+
active_div.div(:class=>"inbox_subject").link(:text=>subject).click
|
723
|
+
self.wait_for_ajax(2)
|
724
|
+
self.class.class_eval { include SendMessagePopUp }
|
725
|
+
end
|
726
|
+
|
727
|
+
# Clicks the Delete button for the specified message (specified by Subject), then
|
728
|
+
# waits for Ajax calls to complete.
|
729
|
+
def delete_message(subject)
|
730
|
+
subject_div = active_div.div(:class=>"inbox_subject", :text=>subject)
|
731
|
+
subject_div.parent.parent.parent.button(:title=>"Delete message").click
|
732
|
+
self.wait_for_ajax
|
733
|
+
end
|
734
|
+
|
735
|
+
# Clicks the Reply button for the specified (by Subject) message
|
736
|
+
# then waits for the Ajax calls to complete
|
737
|
+
def reply_to_message(subject)
|
738
|
+
subject_div = active_div.div(:class=>"inbox_subject", :text=>subject)
|
739
|
+
subject_div.parent.parent.parent.link(:title=>"Reply").click
|
740
|
+
self.wait_for_ajax
|
741
|
+
self.class.class_eval { include SendMessagePopUp }
|
742
|
+
end
|
743
|
+
alias read_message open_message
|
744
|
+
|
745
|
+
# Message Preview methods...
|
746
|
+
|
747
|
+
# Returns the Sender text for the specified message (by Subject)
|
748
|
+
def preview_sender(subject)
|
749
|
+
message_div(subject).div(:class=>"inbox_name").button.text
|
750
|
+
end
|
751
|
+
|
752
|
+
# Returns the message recipient name for the specified message (by Subject)
|
753
|
+
def preview_recipient(subject)
|
754
|
+
message_div(subject).div(:class=>"inbox_name").span.text
|
755
|
+
end
|
756
|
+
|
757
|
+
# Returns the date/time string for the specified message.
|
758
|
+
def preview_date(subject)
|
759
|
+
message_div(subject).div(:class=>"inbox_date").span.text
|
760
|
+
end
|
761
|
+
|
762
|
+
# Returns the file path and name of the displayed profile pic of the
|
763
|
+
# specified message.
|
764
|
+
def preview_profile_pic(subject)
|
765
|
+
message_div(subject).parent.image(:class=>"person_icon").src
|
766
|
+
end
|
767
|
+
|
768
|
+
# Returns the text of the Body of the specified message.
|
769
|
+
def preview_body(subject)
|
770
|
+
message_div(subject).div(:class=>"inbox_excerpt").text
|
771
|
+
end
|
772
|
+
|
773
|
+
# The New Message page is controlled by the SendMessagePopUp module
|
774
|
+
|
775
|
+
# Read/Reply Page Objects (defined with Watir)
|
776
|
+
|
777
|
+
# Returns the text of the name of the sender of the message being viewed
|
778
|
+
def message_sender
|
779
|
+
active_div.div(:id=>"inbox_show_message").div(:class=>"inbox_name").button(:class=>"s3d-regular-links s3d-link-button s3d-bold personinfo_trigger_click personinfo_trigger").text
|
780
|
+
end
|
781
|
+
|
782
|
+
# Returns the recipient name text. The method assumes you're currently
|
783
|
+
# viewing a message--that you're not looking at a list of messages.
|
784
|
+
def message_recipient
|
785
|
+
active_div.div(:class, "inbox_name").span(:class=>"inbox_to_list").text
|
786
|
+
end
|
787
|
+
|
788
|
+
# Returns the date of the message being viewed (as a String object)
|
789
|
+
def message_date
|
790
|
+
active_div.div(:id=>"inbox_show_message").div(:class=>"inbox_date").span.text
|
791
|
+
end
|
792
|
+
|
793
|
+
# Returns the text of the message subject. The method assumes you're currently
|
794
|
+
# viewing a message--that you're not looking at a list of messages.
|
795
|
+
def message_subject
|
796
|
+
active_div.div(:id=>"inbox_show_message").div(:class=>"inbox_subject").link.text
|
797
|
+
end
|
798
|
+
|
799
|
+
# Returns the text of the message body.The method assumes you're currently
|
800
|
+
# viewing a message--that you're not looking at a list of messages.
|
801
|
+
def message_body
|
802
|
+
active_div.div(:id=>"inbox_show_message").div(:class=>"inbox_excerpt").text
|
803
|
+
end
|
804
|
+
|
805
|
+
# The page element for the "Delete selected" button.
|
806
|
+
def delete_selected_element
|
807
|
+
active_div.button(:id=>"inbox_delete_selected")
|
808
|
+
end
|
809
|
+
|
810
|
+
# Clicks the "Delete selected" buton and waits for Ajax calls to complete
|
811
|
+
def delete_selected
|
812
|
+
delete_selected_element.click
|
813
|
+
self.wait_for_ajax
|
814
|
+
end
|
815
|
+
|
816
|
+
# The page element for the "Mark as read" button
|
817
|
+
def mark_as_read_element
|
818
|
+
active_div.button(:id=>"inbox_mark_as_read")
|
819
|
+
end
|
820
|
+
|
821
|
+
# Clicks the "Mark as read" button, then waits for Ajax calls to complete
|
822
|
+
def mark_as_read
|
823
|
+
mark_as_read_element.click
|
824
|
+
sleep 0.3
|
825
|
+
self.wait_for_ajax
|
826
|
+
end
|
827
|
+
|
828
|
+
# Checks the checkbox for the specified message in the list.
|
829
|
+
def select_message(subject)
|
830
|
+
subject_div = active_div.div(:class=>"inbox_subject", :text=>subject)
|
831
|
+
subject_div.parent.parent.parent.checkbox.set
|
832
|
+
end
|
833
|
+
|
834
|
+
# Returns "read" or "unread", based on the relevant div's class setting.
|
835
|
+
def message_status(subject)
|
836
|
+
classname = self.div(:class=>/^inbox_item fl-container fl-fix/, :text=>/#{Regexp.escape(subject)}/).class_name
|
837
|
+
if classname =~ /unread/
|
838
|
+
return "unread"
|
839
|
+
else
|
840
|
+
return "read"
|
841
|
+
end
|
842
|
+
end
|
843
|
+
|
844
|
+
# Counts the number of displayed messages on the current page.
|
845
|
+
# Returns a hash containing counts for read and unread messages on the page.
|
846
|
+
# Keys in the hash are :all, :read, and :unread.
|
847
|
+
def message_counts
|
848
|
+
hash = {}
|
849
|
+
total = active_div.divs(:class=>"inbox_items_inner").length
|
850
|
+
unread = active_div.divs(:class=>/unread/).length
|
851
|
+
read = total - unread
|
852
|
+
hash.store(:total, total)
|
853
|
+
hash.store(:unread, unread)
|
854
|
+
hash.store(:read, read)
|
855
|
+
return hash
|
856
|
+
end
|
857
|
+
|
858
|
+
# Search
|
859
|
+
|
860
|
+
# The page element for the Search text field.
|
861
|
+
def search_field_element
|
862
|
+
active_div.text_field(:id=>"inbox_search_messages")
|
863
|
+
end
|
864
|
+
|
865
|
+
# Enters the specified text in the Search text box.
|
866
|
+
# Note that it appends a line feed on the end of the text
|
867
|
+
# so that the search happens immediately. Thus there is
|
868
|
+
# no need for a second line in the test script to
|
869
|
+
# specify clicking the Search button.
|
870
|
+
def search_messages=(text)
|
871
|
+
search_field_element.set(text+"\n")
|
872
|
+
begin
|
873
|
+
active_div.p(:id=>"inbox_search_term").wait_until_present(3)
|
874
|
+
rescue
|
875
|
+
# do nothing because the result set might be empty
|
876
|
+
end
|
877
|
+
end
|
878
|
+
|
879
|
+
# The page element for the Search button
|
880
|
+
def search_button_element
|
881
|
+
active_div.button(:class=>"s3d-button s3d-overlay-button s3d-search-button")
|
882
|
+
end
|
883
|
+
|
884
|
+
# Clicks the Search button and waits for Ajax calls to complete
|
885
|
+
def search_messages
|
886
|
+
search_button_element.click
|
887
|
+
self.wait_for_ajax
|
888
|
+
end
|
889
|
+
|
890
|
+
# Private Methods in this class
|
891
|
+
private
|
892
|
+
|
893
|
+
# Determines which sub page is currently active,
|
894
|
+
# so that all the other methods in the class will work
|
895
|
+
# properly.
|
896
|
+
def active_div
|
897
|
+
container_div = self.div(:id=>"s3d-page-container")
|
898
|
+
id = "x"
|
899
|
+
mail_divs = container_div.divs(:id=>"inbox_widget")
|
900
|
+
if mail_divs.length == 1
|
901
|
+
id = mail_divs[0].id
|
902
|
+
else
|
903
|
+
mail_divs.each do |div|
|
904
|
+
if div.visible?
|
905
|
+
id = div.parent.parent.parent.id
|
906
|
+
break
|
907
|
+
end
|
908
|
+
end
|
909
|
+
end
|
910
|
+
if id == "x"
|
911
|
+
puts "==========================="
|
912
|
+
puts "Couldn't find the visible inbox widget div, so didn't get a valid ID"
|
913
|
+
puts "==========================="
|
914
|
+
end
|
915
|
+
return self.div(:id=>id)
|
916
|
+
end
|
917
|
+
|
918
|
+
# Uses the specified subject text to determine which message div to
|
919
|
+
# use.
|
920
|
+
def message_div(subject)
|
921
|
+
active_div.div(:class=>"inbox_subject",:text=>subject).parent.parent
|
922
|
+
end
|
923
|
+
|
924
|
+
end
|
925
|
+
|
926
|
+
# Methods related to the Basic Info page in My Profile
|
927
|
+
class MyProfileBasicInfo
|
928
|
+
|
929
|
+
include PageObject
|
930
|
+
include GlobalMethods
|
931
|
+
include HeaderFooterBar
|
932
|
+
include LeftMenuBarYou
|
933
|
+
|
934
|
+
# Basic Information
|
935
|
+
text_field(:given_name, :name=>"firstName")
|
936
|
+
text_field(:family_name, :id=>"lastName")
|
937
|
+
text_field(:preferred_name, :id=>"preferredName")
|
938
|
+
text_area(:tags, :name=>"tags")
|
939
|
+
span(:first_name_error, :id=>"firstName_error")
|
940
|
+
span(:last_name_error, :id=>"lastName_error")
|
941
|
+
|
942
|
+
# Clicks the "List categories" button and waits for the
|
943
|
+
# pop-up dialog to load. Includes the AddRemoveCategories
|
944
|
+
# module in the class object.
|
945
|
+
def list_categories
|
946
|
+
self.form(:id=>"displayprofilesection_form_basic").button(:text=>"List categories").click
|
947
|
+
self.wait_for_ajax(3)
|
948
|
+
self.class.class_eval { include AddRemoveCategories }
|
949
|
+
end
|
950
|
+
|
951
|
+
# Returns an array consisting of the contents of the Tags and Categories
|
952
|
+
# field. Returns the categories split up among children and parents.
|
953
|
+
# In other words, for example, "Engineering & Technology » Computer Engineering"
|
954
|
+
# becomes "Engineering & Technology", "Computer Engineering"
|
955
|
+
def categories
|
956
|
+
list = []
|
957
|
+
self.form(:id=>"displayprofilesection_form_basic").ul(:class=>"as-selections").lis.each do |li|
|
958
|
+
next if li.text == ""
|
959
|
+
string = li.text[/(?<=\n).+/]
|
960
|
+
split = string.split(" » ")
|
961
|
+
list << split
|
962
|
+
end
|
963
|
+
list.flatten!
|
964
|
+
list.uniq!
|
965
|
+
return list
|
966
|
+
end
|
967
|
+
|
968
|
+
# Removes the specified category from the list
|
969
|
+
def remove_category(name)
|
970
|
+
self.form(:id=>"displayprofilesection_form_basic").li(:text=>/#{Regexp.escape(name)}/).link(:class=>"as-close").click
|
971
|
+
self.wait_for_ajax
|
972
|
+
end
|
973
|
+
|
974
|
+
# Clicks the "Update" button and waits for any Ajax calls
|
975
|
+
# to complete.
|
976
|
+
def update
|
977
|
+
self.form(:id=>"displayprofilesection_form_basic").button(:text=>"Update").click
|
978
|
+
self.wait_for_ajax(2)
|
979
|
+
end
|
980
|
+
|
981
|
+
end
|
982
|
+
|
983
|
+
# Methods related to the About Me page in My Profile
|
984
|
+
class MyProfileAboutMe
|
985
|
+
|
986
|
+
include PageObject
|
987
|
+
include GlobalMethods
|
988
|
+
include HeaderFooterBar
|
989
|
+
include LeftMenuBarYou
|
990
|
+
|
991
|
+
text_area(:about_Me, :id=>"aboutme")
|
992
|
+
text_area(:academic_interests, :id=>"academicinterests")
|
993
|
+
text_area(:personal_interests, :id=>"personalinterests")
|
994
|
+
|
995
|
+
# Clicks the "Update" button and waits for any Ajax calls
|
996
|
+
# to complete.
|
997
|
+
def update
|
998
|
+
self.form(:id=>"displayprofilesection_form_aboutme").button(:text=>"Update").click
|
999
|
+
self.wait_for_ajax(2)
|
1000
|
+
end
|
1001
|
+
|
1002
|
+
end
|
1003
|
+
|
1004
|
+
# Methods related to the 'Online' page in My Profile.
|
1005
|
+
class MyProfileOnline
|
1006
|
+
|
1007
|
+
include PageObject
|
1008
|
+
include GlobalMethods
|
1009
|
+
include HeaderFooterBar
|
1010
|
+
include LeftMenuBarYou
|
1011
|
+
|
1012
|
+
button(:add_another_online, :text=>"Add another Online", :id=>"displayprofilesection_add_online")
|
1013
|
+
|
1014
|
+
text_field(:site, :id=>/siteOnline_\d+/, :index=>-1)
|
1015
|
+
text_field(:url, :id=>/urlOnline_\d+/, :index=>-1)
|
1016
|
+
|
1017
|
+
# Clicks the "Update" button and waits for any Ajax calls
|
1018
|
+
# to complete.
|
1019
|
+
def update
|
1020
|
+
self.form(:id=>"displayprofilesection_form_online").button(:text=>"Update").click
|
1021
|
+
self.wait_for_ajax
|
1022
|
+
end
|
1023
|
+
|
1024
|
+
# Returns a hash object, where the keys are the site names and the
|
1025
|
+
# values are the urls.
|
1026
|
+
def sites_list
|
1027
|
+
hash = {}
|
1028
|
+
self.div(:id=>"displayprofilesection_sections_online").divs(:class=>"s3d-form-field-wrapper").each do |div|
|
1029
|
+
hash.store(div.text_field(:id=>/siteOnline_/).value, div.text_field(:id=>/urlOnline_/).value)
|
1030
|
+
end
|
1031
|
+
return hash
|
1032
|
+
end
|
1033
|
+
|
1034
|
+
# Removes the specified Online record from the list by clicking its
|
1035
|
+
# "Remove this Online" button.
|
1036
|
+
def remove_this_online(text)
|
1037
|
+
#find the div...
|
1038
|
+
div = self.div(:class=>"online").text_field(:value=>text).parent.parent.parent
|
1039
|
+
#click the button
|
1040
|
+
div.button(:id=>/displayprofilesection_remove_link_/).click
|
1041
|
+
self.wait_for_ajax
|
1042
|
+
end
|
1043
|
+
end
|
1044
|
+
|
1045
|
+
# Methods related to the My Profile: Contact Information page
|
1046
|
+
class MyProfileContactInfo
|
1047
|
+
|
1048
|
+
include PageObject
|
1049
|
+
include GlobalMethods
|
1050
|
+
include HeaderFooterBar
|
1051
|
+
include LeftMenuBarYou
|
1052
|
+
|
1053
|
+
# Page Objects
|
1054
|
+
#button(:add_another, :text=>"Add another", :id=>/profilesection_add_link_\d/)
|
1055
|
+
text_field(:institution, :id=>"college", :name=>"college")
|
1056
|
+
text_field(:department, :id=>"department", :name=>"department")
|
1057
|
+
text_field(:title_role, :name=>"role")
|
1058
|
+
text_field(:role, :name=>"role")
|
1059
|
+
text_field(:email, :name=>"emailContact")
|
1060
|
+
text_field(:instant_messaging, :name=>"imContact")
|
1061
|
+
text_field(:phone, :name=>"phoneContact")
|
1062
|
+
text_field(:mobile, :name=>"mobileContact")
|
1063
|
+
text_field(:fax, :name=>"faxContact")
|
1064
|
+
text_field(:address, :name=>"addressContact")
|
1065
|
+
text_field(:city, :id=>"cityContact")
|
1066
|
+
text_field(:state, :name=>"stateContact")
|
1067
|
+
text_field(:postal_code, :name=>"postalContact")
|
1068
|
+
text_field(:country, :name=>"countryContact")
|
1069
|
+
|
1070
|
+
# Custom Methods
|
1071
|
+
|
1072
|
+
# Clicks the "Update" button and waits for any Ajax calls
|
1073
|
+
# to complete.
|
1074
|
+
def update
|
1075
|
+
self.form(:id=>"displayprofilesection_form_contact").button(:text=>"Update").click
|
1076
|
+
self.wait_for_ajax(2)
|
1077
|
+
end
|
1078
|
+
|
1079
|
+
# Takes a hash object and uses it to fill out
|
1080
|
+
# the fields on the form. The necessary key values in the
|
1081
|
+
# hash argument are as follows:
|
1082
|
+
# :institution, :department, :title, :email, :im, :phone,
|
1083
|
+
# :mobile, :fax, :address, :city, :state, :zip, and :country.
|
1084
|
+
# Any keys that are different or missing will be ignored.
|
1085
|
+
def fill_out_form(hash)
|
1086
|
+
self.email=hash[:email]
|
1087
|
+
self.fax=hash[:fax]
|
1088
|
+
self.institution=hash[:institution]
|
1089
|
+
self.department=hash[:department]
|
1090
|
+
self.title_role=hash[:title]
|
1091
|
+
self.instant_messaging=hash[:im]
|
1092
|
+
self.phone=hash[:phone]
|
1093
|
+
self.mobile=hash[:mobile]
|
1094
|
+
self.address=hash[:address]
|
1095
|
+
self.city=hash[:city]
|
1096
|
+
self.state=hash[:state]
|
1097
|
+
self.postal_code=hash[:zip]
|
1098
|
+
self.country=hash[:country]
|
1099
|
+
end
|
1100
|
+
|
1101
|
+
end
|
1102
|
+
|
1103
|
+
# Publications
|
1104
|
+
class MyProfilePublications
|
1105
|
+
|
1106
|
+
include PageObject
|
1107
|
+
include GlobalMethods
|
1108
|
+
include HeaderFooterBar
|
1109
|
+
include LeftMenuBarYou
|
1110
|
+
|
1111
|
+
# Page Objects
|
1112
|
+
|
1113
|
+
button(:add_another_publication, :text=>"Add another publication", :id=>"displayprofilesection_add_publications")
|
1114
|
+
text_field(:main_title, :id=>/maintitle_\d+/, :index=>-1)
|
1115
|
+
text_field(:main_author, :name=>/mainauthor_\d+/, :index=>-1)
|
1116
|
+
text_field(:co_authors, :id=>/coauthor_\d+/, :index=>-1)
|
1117
|
+
text_field(:publisher, :id=>/publisher_\d+/, :index=>-1)
|
1118
|
+
text_field(:place_of_publication, :id=>/placeofpublication_\d+/, :index=>-1)
|
1119
|
+
text_field(:volume_title, :id=>/volumetitle_\d+/, :index=>-1)
|
1120
|
+
text_field(:volume_information, :id=>/volumeinformation_\d+/, :index=>-1)
|
1121
|
+
text_field(:year, :id=>/year_\d+/, :index=>-1)
|
1122
|
+
text_field(:number, :id=>/number_\d+/, :index=>-1)
|
1123
|
+
text_field(:series_title, :id=>/series.title_\d+/, :index=>-1)
|
1124
|
+
text_field(:url, :id=>/url_\d+/, :index=>-1)
|
1125
|
+
span(:title_error, :id=>/maintitle_\d+_error/)
|
1126
|
+
span(:author_error, :id=>/mainauthor_\d+_error/)
|
1127
|
+
span(:publisher_error, :id=>/publisher_\d+_error/)
|
1128
|
+
span(:place_error, :id=>/placeofpublication_\d+_error/)
|
1129
|
+
span(:year_error, :id=>/year_\d+_error/)
|
1130
|
+
span(:url_error, :id=>/url_\d+_error/)
|
1131
|
+
|
1132
|
+
# Custom Methods...
|
1133
|
+
|
1134
|
+
# Clicks the "Update" button and waits for any Ajax calls
|
1135
|
+
# to complete.
|
1136
|
+
def update
|
1137
|
+
self.form(:id=>"displayprofilesection_form_publications").button(:text=>"Update").click
|
1138
|
+
self.wait_for_ajax(2)
|
1139
|
+
end
|
1140
|
+
|
1141
|
+
# Takes a hash object and uses it to fill
|
1142
|
+
# in all the publication fields. The key values
|
1143
|
+
# that the has must contain are as follows:
|
1144
|
+
# :main_title, :main_author, :co_authors, :publisher,
|
1145
|
+
# :place, :volume_title, :volume_info, :year, :number,
|
1146
|
+
# :series, :url.
|
1147
|
+
# Any missing or misspelled key values will be ignored.
|
1148
|
+
def fill_out_form(hash)
|
1149
|
+
self.main_title=hash[:main_title]
|
1150
|
+
self.main_author=hash[:main_author]
|
1151
|
+
self.co_authors=hash[:co_authors]
|
1152
|
+
self.publisher=hash[:publisher]
|
1153
|
+
self.place_of_publication=hash[:place]
|
1154
|
+
self.volume_title=hash[:volume_title]
|
1155
|
+
self.volume_information=hash[:volume_info]
|
1156
|
+
self.year=hash[:year]
|
1157
|
+
self.number=hash[:number]
|
1158
|
+
self.series_title=hash[:series]
|
1159
|
+
self.url=hash[:url]
|
1160
|
+
end
|
1161
|
+
|
1162
|
+
# Clicks the "Remove this publication" link for the publication specified (by
|
1163
|
+
# the Main title of the record in question).
|
1164
|
+
def remove_this_publication(main_title)
|
1165
|
+
target_div_id = self.text_field(:value=>main_title).parent.parent.parent.id
|
1166
|
+
self.div(:id=>target_div_id).button(:id=>/displayprofilesection_remove_link_/).click
|
1167
|
+
self.wait_for_ajax
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
# Returns an array containing all of the titles of the publications that exist
|
1171
|
+
# on the page.
|
1172
|
+
def publication_titles
|
1173
|
+
array = []
|
1174
|
+
self.div(:id=>"displayprofilesection_sections_publications").text_fields(:id=>/maintitle_/).each { |field| array << field.value }
|
1175
|
+
return array
|
1176
|
+
end
|
1177
|
+
alias titles publication_titles
|
1178
|
+
|
1179
|
+
# Returns an array of hashes. Each hash in the array refers to one of
|
1180
|
+
# the listed publications.
|
1181
|
+
#
|
1182
|
+
# Each hash's key=>value pairs are determined by the field title and field values for the publications.
|
1183
|
+
#
|
1184
|
+
# Example: "Main title:"=>"War and Peace","Main author:"=>"Tolstoy", etc....
|
1185
|
+
def publications_data
|
1186
|
+
list = []
|
1187
|
+
self.div(:id=>"displayprofilesection_sections_publications").divs(:class=>"displayprofilesection_multiple_section").each do |div|
|
1188
|
+
hash = {}
|
1189
|
+
div.divs(:class=>"displayprofilesection_field").each { |subdiv| hash.store(subdiv.label(:class=>"s3d-input-label").text, subdiv.text_field.value) }
|
1190
|
+
list << hash
|
1191
|
+
end
|
1192
|
+
return list
|
1193
|
+
end
|
1194
|
+
alias publication_data publications_data
|
1195
|
+
alias publications_list publications_data
|
1196
|
+
|
1197
|
+
end
|
1198
|
+
|
1199
|
+
# Methods related to the User's My Library page.
|
1200
|
+
class MyLibrary
|
1201
|
+
|
1202
|
+
include PageObject
|
1203
|
+
include GlobalMethods
|
1204
|
+
include HeaderFooterBar
|
1205
|
+
include ListWidget
|
1206
|
+
include ListContent
|
1207
|
+
include LeftMenuBarYou
|
1208
|
+
include LibraryWidget
|
1209
|
+
|
1210
|
+
# Page Objects
|
1211
|
+
button(:empty_library_add_content_button, :id=>"mylibrary_addcontent")
|
1212
|
+
|
1213
|
+
# Custom Methods and Page Objects...
|
1214
|
+
|
1215
|
+
# Defines the div that displays when the library has no contents.
|
1216
|
+
# Useful for testing whether the library is empty or not.
|
1217
|
+
def empty_library
|
1218
|
+
active_div.div(:id=>"mylibrary_empty")
|
1219
|
+
end
|
1220
|
+
|
1221
|
+
# Returns the text of the Page Title.
|
1222
|
+
def page_title
|
1223
|
+
active_div.div(:class=>"s3d-contentpage-title").text
|
1224
|
+
end
|
1225
|
+
|
1226
|
+
# Private methods...
|
1227
|
+
private
|
1228
|
+
|
1229
|
+
def active_div
|
1230
|
+
id = self.div(:id=>/mylibrarycontainer/).parent.id
|
1231
|
+
return self.div(:id=>id)
|
1232
|
+
end
|
1233
|
+
|
1234
|
+
end
|
1235
|
+
|
1236
|
+
# Methods related to the User's My Memberships page.
|
1237
|
+
class MyMemberships
|
1238
|
+
|
1239
|
+
include PageObject
|
1240
|
+
include GlobalMethods
|
1241
|
+
include HeaderFooterBar
|
1242
|
+
include ListWidget
|
1243
|
+
include ListGroups
|
1244
|
+
include LeftMenuBarYou
|
1245
|
+
|
1246
|
+
# Page Objects
|
1247
|
+
text_field(:search_memberships_field, :id=>"mymemberships_livefilter")
|
1248
|
+
select_list(:sort_by_list, :id=>"mymemberships_sortby")
|
1249
|
+
div(:gridview_button, :class=>/search-results-gridview/)
|
1250
|
+
div(:listview_button, :class=>/search-results-listview/)
|
1251
|
+
checkbox(:select_all, :id=>"mymemberships_select_checkbox")
|
1252
|
+
button(:add_selected_button, :id=>"mymemberships_addpeople_button") # Don't use this method. Use the custom one defined below
|
1253
|
+
button(:message_selected_button, :id=>"mymemberships_message_button") # Don't use this method. Use the custom one defined below
|
1254
|
+
link(:create_a_new_group_button, :text=>"Create a new group") # Don't use this method. Use the custom one defined below
|
1255
|
+
|
1256
|
+
# Custom methods...
|
1257
|
+
|
1258
|
+
# Returns the text of the Page Title.
|
1259
|
+
def page_title
|
1260
|
+
active_div.div(:class=>"s3d-contentpage-title").text
|
1261
|
+
end
|
1262
|
+
|
1263
|
+
# Returns the div object for the blue quote bubble that
|
1264
|
+
# appears when the page is empty of memberships.
|
1265
|
+
def quote_bubble
|
1266
|
+
active_div.div(:class=>"s3d-no-results-container")
|
1267
|
+
end
|
1268
|
+
|
1269
|
+
# Clicks the "Create one" link in the quote_bubble
|
1270
|
+
# div and then returns the CreateGroups class object.
|
1271
|
+
def create_one
|
1272
|
+
quote_bubble.link(:text=>"Create one").click
|
1273
|
+
self.wait_until { self.button(:class=>/create_simple_group/).present? }
|
1274
|
+
CreateGroups.new @browser
|
1275
|
+
end
|
1276
|
+
|
1277
|
+
# Sorts the list of memberships according to the specified value
|
1278
|
+
def sort_by=(type)
|
1279
|
+
self.sort_by_list=type
|
1280
|
+
self.wait_for_ajax
|
1281
|
+
end
|
1282
|
+
|
1283
|
+
# Private methods...
|
1284
|
+
private
|
1285
|
+
|
1286
|
+
def active_div
|
1287
|
+
id = self.div(:id=>/mymembershipscontainer/).parent.id
|
1288
|
+
return self.div(:id=>id)
|
1289
|
+
end
|
1290
|
+
|
1291
|
+
end
|
1292
|
+
|
1293
|
+
# Page Objects and Custom Methods for the My Contacts page.
|
1294
|
+
class MyContacts
|
1295
|
+
|
1296
|
+
include PageObject
|
1297
|
+
include GlobalMethods
|
1298
|
+
include HeaderFooterBar
|
1299
|
+
include ListWidget
|
1300
|
+
include ListPeople
|
1301
|
+
include LeftMenuBarYou
|
1302
|
+
|
1303
|
+
# Page Objects
|
1304
|
+
navigating_link(:find_and_add_people, "Find and add people", "ExplorePeople")
|
1305
|
+
|
1306
|
+
# Page Objects defined with Watir-webdriver...
|
1307
|
+
|
1308
|
+
# The text of the Page Title. It should usually return "My contacts".
|
1309
|
+
def page_title
|
1310
|
+
active_div.div(:class=>"s3d-contentpage-title").text
|
1311
|
+
end
|
1312
|
+
|
1313
|
+
# This returns the the "contacts_invited" page div
|
1314
|
+
# (as a Watir page object). This can be used to validate
|
1315
|
+
# that there are pending invites on the page. Primarily, however, it's used
|
1316
|
+
# by other methods in this class--to differentiate the pending list
|
1317
|
+
# from the contacts list.
|
1318
|
+
def pending_contacts
|
1319
|
+
active_div.div(:id=>"contacts_invited")
|
1320
|
+
end
|
1321
|
+
|
1322
|
+
# Custom Methods...
|
1323
|
+
|
1324
|
+
# Returns the list of names of people requesting
|
1325
|
+
# contact
|
1326
|
+
def pending_invites
|
1327
|
+
list = []
|
1328
|
+
pending_contacts.links(:class=>"s3d-bold s3d-regular-light-links", :title=>/View/).each { |link| list << link.text }
|
1329
|
+
return list
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
# Clicks the "accept connection" plus sign for the specified
|
1333
|
+
# person in the Pending contacts list
|
1334
|
+
def accept_connection(name)
|
1335
|
+
self.li(:text=>/#{Regexp.escape(name)}/).button(:title=>"Accept connection").click
|
1336
|
+
sleep 0.8
|
1337
|
+
self.wait_for_ajax(2)
|
1338
|
+
end
|
1339
|
+
|
1340
|
+
# Clicks the "Find more people" link, then returns
|
1341
|
+
# the ExplorePeople class object.
|
1342
|
+
def find_more_people
|
1343
|
+
active_div.link(:text=>"Find more people").click
|
1344
|
+
self.wait_for_ajax
|
1345
|
+
ExplorePeople.new @browser
|
1346
|
+
end
|
1347
|
+
|
1348
|
+
# Private methods...
|
1349
|
+
private
|
1350
|
+
|
1351
|
+
# The top div for the contents of the page.
|
1352
|
+
# This method is a helper method for other objects
|
1353
|
+
# defined on the page.
|
1354
|
+
def active_div
|
1355
|
+
self.div(:id=>/^contactscontainer\d+/)
|
1356
|
+
end
|
1357
|
+
|
1358
|
+
end
|
1359
|
+
|
1360
|
+
# Methods related to the page for viewing a User's profile
|
1361
|
+
class ViewPerson
|
1362
|
+
|
1363
|
+
include PageObject
|
1364
|
+
include GlobalMethods
|
1365
|
+
include HeaderFooterBar
|
1366
|
+
include LeftMenuBarYou
|
1367
|
+
|
1368
|
+
# Page Objects
|
1369
|
+
navigating_link(:basic_information, "Basic Information", "ViewPerson")
|
1370
|
+
navigating_link(:categories, "Categories", "ViewPerson")
|
1371
|
+
navigating_link(:about_me, "About Me", "ViewPerson")
|
1372
|
+
navigating_link(:online, "Online", "ViewPerson")
|
1373
|
+
navigating_link(:contact_information, "Contact Information", "ViewPerson")
|
1374
|
+
navigating_link(:publications, "Publications", "ViewPerson")
|
1375
|
+
div(:contact_name, :id=>"entity_name")
|
1376
|
+
button(:message_button, :id=>"entity_user_message")
|
1377
|
+
button(:add_to_contacts_button, :id=>"entity_user_add_to_contacts")
|
1378
|
+
button(:invitation_sent_button, :text=>"Contact invitation sent")
|
1379
|
+
button(:accept_invitation_button, :id=>"entity_user_accept_invitation")
|
1380
|
+
|
1381
|
+
# Custom Methods...
|
1382
|
+
|
1383
|
+
# Clicks the "Accept invitation" button and waits for any Ajax calls
|
1384
|
+
# to complete
|
1385
|
+
def accept_invitation
|
1386
|
+
self.accept_invitation_button
|
1387
|
+
self.wait_for_ajax(2)
|
1388
|
+
end
|
1389
|
+
|
1390
|
+
# Clicks the message button and waits for the Pop-up dialog to appear.
|
1391
|
+
def message
|
1392
|
+
self.message_button
|
1393
|
+
self.wait_until { self.text.include? "Send this message to:" }
|
1394
|
+
self.class.class_eval { include SendMessagePopUp }
|
1395
|
+
end
|
1396
|
+
|
1397
|
+
# Clicks the "Add to contacts" button and waits for the Pop-up dialog
|
1398
|
+
# to appear.
|
1399
|
+
def add_to_contacts
|
1400
|
+
self.add_to_contacts_button
|
1401
|
+
self.wait_until { @browser.text.include? "Add a personal note to the invitation:" }
|
1402
|
+
self.class.class_eval { include AddToContactsPopUp }
|
1403
|
+
end
|
1404
|
+
|
1405
|
+
# Clicks the link to open the user's library profile page.
|
1406
|
+
def users_library
|
1407
|
+
self.link(:class=>/s3d-bold lhnavigation_toplevel lhnavigation_page_title_value/, :text=>/Library/).click
|
1408
|
+
sleep 2
|
1409
|
+
self.wait_for_ajax(2)
|
1410
|
+
self.class.class_eval { include ListWidget
|
1411
|
+
include LibraryWidget }
|
1412
|
+
end
|
1413
|
+
|
1414
|
+
# I believe this is not supported by rSmart's OAE preview
|
1415
|
+
#def users_memberships
|
1416
|
+
# @browser.link(:class=>/s3d-bold lhnavigation_toplevel lhnavigation_page_title_value/, :text=>/Memberships/).click
|
1417
|
+
# self.class.class_eval { include ListWidget }
|
1418
|
+
#end
|
1419
|
+
|
1420
|
+
# Opens the user's Contacts page to display their contacts
|
1421
|
+
def users_contacts
|
1422
|
+
self.link(:class=>/s3d-bold lhnavigation_toplevel lhnavigation_page_title_value/, :text=>/Contacts/).click
|
1423
|
+
self.class.class_eval { include ListWidget }
|
1424
|
+
end
|
1425
|
+
|
1426
|
+
# This method assumes the current page is the Basic Info page.
|
1427
|
+
# It returns a hash object where the key => value pair is:
|
1428
|
+
# "Field Title" => "Field Value" --e.g.: "Given name:"=>"Billy"
|
1429
|
+
def basic_info_data
|
1430
|
+
hash = {}
|
1431
|
+
target_div = self.div(:class=>"s3d-contentpage-title", :text=>"Basic Information").parent.parent.div(:id=>"displayprofilesection_body")
|
1432
|
+
target_div.divs(:class=>"displayprofilesection_field").each { |div| hash.store(div.span(:class=>"s3d-input-label").text, div.span(:class=>"field_value").text) }
|
1433
|
+
return hash
|
1434
|
+
end
|
1435
|
+
|
1436
|
+
# Returns an array containing the text of all of the tags and categories
|
1437
|
+
# listed on the Basic Information page. Note that it splits them up into
|
1438
|
+
# parent and child categories.
|
1439
|
+
def tags_and_categories_list
|
1440
|
+
list = []
|
1441
|
+
target_div = self.div(:class=>"s3d-contentpage-title", :text=>"Basic Information").parent.parent.div(:id=>"displayprofilesection_body")
|
1442
|
+
target_div.links.each { |link| list << link.text.split(" » ") }
|
1443
|
+
return list.flatten.uniq
|
1444
|
+
end
|
1445
|
+
|
1446
|
+
# This method assumes it will be run on the About Me page.
|
1447
|
+
# It returns a hash object where the key => value pair is:
|
1448
|
+
# "Field Title" => "Field Value" --e.g.: "About Me:"=>"Text of field"
|
1449
|
+
def about_me_data
|
1450
|
+
hash = {}
|
1451
|
+
target_div = self.div(:class=>"s3d-contentpage-title", :text=>"About Me").parent.parent.div(:id=>"displayprofilesection_body")
|
1452
|
+
target_div.divs(:class=>"displayprofilesection_field").each { |div| hash.store(div.span(:class=>"s3d-input-label").text, div.span(:class=>"field_value").text) }
|
1453
|
+
return hash
|
1454
|
+
end
|
1455
|
+
|
1456
|
+
# Returns a hash object where the key=>value pair is determined by the
|
1457
|
+
# field title and field values shown on the "Online" page.
|
1458
|
+
#
|
1459
|
+
# Because there can be an arbitrary number of Sites listed on the page,
|
1460
|
+
# The hash keys will have a number appended on the end. Example:
|
1461
|
+
# { "Site:1"=>"Twitter", "URL:1"=>"www.twitter.com","Site:2"=>"Facebook","URL:2"=>"www.facebook.com"}
|
1462
|
+
def online_data
|
1463
|
+
hash = {}
|
1464
|
+
target_div = self.div(:class=>"s3d-contentpage-title", :text=>"Online").parent.parent.div(:id=>"displayprofilesection_body")
|
1465
|
+
x=0
|
1466
|
+
target_div.divs(:class=>"displayprofilesection_field").each do |div|
|
1467
|
+
div.span(:class=>"s3d-input-label").text == "Site:" ? x+=1 : x
|
1468
|
+
hash.store("#{div.span(:class=>"s3d-input-label").text}#{x}", div.span(:class=>"field_value").text)
|
1469
|
+
end
|
1470
|
+
return hash
|
1471
|
+
end
|
1472
|
+
|
1473
|
+
# Returns a hash object where the key=>value pair is determined by the
|
1474
|
+
# field title and field values shown on the "Contact Information" page.
|
1475
|
+
#
|
1476
|
+
# Example: "Institution:"=>"University of Hard Knocks"
|
1477
|
+
def contact_info_data
|
1478
|
+
hash = {}
|
1479
|
+
target_div = self.div(:class=>"s3d-contentpage-title", :text=>"Contact Information").parent.parent.div(:id=>"displayprofilesection_body")
|
1480
|
+
target_div.divs(:class=>"displayprofilesection_field").each { |div| hash.store(div.span(:class=>"s3d-input-label").text, div.span(:class=>"field_value").text) }
|
1481
|
+
return hash
|
1482
|
+
end
|
1483
|
+
|
1484
|
+
# Takes a hash object containing the test contact info (see required format below),
|
1485
|
+
# evaluates that data against the data returned with the contact_info_data
|
1486
|
+
# method and returns true if the data match, and false, if they do not.
|
1487
|
+
#
|
1488
|
+
# The hash object passed to the method must contain the following keys, exactly:
|
1489
|
+
# :institution, :department, :title, :email, :im, :phone, :mobile, :fax,
|
1490
|
+
# :address, :city, :state, :zip, :country
|
1491
|
+
def expected_contact_info?(hash)
|
1492
|
+
info = self.contact_info_data
|
1493
|
+
key_map = {"Institution:"=>:institution, "Department:"=>:department, "Title/Role:"=>:title,
|
1494
|
+
"Email:"=>:email, "Instant Messaging:"=>:im, "Phone:"=>:phone, "Mobile:"=>:mobile, "Fax:"=>:fax,
|
1495
|
+
"Address:"=>:address, "City:"=>:city, "State:"=>:state, "Postal Code:"=>:zip, "Country:"=>:country}
|
1496
|
+
fixed = Hash[info.map { |k, v| [key_map[k], v ] } ]
|
1497
|
+
fixed==hash ? true : false
|
1498
|
+
end
|
1499
|
+
|
1500
|
+
# Returns an array of hashes. Each hash in the array refers to one of
|
1501
|
+
# the listed publications.
|
1502
|
+
#
|
1503
|
+
# Each hash's key=>value pairs are determined by the field title and field values for the publications.
|
1504
|
+
#
|
1505
|
+
# Example: "Main title:"=>"War and Peace","Main author:"=>"Tolstoy", etc....
|
1506
|
+
def publications_data
|
1507
|
+
list = []
|
1508
|
+
hash = {}
|
1509
|
+
self.div(:class=>"publications").divs(:class=>"displayprofilesection_field").each do |div|
|
1510
|
+
if div.span(:class=>"s3d-input-label").text=="Main title:" && hash != {}
|
1511
|
+
list << hash
|
1512
|
+
hash={}
|
1513
|
+
end
|
1514
|
+
hash.store(div.span(:class=>"s3d-input-label").text, div.span(:class=>"field_value").text)
|
1515
|
+
end
|
1516
|
+
list << hash
|
1517
|
+
return list
|
1518
|
+
end
|
1519
|
+
alias publication_data publications_data
|
1520
|
+
alias publications_list publications_data
|
1521
|
+
|
1522
|
+
# Expects to be passed an array containing one or more hashes (see below for
|
1523
|
+
# how the hash should be formed). Note that it's okay to send a single hash instead
|
1524
|
+
# of an array containing one hash element. The method will do the
|
1525
|
+
# necessary cleaning of the data.
|
1526
|
+
#
|
1527
|
+
# The method returns true if the contents of the hash(es) match the data evaluated against,
|
1528
|
+
# otherwise returns false.
|
1529
|
+
# The hash(es) should contain all of the following keys...
|
1530
|
+
# :main_title, main_author, :co_authors, :publisher, :place, :volume_title,
|
1531
|
+
# :volume_info, :year, :number, :series, :url
|
1532
|
+
def expected_publications_data?(array_of_hash)
|
1533
|
+
expected_data=[]
|
1534
|
+
expected_data << array_of_hash
|
1535
|
+
expected_data.flatten!
|
1536
|
+
data_array = self.publications_data
|
1537
|
+
new_array = []
|
1538
|
+
key_map = { "Main title:"=>:main_title, "Main author:"=>:main_author,
|
1539
|
+
"Co-author(s):"=>:co_authors, "Publisher:"=>:publisher, "Place of publication:"=>:place,
|
1540
|
+
"Volume title:"=>:volume_title, "Volume information:"=>:volume_info,
|
1541
|
+
"Year:"=>:year, "Number:"=>:number, "Series title:"=>:series, "URL:"=>:url }
|
1542
|
+
data_array.each do |hash|
|
1543
|
+
fixed = Hash[hash.map { |k, v| [key_map[k], v ] } ]
|
1544
|
+
new_array << fixed
|
1545
|
+
end
|
1546
|
+
expected_data==new_array ? true : false
|
1547
|
+
end
|
1548
|
+
|
1549
|
+
end
|
1550
|
+
|
1551
|
+
# TODO - describe class
|
1552
|
+
class MyPreferences
|
1553
|
+
|
1554
|
+
include PageObject
|
1555
|
+
include GlobalMethods
|
1556
|
+
|
1557
|
+
end
|
1558
|
+
|
1559
|
+
# Library pages in Courses/Groups/Research
|
1560
|
+
class Library
|
1561
|
+
|
1562
|
+
include PageObject
|
1563
|
+
include GlobalMethods
|
1564
|
+
include HeaderFooterBar
|
1565
|
+
include HeaderBar
|
1566
|
+
include LeftMenuBar
|
1567
|
+
include LibraryWidget
|
1568
|
+
include ListWidget
|
1569
|
+
include ListContent
|
1570
|
+
|
1571
|
+
# The page element that appears when there are no contents
|
1572
|
+
# in the Library
|
1573
|
+
def empty_library_element
|
1574
|
+
self.div(:id=>data_sakai_ref).div(:id=>"mylibrary_empty")
|
1575
|
+
end
|
1576
|
+
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
#
|
1580
|
+
class Participants
|
1581
|
+
|
1582
|
+
include PageObject
|
1583
|
+
include GlobalMethods
|
1584
|
+
include HeaderFooterBar
|
1585
|
+
include LeftMenuBar
|
1586
|
+
include HeaderBar
|
1587
|
+
|
1588
|
+
text_field(:search_participants, :id=>"participants_search_field")
|
1589
|
+
|
1590
|
+
end
|
1591
|
+
|
1592
|
+
# Methods related to the Discussions "Area" in a Group/Course.
|
1593
|
+
class Discussions
|
1594
|
+
|
1595
|
+
include PageObject
|
1596
|
+
include GlobalMethods
|
1597
|
+
include HeaderFooterBar
|
1598
|
+
include LeftMenuBar
|
1599
|
+
include HeaderBar
|
1600
|
+
include DocButtons
|
1601
|
+
|
1602
|
+
button(:add_new_topic, :id=>"discussion_add_new_topic")
|
1603
|
+
text_field(:topic_title, :id=>"discussion_create_new_topic_title")
|
1604
|
+
text_area(:message_text, :name=>/message.text/i)
|
1605
|
+
text_area(:reply_text, :id=>"discussion_topic_reply_text")
|
1606
|
+
button(:dont_add_topic, :id=>"discussion_dont_add_topic")
|
1607
|
+
|
1608
|
+
# Clicks the "Add topic" button.
|
1609
|
+
def add_topic
|
1610
|
+
self.button(:id=>"discussion_add_topic").click
|
1611
|
+
self.wait_for_ajax(2) #
|
1612
|
+
#@browser.wait_until { @browser.h1(:class=>"discussion_topic_subject").parent.button(:text=>"Reply").present? }
|
1613
|
+
end
|
1614
|
+
|
1615
|
+
button(:collapse_all, :text=>"Collapse all")
|
1616
|
+
button(:expand_all, :text=>"Expand all")
|
1617
|
+
button(:dont_add_reply, :id=>"discussion_dont_add_reply")
|
1618
|
+
button(:add_reply, :id=>"discussion_add_reply")
|
1619
|
+
|
1620
|
+
# Clicks the "Reply" button for the specified message.
|
1621
|
+
def reply_to(message_title)
|
1622
|
+
self.h1(:class=>"discussion_topic_subject", :text=>message_title).parent.button(:text=>"Reply").click
|
1623
|
+
self.wait_until { self.h1(:class=>"discussion_topic_subject", :text=>message_title).parent.text_field(:id=>"discussion_topic_reply_text").present? }
|
1624
|
+
end
|
1625
|
+
|
1626
|
+
# Clicks the "Quote" button for the specified message.
|
1627
|
+
def quote(message_text)
|
1628
|
+
self.span(:class=>"discussion_post_message", :text=>message_text).parent.parent.button(:text=>"Quote").fire_event "onclick"
|
1629
|
+
self.wait_for_ajax(2) #
|
1630
|
+
#self.wait_until { self.textarea(:name=>"quoted_text", :text=>message_text).present? }
|
1631
|
+
end
|
1632
|
+
|
1633
|
+
# Clicks the "Edit" button for the specified message.
|
1634
|
+
def edit(message_text)
|
1635
|
+
self.span(:class=>"discussion_post_message", :text=>message_text).parent.parent.button(:text=>"Edit").click
|
1636
|
+
self.wait_until { self.textarea(:name=>"discussion_topic_reply_text", :text=>message_text).present? }
|
1637
|
+
end
|
1638
|
+
|
1639
|
+
# Clicks the "Delete" button for a specified message.
|
1640
|
+
def delete(message_text)
|
1641
|
+
self.span(:class=>"discussion_post_message", :text=>message_text).parent.parent.button(:text=>"Delete").click
|
1642
|
+
end
|
1643
|
+
|
1644
|
+
button(:restore, :text=>"Restore")
|
1645
|
+
|
1646
|
+
# Clicks the button that expands the thread to view the replies.
|
1647
|
+
def view_replies(topic_title)
|
1648
|
+
self.h1(:class=>"discussion_topic_subject", :text=>topic_title).parent.button(:class=>"discussion_show_topic_replies s3d-button s3d-link-button").click
|
1649
|
+
|
1650
|
+
end
|
1651
|
+
|
1652
|
+
# Clicks the button that collapses an expanded message thread.
|
1653
|
+
def hide_replies(topic_title)
|
1654
|
+
self.h1(:class=>"discussion_topic_subject", :text=>topic_title).parent.button(:class=>"discussion_show_topic_replies s3d-button s3d-link-button").click
|
1655
|
+
|
1656
|
+
end
|
1657
|
+
|
1658
|
+
end
|
1659
|
+
|
1660
|
+
# Methods related to the Comments "Area" in a Course/Group.
|
1661
|
+
class Comments
|
1662
|
+
|
1663
|
+
include PageObject
|
1664
|
+
include GlobalMethods
|
1665
|
+
include HeaderFooterBar
|
1666
|
+
include LeftMenuBar
|
1667
|
+
include HeaderBar
|
1668
|
+
include DocButtons
|
1669
|
+
|
1670
|
+
button(:add_comment, :text=>"Add comment")
|
1671
|
+
button(:cancel, :id=>/comments_editComment_cancel/)
|
1672
|
+
text_area(:comment, :id=>"comments_txtMessage")
|
1673
|
+
text_area(:new_comment, :id=>/comments_editComment_txt_/)
|
1674
|
+
button(:undelete, :text=>"Undelete")
|
1675
|
+
|
1676
|
+
# Clicks the "Submit comment" button.
|
1677
|
+
def submit_comment
|
1678
|
+
self.button(:text=>"Submit comment").click
|
1679
|
+
self.wait_for_ajax(2) #wait_until { self.text.include? "about 0 seconds ago" }
|
1680
|
+
end
|
1681
|
+
|
1682
|
+
# Clicks the "Edit comment" button.
|
1683
|
+
def edit_comment
|
1684
|
+
self.button(:text=>"Edit comment").click
|
1685
|
+
self.wait_for_ajax(2) #wait_until { self.textarea(:title=>"Edit your comment").present? == false }
|
1686
|
+
end
|
1687
|
+
|
1688
|
+
# Clicks the "Edit button" for the specified comment.
|
1689
|
+
def edit(comment)
|
1690
|
+
comment.gsub!("\n", " ")
|
1691
|
+
self.p(:text=>comment).parent.parent.button(:text=>"Edit").click
|
1692
|
+
self.wait_for_ajax(2) #wait_until { self.textarea(:title=>"Edit your comment").present? }
|
1693
|
+
end
|
1694
|
+
|
1695
|
+
# Deletes the specified comment.
|
1696
|
+
def delete(comment)
|
1697
|
+
comment.gsub!("\n", " ")
|
1698
|
+
self.div(:text=>comment).parent.button(:text=>"Delete").click
|
1699
|
+
self.wait_for_ajax(2) #wait_until { self.button(:text=>"Undelete").present? }
|
1700
|
+
end
|
1701
|
+
|
1702
|
+
end
|
1703
|
+
|
1704
|
+
# TODO - describe class
|
1705
|
+
class RSS
|
1706
|
+
|
1707
|
+
include PageObject
|
1708
|
+
include GlobalMethods
|
1709
|
+
include HeaderFooterBar
|
1710
|
+
include LeftMenuBar
|
1711
|
+
include HeaderBar
|
1712
|
+
include DocButtons
|
1713
|
+
|
1714
|
+
button(:sort_by_source, :text=>"Sort by source")
|
1715
|
+
button(:sort_by_date, :text=>"Sort by date")
|
1716
|
+
|
1717
|
+
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
# TODO - describe class
|
1721
|
+
class Tests
|
1722
|
+
|
1723
|
+
include PageObject
|
1724
|
+
include GlobalMethods
|
1725
|
+
include HeaderFooterBar
|
1726
|
+
include LeftMenuBar
|
1727
|
+
include HeaderBar
|
1728
|
+
include DocButtons
|
1729
|
+
|
1730
|
+
# The frame object that contains all of the CLE Tests and Quizzes objects
|
1731
|
+
def tests_frame
|
1732
|
+
self.frame(:src=>/sakai2samigo.launch.html/)
|
1733
|
+
end
|
1734
|
+
|
1735
|
+
end
|
1736
|
+
|
1737
|
+
# TODO - describe class
|
1738
|
+
class Files
|
1739
|
+
|
1740
|
+
include PageObject
|
1741
|
+
include GlobalMethods
|
1742
|
+
include HeaderFooterBar
|
1743
|
+
include LeftMenuBar
|
1744
|
+
include HeaderBar
|
1745
|
+
include DocButtons
|
1746
|
+
|
1747
|
+
# Edits the page, then opens the File Settings pop up.
|
1748
|
+
def files_settings
|
1749
|
+
widget_settings
|
1750
|
+
self.text_field(:class=>"as-input").when_present { self.class.class_eval { include FilesAndDocsPopUp } }
|
1751
|
+
end
|
1752
|
+
|
1753
|
+
# TODO - Describe method
|
1754
|
+
def remove_files_widget
|
1755
|
+
remove_widget
|
1756
|
+
end
|
1757
|
+
|
1758
|
+
# TODO - Describe method
|
1759
|
+
def files_wrapping
|
1760
|
+
widget_wrapping
|
1761
|
+
end
|
1762
|
+
|
1763
|
+
end
|
1764
|
+
|
1765
|
+
# Methods related to the Forum page in Courses/Groups
|
1766
|
+
class Forum
|
1767
|
+
|
1768
|
+
include GlobalMethods
|
1769
|
+
include LeftMenuBar
|
1770
|
+
include HeaderBar
|
1771
|
+
include HeaderFooterBar
|
1772
|
+
include DocButtons
|
1773
|
+
|
1774
|
+
# The frame that contains the CLE Forums objects
|
1775
|
+
def forum_frame
|
1776
|
+
self.frame(:src=>/sakai2forums.launch.html/)
|
1777
|
+
end
|
1778
|
+
|
1779
|
+
end
|
1780
|
+
|
1781
|
+
# TODO - describe class
|
1782
|
+
class Gadget
|
1783
|
+
|
1784
|
+
include PageObject
|
1785
|
+
include GlobalMethods
|
1786
|
+
include HeaderFooterBar
|
1787
|
+
include LeftMenuBar
|
1788
|
+
include HeaderBar
|
1789
|
+
include DocButtons
|
1790
|
+
|
1791
|
+
# TODO - Describe method
|
1792
|
+
def gadget_frame
|
1793
|
+
self.frame(:id=>"ggadget_remotecontent_settings_preview_frame")
|
1794
|
+
end
|
1795
|
+
|
1796
|
+
|
1797
|
+
|
1798
|
+
end
|
1799
|
+
|
1800
|
+
# TODO - describe class
|
1801
|
+
class Gradebook
|
1802
|
+
|
1803
|
+
include PageObject
|
1804
|
+
include GlobalMethods
|
1805
|
+
include HeaderFooterBar
|
1806
|
+
include LeftMenuBar
|
1807
|
+
include HeaderBar
|
1808
|
+
include DocButtons
|
1809
|
+
|
1810
|
+
# TODO - Describe method
|
1811
|
+
def gradebook_frame
|
1812
|
+
self.frame(:src=>/sakai2gradebook.launch.html/)
|
1813
|
+
end
|
1814
|
+
|
1815
|
+
end
|
1816
|
+
|
1817
|
+
# TODO - describe class
|
1818
|
+
class LTI
|
1819
|
+
|
1820
|
+
include PageObject
|
1821
|
+
include GlobalMethods
|
1822
|
+
include HeaderFooterBar
|
1823
|
+
include LeftMenuBar
|
1824
|
+
include HeaderBar
|
1825
|
+
include DocButtons
|
1826
|
+
|
1827
|
+
text_field(:url, :id=>"basiclti_settings_ltiurl")
|
1828
|
+
text_field(:key, :id=>"basiclti_settings_ltikey")
|
1829
|
+
text_field(:secret, :id=>"basiclti_settings_ltisecret")
|
1830
|
+
button(:advanced_settings, :id=>"basiclti_settings_advanced_toggle_settings")
|
1831
|
+
button(:save, :id=>"basiclti_settings_insert")
|
1832
|
+
button(:cancel, :id=>"basiclti_settings_cancel")
|
1833
|
+
|
1834
|
+
|
1835
|
+
end
|
1836
|
+
|
1837
|
+
# Methods related to the Maps "Area" in a Course/Group.
|
1838
|
+
class GoogleMaps
|
1839
|
+
|
1840
|
+
include PageObject
|
1841
|
+
include GlobalMethods
|
1842
|
+
include HeaderFooterBar
|
1843
|
+
include LeftMenuBar
|
1844
|
+
include HeaderBar
|
1845
|
+
include DocButtons
|
1846
|
+
|
1847
|
+
# Defines the Google Maps image as an object.
|
1848
|
+
# Use this for verifying the presence of any text it's supposed to
|
1849
|
+
# contain (like the specified address it's supposed to be showing).
|
1850
|
+
def map_frame
|
1851
|
+
self.frame(:id, "googlemaps_iframe_map")
|
1852
|
+
end
|
1853
|
+
|
1854
|
+
# Edits the page, then opens the Google Maps widget's Settings Dialog.
|
1855
|
+
def map_settings
|
1856
|
+
widget_settings
|
1857
|
+
self.text_field(:id=>"googlemaps_input_text_location").when_present { self.class.class_eval { include GoogleMapsPopUp } }
|
1858
|
+
end
|
1859
|
+
|
1860
|
+
# Edits the page, then removes the widget from it.
|
1861
|
+
def remove_map
|
1862
|
+
remove_widget
|
1863
|
+
end
|
1864
|
+
|
1865
|
+
# Edits the page, then opens the "Appearance" pop-up dialog.
|
1866
|
+
def map_wrapping
|
1867
|
+
widget_wrapping
|
1868
|
+
end
|
1869
|
+
|
1870
|
+
end
|
1871
|
+
|
1872
|
+
# TODO - describe class
|
1873
|
+
class Remote
|
1874
|
+
|
1875
|
+
include PageObject
|
1876
|
+
include GlobalMethods
|
1877
|
+
include HeaderFooterBar
|
1878
|
+
include LeftMenuBar
|
1879
|
+
include HeaderBar
|
1880
|
+
include DocButtons
|
1881
|
+
|
1882
|
+
# TODO - Describe method
|
1883
|
+
def remote_frame
|
1884
|
+
self.frame(:id=>"remotecontent_settings_preview_frame")
|
1885
|
+
end
|
1886
|
+
|
1887
|
+
|
1888
|
+
|
1889
|
+
end
|
1890
|
+
|
1891
|
+
# TODO - describe class
|
1892
|
+
class ResearchIntro
|
1893
|
+
|
1894
|
+
include PageObject
|
1895
|
+
include GlobalMethods
|
1896
|
+
include HeaderFooterBar
|
1897
|
+
include LeftMenuBar
|
1898
|
+
include HeaderBar
|
1899
|
+
include DocButtons
|
1900
|
+
|
1901
|
+
end
|
1902
|
+
|
1903
|
+
|
1904
|
+
# TODO - describe class
|
1905
|
+
class Acknowledgements
|
1906
|
+
|
1907
|
+
include PageObject
|
1908
|
+
include GlobalMethods
|
1909
|
+
include HeaderFooterBar
|
1910
|
+
|
1911
|
+
# Page Objects
|
1912
|
+
div(:page_title, :class=>"s3d-bold entity_plaintitle")
|
1913
|
+
link(:featured, :text=>"Featured")
|
1914
|
+
link(:ui_technologies, :text=>"UI Technologies")
|
1915
|
+
link(:back_end_technologies, :text=>"Back-end Technologies")
|
1916
|
+
|
1917
|
+
# Custom Methods
|
1918
|
+
|
1919
|
+
end
|
1920
|
+
|
1921
|
+
# TODO - describe class
|
1922
|
+
class FourOhFourPage
|
1923
|
+
|
1924
|
+
include PageObject
|
1925
|
+
include GlobalMethods
|
1926
|
+
include HeaderFooterBar
|
1927
|
+
include CommonErrorElements
|
1928
|
+
|
1929
|
+
end
|
1930
|
+
|
1931
|
+
# TODO - describe class
|
1932
|
+
class FourOhThreePage
|
1933
|
+
|
1934
|
+
include PageObject
|
1935
|
+
include GlobalMethods
|
1936
|
+
include HeaderFooterBar
|
1937
|
+
include CommonErrorElements
|
1938
|
+
|
1939
|
+
end
|
1940
|
+
|
1941
|
+
# TODO - describe class
|
1942
|
+
class FourOhFourPage
|
1943
|
+
|
1944
|
+
include PageObject
|
1945
|
+
include GlobalMethods
|
1946
|
+
include HeaderFooterBar
|
1947
|
+
include CommonErrorElements
|
1948
|
+
|
1949
|
+
end
|