How to measure and report execution times in WebdriverIO? - automated-tests

I would like to measure and report the time some elements appear on a webpage after a certain action. So something like
describe('Load Time', () => {
it('API Page', () => {
browser.url('https://webdriver.io');
browser.$('=API').waitForDisplayed();
// measure time from here
browser.$('=API').click();
browser.$('=Introduction').waitForDisplayed();
// until here
});
});
Approach A that I tried:
Manually measure the time and write it to the console like this:
describe('Load Time', () => {
it('API Page', () => {
browser.url('https://webdriver.io');
browser.$('=API').waitForDisplayed();
const start = process.hrtime();
browser.$('=API').click();
browser.$('=Introduction').waitForDisplayed();
const elapsedhrtime = process.hrtime(start);
const elapsedmilliseconds = (elapsedhrtime[1] / 1000000) + elapsedhrtime[0] * 1000;
console.log(elapsedmilliseconds);
});
});
However, just printing it to the console makes it difficult to parse it for later analysis, especially when having a lot of similar test cases.
Approach B that I tried:
When using the JUnit reporter, there is a time attribute for each test case. However, of course, it measures the whole execution time of the test case. One idea I had was to do this
describe('Load Time', () => {
beforeAll(() => {
browser.url('https://webdriver.io');
browser.$('=API').waitForDisplayed();
});
it('API Page', () => {
browser.$('=API').click();
browser.$('=Introduction').waitForDisplayed();
});
});
Then I get only the relevant time in the JUnit report, but this only works with beforeAll and not with beforeEach, so I can not put multiple test cases in one suite.

Related

NGRX - How to subscribe to multiple requests that update a list, but prevent it from loading multiple times on the first load?

I'm working with Angular-NGRX and i have 3 components that can modify the data of a list. In the main component I listen for all the changes to update the listing and it works. The problem is that the first load subscribes me 3 times.
This is the ngOnInit of the main component:
ngOnInit(): void {
// Change order
this.store.select(state => state.shared.orderBy).pipe(
takeUntil(this.onDestroy$),
tap(resp => {
this.orderBy = resp;
this._cmsFunctions.deselectAll('Resource');
}),
switchMap(resp => this._dynamicComponentsService.loadResources(this.itemsToShow, this.tabCounter, this.orderBy, this.filters, this.searchTerm, this.company, this.workgroup, (this.paramCode) ? this.paramCode : 0))
).subscribe();
// Change filters
this.store.select(state => state.shared.filters).pipe(
takeUntil(this.onDestroy$),
tap(resp => {
this.filters = resp;
this._cmsFunctions.deselectAll('Resource');
}),
switchMap(resp => this._dynamicComponentsService.loadResources(this.itemsToShow, this.tabCounter, this.orderBy, this.filters, this.searchTerm, this.company, this.workgroup, (this.paramCode) ? this.paramCode : 0))
).subscribe();
// Change search term
this.store.select(state => state.shared.searchTerm).pipe(
takeUntil(this.onDestroy$),
tap(resp => {
this.searchTerm = resp;
this._cmsFunctions.deselectAll('Resource');
}),
switchMap(resp => this._dynamicComponentsService.loadResources(this.itemsToShow, this.tabCounter, this.orderBy, this.filters, this.searchTerm, this.company, this.workgroup, (this.paramCode) ? this.paramCode : 0))
).subscribe();
}
And all i need is that when starting it only subscribes once:
enter image description here
How can I improve this code?
Thanks!
You shouldn't be making calls to a data service from the component, and certainly not based on selector calls. You should be dispatching an Action which is captured and handled by an Effect. You can use dataLoading and dataLoaded flags in your store, for example, to prevent multiple loads.

How to limit mergeMap inner subscriptions to the N latest or a sliding window queue

