I'm writing a protractor test for angular code that adds an item to a list displayed with ng-repeat.
I want to check that when the user clicks a button, another item is added to the list within a few hundred milliseconds.
The initial count of items is unknown.
The test pseudocode is like this :
var e = element.all(by.repeater(...))
getButtonPromise().click() // -> XHR to server, which adds item to list
expect(e.count()).toBe(XXX)
Constraints :
1) the initial number of items must be measured BEFORE the click is executed
2) the expected value is (the initial value + 1)
3) expect(...) must be run periodically until timeout or list count has reached the expected value
4) the control flow for the rest of the test must be stopped until expect(...) resolves to success or timeout
I have tried various approaches but nothing works.
Your help in solving this common testing pattern is appreciated.
var e = element.all(by.repeater(...))
e.count().then(function(oldCount) {
getButtonPromise().click();
browser.wait(function() {
return e.count().then(function(newCount) {
return newCount === oldCount + 1;
})
}, YOUR_TIMEOUT_IN_MILLISECONDS);
expect(e.count()).toEqual(oldCount + 1); // this is optional because if this isn't true, it would have timed out already.
})
Related
I have a timed trigger that runs every 15 minutes. A simplified partial version of the script is shown below. The script compiles data from about 50 other spreadsheets and records a row for each spreadsheet, then writes that summary data to the active spreadsheet.
I noticed that in the logs, there is an alternating pattern in the execution times for this script: half the executions take 200-400 seconds, and the other half typically take 700-900 seconds. It's a pretty significant difference, and the pattern persists over the past several days of logs.
There's nothing in the script itself that changes from one execution to the next, so I'm curious if anyone can suggest a reason this would happen (even better if it's a documented reason). For example, is there some sort of caching of the spreadsheet reads so that the next execution gets those values faster?
// The triggered function.
function updateRankings()
{
var rankingSheet = SS.getSheetByName(RANKING_SHEET_NAME) // SS is the active spreadsheet
// Read the id's of the target spreadsheets, which are stored on an external spreadsheet
var gyms = getRowsData( SpreadsheetApp.openById(ADMIN_PANEL_ID).getSheetByName(ADMIN_PANEL_SHEET_NAME))
// Iterate over gyms
gyms.forEach(getGymStats)
// Write the compiled data back to the active sheet
setRowsData(rankingSheet, gyms)
}
function getGymStats(gym)
{
var gymSpreadsheet = SpreadsheetApp.openById(gym.spreadsheetId)
// Force spreadsheet formulas to calculate before reading values
SpreadsheetApp.flush()
var metricsSheet = gymSpreadsheet.getSheetByName('Detailed Metrics')
var statsColumn = metricsSheet.getRange('E:E').getValues()
var roasColumn = metricsSheet.getRange('J:J').getValues()
// Get stats
var gymStats = {
facebookAdSpend: getFacebookAdSpend(gymSpreadsheet),
scheduling: statsColumn[8][0],
showup: statsColumn[9][0],
closing: statsColumn[10][0],
costPerLead: statsColumn[25][0],
costPerAppointment: statsColumn[26][0],
costPerShow: statsColumn[27][0],
costPerAcquisition: statsColumn[28][0],
leadCount: statsColumn[13][0],
frontEndRoas: (roasColumn[21][0] / statsColumn[5][0]) || 0,
totalRoas: (roasColumn[35][0] / statsColumn[5][0]) || 0,
totalProjectedRoas: (roasColumn[36][0] / statsColumn[5][0]) || 0,
conversionRate: (gym.currency ?
'=IFS(ISBLANK(INDIRECT("R[0]C[-4]", FALSE)),,ISBLANK(INDIRECT("R[0]C[-2]", FALSE)), 1,TRUE, IFERROR(GOOGLEFINANCE("Currency:"&INDIRECT("R[0]C[-2]", FALSE)&"USD")))' :
1)
}
Object.assign(gym, gymStats)
}
function getFacebookAdSpend(spreadsheet)
{
var range = spreadsheet.getRangeByName('FacebookAdSpend')
if (!range) return ''
return range.getValue()
}
I want to create a list of items which are coming up in a particular time period. When the first item comes up, I want to start the timer, and, till the timer completes, any further items coming has to be added to the same list. Once the timer time completes, need to get this total list. After this, again another item comes, it has to start with a new list.
How do I do this in RxJS?
If I understood you correctly, you can use a bufferToggle() and a BehaviorSubject.
Note that I multicasted the randomIntGenerator, which would be your asynchronous list of items.
You can manipulate the predicatei > 5 suitable to your context
The BehaviorSubject's state is used to ensure that bufferToggle() is only allowed to track ONE stream of buffer at any time.
var Rx = require('rxjs');
let tap = new Rx.BehaviorSubject(false);
tap.subscribe(i=>console.log(`tap...${(i? 'on' : 'off')}`))
let randomIntGenerator = Rx.Observable
.interval(1000)
.map( ()=>getRandomInt(1 , 10) )
.share()
randomIntGenerator
.do(i=>console.log('emits: ', i))
.bufferToggle(
randomIntGenerator,
(i)=>{
if(i>5 && !tap.value){
tap.next(true)
return Rx.Observable.interval(3000)
}
}
)
.do(()=>tap.next(false) )
.subscribe( list=>console.log('#newlist -->', list) )
function getRandomInt (min, max){
//From MDN's Math,random() example
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
See the live implementation here
This code will do it for you:
console.clear()
const getRandom = () => {
return Math.round(Math.random()*300,0)
}
const timer$ = Rx.Observable.interval(1000) // A stream of numbers
.map(getRandom)
// .do(console.log) // uncomment to see each number as it is generated
.bufferTime(5000)
const s = timer$.subscribe(val => console.log( val));
This one uses a timer and a random number generator to provide a series of random numbers. These are buffered up for 5 seconds and then returned as an array.
The process starts again a second later as the next number arrives
A codepen is here https://codepen.io/mikkel/pen/VyzEgb?editors=1001
I am using the Indeed job search API which only shows 25 results per GET request.
I need to make 4 GET requests to receive 100 search results, incrementing the &start= number by 25 in the query URL every time.
I can recieve the first set of 25 results just fine, but I want to add a loop that increments the search results by 25, 4 times. The problem is I don't know how to add a callback function that waits for the loop to run 4 times and then pushes ALL 100 results into a single array, and THEN returns the results.
Can anyone help? This is my non-working code.
I am using NODE, and AXIOS for the GET requests.
var axios = require("axios");
var emptyArray = [];
var num = 0;
function runQuery(term){
for(var x=0; x<=3; x++){
var URL = "https://api.indeed.com/ads/apisearch?
publisher=4548xxxxxxxxxxxx&v=2&format=json&q=&l=" + term + "&radius=25&start=" + num + "
&limit=25&latlong=1&co=us&userip=1.2.3.4&useragent=GoogleChrome&v=2"
return axios.get(URL).then(function(response) {
for(var y=0; y<=response.data.results.length-1; y++){
emptyArray.push(response.data.results[y]);
}
return emptyArray;
}); //End axios.get
num = num+25;
}//End For Loop
} // End runQuery
runQuery("New York");
I might be wrong, but I think u just don't need the second for loop inside your callback, because your first loop already do the magic. It will trigger .get four times with 4 different callbacks. And use applyfunction to add response array to the existing one. Try to do it like this:
return axios.get(URL).then(function(response) {
emptyArray.push.apply(emptyArray, response.data.results);
}); //End axios.get
num = num+25;
So I've encountered a weird issue when dealing with making Groups based on a variable when the crossfilter is using an array, instead of a literal number.
I currently have an output array of a date, then 4 values, that I then map into a composite graph. The problem is that the 4 values can fluctuate depending on the input given to the page. What I mean is that based on what it receives, I can have 3 values, or 10, and there's no way to know in advance. They're placed into an array which is then given to a crossfilter. When in testing, I was accessing using
dimension.group.reduceSum(function(d) { return d[0]; });
Where 0 was changed to whatever I needed. But I've finished testing, for the most part, and began to adapt it into a dynamic system where it can change, but there's always at least the first two. To do this I created an integer that keeps track of what index I'm at, and then increases it after the group has been created. The following code is being used:
var range = crossfilter(results);
var dLen = 0;
var curIndex = 0;
var dateDimension = range.dimension(function(d) { dLen = d.length; return d[curIndex]; });
curIndex++;
var aGroup = dateDimension.group().reduceSum(function(d) { return d[curIndex]; });
curIndex++;
var bGroup = dateDimension.group().reduceSum(function(d) { return d[curIndex]; });
curIndex++;
var otherGroups = [];
for(var h = 0; h < dLen-3; h++) {
otherGroups[h] = dateDimension.group().reduceSum(function(d) { return d[curIndex]; });
curIndex++;
}
var charts = [];
for(var x = 0; x < dLen - 3; x++) {
charts[x] = dc.barChart(dataGraph)
.group(otherGroups[x], "Extra Group " + (x+1))
.hidableStacks(true)
}
charts[charts.length] = dc.lineChart(dataGraph)
.group(aGroup, "Group A")
.hidableStacks(true)
charts[charts.length] = dc.lineChart(dataGraph)
.group(aGroup, "Group B")
.hidableStacks(true)
The issue is this:
The graph gets built empty. I checked the curIndex variable multiple times and it was always correct. I finally decided to instead check the actual group's resulting data using the .all() method.
The weird thing is that AFTER I used .all(), now the data works. Without a .all() call, the graph cannot determine the data and outputs absolutely nothing, however if I call .all() immediately after the group has been created, it populates correctly.
Each Group needs to call .all(), or only the ones that do will work. For example, when I first was debugging, I used .all() only on aGroup, and only aGroup populated into the graph. When I added it to bGroup, then both aGroup and bGroup populated. So in the current build, every group has .all() called directly after it is created.
Technically there's no issue, but I'm really confused on why this is required. I have absolutely no idea what the cause of this is, and I was wondering if there was any insight into it. When I was using literals, there was no issue, it only happens when I'm using a variable to create the groups. I tried to get output later, and when I do I received NaN for all the values. I'm not really sure why .all() is changing values into what they should be especially when it only occurs if I do it immediately after the group has been created.
Below is a screenshot of the graph. The top is when everything has a .all() call after being created, while the bottom is when the Extra Groups (the ones defined in the for loop) do not have the .all() call anymore. The data is just not there at all, I'm not really sure why. Any thoughts would be great.
http://i.stack.imgur.com/0j1ey.jpg
It looks like you may have run into the classic "generating lambdas from loops" JavaScript problem.
You are creating a whole bunch of functions that reference curIndex but unless you call those functions immediately, they will refer to the same instance of curIndex in the global environment. So if you call them after initialization, they will probably all try to use a value which is past the end.
Instead, you might create a function which generates your lambdas, like so:
function accessor(curIndex) {
return function(d) { return d[curIndex]; };
}
And then each time call .reduceSum(accessor(curIndex))
This will cause the value of curIndex to get copied each time you call the accessor function (or you can think of each generated function as having its own environment with its own curIndex).
I read on many forums about how to implement a solution for view pagionation, but I didn't solve it.
I created $$ViewTemplateDefault containing some personalized hotspotbuttons for Next, Previous and a text field $$ViewBody. ( or, alternatively, an embedded view ).
Any tips and help will be really appreciated.
I will explain in a couple words, just to be clear:
So, initially: the first 30 lines will appear => in a right corner: Page 1.
If Next is clicked => the next 30 lines => Page 2. and so on.
Here is a working solution for categorized views too. It calculates the current page number based on the previous page number and uses cookies.
Add to your form a Path-Thru HTML text <span id="pageNumber"></span > for the page number:
and add following code to form's onLoad event as Web/JavaScript:
function getURLParameter(parameter) {
return decodeURIComponent((new RegExp('[?|&]' + parameter + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [, ""])[1].replace(/\+/g, '%20')) || null;
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for(var i=0; i<ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name)==0) return c.substring(name.length,c.length);
}
return "";
}
function compareStart(start1, start2) {
var list1 = start1.split(".");
var list2 = start2.split(".");
for (i=0; i <100; i++) {
var value1 = list1[i];
var value2 = list2[i];
if (value1 == null) {
return value2 == null ? 0 : -1;
} else if (value2 == null) {
return 1;
}
value1 = Math.round(value1);
value2 = Math.round(value2);
if (value1 !== value2) {
return value1 < value2 ? -1 : 1;
}
}
}
var start = getURLParameter("Start");
var page = "1";
if (start == null || start === "1") {
window.name = Math.floor((Math.random()*10000)+1);
start = "1";
} else {
page = getCookie("page" + window.name);
var oldStart = getCookie("start" + window.name);
page = Math.round(page) + compareStart(start, oldStart);
}
document.getElementById('pageNumber').innerHTML = page;
document.cookie = "page" + window.name + "=" + page;
document.cookie = "start" + window.name + "=" + start;
How does it work?
The commands #DbCommand("Domino"; "ViewNextPage") and #DbCommand("Domino"; "ViewPreviousPage") return an URL with parameter "&Start=". This is the row number in view where the current page starts. For categorized views they return a hierarchical number like "&Start=1.2.4.2". That means that the view starts at the first main topic, subtopic 2, subsubtopic 4, document 2.
This parameter "&Start=" gives us the possibility to recognize if user pressed "prev" or "next": we just compare the URL "&Start=" parameter of current and former page.
For that, we have to remember the URL "&Start=" parameter and put it into a cookie "start".
We also need to save the current page number. We put it into a cookie "page".
At onload event we calculate the current page number based on previous page number:
if "&Start=" parameter is larger now then we add 1
if "&Start=" parameter is smaller now then we subtract 1
if "&Start=" parameter didn't change then we keep the former value
If "&Start=" parameter is empty we know we are on page 1.
Here is one other thing we have to deal with: cookies are saved per user session not per browser tab. That means, if we have two views open in browser same cookies "start" and "page" would be used. To avoid that, we have to add to cookie name something tab specific. I use for that a random four digit number and save it in window.name which is tab specific.
I understand your question that you have a working form $$ViewTemplateDefault and now looking for a possibility to show the current page number "Page nn" in that form.
I assume that you use #DbCommand("Domino"; "ViewNextPage") for getting next page and #DbCommand("Domino"; "ViewPreviousPage") for getting previous page.
Those next and prev functions working the way that always one document will "overlap". If you have 30 lines per page and click next, then last document will be first in next page and next 29 show up in addition. You can watch that in used URL parameter "&Start=": 1 ... 30 ... 59 ... 88 ...
Knowing this you can count the current page number this way:
_start := #ToNumber(#Replace(#UrlQueryString("start"); ""; "1"));
_count := #ToNumber(#Replace(#UrlQueryString("count"); ""; "30")) - 1;
#Integer((#ToNumber(_start) / _count) + 1)
Be aware that this will work for non-categorized and non-collapsible views only.
A more sophisticated solution you can find here. It has additional features like GoTo page and Documents per page.
If you have the chance for your project then use XPages instead. You can do pagination much easier as it is available "out of the box".
Update:
You won't find a reasonable solution for categorized views. If you don't want to use Domino Data/Access Services REST API you have to live with the Domino view URL parameters (look here for "OpenView"). You aren't able to tell from "&Start=" or any other parameter on which page you are currently on.
The easiest way to get a good working pagination is using XPages. Hope you are allowed to use it in your project...