Skip to content
This repository was archived by the owner on Jun 7, 2023. It is now read-only.

Commit 8094684

Browse files
authored
Merge pull request #1108 from bjones1/selenium_fixse
Selenium fixes
2 parents 7f705eb + 39f28b0 commit 8094684

2 files changed

Lines changed: 51 additions & 38 deletions

File tree

runestone/mchoice/test/test_assess.py

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
1-
"""
2-
Test Multiple Choice question directive
3-
"""
1+
# ****************************************************
2+
# |docname| - Test Multiple Choice question directive
3+
# ****************************************************
44

55
__author__ = "yasinovskyy"
66

77
from unittest import TestCase
8-
from selenium.webdriver.support import expected_conditions as EC
98
from selenium.webdriver.common.by import By
10-
from runestone.unittest_base import module_fixture_maker, RunestoneTestCase
9+
from selenium.webdriver.support import expected_conditions as EC
10+
from runestone.unittest_base import (
11+
module_fixture_maker,
12+
RunestoneTestCase,
13+
element_has_css_class,
14+
)
1115

1216
mf, setUpModule, tearDownModule = module_fixture_maker(__file__, True)
1317

18+
1419
# Look for errors producted by invalid questions.
1520
class MultipleChoiceQuestion_Error_Tests(TestCase):
1621
def test_1(self):
@@ -132,14 +137,9 @@ def test_mc1(self):
132137
t1 = self.driver.find_element_by_id("question2")
133138
btn_check = t1.find_element_by_tag_name("button")
134139
btn_check.click()
135-
fb = t1.find_element_by_id("question2_feedback")
136-
self.assertIsNotNone(fb)
137140
self.wait.until(
138-
EC.text_to_be_present_in_element((By.ID, "question2_feedback"), "✖️"),
139-
message="Did not find expected text",
141+
element_has_css_class((By.ID, "question2_feedback"), "alert-danger")
140142
)
141-
cnamestr = fb.get_attribute("class")
142-
self.assertIn("alert-danger", cnamestr)
143143

144144
def test_mc2(self):
145145
"""Multiple Choice: Correct answer selected"""

runestone/unittest_base.py

Lines changed: 40 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
# -------------
3636
# None
3737

38+
# Globals
39+
# =======
3840
# Select an unused port for serving web pages to the test suite.
3941
PORT = "8081"
4042
# Use the localhost for testing.
@@ -50,6 +52,8 @@
5052
mf = None
5153

5254

55+
# Code
56+
# ====
5357
# Define `module fixtures <https://docs.python.org/2/library/unittest.html#setupmodule-and-teardownmodule>`_ to build the test Runestone project, run the server, then shut it down when the tests complete.
5458
class ModuleFixture(unittest.TestCase):
5559
def __init__(
@@ -75,19 +79,17 @@ def setUpModule(self):
7579
# otherwise the runestone build may fail due to lack of a runestone.js file!
7680
p = subprocess.run(
7781
["npm.cmd" if IS_WINDOWS else "npm", "run", "build"],
78-
stdout=subprocess.PIPE,
79-
stderr=subprocess.PIPE,
80-
universal_newlines=True,
82+
capture_output=True,
83+
text=True,
8184
)
85+
print(p.stdout + p.stderr)
8286
self.assertFalse(p.returncode)
8387
# Compile the docs. Save the stdout and stderr for examination.
84-
p = subprocess.Popen(
85-
["runestone", "build", "--all"],
86-
stdout=subprocess.PIPE,
87-
stderr=subprocess.PIPE,
88-
universal_newlines=True,
88+
p = subprocess.run(
89+
["runestone", "build", "--all"], capture_output=True, text=True,
8990
)
90-
self.build_stdout_data, self.build_stderr_data = p.communicate()
91+
self.build_stdout_data = p.stdout
92+
self.build_stderr_data = p.stderr
9193
print(self.build_stdout_data + self.build_stderr_data)
9294
if self.exit_status_success:
9395
self.assertFalse(p.returncode)
@@ -99,8 +101,8 @@ def setUpModule(self):
99101
# -n: Display addresses numerically. Looking up names is slow.
100102
# -o: Include the PID for each connection.
101103
["netstat", "-no"],
102-
universal_newlines=True,
103-
stdout=subprocess.PIPE,
104+
capture_output=True,
105+
text=True,
104106
).stdout
105107
# Skip the first four lines, which are headings.
106108
for connection in netstat_output.splitlines()[4:]:
@@ -113,9 +115,7 @@ def setUpModule(self):
113115
os.kill(pid, 0)
114116
else:
115117
lsof_output = subprocess.run(
116-
["lsof", "-i", ":{0}".format(PORT)],
117-
universal_newlines=True,
118-
stdout=subprocess.PIPE,
118+
["lsof", "-i", ":{0}".format(PORT)], capture_output=True, text=True,
119119
).stdout
120120
for process in lsof_output.split("\n")[1:]:
121121
data = [x for x in process.split(" ") if x != ""]
@@ -195,19 +195,6 @@ def tearDownModule(self):
195195
global mf
196196
mf = None
197197

198-
# Without this, Python 2.7 produces errors when running unit tests:
199-
#
200-
# .. code::
201-
# :number-lines:
202-
#
203-
# python -m unittest discover
204-
#
205-
# ImportError: Failed to import test module: runestone.tabbedStuff.test.test_tabbedStuff
206-
# Traceback (most recent call last): (omitted)
207-
# ValueError: no such test method in <class 'runestone.unittest_base.ModuleFixture'>: runTest
208-
def runTest(self):
209-
pass
210-
211198

212199
# Provide a simple way to instantiante a ModuleFixture in a test module. Typical use:
213200
#
@@ -241,3 +228,29 @@ def tearDown(self):
241228
self.driver.execute_script("window.localStorage.clear();")
242229
self.driver.execute_script("window.sessionStorage.clear();")
243230
self.driver.delete_all_cookies()
231+
232+
233+
# An expectation for Selenium, used for checking that an element has a particular css class. From the `Selenium docs <https://selenium-python.readthedocs.io/waits.html#explicit-waits>`_, under the "Custom wait conditions" subheading.
234+
#
235+
# locator - used to find the element
236+
#
237+
# returns the WebElement once it has the particular css class.
238+
class element_has_css_class:
239+
def __init__(
240+
self,
241+
# The element to find; this is passed directly to `driver.find_element <https://selenium-python.readthedocs.io/api.html#selenium.webdriver.remote.webdriver.WebDriver.find_element>`_. See the `Selenium docs`_.
242+
locator,
243+
# The CSS class to look for.
244+
css_class,
245+
):
246+
247+
self.locator = locator
248+
self.css_class = css_class
249+
250+
def __call__(self, driver):
251+
# Find the referenced element.
252+
element = driver.find_element(*self.locator)
253+
if self.css_class in element.get_attribute("class"):
254+
return element
255+
else:
256+
return False

0 commit comments

Comments
 (0)