Ionic 3 infinite-scroll simulate in e2e test jasmine/protractor - testing

If you go here: http://ionicframework.com/docs/api/components/infinite-scroll/InfiniteScroll/
Inspect the demo and click the last item on the list:
Then in the console type: $0.scrollIntoView()
Infinite Scroll is never triggered.
Is there a way to programmatically trigger infinite-scroll in protractor context?

The implementation of the scroll in your example rely on the speed/velocity of the scroll which I guess falls far from the expected range when scrollIntoView is called.
One workaround is to simulates a smooth scroll by emitting multiple scroll events over a reasonable time. The idea is to reproduce as close as possible the behavior of a real user.
Some browsers already provides the option via scrollIntoView (supported by Chrome 62) :
$0.scrollIntoView({behavior: "smooth", block: "end"});

Using the accepted answer, in my case, I used ion-infinite-scroll as the argument.
Complete test to check if more content is loaded in Ionic:
describe('Scroll', () => {
it('should load more when reached end', async () => {
let list = getList();
let currentCount = await list.count();
const refresher = element(by.tagName('ion-infinite-scroll')).getWebElement();
let count = 0;
while(true){
browser.executeScript(`arguments[0].scrollIntoView({behavior: "smooth", block: "end"});`, refresher);
browser.sleep(1000); // wait for data to be loaded from api
list = getList();
let newCount = await list.count();
expect(newCount).toBeGreaterThanOrEqual(currentCount)
expect(newCount).toBeLessThanOrEqual(currentCount * 2)
if(newCount === currentCount){
break;
}
currentCount = newCount;
count++;
}
expect(count).toBeGreaterThan(0);
})
});
function getList() {
return element(by.className(pageId + ' list')).all(by.tagName('ion-item'));
}

Related

It is so slow when a callback function be called repeatedly in react native app

It will have a callback function called repeatedly to when I start a download task. But It will be so slow that it can't give feedback when touch a button.
Fileio.downloadFile(downloadData[downloadFileIndex].uri, '1.jpg',this.progressFunc.bind(this)).then((DownloadResult)=> {
if (DownloadResult.statusCode == 200) {
let nextDownloadFileIndex = this.props.downloadFileIndex + 1;
this.props.dispatch(DOWNLOADED_A_FILE({
downloadFileIndex:nextDownloadFileIndex,
progressNum:0
}))
}
}).catch((error)=> {
console.log(error)
})
This is my code and the callback function are as be folllowed
progressFunc(DownloadBeginCallbackResult) {
let progressNum = DownloadBeginCallbackResult.bytesWritten / DownloadBeginCallbackResult.contentLength;
if(progressNum<=0.99){
this.props.dispatch(DOWNLOADING_A_FILE({
progressNum:progressNum,
jobId:this.props.jobId
}));
}else{
this.props.dispatch(DOWNLOADING_A_FILE({
progressNum:0,
jobId:this.props.jobId
}));
}
}
I mean I can't get feedback immediately when I touch button. I think that it is because I have a callback function called repeatedly. so js can't handle so many tasks;
It does sound like JS thread is busy doing the request and not able to communicate back to UI thread. One thing you can try is to wrap you on press handler in an InteractionManager.runAfterInteractions(() => ...)
See https://facebook.github.io/react-native/docs/interactionmanager.html

Selenium: are there events like "New element inserted in DOM"

