Commit c2afd442 by Ned Batchelder Committed by GitHub

Merge pull request #14593 from edx/estute/xenial-and-browser-updates-for-testing

Estute/xenial and browser updates for testing
parents 52fc790c 3cbdf962
...@@ -110,7 +110,7 @@ Feature: CMS.HTML Editor ...@@ -110,7 +110,7 @@ Feature: CMS.HTML Editor
When I edit the page When I edit the page
And I click font selection dropdown And I click font selection dropdown
Then I should see a list of available fonts Then I should see a list of available fonts
And "Default" option sets "'Open Sans', Verdana, Arial, Helvetica, sans-serif" font family And "Default" option sets the expected font family
And all standard tinyMCE fonts should be available And all standard tinyMCE fonts should be available
# Skipping in master due to brittleness JZ 05/22/2014 # Skipping in master due to brittleness JZ 05/22/2014
......
...@@ -229,41 +229,45 @@ def font_selector_dropdown_is_shown(step): ...@@ -229,41 +229,45 @@ def font_selector_dropdown_is_shown(step):
assert_equal(actual_fonts, expected_fonts) assert_equal(actual_fonts, expected_fonts)
@step('"Default" option sets "(.*)" font family') @step('"Default" option sets the expected font family')
def default_options_sets_expected_font_family(step, expected_font_family): def default_options_sets_expected_font_family(step): # pylint: disable=unused-argument, redefined-outer-name
fonts = get_available_fonts(get_fonts_list_panel(world)) fonts = get_available_fonts(get_fonts_list_panel(world))
assert_equal(fonts.get("Default", None), expected_font_family) fonts_found = fonts.get("Default", None)
expected_font_family = CUSTOM_FONTS.get('Default')
for expected_font in expected_font_family:
assert_in(expected_font, fonts_found)
@step('all standard tinyMCE fonts should be available') @step('all standard tinyMCE fonts should be available')
def check_standard_tinyMCE_fonts(step): def check_standard_tinyMCE_fonts(step):
fonts = get_available_fonts(get_fonts_list_panel(world)) fonts = get_available_fonts(get_fonts_list_panel(world))
for label, expected_font in TINYMCE_FONTS.items(): for label, expected_fonts in TINYMCE_FONTS.items():
assert_equal(fonts.get(label, None), expected_font) for expected_font in expected_fonts:
assert_in(expected_font, fonts.get(label, None))
TINYMCE_FONTS = OrderedDict([ TINYMCE_FONTS = OrderedDict([
("Andale Mono", "'andale mono', times"), ("Andale Mono", ['andale mono', 'times']),
("Arial", "arial, helvetica, sans-serif"), ("Arial", ['arial', 'helvetica', 'sans-serif']),
("Arial Black", "'arial black', 'avant garde'"), ("Arial Black", ['arial black', 'avant garde']),
("Book Antiqua", "'book antiqua', palatino"), ("Book Antiqua", ['book antiqua', 'palatino']),
("Comic Sans MS", "'comic sans ms', sans-serif"), ("Comic Sans MS", ['comic sans ms', 'sans-serif']),
("Courier New", "'courier new', courier"), ("Courier New", ['courier new', 'courier']),
("Georgia", "georgia, palatino"), ("Georgia", ['georgia', 'palatino']),
("Helvetica", "helvetica"), ("Helvetica", ['helvetica']),
("Impact", "impact, chicago"), ("Impact", ['impact', 'chicago']),
("Symbol", "symbol"), ("Symbol", ['symbol']),
("Tahoma", "tahoma, arial, helvetica, sans-serif"), ("Tahoma", ['tahoma', 'arial', 'helvetica', 'sans-serif']),
("Terminal", "terminal, monaco"), ("Terminal", ['terminal', 'monaco']),
("Times New Roman", "'times new roman', times"), ("Times New Roman", ['times new roman', 'times']),
("Trebuchet MS", "'trebuchet ms', geneva"), ("Trebuchet MS", ['trebuchet ms', 'geneva']),
("Verdana", "verdana, geneva"), ("Verdana", ['verdana', 'geneva']),
# tinyMCE does not set font-family on dropdown span for these two fonts # tinyMCE does not set font-family on dropdown span for these two fonts
("Webdings", ""), # webdings ("Webdings", [""]), # webdings
("Wingdings", ""), # wingdings, 'zapf dingbats' ("Wingdings", [""]), # wingdings, 'zapf dingbats'
]) ])
CUSTOM_FONTS = OrderedDict([ CUSTOM_FONTS = OrderedDict([
('Default', "'Open Sans', Verdana, Arial, Helvetica, sans-serif"), ('Default', ['Open Sans', 'Verdana', 'Arial', 'Helvetica', 'sans-serif']),
]) ])
......
...@@ -279,10 +279,9 @@ def after_each_step(step): ...@@ -279,10 +279,9 @@ def after_each_step(step):
@after.harvest @after.harvest
def teardown_browser(total): def saucelabs_status(total):
""" """
Quit the browser after executing the tests. Collect data for saucelabs.
""" """
if world.LETTUCE_SELENIUM_CLIENT == 'saucelabs': if world.LETTUCE_SELENIUM_CLIENT == 'saucelabs':
set_saucelabs_job_status(world.jobid, total.scenarios_ran == total.scenarios_passed) set_saucelabs_job_status(world.jobid, total.scenarios_ran == total.scenarios_passed)
world.browser.quit()
...@@ -56,12 +56,19 @@ def stop_video_server(_total): ...@@ -56,12 +56,19 @@ def stop_video_server(_total):
video_server.shutdown() video_server.shutdown()
@before.all # pylint: disable=no-member
def start_stub_servers():
"""
Start all stub servers
"""
for stub in SERVICES.keys():
start_stub(stub)
@before.each_scenario # pylint: disable=no-member @before.each_scenario # pylint: disable=no-member
def process_requires_tags(scenario): def skip_youtube_if_not_available(scenario):
""" """
Process the scenario tags to make sure that any
requirements are met prior to that scenario
being executed.
Scenario tags must be named with this convention: Scenario tags must be named with this convention:
@requires_stub_bar, where 'bar' is the name of the stub service to start @requires_stub_bar, where 'bar' is the name of the stub service to start
...@@ -85,7 +92,7 @@ def process_requires_tags(scenario): ...@@ -85,7 +92,7 @@ def process_requires_tags(scenario):
scenario.steps = [] scenario.steps = []
return return
start_stub(requires.group('server')) return
def start_stub(name): def start_stub(name):
...@@ -124,11 +131,13 @@ def is_youtube_available(urls): ...@@ -124,11 +131,13 @@ def is_youtube_available(urls):
return True return True
@after.each_scenario # pylint: disable=no-member @after.all # pylint: disable=no-member
def stop_stubs(_scenario): def stop_stubs(_scenario):
""" """
Shut down any stub services that were started up for the scenario. Shut down any stub services.
""" """
# close browser to ensure no open connections to the stub servers
world.browser.quit()
for name in SERVICES.keys(): for name in SERVICES.keys():
stub_server = getattr(world, name, None) stub_server = getattr(world, name, None)
if stub_server is not None: if stub_server is not None:
......
...@@ -3,6 +3,7 @@ Stub implementation of an HTTP service. ...@@ -3,6 +3,7 @@ Stub implementation of an HTTP service.
""" """
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from SocketServer import ThreadingMixIn
import urllib import urllib
import urlparse import urlparse
import threading import threading
...@@ -233,7 +234,7 @@ class StubHttpRequestHandler(BaseHTTPRequestHandler, object): ...@@ -233,7 +234,7 @@ class StubHttpRequestHandler(BaseHTTPRequestHandler, object):
self.send_response(200) self.send_response(200)
class StubHttpService(HTTPServer, object): class StubHttpService(ThreadingMixIn, HTTPServer, object):
""" """
Stub HTTP service implementation. Stub HTTP service implementation.
""" """
......
...@@ -191,13 +191,13 @@ class StubLtiHandler(StubHttpRequestHandler): ...@@ -191,13 +191,13 @@ class StubLtiHandler(StubHttpRequestHandler):
if submit_url: if submit_url:
submit_form = textwrap.dedent(""" submit_form = textwrap.dedent("""
<form action="{submit_url}/grade" method="post"> <form action="{submit_url}/grade" method="post">
<input type="submit" name="submit-button" value="Submit"> <input type="submit" name="submit-button" value="Submit" id="submit-button">
</form> </form>
<form action="{submit_url}/lti2_outcome" method="post"> <form action="{submit_url}/lti2_outcome" method="post">
<input type="submit" name="submit-lti2-button" value="Submit"> <input type="submit" name="submit-lti2-button" value="Submit" id="submit-lti2-button">
</form> </form>
<form action="{submit_url}/lti2_delete" method="post"> <form action="{submit_url}/lti2_delete" method="post">
<input type="submit" name="submit-lti2-delete-button" value="Submit"> <input type="submit" name="submit-lti2-delete-button" value="Submit" id="submit-lti-delete-button">
</form> </form>
""").format(submit_url=submit_url) """).format(submit_url=submit_url)
else: else:
......
...@@ -269,7 +269,8 @@ class VideoStudentViewHandlers(object): ...@@ -269,7 +269,8 @@ class VideoStudentViewHandlers(object):
headerlist=[ headerlist=[
('Content-Disposition', 'attachment; filename="{}"'.format(transcript_filename.encode('utf8'))), ('Content-Disposition', 'attachment; filename="{}"'.format(transcript_filename.encode('utf8'))),
('Content-Language', self.transcript_language), ('Content-Language', self.transcript_language),
] ],
charset='utf8'
) )
response.content_type = transcript_mime_type response.content_type = transcript_mime_type
......
...@@ -5,6 +5,7 @@ import datetime ...@@ -5,6 +5,7 @@ import datetime
from bok_choy.page_object import PageObject from bok_choy.page_object import PageObject
from bok_choy.promise import EmptyPromise from bok_choy.promise import EmptyPromise
from bok_choy.javascript import js_defined, wait_for_js
from selenium.webdriver import ActionChains from selenium.webdriver import ActionChains
from selenium.webdriver.support.ui import Select from selenium.webdriver.support.ui import Select
...@@ -18,6 +19,7 @@ from common.test.acceptance.pages.studio.container import ContainerPage ...@@ -18,6 +19,7 @@ from common.test.acceptance.pages.studio.container import ContainerPage
from common.test.acceptance.pages.studio.utils import set_input_value_and_save, set_input_value from common.test.acceptance.pages.studio.utils import set_input_value_and_save, set_input_value
@js_defined('jQuery')
class CourseOutlineItem(object): class CourseOutlineItem(object):
""" """
A mixin class for any :class:`PageObject` shown in a course outline. A mixin class for any :class:`PageObject` shown in a course outline.
...@@ -174,6 +176,7 @@ class CourseOutlineItem(object): ...@@ -174,6 +176,7 @@ class CourseOutlineItem(object):
element = self.q(css=self._bounded_selector(".status-grading-value")) # pylint: disable=no-member element = self.q(css=self._bounded_selector(".status-grading-value")) # pylint: disable=no-member
return element.first.text[0] if element.present else None return element.first.text[0] if element.present else None
@wait_for_js
def publish(self): def publish(self):
""" """
Publish the unit. Publish the unit.
......
...@@ -436,6 +436,7 @@ class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin): ...@@ -436,6 +436,7 @@ class CoursewareMultipleVerticalsTest(UniqueCourseTest, EventsTestMixin):
self.courseware_page.visit() self.courseware_page.visit()
self.course_nav = CourseNavPage(self.browser) self.course_nav = CourseNavPage(self.browser)
@flaky # TODO: fix this, see TNL-5762
def test_navigation_buttons(self): def test_navigation_buttons(self):
# start in first section # start in first section
self.assert_navigation_state('Test Section 1', 'Test Subsection 1,1', 0, next_enabled=True, prev_enabled=False) self.assert_navigation_state('Test Section 1', 'Test Subsection 1,1', 0, next_enabled=True, prev_enabled=False)
......
...@@ -274,6 +274,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin): ...@@ -274,6 +274,7 @@ class EdxNotesDefaultInteractionsTest(EdxNotesTestMixin):
components = self.note_unit_page.components components = self.note_unit_page.components
self.assert_notes_are_removed(components) self.assert_notes_are_removed(components)
@flaky # TODO: fix this, see TNL-6494
def test_can_create_note_with_tags(self): def test_can_create_note_with_tags(self):
""" """
Scenario: a user of notes can define one with tags Scenario: a user of notes can define one with tags
...@@ -1061,6 +1062,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin): ...@@ -1061,6 +1062,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
self.assertNotIn(u"Search Results", self.notes_page.tabs) self.assertNotIn(u"Search Results", self.notes_page.tabs)
self.assertEqual(len(self.notes_page.notes), 5) self.assertEqual(len(self.notes_page.notes), 5)
@flaky # TODO: fix this, see TNL-6493
def test_open_note_when_accessed_from_notes_page(self): def test_open_note_when_accessed_from_notes_page(self):
""" """
Scenario: Ensure that the link to the Unit opens a note only once. Scenario: Ensure that the link to the Unit opens a note only once.
...@@ -1115,6 +1117,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin): ...@@ -1115,6 +1117,7 @@ class EdxNotesPageTest(EventsTestMixin, EdxNotesTestMixin):
note = self.note_unit_page.notes[0] note = self.note_unit_page.notes[0]
self.assertFalse(note.is_visible) self.assertFalse(note.is_visible)
self.courseware_page.go_to_sequential_position(1) self.courseware_page.go_to_sequential_position(1)
self.courseware_page.wait_for_ajax()
note = self.note_unit_page.notes[0] note = self.note_unit_page.notes[0]
self.assertFalse(note.is_visible) self.assertFalse(note.is_visible)
......
...@@ -5,9 +5,9 @@ progress page. ...@@ -5,9 +5,9 @@ progress page.
""" """
import ddt import ddt
from bok_choy.javascript import js_defined
from contextlib import contextmanager from contextlib import contextmanager
from nose.plugins.attrib import attr from nose.plugins.attrib import attr
from flaky import flaky
from ..helpers import ( from ..helpers import (
UniqueCourseTest, auto_auth, create_multiple_choice_problem, create_multiple_choice_xml, get_modal_alert UniqueCourseTest, auto_auth, create_multiple_choice_problem, create_multiple_choice_xml, get_modal_alert
...@@ -126,7 +126,6 @@ class ProgressPageBaseTest(UniqueCourseTest): ...@@ -126,7 +126,6 @@ class ProgressPageBaseTest(UniqueCourseTest):
@attr(shard=9) @attr(shard=9)
@ddt.ddt @ddt.ddt
@js_defined('window.jQuery')
class PersistentGradesTest(ProgressPageBaseTest): class PersistentGradesTest(ProgressPageBaseTest):
""" """
Test that grades for completed assessments are persisted Test that grades for completed assessments are persisted
...@@ -228,6 +227,7 @@ class PersistentGradesTest(ProgressPageBaseTest): ...@@ -228,6 +227,7 @@ class PersistentGradesTest(ProgressPageBaseTest):
_change_subsection_structure, _change_subsection_structure,
_change_weight_for_problem _change_weight_for_problem
) )
@flaky # TODO: fix this, see TNL-6040
def test_content_changes_do_not_change_score(self, edit): def test_content_changes_do_not_change_score(self, edit):
with self._logged_in_session(): with self._logged_in_session():
self.courseware_page.visit() self.courseware_page.visit()
......
...@@ -359,7 +359,10 @@ def click_grade(_step, version): ...@@ -359,7 +359,10 @@ def click_grade(_step, version):
location = world.scenario_dict['LTI'].location.html_id() location = world.scenario_dict['LTI'].location.html_id()
iframe_name = 'ltiFrame-' + location iframe_name = 'ltiFrame-' + location
with world.browser.get_iframe(iframe_name) as iframe: with world.browser.get_iframe(iframe_name) as iframe:
iframe.find_by_name(version_map[version]['selector']).first.click() css_ele = version_map[version]['selector']
css_loc = '#' + css_ele
world.wait_for_visible(css_loc)
world.css_click(css_loc)
assert iframe.is_text_present(version_map[version]['expected_text']) assert iframe.is_text_present(version_map[version]['expected_text'])
......
...@@ -14,10 +14,10 @@ from mock import patch ...@@ -14,10 +14,10 @@ from mock import patch
from polib import pofile from polib import pofile
from pytz import UTC from pytz import UTC
from i18n import config
from i18n import dummy
from i18n import extract from i18n import extract
from i18n import generate from i18n import generate
from i18n import dummy
from i18n.config import CONFIGURATION
class TestGenerate(TestCase): class TestGenerate(TestCase):
...@@ -57,6 +57,8 @@ class TestGenerate(TestCase): ...@@ -57,6 +57,8 @@ class TestGenerate(TestCase):
def setUp(self): def setUp(self):
super(TestGenerate, self).setUp() super(TestGenerate, self).setUp()
self.configuration = config.Configuration()
# Subtract 1 second to help comparisons with file-modify time succeed, # Subtract 1 second to help comparisons with file-modify time succeed,
# since os.path.getmtime() is not millisecond-accurate # since os.path.getmtime() is not millisecond-accurate
self.start_time = datetime.now(UTC) - timedelta(seconds=1) self.start_time = datetime.now(UTC) - timedelta(seconds=1)
...@@ -65,13 +67,11 @@ class TestGenerate(TestCase): ...@@ -65,13 +67,11 @@ class TestGenerate(TestCase):
""" """
Tests merge script on English source files. Tests merge script on English source files.
""" """
filename = os.path.join(CONFIGURATION.source_messages_dir, random_name()) filename = os.path.join(self.configuration.source_messages_dir, random_name())
generate.merge(CONFIGURATION.source_locale, target=filename) generate.merge(self.configuration, self.configuration.source_locale, target=filename)
self.assertTrue(os.path.exists(filename)) self.assertTrue(os.path.exists(filename))
os.remove(filename) os.remove(filename)
# Patch dummy_locales to not have esperanto present
@patch.object(CONFIGURATION, 'dummy_locales', ['fake2'])
def test_main(self): def test_main(self):
""" """
Runs generate.main() which should merge source files, Runs generate.main() which should merge source files,
...@@ -80,11 +80,14 @@ class TestGenerate(TestCase): ...@@ -80,11 +80,14 @@ class TestGenerate(TestCase):
.mo files should exist, and be recently created (modified .mo files should exist, and be recently created (modified
after start of test suite) after start of test suite)
""" """
# Change dummy_locales to not have Esperanto present.
self.configuration.dummy_locales = ['fake2']
generate.main(verbosity=0, strict=False) generate.main(verbosity=0, strict=False)
for locale in CONFIGURATION.translated_locales: for locale in self.configuration.translated_locales:
for filename in ('django', 'djangojs'): for filename in ('django', 'djangojs'):
mofile = filename + '.mo' mofile = filename + '.mo'
path = os.path.join(CONFIGURATION.get_messages_dir(locale), mofile) path = os.path.join(self.configuration.get_messages_dir(locale), mofile)
exists = os.path.exists(path) exists = os.path.exists(path)
self.assertTrue(exists, msg='Missing file in locale %s: %s' % (locale, mofile)) self.assertTrue(exists, msg='Missing file in locale %s: %s' % (locale, mofile))
self.assertGreaterEqual( self.assertGreaterEqual(
...@@ -108,7 +111,7 @@ class TestGenerate(TestCase): ...@@ -108,7 +111,7 @@ class TestGenerate(TestCase):
# #-#-#-#-# django-partial.po (0.1a) #-#-#-#-# # #-#-#-#-# django-partial.po (0.1a) #-#-#-#-#
""" """
path = os.path.join(CONFIGURATION.get_messages_dir(locale), 'django.po') path = os.path.join(self.configuration.get_messages_dir(locale), 'django.po')
pof = pofile(path) pof = pofile(path)
pattern = re.compile('^#-#-#-#-#', re.M) pattern = re.compile('^#-#-#-#-#', re.M)
match = pattern.findall(pof.header) match = pattern.findall(pof.header)
......
...@@ -93,6 +93,8 @@ class AcceptanceTest(TestSuite): ...@@ -93,6 +93,8 @@ class AcceptanceTest(TestSuite):
report_file = self.report_dir / "{}.xml".format(self.system) report_file = self.report_dir / "{}.xml".format(self.system)
report_args = ["--xunit-file {}".format(report_file)] report_args = ["--xunit-file {}".format(report_file)]
return [ return [
# set DBUS_SESSION_BUS_ADDRESS to avoid hangs on Chrome
"DBUS_SESSION_BUS_ADDRESS=/dev/null",
"DEFAULT_STORE={}".format(self.default_store), "DEFAULT_STORE={}".format(self.default_store),
"./manage.py", "./manage.py",
self.system, self.system,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment