This repository has been archived by the owner on Oct 5, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 25
/
base_test.py
174 lines (148 loc) · 7.5 KB
/
base_test.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
#
# Copyright (C) 2014-2015 edX
#
# This software's license gives you freedom; you can copy, convey,
# propagate, redistribute and/or modify this program under the terms of
# the GNU Affero General Public License (AGPL) as published by the Free
# Software Foundation (FSF), either version 3 of the License, or (at your
# option) any later version of the AGPL published by the FSF.
#
# This program 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 in a file in the toplevel directory called
# "AGPLv3". If not, see <http://www.gnu.org/licenses/>.
#
"""
Base classes for Selenium or bok-choy based integration tests of XBlocks.
"""
import time
from selenium.webdriver.support.ui import WebDriverWait
from workbench.runtime import WorkbenchRuntime
from workbench.scenarios import SCENARIOS, add_xml_scenario, remove_scenario
from workbench.test.selenium_test import SeleniumTest
from .resources import ResourceLoader
class SeleniumXBlockTest(SeleniumTest):
"""
Base class for using the workbench to test XBlocks with Selenium or bok-choy.
If you want to test an XBlock that's not already installed into the python environment,
you can use @XBlock.register_temp_plugin around your test method[s].
"""
timeout = 10 # seconds
def setUp(self):
super().setUp()
# Delete all scenarios from the workbench:
# Trigger initial scenario load.
import workbench.urls # pylint: disable=import-outside-toplevel
SCENARIOS.clear()
# Disable CSRF checks on XBlock handlers:
import workbench.views # pylint: disable=import-outside-toplevel
workbench.views.handler.csrf_exempt = True
def wait_until_visible(self, elem):
""" Wait until the given element is visible """
wait = WebDriverWait(elem, self.timeout)
wait.until(lambda e: e.is_displayed(), f"{elem.text} should be visible")
def wait_until_hidden(self, elem):
""" Wait until the DOM element elem is hidden """
wait = WebDriverWait(elem, self.timeout)
wait.until(lambda e: not e.is_displayed(), f"{elem.text} should be hidden")
def wait_until_disabled(self, elem):
""" Wait until the DOM element elem is disabled """
wait = WebDriverWait(elem, self.timeout)
wait.until(lambda e: not e.is_enabled(), f"{elem.text} should be disabled")
def wait_until_clickable(self, elem):
""" Wait until the DOM element elem is display and enabled """
wait = WebDriverWait(elem, self.timeout)
wait.until(lambda e: e.is_displayed() and e.is_enabled(), f"{elem.text} should be clickable")
def wait_until_text_in(self, text, elem):
""" Wait until the specified text appears in the DOM element elem """
wait = WebDriverWait(elem, self.timeout)
wait.until(lambda e: text in e.text, f"{text} should be in {elem.text}")
def wait_until_html_in(self, html, elem):
""" Wait until the specified HTML appears in the DOM element elem """
wait = WebDriverWait(elem, self.timeout)
wait.until(lambda e: html in e.get_attribute('innerHTML'),
"{} should be in {}".format(html, elem.get_attribute('innerHTML')))
def wait_until_exists(self, selector):
""" Wait until the specified selector exists on the page """
wait = WebDriverWait(self.browser, self.timeout)
wait.until(
lambda driver: driver.find_element_by_css_selector(selector),
f"Selector '{selector}' should exist."
)
@staticmethod
def set_scenario_xml(xml):
""" Reset the workbench to have only one scenario with the specified XML """
SCENARIOS.clear()
add_xml_scenario("test", "Test Scenario", xml)
def go_to_view(self, view_name='student_view', student_id="student_1"):
"""
Navigate to the page `page_name`, as listed on the workbench home
Returns the DOM element on the visited page located by the `css_selector`
"""
url = self.live_server_url + f'/scenario/test/{view_name}/'
if student_id:
url += f'?student={student_id}'
self.browser.get(url)
return self.browser.find_element_by_css_selector('.workbench .preview > div.xblock-v1:first-child')
def load_root_xblock(self, student_id="student_1"):
"""
Load (in Python) the XBlock at the root of the current scenario.
"""
dom_node = self.browser.find_element_by_css_selector('.workbench .preview > div.xblock-v1:first-child')
usage_id = dom_node.get_attribute('data-usage')
runtime = WorkbenchRuntime(student_id)
return runtime.get_block(usage_id)
class SeleniumBaseTest(SeleniumXBlockTest):
"""
Selenium Base Test for loading a whole folder of XML scenarios and then running tests.
This is kept for compatibility, but it is recommended that SeleniumXBlockTest be used
instead, since it is faster and more flexible (specifically, scenarios are only loaded
as needed, and can be defined inline with the tests).
"""
module_name = None # You must set this to __name__ in any subclass so ResourceLoader can find scenario XML files
default_css_selector = None # Selector used by go_to_page to return the XBlock DOM element
relative_scenario_path = 'xml' # Path from the module (module_name) to the secnario XML files
@property
def _module_name(self):
""" Internal method to access module_name with a friendly warning if it's unset """
if self.module_name is None:
raise NotImplementedError("Overwrite cls.module_name in your derived class.")
return self.module_name
@property
def _default_css_selector(self):
""" Internal method to access default_css_selector with a warning if it's unset """
if self.default_css_selector is None:
raise NotImplementedError("Overwrite cls.default_css_selector in your derived class.")
return self.default_css_selector
def setUp(self):
super().setUp()
# Use test scenarios:
loader = ResourceLoader(self._module_name)
scenarios_list = loader.load_scenarios_from_path(self.relative_scenario_path, include_identifier=True)
for identifier, title, xml in scenarios_list:
add_xml_scenario(identifier, title, xml)
self.addCleanup(remove_scenario, identifier)
# Suzy opens the browser to visit the workbench
self.browser.get(self.live_server_url)
# She knows it's the site by the header
header1 = self.browser.find_element_by_css_selector('h1')
self.assertEqual(header1.text, 'XBlock scenarios')
def go_to_page(self, page_name, css_selector=None, view_name=None):
"""
Navigate to the page `page_name`, as listed on the workbench home
Returns the DOM element on the visited page located by the `css_selector`
"""
if css_selector is None:
css_selector = self._default_css_selector
self.browser.get(self.live_server_url)
target_url = self.browser.find_element_by_link_text(page_name).get_attribute('href')
if view_name:
target_url += f'{view_name}/'
self.browser.get(target_url)
time.sleep(1)
block = self.browser.find_element_by_css_selector(css_selector)
return block