Website : rimsha.abasa.com
backdoor
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
var
/
canvas
/
spec
/
selenium
/
helpers
/
Filename :
quizzes_common.rb
back
Copy
# frozen_string_literal: true # # Copyright (C) 2012 - present Instructure, Inc. # # This file is part of Canvas. # # Canvas is free software: you can redistribute it and/or modify it under # the terms of the GNU Affero General Public License as published by the Free # Software Foundation, version 3 of the License. # # Canvas is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more # details. # # You should have received a copy of the GNU Affero General Public License along # with this program. If not, see <http://www.gnu.org/licenses/>. module QuizzesCommon def create_quiz_with_due_date(opts = {}) @context = opts.fetch(:course, @course) @quiz = quiz_model @quiz.generate_quiz_data @quiz.due_at = opts.fetch(:due_at, default_time_for_due_date(Time.zone.now)) @quiz.lock_at = opts.fetch(:lock_at, default_time_for_lock_date(4.days.from_now)) @quiz.unlock_at = opts.fetch(:unlock_at, default_time_for_unlock_date(1.week.ago)) @quiz.save! @quiz end def add_quiz_to_module @course.context_modules.create! name: "Module 1" mod = @course.context_modules.first mod.add_item(type: "quiz", id: @quiz.id) end # The default time for a quiz due date is 11:59pm def default_time_for_due_date(date) date.change({ hour: 23, min: 59 }) end # The default time for a quiz lock date is 11:59pm def default_time_for_lock_date(date) date.change({ hour: 23, min: 59 }) end # The default time for a quiz unlock date is 12am def default_time_for_unlock_date(date) date.change({ hour: 0, min: 0 }) end def assign_quiz_to_no_one f(".ContainerDueDate .ic-token-delete-button").click end def create_multiple_choice_question(opts = {}) question = fj(".question_form:visible") click_option(".question_form:visible .question_type", "Multiple Choice") question_description = opts.fetch(:description, "Hi, this is a multiple choice question.") type_in_tiny ".question_form:visible textarea.question_content", question_description answers = question.find_elements(:css, ".form_answers > .answer") expect(answers.length).to eq 4 replace_content(answers[0].find_element(:css, ".select_answer input"), "Correct Answer") set_answer_comment(0, "Good job!") replace_content(answers[1].find_element(:css, ".select_answer input"), "Wrong Answer #1") set_answer_comment(1, "Bad job :(") replace_content(answers[2].find_element(:css, ".select_answer input"), "Second Wrong Answer") replace_content(answers[3].find_element(:css, ".select_answer input"), "Wrongest Answer") set_question_comment(".question_correct_comment", "Good job on the question!") set_question_comment(".question_incorrect_comment", "You know what they say - study long study wrong.") set_question_comment(".question_neutral_comment", "Pass or fail you are a winner!") button_locator = "//button[contains(.,'Update Question') and not(contains(.,'Create'))]" update_question_button = driver.find_element(:xpath, button_locator) scroll_to(update_question_button) update_question_button.click wait_for_ajaximations end def create_true_false_question question = fj(".question_form:visible") click_option(".question_form:visible .question_type", "True/False") replace_content(question.find_element(:css, "input[name='question_points']"), "4") type_in_tiny ".question:visible textarea.question_content", "This is not a true/false question." answers = question.find_elements(:css, ".form_answers > .answer") expect(answers.length).to eq 2 answers[1].find_element(:css, ".select_answer_link").click # false - get it? set_answer_comment(1, "Good job!") submit_form(question) wait_for_ajaximations end def create_fill_in_the_blank_question question = fj(".question_form:visible") click_option(".question_form:visible .question_type", "Fill In the Blank") replace_content(question.find_element(:css, "input[name='question_points']"), "4") type_in_tiny ".question_form:visible textarea.question_content", "This is a fill in the _________ question." answers = question.find_elements(:css, ".form_answers > .answer") replace_content(answers[0].find_element(:css, ".short_answer input"), "blank") replace_content(answers[1].find_element(:css, ".short_answer input"), "Blank") submit_form(question) wait_for_ajaximations end def create_essay_question question = fj(".question_form:visible") click_option(".question_form:visible .question_type", "Essay Question") replace_content(question.find_element(:css, "input[name='question_points']"), "4") submit_form(question) wait_for_ajaximations end def create_file_upload_question question = fj(".question_form:visible") click_option(".question_form:visible .question_type", "File Upload Question") replace_content(question.find_element(:css, "input[name='question_points']"), "4") type_in_tiny ".question_form:visible textarea.question_content", "This is a file upload question." submit_form(question) wait_for_ajaximations end def add_quiz_question(points) click_questions_tab click_new_question_button wait_for_ajaximations question = fj(".question_form:visible") replace_content(question.find_element(:css, "input[name='question_points']"), points) submit_form(question) wait_for_ajaximations click_settings_tab end def quiz_with_multiple_type_questions(goto_edit = true) @context = @course bank = @context.assessment_question_banks.create!(title: "Test Bank") @quiz = quiz_model a = bank.assessment_questions.create! b = bank.assessment_questions.create! c = bank.assessment_questions.create! answers = [{ "id" => 1 }, { "id" => 2 }, { "id" => 3 }] @quest1 = @quiz.quiz_questions.create!( question_data: { name: "first question", question_type: "multiple_choice_question", answers:, points_possible: 1 }, assessment_question: a ) @quest2 = @quiz.quiz_questions.create!( question_data: { name: "second question", question_text: "What is 5+5?", question_type: "numerical_question", answers: [], points_possible: 1 }, assessment_question: b ) @quest3 = @quiz.quiz_questions.create!( question_data: { name: "third question", question_type: "essay_question", answers: [], points_possible: 1 }, assessment_question: c ) yield bank, @quiz if block_given? @quiz.generate_quiz_data @quiz.save! open_quiz_edit_form if goto_edit @quiz end def quiz_with_essay_questions(goto_edit = true) # TODO: DRY this up @context = @course bank = @context.assessment_question_banks.create!(title: "Test Bank") @quiz = quiz_model a = bank.assessment_questions.create! b = bank.assessment_questions.create! c = bank.assessment_questions.create! @quest1 = @quiz.quiz_questions.create!( question_data: { name: "first question", question_type: "essay_question", answers: [], points_possible: 1 }, assessment_question: a ) @quest2 = @quiz.quiz_questions.create!( question_data: { name: "second question", question_type: "essay_question", answers: [], points_possible: 1 }, assessment_question: b ) @quest3 = @quiz.quiz_questions.create!( question_data: { name: "third question", question_type: "essay_question", answers: [], points_possible: 1 }, assessment_question: c ) yield bank, @quiz if block_given? @quiz.generate_quiz_data @quiz.save! open_quiz_edit_form if goto_edit @quiz end def quiz_with_new_questions(*answers, goto_edit: true) @context = @course bank = @context.assessment_question_banks.create!(title: "Test Bank") @quiz = quiz_model a = bank.assessment_questions.create! b = bank.assessment_questions.create! answers = [{ id: 1 }, { id: 2 }, { id: 3 }] if answers.empty? @quest1 = @quiz.quiz_questions.create!( question_data: { name: "first question", question_type: "multiple_choice_question", answers:, points_possible: 1 }, assessment_question: a ) @quest2 = @quiz.quiz_questions.create!( question_data: { name: "second question", question_type: "multiple_choice_question", answers:, points_possible: 1 }, assessment_question: b ) yield bank, @quiz if block_given? @quiz.generate_quiz_data @quiz.save! open_quiz_edit_form if goto_edit @quiz end def click_settings_tab wait_for_ajaximations fj("#quiz_tabs ul:first a:eq(0)").click end def click_questions_tab wait_for_ajaximations tab = f("a[href='#questions_tab']") keep_trying_until do tab.click true end end # Locate an anchor using its text() node value. The anchor is expected to # contain an "accessible variant"; a span.screenreader-only with a clone of its # text() value. # # @argument text [String] # The label, or text() value, of the anchor. # # We can't use Selenium's `:link_text` because the text() of <a> will actually # contain two times the text value for the reason above, so we'll use XPath # instead. # # We can't use Selenium's `:partial_link_text` or XPath's `fn:contains` either # because we're not after a partial match (ie, "New Question" would match # "New Question Group" and that's incorrect.) def find_accessible_link(text) driver.find_elements(:xpath, "//a[normalize-space(.)=\"#{text} #{text}\"]")[0] end # Matcher for a label (or a word) to be used against a block of text that # contains an accessible variant. # # @argument label [String] # The label, or text() value of the element, to create the matcher for. # # Example: testing the content of an accessible link whose label is 'Publish' # # my_link.text.should match accessible_variant_of 'Publish' # => passes # my_link.text.should == 'Publish' # => fails, text will be 'Publish Publish' # # See #find_accessible_link for more info. def accessible_variant_of(label) /(?:#{label}\s*){2}/ end def click_new_quiz_button f(".new-quiz-link").click wait_for_new_page_load end def click_new_question_button find_accessible_link("New Question").click end def click_quiz_statistics_button find_accessible_link("Quiz Statistics").click end def click_save_settings_button f(".save_quiz_button").click end def start_quiz_question @context = @course quiz_model open_quiz_edit_form wait_for_rce click_questions_tab click_new_question_button wait_for_rce(".question .rce-wrapper") Quizzes::Quiz.last end def rcs_start_quiz_question @context = @course quiz_model open_quiz_edit_form wait_for_ajaximations click_questions_tab wait_for_ajaximations click_new_question_button wait_for_ajaximations Quizzes::Quiz.last end def select_question_from_column_links(question_id) f("#list_question_#{question_id} > a:nth-child(1)").click wait_for_ajaximations end def answer_question(question_answer_id) fj("input[type=radio][value=#{question_answer_id}]").click end def take_quiz @quiz ||= quiz_with_new_questions(goto_edit: false) begin_quiz yield ensure # This step is to prevent selenium from freezing when the dialog appears when leaving the page fln("Quizzes").click driver.switch_to.alert.accept if alert_present? end def take_and_answer_quiz(opts = {}) @quiz = opts[:quiz] if opts.fetch(:quiz, false) submit = opts.fetch(:submit, true) access_code = opts.fetch(:access_code, nil) begin_quiz(access_code, opts) complete_and_submit_quiz(submit) end def begin_quiz(access_code = nil, opts = {}) get take_quiz_url # do this as close to submission creation as possible to avoid being locked by the time the page loads if opts[:lock_after] @quiz.lock_at = Time.now + opts[:lock_after] @quiz.save! end if access_code.nil? wait_for_new_page_load { f("#take_quiz_link").click } else f("#quiz_access_code").send_keys(access_code) wait_for_new_page_load { fj(".btn", "#main").click } end or raise "unable to start quiz" end def submit_quiz f("#submit_quiz_button").click accept_alert if alert_present? wait_for_ajax_requests expect(f(".quiz-submission .quiz_score .score_value")).to be_truthy end def preview_quiz(submit = true) open_quiz_show_page expect_new_page_load { f("#preview_quiz_button").click } complete_and_submit_quiz(submit) end def wait_for_quiz_publish_button_to_populate link = f("#quiz-publish-link") keep_trying_until { link.text.present? } end # @argument answer_chooser [#call] # You can pass a block to specify which answer to choose, the block will # receive the set of possible answers. If you don't, the first (and correct) # answer will be chosen. def complete_and_submit_quiz(submit = true) answer = if block_given? yield(@quiz.stored_questions[0][:answers]) else @quiz.stored_questions[0][:answers][0][:id] end answer_question(answer) if answer submit_quiz if submit end def answer_questions_and_submit(quiz, num_questions, submit = true) num_questions.times do |o| question = quiz.stored_questions[o][:id] case quiz.stored_questions[o][:question_type] when "multiple_choice_question" fj("input[type=radio][name= 'question_#{question}']").click when "essay_question" type_in_tiny ".question:visible textarea[name = 'question_#{question}']", "This is an essay question." when "numerical_question" fj("input[type=text][name= 'question_#{question}']").send_keys("10") end end submit_quiz if submit end def set_answer_comment(answer_num, text) driver.execute_script("$('.question_form:visible .form_answers .answer:eq(#{answer_num}) .answer_comments').click()") wait_for_ajaximations type_in_tiny(".question_form:visible .form_answers .answer:eq(#{answer_num}) .answer_comments textarea", text) end def set_question_comment(selector, text) driver.execute_script("$('.question_form:visible #{selector} .comment_focus').click()") wait_for_ajaximations type_in_tiny(".question_form:visible #{selector} textarea", text) end def question_answers ffj(".answer", ".form_answers") end def delete_possible_answer(question_answer_index) question_answer = question_answers[question_answer_index] hover(question_answer) delete_question_link = fj(".delete_answer_link", question_answer) hover(delete_question_link) delete_question_link.click end def select_different_correct_answer(index_of_new_correct_answer) new_correct_answer = fj(".select_answer_link", question_answers[index_of_new_correct_answer]) hover(new_correct_answer) new_correct_answer.click wait_for_ajaximations end def hover_first_question question = f(".display_question") hover(question) end def select_regrade_option(option_index = 0) visible_regrade_options[option_index].click fj(".ui-dialog:visible .btn-primary").click wait_for_ajaximations end def visible_regrade_options ffj("label.checkbox:visible", ".regrade_enabled") end # clicks |Okay, fine| def close_times_up_dialog times_up_dialog = fj("div#times_up_dialog:visible") fj("button.submit_quiz_button", times_up_dialog).click unless times_up_dialog.nil? end def edit_first_question hover_first_question wait_for_animations exceptions = [Selenium::WebDriver::Error::ElementNotInteractableError, Selenium::WebDriver::Error::ElementClickInterceptedError] begin # for some reason in flakey-spec-catcher this will fail 1/10 times # have a backup plan f(".edit_question_link").click rescue *exceptions # rubocop:disable Specs/NoExecuteScript driver.execute_script "document.querySelector('.edit_question_link').click()" # rubocop:enable Specs/NoExecuteScript ensure wait_for_ajaximations end end def save_question submit_form(".question_form") wait_for_ajax_requests end def change_quiz_type_to(option_text) click_option "#quiz_assignment_id", option_text end def save_settings wait_for_new_page_load { f(".save_quiz_button").click } end def edit_quiz expect_new_page_load { f(".quiz-edit-button").click } end def cancel_quiz_edit expect_new_page_load { fj("#cancel_button", "div#quiz_edit_actions").click } end def edit_first_multiple_choice_answer(text) element = fj("input[name=answer_text]:visible") element.click element.send_keys text end def edit_and_save_first_multiple_choice_answer(text) edit_first_question edit_first_multiple_choice_answer text save_question end def delete_first_multiple_choice_answer driver.execute_script "$('.answer').addClass('hover');" fj(".delete_answer_link:visible").click end ## # creates a question group through the browser def create_question_group click_questions_tab find_accessible_link("New Question Group").click submit_form("#group_top_new form") wait_for_ajax_requests @group = Quizzes::QuizGroup.last end ## # Returns the question/group data as an array of hashes # # a question hash looks like this: # # {:id => 23, :el => <#SeleniumElement>, :type => 'question'} # # a group looks like # # {:id => 2, :el => <#SeleniumElement>, :type => 'group', :questions => []} # # where :questions is an array of questions in the group def get_question_data els = ff "#questions > *" last_group_id = nil data = [] els.each do |el| # its a question if el["class"].include?("question_holder") id = el.find_element(:css, "a")["name"].gsub("question_", "") question = { id: id.to_i, el:, type: "question" } if last_group_id # add question to last group data.last[:questions] << question else # not in a group data << question end # its a group elsif el["class"].include?("group_top") last_group_id = el["id"].gsub("group_top_", "").to_i data << { id: last_group_id, questions: [], type: "group", el: } # group ended elsif el["class"].include?("group_bottom") last_group_id = nil end end data end ## # Gets the questions hashes out of a group def get_question_data_for_group(id) data = get_question_data group_data = data.detect do |item| item[:type] == "group" && item[:id] == id end group_data[:questions] end ## # moves the cursor to a question preparatory to dragging it def move_to_question(id) element = f "#question_#{id}" driver.action.move_to(element).perform end ## # moves the cursor to a group preparatory to dragging it def move_to_group(id) group = f "#group_top_#{id}" driver.action.move_to(group).perform end ## # Drags a question with ActiveRecord id `question_id` into group with # ActiveRecord id `group_id` def drag_question_into_group(question_id, group_id) move_to_question question_id source = "#question_#{question_id} .draggable-handle" target = "#group_top_#{group_id}" # drag math gets off if we don't do this and things end up dropped in the wrong place scroll_page_to_top js_drag_and_drop source, target end ## # Asserts that a group contains a question both in the database and # in the interface def group_should_contain_question(group, question) # check active record question.reload expect(question.quiz_group_id).to eq group.id # check the interface questions = get_question_data_for_group group.id expect(questions.detect { |item| item[:id] == question.id }).not_to be_nil end ## # Drags a question with ActiveRecord id of `id` to the top of the list def drag_question_to_top(id) move_to_question id source = "#question_#{id} .draggable-handle" target = "#questions > *" # drag math gets off if we don't do this and things end up dropped in the wrong place scroll_page_to_top js_drag_and_drop source, target end ## # Drags a group with ActiveRecord id of `id` to the top of the question list def drag_group_to_top(id) move_to_group id source = "#group_top_#{id} .draggable-handle" target = "#questions > *" # drag math gets off if we don't do this and things end up dropped in the wrong place scroll_page_to_top js_drag_and_drop source, target end ## # Drags a question to the top of the group def drag_question_to_top_of_group(question_id, group_id) move_to_question question_id source = "#question_#{question_id} .draggable-handle" target = "#group_top_#{group_id} + *" # drag math gets off if we don't do this and things end up dropped in the wrong place scroll_page_to_top js_drag_and_drop source, target end def quiz_create(opts = {}) course = opts.fetch(:course, @course) @quiz = course.quizzes.create data = { question_name: "Question 1", points_possible: 1, question_text: "This is a multiple choice question", answers: [ { weight: 100, answer_text: "A", answer_comments: "", id: 1490 }, { weight: 0, answer_text: "B", answer_comments: "", id: 1020 }, { weight: 0, answer_text: "C", answer_comments: "", id: 7051 } ], question_type: "multiple_choice_question" } @quiz.quiz_questions.create!(question_data: data) @quiz.allowed_attempts = -1 if opts.fetch(:unlimited_attempts, false) @quiz.lock_at = opts.fetch(:lock_at, nil) @quiz.due_at = opts.fetch(:due_at, nil) @quiz.unlock_at = opts.fetch(:unlock_at, nil) @quiz.generate_quiz_data @quiz.publish! @quiz.save! @quiz end def seed_quiz_with_submission(num = 1, opts = {}) quiz_data = opts[:question_data] || [ { question_name: "Multiple Choice", points_possible: 10, question_text: "Pick wisely...", answers: [ { weight: 100, answer_text: "Correct", id: 1 }, { weight: 0, answer_text: "Wrong", id: 2 }, { weight: 0, answer_text: "Wrong", id: 3 } ], question_type: "multiple_choice_question" }, { question_name: "File Upload", points_possible: 5, question_text: "Upload a file", question_type: "file_upload_question" }, { question_name: "Short Essay", points_possible: 20, question_text: "Write an essay", question_type: "essay_question" }, { question_name: "Text (no question)", question_text: "This is just text", question_type: "text_only_question" } ] quiz = @course.quizzes.create title: "Quiz Me!" num.times do quiz_data.each do |question| quiz.quiz_questions.create! question_data: question end end quiz.workflow_state = "available" quiz.save! submission = quiz.generate_submission opts[:student] || @students[0] submission.workflow_state = "complete" submission.save! quiz end def verify_quiz_show_page_due_date(due_date) open_quiz_show_page expect(f("#quiz_show")).to include_text due_date end def verify_quiz_is_locked open_quiz_show_page unless driver.current_url == quiz_show_page_url expect(fj(".lock_explanation")).to include_text "This quiz was locked" end def verify_quiz_is_submitted open_quiz_show_page unless driver.current_url == quiz_show_page_url expect(fj(".quiz-submission")).to include_text "Submitted" end def verify_quiz_submission_is_late verify_quiz_submission_late_status(late: true) end def verify_quiz_submission_is_not_late verify_quiz_submission_late_status(late: false) end def open_quiz_show_page get quiz_show_page_url end def open_student_quiz_submission get quiz_student_submission_url end def verify_quiz_submission_is_late_in_speedgrader verify_quiz_submission_status_in_speedgrader(late: true) end def verify_quiz_submission_is_not_late_in_speedgrader verify_quiz_submission_status_in_speedgrader(late: false) end def open_quiz_in_speedgrader user_session(@teacher) get quiz_submission_speedgrader_url end def open_quiz_edit_form(should_wait_for_rce = true) get quiz_edit_form_url wait_for_rce if should_wait_for_rce end private def quiz_show_page_url "/courses/#{@course.id}/quizzes/#{@quiz.id}" end def quiz_edit_form_url "/courses/#{@course.id}/quizzes/#{@quiz.id}/edit" end def quiz_student_submission_url "/courses/#{@course.id}/assignments/#{@quiz.assignment_id}/submissions/#{@student.id}" end def quiz_submission_speedgrader_url "/courses/#{@course.id}/gradebook/speed_grader?assignment_id=#{@quiz.assignment_id}" end def take_quiz_url "/courses/#{@course.id}/quizzes/#{@quiz.id}/take?user_id=#{@user.id}" end def verify_quiz_submission_late_status(late:) open_student_quiz_submission submission_page_info = f(".submission_details", f("#not_right_side")) if late expect(submission_page_info).to contain_css(".submission-late-pill") else expect(submission_page_info).not_to contain_css(".submission-late-pill") end end def verify_quiz_submission_status_in_speedgrader(late:) open_quiz_in_speedgrader speedgrader_submission_details = f("#submission_details", f(".right_side_content")) if late expect(speedgrader_submission_details).to contain_css(".submission-late-pill") else expect(speedgrader_submission_details).not_to contain_css(".submission-late-pill") end end def generate_and_save_submission(quiz, student) submission = quiz.generate_submission student submission.workflow_state = "complete" submission.save! end end