I have a source stream merged from two streams. When the source stream emit event I'd like to call a subscription function Meteor.subscribe and keep it open, so I use mergeMap. When subscription is ready I pipe to another mergeMap to populate the data. It works well until I do 100 clicks and memory consumption is skyrockets. The question is, how is it possible to limit mergeMap, not to the first N subscriptions by concurrent: Number, but to the N recent ones, like a sliding window?
function paginationCache$(): Observable<any> {
return merge(this.pageParamsChanged$, this.routerParamsChanged$)
.pipe(
mergeMap((newParams) => {
// First merge map subscribes to data and un subscribes when second merge map unsubscribes
return Observable.create((observer: Subscriber<any>) => {
let subscription = Meteor.subscribe('my/best/data/sub', newParams,
() => {
observer.next(subscription);
observer.complete();
});
});
}),
mergeMap((subscription: any) => {
// second subscription is just populating the data
return Observable.create((observer: Subscriber<Meteor.Error | any>) => {
const collection = new Mongo.Collection(subscription.collectionName);
const { selector, options } = this.mongoParams();
collection.find(selector, options).dataChanges((data) => {
observer.next({ data });
});
return () => {
subscription.stop();
};
});
})
);
}
I'd like to give more detailed explanation what happening in that code.
In my example, source stream (the merge before pipe) it's never completes as long as I click button in my web interface, so it emits changes as I click next or previous button in my interface. First mergeMap gets changes from the source stream and sends them to backend API (which also has conflicted naming publication/subscription). So when data available on the client I call observer.next(subscription) to move to the second mergeMap, but I can't destroy or stop meteor's subscription. Two reasons: 1. I'd like to get realtime changes to selected data, 2. if I stop meteor's subscription, data on the client side will be removed. So, now second mergeMap it continuously updates selected data if it was updated on the server.
So after each UI button click (next, previous) I have new chain of subscriptions. It is okey if the original data table not big (1000 records) and I just clicked couple times. But, I can have more than that 30000 and I can click my buttons many times.
So, the idea is to make mergeMap like a limited size queue that holds just last N subscriptions, but queue is changing all the time I click the button.
LAST EDIT: working code:
function paginationCache$(): Observable<any> {
const N = 3;
const subscriptionsSubject = new Subject();
return merge(this.pageParamsChanged$, this.routerParamsChanged$)
.pipe(
mergeMap((newParams) => {
// First merge map subscribes to data and un subscribes when second merge map unsubscribes
subscriptionsSubject.next();
return Observable.create((observer: Subscriber<any>) => {
let subscription = Meteor.subscribe('mu/best/data/sub', newParams,
() => {
observer.next(subscription);
observer.complete();
});
});
}),
mergeMap((subscription: any) => {
// second subscription is just populating the data
return Observable.create((observer: Subscriber<Meteor.Error | any>) => {
const collection = new Mongo.Collection(subscription.collectionName);
const { selector, options } = this.mongoParams();
collection.find(selector, options).dataChanges((data) => {
observer.next({ data });
});
return () => {
subscription.stop();
};
}).pipe(
takeUntil(subscriptionsSubject
.pipe(
take(N),
filter((_, idx) => idx === N - 1)
)
)
);
})
);
}
Without considering your snippet, here's how I'd go about this:
not to the first N subscriptions by concurrent: Number, but to the N recent ones, like a sliding window
If I understood correctly, you'd want something like this(assuming N = 3):
N = 3
Crt | 1 | 2 | 3 |
Subscriptions | S1 | S2 | S3 |
When Crt = 4
Crt | 2 | 3 | 4 |
Subscriptions | S2 | S3 | S4 |
If that's the case, here's how I'd solve it:
const subscriptionsSubject = new Subject();
src$.pipe(
mergeMap(
data => (new Observable(s => {/* ... */ subscriptionsSubject.next(null) /* Notify about a new subscription when it's the case */ }))
.pipe(
takeUntil(
subscriptionsSubject.pipe(
take(N), // After `N` subscriptions, it will complete
filter((_, idx) => idx === N - 1) // Do not want to complete immediately, but only when exactly `N` subscriptions have been reached
)
)
)
)
)
I have two ideas here:
You're not completing the second inner Observable. I guess this shouldn't be the source of your problem but it's better to complete observers if you can:
return () => {
subscription.stop();
observer.complete();
};
You can use bufferCount to make a sliding window of Observables and then subscribe to them with switchMap(). Something along these lines:
import { of, range } from 'rxjs';
import { map, bufferCount, switchMap, shareReplay, tap } from 'rxjs/operators';
range(10)
.pipe(
// turn each value to an Observable
// `refCount` is used so that when `switchMap` switches to a new window
// it won't trigger resubscribe to its sources and make more requests.
map(v => of(v).pipe(shareReplay({ refCount: false, bufferSize: 1 }))),
bufferCount(3, 1),
tap(console.log), // for debugging purposes only
switchMap(sourcesArray => merge(...sourcesArray)),
)
.subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs-kuybbs?devtoolsheight=60
I'm not completely sure this simulates your use-case but I tried to include also shareReplay so that it won't trigger multiple Meteor.subscribe calls for the same Observable. I'd have to have a working demo of your code to test it myself.

ngrx effect unit test mulitple actions in mergemap

