I'm writing cucumber feature specs using capybara, with poltergeist as the Javascript driver.
In one scenario, a step asks capybara to fill in a number field:
When /^I set the group count to (\d+)$/ do |group_count|
within 'form#new_practitioner_activity' do
find('input#number_of_groups').set group_count
end
end
For some reason, at that step a request gets generated (I've used Poltergeist's page.driver.network_traffic object to check it out) and the whole scenario is derailed as the request is redirected to the welcome page.
Changing the value of the field does trigger some Javascript (that's the point of writing the scenario to test it), but when I run in Chrome and watch the network panel, I don't see any requests there, and the JS code isn't intended to make any calls back to the server. (The script picks up a nested form, clones it, and appends it, if that matters.)
Here's the Coffeescript relevant to the field change. I'm eliding a good bit of each of these classes to try to clarify it.
class GroupCountElement
constructor: (#controller, #form) ->
# On instantiation we set up instance attributes for a div, and an input
# contained in it
#input = #form.find('.input.group-count')
#inputField = #input.find('input#number_of_groups')
#form.on('change', 'input#number_of_groups', #countChanged)
countChanged: =>
#form.trigger('groupcount:changed')
val: (newValue) =>
# This method just lets us treat the object like an input element
if newValue?
#inputField.val(newValue)
else
#inputField.val()
Here's what #form does with that groupcount:changed event:
class Form
constructor: (#controller, #form, #academicYearId) ->
# ... other setup stuff...
#groupInputs = []
#form.find('.group-activity').each (i, row) =>
#groupInputs.push(new GroupActivity(#controller, #form, row))
#groupCount = new GroupCountElement(#controller, #form)
#form.on('groupcount:changed', #groupCountChanged)
groupCountChanged: (e) =>
delta = #groupCount.val() - #groupInputs.length
if delta > 0
#groupInputs.push(new GroupActivity(#controller, #form)) for i in [1..Math.min(delta, 10-#groupInputs.length)]
else if delta < 0
#groupInputs.pop().delete() for i in [Math.max(delta, 1-#groupInputs.length)..-1] unless #groupInputs.length is 1
Then this is the GroupActivity class which is having instances of itself pushed and popped on the Form:
class GroupActivity
constructor: (#controller, #form, #groupForm) ->
if #groupForm?
# This means we're creating an instance reflecting DOM already in the form
#$groupForm = $(#groupForm)
else
# This means we're cloning an existing chunk of DOM - a row - and adding
# it to the form
#$groupForm = #form.find('.group-activity').last().clone()
# Because the nested form elements we just cloned have names
# and IDs with an array index, our new row needs to have those
# attributes updated with the index +1. #incrementFormAttrs()
# handles that.
#incrementFormAttrs()
#append() # Sticks the cloned DOM into the form
#reset() # Clears any values in the cloned DOM
incrementFormAttrs: =>
oldIndex = #$groupForm.find('.input.instructional-focus label').attr('for').match(/practitioner_activity_group_activities_attributes_(\d+)/)[1]
newIndex = parseInt(oldIndex, 10) + 1
#$groupForm.find('label').attr 'for', (i, val) ->
val.replace(/practitioner_activity_group_activities_attributes_(\d+)/, "practitioner_activity_group_activities_attributes_#{newIndex}")
#$groupForm.find('select, input').attr
id: (i, val) -> val.replace(/practitioner_activity_group_activities_attributes_(\d+)/, "practitioner_activity_group_activities_attributes_#{newIndex}")
name: (i, val) -> val.replace(/practitioner_activity\[group_activities_attributes\]\[(\d+)\]/, "practitioner_activity[group_activities_attributes][#{newIndex}]")
append: =>
#$groupForm.insertBefore( #form.find('.actions') )
reset: =>
if #$groupForm
new Deselector(#$groupForm.find('select'))
#$groupForm.find('input').val(undefined)
delete: =>
#$groupForm.remove()
So, to trace it out: the test changes the value of the #inputField in the GroupCountElement. That sends a groupcount:changed event to the Form, which calls its groupCountChanged() method. In this case the number is most likely to go UP, so we're adding at least one GroupActivity instance, which in its constructor will call incrementFormAttrs(), append(), and reset().
None of these should trigger a request as I understand them. What could be kicking off this extra request which is breaking my test?
Related
I am trying to programmatically input text in a textfield of an AJAX website which then updates a list of stores. Unfortunately, the list of stores only updates when the browser window is showing. Whenever the browser window is hidden, my code always generates the default list and not the updated list.
Here is my code (I am using Python / Selenium):
def select_state(driver, state):
# select a state
log = logging.getLogger(__name__)
log.debug("Select a state")
xpath = '//*[#id="store-finder"]'
driver.implicitly_wait(10)
wait = WebDriverWait(driver, 5)
wait.until(ec.presence_of_element_located((By.XPATH, xpath)))
element = driver.find_element(By.XPATH, xpath)
element.clear()
logger.info("Processing state {0}".format(state))
element.send_keys(state + Keys.TAB)
The problem occurs when it reaches "element.send_keys(state + Keys.TAB)". The state is keyed in properly into the text field but the list of stores does not update. I have tried (i) Keys.RETURN, (ii) executing JavaScript directly (i.e. river.execute_script()), (iii) including implicit_wait() times, (iv) even explored how to deconstruct the encrypted XHR request which generates the store list.
Any suggestions would be greatly appreciated.
Thank you
I'd like to move the focus to the next cell of my Jupyter notebook. I've written a Javascript function that is fired when a user presses an HTML button. I added this button to my notebook by making use of functionality from IPython.core.display.HTML. This is the HTML part for the button: button onclick="set_params();" [in html brackets]. In the function - yes, with name set_params - I prepare a command that should be executed by the Python kernel. I establish that this is actually done when the button is pressed. Afterwards I want the cursor to move to the next cell - and that is not happening unfortunately. Here is part of my Javascript function:
var command = 'input_params = [' + names + ']';
IPython.notebook.kernel.execute(command);
i = IPython.notebook.get_selected_index();
IPython.notebook.select(i+1);
I even checked with some extra code whether the cell with index i+1 is really a cell and it is. Why is the focus not moving?
If you import Javascript:
from IPython.display import Javascript
You can do something like:
def goto_cell_two():
display(Javascript("Jupyter.notebook.execute_cells([2])"))
go_to_cell_two()
Cells start with index 0
A few million years later ....
I end up calling 3 javascript functions consequently, namely select_next(), focus_cell(), and execute_selected_cells(). All these calls can be combined in one procedure in the first cell, and then call this function every time you want to jump into the next cell.
def run_next(ev):
display(Javascript('IPython.notebook.select_next()'))
display(Javascript('IPython.notebook.focus_cell()'))
display(Javascript('IPython.notebook.execute_selected_cells()'))
So in the "linear" part of a code without any input from the user, this function is called at the end of each cell
run_next(None)
However, in interface cells, this function should be called only when the user selects/accepts parameters and these parameters are correct.
recacceptbtn = widgets.Button(
description='Accept',
disabled=False,
button_style='success', # 'success', 'info', 'warning', 'danger' or ''
tooltip='Click to accept',
icon='check-square'
)
def rec_check_update_runnext(evn):
# testing parameters
if recfile.value is not None and recprobe.value is not None:
# saving parameters
run_next(evn)
else:
display(Markdown('**You must select file and probe!**'))
recacceptbtn.on_click(rec_check_update_runnext)
I haven't tested this approach extensively, but it works so far and works pretty well.
I have a rather complex relation with a polymorphic through-model. In the app, there is a tag system, where users and another model can be tagged. To avoid duplicate string entries, I used a through model (TagLink) which contains the tag ID, the tagged model name and the tagged instance ID.
So I have
User --+
|
+---> TagLink -----------> Tag
| - tagId - id
Item --+ - taggableId - text
- taggableName
(User or Item)
The linking relation is hasAndBelongsToMany. The problem is, when I post a new tag to /items/:id/tags or /users/:id/tags, it is saved, but I can create as many as I want with the same text without any error.
What I would like is, upon posting to /items/:id/tags or /users/:id/tags, it :
creates a new tag entry if it does not already exists, then adds a new TagLink entry (this is the current behaviour, but even when the same tag already exists)
solely creates a TagLink entry when a tag already exists
I thought about two solutions :
Somehow override the create method of Tag to check for existence, then manually create a TagLink entry if it exists. If not, proceed as by default.
Expose the tag list (/tags) with ~all CRUD URIs, then force to use the tag ID on /{items,users}/:id/tags.
Ideally, I would prefer the first one as it is more transparent and makes a smoother API.
So, any leads would be welcome!
I ended up doing the first solution, which was pretty easy. The idea is to replace the User.create method in a boot script which finds a tag with the same text, and returns it if one is found.
module.exports = function(app) {
var Tag = app.models.Tag;
// Override Tag.create to avoid duplicates
Tag.createRaw = Tag.create;
Tag.create = function(data, token, cb) {
// Find matching tag
Tag.findOne({where: {text: data.text}}, (err, tag) => {
if (err) cb(err);
// If the tag is found, return it
else if (tag) cb(null, tag);
// Else create it
else Tag.createRaw(data, token, cb);
});
}
}
I am trying to use protractor to get text from several pages, however the recursion seems not working...
The idea is:
getText() from elements with the same "name"
push the text into an array
browser.wait until the push is completed, and then check if the next page button is enable.
If enabled, go to the next page, and call the function again (keep pushing text in the next page to the array).
The codes are (coffeescript):
# define function getMyList
getMyList = (CL, CLTemp) ->
console.log "In function"
tabs = element.all(By.css "[name='some selector']").map((elm) -> elm.getText())
tabs.then((text) ->
CL.push text
return)
browser.wait(->
CL.length != CLTemp.length).then ->
console.log CL.length
nextPage = element(By.css "[title='Next Page']")
# hasClass is a function determining if the button is enabled or not
return hasClass("[title='Next Page']", 'disabled').then (class_found) ->
if class_found
console.log CL
return CL
else
console.log "Enabled"
CLTemp = CL
# scroll down to the button
browser.executeScript("arguments[0].scrollIntoView();", nextPage.getWebElement()).then ->
nextPageGo = element(By.css "[title='Next Page'] span[class=ng-binding]")
nextPageGo.click().then ->
getMyList (CL, CLTemp)
return
return
return
# Run the function. myList = [] and myListTemp = []
getMyList (myList, myListTemp)
The results print:
In function
1
Enabled
In function
Meaning it can run to the first recursion (i.e., goes to the next page), but stops at getting text of second page?
I've scratched my head for two days but still cannot figure out why the recursion is not working...
I am new to protractor. It will be great if I can get some hints!! Thank you very much.
I have a field named "selectedTime" in a document, this fields stores the selected timings added by user.Adding times is working perfect.This is back-end.
Now I will explain this issue of selecting date from front end.I have given a button add to add times.The custom control of date-time gets added to repeat control on click of Add button.Even if I check in document it shows the list of selected times.Even this works fine.
Now if I want to delete a selected time from repeat control randomly, it deleted that particular record from document, but on the page the last record of the repeat gets disappears,
I was assuming that this is the issue with partial refresh of repeat control,I have even tried that but no result.Full refresh breaks the page.
java script code for the delete button
`var doc:NotesDocument = database.getDocumentByUNID(context.getUrlParameter("refId"))
var selectedTimes:java.util.Vector = doc.getItemValue("selectedTimes");
if(selectedTimes != null){
var sdtString = getComponent("inputHidden1").getValue();
if(selectedTimes.contains(sdtString))
selectedTimes.remove(sdtString);
doc.replaceItemValue("selectedTimes",selectedTimes);
doc.save();
};
var url:XSPUrl = context.getUrl();
view.postScript("window.refresh('"+url+"')");`
I know it is difficult to understand what i want to explain but any suggestion on this will be appreciated.
Even if anybody have any idea to delete the a field values of a documents,In my case field name is "selectedTimes" and the values are added times in repeat control, Please share.
Edit 1:
//Repeat Control
var doc:NotesDocument = database.getDocumentByUNID(context.getUrlParameter("refId"))
var selectedTimes:java.util.Vector = doc.getItemValue("selectedTimes");
return selectedTimes;
Another try could be link the repeat with a viewScope instead of the document:
1) In the event beforeLoadPage/afterLoadPage: Get the value from the document, and put it in a viewScope variable:
// beforeLoadPage event:
// ... get the doc
viewScope.selectedTimes = doc.getItemValue("selectedTimes");
2) In the repeat control, use the viewScope:
<xp:repeat value="#{viewScope.selectedTimes}"...
3) When an update is done, update both the viewScope and the document:
//...update the View Scope variable and get the document:
doc.replaceItemValue("selectedTimes", viewScope.selectedTimes);
This could be a hint if the document would be added as DataSource:
Do you have the document included in the XPage as a DataSource? In that case, try to get and update the NotesXspDocument instead of the Document from the DB:
XPage:
<xp:this.data>
<xp:dominoDocument var="xspDocument"
action="editDocument"
documentId="#{param.unid}">
</xp:dominoDocument>
</xp:this.data>
SSJS code: work directly with the XspDocument
var selectedTimes:java.util.Vector = xspDocument.getItemValue("selectedTimes");
...
xspDocument.replaceItemValue("selectedTimes", selectedTimes);
This could be a hint if the value would not be removed from the document:
In sdtString you are getting a String value:
var sdtString = getComponent("inputHidden1").getValue();
If you have the time values stored as NotesDateTimes, you will get this type of value inside the Vector and the remove method won't find the String and nothing will be removed.
// In a Vector<NotesDateTime> the String cannot be found:
selectedTimes.remove(sdtString);
Be sure you remove the same type of value you get in the Vector