from django.test import LiveServerTestCase from selenium import webdriver from selenium.webdriver.common.by import By from selenium.common.exceptions import StaleElementReferenceException from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as ec from core.models import UserAccount, Submission, FeedbackComment from functional_tests.util import (login, create_browser, reset_browser_after_test, subscriptions_loaded_cond) from util import factory_boys as fact class UntestedParent: class TestFeedbackCreationGeneric(LiveServerTestCase): browser: webdriver.Firefox = None username = None password = None role = None @classmethod def setUpClass(cls): super().setUpClass() cls.browser = create_browser() @classmethod def tearDownClass(cls): super().tearDownClass() cls.browser.quit() def setUp(self): self.sub_type = fact.SubmissionTypeFactory.create() fact.SubmissionFactory.create_batch(2, type=self.sub_type) def tearDown(self): reset_browser_after_test(self.browser, self.live_server_url) def _login(self): login(self.browser, self.live_server_url, self.username, self.password) def _reconstruct_submission_code(self): sub_table = self.browser.find_element_by_class_name('submission-table') lines = sub_table.find_elements_by_tag_name('tr') line_no_code_pairs = [ (line.get_attribute('id'), # call get_attribute here to get non normalized text # https://github.com/SeleniumHQ/selenium/issues/2608 line.find_element_by_class_name('code-cell-content').get_attribute('textContent')) for line in lines ] line_no_code_pairs.sort(key=lambda x: x[0]) # sort by ids code_lines = list(zip(*line_no_code_pairs))[1] return '\n'.join(code_lines) # stage can be 'initial', 'validate', or 'conflict' def _go_to_subscription(self, stage='initial', sub_type=None): tasks = self.browser.find_element_by_name('subscription-list') WebDriverWait(self.browser, 10).until(subscriptions_loaded_cond(tasks)) tab = tasks.find_element_by_xpath(f'//*[contains(text(), "{stage}")]') tab.click() sub_type = sub_type if sub_type is not None else self.sub_type sub_type_xpath = f'//*[contains(text(), "{sub_type.name}") ' \ f'and not(contains(@class, "inactive"))]' WebDriverWait(self.browser, 10).until( ec.element_to_be_clickable((By.XPATH, sub_type_xpath)), message="SubmissionType not clickable" ) sub_type_el = tasks.find_element_by_xpath(sub_type_xpath) sub_type_el.click() WebDriverWait(self.browser, 10).until(ec.url_contains('subscription')) def write_comments_on_lines(self, line_comment_tuples): """ line_comment_tuples is an iterable containing tuples of (line_no, comment) where the line number starts at 1 """ sub_table = self.browser.find_element_by_class_name('submission-table') lines = sub_table.find_elements_by_tag_name('tr') for (line_no, comment) in line_comment_tuples: line = lines[line_no-1] line.find_element_by_tag_name('button').click() textarea = line.find_element_by_tag_name('textarea') textarea.send_keys(comment) line.find_element_by_id('submit-comment').click() def test_student_text_is_correctly_displayed(self): self._login() self._go_to_subscription() code = self._reconstruct_submission_code() # query db for Submission with seen code, throws if not present and test fails Submission.objects.get(text=code) def test_submission_type_is_correctly_displayed(self): self._login() self._go_to_subscription() sub_type_el = self.browser.find_element_by_id('submission-type') title = sub_type_el.find_element_by_class_name('title') self.assertEqual( f'{self.sub_type.name} - Full score: {self.sub_type.full_score}', title.text ) solution = sub_type_el.find_element_by_class_name('solution-code') self.assertEqual(self.sub_type.solution, solution.get_attribute('textContent')) description = sub_type_el.find_element_by_class_name('type-description') html_el_in_desc = description.find_element_by_tag_name('h1') self.assertEqual('This', html_el_in_desc.text) def test_test_output_is_displayed(self): # create a test for every submission test = None for submission in Submission.objects.all(): test = fact.TestFactory.create(submission=submission, annotation='This is a test') self._login() self._go_to_subscription() tests = self.browser.find_element_by_id('submission-tests') name_label = tests.find_element_by_name('test-name-label') name_label.click() self.assertIn(test.name, name_label.text) self.assertIn(test.label, name_label.text) test_output = tests.find_element_by_name('test-output') WebDriverWait(self.browser, 10).until(ec.visibility_of(test_output)) self.assertEqual(test.annotation, test_output.text) def wait_until_code_changes(self, code): def condition(*args): try: # code might change during the call resulting in the exception new_code = self._reconstruct_submission_code() except StaleElementReferenceException: return False return code != new_code return condition def test_can_give_max_score(self): self._login() self._go_to_subscription() code = self._reconstruct_submission_code() self.browser.find_element_by_id('score-full').click() submit_btn = self.browser.find_element_by_id('submit-feedback') submit_btn.click() WebDriverWait(self.browser, 10).until( self.wait_until_code_changes(code) ) submission_for_code = Submission.objects.get(text=code) self.assertEqual(self.sub_type.full_score, submission_for_code.feedback.score) def test_zero_score_without_warning_gives_error(self): self._login() self._go_to_subscription() self.browser.find_element_by_id('score-zero').click() submit_btn = self.browser.find_element_by_id('submit-feedback') submit_btn.click() notification = self.browser.find_element_by_class_name('notification-content') self.assertIn('comment', notification.text) def test_can_give_zero_score(self): self._login() self._go_to_subscription() code = self._reconstruct_submission_code() self.browser.find_element_by_id('score-zero').click() self.write_comments_on_lines([(0, 'A comment')]) self.browser.find_element_by_id('submit-feedback').click() WebDriverWait(self.browser, 10).until(self.wait_until_code_changes(code)) submission_for_code = Submission.objects.get(text=code) self.assertEqual(0, submission_for_code.feedback.score) def test_can_give_comments_and_decreased_score(self): self._login() self._go_to_subscription() code = self._reconstruct_submission_code() # give half full score score_input = self.browser.find_element_by_id('score-input') score_input.send_keys(self.sub_type.full_score // 2) # give feedback on first and last line of submission comment_text = 'This is feedback' self.write_comments_on_lines([ (1, comment_text), (0, comment_text) # 0 corresponds to the last line ]) submit_btn = self.browser.find_element_by_id('submit-feedback') submit_btn.click() WebDriverWait(self.browser, 10).until( self.wait_until_code_changes(code) ) submission_for_code = Submission.objects.get(text=code) self.assertEqual(self.sub_type.full_score // 2, submission_for_code.feedback.score) self.assertEqual(2, submission_for_code.feedback.feedback_lines.count()) fst_comment = FeedbackComment.objects.get( of_feedback=submission_for_code.feedback, of_line=1 ) self.assertEqual(comment_text, fst_comment.text) last_line_of_sub = len(submission_for_code.text.split('\n')) snd_comment = FeedbackComment.objects.get( of_feedback=submission_for_code.feedback, of_line=last_line_of_sub ) self.assertEqual(comment_text, snd_comment.text) def test_can_skip_submission(self): self._login() self._go_to_subscription() code = self._reconstruct_submission_code() self.browser.find_element_by_id('skip-submission').click() WebDriverWait(self.browser, 10).until(self.wait_until_code_changes(code)) def test_can_validate_submission(self): self._login() self._go_to_subscription() def correct(): code = self._reconstruct_submission_code() self.write_comments_on_lines([(0, 'A comment by me')]) self.browser.find_element_by_id('score-zero').click() self.browser.find_element_by_id('submit-feedback').click() return code code = correct() WebDriverWait(self.browser, 10).until(self.wait_until_code_changes(code)) correct() sub_url = 'subscription/' + str(self.sub_type.pk) + '/ended' WebDriverWait(self.browser, 10).until(ec.url_contains(sub_url)) reset_browser_after_test(self.browser, self.live_server_url) # logs out user user_snd = 'tutor_snd' password = 'p' fact.UserAccountFactory(username=user_snd, password=password) login(self.browser, self.live_server_url, user_snd, password) self._go_to_subscription(stage='validate') self.write_comments_on_lines([(0, 'I disagree'), (1, 'Full points!')]) code_final = self._reconstruct_submission_code() self.browser.find_element_by_id('score-full').click() self.browser.find_element_by_id('submit-feedback').click() WebDriverWait(self.browser, 10).until(self.wait_until_code_changes(code_final)) code_non_final = self._reconstruct_submission_code() self.browser.find_element_by_class_name('final-checkbox').click() self.browser.find_element_by_id('submit-feedback').click() sub_url = 'subscription/' + str(self.sub_type.pk) + '/ended' WebDriverWait(self.browser, 10).until(ec.url_contains(sub_url)) reset_browser_after_test(self.browser, self.live_server_url) user_rev = 'rev' password = 'p' role = UserAccount.REVIEWER fact.UserAccountFactory(username=user_rev, password=password, role=role) login(self.browser, self.live_server_url, user_rev, password) self._go_to_subscription('conflict') code = self._reconstruct_submission_code() self.assertEqual(code, code_non_final) submission_for_code = Submission.objects.get(text=code_final) self.assertEqual(self.sub_type.full_score, submission_for_code.feedback.score) self.assertEqual(3, submission_for_code.feedback.feedback_lines.count()) submission_for_code = Submission.objects.get(text=code_non_final) self.assertEqual(0, submission_for_code.feedback.score) self.assertEqual(1, submission_for_code.feedback.feedback_lines.count()) class TestFeedbackCreationTutor(UntestedParent.TestFeedbackCreationGeneric): def setUp(self): super().setUp() self.username = 'tutor' self.password = 'p' self.role = UserAccount.TUTOR fact.UserAccountFactory( username=self.username, password=self.password, role=self.role )