I am using ngrx library and have an effect like this
#Effect()
loadCollection$: Observable<Action> = this.actions$
.ofType(authAction.GET_USER)
.startWith(new authAction.GetUserAction()) // call on app load
.switchMap(() =>
this.service.getUser()
.mergeMap((user: User) => [
new authAction.GetUserCompleteAction(user),
new navigationAction.GetLinksCompleteAction(user.role)
])
);
I am writing spec for it and it looks like this
actions = new ReplaySubject(2);
actions.next(new auth.GetUserAction());
effects.loadCollection$.subscribe(result => {
expect(service.getUser).toHaveBeenCalled();
expect(result).toEqual(new navigation.GetLinksCompleteAction('test')); --> this line fails
});
How can I expect that multiple actions were called in the merge map.
You can use jasmine-marbles to test conditions like mergeMap. See #ngrx/effects testing docs for examples: https://github.com/ngrx/platform/blob/master/docs/effects/testing.md
In your case the test would look something like this:
actions = hot('-a', { a: new authAction.GetUserAction() });
const expected = cold('-(bc)', { // the ( ) groups the values into the same timeframe
b: new authAction.GetUserCompleteAction({}), // put whatever mock value you have here
c: new navigationAction.GetLinksCompleteAction('test')
};
expect(effects.loadCollection$).toBeObservable(expected);
I would then split the test for checking expect(service.getUser).toHaveBeenCalled(); into a separate test case.
See https://github.com/ReactiveX/rxjs/blob/master/doc/writing-marble-tests.md for the hot/cold syntax.

Data validation using RxJS

I have the following function that validates that rangeFrom is not superior to rangeTo and that the rangeFrom does not already exist in the list of ranges.
How can rewrite this using RxJS?
const isTagAlreadyExist = (tags, currentTag) => _(tags)
.filter(x => x.id !== currentTag.id)
.some(x => _.inRange(currentTag.rangeTo, x.rangeFrom, x.rangeTo))
.value();
const validateRangeFrom = (tags, currentTag) => {
const errors = {};
if (isNumeric(currentTag.rangeFrom)) {
if (!_.inRange(currentTag.rangeFrom, 0, currentTag.rangeTo)) {
errors.rangeFrom = 'FROM_TAG_CANNOT_BE_GREATER_THAN_TO_TAG';
} else if (isTagAlreadyExist(tags, currentTag)) {
errors.rangeFrom ='TAG_ALREADY_EXISTS';
}
}
return {
errors
};
};
The question is: what parts do you want to rewrite to rxjs? Those are two pure functions that run synchronously from what I can see, I do not really see much a usecase for rxjs here - of course you could always utilize your functions within an rxjs stream:
const validateRangeFrom$ = (tags, currentTag) => {
return Observable.of(currentTag)
.map(tag => validateRangeFrom(tags, tag));
}
validateRangeFrom$(myTags, currentTag)
.subscribe(errors => console.log(errors));
But as you can see, this does not make much sense if you simply wrap it inside a stream, the essence of useful reactive programming is, that everything is reactive, not just some small parts, so for your example, you should start with having tags$ and currentTag$ as observables - let's assume that you have that, then you could do something like:
const tags$: Observable<ITag[]>... // is set somewhere, and emits a new array whenever it is changed
const currentTag$: Observable<ITag>... // is set somewhere and emits the tag whenever a new currentTag is set
const validateRangeFrom$ = Observable
.combineLatest(tags$, currentTag$, (tags, tag) => ({tags, tag}))
.map(({tags, tag}) => validateRangeFrom(tags, tag));
validateRangeFrom$.subscribe(errors => console.log(errors));
This will automatically trigger the validation for you whenever a new tags-array is emitted or a new currentTag is selected/set - but again: your validation-method is kept the same - as even in reactive programming you have to do validation and logic-operations at some point, the reactive part usually just concerns the flow of the data (see: tags$ and currentTag$)

Rxjs: add data to elements of array returned from http response

Following this question: Add data to http response using rxjs
I've tried to adapt this code to my use case where the result of the first http call yields an array instead of a value... but I can't get my head around it.
How do I write in rxjs (Typescript) the following pseudo code?
call my server
obtain an array of objects with the following properties: (external id, name)
for each object, call another server passing the external id
for each response from the external server, obtain another object and merge some of its properties into the object from my server with the same id
finally, subscribe and obtain an array of augmented objects with the following structure: (external id, name, augmented prop1, augmented prop2, ...)
So far the only thing I was able to do is:
this._appService
.getUserGames()
.subscribe(games => {
this._userGames = _.map(games, game => ({ id: game.id, externalGameId: game.externalGameId, name: game.name }));
_.forEach(this._userGames, game => {
this._externalService
.getExternalGameById(game.externalGameId)
.subscribe(externalThing => {
(<any>game).thumbnail = externalThing.thumbnail;
(<any>game).name = externalThing.name;
});
});
});
Thanks in advance
I found a way to make it work. I'll comment the code to better explain what it does, especially to myself :D
this._appService
.getUserGames() // Here we have an observable that emits only 1 value: an any[]
.mergeMap(games => _.map(games, game => this._externalService.augmentGame(game))) // Here we map the any[] to an Observable<any>[]. The external service takes the object and enriches it with more properties
.concatAll() // This will take n observables (the Observable<any>[]) and return an Observable<any> that emits n values
.toArray() // But I want a single emission of an any[], so I turn that n emissions to a single emission of an array
.subscribe(games => { ... }); // TA-DAAAAA!
Don't use subscribe. Use map instead.
Can't test it, but should look more like this:
this._appService
.getUserGames()
.map(games => {
this._userGames = _.map(games, game => ({ id: game.id, externalGameId: game.externalGameId, name: game.name }));
return this._userGames.map(game => { /* this should return an array of observables.. */
return this._externalService
.getExternalGameById(game.externalGameId)
.map(externalThing => {
(<any>game).thumbnail = externalThing.thumbnail;
(<any>game).name = externalThing.name;
return game;
});
});
})
.mergeAll()
.subscribe(xx => ...); // here you can subscribe..

Resources