sakai-oae-test-api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|