The site I am testing has a notification logic that brings up a message at the bottom of the screen, keeps it there for one second and then sends it away. When the notification is displayed it hides other elements and that makes my test unstable. I did my best to figure out when the notification is displayed (when the business logic displays it) and dismiss it but every now and then I detect new cases my code are not aware of when the notification is displayed.
Is there a way (using Selenium) to subscribe to an event like "New element inserted in DOM". Dismissing the notification on its callback would solve my problem once and for all.
Selenium doesn't support this use case out of the box but you can achieve that using MutationObserver in javascript. I don't know what language you are using to write selenium test but in C# you can create extensions method as follow
public static void StartWatchingForContentChange(this RemoteWebDriver driver, string containerId, int timeout = SearchElementDefaultTimeout)
{
driver.ExecuteScript(#"var $$expectedId = arguments[0];
__selenium_observers__ = window.__selenium_observers__ || {};
(function(){
var target = document.getElementById($$expectedId);
__selenium_observers__[$$expectedId] = {
observer: new MutationObserver(function(mutations) {
__selenium_observers__[$$expectedId].occured = true;
__selenium_observers__[$$expectedId].observer.disconnect();
}),
occured:false
};
var config = { attributes: true, childList: true, characterData: true, subtree: true };
__selenium_observers__[$$expectedId].observer.observe(target, config);
})();", containerId);
}
public static bool WasContentChanged(this RemoteWebDriver driver, string containerId)
{
return (bool) driver.ExecuteScript( "return window.__selenium_observers__ && window.__selenium_observers__[arguments[0]].occured;", containerId)
}
You can use some kind of timer to asynchronously invoke WasContentChanged method and react for content changes. Please read MutationObserver documentation for more details https://developer.mozilla.org/pl/docs/Web/API/MutationObserver

Unable to find element and send keys

So just a brief overview, I'm unable to send keys to a edit text field for android. I've successfully sent keys to this element via browser but in order to test the mobile application fully, I'd like to run e2e tests on a device using Appium.
I've successfully got Appium to click button elements but am having a hard time getting it to send keys to an edit field element.
Am I able to find elements by model when testing with android as I have set in my forgot-pin-page.js?
pin-reset-page.js
var pinResetPage = function() {
describe('The Reset Pin Flow', function () {
forgotPinPage = forgotPinPageBuilder.getForgotPinPage(),
describe('The Forgot Pin Page', function () {
it('should allow the user to enter their MSISDN and continue',
function () {
forgotPinPage.enterMsisdn('123123123');
forgotPinPage.doForgotPin();
expect(securityPage.isOnSecurityPage()).toBe(true);
});
});
}
forgot-pin-page.js
'use strict';
var ForgotPin = function () {
var forgotPinPageContent = element(by.id('forgot')),
msisdnInput = element(by.model('data.msisdn')),
return {
enterMsisdn: function (msisdn) {
return msisdnInput.sendKeys(msisdn);
}
};
module.exports.getForgotPinPage = function () {
return new ForgotPin();
};
The error i'm getting is
? should allow the user to enter their MSISDN and continue
- Error: Timeout - Async callback was not invoked within timeout spe
cified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Not sure if this is the correct solution but it worked for me. I downgraded jasmine2 to jasmine and that seemed to resolved the async timeouts I was having.

backbone view in router lost after creation

When I try to associate my router's public variable this.currentView to a newly created view, the view gets lost, the public variable is null instead of containing the newly created view.
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
});
});
});
alert(this.currentView); //null
The fetch() calls you do are firing asynchronous AJAX requests, meaning the code in your done handlers are not going to be executed untill the server calls return. Once you've executed user.fetch() the browser will fire off a request and then continue running your program and alert this.currentView without waiting for the requests to finish.
The sequence of events is basically going to be
call user.fetch()
alert this.currentView
call watchListsCollection.fetch()
call loggedUser.fetch()
set the value of self.currentView
You will not be able to see the value of your currentView before the last server request have completed.
If you change your code to
var self=this;
var watchListsCollection = new WatchlistCollection;
watchListsCollection.url = "watchlists";
user.fetch().done(function() {
watchListsCollection.fetch().done(function () {
loggedUser.fetch().done(function () {
self.currentView = new UserView(user, watchListsCollection,loggedUser);
alertCurrentView();
});
});
});
function alertCurrentView() {
alert(this.currentView);
}
You should see the correct value displayed. Now, depending on what you intend to use your this.currentView for that might or might not let you fix whatever issue you have, but there's no way you're not going to have to wait for all the requests to complete before it's available. If you need to do something with it straight away you should create your UserView immediately and move the fetch() calls into that view's initialize().
fetch() is asynchronous, but you check your variable right after you've started your task. Probably these tasks, as they supposed to be just reads, should be run in parallel. And forget making a copy of this, try _.bind instead according to the Airbnb styleguide: https://github.com/airbnb/javascript
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
Promise.all(tasks).then(_.bind(function() {
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}, this));
or using ES6 generators:
function* () {
var tasks = [];
tasks.push(user.fetch());
tasks.push(watchListsCollection.fetch());
tasks.push(loggedUser.fetch());
yield Promise.all(tasks);
this.currentView = new UserView(user, watchListsCollection, loggedUser);
}

Youtube API event on a specified time

Is it possible through the YouTube API to fire an event when the video reaches a specified time? e.g. when the video reaches 20 sec. fire the event.
Thanks,
Mauro
not sure if you still need an answer to this (as I'm finding it 4 months later) but here's how I accomplished this with youtube's iframe embed api. It's ugly in that it requires a setInterval, but there really isn't any kind of "timeupdate" event in the YouTube API (at least not in the iframe API), so you have to kind of fake it by checking the video time every so often. It seems to run just fine.
Let's say you want to start up your player as shown here with YT.Player(), and you want to implement your own onProgress() function that is called whenever the video time changes:
In HTML:
<div id="myPlayer"></div>
In JS:
// first, load the YouTube Iframe API:
var tag = document.createElement('script');
tag.src = "//www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// some variables (global here, but they don't have to be)
var player;
var videoId = 'SomeYoutubeIdHere';
var videotime = 0;
var timeupdater = null;
// load your player when the API becomes ready
function onYoutubeIframeAPIReady() {
player = new YT.Player('myPlayer', {
width: '640',
height: '390',
videoId: videoId,
events: {
'onReady': onPlayerReady
}
});
}
// when the player is ready, start checking the current time every 100 ms.
function onPlayerReady() {
function updateTime() {
var oldTime = videotime;
if(player && player.getCurrentTime) {
videotime = player.getCurrentTime();
}
if(videotime !== oldTime) {
onProgress(videotime);
}
}
timeupdater = setInterval(updateTime, 100);
}
// when the time changes, this will be called.
function onProgress(currentTime) {
if(currentTime > 20) {
console.log("the video reached 20 seconds!");
}
}
It's a little sloppy, requiring a few global variables, but you could easily refactor it into a closure and/or make the interval stop and start itself on play/pause by also including the onStateChange event when you initialize the player, and writing an onPlayerStateChange function that checks for play and pause. You'd just need to seperate the updateTime() function from onPlayerReady, and strategically call timeupdater = setInterval(updateTime, 100); and clearInterval(timeupdater); in the right event handlers. See here for more on using events with YouTube.
I think you may have a look at popcorn.js.
It's an interesting mozilla project and it seems to solve your problem.
Not sure if anyone agrees here but I might prefer to use setTimeout() over setInterval() as it avoids using event listeners, eg:
function checkTime(){
setTimeout(function(){
videotime = getVideoTime();
if(videotime !== requiredTime) {
checkTime();//Recursive call.
}else{
//run stuff you want to at a particular time.
//do not call checkTime() again.
}
},100);
}
Its a basic pseudocode but hopefully conveys the idea.