problem.py 17.6 KB
Newer Older
1 2 3 4
"""
Problem Page.
"""
from bok_choy.page_object import PageObject
5
from selenium.webdriver.common.keys import Keys
6

7 8
from common.test.acceptance.pages.common.utils import click_css

9 10 11 12 13 14 15

class ProblemPage(PageObject):
    """
    View of problem page.
    """

    url = None
16
    CSS_PROBLEM_HEADER = '.problem-header'
17 18 19 20 21 22 23 24 25

    def is_browser_on_page(self):
        return self.q(css='.xblock-student_view').present

    @property
    def problem_name(self):
        """
        Return the current problem name.
        """
26
        self.wait_for_element_visibility(self.CSS_PROBLEM_HEADER, 'wait for problem header')
27
        return self.q(css='.problem-header').text[0]
28 29 30 31 32 33 34 35

    @property
    def problem_text(self):
        """
        Return the text of the question of the problem.
        """
        return self.q(css="div.problem p").text

36
    @property
37 38 39 40 41 42 43
    def problem_input_content(self):
        """
        Return the text of the question of the problem.
        """
        return self.q(css="div.wrapper-problem-response").text[0]

    @property
44 45 46 47 48 49 50
    def problem_content(self):
        """
        Return the content of the problem
        """
        return self.q(css="div.problems-wrapper").text[0]

    @property
51 52 53 54 55 56 57
    def problem_meta(self):
        """
        Return the problem meta text
        """
        return self.q(css=".problems-wrapper .problem-progress").text[0]

    @property
58 59 60 61 62 63 64
    def message_text(self):
        """
        Return the "message" text of the question of the problem.
        """
        return self.q(css="div.problem span.message").text[0]

    @property
65 66 67 68
    def extract_hint_text_from_html(self):
        """
        Return the "hint" text of the problem from html
        """
69 70
        hints_html = self.q(css="div.problem .notification-hint .notification-message li").html
        return [hint_html.split(' <span', 1)[0] for hint_html in hints_html]
71 72

    @property
73 74 75 76
    def hint_text(self):
        """
        Return the "hint" text of the problem from its div.
        """
77
        return self.q(css="div.problem .notification-hint .notification-message").text[0]
78

79
    def verify_mathjax_rendered_in_problem(self):
80 81 82
        """
        Check that MathJax have been rendered in problem hint
        """
83 84
        def mathjax_present():
            """ Returns True if MathJax css is present in the problem body """
85
            mathjax_container = self.q(css="div.problem p .MathJax_SVG")
86
            return mathjax_container.visible and mathjax_container.present
87

88 89 90 91 92 93
        self.wait_for(
            mathjax_present,
            description="MathJax rendered in problem body"
        )

    def verify_mathjax_rendered_in_hint(self):
94 95 96
        """
        Check that MathJax have been rendered in problem hint
        """
97 98
        def mathjax_present():
            """ Returns True if MathJax css is present in the problem body """
99
            mathjax_container = self.q(css="div.problem div.problem-hint .MathJax_SVG")
100 101 102 103 104 105
            return mathjax_container.visible and mathjax_container.present

        self.wait_for(
            mathjax_present,
            description="MathJax rendered in hint"
        )
106

107
    def fill_answer(self, text, input_num=None):
108 109
        """
        Fill in the answer to the problem.
110 111 112 113 114 115 116

        args:
            text: String to fill the input with.

        kwargs:
            input_num: If provided, fills only the input_numth field. Else, all
                input fields will be filled.
117
        """
118 119 120
        fields = self.q(css='div.problem div.capa_inputtype.textline input')
        fields = fields.nth(input_num) if input_num is not None else fields
        fields.fill(text)
121

122 123 124 125
    def fill_answer_numerical(self, text):
        """
        Fill in the answer to a numerical problem.
        """
126
        self.q(css='div.problem div.inputtype input').fill(text)
127
        self.wait_for_element_invisibility('.loading', 'wait for loading icon to disappear')
128
        self.wait_for_ajax()
129

130
    def click_submit(self):
131
        """
132
        Click the Submit button.
133
        """
134
        click_css(self, '.problem .submit', require_notification=False)
135

136 137 138 139
    def click_save(self):
        """
        Click the Save button.
        """
