problem.py 13.9 KB
Newer Older
1 2 3 4
"""
Problem Page.
"""
from bok_choy.page_object import PageObject
5 6
from common.test.acceptance.pages.common.utils import click_css
from selenium.webdriver.common.keys import Keys
7 8 9 10 11 12 13 14


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

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

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

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

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

35
    @property
36 37 38 39 40 41 42
    def problem_content(self):
        """
        Return the content of the problem
        """
        return self.q(css="div.problems-wrapper").text[0]

    @property
43 44 45 46 47 48 49
    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
50 51 52 53
    def extract_hint_text_from_html(self):
        """
        Return the "hint" text of the problem from html
        """
54 55
        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]
56 57

    @property
58 59 60 61
    def hint_text(self):
        """
        Return the "hint" text of the problem from its div.
        """
62
        return self.q(css="div.problem .notification-hint .notification-message").text[0]
63

64
    def verify_mathjax_rendered_in_problem(self):
65 66 67
        """
        Check that MathJax have been rendered in problem hint
        """
68 69
        def mathjax_present():
            """ Returns True if MathJax css is present in the problem body """
70
            mathjax_container = self.q(css="div.problem p .MathJax_SVG")
71
            return mathjax_container.visible and mathjax_container.present
72

73 74 75 76 77 78
        self.wait_for(
            mathjax_present,
            description="MathJax rendered in problem body"
        )

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

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

92
    def fill_answer(self, text, input_num=None):
93 94
        """
        Fill in the answer to the problem.
95 96 97 98 99 100 101

        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.
102
        """
103 104 105
        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)
106

107 108 109 110
    def fill_answer_numerical(self, text):
        """
        Fill in the answer to a numerical problem.
        """
111
        self.q(css='div.problem div.inputtype input').fill(text)
112
        self.wait_for_element_invisibility('.loading', 'wait for loading icon to disappear')
113
        self.wait_for_ajax()
114

115
    def click_submit(self):
116
        """
117
        Click the Submit button.
118
        """
119
        click_css(self, '.problem .submit', require_notification=False)
120

121 122 123 124
    def click_save(self):
        """
        Click the Save button.
        """
125
        click_css(self, '.problem .save', require_notification=False)
126 127 128 129 130

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

133 134 135 136 137
    def click_show(self):
        """
        Click the Show Answer button.
        """
        self.q(css='.problem .show').click()
138 139
        self.wait_for_ajax()

140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
    def is_hint_notification_visible(self):
        """
        Is the Hint Notification visible?
        """
        return self.q(css='.notification.notification-hint').visible

    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

    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')

    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.
        """
        return self.q(css='.problem-header').focused

    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')

222 223 224 225
    def wait_for_status_icon(self):
        """
        wait for status icon
        """
226
        self.wait_for_element_visibility('div.problem div.inputtype div .status', 'wait for status icon')
227

228 229 230 231 232 233 234 235 236 237 238
    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)

239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265
    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()

266 267 268 269
    def click_hint(self):
        """
        Click the Hint button.
        """
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299
        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'
        )

    def click_review_in_notification(self):
        """
        Click on the "Review" button within the visible notification.
        """
        # 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)
            return self.q(css='.notification .review-btn').focused

        self.wait_for(tab_until_review_focused, 'Waiting for the Review button to become focused')

        click_css(self, '.notification .review-btn', require_notification=False)

    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')
300

301 302 303 304
    def click_choice(self, choice_value):
        """
        Click the choice input(radio, checkbox or option) where value matches `choice_value` in choice group.
        """
305
        self.q(css='div.problem .choicegroup input[value="' + choice_value + '"]').first.click()
306 307
        self.wait_for_ajax()

308 309 310 311
    def is_correct(self):
        """
        Is there a "correct" status showing?
        """
312
        return self.q(css="div.problem div.capa_inputtype.textline div.correct span.status").is_present()
313

314 315 316 317
    def simpleprob_is_correct(self):
        """
        Is there a "correct" status showing? Works with simple problem types.
        """
318
        return self.q(css="div.problem div.inputtype div.correct span.status").is_present()
319 320 321 322 323

    def simpleprob_is_partially_correct(self):
        """
        Is there a "partially correct" status showing? Works with simple problem types.
        """
324
        return self.q(css="div.problem div.inputtype div.partially-correct span.status").is_present()
325 326 327 328 329

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

332 333 334 335 336 337
    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
        """
338
        self.q(css='div.problem .clarification:nth-child({index}) span[data-tooltip]'.format(index=index + 1)).click()
339 340 341 342 343 344 345 346

    @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]
347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363

    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()

    def is_correct_choice_highlighted(self, correct_choices):
        """
        Check if correct answer/choice highlighted for choice group.
        """
        xpath = '//fieldset/div[contains(@class, "field")][{0}]/label[contains(@class, "choicegroup_correct")]'
        for choice in correct_choices:
            if not self.q(xpath=xpath.format(choice)).is_present():
                return False
        return True
364 365 366 367 368 369 370 371 372 373 374 375 376 377

    @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
378 379 380 381 382 383 384 385

    @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]
386 387 388 389 390 391 392

    @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]