While loops in protractor - javascript

I have recently started using protractor for e2e tests. I have a combobox which lists events that match what the user types in - so the list is empty if no such events are found.
What I would like to do is enter 3 random characters, and if the combobox list is empty, clear the combobox and retry another 3 random characters. This should be repeated until a non-empty list is found.
I have tried using a simple while loop to do this, but the asynchronous nature of webdriverjs means that I become stuck in an infinite loop. Is it possible to somehow wait inside the loop for the combobox to be populated? Or is there some other, cleaner solution to this problem?
Code:
var query = element(by.model('searchStr'));
query.clear();
var letters = generateRandomLetters();
console.log(letters);
query.sendKeys(letters);
var eventList = element.all(by.repeater('result in results'));
eventList.count().then(function(count) {
if(count) {
//test continues here
}
});

Declare a function that calls itself recursively until your condition is met. That should to the trick.

Related

How to speed up performance of autocomplete from indexeddb database

I have jQuery autocomplete field that has to search through several thousand items, populated from an IndexedDB query (using the idb wrapper). The following is the autocomplete function called when the user begins typing in the box. hasKW() is a function that finds keywords.
async function siteAutoComplete(request, response) {
const db = await openDB('AgencySite');
const hasKW = createKeyWordFunction(request.term);
const state = "NY";
const PR = 0;
const agency_id = 17;
const range = IDBKeyRange.bound([state, PR, agency_id], [state, PR, agency_id || 9999999]);
let cursor = await db.transaction('sites').store.index("statePRAgency").openCursor(range);
let result = [];
while (cursor) {
if (hasKW(cursor.value.name)) result.push({
value: cursor.value.id,
label: cursor.value.name
});
cursor = await cursor.continue();
}
response(result);
}
My question is this: I'm not sure if the cursor is making everything slow. Is there a way to get all database rows that match the query without using a cursor? Is building the result array slowing me down? Is there a better way of doing this? Currently it takes 2-3s to show the autocomplete list.
I hope this will be useful to someone else. I removed the cursor and just downloaded the whole DB into a javascript array and then used .filter. The speedup was dramatic. It took 2300ms using the way above and about 21ms using this:
let result = await db.transaction('sites').store.index("statePRAgency").getAll();
response(result.filter(hasKW));
You probably want to use an index, where by the term index, I mean a custom built one that represents a search engine index. You cannot easily and efficiently perform "startsWith" style queries over one of indexedDB's indices because they are effectively whole value (or least lexicographic).
There are many ways to create the search engine index I am suggesting. You probably want something like a prefix-tree, also known informally as a trie.
Here is a nice article by John Resig that you might find helpful: https://johnresig.com/blog/javascript-trie-performance-analysis/. Otherwise, I suggest searching around on Google for trie implementations and then figuring out how to represent a similar data structure within an indexedDb object store or indexdDb index on an object store.
Essentially, insert the data first without the properties used by the index. Then, in an "indexing step", visit each object and index its value, and set the properties used by the indexedDb index. Or do this at time of insert/update.
From there, you probably want to open a connection shortly after page load and keep it open for the entire duration of the page. Then query against the index every time a character is typed (probably want to rate limit this call to refrain from querying more than n/second, perhaps using some kind of debounce helper function).
On the other hand, I might be a bit rusty on this one, but maybe you can create an index on the string prop, then use a lower bound that is the entered characters. A string that is lesser length than another string that contains it is present earlier in lexicographic order. So maybe it is actually that easy. You would also need to impose an upper bound that contains the entered characters thus far concatenated with some kind of sentinel value that can never realistically exist in the data, something silly like ZZZZZ.
Try this out in the browser's console:
indexedDB.cmp('test', 'tasting'); // 1
indexedDB.cmp('test', 'testing'); // -1
indexedDB.cmp('test', 'test'); // 0
You essentially want to experiment with a query like this:
const sentinel = 'ZZZ';
const index = store.index('myStore');
const bounds = IDBKeyRange.bound(value, value + sentinel);
const request = index.get(bounds);
You might need to tweak the sentinel, experiment with other parameters to IDBKeyRange.bound (the inclusive/exclusive flags), probably need to store the value in homogenized case so that the search is case insensitive, avoid every sending a query when nothing has been typed, etc.

Cypress .each() function only iterating on first instance of element

