A project includes a page on which exists a column showing foods associated with a meal and a (paginated) column of foods not associated with a meal. A script allows to click on a food in the paginated column, have that food removed from the list of available foods and appear in the list of associated foods. In the dev environment I can demonstrate that the script works.
In an effort to learn how to use Panther in testing I've tried to reproduce the effect of clicking on a food. The test code below hopes to show that the first row of the table of unassociated foods changes after a click. That test runs without error but fails. The question, then, is how or whether to make a test that shows a change in the table.
Edit: When I forced a Firefox client (static::createPantherClient(['browser' => static::FIREFOX]);) and adding PANTHER_NO_HEADLESS=trueto.env.test.local` the test ran slow enough for me to observe the first table row was removed. So somehow the test needs a way to read the new first row (and is different from how the test tries to do that now).
Edit 2: If I insert the lines $client->request('GET', 'http://diet/meal');$newCrawler = $client->clickLink('edit'); after executing the script I can get the test to pass. This seems to be a different test from expecting the test to pass without leaving and returning to the page. Or is it not possible to test without leaving?
Edit 3: Since Panther says it "can wait for asynchronously loaded elements to show up" I added a data attribute which increments on clicking. The test now includes the line $this->assertSelectorWillNotContain("document.querySelector('#mealid').getAttribute('data-rte')", $rteCount);. Nice, except that Panther returns Given css selector expression "document.querySelector('#mealid').getAttribute('data-rte')" is invalid even though a Firefox console with the identical selector returns an integer.
table row: <td data-foodid="185">dolor</td>
test code:
class MealTest extends PantherTestCase
{
public function testFood()
{
$client = static::createPantherClient();
$client->followRedirects();
$client->request('GET', 'http://diet/meal');
$this->assertPageTitleContains('Meals');
$crawler = $client->clickLink('edit');
$this->assertPageTitleContains('Edit Meal');
$foodLink = $crawler->filter('#meal_pantry td')->first();
$q = $foodLink->attr('data-foodid');
$client->executeScript("document.querySelector('#meal_pantry td').click()");
$client->waitFor('#meal_pantry');
$nextUp = $crawler->filter('#meal_pantry td')->first();
$p = $nextUp->attr('data-foodid');
$this->assertNotEquals($p, $q);
}
}
javascript:
$('td').on('click', function (e) {
var foodId = $(e.currentTarget).data('foodid');
var mealId = $("#mealid").data("mealid");
var tableId = $(this).parents('table').attr('id');
var pageLimit = $("#mealid").data("pagelimit");
$packet = JSON.stringify([foodId, mealId, tableId]);
$.post('http://diet/meal/' + mealId + '/editMealFood', $packet, function (response) {
editFoods = $.parseJSON(response);
var readyToEat = $.parseJSON(editFoods[0]);
var pantry = $.parseJSON(editFoods[1]);
var table = document.getElementById('ready_foods');
$('#ready_foods tr:not(:first)').remove();
$.each(readyToEat, function (key, food) {
row = table.insertRow(-1);
cell = row.insertCell(0);
cell.innerHTML = food;
});
var table = document.getElementById('meal_pantry');
$('#meal_pantry tr:not(:first)').remove();
$('li.active').removeClass('active');
$('li.page-item:nth-of-type(2)').addClass('active');
$.each(pantry.slice(0, pageLimit), function (key, array) {
food = array.split(",");
foodId = food[0];
foodName = food[1];
var row = table.insertRow(-1);
var cell = row.insertCell(0);
cell.innerHTML = foodName;
cell.setAttribute('data-foodid', foodId);
});
location.reload();
});
});
Turns out I've been using the wrong assertions. The eventual solution:
$client = static::createPantherClient();
$client->followRedirects();
$client->request('GET', 'http://diet/meal');
$this->assertPageTitleContains('Meals');
$crawler = $client->clickLink('edit');
$this->assertPageTitleContains('Edit Meal');
$rteCount = $crawler->filter('#mealid')->attr('data-rte');
$client->executeScript("document.querySelector('#meal_pantry td').click()");
$client->waitForAttributeToContain('#mealid', 'data-rte', $rteCount + 1);
Related
Can someone suggest a solution for below:
I am taking inputs from a CSV file using javascript. On a button click, I would like to update two tables in HTML. I have set two dvis; dvCSV and alld.
When I click on button the table only gets updated in "alldc" not in "dvCSV".
If I remove var alld =......upto "alld.appendChild(table);" then only "dvCSV" table gets updated with the data.
var dvCSV = document.getElementById("dvCSV");
dvCSV.innerHTML = "";
dvCSV.appendChild(table);
var alld = document.getElementById("alld");
alld.innerHTML = "";
alld.appendChild(table);
I'm failry new to Javascript and Servicenow so bear with me please. I'm creating a fix script to populate the incident breach time label on the incident task. It will show the same SLA as the parent incident. My below script returns the correct number of records that should be updated. However, when I go to update the record in my second GlideRecord, it only updates one record(Should be about 125). I commented the update portion of the code for testing purposes. Any suggestions? Thanks
var grSLA = new GlideRecord('u_incident_task');
grSLA.addEncodedQuery('u_incident_breach_timeISEMPTY^parentISNOTEMPTY^stateIN1,2,4');
grSLA.query();
var count=0;
gs.info("{0} of Itasks found to update", grSLA.getRowCount());
while (grSLA.next())
{
var ipar = grSLA.parent.sys_id;
}
gs.print(ipar);
var grName = new GlideRecord('task_sla');
grName.addQuery('task', ipar);
grName.addQuery('stage', 'in_progress');
grName.query();
while (grName.next())
{
var bTime = grName.planned_end_time.getDisplayValue();
grSLA.u_incident_breach_time=bTime; //sets the Incident Breach Time on the iTask
//grSLA.update();
count++;
}
I believe you need to move your task_sla loop inside of your u_incident_task loop.
It is only updating the final one since ipar is the last record in the loop.
var grSLA = new GlideRecord('u_incident_task');
grSLA.addEncodedQuery('u_incident_breach_timeISEMPTY^parentISNOTEMPTY^stateIN1,2,4');
grSLA.query();
var count=0;
gs.info("{0} of Itasks found to update", grSLA.getRowCount());
while (grSLA.next())
{
var ipar = grSLA.parent.sys_id;
gs.print(ipar);
var grName = new GlideRecord('task_sla');
grName.addQuery('task', ipar);
grName.addQuery('stage', 'in_progress');
grName.query();
while (grName.next())
{
var bTime = grName.planned_end_time.getDisplayValue();
grSLA.u_incident_breach_time=bTime; //sets the Incident Breach Time on the iTask
grSLA.update();
count++;
}
}
I am using https://github.com/wenzhixin/bootstrap-table on some project.
I find that is really easy to use and implement pagination. However I have some problem regarding custom html in different rows. This is just peace of code.
$('#selector').bootstrapTable({
pagination: true,
url : some_rest_url,
sidePagination: 'server',
onLoadSuccess: function (res) {
var data_ = [];
var rows = res.rows;
for (var i =0; i < rows.length; i ++) {
var data = {};
var item = rows[i];
$.each(item, function (key, value) {
if (key == "cost") value = "< span class="cl" >"currency + " " + parseFloat(value).formatNumber(2, '.', ',')."< / span >";
//and so on some more styling and formatting for other elements/columns of table
data[key] = value;
});
data_.push(data);
}
$('#selector').bootstrapTable("load", data_);
So table should have one column and in each row span element with that class but that is not happening.
I just have that default plain text data from boostrapTable default load (json data).
BTW when using plain ajax call instead of that default boostrapTable pagination thingy everything works great but then i have to make custom pagination (and using sidePagination = client is just wrong and working slow when have like 1000 records ).
After wasting couple of hours, solution was to use formatter for columns. For example:
field: 'column_name',
formatter: operateFormatter
function operateFormatter(value, row, index){
//value is text from json
//row is all values from json for that row
}
well this way, code will be much more clearer.
I'm trying to condense two processes down in to one by having the two pages I need on one page using an iframe.
I have a page that contains a text area (used for sending an email) and then I have a purchase reference page that contains the details of someones purchase.
I'm trying to append an iframe of the purchase page to the bottom of my email page and then grab some data that's on it and insert it in to the text area.
EDIT: This is what I have so far:
Script one
//Grabs the selected purchase number
var purchaseNumber = window.getSelection();
purchaseNumber = purchaseNumber.toString();
var purchaseTitle;
var purchaseNumber;
function frameLoaded() {
purchaseTitle = window.frames['purchaseIframe'].contentDocument.getElementById ('listingTitle');
purchaseNumber = window.frames['purchaseIframe'].contentDocument.getElementById ('auctionSoldIdDisplay');
purchaseTitle = purchaseTitle.innerHTML;
purchaseNumber = purchaseNumber.innerHTML
var purchaseDetails = purchaseTitle + " - " + purchaseNumber;
insertText = insertText.replace("PURCHASEDETAILS", purchaseDetails);
}
if(purchaseNumber.length > 0){
var purchaseIframe = document.createElement('iframe');
purchaseIframe.src = 'http://www.mysite.co.nz/Admin/Listing/PurchaseDisplay.aspx?asid=' + purchaseNumber + '&submit1=++GO++';
purchaseIframe.setAttribute("height","1000");
purchaseIframe.setAttribute("width","100%");
purchaseIframe.setAttribute("id","purchaseIframe");
purchaseIframe.setAttribute("onload", "frameLoaded();");
void(document.body.appendChild(purchaseIframe));
alert(purchaseNumber);
}
Script Two
//Gather the selected template
var selectedTxt = document.getElementById('txtEmailText').value;
//Change the selected txt to a string
var insertText = selectedTxt.toString();
var purchaseTitle = window.frames['purchaseIframe'].contentDocument.getElementById ('listingTitle');
var purchaseNumber = window.frames['purchaseIframe'].contentDocument.getElementById ('auctionSoldIdDisplay');
purchaseTitle = purchaseTitle.innerHTML;
purchaseNumber = purchaseNumber.innerHTML
var purchaseDetails = purchaseTitle + " - " + purchaseNumber;
insertText = insertText.replace("PURCHASEDETAILS", purchaseDetails);
//Pasting the variable in to the textarea
document.getElementById('txtEmailText').value = insertText;
Effectively I am highlighting the purchase reference number on the page then executing this script to open the purchase page using the highlighted number. I am then grabbing the text values of the elements I need and pasting them in to the text area.
I'm still pretty new to javascript and am teaching myself as I go.
If i run the above scripts one after the other then it works like a charm, however if I try to run them together with the second in an onload() function set to the iframe then it won't.
Any help would be greatly appreciated or if you could point me in the direction of an article to help.
My first thought is that the iframe is not fully loaded before you try to get the values from it. My thought would be to try adding an onload event to your iframe and then when it loads invoke a function that grabs the value.
I would add purchaseIframe.setAttribute("onload", "frameLoaded();"); to your purchaseIframe block and then add the frameLoaded() function to your script. something like:
function frameLoaded() {
var purchaseTitle = window.frames[0].document.getElementById("listingTitle" );
var purchaseNumber = window.frames[0].document.getElementById("auctionSoldIdDisplay");
console.log(purchaseTitle.innerHTML);
console.log(purchaseNumber.innnerHTML);
}
And see if something like that grabs the right values. If it does than you can plug it in where you need it.
Am I understanding your problem correctly?
I am trying to add create an editable table in HTML. So, I need to add rows, when user clicks on a button. Here is the code I am using.
//remove all old rows.
while (summaryTableElement.firstChild) {
summaryTableElement.removeChild(summaryTableElement.firstChild);
}
//add a new text area in the summary field.
summaryTableElement.innerHTML = "<textarea id='summaryTextBox' value='' onblur='saveSummary()'> </textarea> ";
This code works fine on Chrome and Mozilla. But, IE doesn't allow editing innerHTML. This HTML will be displayed inside a VB form, so I have "browser" and "webbrowser" controls available. Both of those controls use IE engine, so this code does not work there.
I have tried using firstchild.innerHTML instead so that I am not editing table row directly, and even that gave me same error.
I tried using WebKitBrowser. But, that causes an exception on load. Apparently that control uses some old method, and the method is changed. So, that control is out of question too.
Now, I am confused as to how to solve this problem?
Edit:
I found the issue with WebKitBrowser. It was specific to x86, while my project was targeting both x64 and x86. Switching it to x86 for both, worked. Although, webkit browser do not take return key values. So, I have a new problem. Pressing enter does not create a new line in table cell, and instead passes the event to vb form.
Edit:
Okay, so I tried working further with WebKitBrowser. But, that control gives me accessviolatedexception. So, I am thinking about going back to using the default browser control, for now.
As for default control, I checked further. Apparently, code works fine in IE9 and above. But, vb.net control uses some weird JS engine. So, I am receiving error only in vb control.
Here is the complete code for reference:
var summaryChanges = "";
var isSummaryClickable = true;
//creates a textbox for editing purpose and include the original summary data.
function modifySummary(id) {
var summaryTableElement; //to get the table cells so that we can access the value
var originalSummary = ""; //to hold the original summary while we create a text box
var loopCounter = 0;
if (isSummaryClickable == true) {
isSummaryClickable = false; //saves event spilling when clicked on the newly created textbox
//get the summary table id saved.
summaryTableElement = document.getElementById (id).childNodes.item(0);
//add existing data to a string to save it for later.
for (loopCounter = 0; loopCounter < summaryTableElement.childNodes.length; loopCounter++) {
originalSummary = originalSummary + summaryTableElement.childNodes.item(loopCounter).childNodes.item(0).innerHTML + "\n";
}
//remove all old rows.
while (summaryTableElement.firstChild) {
summaryTableElement.removeChild(summaryTableElement.firstChild);
}
//add a new text area in the summary field.
summaryTableElement.innerHTML = "<tr><td><textarea id='summaryTextBox' value='' onblur='saveSummary()'> </textarea></td></tr> ";
//set the width of the textbox to allow easy modification
document.getElementById("summaryTextBox").style.width = '600px';
//add original summary text to the text area and set focus
document.getElementById("summaryTextBox").value = originalSummary;
document.getElementById("summaryTextBox").focus();
}
}
// saves the updated summary data back to HTML
function saveSummary()
{
var newHTML = "";
var changesSummary;
var changesSubString = new Array();
var index = 0;
var elementToUpdate;
//get the summary table id saved.
elementToUpdate = document.getElementById("SummaryData"); //.childNodes.item(0);
//get changes from the textbox
changesSummary = document.getElementById("summaryTextBox").value;
//generate string array for separate lines from summaryChanges
changesSubString = changesSummary.split("\n");
//generate new HTML string for the summary fields
//alert(changesSubString.length);
for (index = 0; index < changesSubString.length; index++) {
newHTML = newHTML + "<tr><td class='Text' style='padding-left: 4px;padding-bottom: 4px;' id='SummaryLine" + index + "'</td>" + changesSubString[index] + "</tr>"
}
//update the HTML to the element
elementToUpdate.innerHTML = newHTML;
//allow clicking on summary table
isSummaryClickable = true;
}
function doNothing(id) {
//empty function for future use
return;
}
//this function is called from vb.net script to read the js variable.
//do not change the function definition (name or parameters or return type)
//any change will cause error in vb.net script
function jsInvokedFunction() {
var updatedHTML; // to be used by vb.net invoked function to get updated body
var testVariable = "test";
updatedHTML = document.body.innerHTML.toString();
return updatedHTML.toString();
}
I receive an error at the start of the line summaryTableElement.innerHTML = " ";
It is possible I am missing some other error, but I couldn't find any.