diff --git a/e2e_tests/test_batch_assess.py b/e2e_tests/test_batch_assess.py new file mode 100644 index 000000000..40c219581 --- /dev/null +++ b/e2e_tests/test_batch_assess.py @@ -0,0 +1,143 @@ +from playwright.sync_api import Page, expect +from e2e_tests.helpers import login, navigate_to_default_course, logout +import re + +def test_batch_assess(page: Page) -> None: # pylint: disable=too-many-statements + page.goto("http://localhost:8010/?hl=en") + login(page, "teacher", "teacher") + + navigate_to_default_course(page) + page.get_by_role("link", name="Participants").click() + + #select 3 students (one of them Stacy Student) and first check that + #2 have no points and that all have no submissions on exercise 6.10.1 + + page.get_by_role("link", name="11110001").click() + + expect(page.get_by_role("navigation", name="Breadcrumb")).to_contain_text("Zac Singh") + + cards = page.locator("div.card") + expect(cards.filter(has_text="jututfeedback")).to_contain_text("0 / 4") + expect(cards.filter(has_text="questionnaire")).to_contain_text("0 / 97") + expect(cards.filter(has_text="submit")).to_contain_text("0 / 750") + + exercise_row = (page.get_by_role("link", name="6.10.1 (Git) Hello Python!").locator("xpath=ancestor::tr")) + expect(exercise_row).to_contain_text("submit 0 / 10 No submissions yet 0 / 10") + + page.get_by_role("link", name="Participants").first.click() + page.get_by_role("link", name="11110002").click() + + expect(page.get_by_role("navigation", name="Breadcrumb")).to_contain_text("Felicity Moran") + + cards = page.locator("div.card") + expect(cards.filter(has_text="jututfeedback")).to_contain_text("0 / 4") + expect(cards.filter(has_text="questionnaire")).to_contain_text("0 / 97") + expect(cards.filter(has_text="submit")).to_contain_text("0 / 750") + + exercise_row = (page.get_by_role("link", name="6.10.1 (Git) Hello Python!").locator("xpath=ancestor::tr")) + expect(exercise_row).to_contain_text("submit 0 / 10 No submissions yet 0 / 10") + + page.get_by_role("link", name="Participants").first.click() + page.locator('input[data-column="1"]').fill("123456") + page.get_by_role("link", name="123456").click() + + expect(page.get_by_role("navigation", name="Breadcrumb")).to_contain_text("Stacy Student") + + #don't verify Stacy Student's point totals since other tests affect them + + exercise_row = (page.get_by_role("link", name="6.10.1 (Git) Hello Python!").locator("xpath=ancestor::tr")) + expect(exercise_row).to_contain_text("submit 0 / 10 No submissions yet 0 / 10") + + page.get_by_role("link", name="Participants").first.click() + #first select all 3 students + page.locator("select.form-select.form-select-sm").select_option("-1") + + row1 = page.locator("tr").filter(has_text="11110001") + row1.get_by_role("checkbox").check() + + row2 = page.locator("tr").filter(has_text="11110002") + row2.get_by_role("checkbox").check() + + row3 = page.locator("tr").filter(has_text="123456") + row3.get_by_role("checkbox").check() + + #then do the batch assessment (exercise 6.10.1, with message) + + page.get_by_role("button", name="Batch assess").click() + page.locator("#batch-assess-category").select_option( + label="6.10.1 (Git) Hello Python!" + ) + page.locator("#batch-assess-value-display").fill("7") + page.locator("#batch-assess-feedback").fill("good job") + page.get_by_role("button", name="Submit").click() + + expect(page.get_by_role("heading", name="Batch assessment successful")).to_be_visible() + + expect(page.locator("#batch-assess-success-count")).to_have_text("3") + expect(page.locator("#batch-assess-success-exercise")).to_have_text( + "6.10.1 (Git) Hello Python!" + ) + expect(page.locator("#batch-assess-success-students")).to_contain_text( + "Zac Singh (ID: 7)" + ) + expect(page.locator("#batch-assess-success-students")).to_contain_text( + "Felicity Moran (ID: 8)" + ) + expect(page.locator("#batch-assess-success-students")).to_contain_text( + "Stacy Student (ID: 5)" + ) + expect(page.locator("#batch-assess-success-points")).to_have_text("7") + expect(page.locator("#batch-assess-success-feedback")).to_have_text("good job") + + page.get_by_role("button", name="Close").first.click() + + #then check as teacher that the points and submissions are correct + + page.get_by_role("link", name="11110001").click() + + expect(page.get_by_role("navigation", name="Breadcrumb")).to_contain_text("Zac Singh") + + cards = page.locator("div.card") + expect(cards.filter(has_text="jututfeedback")).to_contain_text("0 / 4") + expect(cards.filter(has_text="questionnaire")).to_contain_text("0 / 97") + expect(cards.filter(has_text="submit")).to_contain_text("7 / 750") + + exercise_row = (page.get_by_role("link", name="6.10.1 (Git) Hello Python!").locator("xpath=ancestor::tr")) + expect(exercise_row).to_contain_text("1 / 10") + expect(exercise_row).to_contain_text("7 / 10") + + page.get_by_role("link", name="Participants").first.click() + page.get_by_role("link", name="11110002").click() + + expect(page.get_by_role("navigation", name="Breadcrumb")).to_contain_text("Felicity Moran") + + cards = page.locator("div.card") + expect(cards.filter(has_text="jututfeedback")).to_contain_text("0 / 4") + expect(cards.filter(has_text="questionnaire")).to_contain_text("0 / 97") + expect(cards.filter(has_text="submit")).to_contain_text("7 / 750") + + exercise_row = (page.get_by_role("link", name="6.10.1 (Git) Hello Python!").locator("xpath=ancestor::tr")) + expect(exercise_row).to_contain_text("1 / 10") + expect(exercise_row).to_contain_text("7 / 10") + + #don't check Stacy Student's points since other tests affect them + #then logout and check as student that the points, message and submissions are correct + + logout(page) + login(page, "student", "student") + navigate_to_default_course(page) + page.get_by_role("link", name="6.10 Submit from Git").click() + + exercise = page.locator('#chapter-exercise-1') + submissions = exercise.get_by_role("button", name=re.compile("My submissions")) + points = exercise.get_by_role("button", name=re.compile("Points")) + + expect(submissions).to_contain_text("1 / 10") + expect(points).to_contain_text("7 / 10") + + submissions.click() + exercise.locator(".dropdown-menu li a.page-modal").click() + + points_dialog = page.get_by_role("dialog").filter(has_text="6.10.1") + expect(points_dialog).to_contain_text("good job") + \ No newline at end of file diff --git a/e2e_tests/test_compare_submissions.py b/e2e_tests/test_compare_submissions.py index ddc914b2c..fd7d389fb 100644 --- a/e2e_tests/test_compare_submissions.py +++ b/e2e_tests/test_compare_submissions.py @@ -84,10 +84,7 @@ def assert_line(filename: str, line_number: int, text: str, color: str): expect(page.locator('.site-message')).to_contain_text( "The review was saved successfully and the submitters were notified.") - page.goto( - "http://localhost:8010/def/current/programming_exercises/graderutils" + - "/programming_exercises_graderutils_iotester_exercise2/submissions/2/inspect/?compare_to=invalid" - ) + page.goto(page.url + "?compare_to=invalid") expect(page.get_by_role("main")).to_contain_text( "The file you are attempting to compare to was not found.") diff --git a/e2e_tests/test_deadline_deviations.py b/e2e_tests/test_deadline_deviations.py new file mode 100644 index 000000000..fed130bcd --- /dev/null +++ b/e2e_tests/test_deadline_deviations.py @@ -0,0 +1,55 @@ +from playwright.sync_api import Page, expect +from e2e_tests.helpers import login, logout, navigate_to_default_course +from datetime import datetime + +def test_deadline_deviations(page: Page) -> None: + page.goto("http://localhost:8010/?hl=en") + + #first check as student that there is no deadline deviation + login(page, "student", "student") + navigate_to_default_course(page) + expect(page.locator("#course-content")).not_to_contain_text("Assignments with personal deadline extensions") + page.get_by_role("link", name="6.2 Hello Worlds").first.click() + + exercise = page.locator("#chapter-exercise-1") + info = exercise.locator("div.container-fluid.bg-info-subtle.text-info-emphasis") + expect(info).not_to_contain_text("Personal extended") + expect(info).to_contain_text("Deadline Tuesday, 31 December 2030, 12:00") + + #create deadline deviation as teacher + logout(page) + login(page, "teacher", "teacher") + + navigate_to_default_course(page) + page.get_by_role("link", name="Deadline deviations").click() + page.get_by_role("button", name="Add new deadline deviations").click() + + #create the deviation + exercise_input = page.locator("#id_exercise_wrapper") + exercise_input.get_by_role("textbox").fill("6.2.1 Hello Python!") + exercise_input.get_by_role("button", name="Search").click() + exercise_input.get_by_role("link", name="6.2.1 Hello Python!").click() + + submitter_input = page.locator("#id_submitter_wrapper") + submitter_input.get_by_role("textbox").fill("Stacy Student, 123456, student@localhost.invalid") + submitter_input.get_by_role("button", name="Search").click() + submitter_input.get_by_role("link", name="Stacy Student, 123456, student@localhost.invalid").click() + + current_year = datetime.now().year + page.locator("#id_new_date").fill(f"{current_year}-12-31T12:00") + + page.get_by_role("button", name="Save").click() + + #lastly check as student that the deadline deviation is visible + logout(page) + login(page, "student", "student") + navigate_to_default_course(page) + + expect(page.locator("#course-content")).to_contain_text("Assignments with personal deadline extensions") + + page.get_by_role("link", name="6.2 Hello Worlds").first.click() + + exercise = page.locator("#chapter-exercise-1") + info = exercise.locator("div.container-fluid.bg-info-subtle.text-info-emphasis") + expect(info).to_contain_text("Personal extended deadline") + expect(info).to_contain_text(f"31 December {current_year}, 12:00") \ No newline at end of file diff --git a/e2e_tests/test_edit_exercise.py b/e2e_tests/test_edit_exercise.py new file mode 100644 index 000000000..f03ca1191 --- /dev/null +++ b/e2e_tests/test_edit_exercise.py @@ -0,0 +1,52 @@ +import re + +from playwright.sync_api import Page, expect +from e2e_tests.helpers import login, navigate_to_default_course, logout + + +def test_edit_exercise(page: Page) -> None: + page.goto("http://localhost:8010/?hl=en") + login(page, "student", "student") + + navigate_to_default_course(page) + page.get_by_role("link", name="5.2 Question groups").first.click() + + # Target exercise 5.2.2 from the chapter exercise list. + exercise = page.locator("#chapter-exercise-2") + submissions = exercise.get_by_role("button", name=re.compile("My submissions")) + points = exercise.get_by_role("button", name=re.compile("Points")) + + # Verify current values before editing. + expect(submissions).to_contain_text("0 / 10") + expect(points).to_contain_text("0 / 30") + + + #edit values as teacher + logout(page) + login(page, "teacher", "teacher") + navigate_to_default_course(page) + page.locator("#main-course-menu").get_by_role("link", name="Edit course").click() + page.locator("tr").filter( + has_text="5.2.2 Question groups" + ).get_by_role("button", name="Edit assignment").click() + + page.locator("#id_max_points").fill("50") + page.locator("#id_max_submissions").fill("5") + + page.get_by_role("button", name="Save").click() + + #check edited values as student + logout(page) + login(page, "student", "student") + + navigate_to_default_course(page) + page.get_by_role("link", name="5.2 Question groups").first.click() + + # Target exercise 5.2.2 from the chapter exercise list. + exercise = page.locator("#chapter-exercise-2") + submissions = exercise.get_by_role("button", name=re.compile("My submissions")) + points = exercise.get_by_role("button", name=re.compile("Points")) + + # Verify new values after editing. + expect(submissions).to_contain_text("0 / 5") + expect(points).to_contain_text("0 / 50") diff --git a/e2e_tests/test_grading_modes.py b/e2e_tests/test_grading_modes.py new file mode 100644 index 000000000..2cb5fae1f --- /dev/null +++ b/e2e_tests/test_grading_modes.py @@ -0,0 +1,65 @@ +import re +from playwright.sync_api import Page, expect +from e2e_tests.helpers import navigate_to_default_course, login, logout + + +def test_grading_modes(page: Page): + page.goto("http://localhost:8010/?hl=en") + login(page, "student", "student") + navigate_to_default_course(page) + page.get_by_role("link", name="5.1 Creating questionnaire exercises").first.click() + + #login as student and make two submissions to exercise 5.1.4 + #first with max points and second with partial points + + exercise = page.locator("#chapter-exercise-4") + + exercise.get_by_role("textbox", name="Question 1").fill("dog") + exercise.get_by_role("textbox", name="Question 2").fill("3.141") + + exercise.get_by_role("button", name="Submit").click() + + submissions = exercise.get_by_role("button", name=re.compile("My submissions")) + expect(submissions).to_contain_text("1 / 5") + + points = exercise.get_by_role("button", name=re.compile("Points")) + expect(points).to_contain_text("20 / 20") + + exercise.get_by_role("textbox", name="Question 1").fill("dog") + exercise.get_by_role("textbox", name="Question 2").fill("3.142") + + exercise.get_by_role("button", name="Submit").click() + + submissions = exercise.get_by_role("button", name=re.compile("My submissions")) + expect(submissions).to_contain_text("2 / 5") + + points = exercise.get_by_role("button", name=re.compile("Points")) + expect(points).to_contain_text("20 / 20") + + #then login as teacher and change the grading mode to latest submission + + logout(page) + login(page, "teacher", "teacher") + navigate_to_default_course(page) + page.get_by_role("link", name="Edit course").click() + + page.locator("tr").filter( + has_text="Questionnaire using regular expressions" + ).get_by_role("button", name="Edit assignment").click() + page.locator("#id_grading_mode").select_option("2") + page.get_by_role("button", name="Save").click() + + + #login as student and re-check exercise, should have partial points + logout(page) + login(page, "student", "student") + navigate_to_default_course(page) + page.get_by_role("link", name="5.1 Creating questionnaire exercises").first.click() + + exercise = page.locator("#chapter-exercise-4") + + submissions = exercise.get_by_role("button", name=re.compile("My submissions")) + expect(submissions).to_contain_text("2 / 5") + + points = exercise.get_by_role("button", name=re.compile("Points")) + expect(points).to_contain_text("10 / 20") \ No newline at end of file diff --git a/e2e_tests/test_homepage.py b/e2e_tests/test_homepage.py index 50ec01975..b72dad8c4 100644 --- a/e2e_tests/test_homepage.py +++ b/e2e_tests/test_homepage.py @@ -9,5 +9,77 @@ def test_homepage(page: Page): login(page, "student", "student") navigate_to_default_course(page) + + #test top navbar + + top_navbar = page.get_by_role("navigation", name="Main") + expect(top_navbar).to_be_visible() + + expect(top_navbar.get_by_role("link", name="A+", exact=True)).to_be_visible() + expect(top_navbar.locator("#bd-theme")).to_be_visible() + expect(top_navbar.get_by_test_id("user-menu")).to_be_visible() + + course_toggle = top_navbar.locator("a.dropdown-toggle").filter(has_text="DEF000") + expect(course_toggle).to_be_visible() + course_toggle.first.click() + expect(page.locator("#courseDropdownMenu")).to_be_visible() + expect(page.locator("#courseDropdownMenu").get_by_role("link", name="DEF000 Def. Course: Current")).to_be_visible() + + top_navbar.locator("#bd-theme").click() + expect(top_navbar.get_by_role("button", name="Dark (experimental)")).to_be_visible() + + top_navbar.get_by_test_id("user-menu").click() + expect(page.get_by_role("link", name="Account")).to_be_visible() + expect(page.get_by_role("button", name="Log out")).to_be_visible() + + #test main content + #(don't test first module as its name is changed in test_edit_module_page.py) + + top_level_titles = [ + "2. Set up your environment", + "3. RST Guide", + "4. Style Aplus courses", + "5. Questionnaires", + "6. Programming exercises", + "7. Acos server", + "8. Jutut service for feedback and messaging", + "9. Interactive code blocks", + "10. Converting an old course for current A+", + "11. External LTI (Learning Tools Interoperability) exercises and services", + "12. Rubyric", + "13. Course administration", + "14. Languages", + "15. Moodle Astra plugin", + "16. Active elements", + "17. Point of Interest", + "18. Adding Sphinx extensions", + ] + main_toc = page.locator("ul.toc").first + for title in top_level_titles: + expect(main_toc.get_by_role("link", name=title, exact=True)).to_be_visible() + + #test left sidebar course menu, make sure that only links visible to students are present + + course_menu = page.locator("#main-course-menu") + expected_course_menu_link_names_core = [ + "DEF000", + "Course materials", + "Your points", + ] + expected_course_menu_link_names_with_local_services = [ + "DEF000", + "Course materials", + "Your points", + "Rubyric", + "Radar", + "Jutut", + ] + actual_course_menu_link_names = course_menu.locator("a.nav-link .course-menu-label").evaluate_all( + "elements => elements.map((el) => el.textContent.trim())" + ) + assert actual_course_menu_link_names in [ + expected_course_menu_link_names_core, + expected_course_menu_link_names_with_local_services, + ] + expect(page.get_by_role("heading", name="A+ Manual")).to_be_visible() - #TODO figure out more testing for the homepage diff --git a/e2e_tests/test_text_exercise.py b/e2e_tests/test_text_exercise.py index 06457765e..12242d452 100644 --- a/e2e_tests/test_text_exercise.py +++ b/e2e_tests/test_text_exercise.py @@ -80,19 +80,29 @@ def test_should_give_full_points_on_correct_submission(page: Page): navigate_to_default_course(page) page.get_by_role("link", name="5.1 Creating questionnaire exercises").first.click() - #fill in an correct answer for exercise 5.1.4 - #fill in different exercise to make the test work independently of the previous test + #fill in an correct answer for exercise 5.1.3 (which is also used for previous test) # TODO: figure out a way to make the tests independent? - exercise = page.locator("#chapter-exercise-4") - exercise.get_by_role("textbox", name="Question 1").fill("cat") - exercise.get_by_role("textbox", name="Question 2").fill("3.141") + exercise = page.locator("#chapter-exercise-3") + + submissions = exercise.get_by_role("button", name=re.compile("My submissions")) + testrun = False + if "1" in submissions.inner_text(): + testrun = True + + exercise.get_by_role("textbox", name="Question 1").fill("anothertest") + exercise.get_by_role("textbox", name="Question 2").fill("aoi") exercise.get_by_role("button", name="Submit").click() submissions = exercise.get_by_role("button", name=re.compile("My submissions")) - expect(submissions).to_contain_text("1 / 5") #correct submission counts + + if testrun: + expect(submissions).to_contain_text("2 / 5") + else: + expect(submissions).to_contain_text("1 / 5") + #correct submission counts, accepting either 1 or 2 submissions due to overlapping other tests points = exercise.get_by_role("button", name=re.compile("Points")) - expect(points).to_contain_text("20 / 20") + expect(points).to_contain_text("10 / 10") def test_should_not_accept_submission_after_max_submissions_reached(page: Page): diff --git a/e2e_tests/test_user_permissions.py b/e2e_tests/test_user_permissions.py index 78d6ffa16..2453bf577 100644 --- a/e2e_tests/test_user_permissions.py +++ b/e2e_tests/test_user_permissions.py @@ -30,6 +30,34 @@ def test_teacher_permissions(page: Page): expect(page).to_have_title("Edit course | Def. Course | A+") +def test_admin_permissions(page: Page): + page.goto("http://localhost:8010/?hl=en") + login(page, "admin", "admin") + navigate_to_default_course(page) + + left_sidebar = page.locator("#main-course-menu") + expect(left_sidebar).to_contain_text("Course staff") + + expected_links = [ + "Participants", + "Groups", + "All results", + "Visualizations", + "Pseudonymize", + "Edit news", + "Edit course", + "Deadline deviations", + "Submission deviations", + "All submissions", + ] + + for link in expected_links: + expect(left_sidebar.get_by_role("link", name=link)).to_be_visible() + + left_sidebar.get_by_role("link", name="Edit course").click() + expect(page).to_have_title("Edit course | Def. Course | A+") + + def test_assistant_permissions(page: Page): page.goto("http://localhost:8010/?hl=en") login(page, "assistant", "assistant")