I'm attempting to run through a list of dynamic items on my app. Each item is made up of several displayed elements like name, due date, etc. - These are currently entered by users, so I'm stubbing the data in my test via fixtures to verify they're working as expected.
I'm currently using a .each() loop that references the fixture, referred to as patientHealthGoals. Listed below is the relevant portions of the .each() loop.
cy.get('userGoalList')
.each(($el, index) => {
cy.get('userGoalItem')
.within(() => {
cy.get('[data-cy="due-date"]')
.should('have.value', userGoals[index].due_at);
cy.get('[data-cy="comments"]')
.should('have.value', userGoals[index].comment);
});
});
What's strange is that, on each iteration, it correctly checks the [data-cy="due-date"] field. However, on the [data-cy="comments"], it always compares against the first instance of that element, no matter what index it's on. Here is the relevant error I'm getting:
expected [ < textarea.block.-with-updated.ng-untouched.ng-pristine.ng-valid.om-input >, 3 more... ] to have value 'Notes for second item', but the value was 'Notes for first item'
The only difference between the [data-cy="due-date"] and [data-cy="comments"] checks that I can see are the elements themselves. The first one is an input and the latter is a textarea, but I fail to see why Cypress would be treating them completely differently. Any ideas what might be breaking here?

How can I know when a Firebase query has ended

I want to collect the most recent 10 items from my datastore. I believe I am supposed to do this using .child() and .limitToLast(), which emits an event every time a result has been added to the child.
I don't necessarily know if there are 10 items in total, so a simple counter won't work.
How do I know when Firebase is finished giving me results?
Example lookup code:
var plots = [];
firebaseDatastore.child("plots").orderByChild("unicode-timestamp").limitToLast(10).on("child_added", function(snapshot) {
// Add the new item to the list
plots.push(snapshot.val());
});
I need to know when the final plot has been added, regardless of whether it has hit the limit.
A Firebase query never really ends. It continues to monitor (in your case) the plots by unicode-timestamp and keep a "window" of the last 10 plots.
So:
child_added:timestamp1
child_added:timestamp2
...
child_added:timestamp9
child_added:timestamp10
And then when you add another plot:
child_removed:timestamp1
child_added:timestamp11
If you are not looking to use this behavior, you have two options:
use a value event and then snapshot.forEach over the child nodes
keep a counter and off your listener when you've reached the number of children you expect

Select list search on very large dataset

Currently I have Select2 in my application, and have previously implemented ajax calls to the database to get a smaller subset based on search query entered by a user.
However, users want to be able to click the back arrow on the browser, and have the query automatically run again (something that currently does not happen with Select2). I was able to implement this by pulling the entire dataset (over 18,000 elements) in and calling select2 on that.
The problem with this is that Select2 basically does a foreach in a foreach when doing a search (foreach element in the dataset, go through each string and get the index of the query - which I understand is basically breaking the string into a char array and checking each char individually to see if the combination is found). So every time someone types a character, we're looking at over 18,000 operations, even though the majority of elements are eliminated as options.
Is there a way to make Select2 actually eliminate the options that don't match (create and bind to a temp array or something like that) or perform a binary search instead of a linear search? If not, are there any alternatives to Select2 that DO implement binary search instead of linear search, or would I need to create my own jQuery plugin to do this?
In this jsfiddle1 a hidden select element is used and a clone of that to filter input. The filtering is done with:
for (var i = 0; i < that.selector.options.length; i++) {
if (re.test(that.selector.options[i].text)) {
sclone.add(new Option(that.selector.options[i].text, i));
}
}
Where re is a RegExp created from an input field that placed above the select clone.
Maybe that's an idea to play with?
1 The language used in the first selector is dutch, but I suppose that shouldn't be obstructive to the idea.

Filtering with large dataset

I have a large data-set with roughly 10,000 records. I want to be able to have a filter mechanism on this data-set. That basically performs a LIKE sql expression on a field and returns the matching results.
To do this, I've used JQuery to bind the "input" event on my filter textbox to my filter handler function.
The issue at the moment is, that If a load of keys are pressed at once in the textbox, then the filter function gets called many times, thus making many SQL calls to filter which is very inefficient.
Is there a way I can detect in my handler when the user has finished typing or when there's a gap of a certain period and only then performing the filtering? So i only make one database call when loads of characters get input at once. If the characters get input slowly though, I want to be able to filter each time.
Cheers.
Here is a way of doing it jsfiddle
var test = 0;
$('body').on('keyup','input',function(){
var val = $.trim($(this).val());
test++;
if(val !== ""){
doSomething(test);
}
});
function doSomething(t){
setTimeout(function(){
if(t === test){
//Place to Call the other Function or just do sql stuff
alert($.trim($('input').val()));
}
},500);
}
There is a global variable to test against, if the user typed another letter. And the setTimeout function waits 500ms to see if they did type another letter.
I'm sure there are better ways to do this, but that will probably get you started.

Categories