3030 from selenium .webdriver .common .by import By
3131 from selenium .webdriver .support .ui import WebDriverWait
3232 from selenium .webdriver .support import expected_conditions
33+ from selenium .common .exceptions import NoSuchElementException
3334except ImportError as e :
3435 skip_selenium = True
3536 skip_message = "Skipping selenium tests: %s" % e
@@ -50,29 +51,37 @@ def start_web_driver():
5051 options .add_argument ("no-sandbox" ) # docker needs this
5152 return webdriver .Chrome (options = options , service_log_path = settings .TEST_GHOSTDRIVER_LOG_PATH )
5253
53- @skipIf (skip_selenium , skip_message )
54- class EditMeetingScheduleTests (IetfLiveServerTestCase ):
54+ class MeetingTestCase (IetfLiveServerTestCase ):
55+ def __init__ (self , * args , ** kwargs ):
56+ super (MeetingTestCase , self ).__init__ (* args , ** kwargs )
57+ self .login_view = 'ietf.ietfauth.views.login'
58+
5559 def setUp (self ):
60+ super (MeetingTestCase , self ).setUp ()
5661 self .driver = start_web_driver ()
5762 self .driver .set_window_size (1024 ,768 )
5863
5964 def tearDown (self ):
6065 self .driver .close ()
6166
62- def debug_snapshot (self ,filename = 'debug_this.png' ):
63- self .driver .execute_script ("document.body.bgColor = 'white';" )
64- self .driver .save_screenshot (filename )
65-
6667 def absreverse (self ,* args ,** kwargs ):
6768 return '%s%s' % (self .live_server_url ,urlreverse (* args ,** kwargs ))
6869
69- def login (self ):
70- url = self .absreverse ('ietf.ietfauth.views.login' )
70+ def login (self , username = 'plain' ):
71+ url = self .absreverse (self .login_view )
72+ password = '%s+password' % username
7173 self .driver .get (url )
72- self .driver .find_element_by_name ('username' ).send_keys ('plain' )
73- self .driver .find_element_by_name ('password' ).send_keys ('plain+ password' )
74+ self .driver .find_element_by_name ('username' ).send_keys (username )
75+ self .driver .find_element_by_name ('password' ).send_keys (password )
7476 self .driver .find_element_by_xpath ('//button[@type="submit"]' ).click ()
7577
78+ def debug_snapshot (self ,filename = 'debug_this.png' ):
79+ self .driver .execute_script ("document.body.bgColor = 'white';" )
80+ self .driver .save_screenshot (filename )
81+
82+
83+ @skipIf (skip_selenium , skip_message )
84+ class EditMeetingScheduleTests (MeetingTestCase ):
7685 def test_edit_meeting_schedule (self ):
7786 meeting = make_meeting_test_data ()
7887
@@ -206,28 +215,7 @@ def test_edit_meeting_schedule(self):
206215
207216@skipIf (skip_selenium , skip_message )
208217@skipIf (django .VERSION [0 ]== 2 , "Skipping test with race conditions under Django 2" )
209- class ScheduleEditTests (IetfLiveServerTestCase ):
210- def setUp (self ):
211- self .driver = start_web_driver ()
212- self .driver .set_window_size (1024 ,768 )
213-
214- def tearDown (self ):
215- self .driver .close ()
216-
217- def debug_snapshot (self ,filename = 'debug_this.png' ):
218- self .driver .execute_script ("document.body.bgColor = 'white';" )
219- self .driver .save_screenshot (filename )
220-
221- def absreverse (self ,* args ,** kwargs ):
222- return '%s%s' % (self .live_server_url ,urlreverse (* args ,** kwargs ))
223-
224- def login (self ):
225- url = self .absreverse ('ietf.ietfauth.views.login' )
226- self .driver .get (url )
227- self .driver .find_element_by_name ('username' ).send_keys ('plain' )
228- self .driver .find_element_by_name ('password' ).send_keys ('plain+password' )
229- self .driver .find_element_by_xpath ('//button[@type="submit"]' ).click ()
230-
218+ class ScheduleEditTests (MeetingTestCase ):
231219 def testUnschedule (self ):
232220
233221 meeting = make_meeting_test_data ()
@@ -265,27 +253,16 @@ def testUnschedule(self):
265253 self .assertEqual (SchedTimeSessAssignment .objects .filter (session__meeting__number = 72 ,session__group__acronym = 'mars' ,schedule__name = 'test-schedule' ).count (),0 )
266254
267255@skipIf (skip_selenium , skip_message )
268- class SlideReorderTests (IetfLiveServerTestCase ):
256+ class SlideReorderTests (MeetingTestCase ):
269257 def setUp (self ):
270- self .driver = start_web_driver ()
271- self .driver .set_window_size (1024 ,768 )
258+ super (SlideReorderTests , self ).setUp ()
272259 self .session = SessionFactory (meeting__type_id = 'ietf' , status_id = 'sched' )
273260 self .session .sessionpresentation_set .create (document = DocumentFactory (type_id = 'slides' ,name = 'one' ),order = 1 )
274261 self .session .sessionpresentation_set .create (document = DocumentFactory (type_id = 'slides' ,name = 'two' ),order = 2 )
275262 self .session .sessionpresentation_set .create (document = DocumentFactory (type_id = 'slides' ,name = 'three' ),order = 3 )
276263
277- def tearDown (self ):
278- self .driver .close ()
279-
280- def absreverse (self ,* args ,** kwargs ):
281- return '%s%s' % (self .live_server_url ,urlreverse (* args ,** kwargs ))
282-
283264 def secr_login (self ):
284- url = '%s%s' % (self .live_server_url , urlreverse ('ietf.ietfauth.views.login' ))
285- self .driver .get (url )
286- self .driver .find_element_by_name ('username' ).send_keys ('secretary' )
287- self .driver .find_element_by_name ('password' ).send_keys ('secretary+password' )
288- self .driver .find_element_by_xpath ('//button[@type="submit"]' ).click ()
265+ self .login ('secretary' )
289266
290267 #@override_settings(DEBUG=True)
291268 def testReorderSlides (self ):
@@ -305,6 +282,223 @@ def testReorderSlides(self):
305282 names = self .session .sessionpresentation_set .values_list ('document__name' ,flat = True )
306283 self .assertEqual (list (names ),['one' ,'three' ,'two' ])
307284
285+
286+ @skipIf (skip_selenium , skip_message )
287+ class AgendaTests (MeetingTestCase ):
288+ # Groups whose display logic is inverted in agenda.html. These have
289+ # toggles with class 'pickviewneg' in the template.
290+ PICKVIEWNEG = ['iepg' , 'tools' , 'edu' , 'ietf' , 'iesg' , 'iab' ]
291+
292+ def setUp (self ):
293+ super (AgendaTests , self ).setUp ()
294+ self .meeting = make_meeting_test_data ()
295+
296+ def row_id_for_item (self , item ):
297+ return 'row-%s' % item .slug ()
298+
299+ def get_expected_items (self ):
300+ expected_items = self .meeting .schedule .assignments .exclude (timeslot__type__in = ['lead' ,'offagenda' ])
301+ self .assertGreater (len (expected_items ), 0 , 'Test setup generated an empty schedule' )
302+ return expected_items
303+
304+ def test_agenda_view_displays_all_items (self ):
305+ """By default, all agenda items should be displayed"""
306+ self .login ()
307+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ))
308+
309+ for item in self .get_expected_items ():
310+ row_id = 'row-%s' % item .slug ()
311+ try :
312+ item_row = self .driver .find_element_by_id (row_id )
313+ except NoSuchElementException :
314+ item_row = None
315+ self .assertIsNotNone (item_row , 'No row for schedule item "%s"' % row_id )
316+ self .assertTrue (item_row .is_displayed (), 'Row for schedule item "%s" is not displayed' % row_id )
317+
318+ def test_agenda_view_js_func_parse_query_params (self ):
319+ """Test parse_query_params() function"""
320+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ))
321+
322+ # Only 'show' param
323+ result = self .driver .execute_script (
324+ 'return parse_query_params("?show=group1,group2,group3");'
325+ )
326+ self .assertEqual (result , dict (show = 'group1,group2,group3' ))
327+
328+ # Only 'hide' param
329+ result = self .driver .execute_script (
330+ 'return parse_query_params("?hide=group4,group5,group6");'
331+ )
332+ self .assertEqual (result , dict (hide = 'group4,group5,group6' ))
333+
334+ # Both 'show' and 'hide'
335+ result = self .driver .execute_script (
336+ 'return parse_query_params("?show=group1,group2,group3&hide=group4,group5,group6");'
337+ )
338+ self .assertEqual (result , dict (show = 'group1,group2,group3' , hide = 'group4,group5,group6' ))
339+
340+ def test_agenda_view_js_func_toggle_list_item (self ):
341+ """Test toggle_list_item() function"""
342+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ))
343+
344+ result = self .driver .execute_script (
345+ """
346+ // start empty, add item
347+ var list0=[];
348+ toggle_list_item(list0, 'item');
349+
350+ // one item, remove it
351+ var list1=['item'];
352+ toggle_list_item(list1, 'item');
353+
354+ // one item, add another
355+ var list2=['item1'];
356+ toggle_list_item(list2, 'item2');
357+
358+ // multiple items, remove first
359+ var list3=['item1', 'item2', 'item3'];
360+ toggle_list_item(list3, 'item1');
361+
362+ // multiple items, remove middle
363+ var list4=['item1', 'item2', 'item3'];
364+ toggle_list_item(list4, 'item2');
365+
366+ // multiple items, remove last
367+ var list5=['item1', 'item2', 'item3'];
368+ toggle_list_item(list5, 'item3');
369+
370+ return [list0, list1, list2, list3, list4, list5];
371+ """
372+ )
373+ self .assertEqual (result [0 ], ['item' ], 'Adding item to empty list failed' )
374+ self .assertEqual (result [1 ], [], 'Removing only item in a list failed' )
375+ self .assertEqual (result [2 ], ['item1' , 'item2' ], 'Adding second item to list failed' )
376+ self .assertEqual (result [3 ], ['item2' , 'item3' ], 'Removing first item from list failed' )
377+ self .assertEqual (result [4 ], ['item1' , 'item3' ], 'Removing middle item from list failed' )
378+ self .assertEqual (result [5 ], ['item1' , 'item2' ], 'Removing last item from list failed' )
379+
380+ def test_agenda_view_filter_show_one (self ):
381+ """Filtered agenda view should display only matching rows (one group selected)"""
382+ self .login ()
383+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ) + '?show=mars' )
384+ self .assert_agenda_item_visibility (['mars' ] + self .PICKVIEWNEG ) # ames and secretariat not selected
385+
386+ def test_agenda_view_filter_show_two (self ):
387+ """Filtered agenda view should display only matching rows (two groups selected)"""
388+ self .login ()
389+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ) + '?show=mars,ames' )
390+ self .assert_agenda_item_visibility (['mars' , 'ames' ] + self .PICKVIEWNEG ) # secretariat not selected
391+
392+ def test_agenda_view_filter_all (self ):
393+ """Filtered agenda view should display only matching rows (all groups selected)"""
394+ self .login ()
395+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ))
396+ self .assert_agenda_item_visibility ()
397+
398+ def test_agenda_view_filter_hide (self ):
399+ self .login ()
400+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ) + '?hide=ietf' )
401+ self .assert_agenda_item_visibility ([g for g in self .PICKVIEWNEG if g != 'ietf' ])
402+
403+ def test_agenda_view_filter_show_and_hide (self ):
404+ self .login ()
405+ self .driver .get (self .absreverse ('ietf.meeting.views.agenda' ) + '?show=mars&hide=ietf' )
406+ self .assert_agenda_item_visibility (
407+ ['mars' ] + [g for g in self .PICKVIEWNEG if g != 'ietf' ]
408+ )
409+
410+ def assert_agenda_item_visibility (self , visible_groups = ()):
411+ """Assert that correct items are visible in current browser window
412+
413+ If visible_groups is empty (the default), expects all items to be visible.
414+ """
415+ for item in self .get_expected_items ():
416+ row_id = self .row_id_for_item (item )
417+ try :
418+ item_row = self .driver .find_element_by_id (row_id )
419+ except NoSuchElementException :
420+ item_row = None
421+ self .assertIsNotNone (item_row , 'No row for schedule item "%s"' % row_id )
422+ if len (visible_groups ) == 0 or item .session .group .acronym in visible_groups :
423+ self .assertTrue (item_row .is_displayed (), 'Row for schedule item "%s" is not displayed but should be' % row_id )
424+ else :
425+ self .assertFalse (item_row .is_displayed (), 'Row for schedule item "%s" is displayed but should not be' % row_id )
426+
427+ def test_agenda_view_group_filter_toggle (self ):
428+ """Clicking a group toggle enables/disables agenda filtering"""
429+ group_acronym = 'mars'
430+
431+ self .login ()
432+ url = self .absreverse ('ietf.meeting.views.agenda' )
433+ self .driver .get (url )
434+
435+ # Click the 'customize' anchor to reveal the group buttons
436+ customize_anchor = WebDriverWait (self .driver , 2 ).until (
437+ expected_conditions .element_to_be_clickable (
438+ (By .CSS_SELECTOR , '#accordion a[data-toggle="collapse"]' )
439+ )
440+ )
441+ customize_anchor .click ()
442+
443+ # Click the group button
444+ group_button = WebDriverWait (self .driver , 2 ).until (
445+ expected_conditions .element_to_be_clickable (
446+ (By .CSS_SELECTOR , 'button.pickview.%s' % group_acronym )
447+ )
448+ )
449+ group_button .click ()
450+
451+ # Check visibility
452+ self .assert_agenda_item_visibility ([group_acronym ] + self .PICKVIEWNEG )
453+
454+ # Click the group button again
455+ group_button = WebDriverWait (self .driver , 2 ).until (
456+ expected_conditions .element_to_be_clickable (
457+ (By .CSS_SELECTOR , 'button.pickview.%s' % group_acronym )
458+ )
459+ )
460+ group_button .click ()
461+
462+ # Check visibility
463+ self .assert_agenda_item_visibility ()
464+
465+ def test_agenda_view_group_filter_toggle_without_replace_state (self ):
466+ """Toggle should function for browsers without window.history.replaceState"""
467+ group_acronym = 'mars'
468+
469+ self .login ()
470+ url = self .absreverse ('ietf.meeting.views.agenda' )
471+ self .driver .get (url )
472+
473+ # Rather than digging up an ancient browser, simulate absence of history.replaceState
474+ self .driver .execute_script ('window.history.replaceState = undefined;' )
475+
476+
477+ # Click the 'customize' anchor to reveal the group buttons
478+ customize_anchor = WebDriverWait (self .driver , 2 ).until (
479+ expected_conditions .element_to_be_clickable (
480+ (By .CSS_SELECTOR , '#accordion a[data-toggle="collapse"]' )
481+ )
482+ )
483+ customize_anchor .click ()
484+
485+
486+ # Get ready to click the group button
487+ group_button = WebDriverWait (self .driver , 2 ).until (
488+ expected_conditions .element_to_be_clickable (
489+ (By .CSS_SELECTOR , 'button.pickview.%s' % group_acronym )
490+ )
491+ )
492+
493+ # Be sure we're at the URL we think we're at before we click
494+ self .assertEqual (self .driver .current_url , url )
495+ group_button .click () # click!
496+
497+ expected_url = '%s?show=%s' % (url , group_acronym )
498+ WebDriverWait (self .driver , 2 ).until (expected_conditions .url_to_be (expected_url ))
499+ # no assertion here - if WebDriverWait raises an exception, the test will fail.
500+ # We separately test whether this URL will filter correctly.
501+
308502# The following are useful debugging tools
309503
310504# If you add this to a LiveServerTestCase and run just this test, you can browse
0 commit comments