Commit dbfc38df by Jay Zoldak

Merge pull request #1370 from edx/zoldak/refactor-cms-acceptance-js

refactor studio component creation in acceptance tests
parents 4d7606f0 33f6d3b8
......@@ -2,7 +2,7 @@
# pylint: disable=W0621
from lettuce import world, step
from nose.tools import assert_true, assert_equal, assert_in, assert_false # pylint: disable=E0611
from nose.tools import assert_true, assert_in, assert_false # pylint: disable=E0611
from auth.authz import get_user_by_email, get_course_groupname_for_role
from django.conf import settings
......@@ -224,14 +224,50 @@ def i_enabled_the_advanced_module(step, module):
press_the_notification_button(step, 'Save')
@step('I have clicked the new unit button')
def open_new_unit(step):
step.given('I have opened a new course section in Studio')
step.given('I have added a new subsection')
step.given('I expand the first section')
old_url = world.browser.url
world.css_click('a.new-unit-item')
world.wait_for(lambda x: world.browser.url != old_url)
@world.absorb
def create_course_with_unit():
"""
Prepare for tests by creating a course with a section, subsection, and unit.
Performs the following:
Clear out all courseware
Create a course with a section, subsection, and unit
Create a user and make that user a course author
Log the user into studio
Open the course from the dashboard
Expand the section and click on the New Unit link
The end result is the page where the user is editing the new unit
"""
world.clear_courses()
course = world.CourseFactory.create()
world.scenario_dict['COURSE'] = course
section = world.ItemFactory.create(parent_location=course.location)
world.ItemFactory.create(
parent_location=section.location,
category='sequential',
display_name='Subsection One',
)
user = create_studio_user(is_staff=False)
add_course_author(user, course)
log_into_studio()
world.css_click('a.course-link')
css_selectors = [
'div.section-item a.expand-collapse-icon', 'a.new-unit-item'
]
for selector in css_selectors:
world.css_click(selector)
world.wait_for_mathjax()
world.wait_for_xmodule()
assert world.is_css_present('ul.new-component-type')
@step('I have clicked the new unit button$')
@step(u'I am in Studio editing a new unit$')
def edit_new_unit(step):
create_course_with_unit()
@step('the save notification button is disabled')
......@@ -267,9 +303,9 @@ def confirm_the_prompt(step):
assert_false(world.css_find(btn_css).visible)
@step(u'I am shown a (.*)$')
def i_am_shown_a_notification(step, notification_type):
assert world.is_css_present('.wrapper-%s' % notification_type)
@step(u'I am shown a prompt$')
def i_am_shown_a_notification(step):
assert world.is_css_present('.wrapper-prompt')
def type_in_codemirror(index, text):
......
......@@ -80,9 +80,3 @@ Feature: CMS.Component Adding
And I add a "Blank Advanced Problem" "Advanced Problem" component
And I delete all components
Then I see no components
Scenario: I see a notification on save
Given I am in Studio editing a new unit
And I add a "Discussion" "single step" component
And I edit and save a component
Then I am shown a notification
......@@ -2,52 +2,19 @@
#pylint: disable=W0621
from lettuce import world, step
from nose.tools import assert_true, assert_in, assert_equal # pylint: disable=E0611
from common import create_studio_user, add_course_author, log_into_studio
@step(u'I am in Studio editing a new unit$')
def add_unit(step):
world.clear_courses()
course = world.CourseFactory.create()
section = world.ItemFactory.create(parent_location=course.location)
world.ItemFactory.create(
parent_location=section.location,
category='sequential',
display_name='Subsection One',)
user = create_studio_user(is_staff=False)
add_course_author(user, course)
log_into_studio()
world.wait_for_requirejs([
"jquery", "gettext", "js/models/course", "coffee/src/models/module",
"coffee/src/views/unit", "jquery.ui",
])
world.wait_for_mathjax()
css_selectors = [
'a.course-link', 'div.section-item a.expand-collapse-icon',
'a.new-unit-item',
]
for selector in css_selectors:
world.css_click(selector)
from nose.tools import assert_true, assert_in # pylint: disable=E0611
@step(u'I add this type of single step component:$')
def add_a_single_step_component(step):
world.wait_for_xmodule()
for step_hash in step.hashes:
component = step_hash['Component']
assert_in(component, ['Discussion', 'Video'])
css_selector = 'a[data-type="{}"]'.format(component.lower())
world.css_click(css_selector)
# In the current implementation, all the "new component"
# buttons are handled by one BackBone.js view.
# If we click two buttons at super-human speed,
# the view will miss the second click while it's
# processing the first.
# To account for this, we wait for each component
# to be created before clicking the next component.
world.wait_for_visible('section.xmodule_{}Module'.format(component))
world.create_component_instance(
step=step,
category='{}'.format(component.lower()),
)
@step(u'I see this type of single step component:$')
......@@ -62,45 +29,13 @@ def see_a_single_step_component(step):
@step(u'I add this type of( Advanced)? (HTML|Problem) component:$')
def add_a_multi_step_component(step, is_advanced, category):
def click_advanced():
css = 'ul.problem-type-tabs a[href="#tab2"]'
world.css_click(css)
my_css = 'ul.problem-type-tabs li.ui-state-active a[href="#tab2"]'
assert(world.css_find(my_css))
def find_matching_link():
"""
Find the link with the specified text. There should be one and only one.
"""
# The tab shows links for the given category
links = world.css_find('div.new-component-{} a'.format(category))
# Find the link whose text matches what you're looking for
matched_links = [link for link in links if link.text == step_hash['Component']]
# There should be one and only one
assert_equal(len(matched_links), 1)
return matched_links[0]
def click_link():
link.click()
world.wait_for_xmodule()
category = category.lower()
for step_hash in step.hashes:
css_selector = 'a[data-type="{}"]'.format(category)
world.css_click(css_selector)
world.wait_for_invisible(css_selector)
if is_advanced:
# Sometimes this click does not work if you go too fast.
world.retry_on_exception(click_advanced, max_attempts=5, ignored_exceptions=AssertionError)
# Retry this in case the list is empty because you tried too fast.
link = world.retry_on_exception(func=find_matching_link, ignored_exceptions=AssertionError)
# Wait for the link to be clickable. If you go too fast it is not.
world.retry_on_exception(click_link)
world.create_component_instance(
step=step,
category='{}'.format(category.lower()),
component_type=step_hash['Component'],
is_advanced=bool(is_advanced),
)
@step(u'I see (HTML|Problem) components in this order:')
......
......@@ -2,30 +2,35 @@
#pylint: disable=C0111
from lettuce import world
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
from nose.tools import assert_equal, assert_true, assert_in # pylint: disable=E0611
from terrain.steps import reload_the_page
@world.absorb
def create_component_instance(step, component_button_css, category,
expected_css, boilerplate=None,
has_multiple_templates=True):
click_new_component_button(step, component_button_css)
if category in ('problem', 'html'):
def create_component_instance(step, category, component_type=None, is_advanced=False):
"""
Create a new component in a Unit.
def animation_done(_driver):
script = "$('div.new-component').css('display')"
return world.browser.evaluate_script(script) == 'none'
Parameters
----------
category: component type (discussion, html, problem, video)
component_type: for components with multiple templates, the link text in the menu
is_advanced: for html and problem, is the desired component under the
advanced menu
"""
assert_in(category, ['problem', 'html', 'video', 'discussion'])
world.wait_for(animation_done)
component_button_css = '.large-{}-icon'.format(category.lower())
world.css_click(component_button_css)
if has_multiple_templates:
click_component_from_menu(category, boilerplate, expected_css)
if category in ('problem', 'html'):
world.wait_for_invisible(component_button_css)
click_component_from_menu(category, component_type, is_advanced)
if category in ('video',):
world.wait_for_xmodule()
if category == 'problem':
expected_css = 'section.xmodule_CapaModule'
else:
expected_css = 'section.xmodule_{}Module'.format(category.title())
assert_true(world.is_css_present(expected_css))
......@@ -33,29 +38,53 @@ def create_component_instance(step, component_button_css, category,
@world.absorb
def click_new_component_button(step, component_button_css):
step.given('I have clicked the new unit button')
world.wait_for_requirejs(
["jquery", "js/models/course", "coffee/src/models/module",
"coffee/src/views/unit", "jquery.ui", "domReady!"]
)
world.css_click(component_button_css)
@world.absorb
def click_component_from_menu(category, boilerplate, expected_css):
def _click_advanced():
css = 'ul.problem-type-tabs a[href="#tab2"]'
world.css_click(css)
my_css = 'ul.problem-type-tabs li.ui-state-active a[href="#tab2"]'
assert(world.css_find(my_css))
def _find_matching_link(category, component_type):
"""
Creates a component from `instance_id`. For components with more
than one template, clicks on `elem_css` to create the new
component. Components with only one template are created as soon
as the user clicks the appropriate button, so we assert that the
expected component is present.
Find the link with the specified text. There should be one and only one.
"""
if boilerplate:
elem_css = "a[data-category='{}'][data-boilerplate='{}']".format(category, boilerplate)
else:
elem_css = "a[data-category='{}']:not([data-boilerplate])".format(category)
elements = world.css_find(elem_css)
assert_equal(len(elements), 1)
world.css_click(elem_css)
# The tab shows links for the given category
links = world.css_find('div.new-component-{} a'.format(category))
# Find the link whose text matches what you're looking for
matched_links = [link for link in links if link.text == component_type]
# There should be one and only one
assert_equal(len(matched_links), 1)
return matched_links[0]
def click_component_from_menu(category, component_type, is_advanced):
"""
Creates a component for a category with more
than one template, i.e. HTML and Problem.
For some problem types, it is necessary to click to
the Advanced tab.
The component_type is the link text, e.g. "Blank Common Problem"
"""
if is_advanced:
# Sometimes this click does not work if you go too fast.
world.retry_on_exception(_click_advanced,
ignored_exceptions=AssertionError)
# Retry this in case the list is empty because you tried too fast.
link = world.retry_on_exception(
lambda: _find_matching_link(category, component_type),
ignored_exceptions=AssertionError
)
# Wait for the link to be clickable. If you go too fast it is not.
world.retry_on_exception(lambda: link.click())
@world.absorb
......
......@@ -58,20 +58,3 @@ Feature: CMS.Course Overview
And I click the "Expand All Sections" link
Then I see the "Collapse All Sections" link
And all sections are expanded
Scenario: Notification is shown on grading status changes
Given I have a course with 1 section
When I navigate to the course overview page
And I change an assignment's grading status
Then I am shown a notification
# Notification is not shown on reorder for IE
# Safari does not have moveMouseTo implemented
@skip_internetexplorer
@skip_safari
Scenario: Notification is shown on subsection reorder
Given I have opened a new course section in Studio
And I have added a new subsection
And I have added a new subsection
When I reorder subsections
Then I am shown a notification
......@@ -50,8 +50,8 @@ def other_delete_self(_step):
@step(u'I make "([^"]*)" a course team admin')
def make_course_team_admin(_step, name):
admin_btn_css = '.user-item[data-email="{email}"] .user-actions .add-admin-role'.format(
email=name+'@edx.org')
admin_btn_css = '.user-item[data-email="{name}@edx.org"] .user-actions .add-admin-role'.format(
name=name)
world.css_click(admin_btn_css)
......@@ -80,8 +80,8 @@ def see_course(_step, do_not_see, gender='self'):
@step(u'"([^"]*)" should( not)? be marked as an admin')
def marked_as_admin(_step, name, not_marked_admin):
flag_css = '.user-item[data-email="{email}"] .flag-role.flag-role-admin'.format(
email=name+'@edx.org')
flag_css = '.user-item[data-email="{name}@edx.org"] .flag-role.flag-role-admin'.format(
name=name)
if not_marked_admin:
assert world.is_css_not_present(flag_css)
else:
......
......@@ -2,6 +2,7 @@ import os
from lettuce import world
from django.conf import settings
def import_file(filename):
world.browser.execute_script("$('input.file-input').css('display', 'block')")
path = os.path.join(settings.COMMON_TEST_DATA_ROOT, "imports", filename)
......
......@@ -2,7 +2,7 @@
Feature: CMS.Discussion Component Editor
As a course author, I want to be able to create discussion components.
Scenario: User can view metadata
Scenario: User can view discussion component metadata
Given I have created a Discussion Tag
And I edit and select Settings
Then I see three alphabetized settings and their expected values
......@@ -14,7 +14,3 @@ Feature: CMS.Discussion Component Editor
And I edit and select Settings
Then I can modify the display name
And my display name change is persisted on save
Scenario: Creating a discussion takes a single click
Given I have clicked the new unit button
Then creating a discussion takes a single click
......@@ -6,11 +6,10 @@ from lettuce import world, step
@step('I have created a Discussion Tag$')
def i_created_discussion_tag(step):
world.create_course_with_unit()
world.create_component_instance(
step, '.large-discussion-icon',
'discussion',
'.xmodule_DiscussionModule',
has_multiple_templates=False
step=step,
category='discussion',
)
......@@ -22,12 +21,3 @@ def i_see_only_the_settings_and_values(step):
['Display Name', "Discussion", False],
['Subcategory', "Topic-Level Student-Visible Label", False]
])
@step('creating a discussion takes a single click')
def discussion_takes_a_single_click(step):
component_css = '.xmodule_DiscussionModule'
assert world.is_css_not_present(component_css)
world.css_click("a[data-category='discussion']")
assert world.is_css_present(component_css)
......@@ -180,7 +180,7 @@ def cannot_edit_fail(_step):
def i_change_grace_period(_step, grace_period):
grace_period_css = '#course-grading-graceperiod'
ele = world.css_find(grace_period_css).first
# Sometimes it takes a moment for the JavaScript
# to populate the field. If we don't wait for
# this to happen, then we can end up with
......
......@@ -6,9 +6,11 @@ from lettuce import world, step
@step('I have created a Blank HTML Page$')
def i_created_blank_html_page(step):
world.create_course_with_unit()
world.create_component_instance(
step, '.large-html-icon', 'html',
'.xmodule_HtmlModule'
step=step,
category='html',
component_type='Text'
)
......@@ -18,11 +20,10 @@ def i_see_only_the_html_display_name(step):
@step('I have created an E-text Written in LaTeX$')
def i_created_blank_html_page(step):
def i_created_etext_in_latex(step):
world.create_course_with_unit()
world.create_component_instance(
step,
'.large-html-icon',
'html',
'.xmodule_HtmlModule',
'latex_html.yaml'
step=step,
category='html',
component_type='E-text Written in LaTeX'
)
# disable missing docstring
#pylint: disable=C0111
import os
import json
from lettuce import world, step
from nose.tools import assert_equal, assert_true # pylint: disable=E0611
......@@ -18,12 +17,11 @@ SHOW_ANSWER = "Show Answer"
@step('I have created a Blank Common Problem$')
def i_created_blank_common_problem(step):
world.create_course_with_unit()
world.create_component_instance(
step,
'.large-problem-icon',
'problem',
'.xmodule_CapaModule',
'blank_common.yaml'
step=step,
category='problem',
component_type='Blank Common Problem'
)
......@@ -168,14 +166,13 @@ def cancel_does_not_save_changes(step):
@step('I have created a LaTeX Problem')
def create_latex_problem(step):
world.click_new_component_button(step, '.large-problem-icon')
def animation_done(_driver):
return world.browser.evaluate_script("$('div.new-component').css('display')") == 'none'
world.wait_for(animation_done)
# Go to advanced tab.
world.css_click('#ui-id-2')
world.click_component_from_menu("problem", "latex_problem.yaml", '.xmodule_CapaModule')
world.create_course_with_unit()
world.create_component_instance(
step=step,
category='problem',
component_type='Problem Written in LaTeX',
is_advanced=True
)
@step('I edit and compile the High Level Source')
......
......@@ -5,8 +5,6 @@ from lettuce import world, step
from common import *
from nose.tools import assert_equal # pylint: disable=E0611
############### ACTIONS ####################
@step('I click the New Section link$')
def i_click_new_section_link(_step):
......@@ -53,9 +51,6 @@ def i_see_a_mini_notification(_step, _type):
assert world.is_css_present(saving_css)
############ ASSERTIONS ###################
@step('I see my section on the Courseware page$')
def i_see_my_section_on_the_courseware_page(_step):
see_my_section_on_the_courseware_page('My Section')
......@@ -125,8 +120,6 @@ def the_section_release_date_is_updated(_step):
assert_equal(status_text, 'Will Release: 12/25/2013 at 00:00 UTC')
############ HELPER METHODS ###################
def save_section_name(name):
name_css = '.new-section-name'
save_css = '.new-section-name-save'
......
......@@ -47,7 +47,7 @@ def name_textbook(_step, name):
@step(u'I name the (first|second|third) chapter "([^"]*)"')
def name_chapter(_step, ordinal, name):
index = ["first", "second", "third"].index(ordinal)
input_css = ".textbook .chapter{i} input.chapter-name".format(i=index+1)
input_css = ".textbook .chapter{i} input.chapter-name".format(i=index + 1)
world.css_fill(input_css, name)
if world.is_firefox():
world.trigger_event(input_css)
......@@ -56,7 +56,7 @@ def name_chapter(_step, ordinal, name):
@step(u'I type in "([^"]*)" for the (first|second|third) chapter asset')
def asset_chapter(_step, name, ordinal):
index = ["first", "second", "third"].index(ordinal)
input_css = ".textbook .chapter{i} input.chapter-asset-path".format(i=index+1)
input_css = ".textbook .chapter{i} input.chapter-asset-path".format(i=index + 1)
world.css_fill(input_css, name)
if world.is_firefox():
world.trigger_event(input_css)
......@@ -65,7 +65,7 @@ def asset_chapter(_step, name, ordinal):
@step(u'I click the Upload Asset link for the (first|second|third) chapter')
def click_upload_asset(_step, ordinal):
index = ["first", "second", "third"].index(ordinal)
button_css = ".textbook .chapter{i} .action-upload".format(i=index+1)
button_css = ".textbook .chapter{i} .action-upload".format(i=index + 1)
world.css_click(button_css)
......
......@@ -191,7 +191,7 @@ def view_asset(_step, status):
# Note that world.visit would trigger a 403 error instead of displaying "Unauthorized"
# Instead, we can drop back into the selenium driver get command.
world.browser.driver.get(url)
assert_equal(world.css_text('body'),expected_text)
assert_equal(world.css_text('body'), expected_text)
@step('I see a confirmation that the file was deleted$')
......
......@@ -12,11 +12,10 @@ BUTTONS = {
@step('I have created a Video component$')
def i_created_a_video_component(step):
world.create_course_with_unit()
world.create_component_instance(
step, '.large-video-icon',
'video',
'.xmodule_VideoModule',
has_multiple_templates=False
step=step,
category='video',
)
......@@ -155,4 +154,3 @@ def check_captions_visibility_state(_step, visibility_state, timeout):
assert world.css_visible('.subtitles')
else:
assert not world.css_visible('.subtitles')
......@@ -11,7 +11,6 @@
# Disable the "unused argument" warning because lettuce uses "step"
#pylint: disable=W0613
import re
from lettuce import world, step
from .course_helpers import *
from .ui_helpers import *
......@@ -23,32 +22,15 @@ logger = getLogger(__name__)
@step(r'I wait (?:for )?"(\d+\.?\d*)" seconds?$')
def wait(step, seconds):
def wait_for_seconds(step, seconds):
world.wait(seconds)
REQUIREJS_WAIT = {
re.compile('settings-details'): [
"jquery", "js/models/course",
"js/models/settings/course_details", "js/views/settings/main"],
re.compile('settings-advanced'): [
"jquery", "js/models/course", "js/models/settings/advanced",
"js/views/settings/advanced", "codemirror"],
re.compile('edit\/.+vertical'): [
"jquery", "js/models/course", "coffee/src/models/module",
"coffee/src/views/unit", "jquery.ui"],
}
@step('I reload the page$')
def reload_the_page(step):
world.wait_for_ajax_complete()
world.browser.reload()
requirements = None
for test, req in REQUIREJS_WAIT.items():
if test.search(world.browser.url):
requirements = req
break
world.wait_for_requirejs(requirements)
world.wait_for_js_to_load()
@step('I press the browser back button$')
......@@ -163,9 +145,9 @@ def should_see_in_the_page(step, doesnt_appear, text):
else:
multiplier = 1
if doesnt_appear:
assert world.browser.is_text_not_present(text, wait_time=5*multiplier)
assert world.browser.is_text_not_present(text, wait_time=5 * multiplier)
else:
assert world.browser.is_text_present(text, wait_time=5*multiplier)
assert world.browser.is_text_present(text, wait_time=5 * multiplier)
@step('I am logged in$')
......
......@@ -4,11 +4,13 @@
from lettuce import world
import time
import json
import re
import platform
from textwrap import dedent
from urllib import quote_plus
from selenium.common.exceptions import (
WebDriverException, TimeoutException, StaleElementReferenceException)
WebDriverException, TimeoutException,
StaleElementReferenceException)
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
......@@ -16,11 +18,50 @@ from lettuce.django import django_url
from nose.tools import assert_true # pylint: disable=E0611
REQUIREJS_WAIT = {
# Settings - Schedule & Details
re.compile('^Schedule & Details Settings \|'): [
"jquery", "js/models/course",
"js/models/settings/course_details", "js/views/settings/main"],
# Settings - Advanced Settings
re.compile('^Advanced Settings \|'): [
"jquery", "js/models/course", "js/models/settings/advanced",
"js/views/settings/advanced", "codemirror"],
# Individual Unit (editing)
re.compile('^Individual Unit \|'): [
"coffee/src/models/module", "coffee/src/views/unit",
"coffee/src/views/module_edit"],
# Content - Outline
# Note that calling your org, course number, or display name, 'course' will mess this up
re.compile('^Course Outline \|'): [
"js/models/course", "js/models/location", "js/models/section",
"js/views/overview", "js/views/section_edit"],
# Dashboard
re.compile('^My Courses \|'): [
"js/sock", "gettext", "js/base",
"jquery.ui", "coffee/src/main", "underscore"],
}
@world.absorb
def wait(seconds):
time.sleep(float(seconds))
@world.absorb
def wait_for_js_to_load():
requirements = None
for test, req in REQUIREJS_WAIT.items():
if test.search(world.browser.title):
requirements = req
break
world.wait_for_requirejs(requirements)
# Selenium's `execute_async_script` function pauses Selenium's execution
# until the browser calls a specific Javascript callback; in effect,
# Selenium goes to sleep until the JS callback function wakes it back up again.
......@@ -28,8 +69,6 @@ def wait(seconds):
# passed to this callback get returned from the `execute_async_script`
# function, which allows the JS to communicate information back to Python.
# Ref: https://selenium.googlecode.com/svn/trunk/docs/api/dotnet/html/M_OpenQA_Selenium_IJavaScriptExecutor_ExecuteAsyncScript.htm
@world.absorb
def wait_for_js_variable_truthy(variable):
"""
......@@ -37,7 +76,7 @@ def wait_for_js_variable_truthy(variable):
environment until the given variable is defined and truthy. This process
guards against page reloads, and seamlessly retries on the next page.
"""
js = """
javascript = """
var callback = arguments[arguments.length - 1];
var unloadHandler = function() {{
callback("unload");
......@@ -56,7 +95,13 @@ def wait_for_js_variable_truthy(variable):
}}, 10);
""".format(variable=variable)
for _ in range(5): # 5 attempts max
result = world.browser.driver.execute_async_script(dedent(js))
try:
result = world.browser.driver.execute_async_script(dedent(javascript))
except WebDriverException as wde:
if "document unloaded while waiting for result" in wde.msg:
result = "unload"
else:
raise
if result == "unload":
# we ran this on the wrong page. Wait a bit, and try again, when the
# browser has loaded the next page.
......@@ -105,7 +150,7 @@ def wait_for_requirejs(dependencies=None):
if dependencies[0] != "jquery":
dependencies.insert(0, "jquery")
js = """
javascript = """
var callback = arguments[arguments.length - 1];
if(window.require) {{
requirejs.onError = callback;
......@@ -126,7 +171,13 @@ def wait_for_requirejs(dependencies=None):
}}
""".format(deps=json.dumps(dependencies))
for _ in range(5): # 5 attempts max
result = world.browser.driver.execute_async_script(dedent(js))
try:
result = world.browser.driver.execute_async_script(dedent(javascript))
except WebDriverException as wde:
if "document unloaded while waiting for result" in wde.msg:
result = "unload"
else:
raise
if result == "unload":
# we ran this on the wrong page. Wait a bit, and try again, when the
# browser has loaded the next page.
......@@ -161,7 +212,7 @@ def wait_for_ajax_complete():
keeps track of this information, go here:
http://stackoverflow.com/questions/3148225/jquery-active-function#3148506
"""
js = """
javascript = """
var callback = arguments[arguments.length - 1];
if(!window.jQuery) {callback(false);}
var intervalID = setInterval(function() {
......@@ -171,13 +222,13 @@ def wait_for_ajax_complete():
}
}, 100);
"""
world.browser.driver.execute_async_script(dedent(js))
world.browser.driver.execute_async_script(dedent(javascript))
@world.absorb
def visit(url):
world.browser.visit(django_url(url))
wait_for_requirejs()
wait_for_js_to_load()
@world.absorb
......@@ -246,11 +297,11 @@ def css_has_value(css_selector, value, index=0):
@world.absorb
def wait_for(func, timeout=5):
WebDriverWait(
driver=world.browser.driver,
timeout=timeout,
ignored_exceptions=(StaleElementReferenceException)
).until(func)
WebDriverWait(
driver=world.browser.driver,
timeout=timeout,
ignored_exceptions=(StaleElementReferenceException)
).until(func)
@world.absorb
......@@ -349,14 +400,10 @@ def css_click(css_selector, index=0, wait_time=30):
msg="Element {}[{}] is present but not visible".format(css_selector, index)
)
# Sometimes you can't click in the center of the element, as
# another element might be on top of it. In this case, try
# clicking in the upper left corner.
try:
return retry_on_exception(lambda: world.css_find(css_selector)[index].click())
except WebDriverException:
return css_click_at(css_selector, index=index)
result = retry_on_exception(lambda: world.css_find(css_selector)[index].click())
if result:
wait_for_js_to_load()
return result
@world.absorb
......@@ -372,23 +419,6 @@ def css_check(css_selector, index=0, wait_time=30):
@world.absorb
def css_click_at(css_selector, index=0, x_coord=10, y_coord=10, timeout=5):
'''
A method to click at x,y coordinates of the element
rather than in the center of the element
'''
wait_for_clickable(css_selector, timeout=timeout)
assert_true(
world.css_visible(css_selector, index=index),
msg="Element {}[{}] is present but not visible".format(css_selector, index)
)
element.action_chains.move_to_element_with_offset(element._element, x_coord, y_coord)
element.action_chains.click()
element.action_chains.perform()
@world.absorb
def select_option(name, value, index=0, wait_time=30):
'''
A method to select an option
......@@ -417,6 +447,7 @@ def css_fill(css_selector, text, index=0):
@world.absorb
def click_link(partial_text, index=0):
retry_on_exception(lambda: world.browser.find_link_by_partial_text(partial_text)[index].click())
wait_for_js_to_load()
@world.absorb
......
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