140
        click_css(self, '.problem .save', require_notification=False)
141 142 143 144 145

    def click_reset(self):
        """
        Click the Reset button.
        """
146
        click_css(self, '.problem .reset', require_notification=False)
147

148 149 150 151
    def click_show(self):
        """
        Click the Show Answer button.
        """
152 153 154 155
        css = '.problem .show'
        # First make sure that the button visible and can be clicked on.
        self.scroll_to_element(css)
        self.q(css=css).click()
156 157
        self.wait_for_ajax()

158 159 160 161 162 163
    def is_hint_notification_visible(self):
        """
        Is the Hint Notification visible?
        """
        return self.q(css='.notification.notification-hint').visible

164 165 166 167 168 169
    def is_feedback_message_notification_visible(self):
        """
        Is the Feedback Messaged notification visible
        """
        return self.q(css='.wrapper-problem-response .message').visible

170 171 172 173 174 175 176 177 178 179 180 181
    def is_save_notification_visible(self):
        """
        Is the Save Notification Visible?
        """
        return self.q(css='.notification.warning.notification-save').visible

    def is_success_notification_visible(self):
        """
        Is the Submit Notification Visible?
        """
        return self.q(css='.notification.success.notification-submit').visible

182 183 184 185 186 187 188
    def wait_for_feedback_message_visibility(self):
        """
        Wait for the Feedback Message notification to be visible.
        """
        self.wait_for_element_visibility('.wrapper-problem-response .message',
                                         'Waiting for the Feedback message to be visible')

189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
    def wait_for_save_notification(self):
        """
        Wait for the Save Notification to be present
        """
        self.wait_for_element_visibility('.notification.warning.notification-save',
                                         'Waiting for Save notification to be visible')
        self.wait_for(lambda: self.q(css='.notification.warning.notification-save').focused,
                      'Waiting for the focus to be on the save notification')

    def wait_for_gentle_alert_notification(self):
        """
        Wait for the Gentle Alert Notification to be present
        """
        self.wait_for_element_visibility('.notification.warning.notification-gentle-alert',
                                         'Waiting for Gentle Alert notification to be visible')
        self.wait_for(lambda: self.q(css='.notification.warning.notification-gentle-alert').focused,
                      'Waiting for the focus to be on the gentle alert notification')

207 208 209 210 211 212 213 214 215
    def wait_for_show_answer_notification(self):
        """
        Wait for the show answer Notification to be present
        """
        self.wait_for_element_visibility('.notification.general.notification-show-answer',
                                         'Waiting for Show Answer notification to be visible')
        self.wait_for(lambda: self.q(css='.notification.general.notification-show-answer').focused,
                      'Waiting for the focus to be on the show answer notification')

216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233
    def is_gentle_alert_notification_visible(self):
        """
        Is the Gentle Alert Notification visible?
        """
        return self.q(css='.notification.warning.notification-gentle-alert').visible

    def is_reset_button_present(self):
        """ Check for the presence of the reset button. """
        return self.q(css='.problem .reset').present

    def is_save_button_enabled(self):
        """ Is the Save button enabled """
        return self.q(css='.action .save').attrs('disabled') == [None]

    def is_focus_on_problem_meta(self):
        """
        Check for focus problem meta.
        """
234 235 236 237 238 239 240 241 242 243
        return self.q(css='.problem-header').focused

    def wait_for_focus_on_problem_meta(self):
        """
        Waits for focus on Problem Meta section
        """
        self.wait_for(
            promise_check_func=self.is_focus_on_problem_meta,
            description='Waiting for focus on Problem Meta section'
        )
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270

    def is_submit_disabled(self):
        """
        Checks if the submit button is disabled
        """
        disabled_attr = self.q(css='.problem .submit').attrs('disabled')[0]
        return disabled_attr == 'true'

    def wait_for_submit_disabled(self):
        """
        Waits until the Submit button becomes disabled.
        """
        self.wait_for(self.is_submit_disabled, 'Waiting for submit to be enabled')

    def wait_for_focus_on_submit_notification(self):
        """
        Check for focus submit notification.
        """

        def focus_check():
            """
            Checks whether or not the focus is on the notification-submit
            """
            return self.q(css='.notification-submit').focused

        self.wait_for(promise_check_func=focus_check, description='Waiting for the notification-submit to gain focus')

271 272 273 274
    def wait_for_status_icon(self):
        """
        wait for status icon
        """
275
        self.wait_for_element_visibility('div.problem div.inputtype div .status', 'wait for status icon')
276

277 278 279 280 281 282 283 284 285 286 287
    def wait_for_expected_status(self, status_selector, message):
        """
        Waits for the expected status indicator.

        Args:
            status_selector(str): status selector string.
            message(str): description of promise, to be logged.
        """
        msg = "Wait for status to be {}".format(message)
        self.wait_for_element_visibility(status_selector, msg)

288 289 290 291 292 293 294 295 296
    def is_expected_status_visible(self, status_selector):
        """
        check for the expected status indicator to be visible.

        Args:
            status_selector(str): status selector string.
        """
        return self.q(css=status_selector).visible

297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
    def wait_success_notification(self):
        """
        Check for visibility of the success notification and icon.
        """
        msg = "Wait for success notification to be visible"
        self.wait_for_element_visibility('.notification.success.notification-submit', msg)
        self.wait_for_element_visibility('.fa-check', "Waiting for success icon")
        self.wait_for_focus_on_submit_notification()

    def wait_incorrect_notification(self):
        """
        Check for visibility of the incorrect notification and icon.
        """
        msg = "Wait for error notification to be visible"
        self.wait_for_element_visibility('.notification.error.notification-submit', msg)
        self.wait_for_element_visibility('.fa-close', "Waiting for incorrect notification icon")
        self.wait_for_focus_on_submit_notification()

    def wait_partial_notification(self):
        """
        Check for visibility of the partially visible notification and icon.
        """
        msg = "Wait for partial correct notification to be visible"
        self.wait_for_element_visibility('.notification.success.notification-submit', msg)
        self.wait_for_element_visibility('.fa-asterisk', "Waiting for asterisk notification icon")
        self.wait_for_focus_on_submit_notification()

324 325 326 327 328 329 330 331
    def wait_submitted_notification(self):
        """
        Check for visibility of the "answer received" general notification and icon.
        """
        msg = "Wait for submitted notification to be visible"
        self.wait_for_element_visibility('.notification.general.notification-submit', msg)
        self.wait_for_focus_on_submit_notification()

332 333 334 335
    def click_hint(self):
        """
        Click the Hint button.
        """
336 337 338 339 340 341 342 343 344 345 346 347
        click_css(self, '.problem .hint-button', require_notification=False)
        self.wait_for_focus_on_hint_notification()

    def wait_for_focus_on_hint_notification(self):
        """
        Wait for focus to be on the hint notification.
        """
        self.wait_for(
            lambda: self.q(css='.notification-hint').focused,
            'Waiting for the focus to be on the hint notification'
        )

348
    def click_review_in_notification(self, notification_type):
349 350 351
        """
        Click on the "Review" button within the visible notification.
        """
352 353 354 355
        css_string = '.notification.notification-{notification_type} .review-btn'.format(
            notification_type=notification_type
        )

356 357 358 359 360
        # The review button cannot be clicked on until it is tabbed to, so first tab to it.
        # Multiple tabs may be required depending on the content (for instance, hints with links).
        def tab_until_review_focused():
            """ Tab until the review button is focused """
            self.browser.switch_to_active_element().send_keys(Keys.TAB)
361 362 363
            if self.q(css=css_string).focused:
                self.scroll_to_element(css_string)
            return self.q(css=css_string).focused
364

365 366 367 368 369 370 371 372 373
        self.wait_for(
            tab_until_review_focused,
            'Waiting for the Review button to become focused'
        )
        self.wait_for_element_visibility(
            css_string,
            'Waiting for the button to be visible'
        )
        click_css(self, css_string, require_notification=False)
374 375 376 377

    def get_hint_button_disabled_attr(self):
        """ Return the disabled attribute of all hint buttons (once hints are visible, there will be two). """
        return self.q(css='.problem .hint-button').attrs('disabled')
378

379 380 381 382
    def click_choice(self, choice_value):
        """
        Click the choice input(radio, checkbox or option) where value matches `choice_value` in choice group.
        """
383
        self.q(css='div.problem .choicegroup input[value="' + choice_value + '"]').first.click()
384 385
        self.wait_for_ajax()

386 387 388 389
    def is_correct(self):
        """
        Is there a "correct" status showing?
        """
390
        return self.q(css="div.problem div.capa_inputtype.textline div.correct span.status").is_present()
391

392 393 394 395
    def simpleprob_is_correct(self):
        """
        Is there a "correct" status showing? Works with simple problem types.
        """
396
        return self.q(css="div.problem div.inputtype div.correct span.status").is_present()
397 398 399 400 401

    def simpleprob_is_partially_correct(self):
        """
        Is there a "partially correct" status showing? Works with simple problem types.
        """
402
        return self.q(css="div.problem div.inputtype div.partially-correct span.status").is_present()
403 404 405 406 407

    def simpleprob_is_incorrect(self):
        """
        Is there an "incorrect" status showing? Works with simple problem types.
        """
408
        return self.q(css="div.problem div.inputtype div.incorrect span.status").is_present()
409

410 411 412 413 414 415
    def click_clarification(self, index=0):
        """
        Click on an inline icon that can be included in problem text using an HTML <clarification> element:

        Problem <clarification>clarification text hidden by an icon in rendering</clarification> Text
        """
416
        self.q(css='div.problem .clarification:nth-child({index}) span[data-tooltip]'.format(index=index + 1)).click()
417 418 419 420 421 422 423 424

    @property
    def visible_tooltip_text(self):
        """
        Get the text seen in any tooltip currently visible on the page.
        """
        self.wait_for_element_visibility('body > .tooltip', 'A tooltip is visible.')
        return self.q(css='body > .tooltip').text[0]
425 426 427 428 429 430 431 432

    def is_solution_tag_present(self):
        """
        Check if solution/explanation is shown.
        """
        solution_selector = '.solution-span div.detailed-solution'
        return self.q(css=solution_selector).is_present()

433
    def is_choice_highlighted(self, choice, choices_list):
434
        """
435
        Check if the given answer/choice is highlighted for choice group.
436
        """
437 438 439
        choice_status_xpath = ('//fieldset/div[contains(@class, "field")][{{0}}]'
                               '/label[contains(@class, "choicegroup_{choice}")]'
                               '/span[contains(@class, "status {choice}")]'.format(choice=choice))
440
        any_status_xpath = '//fieldset/div[contains(@class, "field")][{0}]/label/span'
441 442
        for choice in choices_list:
            if not self.q(xpath=choice_status_xpath.format(choice)).is_present():
443
                return False
444 445 446 447 448 449

            # Check that there is only a single status span, as there were some bugs with multiple
            # spans (with various classes) being appended.
            if not len(self.q(xpath=any_status_xpath.format(choice)).results) == 1:
                return False

450
        return True
451

452 453 454 455 456 457 458 459 460 461 462 463
    def is_correct_choice_highlighted(self, correct_choices):
        """
        Check if correct answer/choice highlighted for choice group.
        """
        return self.is_choice_highlighted('correct', correct_choices)

    def is_submitted_choice_highlighted(self, correct_choices):
        """
        Check if submitted answer/choice highlighted for choice group.
        """
        return self.is_choice_highlighted('submitted', correct_choices)

464 465 466 467 468 469 470 471 472 473 474 475 476
    @property
    def problem_question(self):
        """
        Return the question text of the problem.
        """
        return self.q(css="div.problem .wrapper-problem-response legend").text[0]

    @property
    def problem_question_descriptions(self):
        """
        Return a list of question descriptions of the problem.
        """
        return self.q(css="div.problem .wrapper-problem-response .question-description").text
477 478 479 480 481 482 483 484

    @property
    def problem_progress_graded_value(self):
        """
        Return problem progress text which contains weight of problem, if it is graded, and the student's current score.
        """
        self.wait_for_element_visibility('.problem-progress', "Problem progress is visible")
        return self.q(css='.problem-progress').text[0]
485 486 487 488 489 490 491

    @property
    def status_sr_text(self):
        """
        Returns the text in the special "sr" region used for display status.
        """
        return self.q(css='#reader-feedback').text[0]